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}