code wiki / _hdl_build / nx_drv_proto_emit.nx

nx_drv_proto_emit.nx source

↩ module page · 470 lines · 25537 B

1// nx_drv_proto_emit.nx -- DRIVER-PROTOCOL-FROM-SPEC (X-DRV-W1), the GENERIC driver emitter. 2// 3// The thesis (driver-as-shape-composition): a driver SPEC carries the device register map 4// (base + offsets) AND -- crucially -- the PROTOCOL STEP-SEQUENCE itself as DATA: an op-list 5// (the STATE_MACHINE shape). This organ WALKS that op-list and SYNTHESIZES a bare-metal rv64 6// driver image whose control flow IS the spec's sequence -- so a DIFFERENT protocol sequence is 7// a DIFFERENT spec -> a DIFFERENT driver, with ZERO emitter changes. That is the emitter-of- 8// emitters keystone (X-AUT-006c/e) COMPOSED into the device-driver layer (X-DRV-W0 HWMAP gives 9// the bind side; this gives the protocol side). 10// 11// CONTRAST with nx_virtio_hs_emit: that emitter parameterises the virtio register offsets/values 12// from a spec but HARD-CODES the protocol sequence (identity->ACK->FEAT->VQ->DESC->USED->...). 13// Here the sequence is DATA, so virtio-blk is merely ONE spec instance (the last-mile/benchmark 14// transport per the nishi-ecosystem-only law); a virtio-net handshake, or a future Nishi-native 15// device protocol, is simply a different op-list. The gate proves this by emitting TWO distinct 16// op-lists from the ONE binary and running BOTH against the real devices on the sovereign emu. 17// 18// SPEC FORMAT (line-based, '#'=comment): 19// out <path> where to write the flat image (and <path>.gold) 20// base <hex> device MMIO base (0x1000-aligned -> lui t2) 21// op verify <regoff> <const> LW reg ; li const ; bne -> fail (signed read-back) 22// op verifyu <regoff> <const> LWU reg ; li const ; bne -> fail (unsigned/address read-back) 23// op write <regoff> <val> li val ; SW reg (also: notify = a write) 24// op notify <regoff> <val> alias for write (device kick) 25// op read <regoff> LW reg (exercise a RO register, value discarded) 26// op checkbit <regoff> <mask> LW reg ; li mask ; and ; beq x0 -> fail (bit-stuck proof) 27// op setbase <addr> li32u t5 = addr (membase for the following memstore ops) 28// op memstore <memoff> <f3> <val> li val ; S[f3] val, memoff(t5) (f3: 1=SH 2=SW; ring/desc lay) 29// op emit <literal-to-eol> write the literal bytes over the UART (the transcript token) 30// The image always ends with the SiFive finisher (clean halt). Any verify/checkbit that fails 31// BRANCHES PAST the rest of the op-list to the finisher, so the transcript loses its tail -- 32// that is the tamper handle the gate exploits. 33// 34// nx_drv_proto_emit <specpath> -> the spec's `out` (flat rv64 image) + <out>.gold (golden 35// transcript = the concatenation of the emit literals, in op order). 36// VERDICT log -> knowledge/status/driver_spec.log. Sovereign: syscalls only, no gcc/.sh. 37// license_tier: ORIGINAL 38import "nx_syscalls.nx" 39const DP_MAGIC_16384: i64 = 16384 40 41// ---- qemu-virt platform map (device tree as data, not magic) ---- 42const DP_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit) 43const DP_FIN: i64 = 0x100000 // SiFive test finisher (write to exit) 44const DP_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt 45// rv64 register numbers used (mirror nx_virtio_hs_emit so the encoder forms are identical) 46const RV_X0: i64 = 0 47const RV_T0: i64 = 5 // UART base 48const RV_T1: i64 = 6 // scratch / transcript byte / write value 49const RV_T2: i64 = 7 // device MMIO base 50const RV_T3: i64 = 28 // loaded register value (actual) 51const RV_T4: i64 = 29 // expected constant 52const RV_T5: i64 = 30 // finisher base / membase (set by setbase) 53 54// ---- op-list opcodes (the STATE_MACHINE shape, parsed from the spec) ---- 55const OP_VERIFY: i64 = 1 // a0=regoff a1=const (signed LW) 56const OP_VERIFYU: i64 = 2 // a0=regoff a1=const (unsigned LWU) 57const OP_WRITE: i64 = 3 // a0=regoff a1=val 58const OP_READ: i64 = 4 // a0=regoff 59const OP_CHECKBIT: i64 = 5 // a0=regoff a1=mask 60const OP_SETBASE: i64 = 6 // a0=addr (li32u t5) 61const OP_MEMSTORE: i64 = 7 // a0=memoff a1=f3 a2=val 62const OP_EMIT: i64 = 8 // a0=tail-offset a1=len 63 64const DP_MAXOP: i64 = 256 65const DP_TAILCAP: i64 = 2048 66 67// ---- the rv64 mini-encoder (one form per function; integer-only) ---- 68func dp_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 } 69func dp_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 } 70// load; f3=2 -> LW (sign-extended 32-bit), f3=6 -> LWU (zero-extended 32-bit). 71func dp_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x03 } 72// store; f3=0 -> SB, f3=1 -> SH, f3=2 -> SW. imm S-type split (signed 12-bit). 73func dp_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 { 74 let hi: i64 = ((imm >> 5) & 0x7f) << 25 75 let lo: i64 = (imm & 0x1f) << 7 76 return hi | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | lo | 0x23 77} 78// B-type branch; f3=0 -> BEQ, f3=1 -> BNE. imm in bytes (signed, multiple of 2). 79func dp_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 { 80 let b12: i64 = ((imm >> 12) & 0x1) << 31 81 let b11: i64 = ((imm >> 11) & 0x1) << 7 82 let b10_5: i64 = ((imm >> 5) & 0x3f) << 25 83 let b4_1: i64 = ((imm >> 1) & 0xf) << 8 84 return b12 | b10_5 | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | b4_1 | b11 | 0x63 85} 86// J-type jal; imm in bytes (signed, multiple of 2). 87func dp_jal(rd: i64, imm: i64) -> i64 { 88 let b20: i64 = ((imm >> 20) & 0x1) << 31 89 let b19_12: i64 = ((imm >> 12) & 0xff) << 12 90 let b11: i64 = ((imm >> 11) & 0x1) << 20 91 let b10_1: i64 = ((imm >> 1) & 0x3ff) << 21 92 return b20 | b10_1 | b11 | b19_12 | (rd << 7) | 0x6f 93} 94// R-type AND (funct3=7, funct7=0): rd = rs1 & rs2. 95func dp_and(rd: i64, rs1: i64, rs2: i64) -> i64 { return (rs2 << 20) | (rs1 << 15) | (7 << 12) | (rd << 7) | 0x33 } 96// I-type shift-immediate (OP-IMM 0x13): f3=1 -> SLLI, f3=5 -> SRLI. rv64 shamt = imm[5:0]. 97func dp_shift(rd: i64, rs1: i64, f3: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x13 } 98 99func dp_w32(buf: *u8, off: i64, w: i64) -> i64 { 100 buf[off] = (w & 0xff) as u8 101 buf[off+1] = ((w >> 8) & 0xff) as u8 102 buf[off+2] = ((w >> 16) & 0xff) as u8 103 buf[off+3] = ((w >> 24) & 0xff) as u8 104 return off + 4 105} 106 107// load a 32-bit constant into rd: lui hi + addi lo (sign-corrected). 2 words (value-independent 108// size -> two-pass byte-stability). RV64 LUI SIGN-EXTENDS from bit 31 -- and the sovereign sim does 109// too, since the 2026-07-09 nx_rv64im_imm_u fix -- so a bit-31-set constant materialises NEGATIVE 110// here. A caller comparing against a ZERO-extended read-back (LWU / `verifyu`) MUST use dp_li32u 111// instead; see dp_op_verify. (The comment that used to sit here claimed the sim did NOT sign-extend. 112// That became false on 2026-07-09 and the code depending on it broke silently for three weeks.) 113func dp_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 114 var hi: i64 = (val >> 12) & 0xFFFFF 115 var lo: i64 = val & 0xFFF 116 if lo >= 0x800 { lo = lo - 0x1000; hi = (hi + 1) & 0xFFFFF } 117 var o: i64 = dp_w32(buf, off, dp_lui(rd, hi)) 118 o = dp_w32(buf, o, dp_addi(rd, rd, lo)) 119 return o 120} 121 122// load a ZERO-EXTENDED 32-bit address into rd: li32 then slli 32 ; srli 32. 4 words. The 123// canonical rv64 idiom for a positive physical address with bit 31 set (ring/data buffers). 124func dp_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 125 var o: i64 = dp_li32(buf, off, rd, val) 126 o = dp_w32(buf, o, dp_shift(rd, rd, 1, 32)) // slli rd,rd,32 127 o = dp_w32(buf, o, dp_shift(rd, rd, 5, 32)) // srli rd,rd,32 128 return o 129} 130 131// emit n transcript bytes from s over the UART (t0 = UART base). 2 words/char. 132func dp_emit_bytes(buf: *u8, off: i64, s: *u8, n: i64) -> i64 { 133 var o: i64 = off 134 var i: i64 = 0 135 while i < n { 136 o = dp_w32(buf, o, dp_addi(RV_T1, RV_X0, s[i] as i64)) 137 o = dp_w32(buf, o, dp_store(RV_T1, RV_T0, 0, 0)) // sb t1,0(t0) 138 i = i + 1 139 } 140 return o 141} 142 143// verify reg-at-offset == expected, else jump to fail_off. f3sel picks LW (2, signed) or LWU 144// (6, unsigned). lw/lwu t3, regoff(t2) ; li t4, expected ; bne t3,t4,(fail_off-pc). 4 words. 145func dp_op_verify(buf: *u8, off: i64, regoff: i64, expected: i64, f3sel: i64, fail_off: i64) -> i64 { 146 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, f3sel, regoff)) 147 // THE EXPECTED CONSTANT MUST BE MATERIALISED IN THE SAME WIDTH THE LOAD PRODUCES. 148 // f3sel 6 = LWU -> the read-back is ZERO-extended, so the constant must be zero-extended too 149 // (dp_li32u = li32 ; slli 32 ; srli 32). f3sel 2 = LW -> sign-extended, so dp_li32 is correct. 150 // ROOT CAUSE 2026-08-07: this used dp_li32 UNCONDITIONALLY. That was sound only while the sim's 151 // LUI zero-extended. LUI was CORRECTED to sign-extend on 2026-07-09 (rv64im_min_decoder.nx 152 // nx_rv64im_imm_u, caught by the QEMU differential fuzzer), which silently made every `verifyu` 153 // against a bit-31-set constant compare a ZERO-extended load with a NEGATIVE constant -- a bne 154 // that can never be equal. drv_proto_blk_virt.spec stage 6 (verifyu 0x060 0x80003000) died there 155 // and the transcript truncated at VQ. It stayed INVISIBLE until the emu grew a golden-transcript 156 // check (2026-08-01); before that a clean finisher halt ALONE scored GREEN. 157 if f3sel == 6 { 158 o = dp_li32u(buf, o, RV_T4, expected) 159 } else { 160 o = dp_li32(buf, o, RV_T4, expected) 161 } 162 let pc: i64 = o 163 o = dp_w32(buf, o, dp_branch(RV_T3, RV_T4, 1, fail_off - pc)) 164 return o 165} 166 167// write a value into a MMIO register: li t1,val ; sw t1, regoff(t2). 3 words. 168func dp_op_write(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 { 169 var o: i64 = dp_li32(buf, off, RV_T1, val) 170 o = dp_w32(buf, o, dp_store(RV_T1, RV_T2, 2, regoff)) 171 return o 172} 173 174// read (exercise) a MMIO register into t3, value discarded: lw t3, regoff(t2). 1 word. 175func dp_op_read(buf: *u8, off: i64, regoff: i64) -> i64 { 176 return dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff)) 177} 178 179// confirm (reg & mask) != 0, else jump to fail_off: lw t3 ; li t4,mask ; and ; beq t3,x0,fail. 5 words. 180func dp_op_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 { 181 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff)) 182 o = dp_li32(buf, o, RV_T4, mask) 183 o = dp_w32(buf, o, dp_and(RV_T3, RV_T3, RV_T4)) 184 let pc: i64 = o 185 o = dp_w32(buf, o, dp_branch(RV_T3, RV_X0, 0, fail_off - pc)) 186 return o 187} 188 189// store a value into guest RAM at t5 + memoff (f3: 1=SH 2=SW): li t1,val ; s[f3] t1, memoff(t5). 3 words. 190func dp_op_memstore(buf: *u8, off: i64, memoff: i64, f3: i64, val: i64) -> i64 { 191 var o: i64 = dp_li32(buf, off, RV_T1, val) 192 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, f3, memoff)) 193 return o 194} 195 196// finisher block: li t5,FIN ; li t1,PASS ; sw t1,0(t5) -> halt ; jal x0,0 spin. 6 words = 24 bytes. 197func dp_emit_finisher(buf: *u8, off: i64) -> i64 { 198 var o: i64 = dp_li32(buf, off, RV_T5, DP_FIN) 199 o = dp_li32(buf, o, RV_T1, DP_PASS) 200 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, 2, 0)) 201 o = dp_w32(buf, o, dp_jal(RV_X0, 0)) 202 return o 203} 204 205// author the WHOLE driver image into buf by WALKING the op-list (the STATE_MACHINE shape). 206// cfg-free: every byte derives from base + the parsed ops. fail_off = absolute byte offset of 207// the finisher (measure pass passes 0; real pass passes the fixed value). Returns byte length. 208func dp_emit_image(buf: *u8, base: i64, opc: *i64, oa0: *i64, oa1: *i64, oa2: *i64, nop: i64, tail: *u8, fail_off: i64) -> i64 { 209 var o: i64 = 0 210 // boot preamble: t0 = UART base, t2 = device MMIO base (both 0x1000-aligned -> lui-only). 211 o = dp_w32(buf, o, dp_lui(RV_T0, DP_UART >> 12)) 212 o = dp_w32(buf, o, dp_lui(RV_T2, base >> 12)) 213 var i: i64 = 0 214 while i < nop { 215 let op: i64 = opc[i] 216 if op == OP_VERIFY { o = dp_op_verify(buf, o, oa0[i], oa1[i], 2, fail_off) } 217 if op == OP_VERIFYU { o = dp_op_verify(buf, o, oa0[i], oa1[i], 6, fail_off) } 218 if op == OP_WRITE { o = dp_op_write(buf, o, oa0[i], oa1[i]) } 219 if op == OP_READ { o = dp_op_read(buf, o, oa0[i]) } 220 if op == OP_CHECKBIT { o = dp_op_checkbit(buf, o, oa0[i], oa1[i], fail_off) } 221 if op == OP_SETBASE { o = dp_li32u(buf, o, RV_T5, oa0[i]) } 222 if op == OP_MEMSTORE { o = dp_op_memstore(buf, o, oa0[i], oa1[i], oa2[i]) } 223 if op == OP_EMIT { o = dp_emit_bytes(buf, o, (tail as i64 + oa0[i]) as *u8, oa1[i]) } 224 i = i + 1 225 } 226 o = dp_emit_finisher(buf, o) 227 return o 228} 229 230func dp_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 231func dp_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 } 232func dp_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 } 233 234// ---- spec parse helpers ---- 235// parse a hex (0x..) or decimal token at p in [.,le); returns value; *endp = next index. 236func dp_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 { 237 var q: i64 = p 238 var val: i64 = 0 239 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) { 240 q = q + 2 241 var go: i64 = 1 242 while go == 1 { 243 if q >= le { go = 0 } else { 244 let c: i64 = buf[q] as i64 245 var d: i64 = 0 - 1 246 if c >= 48 { if c <= 57 { d = c - 48 } } 247 if c >= 97 { if c <= 102 { d = c - 87 } } 248 if c >= 65 { if c <= 70 { d = c - 55 } } 249 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 } 250 } 251 } 252 endp[0] = q 253 return val 254 }}} 255 var go2: i64 = 1 256 while go2 == 1 { 257 if q >= le { go2 = 0 } else { 258 let c: i64 = buf[q] as i64 259 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 } 260 } 261 } 262 endp[0] = q 263 return val 264} 265 266// if line [ls,le) begins with key (a "name " prefix), parse the trailing number into out[0] and 267// return 1; else 0. (number fields: base.) 268func dp_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 { 269 var k: i64 = 0 270 while key[k] != (0 as u8) { 271 if ls + k >= le { return 0 } 272 if buf[ls + k] != key[k] { return 0 } 273 k = k + 1 274 } 275 let endp: *i64 = sys_mmap(16) as *i64 276 out[0] = dp_parse_num(buf, ls + k, le, endp) 277 return 1 278} 279 280// if line begins with key, copy the rest (minus CR) into out (NUL-terminated); return length, else -1. 281func dp_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 { 282 var k: i64 = 0 283 while key[k] != (0 as u8) { 284 if ls + k >= le { return 0 - 1 } 285 if buf[ls + k] != key[k] { return 0 - 1 } 286 k = k + 1 287 } 288 var o: i64 = 0 289 var q: i64 = ls + k 290 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } } 291 out[o] = 0 as u8 292 return o 293} 294 295// does [p..] match keyword kw FOLLOWED by a space/tab? returns index past "kw " if so, else -1. 296func dp_kw(buf: *u8, p: i64, le: i64, kw: *u8) -> i64 { 297 var k: i64 = 0 298 while kw[k] != (0 as u8) { 299 if p + k >= le { return 0 - 1 } 300 if buf[p + k] != kw[k] { return 0 - 1 } 301 k = k + 1 302 } 303 if p + k >= le { return 0 - 1 } 304 if buf[p + k] != (32 as u8) { if buf[p + k] != (9 as u8) { return 0 - 1 } } 305 return p + k + 1 306} 307 308func dp_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 { 309 let lfd: i64 = sys_openat_append("knowledge/status/driver_spec.log" as *u8, 0x1a4) 310 if lfd < 0 { return 0 - 1 } 311 dp_fp(lfd, "DRVPROTOEMIT name=" as *u8); dp_fp(lfd, name) 312 dp_fp(lfd, " keystone=driver-protocol-from-spec composes=emitter-of-emitters+HWMAP bytes=" as *u8); dp_fn(lfd, bytes) 313 dp_fp(lfd, " golden=" as *u8); dp_fp(lfd, golden) 314 dp_fp(lfd, " verdict=" as *u8); dp_fp(lfd, verdict); dp_fp(lfd, "\n" as *u8) 315 sys_close(lfd) 316 return 0 317} 318 319func main(argc: i64, argv: *i64) -> i64 { 320 if argc < 2 { dp_p("usage: nx_drv_proto_emit <specpath>\n" as *u8); sys_exit(2); return 2 } 321 let sp: *u8 = argv[1] as *u8 322 let lenp: *i64 = sys_mmap(16) as *i64 323 let spec: *u8 = sys_read_file(sp, lenp) 324 let sn: i64 = lenp[0] 325 if sn <= 0 { dp_p("DRVPROTO REFUSED: spec missing\n" as *u8); dp_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 326 327 var base: i64 = 0 - 1 328 let outp: *u8 = sys_mmap(256) 329 outp[0] = 0 as u8 330 let tmp: *i64 = sys_mmap(16) as *i64 331 332 // op-list arrays (the STATE_MACHINE shape) + the transcript-literal tail buffer. 333 let opc: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 334 let oa0: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 335 let oa1: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 336 let oa2: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 337 let tail: *u8 = sys_mmap(DP_TAILCAP) 338 var nop: i64 = 0 339 var tn: i64 = 0 340 let endp: *i64 = sys_mmap(16) as *i64 341 342 var ls: i64 = 0 343 while ls < sn { 344 var le: i64 = ls 345 var scan: i64 = 1 346 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } } 347 if spec[ls] != (35 as u8) { 348 // non-op header fields 349 if dp_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { base = tmp[0] } 350 dp_str_field(spec, ls, le, "out " as *u8, outp) 351 // op lines 352 let ostart: i64 = dp_kw(spec, ls, le, "op" as *u8) 353 if ostart >= 0 { 354 if nop >= DP_MAXOP { dp_p("DRVPROTO REFUSED: too many ops\n" as *u8); dp_log("(too-many-ops)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 355 var q: i64 = ostart 356 var matched: i64 = 0 357 // check VERIFYU before VERIFY (prefix), and the rest. 358 var nq: i64 = dp_kw(spec, q, le, "verifyu" as *u8) 359 if nq >= 0 { opc[nop]=OP_VERIFYU; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } 360 if matched == 0 { nq = dp_kw(spec, q, le, "verify" as *u8) 361 if nq >= 0 { opc[nop]=OP_VERIFY; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } } 362 if matched == 0 { nq = dp_kw(spec, q, le, "write" as *u8) 363 if nq >= 0 { opc[nop]=OP_WRITE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } } 364 if matched == 0 { nq = dp_kw(spec, q, le, "notify" as *u8) 365 if nq >= 0 { opc[nop]=OP_WRITE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } } 366 if matched == 0 { nq = dp_kw(spec, q, le, "read" as *u8) 367 if nq >= 0 { opc[nop]=OP_READ; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } } 368 if matched == 0 { nq = dp_kw(spec, q, le, "checkbit" as *u8) 369 if nq >= 0 { opc[nop]=OP_CHECKBIT; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } } 370 if matched == 0 { nq = dp_kw(spec, q, le, "setbase" as *u8) 371 if nq >= 0 { opc[nop]=OP_SETBASE; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } } 372 if matched == 0 { nq = dp_kw(spec, q, le, "memstore" as *u8) 373 if nq >= 0 { opc[nop]=OP_MEMSTORE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa2[nop]=dp_parse_num(spec,nq,le,endp); matched=1 } } 374 if matched == 0 { nq = dp_kw(spec, q, le, "emit" as *u8) 375 if nq >= 0 { 376 // capture the rest of the line verbatim (minus CR) as the transcript token. 377 var elen: i64 = 0 378 var ep: i64 = nq 379 while ep < le { if spec[ep] == (13 as u8) { ep = le } else { tail[tn + elen] = spec[ep]; elen = elen + 1; ep = ep + 1 } } 380 if elen <= 0 { dp_p("DRVPROTO REFUSED: empty emit literal\n" as *u8); dp_log("(empty-emit)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 381 opc[nop]=OP_EMIT; oa0[nop]=tn; oa1[nop]=elen; oa2[nop]=0 382 tn = tn + elen 383 matched = 1 384 } } 385 if matched == 0 { dp_p("DRVPROTO REFUSED: unknown op\n" as *u8); dp_log("(unknown-op)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 386 nop = nop + 1 387 } 388 } 389 ls = le + 1 390 } 391 392 // optional CLI overrides (X-DRV-W2 autonomous bind): argv[2] = device base discovered by the 393 // probe (0 = keep the spec's base), argv[3] = output path. The base override is what lets the 394 // driver registry BIND a spec to wherever the device was actually found -- the same blk spec 395 // brings up a blk device at any address, proving "bring up ANY iron" is data-driven, not a 396 // per-address hand-edit. (Absent args = the plain X-DRV-W1 single-spec emit, unchanged.) 397 if argc >= 3 { 398 let ov: *u8 = argv[2] as *u8 399 var ovl: i64 = 0 400 while ov[ovl] != (0 as u8) { ovl = ovl + 1 } 401 let oendp: *i64 = sys_mmap(16) as *i64 402 let base_ov: i64 = dp_parse_num(ov, 0, ovl, oendp) 403 if base_ov != 0 { base = base_ov } 404 } 405 if argc >= 4 { 406 let outov: *u8 = argv[3] as *u8 407 var oi: i64 = 0 408 while outov[oi] != (0 as u8) { outp[oi] = outov[oi]; oi = oi + 1 } 409 outp[oi] = 0 as u8 410 } 411 412 // validate (defensive at the spec boundary -- a malformed spec REFUSES, never false-greens). 413 if base == (0 - 1) { dp_p("DRVPROTO REFUSED: no base\n" as *u8); dp_log("(no-base)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 414 if (base & 0xFFF) != 0 { dp_p("DRVPROTO REFUSED: base not 0x1000-aligned\n" as *u8); dp_log("(base-align)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 415 if outp[0] == (0 as u8) { dp_p("DRVPROTO REFUSED: no out\n" as *u8); dp_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 416 if nop <= 0 { dp_p("DRVPROTO REFUSED: empty op-list\n" as *u8); dp_log("(empty-ops)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 417 418 // golden transcript = concatenation of the emit literals, in op order. 419 let golden: *u8 = sys_mmap(DP_TAILCAP) 420 var gn: i64 = 0 421 var gi: i64 = 0 422 while gi < nop { 423 if opc[gi] == OP_EMIT { 424 var c: i64 = 0 425 while c < oa1[gi] { golden[gn] = tail[oa0[gi] + c]; gn = gn + 1; c = c + 1 } 426 } 427 gi = gi + 1 428 } 429 golden[gn] = 0 as u8 430 if gn <= 0 { dp_p("DRVPROTO REFUSED: no emit op (empty golden)\n" as *u8); dp_log("(no-emit)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } 431 432 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target); pass 2 433 // re-emits with that offset baked into every fail branch. Instruction COUNT is identical 434 // across passes (every li32 is 2 words regardless of value, every branch is fixed-width), so 435 // the measured offset is stable -- byte-reproducible synthesis of the spec's state machine. ---- 436 let scratch: *u8 = sys_mmap(DP_MAGIC_16384) 437 let sz0: i64 = dp_emit_image(scratch, base, opc, oa0, oa1, oa2, nop, tail, 0) 438 let fin_len: i64 = 24 // finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words 439 let fail_off: i64 = sz0 - fin_len 440 441 let bin: *u8 = sys_mmap(DP_MAGIC_16384) 442 let sz: i64 = dp_emit_image(bin, base, opc, oa0, oa1, oa2, nop, tail, fail_off) 443 if sz != sz0 { dp_p("DRVPROTO RED: pass size mismatch\n" as *u8); dp_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 } 444 445 let ofd: i64 = sys_openat_wr(outp, 0x1a4) 446 if ofd < 0 { dp_p("DRVPROTO RED: cannot open out\n" as *u8); dp_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 } 447 sys_write(ofd, bin, sz) 448 sys_close(ofd) 449 450 // write the golden next to the image (<out>.gold) for the gate. 451 let gp: *u8 = sys_mmap(512) 452 var gpi: i64 = 0 453 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 } 454 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.' 455 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g' 456 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o' 457 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l' 458 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd' 459 gp[gpi] = 0 as u8 460 let gfd: i64 = sys_openat_wr(gp, 0x1a4) 461 if gfd >= 0 { sys_write(gfd, golden, gn); sys_close(gfd) } 462 463 dp_p("DRVPROTO GREEN: authored " as *u8); dp_p(outp); dp_p(" bytes=" as *u8); dp_fn(1, sz) 464 dp_p(" ops=" as *u8); dp_fn(1, nop) 465 dp_p(" fail_off=" as *u8); dp_fn(1, fail_off) 466 dp_p(" golden=" as *u8); dp_p(golden); dp_p(" (driver SPEC op-list in, bootable rv64 driver image out -- protocol state machine synthesized from the spec)\n" as *u8) 467 dp_log(outp, sz, golden, "GREEN") 468 sys_exit(0) 469 return 0 470}