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}