code wiki / _hdl_build / nx_virtio_dma_probe.nx

nx_virtio_dma_probe.nx source

↩ module page · 133 lines · 6954 B

1// nx_virtio_dma_probe.nx -- BISECT the virtio descriptor-DMA chain by calling the device model 2// DIRECTLY, with no emulated CPU anywhere in the path. 3// 4// WHY IT EXISTS: two independent toolchains (nx_drv_proto_emit + nx_virtio_hs_emit, two different 5// specs, two different images) both drive the sovereign emu to EXACTLY `VIO ACK DRV FEAT OK VQ` and 6// then die at the first DESC-stage check. That isolates the defect to what they SHARE -- the 7// QueueNotify -> nx_virtio_notify_dma -> QueueDescPeek chain -- but it does NOT say which half: 8// (a) the DEVICE MODEL's DMA walk is broken, or 9// (b) the device model is fine and the SIM's store32 dispatch never delivers the driver's 10// descriptor writes / never routes the notify. 11// Reading the source could not separate those two, and I was wrong twice guessing, so this measures. 12// ******BUILD THE INSTRUMENT BEFORE FORMING THE HYPOTHESIS -- the cheapest experiment that could 13// REFUTE runs first, and a bisection is cheaper than either fix. 14// 15// This probe exercises ONLY leg (a). If DESCPEEK comes back correct here, the device model is 16// exonerated and the defect is in the sim's dispatch; if it comes back wrong, the defect is here. 17// Either way the next step is named by the RESULT rather than chosen by taste. 18// 19// Geometry is taken from the REAL consumer (nx_bootcap): base 0x80000000, size 65536, ring page 20// 0x80001000 (QueuePFN 0x80001), data buffer 0x80003000 -- a probe on different geometry than the 21// subject measures a different system. 22// exit 0 = device-model DMA WORKS (defect is downstream) | 1 = device-model DMA is the defect 23// | 2 = the probe could not observe (its own negative control failed -- trust nothing) 24// license_tier: ORIGINAL. Read-only. No hw writes (Rule 26). 25import "nx_syscalls.nx" 26import "rv64im_min_virtio.nx" 27 28// geometry MIRRORED from nx_bootcap.nx -- if that changes, this probe measures the wrong machine. 29const VP_MEM_BASE: i64 = 0x80000000 30const VP_MEM_SIZE: i64 = 65536 31const VP_RING_PHYS: i64 = 0x80001000 32const VP_PFN: i64 = 0x80001 33const VP_DATA_PHYS: i64 = 0x80003000 34const VP_QNUM: i64 = 8 35 36func vp_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 37func vp_n(v: i64) -> i64 { 38 var m: i64 = v 39 if m < 0 { vp_p("-" as *u8); m = 0 - m } 40 let t: *u8 = sys_mmap(32) 41 var k: i64 = 0 42 if m == 0 { t[0] = 48 as u8; k = 1 } 43 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 44 let o: *u8 = sys_mmap(32) 45 var i: i64 = 0 46 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 47 sys_write(1, o, k) 48 return 0 49} 50func vp_wr32(mem: *u8, off: i64, val: i64) -> i64 { 51 mem[off] = (val & 0xff) as u8 52 mem[off + 1] = ((val >> 8) & 0xff) as u8 53 mem[off + 2] = ((val >> 16) & 0xff) as u8 54 mem[off + 3] = ((val >> 24) & 0xff) as u8 55 return 0 56} 57 58func main() -> i64 { 59 vp_p("=== nx_virtio_dma_probe: device-model descriptor DMA, called DIRECTLY (no emulated CPU) ===\n" as *u8) 60 61 let mem: *u8 = sys_mmap(VP_MEM_SIZE) 62 let storage: *i64 = sys_mmap(8 * NX_VIRTIO_SLOT_N) as *i64 63 let v: *NxVirtioMmio = sys_mmap(64) as *NxVirtioMmio 64 nx_virtio_init(v, storage) 65 let box: *i64 = sys_mmap(16) as *i64 66 67 // Lay descriptor 0 in the ring page exactly as the spec's stage 6 does: 68 // +0 addr(lo) = data buffer | +8 len = 512 | +12 flags = 2 (WRITE) | +14 next = 0 69 let ring_off: i64 = VP_RING_PHYS - VP_MEM_BASE 70 vp_wr32(mem, ring_off + 0, VP_DATA_PHYS) 71 vp_wr32(mem, ring_off + 8, 512) 72 mem[ring_off + 12] = 2 as u8 73 mem[ring_off + 13] = 0 as u8 74 mem[ring_off + 14] = 0 as u8 75 mem[ring_off + 15] = 0 as u8 76 // and the sector pattern in the data buffer, so the b3 leg is observable too 77 vp_wr32(mem, VP_DATA_PHYS - VP_MEM_BASE, 0x5EC70DA7) 78 79 // program the queue the way the driver does 80 nx_virtio_write32(v, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUESEL, 0) 81 nx_virtio_write32(v, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUENUM, VP_QNUM) 82 nx_virtio_write32(v, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUEPFN, VP_PFN) 83 nx_virtio_read32(v, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUEPFN, box) 84 let pfn_back: i64 = box[0] 85 vp_p(" queuepfn_readback=" as *u8); vp_n(pfn_back) 86 vp_p(" expected=" as *u8); vp_n(VP_PFN); vp_p("\n" as *u8) 87 88 // ---- THE MEASUREMENT: call the DMA walk directly ---- 89 let rc: i64 = nx_virtio_notify_dma(v, mem, VP_MEM_BASE, VP_MEM_SIZE) 90 vp_p(" notify_dma_rc=" as *u8); vp_n(rc) 91 vp_p(" (0 = completed; negative = the device refused, and the reason is a bounds check)\n" as *u8) 92 93 nx_virtio_read32(v, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUEDESCPEEK, box) 94 let peek: i64 = box[0] 95 vp_p(" queuedescpeek=" as *u8); vp_n(peek) 96 vp_p(" expected=" as *u8); vp_n(VP_DATA_PHYS); vp_p("\n" as *u8) 97 98 // ---- NEGATIVE CONTROL: a ring BELOW mem_base must be REFUSED, and must NOT latch a peek. 99 // A probe that cannot fail proves nothing; without this, a device model that latched a constant 100 // would score exactly like one that really walked the ring. 101 let storage2: *i64 = sys_mmap(8 * NX_VIRTIO_SLOT_N) as *i64 102 let v2: *NxVirtioMmio = sys_mmap(64) as *NxVirtioMmio 103 nx_virtio_init(v2, storage2) 104 nx_virtio_write32(v2, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUEPFN, 1) 105 let nrc: i64 = nx_virtio_notify_dma(v2, mem, VP_MEM_BASE, VP_MEM_SIZE) 106 nx_virtio_read32(v2, NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUEDESCPEEK, box) 107 let npeek: i64 = box[0] 108 vp_p(" neg-control-ring-below-membase: rc=" as *u8); vp_n(nrc) 109 vp_p(" descpeek=" as *u8); vp_n(npeek); vp_p(" (rc MUST be negative and descpeek MUST be 0)\n" as *u8) 110 111 var control_ok: i64 = 0 112 if nrc < 0 { if npeek == 0 { control_ok = 1 } } 113 if control_ok == 0 { 114 vp_p("\nPROBE-UNOBSERVABLE: the negative control did NOT refuse, so this probe cannot tell a real\n" as *u8) 115 vp_p("DMA walk from a device that latches regardless. Publishing a verdict from it would be noise.\n" as *u8) 116 sys_exit(2) 117 return 2 118 } 119 120 if peek == VP_DATA_PHYS { 121 vp_p("\nVIRTIO-DMA-PROBE verdict=DEVICE-MODEL-OK -- the device DMA-read the descriptor the probe laid\n" as *u8) 122 vp_p("and latched it into QueueDescPeek. The device model is EXONERATED: the defect that stops both\n" as *u8) 123 vp_p("drivers at VQ is DOWNSTREAM of here -- in the sim store32 dispatch (the driver descriptor\n" as *u8) 124 vp_p("writes never reaching guest RAM, or the QueueNotify write never routing to this walk).\n" as *u8) 125 sys_exit(0) 126 return 0 127 } 128 vp_p("\nVIRTIO-DMA-PROBE verdict=DEVICE-MODEL-IS-THE-DEFECT -- the walk was invoked with a correctly\n" as *u8) 129 vp_p("laid descriptor and in-range geometry, and QueueDescPeek still does not carry the descriptor\n" as *u8) 130 vp_p("addr-low word. The bug is inside nx_virtio_notify_dma or its storage, not the sim dispatch.\n" as *u8) 131 sys_exit(1) 132 return 1 133}