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}