code wiki / _hdl_build / nx_drv_proto_emit.nx

nx_drv_proto_emit.nx source

↩ module page · 453 lines · 24138 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). On the SOVEREIGN sim LUI does NOT sign-extend bit 31, so a 109// bit-31-set MMIO const/address materialises positive, matching an LWU read-back (verifyu). 110func dp_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 111 var hi: i64 = (val >> 12) & 0xFFFFF 112 var lo: i64 = val & 0xFFF 113 if lo >= 0x800 { lo = lo - 0x1000; hi = (hi + 1) & 0xFFFFF } 114 var o: i64 = dp_w32(buf, off, dp_lui(rd, hi)) 115 o = dp_w32(buf, o, dp_addi(rd, rd, lo)) 116 return o 117} 118 119// load a ZERO-EXTENDED 32-bit address into rd: li32 then slli 32 ; srli 32. 4 words. The 120// canonical rv64 idiom for a positive physical address with bit 31 set (ring/data buffers). 121func dp_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 122 var o: i64 = dp_li32(buf, off, rd, val) 123 o = dp_w32(buf, o, dp_shift(rd, rd, 1, 32)) // slli rd,rd,32 124 o = dp_w32(buf, o, dp_shift(rd, rd, 5, 32)) // srli rd,rd,32 125 return o 126} 127 128// emit n transcript bytes from s over the UART (t0 = UART base). 2 words/char. 129func dp_emit_bytes(buf: *u8, off: i64, s: *u8, n: i64) -> i64 { 130 var o: i64 = off 131 var i: i64 = 0 132 while i < n { 133 o = dp_w32(buf, o, dp_addi(RV_T1, RV_X0, s[i] as i64)) 134 o = dp_w32(buf, o, dp_store(RV_T1, RV_T0, 0, 0)) // sb t1,0(t0) 135 i = i + 1 136 } 137 return o 138} 139 140// verify reg-at-offset == expected, else jump to fail_off. f3sel picks LW (2, signed) or LWU 141// (6, unsigned). lw/lwu t3, regoff(t2) ; li t4, expected ; bne t3,t4,(fail_off-pc). 4 words. 142func dp_op_verify(buf: *u8, off: i64, regoff: i64, expected: i64, f3sel: i64, fail_off: i64) -> i64 { 143 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, f3sel, regoff)) 144 o = dp_li32(buf, o, RV_T4, expected) 145 let pc: i64 = o 146 o = dp_w32(buf, o, dp_branch(RV_T3, RV_T4, 1, fail_off - pc)) 147 return o 148} 149 150// write a value into a MMIO register: li t1,val ; sw t1, regoff(t2). 3 words. 151func dp_op_write(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 { 152 var o: i64 = dp_li32(buf, off, RV_T1, val) 153 o = dp_w32(buf, o, dp_store(RV_T1, RV_T2, 2, regoff)) 154 return o 155} 156 157// read (exercise) a MMIO register into t3, value discarded: lw t3, regoff(t2). 1 word. 158func dp_op_read(buf: *u8, off: i64, regoff: i64) -> i64 { 159 return dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff)) 160} 161 162// confirm (reg & mask) != 0, else jump to fail_off: lw t3 ; li t4,mask ; and ; beq t3,x0,fail. 5 words. 163func dp_op_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 { 164 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff)) 165 o = dp_li32(buf, o, RV_T4, mask) 166 o = dp_w32(buf, o, dp_and(RV_T3, RV_T3, RV_T4)) 167 let pc: i64 = o 168 o = dp_w32(buf, o, dp_branch(RV_T3, RV_X0, 0, fail_off - pc)) 169 return o 170} 171 172// store a value into guest RAM at t5 + memoff (f3: 1=SH 2=SW): li t1,val ; s[f3] t1, memoff(t5). 3 words. 173func dp_op_memstore(buf: *u8, off: i64, memoff: i64, f3: i64, val: i64) -> i64 { 174 var o: i64 = dp_li32(buf, off, RV_T1, val) 175 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, f3, memoff)) 176 return o 177} 178 179// finisher block: li t5,FIN ; li t1,PASS ; sw t1,0(t5) -> halt ; jal x0,0 spin. 6 words = 24 bytes. 180func dp_emit_finisher(buf: *u8, off: i64) -> i64 { 181 var o: i64 = dp_li32(buf, off, RV_T5, DP_FIN) 182 o = dp_li32(buf, o, RV_T1, DP_PASS) 183 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, 2, 0)) 184 o = dp_w32(buf, o, dp_jal(RV_X0, 0)) 185 return o 186} 187 188// author the WHOLE driver image into buf by WALKING the op-list (the STATE_MACHINE shape). 189// cfg-free: every byte derives from base + the parsed ops. fail_off = absolute byte offset of 190// the finisher (measure pass passes 0; real pass passes the fixed value). Returns byte length. 191func dp_emit_image(buf: *u8, base: i64, opc: *i64, oa0: *i64, oa1: *i64, oa2: *i64, nop: i64, tail: *u8, fail_off: i64) -> i64 { 192 var o: i64 = 0 193 // boot preamble: t0 = UART base, t2 = device MMIO base (both 0x1000-aligned -> lui-only). 194 o = dp_w32(buf, o, dp_lui(RV_T0, DP_UART >> 12)) 195 o = dp_w32(buf, o, dp_lui(RV_T2, base >> 12)) 196 var i: i64 = 0 197 while i < nop { 198 let op: i64 = opc[i] 199 if op == OP_VERIFY { o = dp_op_verify(buf, o, oa0[i], oa1[i], 2, fail_off) } 200 if op == OP_VERIFYU { o = dp_op_verify(buf, o, oa0[i], oa1[i], 6, fail_off) } 201 if op == OP_WRITE { o = dp_op_write(buf, o, oa0[i], oa1[i]) } 202 if op == OP_READ { o = dp_op_read(buf, o, oa0[i]) } 203 if op == OP_CHECKBIT { o = dp_op_checkbit(buf, o, oa0[i], oa1[i], fail_off) } 204 if op == OP_SETBASE { o = dp_li32u(buf, o, RV_T5, oa0[i]) } 205 if op == OP_MEMSTORE { o = dp_op_memstore(buf, o, oa0[i], oa1[i], oa2[i]) } 206 if op == OP_EMIT { o = dp_emit_bytes(buf, o, (tail as i64 + oa0[i]) as *u8, oa1[i]) } 207 i = i + 1 208 } 209 o = dp_emit_finisher(buf, o) 210 return o 211} 212 213func 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 } 214func 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 } 215func dp_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; 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 } 216 217// ---- spec parse helpers ---- 218// parse a hex (0x..) or decimal token at p in [.,le); returns value; *endp = next index. 219func dp_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 { 220 var q: i64 = p 221 var val: i64 = 0 222 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) { 223 q = q + 2 224 var go: i64 = 1 225 while go == 1 { 226 if q >= le { go = 0 } else { 227 let c: i64 = buf[q] as i64 228 var d: i64 = 0 - 1 229 if c >= 48 { if c <= 57 { d = c - 48 } } 230 if c >= 97 { if c <= 102 { d = c - 87 } } 231 if c >= 65 { if c <= 70 { d = c - 55 } } 232 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 } 233 } 234 } 235 endp[0] = q 236 return val 237 }}} 238 var go2: i64 = 1 239 while go2 == 1 { 240 if q >= le { go2 = 0 } else { 241 let c: i64 = buf[q] as i64 242 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 } 243 } 244 } 245 endp[0] = q 246 return val 247} 248 249// if line [ls,le) begins with key (a "name " prefix), parse the trailing number into out[0] and 250// return 1; else 0. (number fields: base.) 251func dp_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 { 252 var k: i64 = 0 253 while key[k] != (0 as u8) { 254 if ls + k >= le { return 0 } 255 if buf[ls + k] != key[k] { return 0 } 256 k = k + 1 257 } 258 let endp: *i64 = sys_mmap(16) as *i64 259 out[0] = dp_parse_num(buf, ls + k, le, endp) 260 return 1 261} 262 263// if line begins with key, copy the rest (minus CR) into out (NUL-terminated); return length, else -1. 264func dp_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 { 265 var k: i64 = 0 266 while key[k] != (0 as u8) { 267 if ls + k >= le { return 0 - 1 } 268 if buf[ls + k] != key[k] { return 0 - 1 } 269 k = k + 1 270 } 271 var o: i64 = 0 272 var q: i64 = ls + k 273 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } } 274 out[o] = 0 as u8 275 return o 276} 277 278// does [p..] match keyword kw FOLLOWED by a space/tab? returns index past "kw " if so, else -1. 279func dp_kw(buf: *u8, p: i64, le: i64, kw: *u8) -> i64 { 280 var k: i64 = 0 281 while kw[k] != (0 as u8) { 282 if p + k >= le { return 0 - 1 } 283 if buf[p + k] != kw[k] { return 0 - 1 } 284 k = k + 1 285 } 286 if p + k >= le { return 0 - 1 } 287 if buf[p + k] != (32 as u8) { if buf[p + k] != (9 as u8) { return 0 - 1 } } 288 return p + k + 1 289} 290 291func dp_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 { 292 let lfd: i64 = sys_openat_append("knowledge/status/driver_spec.log" as *u8, 0x1a4) 293 if lfd < 0 { return 0 - 1 } 294 dp_fp(lfd, "DRVPROTOEMIT name=" as *u8); dp_fp(lfd, name) 295 dp_fp(lfd, " keystone=driver-protocol-from-spec composes=emitter-of-emitters+HWMAP bytes=" as *u8); dp_fn(lfd, bytes) 296 dp_fp(lfd, " golden=" as *u8); dp_fp(lfd, golden) 297 dp_fp(lfd, " verdict=" as *u8); dp_fp(lfd, verdict); dp_fp(lfd, "\n" as *u8) 298 sys_close(lfd) 299 return 0 300} 301 302func main(argc: i64, argv: *i64) -> i64 { 303 if argc < 2 { dp_p("usage: nx_drv_proto_emit <specpath>\n" as *u8); sys_exit(2); return 2 } 304 let sp: *u8 = argv[1] as *u8 305 let lenp: *i64 = sys_mmap(16) as *i64 306 let spec: *u8 = sys_read_file(sp, lenp) 307 let sn: i64 = lenp[0] 308 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 } 309 310 var base: i64 = 0 - 1 311 let outp: *u8 = sys_mmap(256) 312 outp[0] = 0 as u8 313 let tmp: *i64 = sys_mmap(16) as *i64 314 315 // op-list arrays (the STATE_MACHINE shape) + the transcript-literal tail buffer. 316 let opc: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 317 let oa0: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 318 let oa1: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 319 let oa2: *i64 = sys_mmap(8 * DP_MAXOP) as *i64 320 let tail: *u8 = sys_mmap(DP_TAILCAP) 321 var nop: i64 = 0 322 var tn: i64 = 0 323 let endp: *i64 = sys_mmap(16) as *i64 324 325 var ls: i64 = 0 326 while ls < sn { 327 var le: i64 = ls 328 var scan: i64 = 1 329 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } } 330 if spec[ls] != (35 as u8) { 331 // non-op header fields 332 if dp_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { base = tmp[0] } 333 dp_str_field(spec, ls, le, "out " as *u8, outp) 334 // op lines 335 let ostart: i64 = dp_kw(spec, ls, le, "op" as *u8) 336 if ostart >= 0 { 337 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 } 338 var q: i64 = ostart 339 var matched: i64 = 0 340 // check VERIFYU before VERIFY (prefix), and the rest. 341 var nq: i64 = dp_kw(spec, q, le, "verifyu" as *u8) 342 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 } 343 if matched == 0 { nq = dp_kw(spec, q, le, "verify" as *u8) 344 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 } } 345 if matched == 0 { nq = dp_kw(spec, q, le, "write" as *u8) 346 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 } } 347 if matched == 0 { nq = dp_kw(spec, q, le, "notify" as *u8) 348 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 } } 349 if matched == 0 { nq = dp_kw(spec, q, le, "read" as *u8) 350 if nq >= 0 { opc[nop]=OP_READ; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } } 351 if matched == 0 { nq = dp_kw(spec, q, le, "checkbit" as *u8) 352 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 } } 353 if matched == 0 { nq = dp_kw(spec, q, le, "setbase" as *u8) 354 if nq >= 0 { opc[nop]=OP_SETBASE; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } } 355 if matched == 0 { nq = dp_kw(spec, q, le, "memstore" as *u8) 356 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 } } 357 if matched == 0 { nq = dp_kw(spec, q, le, "emit" as *u8) 358 if nq >= 0 { 359 // capture the rest of the line verbatim (minus CR) as the transcript token. 360 var elen: i64 = 0 361 var ep: i64 = nq 362 while ep < le { if spec[ep] == (13 as u8) { ep = le } else { tail[tn + elen] = spec[ep]; elen = elen + 1; ep = ep + 1 } } 363 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 } 364 opc[nop]=OP_EMIT; oa0[nop]=tn; oa1[nop]=elen; oa2[nop]=0 365 tn = tn + elen 366 matched = 1 367 } } 368 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 } 369 nop = nop + 1 370 } 371 } 372 ls = le + 1 373 } 374 375 // optional CLI overrides (X-DRV-W2 autonomous bind): argv[2] = device base discovered by the 376 // probe (0 = keep the spec's base), argv[3] = output path. The base override is what lets the 377 // driver registry BIND a spec to wherever the device was actually found -- the same blk spec 378 // brings up a blk device at any address, proving "bring up ANY iron" is data-driven, not a 379 // per-address hand-edit. (Absent args = the plain X-DRV-W1 single-spec emit, unchanged.) 380 if argc >= 3 { 381 let ov: *u8 = argv[2] as *u8 382 var ovl: i64 = 0 383 while ov[ovl] != (0 as u8) { ovl = ovl + 1 } 384 let oendp: *i64 = sys_mmap(16) as *i64 385 let base_ov: i64 = dp_parse_num(ov, 0, ovl, oendp) 386 if base_ov != 0 { base = base_ov } 387 } 388 if argc >= 4 { 389 let outov: *u8 = argv[3] as *u8 390 var oi: i64 = 0 391 while outov[oi] != (0 as u8) { outp[oi] = outov[oi]; oi = oi + 1 } 392 outp[oi] = 0 as u8 393 } 394 395 // validate (defensive at the spec boundary -- a malformed spec REFUSES, never false-greens). 396 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 } 397 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 } 398 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 } 399 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 } 400 401 // golden transcript = concatenation of the emit literals, in op order. 402 let golden: *u8 = sys_mmap(DP_TAILCAP) 403 var gn: i64 = 0 404 var gi: i64 = 0 405 while gi < nop { 406 if opc[gi] == OP_EMIT { 407 var c: i64 = 0 408 while c < oa1[gi] { golden[gn] = tail[oa0[gi] + c]; gn = gn + 1; c = c + 1 } 409 } 410 gi = gi + 1 411 } 412 golden[gn] = 0 as u8 413 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 } 414 415 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target); pass 2 416 // re-emits with that offset baked into every fail branch. Instruction COUNT is identical 417 // across passes (every li32 is 2 words regardless of value, every branch is fixed-width), so 418 // the measured offset is stable -- byte-reproducible synthesis of the spec's state machine. ---- 419 let scratch: *u8 = sys_mmap(DP_MAGIC_16384) 420 let sz0: i64 = dp_emit_image(scratch, base, opc, oa0, oa1, oa2, nop, tail, 0) 421 let fin_len: i64 = 24 // finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words 422 let fail_off: i64 = sz0 - fin_len 423 424 let bin: *u8 = sys_mmap(DP_MAGIC_16384) 425 let sz: i64 = dp_emit_image(bin, base, opc, oa0, oa1, oa2, nop, tail, fail_off) 426 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 } 427 428 let ofd: i64 = sys_openat_wr(outp, 0x1a4) 429 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 } 430 sys_write(ofd, bin, sz) 431 sys_close(ofd) 432 433 // write the golden next to the image (<out>.gold) for the gate. 434 let gp: *u8 = sys_mmap(512) 435 var gpi: i64 = 0 436 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 } 437 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.' 438 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g' 439 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o' 440 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l' 441 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd' 442 gp[gpi] = 0 as u8 443 let gfd: i64 = sys_openat_wr(gp, 0x1a4) 444 if gfd >= 0 { sys_write(gfd, golden, gn); sys_close(gfd) } 445 446 dp_p("DRVPROTO GREEN: authored " as *u8); dp_p(outp); dp_p(" bytes=" as *u8); dp_fn(1, sz) 447 dp_p(" ops=" as *u8); dp_fn(1, nop) 448 dp_p(" fail_off=" as *u8); dp_fn(1, fail_off) 449 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) 450 dp_log(outp, sz, golden, "GREEN") 451 sys_exit(0) 452 return 0 453}