code wiki / _hdl_build / nx_virtio_hs_emit.nx
nx_virtio_hs_emit.nx source
↩ module page · 589 lines · 39031 B
1// nx_virtio_hs_emit.nx -- the virtio-MMIO HANDSHAKE emitter (K-R2-001a of the kernel-up
2// ladder). A NEW transport bring-up IS A SPEC FILE: reads the legacy virtio-MMIO
3// register block (base + offsets), the device identity (magic/version/device-id), the
4// status-progression bits, and the banner from knowledge/specs/virtio_blk_handshake_virt
5// .spec and AUTHORS a bare-metal rv64 flat image whose driver performs the REAL device-
6// initialization handshake (virtio v1.2 sec 3.1):
7// verify MagicValue == "virt", Version, DeviceID == 2 (block) -> emit "VIO "
8// Status |= ACKNOWLEDGE | DRIVER -> emit "ACK "
9// read HostFeatures, write GuestFeatures, Status |= FEATURES_OK -> emit "DRV "
10// re-read Status, confirm FEATURES_OK stuck -> emit "FEAT "
11// Status |= DRIVER_OK -> emit "OK\n"
12// write the SiFive finisher -> clean halt.
13// Any verify that fails BRANCHES PAST the rest of the handshake straight to the finisher,
14// so the transcript loses its tail (the DRIVER_OK "OK\n" canary, and the segments before
15// it) -- that is what the gate's tamper test exploits. Zero hand-written machine code:
16// the emitter is a tiny rv64 encoder (lui/addi/lw/sw/sb + B-type bne/beq + R-type and +
17// jal) and the IMAGE + the GOLDEN transcript are TABLE-COMPUTED from the spec. The image
18// is authored in TWO passes (a measure pass that fixes the finisher byte-offset, then the
19// real pass that backfills the fail-branch targets) so every forward branch is
20// structurally derived, never typed -- byte-reproducibly.
21// nx_virtio_hs_emit <specpath> -> writes the flat image to the spec's `out` and the
22// table-computed golden transcript to <out>.gold
23// VERDICT log -> knowledge/status/virtio_blk.log (VIRTEMIT rows, the gate's evidence).
24// Sovereign: syscalls only, no gcc/.sh. license_tier: ORIGINAL
25//
26// X-DRV-W1 stage A refactor: the rv64 mini-encoder + the four shape-op families (WIRE_TLV /
27// COMMAND_QUEUE / STATE_MACHINE / STRUCT_WALK) now live in the DEVICE-AGNOSTIC nx_driver_shapes
28// library, and every vh_* encoder/emit function below DELEGATES to its ds_* twin. The virtio
29// identity lives ONLY in the spec-derived cfg[] table threaded into vh_emit_image -- the shape
30// library contains zero virtio knowledge. The op bodies are byte-for-byte the same code path as
31// before (a thin pass-through), so this emitter re-emits its image BYTE-IDENTICALLY (the stage-A
32// regression guard the gate proves).
33import "nx_syscalls.nx"
34import "nx_driver_shapes.nx"
35const VH_MAGIC_8192: i64 = 8192
36
37// ---- qemu-virt platform map (device tree as data, not magic) ----
38const VH_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit)
39const VH_FIN: i64 = 0x100000 // SiFive test finisher (write to exit)
40const VH_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt
41// rv64 register numbers used
42const RV_X0: i64 = 0
43const RV_T0: i64 = 5 // UART base
44const RV_T1: i64 = 6 // scratch / transcript byte
45const RV_T2: i64 = 7 // device base
46const RV_T3: i64 = 28 // loaded register value (actual)
47const RV_T4: i64 = 29 // expected constant
48const RV_T5: i64 = 30 // finisher base / feature scratch
49
50// ---- cfg array layout (spec-derived; filled in main, threaded to the emitter) ----
51const VC_OFF_MAGIC: i64 = 0
52const VC_OFF_VERSION: i64 = 1
53const VC_OFF_DEVICEID: i64 = 2
54const VC_OFF_VENDORID: i64 = 3
55const VC_OFF_HOSTFEAT: i64 = 4
56const VC_OFF_GUESTFEAT: i64 = 5
57const VC_OFF_STATUS: i64 = 6
58const VC_MAGIC: i64 = 7
59const VC_VERSION: i64 = 8
60const VC_DEVICEID: i64 = 9
61const VC_HOSTFEAT: i64 = 10
62const VC_ACK: i64 = 11
63const VC_DRIVER: i64 = 12
64const VC_FEATOK: i64 = 13
65const VC_DRIVEROK: i64 = 14
66const VC_BASE: i64 = 15
67// ---- K-R2-001b1 queue-config offsets + values ----
68const VC_OFF_QSEL: i64 = 16
69const VC_OFF_QNUMMAX: i64 = 17
70const VC_OFF_QNUM: i64 = 18
71const VC_OFF_QALIGN: i64 = 19
72const VC_OFF_QPFN: i64 = 20
73const VC_OFF_QNOTIFY: i64 = 21
74const VC_QSEL: i64 = 22
75const VC_QALIGN: i64 = 23
76const VC_QNUM: i64 = 24
77const VC_QPFN: i64 = 25
78const VC_QNOTIFY: i64 = 26
79// VC_QEND = exclusive upper bound of the queue-config (b1) layer; the qcfg all-or-nothing
80// detection scans [VC_BASE+1, VC_QEND). The descriptor-DMA (b2a) layer fields follow and
81// are detected over their own range [VC_QEND, VC_N) so a b1 spec stays b1 (no false descdma).
82const VC_QEND: i64 = 27
83// ---- K-R2-001b2a descriptor-DMA offsets + values ----
84const VC_OFF_QDESCPEEK: i64 = 27
85const VC_OFF_DESC_ADDR: i64 = 28
86const VC_OFF_DESC_LEN: i64 = 29
87const VC_OFF_DESC_FLAGS:i64 = 30
88const VC_OFF_DESC_NEXT: i64 = 31
89const VC_RING_ADDR: i64 = 32
90const VC_DESC_ADDR_LO: i64 = 33
91const VC_DESC_LEN: i64 = 34
92const VC_DESC_FLAGS: i64 = 35
93const VC_DESC_NEXT: i64 = 36
94// VC_DEND = exclusive upper bound of the descriptor-DMA (b2a) layer; the descdma all-or-
95// nothing detection scans [VC_QEND, VC_DEND). The avail/used (b2b) layer fields follow and
96// are detected over their own range [VC_DEND, VC_N) so a b2a spec stays b2a (no false b2b).
97const VC_DEND: i64 = 37
98// ---- K-R2-001b2b avail-consume + used-ring writeback offsets + values ----
99const VC_OFF_QUSEDIDXPEEK: i64 = 37
100const VC_AVAIL_RING_OFF: i64 = 38
101const VC_AVAIL_IDX_OFF: i64 = 39
102const VC_USED_RING_OFF: i64 = 40
103const VC_USED_IDX_OFF: i64 = 41
104const VC_AVAIL_HEAD: i64 = 42
105const VC_AVAIL_IDX_VAL: i64 = 43
106const VC_USED_IDX_EXP: i64 = 44
107// VC_DEND2 = exclusive upper bound of the avail/used (b2b) layer; the availused all-or-
108// nothing detection scans [VC_DEND, VC_DEND2). The status-byte (b2c) layer fields follow
109// and are detected over their own range [VC_DEND2, VC_N) so a b2b spec stays b2b (no false
110// b2c) -- the layer-isolated detection that preserves backward-compat.
111const VC_DEND2: i64 = 45
112// ---- K-R2-001b2c status-byte writeback offsets + values ----
113const VC_OFF_QSTATPEEK: i64 = 45
114const VC_STAT_BUF_ADDR: i64 = 46
115const VC_STAT_BUF_LO: i64 = 47
116const VC_BLK_S_OK: i64 = 48
117const VC_STAT_EXPECTED: i64 = 49
118// VC_DEND3 = exclusive upper bound of the status-byte (b2c) layer; the status all-or-nothing
119// detection scans [VC_DEND2, VC_DEND3). The sector-data (b3) layer fields follow and are
120// detected over their own range [VC_DEND3, VC_N) so a b2c spec stays b2c (no false b3) -- the
121// layer-isolated detection that preserves backward-compat.
122const VC_DEND3: i64 = 50
123// ---- K-R2-001b3 sector-data round-trip offsets + values ----
124const VC_OFF_QSECTPEEK: i64 = 50
125const VC_OFF_DATA_SECTOR: i64 = 51
126const VC_SECT_PATTERN: i64 = 52
127const VC_SECT_DATA_EXP: i64 = 53
128const VC_N: i64 = 54
129
130func vh_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
131func vh_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 }
132func vh_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 }
133func vh_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 }
134
135// ---- the rv64 mini-encoder + the emit-op shapes now live in nx_driver_shapes (device-agnostic).
136// Every vh_* below is a THIN DELEGATOR to its ds_* twin so the emitter routes ALL byte emission
137// through the shared shape library while staying byte-identical (the ds_* bodies are the exact
138// former vh_* code). The virtio identity stays in cfg[] (spec data), never in the shape ops. ----
139func vh_w32(buf: *u8, off: i64, w: i64) -> i64 { return ds_w32(buf, off, w) }
140func vh_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { return ds_li32(buf, off, rd, val) }
141func vh_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { return ds_li32u(buf, off, rd, val) }
142func vh_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ds_load(rd, rs1, f3, imm) }
143func vh_emit_str(buf: *u8, off: i64, s: *u8, n: i64) -> i64 { return ds_emit_str(buf, off, s, n) }
144// WIRE_TLV
145func vh_emit_verify(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 { return ds_emit_verify(buf, off, regoff, expected, fail_off) }
146func vh_emit_verify_u(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 { return ds_emit_verify_u(buf, off, regoff, expected, fail_off) }
147func vh_emit_regwrite(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 { return ds_emit_regwrite(buf, off, regoff, val) }
148func vh_emit_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 { return ds_emit_checkbit(buf, off, regoff, mask, fail_off) }
149// STRUCT_WALK
150func vh_emit_memstore(buf: *u8, off: i64, base_reg: i64, memoff: i64, f3: i64, val: i64) -> i64 { return ds_emit_memstore(buf, off, base_reg, memoff, f3, val) }
151// finisher
152func vh_emit_finisher(buf: *u8, off: i64) -> i64 { return ds_emit_finisher(buf, off) }
153
154// author the whole handshake image into buf; return byte length. cfg holds the spec-
155// derived offsets/constants; segs[5*2]=(start,len) for the transcript segments; tail =
156// the segment bytes back-to-back. fail_off = the absolute byte offset where the finisher
157// block begins (the measure pass passes 0; the real pass passes the fixed value).
158// stage_mode selects how far the driver walks: 0 = handshake only (001a, 5 tokens), 1 =
159// + queue-config (001b1, 6 tokens, "VQ"), 2 = + descriptor-DMA (001b2a, 7 tokens, "DESC").
160func vh_emit_image(buf: *u8, cfg: *i64, tail: *u8, segs: *i64, fail_off: i64, stage_mode: i64) -> i64 {
161 let base: i64 = cfg[VC_BASE]
162 var o: i64 = 0
163 // boot: t0 = UART base, t2 = device base (both exact * 0x1000 -> lui-only). Routed through
164 // the shape library's ds_lui (DS_T0/DS_T2/DS_UART are the same hardware facts as RV_T0/RV_T2/
165 // VH_UART) so the boot prologue is also shape-library-emitted -- byte-identical.
166 o = vh_w32(buf, o, ds_lui(DS_T0, DS_UART >> 12))
167 o = vh_w32(buf, o, ds_lui(DS_T2, base >> 12))
168 // stage 0: verify identity (magic, version, device-id)
169 o = vh_emit_verify(buf, o, cfg[VC_OFF_MAGIC], cfg[VC_MAGIC], fail_off)
170 o = vh_emit_verify(buf, o, cfg[VC_OFF_VERSION], cfg[VC_VERSION], fail_off)
171 o = vh_emit_verify(buf, o, cfg[VC_OFF_DEVICEID], cfg[VC_DEVICEID], fail_off)
172 o = vh_emit_str(buf, o, tail + segs[0], segs[1]) // "VIO "
173 // stage 1: Status |= ACK | DRIVER
174 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_STATUS], cfg[VC_ACK])
175 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_STATUS], cfg[VC_DRIVER])
176 o = vh_emit_str(buf, o, tail + segs[2], segs[3]) // "ACK "
177 // stage 2: read HostFeatures (exercised), write GuestFeatures, Status |= FEATURES_OK
178 o = vh_w32(buf, o, vh_load(RV_T3, RV_T2, 2, cfg[VC_OFF_HOSTFEAT]))
179 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_GUESTFEAT], cfg[VC_HOSTFEAT])
180 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_STATUS], cfg[VC_FEATOK])
181 o = vh_emit_str(buf, o, tail + segs[4], segs[5]) // "DRV "
182 // stage 3: re-read Status, confirm FEATURES_OK stuck
183 o = vh_emit_checkbit(buf, o, cfg[VC_OFF_STATUS], cfg[VC_FEATOK], fail_off)
184 o = vh_emit_str(buf, o, tail + segs[6], segs[7]) // "FEAT "
185 // stage 4: Status |= DRIVER_OK, emit the DRIVER_OK segment. Handshake-only (001a): this
186 // is "OK\n" (the last token). Queue-config (001b1): this is "OK " and stage 5 follows.
187 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_STATUS], cfg[VC_DRIVEROK])
188 o = vh_emit_str(buf, o, tail + segs[8], segs[9]) // "OK " / "OK\n"
189 // stage 5 (K-R2-001b1, only when the queue-config layer is present): virtqueue config.
190 // Read QueueNumMax (exercised), select queue 0, latch the negotiated ring size +
191 // alignment, bind the guest ring page-frame via QueuePFN, kick QueueNotify, then READ
192 // QueuePFN BACK and verify it equals what was written (the binding proof). A mismatch
193 // branches to the finisher, dropping the "VQ\n" canary -- the gate's QueuePFN tamper.
194 if stage_mode >= 1 {
195 o = vh_w32(buf, o, vh_load(RV_T3, RV_T2, 2, cfg[VC_OFF_QNUMMAX]))
196 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QSEL], cfg[VC_QSEL])
197 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QNUM], cfg[VC_QNUM])
198 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QALIGN], cfg[VC_QALIGN])
199 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QPFN], cfg[VC_QPFN])
200 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QNOTIFY], cfg[VC_QNOTIFY])
201 o = vh_emit_verify(buf, o, cfg[VC_OFF_QPFN], cfg[VC_QPFN], fail_off) // PFN read-back
202 o = vh_emit_str(buf, o, tail + segs[10], segs[11]) // "VQ " / "VQ\n"
203 }
204 // stage 6 (K-R2-001b2a, only when the descriptor-DMA layer is present): lay ONE legacy
205 // virtio descriptor into the QueuePFN-bound guest ring page, kick QueueNotify to make the
206 // device DMA-read it, then READ QueueDescPeek (the device's latched copy of the first
207 // descriptor field) and verify it == the addr-low word the driver wrote -- the descriptor-
208 // DMA binding proof. t5 holds the ring page guest address; the descriptor fields are
209 // stored at off_desc_addr/len/flags/next. A mismatch branches to the finisher, dropping
210 // ONLY the "DESC\n" canary (the b1 "VQ" tail survives) -- the gate's QueueDescPeek tamper.
211 if stage_mode >= 2 {
212 o = vh_li32u(buf, o, RV_T5, cfg[VC_RING_ADDR]) // t5 = ring page addr (zero-extended, positive)
213 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_OFF_DESC_ADDR], 2, cfg[VC_DESC_ADDR_LO]) // sw addr-lo
214 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_OFF_DESC_LEN], 2, cfg[VC_DESC_LEN]) // sw len
215 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_OFF_DESC_FLAGS], 1, cfg[VC_DESC_FLAGS]) // sh flags
216 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_OFF_DESC_NEXT], 1, cfg[VC_DESC_NEXT]) // sh next
217 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QNOTIFY], cfg[VC_QNOTIFY]) // kick -> device DMA
218 o = vh_emit_verify_u(buf, o, cfg[VC_OFF_QDESCPEEK], cfg[VC_DESC_ADDR_LO], fail_off) // DescPeek read-back (unsigned addr)
219 o = vh_emit_str(buf, o, tail + segs[12], segs[13]) // "DESC\n"
220 // b3 (K-R2-001b3): the sector-data round-trip needs the driver to place the sector
221 // PATTERN into the descriptor's data buffer (the guest address desc.addr points at)
222 // BEFORE the b2b kick (stage 7), because the device reads it back on THAT kick's
223 // writeback path. So when the b3 layer is present we write the pattern here, right
224 // after laying the descriptor (the data buffer = desc_addr_lo, zero-extended positive).
225 // Guarded by stage_mode >= 5 so b2a/b2b/b2c images stay byte-identical (no extra store).
226 if stage_mode >= 5 {
227 o = vh_li32u(buf, o, RV_T5, cfg[VC_DESC_ADDR_LO]) // t5 = data buffer addr (positive)
228 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_OFF_DATA_SECTOR], 2, cfg[VC_SECT_PATTERN]) // sw sector pattern
229 }
230 }
231 // stage 7 (K-R2-001b2b, only when the avail/used layer is present): publish the AVAILABLE
232 // ring (avail.ring[0] = descriptor head, avail.idx = 1) into the QueuePFN-bound ring page,
233 // kick QueueNotify a SECOND time so the device consumes the avail head, walks the
234 // descriptor (b2a), WRITES the used ring (used.ring[0].id/.len) + BUMPS used.idx back into
235 // guest RAM, then READ QueueUsedIdxPeek (the device's latched copy of used.idx) and verify
236 // it == used_idx_expected -- the used-ring writeback binding proof. t5 is re-loaded with
237 // the ring page guest address (zero-extended, positive). avail.ring[0] + avail.idx are
238 // 16-bit fields -> sh stores. A mismatch branches to the finisher, dropping ONLY the
239 // "USED\n" canary (the b2a "...DESC" tail survives) -- the gate's QueueUsedIdxPeek tamper.
240 if stage_mode >= 3 {
241 o = vh_li32u(buf, o, RV_T5, cfg[VC_RING_ADDR]) // t5 = ring page addr (positive)
242 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_AVAIL_RING_OFF], 1, cfg[VC_AVAIL_HEAD]) // sh avail.ring[0] = head
243 o = vh_emit_memstore(buf, o, RV_T5, cfg[VC_AVAIL_IDX_OFF], 1, cfg[VC_AVAIL_IDX_VAL]) // sh avail.idx = 1 (publish)
244 o = vh_emit_regwrite(buf, o, cfg[VC_OFF_QNOTIFY], cfg[VC_QNOTIFY]) // kick -> device consumes + used writeback
245 o = vh_emit_verify(buf, o, cfg[VC_OFF_QUSEDIDXPEEK], cfg[VC_USED_IDX_EXP], fail_off) // UsedIdxPeek read-back (small int)
246 o = vh_emit_str(buf, o, tail + segs[14], segs[15]) // "USED\n"
247 }
248 // stage 8 (K-R2-001b2c, only when the status-byte layer is present): the b2b kick already
249 // made the device DMA-WRITE the virtio-blk request status byte (VIRTIO_BLK_S_OK) into the
250 // status descriptor's guest buffer + latch it into QueueStatPeek, so the driver does NOT
251 // lay the status buffer (the device wrote it). It simply READS QueueStatPeek back and
252 // verifies it == stat_expected (0 = OK) -- the status-byte writeback binding proof. The
253 // read-back uses LWU (the canary load the gate's tamper scans for); a mismatch branches to
254 // the finisher, dropping ONLY the "STAT\n" canary (the b2b "...USED" tail survives) -- the
255 // gate's QueueStatPeek tamper. The forward fail-branch target (fail_off) is structurally
256 // derived by the two-pass measure/backfill, same as every earlier stage.
257 if stage_mode >= 4 {
258 o = vh_emit_verify_u(buf, o, cfg[VC_OFF_QSTATPEEK], cfg[VC_STAT_EXPECTED], fail_off) // StatPeek read-back (lwu, OK byte)
259 o = vh_emit_str(buf, o, tail + segs[16], segs[17]) // "STAT\n"
260 }
261 // stage 9 (K-R2-001b3, only when the sector-data layer is present): the driver already
262 // wrote the sector PATTERN into the descriptor's data buffer in stage 6 (before the b2b
263 // kick), and on that kick the device FOLLOWED desc[head].addr into guest RAM, DMA-read the
264 // first data word, and latched it into QueueSectPeek. So the driver does NOT touch the data
265 // buffer here -- it simply READS QueueSectPeek back (lwu, the canary load the gate's tamper
266 // scans for) and verifies it == sect_data_expected -- the sector-data round-trip binding
267 // proof (REAL sector DATA went guest-buffer -> device-DMA -> latched -> driver read-back).
268 // A mismatch branches to the finisher, dropping ONLY the " SECT" canary (the "...USED STAT"
269 // tail survives) -- the gate's QueueSectPeek tamper. fail_off is structurally derived by the
270 // two-pass measure/backfill, same as every earlier stage.
271 if stage_mode >= 5 {
272 o = vh_emit_verify_u(buf, o, cfg[VC_OFF_QSECTPEEK], cfg[VC_SECT_DATA_EXP], fail_off) // SectPeek read-back (lwu, sector word)
273 o = vh_emit_str(buf, o, tail + segs[18], segs[19]) // "SECT\n"
274 }
275 o = vh_emit_finisher(buf, o)
276 return o
277}
278
279// ---- spec parse helpers (mirror the K-R0/K-R1 emitter field parser) ----
280// parse a hex (0x..) or decimal token starting at p in [.,le); returns value; *endp=next.
281func vh_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 {
282 var q: i64 = p
283 var val: i64 = 0
284 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) {
285 // hex 0x..
286 q = q + 2
287 var go: i64 = 1
288 while go == 1 {
289 if q >= le { go = 0 } else {
290 let c: i64 = buf[q] as i64
291 var d: i64 = 0 - 1
292 if c >= 48 { if c <= 57 { d = c - 48 } }
293 if c >= 97 { if c <= 102 { d = c - 87 } }
294 if c >= 65 { if c <= 70 { d = c - 55 } }
295 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 }
296 }
297 }
298 endp[0] = q
299 return val
300 }}}
301 // decimal
302 var go2: i64 = 1
303 while go2 == 1 {
304 if q >= le { go2 = 0 } else {
305 let c: i64 = buf[q] as i64
306 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 }
307 }
308 }
309 endp[0] = q
310 return val
311}
312
313// if line [ls,le) begins with key (a "name " prefix), parse the trailing number into
314// out[0] and return 1; else return 0.
315func vh_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 {
316 var k: i64 = 0
317 while key[k] != (0 as u8) {
318 if ls + k >= le { return 0 }
319 if buf[ls + k] != key[k] { return 0 }
320 k = k + 1
321 }
322 let endp: *i64 = sys_mmap(16) as *i64
323 out[0] = vh_parse_num(buf, ls + k, le, endp)
324 return 1
325}
326
327// if line begins with "banner ", copy the rest (minus CR) into out; return length, else -1.
328func vh_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 {
329 var k: i64 = 0
330 while key[k] != (0 as u8) {
331 if ls + k >= le { return 0 - 1 }
332 if buf[ls + k] != key[k] { return 0 - 1 }
333 k = k + 1
334 }
335 var o: i64 = 0
336 var q: i64 = ls + k
337 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } }
338 out[o] = 0 as u8
339 return o
340}
341
342func vh_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 {
343 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4)
344 if lfd < 0 { return 0 - 1 }
345 vh_fp(lfd, "VIRTEMIT name=" as *u8); vh_fp(lfd, name)
346 vh_fp(lfd, " machine=virt transport=legacy-mmio bytes=" as *u8); vh_fn(lfd, bytes)
347 vh_fp(lfd, " golden=" as *u8); vh_fp(lfd, golden)
348 vh_fp(lfd, " verdict=" as *u8); vh_fp(lfd, verdict); vh_fp(lfd, "\n" as *u8)
349 sys_close(lfd)
350 return 0
351}
352
353func main(argc: i64, argv: *i64) -> i64 {
354 if argc < 2 { vh_p("usage: nx_virtio_hs_emit <specpath>\n" as *u8); sys_exit(2); return 2 }
355 let sp: *u8 = argv[1] as *u8
356 let lenp: *i64 = sys_mmap(16) as *i64
357 let spec: *u8 = sys_read_file(sp, lenp)
358 let sn: i64 = lenp[0]
359 if sn <= 0 { vh_p("VIRT-EMIT REFUSED: spec missing\n" as *u8); vh_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
360
361 let cfg: *i64 = sys_mmap(8 * VC_N) as *i64
362 var ci: i64 = 0
363 while ci < VC_N { cfg[ci] = 0 - 1; ci = ci + 1 } // -1 = unset sentinel
364 let banner: *u8 = sys_mmap(256)
365 let outp: *u8 = sys_mmap(256)
366 banner[0] = 0 as u8
367 outp[0] = 0 as u8
368 let tmp: *i64 = sys_mmap(16) as *i64
369
370 var ls: i64 = 0
371 while ls < sn {
372 var le: i64 = ls
373 var scan: i64 = 1
374 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
375 if spec[ls] != (35 as u8) {
376 if vh_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { cfg[VC_BASE] = tmp[0] }
377 if vh_num_field(spec, ls, le, "off_magic " as *u8, tmp) == 1 { cfg[VC_OFF_MAGIC] = tmp[0] }
378 if vh_num_field(spec, ls, le, "off_version " as *u8, tmp) == 1 { cfg[VC_OFF_VERSION] = tmp[0] }
379 if vh_num_field(spec, ls, le, "off_deviceid " as *u8, tmp) == 1 { cfg[VC_OFF_DEVICEID] = tmp[0] }
380 if vh_num_field(spec, ls, le, "off_vendorid " as *u8, tmp) == 1 { cfg[VC_OFF_VENDORID] = tmp[0] }
381 if vh_num_field(spec, ls, le, "off_hostfeatures " as *u8, tmp) == 1 { cfg[VC_OFF_HOSTFEAT] = tmp[0] }
382 if vh_num_field(spec, ls, le, "off_guestfeatures " as *u8, tmp) == 1 { cfg[VC_OFF_GUESTFEAT] = tmp[0] }
383 if vh_num_field(spec, ls, le, "off_status " as *u8, tmp) == 1 { cfg[VC_OFF_STATUS] = tmp[0] }
384 if vh_num_field(spec, ls, le, "magic " as *u8, tmp) == 1 { cfg[VC_MAGIC] = tmp[0] }
385 if vh_num_field(spec, ls, le, "version " as *u8, tmp) == 1 { cfg[VC_VERSION] = tmp[0] }
386 if vh_num_field(spec, ls, le, "deviceid " as *u8, tmp) == 1 { cfg[VC_DEVICEID] = tmp[0] }
387 if vh_num_field(spec, ls, le, "hostfeatures " as *u8, tmp) == 1 { cfg[VC_HOSTFEAT] = tmp[0] }
388 if vh_num_field(spec, ls, le, "st_ack " as *u8, tmp) == 1 { cfg[VC_ACK] = tmp[0] }
389 if vh_num_field(spec, ls, le, "st_driver " as *u8, tmp) == 1 { cfg[VC_DRIVER] = tmp[0] }
390 if vh_num_field(spec, ls, le, "st_features_ok " as *u8, tmp) == 1 { cfg[VC_FEATOK] = tmp[0] }
391 if vh_num_field(spec, ls, le, "st_driver_ok " as *u8, tmp) == 1 { cfg[VC_DRIVEROK] = tmp[0] }
392 if vh_num_field(spec, ls, le, "off_queuesel " as *u8, tmp) == 1 { cfg[VC_OFF_QSEL] = tmp[0] }
393 if vh_num_field(spec, ls, le, "off_queuenummax " as *u8, tmp) == 1 { cfg[VC_OFF_QNUMMAX] = tmp[0] }
394 if vh_num_field(spec, ls, le, "off_queuenum " as *u8, tmp) == 1 { cfg[VC_OFF_QNUM] = tmp[0] }
395 if vh_num_field(spec, ls, le, "off_queuealign " as *u8, tmp) == 1 { cfg[VC_OFF_QALIGN] = tmp[0] }
396 if vh_num_field(spec, ls, le, "off_queuepfn " as *u8, tmp) == 1 { cfg[VC_OFF_QPFN] = tmp[0] }
397 if vh_num_field(spec, ls, le, "off_queuenotify " as *u8, tmp) == 1 { cfg[VC_OFF_QNOTIFY] = tmp[0] }
398 if vh_num_field(spec, ls, le, "queuesel " as *u8, tmp) == 1 { cfg[VC_QSEL] = tmp[0] }
399 if vh_num_field(spec, ls, le, "queuealign " as *u8, tmp) == 1 { cfg[VC_QALIGN] = tmp[0] }
400 if vh_num_field(spec, ls, le, "queuenum " as *u8, tmp) == 1 { cfg[VC_QNUM] = tmp[0] }
401 if vh_num_field(spec, ls, le, "queuepfn " as *u8, tmp) == 1 { cfg[VC_QPFN] = tmp[0] }
402 if vh_num_field(spec, ls, le, "queuenotify " as *u8, tmp) == 1 { cfg[VC_QNOTIFY] = tmp[0] }
403 if vh_num_field(spec, ls, le, "off_queuedescpeek " as *u8, tmp) == 1 { cfg[VC_OFF_QDESCPEEK] = tmp[0] }
404 if vh_num_field(spec, ls, le, "off_desc_addr " as *u8, tmp) == 1 { cfg[VC_OFF_DESC_ADDR] = tmp[0] }
405 if vh_num_field(spec, ls, le, "off_desc_len " as *u8, tmp) == 1 { cfg[VC_OFF_DESC_LEN] = tmp[0] }
406 if vh_num_field(spec, ls, le, "off_desc_flags " as *u8, tmp) == 1 { cfg[VC_OFF_DESC_FLAGS] = tmp[0] }
407 if vh_num_field(spec, ls, le, "off_desc_next " as *u8, tmp) == 1 { cfg[VC_OFF_DESC_NEXT] = tmp[0] }
408 if vh_num_field(spec, ls, le, "ring_page_addr " as *u8, tmp) == 1 { cfg[VC_RING_ADDR] = tmp[0] }
409 if vh_num_field(spec, ls, le, "desc_addr_lo " as *u8, tmp) == 1 { cfg[VC_DESC_ADDR_LO] = tmp[0] }
410 if vh_num_field(spec, ls, le, "desc_len " as *u8, tmp) == 1 { cfg[VC_DESC_LEN] = tmp[0] }
411 if vh_num_field(spec, ls, le, "desc_flags " as *u8, tmp) == 1 { cfg[VC_DESC_FLAGS] = tmp[0] }
412 if vh_num_field(spec, ls, le, "desc_next " as *u8, tmp) == 1 { cfg[VC_DESC_NEXT] = tmp[0] }
413 if vh_num_field(spec, ls, le, "off_queueusedidxpeek " as *u8, tmp) == 1 { cfg[VC_OFF_QUSEDIDXPEEK] = tmp[0] }
414 if vh_num_field(spec, ls, le, "avail_ring_off " as *u8, tmp) == 1 { cfg[VC_AVAIL_RING_OFF] = tmp[0] }
415 if vh_num_field(spec, ls, le, "avail_idx_off " as *u8, tmp) == 1 { cfg[VC_AVAIL_IDX_OFF] = tmp[0] }
416 if vh_num_field(spec, ls, le, "used_ring_off " as *u8, tmp) == 1 { cfg[VC_USED_RING_OFF] = tmp[0] }
417 if vh_num_field(spec, ls, le, "used_idx_off " as *u8, tmp) == 1 { cfg[VC_USED_IDX_OFF] = tmp[0] }
418 if vh_num_field(spec, ls, le, "avail_head " as *u8, tmp) == 1 { cfg[VC_AVAIL_HEAD] = tmp[0] }
419 if vh_num_field(spec, ls, le, "avail_idx_val " as *u8, tmp) == 1 { cfg[VC_AVAIL_IDX_VAL] = tmp[0] }
420 if vh_num_field(spec, ls, le, "used_idx_expected " as *u8, tmp) == 1 { cfg[VC_USED_IDX_EXP] = tmp[0] }
421 if vh_num_field(spec, ls, le, "off_queuestatpeek " as *u8, tmp) == 1 { cfg[VC_OFF_QSTATPEEK] = tmp[0] }
422 if vh_num_field(spec, ls, le, "status_buf_addr " as *u8, tmp) == 1 { cfg[VC_STAT_BUF_ADDR] = tmp[0] }
423 if vh_num_field(spec, ls, le, "status_buf_lo " as *u8, tmp) == 1 { cfg[VC_STAT_BUF_LO] = tmp[0] }
424 if vh_num_field(spec, ls, le, "virtio_blk_s_ok " as *u8, tmp) == 1 { cfg[VC_BLK_S_OK] = tmp[0] }
425 if vh_num_field(spec, ls, le, "stat_expected " as *u8, tmp) == 1 { cfg[VC_STAT_EXPECTED] = tmp[0] }
426 if vh_num_field(spec, ls, le, "off_queuesectpeek " as *u8, tmp) == 1 { cfg[VC_OFF_QSECTPEEK] = tmp[0] }
427 if vh_num_field(spec, ls, le, "off_data_sector_lo " as *u8, tmp) == 1 { cfg[VC_OFF_DATA_SECTOR] = tmp[0] }
428 if vh_num_field(spec, ls, le, "sect_pattern " as *u8, tmp) == 1 { cfg[VC_SECT_PATTERN] = tmp[0] }
429 if vh_num_field(spec, ls, le, "sect_data_expected " as *u8, tmp) == 1 { cfg[VC_SECT_DATA_EXP] = tmp[0] }
430 vh_str_field(spec, ls, le, "banner " as *u8, banner)
431 vh_str_field(spec, ls, le, "out " as *u8, outp)
432 }
433 ls = le + 1
434 }
435
436 // validate the handshake fields (always required: indices 0..VC_BASE inclusive).
437 var bad: i64 = 0
438 var vi: i64 = 0
439 while vi <= VC_BASE { if cfg[vi] == (0 - 1) { bad = 1 } vi = vi + 1 }
440 if bad == 1 { vh_p("VIRT-EMIT REFUSED: spec missing a required handshake field\n" as *u8); vh_log("(bad-spec)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
441 // detect the K-R2-001b1 queue-config layer: it is present iff ALL its fields (the range
442 // [VC_BASE+1, VC_QEND)) parsed. A partial set (some present, some -1) is a malformed
443 // spec -> refuse (contract: the queue layer is all-or-nothing). Absent set = the 5-token
444 // handshake spec (001a).
445 var qpresent: i64 = 0
446 var qmissing: i64 = 0
447 var qi: i64 = VC_BASE + 1
448 while qi < VC_QEND { if cfg[qi] == (0 - 1) { qmissing = qmissing + 1 } else { qpresent = qpresent + 1 } qi = qi + 1 }
449 var qcfg: i64 = 0
450 if qpresent > 0 { if qmissing > 0 { vh_p("VIRT-EMIT REFUSED: queue-config layer is partial (all-or-nothing)\n" as *u8); vh_log("(partial-qcfg)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } qcfg = 1 }
451 // detect the K-R2-001b2a descriptor-DMA layer: present iff ALL its fields (the range
452 // [VC_QEND, VC_DEND)) parsed. Same all-or-nothing contract. descdma REQUIRES qcfg (the
453 // descriptor ring is laid only after the queue is configured).
454 var dpresent: i64 = 0
455 var dmissing: i64 = 0
456 var di: i64 = VC_QEND
457 while di < VC_DEND { if cfg[di] == (0 - 1) { dmissing = dmissing + 1 } else { dpresent = dpresent + 1 } di = di + 1 }
458 var descdma: i64 = 0
459 if dpresent > 0 { if dmissing > 0 { vh_p("VIRT-EMIT REFUSED: descriptor-DMA layer is partial (all-or-nothing)\n" as *u8); vh_log("(partial-descdma)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } descdma = 1 }
460 if descdma == 1 { if qcfg == 0 { vh_p("VIRT-EMIT REFUSED: descriptor-DMA layer requires the queue-config layer\n" as *u8); vh_log("(descdma-no-qcfg)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } }
461 // detect the K-R2-001b2b avail/used layer: present iff ALL its fields (the range
462 // [VC_DEND, VC_DEND2)) parsed. Same all-or-nothing contract. availused REQUIRES descdma
463 // (the avail ring is published + the used ring consumed only after the descriptor is laid).
464 var apresent: i64 = 0
465 var amissing: i64 = 0
466 var ai: i64 = VC_DEND
467 while ai < VC_DEND2 { if cfg[ai] == (0 - 1) { amissing = amissing + 1 } else { apresent = apresent + 1 } ai = ai + 1 }
468 var availused: i64 = 0
469 if apresent > 0 { if amissing > 0 { vh_p("VIRT-EMIT REFUSED: avail/used layer is partial (all-or-nothing)\n" as *u8); vh_log("(partial-availused)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } availused = 1 }
470 if availused == 1 { if descdma == 0 { vh_p("VIRT-EMIT REFUSED: avail/used layer requires the descriptor-DMA layer\n" as *u8); vh_log("(availused-no-descdma)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } }
471 // detect the K-R2-001b2c status-byte layer: present iff ALL its fields (the range
472 // [VC_DEND2, VC_DEND3)) parsed. Same all-or-nothing contract. status REQUIRES availused
473 // (the status byte is written + read back only after the used-ring writeback completes).
474 var spresent: i64 = 0
475 var smissing: i64 = 0
476 var si: i64 = VC_DEND2
477 while si < VC_DEND3 { if cfg[si] == (0 - 1) { smissing = smissing + 1 } else { spresent = spresent + 1 } si = si + 1 }
478 var statlayer: i64 = 0
479 if spresent > 0 { if smissing > 0 { vh_p("VIRT-EMIT REFUSED: status-byte layer is partial (all-or-nothing)\n" as *u8); vh_log("(partial-status)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } statlayer = 1 }
480 if statlayer == 1 { if availused == 0 { vh_p("VIRT-EMIT REFUSED: status-byte layer requires the avail/used layer\n" as *u8); vh_log("(status-no-availused)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } }
481 // detect the K-R2-001b3 sector-data layer: present iff ALL its fields (the range
482 // [VC_DEND3, VC_N)) parsed. Same all-or-nothing contract. sector REQUIRES statlayer (the
483 // sector data round-trips through the descriptor + ring the status layer already exercises).
484 var secpresent: i64 = 0
485 var secmissing: i64 = 0
486 var seci: i64 = VC_DEND3
487 while seci < VC_N { if cfg[seci] == (0 - 1) { secmissing = secmissing + 1 } else { secpresent = secpresent + 1 } seci = seci + 1 }
488 var sectlayer: i64 = 0
489 if secpresent > 0 { if secmissing > 0 { vh_p("VIRT-EMIT REFUSED: sector-data layer is partial (all-or-nothing)\n" as *u8); vh_log("(partial-sector)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } sectlayer = 1 }
490 if sectlayer == 1 { if statlayer == 0 { vh_p("VIRT-EMIT REFUSED: sector-data layer requires the status-byte layer\n" as *u8); vh_log("(sector-no-status)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 } }
491 // stage_mode: 0 = handshake only, 1 = + queue-config, 2 = + descriptor-DMA, 3 = +
492 // avail/used, 4 = + status-byte, 5 = + sector-data.
493 var stage_mode: i64 = 0
494 if qcfg == 1 { stage_mode = 1 }
495 if descdma == 1 { stage_mode = 2 }
496 if availused == 1 { stage_mode = 3 }
497 if statlayer == 1 { stage_mode = 4 }
498 if sectlayer == 1 { stage_mode = 5 }
499 var blen: i64 = 0
500 while banner[blen] != (0 as u8) { blen = blen + 1 }
501 if blen <= 0 { vh_p("VIRT-EMIT REFUSED: no banner row\n" as *u8); vh_log("(no-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
502 if outp[0] == (0 as u8) { vh_p("VIRT-EMIT REFUSED: no out row\n" as *u8); vh_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
503
504 // ---- table-compute the transcript SEGMENTS from the banner. Handshake-only (001a):
505 // banner "VIO ACK DRV FEAT OK" -> 5 tokens. Queue-config (001b1): banner "VIO ACK DRV
506 // FEAT OK VQ" -> 6 tokens. We split on the spaces and re-attach the separator: each
507 // token gets a trailing space except the last, which gets the newline. This makes the
508 // golden = the banner + '\n', emitted in handshake order. The expected segment count is
509 // 5 for the handshake spec and 6 when the queue-config layer is present. ----
510 var VH_EXPECT_SEGS: i64 = 5
511 if qcfg == 1 { VH_EXPECT_SEGS = 6 }
512 if descdma == 1 { VH_EXPECT_SEGS = 7 }
513 if availused == 1 { VH_EXPECT_SEGS = 8 }
514 if statlayer == 1 { VH_EXPECT_SEGS = 9 }
515 if sectlayer == 1 { VH_EXPECT_SEGS = 10 }
516 let tail: *u8 = sys_mmap(256)
517 let segs: *i64 = sys_mmap(8 * 20) as *i64
518 var tn: i64 = 0
519 var seg_idx: i64 = 0
520 var seg_start: i64 = 0
521 var bp: i64 = 0
522 while bp < blen {
523 if banner[bp] == (32 as u8) {
524 // close current segment WITH a trailing space
525 tail[tn] = 32 as u8; tn = tn + 1
526 segs[seg_idx * 2] = seg_start
527 segs[seg_idx * 2 + 1] = tn - seg_start
528 seg_idx = seg_idx + 1
529 seg_start = tn
530 bp = bp + 1
531 } else {
532 tail[tn] = banner[bp]; tn = tn + 1
533 bp = bp + 1
534 }
535 }
536 // last segment gets a trailing newline, not a space.
537 tail[tn] = 10 as u8; tn = tn + 1
538 segs[seg_idx * 2] = seg_start
539 segs[seg_idx * 2 + 1] = tn - seg_start
540 seg_idx = seg_idx + 1
541 tail[tn] = 0 as u8
542 if seg_idx != VH_EXPECT_SEGS { vh_p("VIRT-EMIT REFUSED: banner token count mismatch, expected " as *u8); vh_fn(1, VH_EXPECT_SEGS); vh_p(" got " as *u8); vh_fn(1, seg_idx); vh_p("\n" as *u8); vh_log("(bad-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
543
544 // golden transcript = the whole tail (segments concatenated) = banner + '\n'.
545 let golden: *u8 = sys_mmap(256)
546 var gi: i64 = 0
547 while gi < tn { golden[gi] = tail[gi]; gi = gi + 1 }
548 golden[gi] = 0 as u8
549
550 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target);
551 // pass 2 re-emits with that offset baked into every fail branch. Instruction COUNT is
552 // identical across passes (only immediates differ), so the measured offset is stable. ----
553 let scratch: *u8 = sys_mmap(VH_MAGIC_8192)
554 let sz0: i64 = vh_emit_image(scratch, cfg, tail, segs, 0, stage_mode)
555 // the finisher is the last block; its start = sz0 - finisher_len. Recompute cleanly:
556 // emit_finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words = 24 bytes.
557 let fin_len: i64 = 24
558 let fail_off: i64 = sz0 - fin_len
559
560 let bin: *u8 = sys_mmap(VH_MAGIC_8192)
561 let sz: i64 = vh_emit_image(bin, cfg, tail, segs, fail_off, stage_mode)
562 if sz != sz0 { vh_p("VIRT-EMIT RED: pass size mismatch\n" as *u8); vh_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
563
564 let ofd: i64 = sys_openat_wr(outp, 0x1a4)
565 if ofd < 0 { vh_p("VIRT-EMIT RED: cannot open out\n" as *u8); vh_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
566 sys_write(ofd, bin, sz)
567 sys_close(ofd)
568
569 // write the golden transcript next to the image (<out>.gold) for the gate.
570 let gp: *u8 = sys_mmap(512)
571 var gpi: i64 = 0
572 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 }
573 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.'
574 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g'
575 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o'
576 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l'
577 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd'
578 gp[gpi] = 0 as u8
579 let gfd: i64 = sys_openat_wr(gp, 0x1a4)
580 if gfd >= 0 { sys_write(gfd, golden, gi); sys_close(gfd) }
581
582 vh_p("VIRT-EMIT GREEN: authored " as *u8); vh_p(outp); vh_p(" bytes=" as *u8); vh_fn(1, sz)
583 vh_p(" base=0x" as *u8); vh_hex(1, cfg[VC_BASE])
584 vh_p(" fail_off=" as *u8); vh_fn(1, fail_off)
585 vh_p(" golden=" as *u8); vh_p(golden); vh_p(" (spec in, bootable rv64 virtio handshake image out)\n" as *u8)
586 vh_log(outp, sz, golden, "GREEN" as *u8)
587 sys_exit(0)
588 return 0
589}