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}