rumpk/core/ion/memory.nim

214 lines
6.7 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: ION Slab Allocator
# ION Memory Manager
# The "Slab Allocator" for Zero-Copy IO
import ../ring
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc dbg(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len))
var nl = "\n"
console_write(unsafeAddr nl[0], csize_t(1))
const
SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom)
POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM)
POOL_ALIGN* = 4096 # VirtIO/Page Alignment
SYSTABLE_BASE = 0x83000000'u64
USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE
USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000
USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX
USER_PKT_SIZE = 2048 # 2KB per packet
USER_BITMAP_ADDR = SYSTABLE_BASE + 0x100
type
# The Physical Token representing a packet
IonPacket* = object
data*: ptr UncheckedArray[byte] # Virtual Address of payload
phys*: uint64 # Physical Address (For VirtIO/DMA)
len*: uint16 # Actual data length
id*: uint16 # The Token ID (Slab Index)
# The Monolithic Memory Pool
PacketPool = object
# We use 'align' to ensure the buffer starts on a page boundary for VirtIO
buffer {.align: POOL_ALIGN.}: array[SLAB_SIZE * POOL_COUNT, byte]
free_ring: RingBuffer[uint16, POOL_COUNT] # Stores IDs of free slabs
base_phys: uint64
var global_tx_ring*: RingBuffer[IonPacket, 256]
var global_pool: PacketPool
proc ion_pool_init*() {.exportc.} =
## Initialize the DMA Pool with REAL Physical Address
dbg("[ION] Initializing Pool...")
# 1. Get the VIRTUAL address of the static buffer
let virt_addr = cast[uint64](addr global_pool.buffer[0])
# 2. Translate to PHYSICAL (Identity Mapped for Phase 7)
global_pool.base_phys = virt_addr
# Tracing for Phase 37
proc kprint_hex(v: uint64) {.importc, cdecl.}
dbg("[ION] Pool Base Phys: ")
kprint_hex(global_pool.base_phys)
dbg("")
dbg("[ION] Ring Init...")
global_pool.free_ring.init()
global_tx_ring.init()
# Fill the free ring with all indices [0..1023]
dbg("[ION] Filling Slabs...")
var count = 0
for i in 0 ..< POOL_COUNT:
if global_pool.free_ring.push(uint16(i)):
inc count
dbg("[ION] Pool Ready.")
proc ion_alloc*(): IonPacket {.exportc.} =
## O(1) Allocation. Returns an empty packet struct.
## If OOM, returns packet with data = nil
var pkt: IonPacket
let (ok, idx) = global_pool.free_ring.pop()
if not ok:
dbg("[ION] ALLOC FAILED (Empty Ring?)")
pkt.data = nil
return pkt
pkt.id = idx
pkt.len = 0
# Calculate Virtual Address
let offset = int(idx) * SLAB_SIZE
pkt.data = cast[ptr UncheckedArray[byte]](addr global_pool.buffer[offset])
# Calculate Physical Address (1:1 map for Phase 7)
pkt.phys = global_pool.base_phys + uint64(offset)
return pkt
proc ion_free*(pkt: IonPacket) {.exportc.} =
## O(1) Free. Returns the token to the ring.
if pkt.data == nil: return
if (pkt.id and 0x8000) != 0:
# User Slab - Clear shared bitmap
let slotIdx = pkt.id and 0x7FFF
if slotIdx >= USER_SLAB_COUNT: return
let bitmap = cast[ptr array[16, byte]](USER_BITMAP_ADDR)
let byteIdx = int(slotIdx) div 8
let bitIdx = int(slotIdx) mod 8
let mask = byte(1 shl bitIdx)
bitmap[byteIdx] = bitmap[byteIdx] and (not mask)
return
discard global_pool.free_ring.push(pkt.id)
# Helper for C/Zig Interop (Pure Pointers)
# Return physical address of a allocated block, put ID in out_id
proc ion_alloc_raw*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
let pkt = ion_alloc()
if pkt.data == nil: return 0
out_id[] = pkt.id
return pkt.phys
proc ion_free_raw*(id: uint16) {.exportc, cdecl.} =
var pkt: IonPacket
pkt.id = id
# We don't reconstruct data/phys for free, just push ID
# But for safety we might check bounds? Ring handles it.
pkt.data = cast[ptr UncheckedArray[byte]](1) # Dummy non-nil
ion_free(pkt)
proc ion_get_virt*(id: uint16): ptr byte {.exportc.} =
if (id and 0x8000) != 0:
let idx = id and 0x7FFF
let offset = int(idx) * SLAB_SIZE
return cast[ptr byte](USER_SLAB_BASE + uint64(offset))
let offset = int(id) * SLAB_SIZE
return addr global_pool.buffer[offset]
proc ion_get_phys*(id: uint16): uint64 {.exportc.} =
if (id and 0x8000) != 0:
let idx = id and 0x7FFF
let offset = int(idx) * SLAB_SIZE
return USER_SLAB_BASE + uint64(offset)
let offset = int(id) * SLAB_SIZE
return global_pool.base_phys + uint64(offset)
# =========================================================
# The Global TX Ring (Multiplexing)
# =========================================================
proc ion_tx_init*() {.exportc.} =
global_tx_ring.init()
proc ion_tx_push*(pkt: IonPacket): bool {.exportc.} =
if global_tx_ring.push(pkt):
# dbg("[ION TX] Pushed")
return true
dbg("[ION TX] PUSH FAILED (Global Ring Full)")
return false
proc ion_tx_pop*(out_id: ptr uint16, out_len: ptr uint16): bool {.exportc.} =
if global_tx_ring.isEmpty:
return false
let (ok, pkt) = global_tx_ring.pop()
if not ok: return false
out_id[] = pkt.id
out_len[] = pkt.len
dbg("[ION TX] Popped Packet for VirtIO")
return true
# =========================================================
# User-Visible Slab Allocator (Shared Memory)
# =========================================================
# NOTE: This allocator provides buffers in the SYSTABLE shared region
# (0x83010000+) which is mapped into both kernel and userland page tables.
# Used for network packet egress from userland.
# NOTE: Constants moved to top
# var user_slab_bitmap: array[USER_SLAB_COUNT, bool] # REMOVED: Use Shared Bitmap
proc ion_user_slab_init*() {.exportc.} =
## Initialize shared user slab bitmap (all free)
let bitmap = cast[ptr array[64, byte]](USER_BITMAP_ADDR)
for i in 0 ..< 64:
bitmap[i] = 0
proc ion_alloc_shared*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
## Allocate a buffer from the user-visible slab (Kernel Side, Shared Bitmap)
let bitmap = cast[ptr array[64, byte]](USER_BITMAP_ADDR)
for byteIdx in 0 ..< 64:
if bitmap[byteIdx] != 0xFF:
for bitIdx in 0 ..< 8:
let mask = byte(1 shl bitIdx)
if (bitmap[byteIdx] and mask) == 0:
# Found free
bitmap[byteIdx] = bitmap[byteIdx] or mask
let idx = byteIdx * 8 + bitIdx
if idx >= USER_SLAB_COUNT: return 0
out_id[] = uint16(idx) or 0x8000
return USER_SLAB_BASE + uint64(idx) * USER_PKT_SIZE
return 0