code wiki / _hdl_build / nx_drvgen.nx

nx_drvgen.nx source

↩ module page · 349 lines · 20331 B

1// SUPERSEDED 2026-06-13 (no-sprawl consolidation): the CANONICAL driver-from-spec emitter is the 2// op-list-driven nx_drv_proto_emit.nx -- there a NEW device class is a NEW op-list SPEC with ZERO 3// emitter change (the protocol step-sequence is DATA). nx_drvgen hardcodes the NVMe stage ORDERING 4// in code (a per-class emitter), which the X-DRV-W1 council flagged as a generality dilution. NVMe 5// is now carried as an op-list spec: knowledge/specs/nvme_oplist_virt.spec, gate-proven by 6// _nvme_oplist_gate (knowledge/status/driver_spec.log::NVMEOPGATE verdict=GREEN). nx_drvgen + 7// nx_driver_shapes are KEPT as the regression reference (NVMEGATE stage=B), not extended. New work 8// -> add an op-list spec to nx_drv_proto_emit, NOT a new bespoke emitter. 9// nx_drvgen.nx -- the GENERIC driver-image author for the 2nd device class (X-DRV-W1 stage B). 10// A NEW device class IS A SPEC FILE: nx_drvgen reads an NVMe-class controller spec 11// (knowledge/specs/nvme_admin_enable_virt.spec) and AUTHORS a bare-metal rv64 flat image whose 12// driver performs the REAL NVMe controller bring-up by routing EVERY byte through the SAME 13// device-agnostic shape library (nx_driver_shapes: WIRE_TLV / COMMAND_QUEUE / STATE_MACHINE / 14// STRUCT_WALK) that re-emits the virtio image byte-identically in PROOF-1. The shape library 15// knows ZERO NVMe -- the controller identity, offsets, queue bases, SQE/CQE layout, and the 16// phase-tag KAT all arrive from the spec. So the NVMe driver's distinctness comes from spec 17// DATA, not from any virtio code path. 18// 19// The driver the image runs (NVMe 1.4 base spec bring-up subset): 20// verify VS == 0x00010400 (1.4.0) -> emit "VIO-NVME " 21// write CC.EN=1, POLL CSTS until RDY set (STATE_MACHINE poll loop) -> emit "EN " <-- a REAL backward branch (the structural fingerprint virtio's straight-line handshake lacks) 22// bind AQA + ASQ/ACQ base-address regs, read ASQ_LO back + verify -> emit "RDY " 23// lay the 64-byte SQE (opcode/CID/CDW10) into ASQ_base (STRUCT_WALK) -> emit "ASQ " 24// ring the SQ-Tail doorbell (the device posts the phase-tagged CQE) -> emit "SQE " 25// POLL the CQE phase bit in ACQ RAM, read NvmeCqPeek back + verify -> emit "CQ\n" <-- a SECOND backward branch (the CQE phase-tag poll); the verify is the tamper canary 26// write the SiFive finisher -> clean halt. 27// Any verify that fails BRANCHES PAST the rest of the bring-up straight to the finisher, so the 28// transcript loses its tail (the " CQ" canary, etc.) -- the gate's tamper test exploits this. 29// nx_drvgen <specpath> -> writes the flat image to the spec's `out` + the golden to <out>.gold 30// VERDICT log -> knowledge/status/virtio_blk.log (DRVGEN rows). Sovereign: syscalls only, 31// no gcc/.sh. NVMe/PCI is a last-mile interop PROBE + measuring stick to EXCEED, never a 32// substrate -- the emitted driver + shapes are Nishi-native. license_tier: ORIGINAL 33import "nx_syscalls.nx" 34import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 35import "nx_driver_shapes.nx" 36const NV_MAGIC_8192: i64 = 8192 37 38// rv64 register numbers (mirror the shape library's calling convention). 39const NV_X0: i64 = 0 40const NV_T2: i64 = 7 // device base 41const NV_T3: i64 = 28 // loaded register value (actual) 42const NV_T5: i64 = 30 // struct-base scratch (ASQ / ACQ guest addr) 43 44// ---- cfg array layout (spec-derived; filled in main, threaded to the emitter) ---- 45const NC_BASE: i64 = 0 46const NC_OFF_CAP: i64 = 1 47const NC_OFF_VS: i64 = 2 48const NC_OFF_CC: i64 = 3 49const NC_OFF_CSTS: i64 = 4 50const NC_OFF_AQA: i64 = 5 51const NC_OFF_ASQ_LO: i64 = 6 52const NC_OFF_ASQ_HI: i64 = 7 53const NC_OFF_ACQ_LO: i64 = 8 54const NC_OFF_ACQ_HI: i64 = 9 55const NC_OFF_SQ0TDBL: i64 = 10 56const NC_OFF_CQ0HDBL: i64 = 11 57const NC_OFF_CQPEEK: i64 = 12 58const NC_VS_EXPECTED: i64 = 13 59const NC_CC_EN: i64 = 14 60const NC_CSTS_RDY_MASK: i64 = 15 61const NC_ASQ_ADDR: i64 = 16 62const NC_ACQ_ADDR: i64 = 17 63const NC_AQA_VAL: i64 = 18 64const NC_SQE_OPCODE: i64 = 19 65const NC_SQE_CID: i64 = 20 66const NC_SQE_OFF_OPCODE:i64 = 21 67const NC_SQE_OFF_CID: i64 = 22 68const NC_SQE_OFF_CDW10: i64 = 23 69const NC_SQE_CDW10: i64 = 24 70const NC_CQE_OFF_CID: i64 = 25 71const NC_CQE_OFF_STATUS:i64 = 26 72const NC_CQE_PHASE_BIT: i64 = 27 73const NC_CQE_CID_EXP: i64 = 28 74const NC_CQE_STATUS_EXP:i64 = 29 75const NC_CQPEEK_EXP: i64 = 30 76const NC_N: i64 = 31 77 78func nv_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 79func nv_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 80// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 81// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 82// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 83// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 84func nv_fn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 85func nv_hex(fd: i64, v: i64) -> i64 { let d: *u8=sys_mmap(16); var i: i64=0; while i<8{let nib: i64=(v >> ((7-i)*4)) & 0xf; if nib<10{d[i]=(48+nib) as u8} else {d[i]=(87+nib) as u8} i=i+1}; sys_write(fd,d,8); return 0 } 86 87// ---- author the whole NVMe bring-up image into buf; return byte length. cfg holds the 88// spec-derived offsets/constants; segs[6*2]=(start,len) for the 6 transcript segments; tail = 89// the segment bytes back-to-back. fail_off = the absolute byte offset where the finisher block 90// begins (the measure pass passes 0; the real pass passes the fixed value). EVERY emit goes 91// through the device-agnostic shape library -- the only NVMe knowledge is in cfg[] (spec data). ---- 92func nv_emit_image(buf: *u8, cfg: *i64, tail: *u8, segs: *i64, fail_off: i64) -> i64 { 93 let base: i64 = cfg[NC_BASE] 94 var o: i64 = 0 95 // boot: t0 = UART base, t2 = NVMe device base (both exact * 0x1000 -> lui-only). 96 o = ds_w32(buf, o, ds_lui(DS_T0, DS_UART >> 12)) 97 o = ds_w32(buf, o, ds_lui(DS_T2, base >> 12)) 98 // stage 0 (WIRE_TLV): verify VS == 1.4.0 (controller identity). 99 o = ds_emit_verify(buf, o, cfg[NC_OFF_VS], cfg[NC_VS_EXPECTED], fail_off) 100 o = ds_emit_str(buf, o, tail + segs[0], segs[1]) // "VIO-NVME " 101 // stage 1 (STATE_MACHINE poll): write CC.EN=1, poll CSTS until RDY set. This emits a REAL 102 // backward branch (a poll loop) -- the structural distinctness from virtio's straight-line 103 // forward-only handshake (constant-lifting a forward-only image cannot add a loop). 104 o = ds_emit_poll_until_set(buf, o, cfg[NC_OFF_CC], cfg[NC_CC_EN], cfg[NC_OFF_CSTS], cfg[NC_CSTS_RDY_MASK]) 105 o = ds_emit_str(buf, o, tail + segs[2], segs[3]) // "EN " 106 // stage 2 (COMMAND_QUEUE): bind AQA + the ASQ/ACQ 64-bit base-address registers, then read 107 // ASQ_LO back and verify it == asq_addr (the queue-binding proof, unsigned read-back). 108 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_AQA], cfg[NC_AQA_VAL]) 109 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ASQ_LO], cfg[NC_ASQ_ADDR]) 110 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ASQ_HI], 0) 111 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ACQ_LO], cfg[NC_ACQ_ADDR]) 112 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ACQ_HI], 0) 113 o = ds_emit_verify_u(buf, o, cfg[NC_OFF_ASQ_LO], cfg[NC_ASQ_ADDR], fail_off) // ASQ_LO read-back 114 o = ds_emit_str(buf, o, tail + segs[4], segs[5]) // "RDY " 115 // stage 3 (STRUCT_WALK): lay the 64-byte SQE into ASQ_base. t5 = ASQ guest address 116 // (zero-extended positive); store opcode(sb @+0), CID(sh @+2), CDW10(sw @+40). 117 o = ds_li32u(buf, o, NV_T5, cfg[NC_ASQ_ADDR]) 118 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_OPCODE], 0, cfg[NC_SQE_OPCODE]) // sb opcode 119 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_CID], 1, cfg[NC_SQE_CID]) // sh cid 120 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_CDW10], 2, cfg[NC_SQE_CDW10]) // sw cdw10 121 o = ds_emit_str(buf, o, tail + segs[6], segs[7]) // "ASQ " 122 // stage 4 (COMMAND_QUEUE kick): ring the SQ-Tail doorbell. The doorbell lives at base+0x1000 123 // (FAR -- beyond the 12-bit sw immediate), so the driver materialises the absolute address and 124 // stores at offset 0 (ds_emit_regwrite_far). The device fetches the SQE, executes the admin 125 // command, posts the phase-tagged CQE into ACQ, and latches NvmeCqPeek. 126 o = ds_emit_regwrite_far(buf, o, base + cfg[NC_OFF_SQ0TDBL], 1) 127 o = ds_emit_str(buf, o, tail + segs[8], segs[9]) // "SQE " 128 // stage 5 (STATE_MACHINE poll + WIRE_TLV verify): poll the CQE phase bit in ACQ guest RAM 129 // (a SECOND backward branch), then read NvmeCqPeek back (at base+0x1008, FAR) and verify == 130 // cqpeek_expected (the 2nd-class round-trip binding proof). A mismatch branches to the 131 // finisher, dropping ONLY the " CQ" canary (the "...ASQ SQE" tail survives) -- CqPeek tamper. 132 o = ds_li32u(buf, o, NV_T5, cfg[NC_ACQ_ADDR]) 133 o = ds_emit_poll_mem_until_set(buf, o, NV_T5, cfg[NC_CQE_OFF_STATUS], cfg[NC_CQE_PHASE_BIT]) 134 o = ds_emit_verify_far(buf, o, base + cfg[NC_OFF_CQPEEK], cfg[NC_CQPEEK_EXP], fail_off) // CqPeek read-back 135 o = ds_emit_str(buf, o, tail + segs[10], segs[11]) // "CQ\n" 136 o = ds_emit_finisher(buf, o) 137 return o 138} 139 140// ---- spec parse helpers (mirror nx_virtio_hs_emit's field parser) ---- 141func nv_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 { 142 var q: i64 = p 143 var val: i64 = 0 144 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) { 145 q = q + 2 146 var go: i64 = 1 147 while go == 1 { 148 if q >= le { go = 0 } else { 149 let c: i64 = buf[q] as i64 150 var d: i64 = 0 - 1 151 if c >= 48 { if c <= 57 { d = c - 48 } } 152 if c >= 97 { if c <= 102 { d = c - 87 } } 153 if c >= 65 { if c <= 70 { d = c - 55 } } 154 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 } 155 } 156 } 157 endp[0] = q 158 return val 159 }}} 160 var go2: i64 = 1 161 while go2 == 1 { 162 if q >= le { go2 = 0 } else { 163 let c: i64 = buf[q] as i64 164 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 } 165 } 166 } 167 endp[0] = q 168 return val 169} 170 171func nv_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 { 172 var k: i64 = 0 173 while key[k] != (0 as u8) { 174 if ls + k >= le { return 0 } 175 if buf[ls + k] != key[k] { return 0 } 176 k = k + 1 177 } 178 let endp: *i64 = sys_mmap(16) as *i64 179 out[0] = nv_parse_num(buf, ls + k, le, endp) 180 return 1 181} 182 183func nv_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 { 184 var k: i64 = 0 185 while key[k] != (0 as u8) { 186 if ls + k >= le { return 0 - 1 } 187 if buf[ls + k] != key[k] { return 0 - 1 } 188 k = k + 1 189 } 190 var o: i64 = 0 191 var q: i64 = ls + k 192 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } } 193 out[o] = 0 as u8 194 return o 195} 196 197func nv_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 { 198 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4) 199 if lfd < 0 { return 0 - 1 } 200 nv_fp(lfd, "DRVGEN name=" as *u8); nv_fp(lfd, name) 201 nv_fp(lfd, " class=nvme transport=pci-mmio bytes=" as *u8); nv_fn(lfd, bytes) 202 nv_fp(lfd, " golden=" as *u8); nv_fp(lfd, golden) 203 nv_fp(lfd, " verdict=" as *u8); nv_fp(lfd, verdict); nv_fp(lfd, "\n" as *u8) 204 sys_close(lfd) 205 return 0 206} 207 208func main(argc: i64, argv: *i64) -> i64 { 209 if argc < 2 { nv_p("usage: nx_drvgen <specpath>\n" as *u8); sys_exit(2); return 2 } 210 let sp: *u8 = argv[1] as *u8 211 let lenp: *i64 = sys_mmap(16) as *i64 212 let spec: *u8 = sys_read_file(sp, lenp) 213 let sn: i64 = lenp[0] 214 if sn <= 0 { nv_p("DRVGEN REFUSED: spec missing\n" as *u8); nv_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 215 216 let cfg: *i64 = sys_mmap(8 * NC_N) as *i64 217 var ci: i64 = 0 218 while ci < NC_N { cfg[ci] = 0 - 1; ci = ci + 1 } // -1 = unset sentinel 219 let banner: *u8 = sys_mmap(256) 220 let outp: *u8 = sys_mmap(256) 221 banner[0] = 0 as u8 222 outp[0] = 0 as u8 223 let tmp: *i64 = sys_mmap(16) as *i64 224 225 var ls: i64 = 0 226 while ls < sn { 227 var le: i64 = ls 228 var scan: i64 = 1 229 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } } 230 if spec[ls] != (35 as u8) { 231 if nv_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { cfg[NC_BASE] = tmp[0] } 232 if nv_num_field(spec, ls, le, "off_cap " as *u8, tmp) == 1 { cfg[NC_OFF_CAP] = tmp[0] } 233 if nv_num_field(spec, ls, le, "off_vs " as *u8, tmp) == 1 { cfg[NC_OFF_VS] = tmp[0] } 234 if nv_num_field(spec, ls, le, "off_cc " as *u8, tmp) == 1 { cfg[NC_OFF_CC] = tmp[0] } 235 if nv_num_field(spec, ls, le, "off_csts " as *u8, tmp) == 1 { cfg[NC_OFF_CSTS] = tmp[0] } 236 if nv_num_field(spec, ls, le, "off_aqa " as *u8, tmp) == 1 { cfg[NC_OFF_AQA] = tmp[0] } 237 if nv_num_field(spec, ls, le, "off_asq_lo " as *u8, tmp) == 1 { cfg[NC_OFF_ASQ_LO] = tmp[0] } 238 if nv_num_field(spec, ls, le, "off_asq_hi " as *u8, tmp) == 1 { cfg[NC_OFF_ASQ_HI] = tmp[0] } 239 if nv_num_field(spec, ls, le, "off_acq_lo " as *u8, tmp) == 1 { cfg[NC_OFF_ACQ_LO] = tmp[0] } 240 if nv_num_field(spec, ls, le, "off_acq_hi " as *u8, tmp) == 1 { cfg[NC_OFF_ACQ_HI] = tmp[0] } 241 if nv_num_field(spec, ls, le, "off_sq0tdbl " as *u8, tmp) == 1 { cfg[NC_OFF_SQ0TDBL] = tmp[0] } 242 if nv_num_field(spec, ls, le, "off_cq0hdbl " as *u8, tmp) == 1 { cfg[NC_OFF_CQ0HDBL] = tmp[0] } 243 if nv_num_field(spec, ls, le, "off_cqpeek " as *u8, tmp) == 1 { cfg[NC_OFF_CQPEEK] = tmp[0] } 244 if nv_num_field(spec, ls, le, "vs_expected " as *u8, tmp) == 1 { cfg[NC_VS_EXPECTED] = tmp[0] } 245 if nv_num_field(spec, ls, le, "cc_en " as *u8, tmp) == 1 { cfg[NC_CC_EN] = tmp[0] } 246 if nv_num_field(spec, ls, le, "csts_rdy_mask " as *u8, tmp) == 1 { cfg[NC_CSTS_RDY_MASK] = tmp[0] } 247 if nv_num_field(spec, ls, le, "asq_addr " as *u8, tmp) == 1 { cfg[NC_ASQ_ADDR] = tmp[0] } 248 if nv_num_field(spec, ls, le, "acq_addr " as *u8, tmp) == 1 { cfg[NC_ACQ_ADDR] = tmp[0] } 249 if nv_num_field(spec, ls, le, "aqa_val " as *u8, tmp) == 1 { cfg[NC_AQA_VAL] = tmp[0] } 250 if nv_num_field(spec, ls, le, "sqe_opcode " as *u8, tmp) == 1 { cfg[NC_SQE_OPCODE] = tmp[0] } 251 if nv_num_field(spec, ls, le, "sqe_cid " as *u8, tmp) == 1 { cfg[NC_SQE_CID] = tmp[0] } 252 if nv_num_field(spec, ls, le, "sqe_off_opcode " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_OPCODE] = tmp[0] } 253 if nv_num_field(spec, ls, le, "sqe_off_cid " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_CID] = tmp[0] } 254 if nv_num_field(spec, ls, le, "sqe_off_cdw10 " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_CDW10] = tmp[0] } 255 if nv_num_field(spec, ls, le, "sqe_cdw10 " as *u8, tmp) == 1 { cfg[NC_SQE_CDW10] = tmp[0] } 256 if nv_num_field(spec, ls, le, "cqe_off_cid " as *u8, tmp) == 1 { cfg[NC_CQE_OFF_CID] = tmp[0] } 257 if nv_num_field(spec, ls, le, "cqe_off_status " as *u8, tmp) == 1 { cfg[NC_CQE_OFF_STATUS] = tmp[0] } 258 if nv_num_field(spec, ls, le, "cqe_phase_bit " as *u8, tmp) == 1 { cfg[NC_CQE_PHASE_BIT] = tmp[0] } 259 if nv_num_field(spec, ls, le, "cqe_cid_expected " as *u8, tmp) == 1 { cfg[NC_CQE_CID_EXP] = tmp[0] } 260 if nv_num_field(spec, ls, le, "cqe_status_expected " as *u8, tmp) == 1 { cfg[NC_CQE_STATUS_EXP] = tmp[0] } 261 if nv_num_field(spec, ls, le, "cqpeek_expected " as *u8, tmp) == 1 { cfg[NC_CQPEEK_EXP] = tmp[0] } 262 nv_str_field(spec, ls, le, "banner " as *u8, banner) 263 nv_str_field(spec, ls, le, "out " as *u8, outp) 264 } 265 ls = le + 1 266 } 267 268 // validate: every cfg field must have parsed (the spec is a single complete source). 269 var bad: i64 = 0 270 var vi: i64 = 0 271 while vi < NC_N { if cfg[vi] == (0 - 1) { bad = 1 } vi = vi + 1 } 272 if bad == 1 { nv_p("DRVGEN REFUSED: spec missing a required field\n" as *u8); nv_log("(bad-spec)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 273 var blen: i64 = 0 274 while banner[blen] != (0 as u8) { blen = blen + 1 } 275 if blen <= 0 { nv_p("DRVGEN REFUSED: no banner row\n" as *u8); nv_log("(no-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 276 if outp[0] == (0 as u8) { nv_p("DRVGEN REFUSED: no out row\n" as *u8); nv_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 277 278 // ---- table-compute the 6 transcript SEGMENTS from the banner (split on spaces; each token 279 // gets a trailing space except the last, which gets a newline) -- golden = banner + '\n'. ---- 280 let NV_EXPECT_SEGS: i64 = 6 281 let tail: *u8 = sys_mmap(256) 282 let segs: *i64 = sys_mmap(8 * 12) as *i64 283 var tn: i64 = 0 284 var seg_idx: i64 = 0 285 var seg_start: i64 = 0 286 var bp: i64 = 0 287 while bp < blen { 288 if banner[bp] == (32 as u8) { 289 tail[tn] = 32 as u8; tn = tn + 1 290 segs[seg_idx * 2] = seg_start 291 segs[seg_idx * 2 + 1] = tn - seg_start 292 seg_idx = seg_idx + 1 293 seg_start = tn 294 bp = bp + 1 295 } else { 296 tail[tn] = banner[bp]; tn = tn + 1 297 bp = bp + 1 298 } 299 } 300 tail[tn] = 10 as u8; tn = tn + 1 301 segs[seg_idx * 2] = seg_start 302 segs[seg_idx * 2 + 1] = tn - seg_start 303 seg_idx = seg_idx + 1 304 tail[tn] = 0 as u8 305 if seg_idx != NV_EXPECT_SEGS { nv_p("DRVGEN REFUSED: banner token count mismatch, expected " as *u8); nv_fn(1, NV_EXPECT_SEGS); nv_p(" got " as *u8); nv_fn(1, seg_idx); nv_p("\n" as *u8); nv_log("(bad-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 306 307 let golden: *u8 = sys_mmap(256) 308 var gi: i64 = 0 309 while gi < tn { golden[gi] = tail[gi]; gi = gi + 1 } 310 golden[gi] = 0 as u8 311 312 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target); pass 313 // 2 re-emits with that offset baked into every fail branch. Instruction COUNT is identical 314 // across passes (only immediates differ), so the measured offset is stable -- byte-reproducible. ---- 315 let scratch: *u8 = sys_mmap(NV_MAGIC_8192) 316 let sz0: i64 = nv_emit_image(scratch, cfg, tail, segs, 0) 317 let fin_len: i64 = 24 // ds_emit_finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words = 24 bytes 318 let fail_off: i64 = sz0 - fin_len 319 320 let bin: *u8 = sys_mmap(NV_MAGIC_8192) 321 let sz: i64 = nv_emit_image(bin, cfg, tail, segs, fail_off) 322 if sz != sz0 { nv_p("DRVGEN RED: pass size mismatch\n" as *u8); nv_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 } 323 324 let ofd: i64 = sys_openat_wr(outp, 0x1a4) 325 if ofd < 0 { nv_p("DRVGEN RED: cannot open out\n" as *u8); nv_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 } 326 sys_write(ofd, bin, sz) 327 sys_close(ofd) 328 329 // write the golden transcript next to the image (<out>.gold) for the gate. 330 let gp: *u8 = sys_mmap(512) 331 var gpi: i64 = 0 332 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 } 333 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.' 334 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g' 335 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o' 336 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l' 337 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd' 338 gp[gpi] = 0 as u8 339 let gfd: i64 = sys_openat_wr(gp, 0x1a4) 340 if gfd >= 0 { sys_write(gfd, golden, gi); sys_close(gfd) } 341 342 nv_p("DRVGEN GREEN: authored " as *u8); nv_p(outp); nv_p(" bytes=" as *u8); nv_fn(1, sz) 343 nv_p(" base=0x" as *u8); nv_hex(1, cfg[NC_BASE]) 344 nv_p(" fail_off=" as *u8); nv_fn(1, fail_off) 345 nv_p(" golden=" as *u8); nv_p(golden); nv_p(" (spec in, bootable rv64 NVMe-class bring-up image out, all bytes through nx_driver_shapes)\n" as *u8) 346 nv_log(outp, sz, golden, "GREEN" as *u8) 347 sys_exit(0) 348 return 0 349}