code wiki / _hdl_build / rv64im_min_nndev.nx
rv64im_min_nndev.nx source
↩ module page · 182 lines · 9138 B
1// rv64im_min_nndev.nx -- the NISHI-NATIVE DEVICE PROTOCOL (NNDP) device model.
2//
3// The genuine "exceed virtio" target (nishi-ecosystem-only law: virtio is the last-mile/benchmark
4// transport, NOT the substrate -- the NATIVE protocol that EXCEEDS it is the real goal, the census
5// `sovereign-device-protocol` ABSENT exceed-row). NNDP is a SOVEREIGN, deliberately LEAN block
6// device protocol designed to complete one I/O in FAR fewer MMIO accesses + memory ops than legacy
7// virtio-MMIO -- measured head-to-head on the SAME sovereign rv64 emu (apples-to-apples).
8//
9// Why it is leaner than virtio-MMIO (the measurable exceed):
10// - SELF-DESCRIBING IDENTITY: MAGIC + DEVCLASS (2 reads), no Version/VendorID/HostFeatures/
11// GuestFeatures negotiation.
12// - SINGLE-STEP BRING-UP: one ENABLE write, NOT the 4-stage ACK->DRIVER->FEATURES_OK->DRIVER_OK
13// Status handshake (+ the FEATURES_OK re-read).
14// - NO SPLIT VIRTQUEUE: one INLINE command descriptor at CMD_ADDR (data_addr/len/op), NOT a
15// descriptor table + avail ring + used ring with index bookkeeping and TWO notify kicks.
16// - SINGLE DOORBELL, DIRECT COMPLETION: one DOORBELL write -> the device DMA-processes the command
17// and latches STATUS + RESULT inline; the driver reads them back directly (no used-ring walk,
18// no used.idx/avail.idx, no separate status descriptor).
19//
20// Register window @ NX_NNDEV_BASE (256 bytes), an INDEPENDENT instance (virtio/nvme untouched):
21// 0x00 MAGIC (RO) 0x4E4E4431 ("NND1", little-endian) -- identity, self-describing
22// 0x04 DEVCLASS (RO) 1 = block
23// 0x08 ENABLE (RW) driver writes 1 to bring the device up (single-step); read-back = latched
24// 0x0C CMD_ADDR (RW) guest-physical address of the inline command descriptor; read-back = latched
25// 0x10 DOORBELL (WO) write 1 -> device runs the command at CMD_ADDR (DMA) and completes inline
26// 0x14 STATUS (RO) completion status latched on the last doorbell (0 = OK)
27// 0x18 RESULT (RO) first 32-bit word the device DMA-read from the command's data buffer (the
28// data round-trip binding proof; the driver reads it back == its written word)
29//
30// Inline command descriptor (at CMD_ADDR in guest RAM):
31// +0 data_addr (4, low 32 of the data-buffer guest-physical address)
32// +4 data_len (4)
33// +8 op (4; 0 = read/round-trip)
34//
35// Status: SEED 2026-06-13 (X-DRV sovereign-device-protocol). Block read round-trip + identity +
36// single-step enable + single-doorbell completion. license_tier: ORIGINAL
37import "nx_syscalls.nx"
38import "nishi_hdl_primitives.nx"
39
40// ===== MMIO window =================================================
41const NX_NNDEV_BASE: i64 = 0x10005000 // just past the NVMe window (0x10003000..0x10005000); no overlap
42const NX_NNDEV_END: i64 = 0x10005100
43
44// ===== register offsets =================================================
45const NX_NNDEV_OFF_MAGIC: i64 = 0x00
46const NX_NNDEV_OFF_DEVCLASS: i64 = 0x04
47const NX_NNDEV_OFF_ENABLE: i64 = 0x08
48const NX_NNDEV_OFF_CMDADDR: i64 = 0x0C
49const NX_NNDEV_OFF_DOORBELL: i64 = 0x10
50const NX_NNDEV_OFF_STATUS: i64 = 0x14
51const NX_NNDEV_OFF_RESULT: i64 = 0x18
52
53// ===== identity constants =================================================
54const NX_NNDEV_MAGIC: i64 = 0x4E4E4431 // "NND1"
55const NX_NNDEV_DEVCLASS: i64 = 1 // block
56
57// ===== inline command descriptor offsets =================================================
58const NX_NNDEV_CMD_OFF_DATAADDR: i64 = 0
59const NX_NNDEV_CMD_OFF_DATALEN: i64 = 4
60const NX_NNDEV_CMD_OFF_OP: i64 = 8
61
62// ===== storage slots =================================================
63const NX_NNDEV_SLOT_ENABLE: i64 = 0
64const NX_NNDEV_SLOT_CMDADDR: i64 = 1
65const NX_NNDEV_SLOT_STATUS: i64 = 2
66const NX_NNDEV_SLOT_RESULT: i64 = 3
67const NX_NNDEV_SLOT_N: i64 = 4
68
69// ===== verdicts =================================================
70const NX_NNDEV_OK: i64 = 0
71const NX_NNDEV_ADDR_OUT_OF_RANGE: i64 = 1
72
73struct NxNnDev {
74 storage: *i64 // NX_NNDEV_SLOT_N i64s
75 valid: i64
76 base: i64
77 devclass: i64
78}
79
80func nx_nndev_reset(storage: *i64) -> i64 {
81 storage[NX_NNDEV_SLOT_ENABLE] = 0
82 storage[NX_NNDEV_SLOT_CMDADDR] = 0
83 storage[NX_NNDEV_SLOT_STATUS] = 0
84 storage[NX_NNDEV_SLOT_RESULT] = 0
85 return NX_NNDEV_OK
86}
87
88func nx_nndev_init(d: *NxNnDev, storage: *i64) -> i64 {
89 if (d as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
90 if (storage as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
91 d.storage = storage
92 d.valid = 1
93 d.base = NX_NNDEV_BASE
94 d.devclass = NX_NNDEV_DEVCLASS
95 nx_nndev_reset(storage)
96 return NX_NNDEV_OK
97}
98
99func nx_nndev_addr_in_range(d: *NxNnDev, addr: i64) -> i64 {
100 if addr < d.base { return 0 }
101 if addr >= d.base + 0x100 { return 0 }
102 return 1
103}
104
105// ===== MMIO read (32-bit) =================================================
106func nx_nndev_read32(d: *NxNnDev, addr: i64, value_out: *i64) -> i64 {
107 if d.valid != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
108 if (value_out as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
109 if nx_nndev_addr_in_range(d, addr) != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
110 let off: i64 = addr - d.base
111 if off == NX_NNDEV_OFF_MAGIC { value_out[0] = NX_NNDEV_MAGIC; return NX_NNDEV_OK }
112 if off == NX_NNDEV_OFF_DEVCLASS { value_out[0] = d.devclass; return NX_NNDEV_OK }
113 if off == NX_NNDEV_OFF_ENABLE { value_out[0] = d.storage[NX_NNDEV_SLOT_ENABLE]; return NX_NNDEV_OK }
114 if off == NX_NNDEV_OFF_CMDADDR { value_out[0] = d.storage[NX_NNDEV_SLOT_CMDADDR]; return NX_NNDEV_OK }
115 if off == NX_NNDEV_OFF_STATUS { value_out[0] = d.storage[NX_NNDEV_SLOT_STATUS]; return NX_NNDEV_OK }
116 if off == NX_NNDEV_OFF_RESULT { value_out[0] = d.storage[NX_NNDEV_SLOT_RESULT]; return NX_NNDEV_OK }
117 value_out[0] = 0
118 return NX_NNDEV_OK
119}
120
121// ===== MMIO write (32-bit) =================================================
122// ENABLE/CMD_ADDR latch; ENABLE write of 0 = reset. DOORBELL is acted on by the sim's store32
123// dispatch (it owns guest RAM), which calls nx_nndev_doorbell_dma after this latch.
124func nx_nndev_write32(d: *NxNnDev, addr: i64, value: i64) -> i64 {
125 if d.valid != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
126 if nx_nndev_addr_in_range(d, addr) != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
127 let off: i64 = addr - d.base
128 if off == NX_NNDEV_OFF_ENABLE {
129 let w: i64 = value & 0xffffffff
130 if w == 0 { nx_nndev_reset(d.storage); return NX_NNDEV_OK }
131 d.storage[NX_NNDEV_SLOT_ENABLE] = w
132 return NX_NNDEV_OK
133 }
134 if off == NX_NNDEV_OFF_CMDADDR {
135 d.storage[NX_NNDEV_SLOT_CMDADDR] = value & 0xffffffff
136 return NX_NNDEV_OK
137 }
138 if off == NX_NNDEV_OFF_DOORBELL {
139 // the count/trigger is observed by the sim, which then runs nx_nndev_doorbell_dma.
140 return NX_NNDEV_OK
141 }
142 // identity + RO result registers ignore writes.
143 return NX_NNDEV_OK
144}
145
146// little-endian guest-RAM helpers (bounds-checked by the caller).
147func nx_nndev_rd32(mem_buf: *u8, off: i64) -> i64 {
148 let b0: i64 = mem_buf[off] as i64
149 let b1: i64 = mem_buf[off + 1] as i64
150 let b2: i64 = mem_buf[off + 2] as i64
151 let b3: i64 = mem_buf[off + 3] as i64
152 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
153}
154func nx_nndev_inrange(off: i64, w: i64, mem_size: i64) -> i64 {
155 if off < 0 { return 0 }
156 if off > mem_size - w { return 0 }
157 return 1
158}
159
160// ===== DOORBELL DMA: complete the command at CMD_ADDR =================================================
161// The device reads the inline command descriptor at CMD_ADDR (data_addr/len/op), FOLLOWS data_addr
162// into guest RAM, DMA-READS the first 32-bit word of the data buffer the driver placed its pattern
163// into, latches it into RESULT, and sets STATUS = OK. ONE descriptor, ONE round-trip, NO rings.
164// Bounds-checked at the device/memory boundary: an out-of-range descriptor or data buffer leaves
165// STATUS/RESULT untouched (no out-of-bounds RAM access). Requires ENABLE latched (device up).
166func nx_nndev_doorbell_dma(d: *NxNnDev, mem_buf: *u8, mem_base: i64, mem_size: i64) -> i64 {
167 if d.valid != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
168 if (mem_buf as i64) == 0 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
169 if d.storage[NX_NNDEV_SLOT_ENABLE] == 0 { return NX_NNDEV_OK } // device not enabled: no-op
170 let cmd_phys: i64 = d.storage[NX_NNDEV_SLOT_CMDADDR]
171 if cmd_phys < mem_base { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
172 let cmd_off: i64 = cmd_phys - mem_base
173 if nx_nndev_inrange(cmd_off + NX_NNDEV_CMD_OFF_DATAADDR, 4, mem_size) != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
174 let data_phys: i64 = nx_nndev_rd32(mem_buf, cmd_off + NX_NNDEV_CMD_OFF_DATAADDR)
175 if data_phys < mem_base { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
176 let data_off: i64 = data_phys - mem_base
177 if nx_nndev_inrange(data_off, 4, mem_size) != 1 { return 0 - NX_NNDEV_ADDR_OUT_OF_RANGE }
178 let word: i64 = nx_nndev_rd32(mem_buf, data_off)
179 d.storage[NX_NNDEV_SLOT_RESULT] = word & 0xffffffff
180 d.storage[NX_NNDEV_SLOT_STATUS] = 0 // OK
181 return NX_NNDEV_OK
182}