feat(core): M4 security — CSpace, Pledge, STL, budget enforcement, BKDL manifests

This commit is contained in:
Markus Maiwald 2026-02-15 19:59:07 +01:00
parent 8d4b581519
commit 0c598ce0bd
11 changed files with 872 additions and 268 deletions

View File

@ -27,6 +27,7 @@ proc cspace_grant_cap*(
proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.importc, cdecl.} proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.importc, cdecl.}
proc cspace_revoke*(fiber_id: uint64, slot: uint) {.importc, cdecl.} proc cspace_revoke*(fiber_id: uint64, slot: uint) {.importc, cdecl.}
proc cspace_check_perm*(fiber_id: uint64, slot: uint, perm_bits: uint8): bool {.importc, cdecl.} proc cspace_check_perm*(fiber_id: uint64, slot: uint, perm_bits: uint8): bool {.importc, cdecl.}
proc cspace_check_channel*(fiber_id: uint64, channel_id: uint64, perm_bits: uint8): bool {.importc, cdecl.}
## Capability Types (Mirror from cspace.zig) ## Capability Types (Mirror from cspace.zig)
type type
@ -80,10 +81,10 @@ proc fiber_grant_memory*(
end_addr end_addr
) )
proc fiber_check_channel_access*(fiber_id: uint64, slot: uint, write: bool): bool = proc fiber_check_channel_access*(fiber_id: uint64, channel_id: uint64, write: bool): bool =
## Check if fiber has channel access via capability ## Check if fiber has Channel capability for given channel_id
let perm = if write: PERM_WRITE else: PERM_READ let perm = if write: PERM_WRITE else: PERM_READ
return cspace_check_perm(fiber_id, slot, perm) return cspace_check_channel(fiber_id, channel_id, perm)
proc fiber_revoke_capability*(fiber_id: uint64, slot: uint) = proc fiber_revoke_capability*(fiber_id: uint64, slot: uint) =
## Revoke a capability from a fiber ## Revoke a capability from a fiber

View File

@ -24,6 +24,10 @@ when defined(riscv64):
const ARCH_NAME* = "riscv64" const ARCH_NAME* = "riscv64"
const CONTEXT_SIZE* = 128 const CONTEXT_SIZE* = 128
const RET_ADDR_INDEX* = 0 # Offset in stack for RA const RET_ADDR_INDEX* = 0 # Offset in stack for RA
elif defined(arm64):
const ARCH_NAME* = "aarch64"
const CONTEXT_SIZE* = 96 # 6 register pairs (x19-x30) * 16 bytes
const RET_ADDR_INDEX* = 11 # x30 (LR) at [sp + 88] = index 11
elif defined(amd64) or defined(x86_64): elif defined(amd64) or defined(x86_64):
const ARCH_NAME* = "amd64" const ARCH_NAME* = "amd64"
const CONTEXT_SIZE* = 64 const CONTEXT_SIZE* = 64
@ -112,7 +116,7 @@ const STACK_SIZE* = 4096
# Fiber State # Fiber State
# ========================================================= # =========================================================
var main_fiber: FiberObject var main_fiber*: FiberObject
var current_fiber* {.global.}: Fiber = addr main_fiber var current_fiber* {.global.}: Fiber = addr main_fiber
# ========================================================= # =========================================================
@ -135,6 +139,9 @@ proc fiber_trampoline() {.cdecl, exportc, noreturn.} =
when defined(riscv64): when defined(riscv64):
while true: while true:
{.emit: "asm volatile(\"wfi\");".} {.emit: "asm volatile(\"wfi\");".}
elif defined(arm64):
while true:
{.emit: "asm volatile(\"wfe\");".}
else: else:
while true: discard while true: discard

View File

@ -91,6 +91,8 @@ type
fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.} fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.}
fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.}
fn_vfs_close*: proc(fd: int32): int32 {.cdecl.} fn_vfs_close*: proc(fd: int32): int32 {.cdecl.}
fn_vfs_dup*: proc(fd: int32, min_fd: int32): int32 {.cdecl.}
fn_vfs_dup2*: proc(old_fd, new_fd: int32): int32 {.cdecl.}
fn_log*: pointer fn_log*: pointer
fn_pledge*: proc(promises: uint64): int32 {.cdecl.} fn_pledge*: proc(promises: uint64): int32 {.cdecl.}
@ -123,6 +125,10 @@ type
net_mac*: array[6, byte] net_mac*: array[6, byte]
reserved_mac*: array[2, byte] reserved_mac*: array[2, byte]
# Project LibWeb: LWF Sovereign Channel (16 bytes)
s_lwf_rx*: ptr HAL_Ring[IonPacket] # Kernel Producer -> User Consumer (LWF frames)
s_lwf_tx*: ptr HAL_Ring[IonPacket] # User Producer -> Kernel Consumer (LWF frames)
include invariant include invariant
# --- Sovereign Logic --- # --- Sovereign Logic ---
@ -167,6 +173,12 @@ var net_rx_hal: HAL_Ring[IonPacket]
var net_tx_hal: HAL_Ring[IonPacket] var net_tx_hal: HAL_Ring[IonPacket]
var netswitch_rx_hal: HAL_Ring[IonPacket] var netswitch_rx_hal: HAL_Ring[IonPacket]
# Project LibWeb: LWF Sovereign Channels
var chan_lwf_rx*: SovereignChannel[IonPacket] # Kernel -> User (LWF frames)
var chan_lwf_tx*: SovereignChannel[IonPacket] # User -> Kernel (LWF frames)
var lwf_rx_hal: HAL_Ring[IonPacket]
var lwf_tx_hal: HAL_Ring[IonPacket]
proc ion_init_input*() {.exportc, cdecl.} = proc ion_init_input*() {.exportc, cdecl.} =
guest_input_hal.head = 0 guest_input_hal.head = 0
guest_input_hal.tail = 0 guest_input_hal.tail = 0
@ -192,6 +204,17 @@ proc ion_init_network*() {.exportc, cdecl.} =
netswitch_rx_hal.mask = 255 netswitch_rx_hal.mask = 255
chan_netswitch_rx.ring = addr netswitch_rx_hal chan_netswitch_rx.ring = addr netswitch_rx_hal
# Project LibWeb: LWF Rings
lwf_rx_hal.head = 0
lwf_rx_hal.tail = 0
lwf_rx_hal.mask = 255
chan_lwf_rx.ring = addr lwf_rx_hal
lwf_tx_hal.head = 0
lwf_tx_hal.tail = 0
lwf_tx_hal.mask = 255
chan_lwf_tx.ring = addr lwf_tx_hal
# Initialize user slab # Initialize user slab
ion_user_slab_init() ion_user_slab_init()
@ -218,4 +241,4 @@ proc ion_user_free_systable*(id: uint16) {.exportc, cdecl.} =
static: doAssert(sizeof(IonPacket) == 24, "IonPacket size mismatch!") static: doAssert(sizeof(IonPacket) == 24, "IonPacket size mismatch!")
static: doAssert(sizeof(CmdPacket) == 32, "CmdPacket size mismatch!") static: doAssert(sizeof(CmdPacket) == 32, "CmdPacket size mismatch!")
static: doAssert(sizeof(SysTable) == 208, "SysTable size mismatch! (Expected 208 after MAC+pad)") static: doAssert(sizeof(SysTable) == 240, "SysTable size mismatch! (Expected 240 after LibWeb LWF channels)")

View File

@ -13,17 +13,24 @@
import ../ring import ../ring
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.} proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc dbg(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len)) var NEWLINE_BUF: array[2, char] = ['\n', '\0']
var nl = "\n"
console_write(unsafeAddr nl[0], csize_t(1)) proc dbg(s: cstring) {.inline.} =
if s != nil:
var i = 0
let p = cast[ptr UncheckedArray[char]](s)
while p[i] != '\0': inc i
console_write(cast[pointer](s), csize_t(i))
console_write(addr NEWLINE_BUF[0], csize_t(1))
const const
SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom) SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom)
POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM) POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM)
POOL_ALIGN* = 4096 # VirtIO/Page Alignment POOL_ALIGN* = 4096 # VirtIO/Page Alignment
SYSTABLE_BASE = 0x83000000'u64 SYSTABLE_BASE = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE
USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000 USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000
USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX
@ -53,9 +60,11 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Initializing Pool...") dbg("[ION] Initializing Pool...")
# 1. Get the VIRTUAL address of the static buffer # 1. Get the VIRTUAL address of the static buffer
dbg("[ION] Step 1: Getting virt addr...")
let virt_addr = cast[uint64](addr global_pool.buffer[0]) let virt_addr = cast[uint64](addr global_pool.buffer[0])
# 2. Translate to PHYSICAL (Identity Mapped for Phase 7) # 2. Translate to PHYSICAL (Identity Mapped for Phase 7)
dbg("[ION] Step 2: Setting base phys...")
global_pool.base_phys = virt_addr global_pool.base_phys = virt_addr
# Tracing for Phase 37 # Tracing for Phase 37
@ -64,12 +73,16 @@ proc ion_pool_init*() {.exportc.} =
kprint_hex(global_pool.base_phys) kprint_hex(global_pool.base_phys)
dbg("") dbg("")
dbg("[ION] Ring Init...") dbg("[ION] Step 3: Ring Init (free_ring)...")
dbg(" 3a: About to call init()")
global_pool.free_ring.init() global_pool.free_ring.init()
dbg(" 3b: free_ring init complete")
dbg("[ION] Step 4: Ring Init (tx_ring)...")
global_tx_ring.init() global_tx_ring.init()
dbg(" 4a: tx_ring init complete")
# Fill the free ring with all indices [0..1023] # Fill the free ring with all indices [0..1023]
dbg("[ION] Filling Slabs...") dbg("[ION] Step 5: Filling Slabs...")
var count = 0 var count = 0
for i in 0 ..< POOL_COUNT: for i in 0 ..< POOL_COUNT:
if global_pool.free_ring.push(uint16(i)): if global_pool.free_ring.push(uint16(i)):
@ -77,6 +90,8 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Pool Ready.") dbg("[ION] Pool Ready.")
proc ion_alloc*(): IonPacket {.exportc.} = proc ion_alloc*(): IonPacket {.exportc.} =
## O(1) Allocation. Returns an empty packet struct. ## O(1) Allocation. Returns an empty packet struct.
## If OOM, returns packet with data = nil ## If OOM, returns packet with data = nil

View File

@ -12,11 +12,22 @@ import fiber, ion, sched, pty, cspace, ontology, fastpath, utcp
import fs/vfs, fs/tar import fs/vfs, fs/tar
import loader/elf import loader/elf
import ../libs/membrane/term import ../libs/membrane/term
import ../libs/membrane/net_glue
const const
MAX_WORKERS* = 8 MAX_WORKERS* = 8
MAX_FIBER_STACK* = 128 * 1024 MAX_FIBER_STACK* = 128 * 1024
SYSTABLE_BASE* = 0x83000000'u64 SYSTABLE_BASE* = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
# Cellular Memory Architecture (M3.3)
CELL0_BASE* = when defined(arm64): 0x48000000'u64
else: 0x88000000'u64
CELL1_BASE* = when defined(arm64): 0x4C000000'u64
else: 0x8C000000'u64
USER_VA_BASE* = when defined(arm64): 0x48000000'u64
else: 0x88000000'u64
USER_SP_FALLBACK* = when defined(arm64): 0x4BFFFFF0'u64
else: 0x8BFFFFF0'u64
# Export Nim Timer Handler for HAL (Zig calls this) # Export Nim Timer Handler for HAL (Zig calls this)
proc rumpk_timer_handler() {.exportc, cdecl, used.} = proc rumpk_timer_handler() {.exportc, cdecl, used.} =
@ -35,6 +46,7 @@ proc nexshell_main() {.importc, cdecl.}
proc console_poll() {.importc, cdecl.} proc console_poll() {.importc, cdecl.}
proc ion_get_virt(id: uint16): uint64 {.importc, cdecl.} proc ion_get_virt(id: uint16): uint64 {.importc, cdecl.}
# InitRD Symbols # InitRD Symbols
var initrd_start {.importc: "_initrd_start" .}: byte var initrd_start {.importc: "_initrd_start" .}: byte
var initrd_end {.importc: "_initrd_end" .}: byte var initrd_end {.importc: "_initrd_end" .}: byte
@ -151,7 +163,8 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 =
if phdr.p_type == PT_LOAD: if phdr.p_type == PT_LOAD:
# Enable S-mode access to U-mode pages (SUM=1) # Enable S-mode access to U-mode pages (SUM=1)
{.emit: """asm volatile ("li t1, 0x40000; csrs sstatus, t1" : : : "t1");""" .} when defined(riscv64):
{.emit: """asm volatile ("li t1, 0x40000; csrs sstatus, t1" : : : "t1");""" .}
let dest = cast[ptr UncheckedArray[byte]](phdr.p_vaddr) let dest = cast[ptr UncheckedArray[byte]](phdr.p_vaddr)
@ -168,11 +181,139 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 =
# {.emit: """asm volatile ("li t1, 0x40000; csrc sstatus, t1" : : : "t1");""" .} # {.emit: """asm volatile ("li t1, 0x40000; csrc sstatus, t1" : : : "t1");""" .}
# ⚡ ARCH-SYNC: Flush I-Cache after loading new code # ⚡ ARCH-SYNC: Flush I-Cache after loading new code
{.emit: """asm volatile ("fence.i" : : : "memory");""" .} when defined(riscv64):
{.emit: """asm volatile ("fence.i" : : : "memory");""" .}
elif defined(arm64):
{.emit: """asm volatile ("ic iallu; dsb ish; isb" : : : "memory");""" .}
discard ion_vfs_close(fd) discard ion_vfs_close(fd)
return ehdr.e_entry return ehdr.e_entry
# --- M4.4: MANIFEST-DRIVEN CAPABILITY LOADING ---
proc set_pledge*(f: var FiberObject, pledge: uint64) =
## Set fiber pledge mask, preserving Spectrum bits [63:62]
f.promises = (f.promises and 0xC000000000000000'u64) or (pledge and 0x3FFFFFFFFFFFFFFF'u64)
proc kload_manifest_tar(path: cstring): ManifestResult =
## Scan ELF section headers via TAR for .nexus.manifest containing BKDL data.
## Uses tar.vfs_read_at() for selective reads (same approach as kload_phys).
result.header = nil
result.caps = nil
result.count = 0
# Strip /sysro prefix for TAR paths (same logic as kload_phys)
var tar_path = path
if path[0] == '/':
tar_path = cast[cstring](cast[uint64](path) + 1)
if k_starts_with(path, "/sysro"):
tar_path = cast[cstring](cast[uint64](path) + 6)
if tar_path[0] == '/': tar_path = cast[cstring](cast[uint64](tar_path) + 1)
# 1. Read ELF header
var ehdr: Elf64_Ehdr
if tar.vfs_read_at(tar_path, addr ehdr, uint64(sizeof(ehdr)), 0) != int64(sizeof(ehdr)):
return
if ehdr.e_ident[0] != 0x7F or ehdr.e_ident[1] != 'E'.uint8:
return
if ehdr.e_shoff == 0 or ehdr.e_shnum == 0 or ehdr.e_shstrndx >= ehdr.e_shnum:
return
# 2. Read shstrtab section header to get string table location
let strtab_offset = ehdr.e_shoff + uint64(ehdr.e_shstrndx) * uint64(ehdr.e_shentsize)
var strtab_shdr: Elf64_Shdr
if tar.vfs_read_at(tar_path, addr strtab_shdr, uint64(sizeof(Elf64_Shdr)), strtab_offset) != int64(sizeof(Elf64_Shdr)):
return
# 3. Read the string table itself (cap at 512 bytes — section names are short)
var strtab_buf: array[512, byte]
let strtab_read_size = min(strtab_shdr.sh_size, 512'u64)
if tar.vfs_read_at(tar_path, addr strtab_buf[0], strtab_read_size, strtab_shdr.sh_offset) != int64(strtab_read_size):
return
# 4. Scan section headers for .nexus.manifest
let target = cstring(".nexus.manifest")
for i in 0 ..< int(ehdr.e_shnum):
var shdr: Elf64_Shdr
let sh_offset = ehdr.e_shoff + uint64(i) * uint64(ehdr.e_shentsize)
if tar.vfs_read_at(tar_path, addr shdr, uint64(sizeof(Elf64_Shdr)), sh_offset) != int64(sizeof(Elf64_Shdr)):
continue
if shdr.sh_name < uint32(strtab_read_size):
# Compare section name against ".nexus.manifest"
var match = true
var j = 0
while j < 16: # len(".nexus.manifest") + null = 16
let ch = target[j]
let idx = int(shdr.sh_name) + j
if idx >= int(strtab_read_size):
match = false
break
if ch == '\0':
break # End of target string — all matched
if strtab_buf[idx] != byte(ch):
match = false
break
j += 1
if match and shdr.sh_size >= uint64(sizeof(BkdlHeader)):
# 5. Read the manifest section data into a static buffer
# Max manifest size: BkdlHeader (118) + 16 caps * CapDescriptor (12) = 310 bytes
var manifest_buf {.global.}: array[512, byte]
let read_size = min(shdr.sh_size, 512'u64)
if tar.vfs_read_at(tar_path, addr manifest_buf[0], read_size, shdr.sh_offset) != int64(read_size):
return
let hdr = cast[ptr BkdlHeader](addr manifest_buf[0])
if hdr.magic != BKDL_MAGIC or hdr.version != BKDL_VERSION:
kprintln("[Manifest] Invalid BKDL magic/version")
return
let expected = uint64(sizeof(BkdlHeader)) + uint64(hdr.cap_count) * uint64(sizeof(CapDescriptor))
if expected > read_size:
kprintln("[Manifest] BKDL cap_count exceeds section size")
return
kprint("[Manifest] WARNING: Signature unchecked (dev mode)")
kprintln("")
discard emit_access_denied(0, 0xB0D1, 0, 0) # STL: signature skip event
result.header = hdr
result.caps = cast[ptr UncheckedArray[CapDescriptor]](addr manifest_buf[sizeof(BkdlHeader)])
result.count = int(hdr.cap_count)
return
proc apply_manifest*(fiber_id: uint64, manifest: ManifestResult, pledge_out: var uint64) =
## Apply BKDL manifest capabilities to fiber's CSpace.
## Derives pledge mask from requested capability types.
var derived_pledge: uint64 = PLEDGE_STDIO # Always grant basic I/O
for i in 0 ..< manifest.count:
let cap = manifest.caps[i]
let slot = fiber_grant_channel(fiber_id, cap.resource_id, cap.perms)
if slot >= 0:
discard emit_capability_grant(fiber_id, cap.cap_type, cap.resource_id, uint8(slot), 0)
kprint("[Manifest] Granted cap "); kprint_hex(cap.resource_id)
kprint(" perms="); kprint_hex(uint64(cap.perms))
kprint(" to fiber "); kprint_hex(fiber_id); kprintln("")
else:
discard emit_access_denied(fiber_id, cap.resource_id, cap.perms, 0)
kprint("[Manifest] DENIED cap "); kprint_hex(cap.resource_id)
kprint(" for fiber "); kprint_hex(fiber_id); kprintln(" (CSpace full)")
# Derive pledge bits from capability types
if cap.resource_id == 0x2000: # VFS
derived_pledge = derived_pledge or PLEDGE_RPATH
if (cap.perms and PERM_WRITE) != 0:
derived_pledge = derived_pledge or PLEDGE_WPATH
elif cap.resource_id == 0x500 or cap.resource_id == 0x501: # NET_TX / NET_RX
derived_pledge = derived_pledge or PLEDGE_INET
elif cap.resource_id == 0x1000 or cap.resource_id == 0x1001: # Console
discard # PLEDGE_STDIO already set
pledge_out = derived_pledge
# --- FIBER ENTRIES --- # --- FIBER ENTRIES ---
proc subject_fiber_entry() {.cdecl.} = proc subject_fiber_entry() {.cdecl.} =
@ -185,13 +326,26 @@ proc subject_fiber_entry() {.cdecl.} =
# Load into Cellular Slot (phys_offset) # Load into Cellular Slot (phys_offset)
let entry_addr = kload_phys(cast[cstring](addr subject_loading_path[0]), current_fiber.phys_offset) let entry_addr = kload_phys(cast[cstring](addr subject_loading_path[0]), current_fiber.phys_offset)
# M4.4: Scan ELF for BKDL manifest and apply capabilities
let loading_path = cast[cstring](addr subject_loading_path[0])
let manifest = kload_manifest_tar(loading_path)
if manifest.header != nil:
kprint("[Manifest] Found BKDL manifest: "); kprint_hex(uint64(manifest.count)); kprintln(" capabilities")
var pledge: uint64 = 0
apply_manifest(current_fiber.id, manifest, pledge)
set_pledge(current_fiber[], pledge)
kprint("[Manifest] Pledge mask: "); kprint_hex(pledge); kprintln("")
else:
kprintln("[Manifest] No BKDL manifest — default policy (STDIO only)")
set_pledge(current_fiber[], PLEDGE_STDIO)
if entry_addr != 0: if entry_addr != 0:
kprint("[Subject:"); kprint_hex(fid); kprint("] Entering Payload at: "); kprint_hex(entry_addr); kprintln("") kprint("[Subject:"); kprint_hex(fid); kprint("] Entering Payload at: "); kprint_hex(entry_addr); kprintln("")
proc hal_enter_userland(entry, systable, sp: uint64) {.importc, cdecl.} proc hal_enter_userland(entry, systable, sp: uint64) {.importc, cdecl.}
var sp = current_fiber.user_sp_init var sp = current_fiber.user_sp_init
if sp == 0: if sp == 0:
# Fallback (Legacy/Init) - Top of the 64MB Sentinel Cell # Fallback (Legacy/Init) - Top of the 64MB Sentinel Cell
sp = 0x8BFFFFF0'u64 sp = USER_SP_FALLBACK
kprintln("╔════════════════════════════════════════════════════╗") kprintln("╔════════════════════════════════════════════════════╗")
kprintln("║ PRE-FLIGHT: USERLAND TRANSITION ║") kprintln("║ PRE-FLIGHT: USERLAND TRANSITION ║")
@ -232,51 +386,71 @@ proc compositor_fiber_entry() {.cdecl.} =
proc mm_create_worker_map(stack_base: uint64, stack_size: uint64, packet_addr: uint64, phys_base: uint64, region_size: uint64): uint64 {.importc, cdecl.} proc mm_create_worker_map(stack_base: uint64, stack_size: uint64, packet_addr: uint64, phys_base: uint64, region_size: uint64): uint64 {.importc, cdecl.}
proc setup_mksh_stack(stack_base: pointer, stack_size: int): uint64 = proc setup_cellular_stack(phys_base: uint64, slot_size: uint64, user_base: uint64): uint64 =
var sp = cast[uint64](stack_base) + cast[uint64](stack_size) # Stack grows down from the top of the cell
sp = sp and not 15'u64 # Align 16 var phys_top = phys_base + slot_size
var user_sp = user_base + slot_size
# Term String # Align 16
let term_str = "TERM=nexus\0" phys_top = phys_top and not 15'u64
sp -= uint64(term_str.len) user_sp = user_sp and not 15'u64
copyMem(cast[pointer](sp), unsafeAddr term_str[0], term_str.len)
let term_addr = sp
# Path String # Helper to push data
let path_str = "/bin/mksh\0" template push_str(s: string): uint64 =
sp -= uint64(path_str.len) let s_val = s
copyMem(cast[pointer](sp), unsafeAddr path_str[0], path_str.len) let l = uint64(s_val.len) + 1 # + null terminator
let path_addr = sp phys_top -= l
user_sp -= l
# Zero the memory first (for null safety)
k_zero_mem(cast[pointer](phys_top), l)
copyMem(cast[pointer](phys_top), unsafeAddr s_val[0], s_val.len)
user_sp # Return USER address
sp = sp and not 15'u64 # Align 16 # Environment strings
let home_addr = push_str("HOME=/")
let term_addr = push_str("TERM=vt100")
let path_addr = push_str("/bin/mksh")
kprint("[Stack] HOME env at: "); kprint_hex(home_addr); kprint("\n")
kprint("[Stack] TERM env at: "); kprint_hex(term_addr); kprint("\n")
kprint("[Stack] argv[0] at: "); kprint_hex(path_addr); kprint("\n")
# Align 16 before pointer arrays
phys_top = phys_top and not 15'u64
user_sp = user_sp and not 15'u64
# Helper to push uint64
template push_u64(val: uint64) =
phys_top -= 8
user_sp -= 8
cast[ptr uint64](phys_top)[] = val
# Auxv (0, 0) # Auxv (0, 0)
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = 0 push_u64(0)
cast[ptr uint64](sp+8)[] = 0
# Envp (term, NULL) # Envp (NULL, TERM, HOME) - Reverse order of push
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = term_addr push_u64(term_addr)
cast[ptr uint64](sp+8)[] = 0 push_u64(home_addr)
# Argv (path, NULL) # Argv (NULL, path)
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = path_addr push_u64(path_addr)
cast[ptr uint64](sp+8)[] = 0
# Argc (1) # Argc (1)
sp -= 8 push_u64(1)
cast[ptr uint64](sp)[] = 1
return sp kprint("[Stack] Final SP: "); kprint_hex(user_sp); kprint("\n")
return user_sp
proc ion_fiber_entry() {.cdecl.} = proc ion_fiber_entry() {.cdecl.} =
kprintln("[ION] Fiber Entry reached.") kprintln("[ION] Fiber Entry reached.")
while true: while true:
var pkt: CmdPacket var pkt: CmdPacket
if chan_cmd.recv(pkt): if chan_cmd.recv(pkt):
kprint("[ION] Received Packet Kind: "); kprint_hex(uint64(pkt.kind)) kprint("[ION] Received Packet Kind: "); kprint_hex(uint64(pkt.kind)); kprintln("")
case CmdType(pkt.kind): case CmdType(pkt.kind):
of CMD_SYS_EXIT: of CMD_SYS_EXIT:
kprintln("[ION] Restarting Subject...") kprintln("[ION] Restarting Subject...")
@ -300,21 +474,31 @@ proc ion_fiber_entry() {.cdecl.} =
kprintln("[ION] Failed to allocate PTY for child!") kprintln("[ION] Failed to allocate PTY for child!")
# Re-initialize fiber_child with the requested binary # Re-initialize fiber_child with the requested binary
# Note: stack_child is used for KERNEL context switching, not user stack.
init_fiber(addr fiber_child, subject_fiber_entry, addr stack_child[0], sizeof(stack_child)) init_fiber(addr fiber_child, subject_fiber_entry, addr stack_child[0], sizeof(stack_child))
# Phase 40: Set PTY & Stack
fiber_child.pty_id = pid
fiber_child.user_sp_init = setup_mksh_stack(addr stack_child[0], sizeof(stack_child))
# 🏛️ CELLULAR ALLOCATION (SPEC-202 Rev 2) # 🏛️ CELLULAR ALLOCATION (SPEC-202 Rev 2)
# Sentinel (Init) takes 0x88000000 - 0x8C000000 (64MB) # Sentinel (Init) takes Cell 0, Mksh (First Child) takes Cell 1
# Mksh (First Child) starts in the 'Wild' at 0x8C000000 let cell_base = CELL1_BASE
let cell_base = 0x8C000000'u64 let user_base = USER_VA_BASE
let cell_size = 64 * 1024 * 1024'u64
# Phase 40: Set PTY & User Stack
fiber_child.pty_id = pid
fiber_child.phys_offset = cell_base fiber_child.phys_offset = cell_base
# Allocate 64MB Slot for Mksh (Needs >32MB for BSS) # Setup User Stack in CELLULAR Memory (User Top)
let cell_size = 64 * 1024 * 1024'u64 # We write to Physical Address (cell_base + size), but Mksh sees (user_base + size)
fiber_child.satp_value = mm_create_worker_map(cast[uint64](addr stack_child[0]), uint64(sizeof(stack_child)), SYSTABLE_BASE, cell_base, cell_size) fiber_child.user_sp_init = setup_cellular_stack(cell_base, cell_size, user_base)
# Create Map avoiding Kernel Stack exposure
# We pass 0 as stack_base to skip mapping stack_child into user space.
fiber_child.satp_value = mm_create_worker_map(0, 0, SYSTABLE_BASE, cell_base, cell_size)
# M4.4: Child capabilities are applied in subject_fiber_entry via BKDL manifest.
# No hardcoded grants needed here — manifest drives CSpace + pledge.
# M4.5: Set budget based on child's Spectrum tier
fiber_child.budget_ns = default_budget_for_spectrum((addr fiber_child).getSpectrum())
kprintln("[ION] Child fiber spawned successfully") kprintln("[ION] Child fiber spawned successfully")
else: discard else: discard
else: else:
@ -326,8 +510,12 @@ proc rumpk_yield_internal*() {.exportc, cdecl.} =
switch(active_fibers_arr[6]) switch(active_fibers_arr[6])
proc fiber_yield*() {.exportc, cdecl.} = proc fiber_yield*() {.exportc, cdecl.} =
current_fiber.wants_yield = true # Return to the dispatcher context (main_fiber)
rumpk_yield_internal() # kprint("[Y"); kprint_hex(current_fiber.id); kprint("]")
switch(addr main_fiber)
# The `ld_internal()` part from the instruction was syntactically incorrect
# and likely a typo or incomplete instruction.
# Assuming the intent was to replace the previous yield mechanism with a direct switch.
proc fiber_netswitch_entry() {.cdecl.} = proc fiber_netswitch_entry() {.cdecl.} =
kprintln("[NetSwitch] Traffic Engine Online") kprintln("[NetSwitch] Traffic Engine Online")
@ -343,6 +531,10 @@ proc fiber_netswitch_entry() {.cdecl.} =
kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.") kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.")
# Initialize LwIP Membrane (DHCP, Netif, DNS)
membrane_init()
kprintln("[NetSwitch] Membrane initialized — DHCP running")
while true: while true:
var pkt: IonPacket var pkt: IonPacket
@ -370,6 +562,9 @@ proc fiber_netswitch_entry() {.cdecl.} =
var res = ion_tx_push(pkt) var res = ion_tx_push(pkt)
if not res: kprintln("[NetSwitch] Drop (TX Full)") if not res: kprintln("[NetSwitch] Drop (TX Full)")
# Drive LwIP timers + RX ingestion (DHCP, ARP, TCP, ICMP)
pump_membrane_stack()
# Poll Network # Poll Network
virtio_net_poll() virtio_net_poll()
@ -378,7 +573,6 @@ proc fiber_netswitch_entry() {.cdecl.} =
# Prevent Starvation # Prevent Starvation
fiber_sleep(10) # 10ms - allow DHCP state machine to execute fiber_sleep(10) # 10ms - allow DHCP state machine to execute
# 10ms - allow DHCP state machine to execute
proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} = proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} =
## Handle packet from Network Driver ## Handle packet from Network Driver
@ -397,15 +591,35 @@ proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} =
if not chan_netswitch_rx.send(pkt): if not chan_netswitch_rx.send(pkt):
ion_free_raw(id) ion_free_raw(id)
# --- SCHEDULER STATE ---
# --- SCHEDULER STATE ---
proc ion_push_stdin*(p: pointer, len: csize_t) {.exportc, cdecl.} = proc ion_push_stdin*(p: pointer, len: csize_t) {.exportc, cdecl.} =
if chan_input.ring == nil: return if chan_input.ring == nil: return
var pkt = ion_alloc() var pkt = ion_alloc()
if pkt.data == nil: return if pkt.data == nil:
let to_copy = if int(len) < 2048: int(len) else: 2048 # kprintln("[ION Push] CRITICAL: Slab allocation failed!")
copyMem(pkt.data, p, to_copy) return
pkt.len = uint16(to_copy)
if fiber_subject.sleep_until == 0xFFFFFFFFFFFFFFFF'u64: fiber_subject.sleep_until = 0 pkt.data[0] = cast[ptr byte](p)[]
discard chan_input.send(pkt) pkt.len = 1
var wake_count = 0
if chan_input.send(pkt):
# Wake up any fibers waiting for terminal input
for i in 0..<16:
let f = active_fibers_arr[i]
if f != nil and f.is_blocked and (f.blocked_on_mask and 0x1000) != 0:
f.is_blocked = false
f.blocked_on_mask = 0
f.sleep_until = 0 # Wake immediately
wake_count += 1
if wake_count > 0:
# kprint("[ION] Woke "); kprint_hex(uint64(wake_count)); kprintln(" fibers")
discard
proc k_check_deferred_yield*() {.exportc, cdecl.} = proc k_check_deferred_yield*() {.exportc, cdecl.} =
if current_fiber != nil and current_fiber.wants_yield: if current_fiber != nil and current_fiber.wants_yield:
@ -449,8 +663,14 @@ proc k_handle_exception*(scause, sepc, stval: uint) {.exportc, cdecl.} =
kprintln("") kprintln("")
kprintln("[IMMUNE] System HALTING (Trap Loop Prevention).") kprintln("[IMMUNE] System HALTING (Trap Loop Prevention).")
while true: when defined(riscv64):
{.emit: "asm volatile(\"wfi\");".} while true:
{.emit: "asm volatile(\"wfi\");".}
elif defined(arm64):
while true:
{.emit: "asm volatile(\"wfe\");".}
else:
while true: discard
proc k_get_current_satp*(): uint64 {.exportc, cdecl.} = proc k_get_current_satp*(): uint64 {.exportc, cdecl.} =
if current_fiber != nil: if current_fiber != nil:
@ -460,9 +680,40 @@ proc k_get_current_satp*(): uint64 {.exportc, cdecl.} =
proc wrapper_vfs_write(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} = proc wrapper_vfs_write(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} =
return ion_vfs_write(fd, buf, count) return ion_vfs_write(fd, buf, count)
# --- M4: PLEDGE ENFORCEMENT ---
proc check_pledge(nr: uint, f: Fiber): bool =
## Returns true if syscall is allowed by fiber's pledge mask
let pledges = f.promises and 0x3FFFFFFFFFFFFFFF'u64
if pledges == PLEDGE_ALL: return true # Unrestricted
case nr:
of 0x01, 0x65, 0x66, 0x100, 0x101, 0x102:
# exit, nanosleep, get_time, yield, pledge, wait_multi — always allowed
return true
of 0x203, 0x204: # READ, WRITE
return (pledges and PLEDGE_STDIO) != 0
of 0x200, 0x202: # OPEN, LIST
return (pledges and PLEDGE_RPATH) != 0
of 0x201, 0x205, 0x206, 0x207: # CLOSE, IOCTL, FCNTL, DUP2
return (pledges and PLEDGE_STDIO) != 0
of 0x600, 0x300: # EXECV, SPAWN_FIBER
return (pledges and PLEDGE_EXEC) != 0
of 0x905: # SYS_SOCK_RESOLVE
return (pledges and PLEDGE_INET) != 0
else:
return true # Unknown syscalls pass through
# --- SYSCALL HANDLER --- # --- SYSCALL HANDLER ---
proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} = proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} =
# M4: Pledge enforcement gate
if current_fiber != nil and not check_pledge(nr, current_fiber):
kprint("[DENIED] Fiber "); kprint_hex(current_fiber.id)
kprint(" pledge violation: syscall "); kprint_hex(uint64(nr)); kprintln("")
discard emit_access_denied(current_fiber.id, uint64(nr), 0, 0)
return 0xFFFFFFFFFFFFFFFF'u # -1 EPERM
if nr != 0x100 and nr != 0x205 and nr != 0x204 and nr != 0x203: if nr != 0x100 and nr != 0x205 and nr != 0x204 and nr != 0x203:
kprint("[Syscall] NR: "); kprint_hex(uint64(nr)); kprintln("") kprint("[Syscall] NR: "); kprint_hex(uint64(nr)); kprintln("")
@ -484,60 +735,113 @@ proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} =
of 0x100: # YIELD of 0x100: # YIELD
fiber_yield() fiber_yield()
return 0 return 0
of 0x101: # SYS_PLEDGE (OpenBSD semantics: can only narrow, never widen)
let current = current_fiber.promises and 0x3FFFFFFFFFFFFFFF'u64
let requested = a0 and 0x3FFFFFFFFFFFFFFF'u64
if (requested and (not current)) != 0:
kprint("[DENIED] Fiber "); kprint_hex(current_fiber.id); kprintln(" pledge widen attempt")
discard emit_access_denied(current_fiber.id, 0x101, 0, 0)
return 0xFFFFFFFFFFFFFFFF'u # -1 EPERM
current_fiber.promises = (current_fiber.promises and 0xC000000000000000'u64) or requested
return 0
of 0x102: # SYS_WAIT_MULTI (Silence Doctrine) of 0x102: # SYS_WAIT_MULTI (Silence Doctrine)
current_fiber.blocked_on_mask = a0 current_fiber.blocked_on_mask = a0
current_fiber.is_blocked = true current_fiber.is_blocked = true
fiber_yield() fiber_yield()
return 0 return 0
of 0x200: # OPEN of 0x200: # OPEN
# return uint(libc_impl.libc_impl_open(cast[cstring](a0), int(a1))) # M4: Check VFS capability (channel 0x2000)
return 0 if current_fiber != nil and current_fiber.cspace_id < 16:
let write_mode = (a1 and 1) != 0 # O_WRONLY or O_RDWR
let needed_perm = if write_mode: PERM_WRITE else: PERM_READ
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, needed_perm):
discard emit_access_denied(current_fiber.id, 0x2000, needed_perm, 0)
return 0xFFFFFFFFFFFFFFFF'u
let path = cast[cstring](a0)
let result = ion_vfs_open(path, int32(a1))
kprint("[OPEN] path="); kprint(path); kprint(" result="); kprint_hex(uint64(result)); kprint(" returning...\n")
let ret_val = uint(result)
kprint("[OPEN] About to return value: "); kprint_hex(ret_val); kprint("\n")
return ret_val
of 0x201: # CLOSE of 0x201: # CLOSE
# return uint(libc_impl.libc_impl_close(int(a0))) kprint("[CLOSE] fd="); kprint_hex(a0); kprint("\n")
return 0 return uint(ion_vfs_close(int32(a0)))
of 0x202: # LIST of 0x202: # LIST
# M4: Check VFS read capability (channel 0x2000)
if current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
return uint(ion_vfs_list(cast[pointer](a0), uint64(a1))) return uint(ion_vfs_list(cast[pointer](a0), uint64(a1)))
of 0x205: # IOCTL
kprint("[IOCTL] fd="); kprint_hex(a0); kprint(" request="); kprint_hex(a1); kprint("\n")
return 0 # stub
of 0x206: # FCNTL
kprint("[FCNTL] fd="); kprint_hex(a0); kprint(" cmd="); kprint_hex(a1); kprint("\n")
if a1 == 0: # F_DUPFD
return uint(ion_vfs_dup(int32(a0), int32(a2)))
return 0
of 0x207: # DUP2
return uint(ion_vfs_dup2(int32(a0), int32(a1)))
of 0x905: # SYS_SOCK_RESOLVE of 0x905: # SYS_SOCK_RESOLVE
# TODO: Implement getaddrinfo kernel integration # TODO: Implement getaddrinfo kernel integration
return 0 # Not implemented yet return 0 # Not implemented yet
of 0x203: # READ of 0x203: # READ
# kprint("[Syscall] READ(fd="); kprint_hex(a0); kprint(")\n") # M4: Check console.input capability for stdin (fd 0)
var vres = -2 if a0 == 0 and current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x1000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x1000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
# M4: Check VFS capability for file reads (fd >= 3)
if a0 >= 3 and current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
var vres: int64 = -2
if a0 >= 3:
vres = ion_vfs_read(int32(a0), cast[pointer](a1), uint64(a2))
# Fallback to PTY if FD=0 or VFS returned TTY mode (-2)
if a0 == 0 or vres == -2: if a0 == 0 or vres == -2:
let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0 let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0
while true: while true:
if pty_has_data_for_slave(pid): if pty_has_data_for_slave(pid):
var buf: array[1, byte] var buf: array[1, byte]
let n = pty_read_slave(PTY_SLAVE_BASE + pid, addr buf[0], 1) let n = pty_read_slave(int(PTY_SLAVE_BASE + pid), addr buf[0], 1)
if n > 0: if n > 0:
# kprint("[Kernel] READ delivered PTY byte: "); kprint_hex8(buf[0]); kprint("\n") cast[ptr UncheckedArray[byte]](a1)[0] = buf[0]
cast[ptr UncheckedArray[byte]](a1)[0] = buf[0] return 1
return 1
var pkt: IonPacket var pkt: IonPacket
if chan_input.recv(pkt): if chan_input.recv(pkt):
kprint("[Kernel] Got Input Packet of len: "); kprint_hex(uint64(pkt.len)); kprint("\n") for i in 0..<int(pkt.len):
let n = if uint64(pkt.len) < a2: uint64(pkt.len) else: a2 pty_push_input(pid, char(pkt.data[i]))
if n > 0: ion_free(pkt)
let data = cast[ptr UncheckedArray[byte]](pkt.data)
for i in 0 ..< int(n):
kprint(" Input Char: "); kprint(cast[cstring](unsafeAddr data[i])); kprint("\n")
pty_push_input(pid, char(data[i]))
ion_free_raw(pkt.id)
# Loop again to read from PTY
else: else:
current_fiber.sleep_until = 0xFFFFFFFFFFFFFFFF'u64 current_fiber.is_blocked = true
current_fiber.blocked_on_mask = 0x1000 # Terminal Input
current_fiber.sleep_until = 0xFFFFFFFFFFFFFFFF'u64 # Infinite
fiber_yield() fiber_yield()
return uint(vres) return uint(vres)
of 0x204: # WRITE of 0x204: # WRITE
if a0 == 1 or a0 == 2: if a0 == 1 or a0 == 2:
# M4: Check console.output capability (channel 0x1001)
if current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x1001, PERM_WRITE):
discard emit_access_denied(current_fiber.id, 0x1001, PERM_WRITE, 0)
return 0xFFFFFFFFFFFFFFFF'u
console_write(cast[pointer](a1), csize_t(a2)) console_write(cast[pointer](a1), csize_t(a2))
let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0 let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0
discard pty_write_slave(PTY_SLAVE_BASE + pid, cast[ptr byte](a1), int(a2)) discard pty_write_slave(PTY_SLAVE_BASE + pid, cast[ptr byte](a1), int(a2))
return a2 return a2
# var vres = libc_impl.libc_impl_write(int(a0), cast[pointer](a1), uint64(a2)) elif a0 >= 3:
var vres = -1 # M4: Check VFS write capability (channel 0x2000)
return uint(vres) if current_fiber != nil and current_fiber.cspace_id < 16:
of 0x205: return 0 # IOCTL stub if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_WRITE):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_WRITE, 0)
return 0xFFFFFFFFFFFFFFFF'u
return uint(ion_vfs_write(int32(a0), cast[pointer](a1), uint64(a2)))
return 0xFFFFFFFFFFFFFFFF'u # -1
of 0x600: # EXECV (Legacy - use SYS_SPAWN_FIBER instead) of 0x600: # EXECV (Legacy - use SYS_SPAWN_FIBER instead)
# Manual copy path to subject_loading_path # Manual copy path to subject_loading_path
let p = cast[ptr UncheckedArray[char]](a0) let p = cast[ptr UncheckedArray[char]](a0)
@ -585,8 +889,6 @@ proc ion_wait_multi*(mask: uint64): int32 {.exportc, cdecl.} =
proc kmain() {.exportc, cdecl.} = proc kmain() {.exportc, cdecl.} =
var next_mmio_addr {.importc: "virtio_pci_next_mmio_addr", nodecl.}: uint32 var next_mmio_addr {.importc: "virtio_pci_next_mmio_addr", nodecl.}: uint32
kprint("\n[Kernel] next_mmio_addr check: ")
kprint_hex(uint64(next_mmio_addr))
kprintln("\nNexus Sovereign Core v1.1.2 Starting...") kprintln("\nNexus Sovereign Core v1.1.2 Starting...")
# HAL Hardware Inits # HAL Hardware Inits
@ -636,6 +938,8 @@ proc kmain() {.exportc, cdecl.} =
sys.fn_vfs_read = ion_vfs_read sys.fn_vfs_read = ion_vfs_read
sys.fn_vfs_list = ion_vfs_list sys.fn_vfs_list = ion_vfs_list
sys.fn_vfs_write = wrapper_vfs_write sys.fn_vfs_write = wrapper_vfs_write
sys.fn_vfs_dup = ion_vfs_dup
sys.fn_vfs_dup2 = ion_vfs_dup2
sys.fn_wait_multi = ion_wait_multi sys.fn_wait_multi = ion_wait_multi
# Point to user slab allocator (shared memory) instead of kernel pool # Point to user slab allocator (shared memory) instead of kernel pool
sys.fn_ion_alloc = ion_user_alloc_systable sys.fn_ion_alloc = ion_user_alloc_systable
@ -669,11 +973,16 @@ proc kmain() {.exportc, cdecl.} =
chan_net_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xC000) chan_net_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xC000)
chan_net_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xE000) chan_net_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xE000)
# Project LibWeb: LWF Rings (after user slab at 0x110000)
chan_lwf_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0x110000)
chan_lwf_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0x112000)
# Initialize Shared Memory Rings # Initialize Shared Memory Rings
chan_rx.ring.mask = 255; chan_tx.ring.mask = 255 chan_rx.ring.mask = 255; chan_tx.ring.mask = 255
ring_event.mask = 255; chan_cmd.ring.mask = 255 ring_event.mask = 255; chan_cmd.ring.mask = 255
chan_input.ring.mask = 255 chan_input.ring.mask = 255
chan_net_rx.ring.mask = 255; chan_net_tx.ring.mask = 255 chan_net_rx.ring.mask = 255; chan_net_tx.ring.mask = 255
chan_lwf_rx.ring.mask = 255; chan_lwf_tx.ring.mask = 255
# Force reset pointers to zero # Force reset pointers to zero
chan_rx.ring.head = 0; chan_rx.ring.tail = 0 chan_rx.ring.head = 0; chan_rx.ring.tail = 0
chan_tx.ring.head = 0; chan_tx.ring.tail = 0 chan_tx.ring.head = 0; chan_tx.ring.tail = 0
@ -682,6 +991,8 @@ proc kmain() {.exportc, cdecl.} =
chan_input.ring.head = 0; chan_input.ring.tail = 0 chan_input.ring.head = 0; chan_input.ring.tail = 0
chan_net_rx.ring.head = 0; chan_net_rx.ring.tail = 0 chan_net_rx.ring.head = 0; chan_net_rx.ring.tail = 0
chan_net_tx.ring.head = 0; chan_net_tx.ring.tail = 0 chan_net_tx.ring.head = 0; chan_net_tx.ring.tail = 0
chan_lwf_rx.ring.head = 0; chan_lwf_rx.ring.tail = 0
chan_lwf_tx.ring.head = 0; chan_lwf_tx.ring.tail = 0
sys.s_rx = chan_rx.ring; sys.s_tx = chan_tx.ring; sys.s_event = ring_event sys.s_rx = chan_rx.ring; sys.s_tx = chan_tx.ring; sys.s_event = ring_event
sys.s_cmd = chan_cmd.ring; sys.s_input = chan_input.ring sys.s_cmd = chan_cmd.ring; sys.s_input = chan_input.ring
@ -690,6 +1001,10 @@ proc kmain() {.exportc, cdecl.} =
sys.s_net_rx = chan_net_rx.ring sys.s_net_rx = chan_net_rx.ring
sys.s_net_tx = chan_net_tx.ring sys.s_net_tx = chan_net_tx.ring
# Project LibWeb: Map LWF Rings
sys.s_lwf_rx = chan_lwf_rx.ring
sys.s_lwf_tx = chan_lwf_tx.ring
sys.magic = 0x4E585553 sys.magic = 0x4E585553
sys.fb_addr = fb_kern_get_addr() sys.fb_addr = fb_kern_get_addr()
sys.fb_width = 1920; sys.fb_height = 1080; sys.fb_stride = 1920 * 4; sys.fb_bpp = 32 sys.fb_width = 1920; sys.fb_height = 1080; sys.fb_stride = 1920 * 4; sys.fb_bpp = 32
@ -726,45 +1041,51 @@ proc kmain() {.exportc, cdecl.} =
discard emit_capability_grant(2, 2, 0x1001, 1, shell_spawn_id) # Log event discard emit_capability_grant(2, 2, 0x1001, 1, shell_spawn_id) # Log event
kprintln("[CSpace] Granted console capabilities to NexShell") kprintln("[CSpace] Granted console capabilities to NexShell")
# Grant console output to Subject (fiber 4) # M4.4: Subject (fiber 4) capabilities are now manifest-driven.
discard fiber_grant_channel(4, 0x1001, PERM_WRITE) # console.output (write-only) # The BKDL manifest in the ELF binary declares what it needs.
discard emit_capability_grant(4, 2, 0x1001, 0, subject_spawn_id) # Log event # Grants are applied in subject_fiber_entry via kload_manifest_tar + apply_manifest.
kprintln("[CSpace] Granted output capability to Subject") kprintln("[CSpace] Subject capabilities deferred to BKDL manifest")
# Grant Network I/O (RX/TX) # Grant Network I/O (RX/TX) — kernel fibers only
# NetSwitch (Fiber 6): Full access to shuttle packets # NetSwitch (Fiber 6): Full access to shuttle packets
discard fiber_grant_channel(6, 0x500, PERM_READ or PERM_WRITE) # CMD_NET_TX discard fiber_grant_channel(6, 0x500, PERM_READ or PERM_WRITE) # CMD_NET_TX
discard fiber_grant_channel(6, 0x501, PERM_READ or PERM_WRITE) # CMD_NET_RX discard fiber_grant_channel(6, 0x501, PERM_READ or PERM_WRITE) # CMD_NET_RX
kprintln("[CSpace] Granted network capabilities to NetSwitch")
# Subject (Fiber 4): Needs to READ RX (0x501) and WRITE TX (0x500) # M4: Grant VFS capabilities — kernel fibers only
discard fiber_grant_channel(4, 0x500, PERM_WRITE) # Can send packets discard fiber_grant_channel(1, 0x2000, PERM_READ or PERM_WRITE) # ION: VFS (ELF loading)
discard fiber_grant_channel(4, 0x501, PERM_READ) # Can receive packets discard fiber_grant_channel(1, 0x1000, PERM_READ or PERM_WRITE) # ION: console.input
kprintln("[CSpace] Granted network capabilities to NetSwitch and Subject") discard fiber_grant_channel(1, 0x1001, PERM_READ or PERM_WRITE) # ION: console.output
discard fiber_grant_channel(2, 0x2000, PERM_READ or PERM_WRITE) # NexShell: VFS
kprintln("[CSpace] Granted VFS capabilities to kernel fibers")
# Init (Subject) lives in Cell 0 (0x88000000) - Needs 64MB for large BSS # Init (Subject) lives in Cell 0 Needs 64MB for large BSS
fiber_subject.phys_offset = 0x88000000'u64 fiber_subject.phys_offset = CELL0_BASE
let init_size = 64 * 1024 * 1024'u64 let init_size = 64 * 1024 * 1024'u64
fiber_subject.satp_value = mm_create_worker_map(cast[uint64](addr stack_subject[0]), uint64(sizeof(stack_subject)), SYSTABLE_BASE, fiber_subject.phys_offset, init_size) fiber_subject.satp_value = mm_create_worker_map(cast[uint64](addr stack_subject[0]), uint64(sizeof(stack_subject)), SYSTABLE_BASE, fiber_subject.phys_offset, init_size)
# Interrupt Setup # Interrupt Setup (Architecture-specific)
asm "csrsi sstatus, 2" when defined(riscv64):
{.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".} asm "csrsi sstatus, 2"
let plic_base = 0x0c000000'u64 {.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".}
# Priority (each IRQ has a 4-byte priority register) let plic_base = 0x0c000000'u64
cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40) # Priority (each IRQ has a 4-byte priority register)
# cast[ptr uint32](plic_base + 128)[] = 1 # VirtIO-Net (IRQ 32: 32*4 = 128) cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40)
# cast[ptr uint32](plic_base + 132)[] = 1 # VirtIO-Net (IRQ 33: 33*4 = 132)
# cast[ptr uint32](plic_base + 136)[] = 1 # VirtIO-Net (IRQ 34: 34*4 = 136)
# cast[ptr uint32](plic_base + 140)[] = 1 # VirtIO-Net (IRQ 35: 35*4 = 140)
# Enable (Supervisor Context 1) # Enable (Supervisor Context 1)
# IRQs 0-31 (Enable IRQ 10 = UART) # IRQs 0-31 (Enable IRQ 10 = UART)
cast[ptr uint32](plic_base + 0x2000 + 0x80)[] = (1'u32 shl 10) cast[ptr uint32](plic_base + 0x2000 + 0x80)[] = (1'u32 shl 10)
# IRQs 32-63
# cast[ptr uint32](plic_base + 0x2000 + 0x80 + 4)[] = 0x0000000F # Enable 32,33,34,35
# Threshold # Threshold
cast[ptr uint32](plic_base + 0x201000)[] = 0 cast[ptr uint32](plic_base + 0x201000)[] = 0
let en_addr = plic_base + 0x2000 + 0x80
let en_val = cast[ptr uint32](en_addr)[]
kprint("[PLIC] UART Enable Register at "); kprint_hex(en_addr)
kprint(" is "); kprint_hex(uint64(en_val)); kprintln("")
elif defined(arm64):
# GICv2 is initialized in aarch64_init() before Nim entry
kprintln("[GIC] Interrupts configured by HAL")
active_fibers_arr[0] = addr fiber_ion; active_fibers_arr[1] = addr fiber_nexshell active_fibers_arr[0] = addr fiber_ion; active_fibers_arr[1] = addr fiber_nexshell
active_fibers_arr[2] = addr fiber_compositor; active_fibers_arr[3] = addr fiber_netswitch active_fibers_arr[2] = addr fiber_compositor; active_fibers_arr[3] = addr fiber_netswitch
@ -777,28 +1098,42 @@ proc kmain() {.exportc, cdecl.} =
(addr fiber_compositor).setSpectrum(Spectrum.Photon) (addr fiber_compositor).setSpectrum(Spectrum.Photon)
(addr fiber_netswitch).setSpectrum(Spectrum.Photon) (addr fiber_netswitch).setSpectrum(Spectrum.Photon)
(addr fiber_nexshell).setSpectrum(Spectrum.Matter) # Interactive (addr fiber_nexshell).setSpectrum(Spectrum.Matter) # Interactive
(addr fiber_subject).setSpectrum(Spectrum.Void) # Untrusted Background (addr fiber_subject).setSpectrum(Spectrum.Matter) # Elevated from Void
(addr fiber_child).setSpectrum(Spectrum.Void) # Child process (spawned) (addr fiber_child).setSpectrum(Spectrum.Matter) # Elevated from Void
current_fiber.setSpectrum(Spectrum.Void) # Main loop (dispatcher) current_fiber.setSpectrum(Spectrum.Void) # Main loop (dispatcher)
# M4.5: Kinetic Economy — default budgets per Spectrum tier
(addr fiber_ion).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_compositor).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_netswitch).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_nexshell).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
(addr fiber_subject).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
(addr fiber_child).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
# Void (main loop): budget_ns = 0 → unlimited (already default)
# M4: Set initial pledge masks (using top-level set_pledge)
fiber_ion.set_pledge(PLEDGE_ALL)
fiber_compositor.set_pledge(PLEDGE_ALL)
fiber_netswitch.set_pledge(PLEDGE_ALL)
fiber_nexshell.set_pledge(PLEDGE_STDIO or PLEDGE_RPATH or PLEDGE_WPATH or PLEDGE_EXEC)
# M4.4: Subject/child pledge is set by BKDL manifest in subject_fiber_entry.
# Start with PLEDGE_STDIO as safe default (manifest will override).
fiber_subject.set_pledge(PLEDGE_STDIO)
fiber_child.set_pledge(PLEDGE_STDIO)
kprintln("[M4] Pledge masks applied (Subject/child: manifest-driven)")
# Ground Zero Phase 2: Introspection # Ground Zero Phase 2: Introspection
stl_print_summary() stl_print_summary()
kprintln("[Rumpk] Multi-Fiber Dispatcher starting...") kprintln("[Rumpk] Multi-Fiber Dispatcher starting...")
switch(addr fiber_ion) switch(addr fiber_ion)
# Exported from Zig
proc uart_poll_input() {.importc, cdecl.}
while true: while true:
if not sched_tick_spectrum(active_fibers_arr.toOpenArray(0, 5)): # ⌨️ Hardware Input Driver (Polling fallback)
# The Silence Doctrine: Wait for Interrupt uart_poll_input()
let next_wake = sched_get_next_wakeup(active_fibers_arr.toOpenArray(0, 5))
let now = sched_get_now_ns()
if next_wake > now and next_wake != 0xFFFFFFFFFFFFFFFF'u64: if not sched_tick_spectrum(active_fibers_arr):
proc rumpk_timer_set_ns(ns: uint64) {.importc, cdecl.} # Wait for data or timeout
# kprint("Interval: "); kprint_hex(next_wake - now); kprintln("") fiber_sleep(1)
rumpk_timer_set_ns(next_wake - now) # Pass interval
else:
# No timer needed (or overdue), just WFI for other interrupts (IO)
discard
asm "csrsi sstatus, 2"
asm "wfi"

View File

@ -67,6 +67,76 @@ proc kload*(path: string): uint64 =
return ehdr.e_entry return ehdr.e_entry
# --- M4.4: BKDL Manifest Extraction ---
proc streq_n(a: ptr UncheckedArray[byte], b: cstring, maxlen: int): bool =
## Compare byte array against C string, bounded by maxlen
var i = 0
while i < maxlen:
if b[i] == '\0':
return true # b ended, all matched
if a[i] != byte(b[i]):
return false
i += 1
return false
proc kload_manifest*(file_content: openArray[byte]): ManifestResult =
## Scan ELF section headers for .nexus.manifest containing BKDL data.
## Returns header=nil if no manifest found.
result.header = nil
result.caps = nil
result.count = 0
if file_content.len < int(sizeof(Elf64_Ehdr)):
return
let ehdr = cast[ptr Elf64_Ehdr](unsafeAddr file_content[0])
let base = cast[uint64](unsafeAddr file_content[0])
let file_len = uint64(file_content.len)
# Validate section header table is within file
if ehdr.e_shoff == 0 or ehdr.e_shnum == 0:
return
if ehdr.e_shoff + uint64(ehdr.e_shnum) * uint64(ehdr.e_shentsize) > file_len:
return
# Get string table section (shstrtab)
if ehdr.e_shstrndx >= ehdr.e_shnum:
return
let strtab_shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(ehdr.e_shstrndx) * uint64(ehdr.e_shentsize))
if strtab_shdr.sh_offset + strtab_shdr.sh_size > file_len:
return
let strtab = cast[ptr UncheckedArray[byte]](base + strtab_shdr.sh_offset)
# Scan sections for .nexus.manifest
let target = cstring(".nexus.manifest")
for i in 0 ..< int(ehdr.e_shnum):
let shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(i) * uint64(ehdr.e_shentsize))
if shdr.sh_name < uint32(strtab_shdr.sh_size):
let name_ptr = cast[ptr UncheckedArray[byte]](cast[uint64](strtab) + uint64(shdr.sh_name))
let remaining = int(strtab_shdr.sh_size) - int(shdr.sh_name)
if streq_n(name_ptr, target, remaining):
# Found .nexus.manifest section
if shdr.sh_offset + shdr.sh_size > file_len:
return # Section data out of bounds
if shdr.sh_size < uint64(sizeof(BkdlHeader)):
return # Too small
let hdr = cast[ptr BkdlHeader](base + shdr.sh_offset)
if hdr.magic != BKDL_MAGIC or hdr.version != BKDL_VERSION:
kprintln("[Manifest] Invalid BKDL magic/version")
return
let expected_size = uint64(sizeof(BkdlHeader)) + uint64(hdr.cap_count) * uint64(sizeof(CapDescriptor))
if expected_size > shdr.sh_size:
kprintln("[Manifest] BKDL cap_count exceeds section size")
return
result.header = hdr
result.caps = cast[ptr UncheckedArray[CapDescriptor]](base + shdr.sh_offset + uint64(sizeof(BkdlHeader)))
result.count = int(hdr.cap_count)
return
proc kexec*(path: string) = proc kexec*(path: string) =
let entry = kload(path) let entry = kload(path)
if entry != 0: if entry != 0:

View File

@ -37,8 +37,48 @@ type
p_memsz*: uint64 p_memsz*: uint64
p_align*: uint64 p_align*: uint64
Elf64_Shdr* {.packed.} = object
sh_name*: uint32
sh_type*: uint32
sh_flags*: uint64
sh_addr*: uint64
sh_offset*: uint64
sh_size*: uint64
sh_link*: uint32
sh_info*: uint32
sh_addralign*: uint64
sh_entsize*: uint64
const const
PT_LOAD* = 1 PT_LOAD* = 1
PT_NOTE* = 4
PF_X* = 1 PF_X* = 1
PF_W* = 2 PF_W* = 2
PF_R* = 4 PF_R* = 4
# SPEC-071: BKDL (Binary Manifest) types
const
BKDL_MAGIC* = 0x4E585553'u32 # "NXUS" (little-endian)
BKDL_VERSION* = 1'u16
type
BkdlHeader* {.packed.} = object
magic*: uint32
version*: uint16
flags*: uint16
signature*: array[64, uint8] # Ed25519 (unchecked in dev mode)
pubkey_hash*: array[32, uint8] # SHA-256 of signing key
cap_count*: uint16
blob_size*: uint32
entry_point*: uint64 # 0 = use ELF e_entry
CapDescriptor* {.packed.} = object
cap_type*: uint8 # CapType enum value
perms*: uint8 # Permission bitmask
reserved*: uint16 # Alignment padding
resource_id*: uint64 # SipHash of resource name
ManifestResult* = object
header*: ptr BkdlHeader
caps*: ptr UncheckedArray[CapDescriptor]
count*: int

View File

@ -34,6 +34,11 @@ proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.} proc kprint_hex(v: uint64) {.importc, cdecl.}
proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.} proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Project LibWeb: LWF Adapter (Zig FFI)
proc lwf_validate(data: pointer, len: uint16): uint8 {.importc, cdecl.}
const ETHERTYPE_LWF = 0x4C57'u16 # "LW" — Libertaria Wire Frame
# Membrane Infrastructure (LwIP Glue) # Membrane Infrastructure (LwIP Glue)
proc membrane_init*() {.importc, cdecl.} proc membrane_init*() {.importc, cdecl.}
proc pump_membrane_stack*() {.importc, cdecl.} proc pump_membrane_stack*() {.importc, cdecl.}
@ -73,6 +78,11 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
case etype: case etype:
of 0x0800, 0x0806, 0x86DD: # IPv4, ARP, IPv6 of 0x0800, 0x0806, 0x86DD: # IPv4, ARP, IPv6
# NOTE(LibWeb): IPv6 is the first-class citizen for sovereign mesh.
# Most chapter nodes sit behind consumer NAT — IPv6 provides
# end-to-end addressability without traversal hacks.
# IPv4 is the fallback, not the default. Membrane/Transport
# layer enforces preference order (IPv6 → IPv4).
# Route to Legacy/LwIP Membrane # Route to Legacy/LwIP Membrane
if not chan_net_rx.send(pkt): if not chan_net_rx.send(pkt):
# Ring full (Backpressure) # Ring full (Backpressure)
@ -86,6 +96,21 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
ion_free(pkt) ion_free(pkt)
return true # Handled (dropped) return true # Handled (dropped)
of ETHERTYPE_LWF: # Project LibWeb: Libertaria Wire Frame
# Validate LWF magic before routing (Fast Drop)
let lwf_data = cast[pointer](cast[uint64](pkt.data) + 14) # Skip Eth header
let lwf_len = if pkt.len > 14: uint16(pkt.len - 14) else: 0'u16
if lwf_len >= 88 and lwf_validate(lwf_data, lwf_len) == 1:
# Valid LWF — route to dedicated LWF channel
if not chan_lwf_rx.send(pkt):
ion_free(pkt) # Backpressure
return false
return true
else:
# Invalid LWF frame — drop
ion_free(pkt)
return false
else: else:
# Drop unknown EtherTypes (Security/Sovereignty) # Drop unknown EtherTypes (Security/Sovereignty)
ion_free(pkt) ion_free(pkt)
@ -116,7 +141,7 @@ proc fiber_netswitch_entry*() {.cdecl.} =
rx_activity = true rx_activity = true
inc rx_count inc rx_count
# 3. TX PATH: Userland -> Hardware # 3. TX PATH: Userland -> Hardware (Legacy/LwIP)
var tx_pkt: IonPacket var tx_pkt: IonPacket
while chan_net_tx.recv(tx_pkt): while chan_net_tx.recv(tx_pkt):
if tx_pkt.data != nil and tx_pkt.len > 0: if tx_pkt.data != nil and tx_pkt.len > 0:
@ -125,6 +150,15 @@ proc fiber_netswitch_entry*() {.cdecl.} =
ion_free(tx_pkt) ion_free(tx_pkt)
tx_activity = true tx_activity = true
# 3b. TX PATH: LWF Egress (Project LibWeb)
var lwf_pkt: IonPacket
while chan_lwf_tx.recv(lwf_pkt):
if lwf_pkt.data != nil and lwf_pkt.len > 0:
virtio_net_send(cast[pointer](lwf_pkt.data), uint32(lwf_pkt.len))
inc tx_count
ion_free(lwf_pkt)
tx_activity = true
# 4. Periodically print stats # 4. Periodically print stats
let now = get_now_ns() let now = get_now_ns()
if now - last_stat_print > 5000000000'u64: # 5 seconds if now - last_stat_print > 5000000000'u64: # 5 seconds

View File

@ -161,6 +161,24 @@ proc emit_access_denied*(
0, 0 0, 0
) )
proc emit_policy_violation*(
fiber_id: uint64,
budget_ns: uint64,
actual_ns: uint64,
violation_count: uint32,
cause_id: uint64 = 0
): uint64 {.exportc, cdecl.} =
## Emit budget violation event to STL (The Ratchet, M4.5)
return stl_emit(
uint16(EvPolicyViolation),
fiber_id,
budget_ns,
cause_id,
actual_ns,
uint64(violation_count),
0
)
## Initialization ## Initialization
proc init_stl_subsystem*() = proc init_stl_subsystem*() =
## Initialize the STL subsystem (call from kmain) ## Initialize the STL subsystem (call from kmain)

View File

@ -175,11 +175,18 @@ proc pty_write_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.}
if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b): if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b):
written += 1 written += 1
# Mirror to UART console
var c_buf: array[2, char]
c_buf[0] = char(b)
c_buf[1] = '\0'
kprint(cast[cstring](addr c_buf[0]))
# Also render to FB terminal # Also render to FB terminal
term_putc(char(b)) term_putc(char(b))
else: else:
break break
# Render frame after batch write # Render frame after batch write
if written > 0: if written > 0:
term_render() term_render()

View File

@ -42,6 +42,18 @@ import fiber
# We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper). # We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).
proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.} proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
proc console_write(p: pointer, len: csize_t) {.importc: "hal_console_write", cdecl.}
proc uart_print_hex(v: uint64) {.importc: "uart_print_hex", cdecl.}
proc uart_print_hex8(v: uint8) {.importc: "uart_print_hex8", cdecl.}
# M4.5: STL emission for budget violations (The Ratchet)
proc emit_policy_violation*(fiber_id, budget_ns, actual_ns: uint64,
violation_count: uint32, cause_id: uint64): uint64 {.importc, cdecl.}
# Forward declaration — implementation is in THE RATCHET section below
proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64)
var photon_idx, matter_idx, gravity_idx, void_idx: int
# Forward declaration for channel data check (provided by kernel/channels) # Forward declaration for channel data check (provided by kernel/channels)
proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.} proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.}
@ -63,11 +75,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 1: PHOTON (Hard Real-Time / Hardware Driven) # Phase 1: PHOTON (Hard Real-Time / Hardware Driven)
# ========================================================= # =========================================================
var run_photon = false var run_photon = false
for f in fibers: for i in 0..<fibers.len:
let idx = (photon_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Photon: if f != nil and f.getSpectrum() == Spectrum.Photon:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true photon_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_photon = true run_photon = true
if run_photon: return true if run_photon: return true
@ -76,11 +94,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 2: MATTER (Interactive / Latency Sensitive) # Phase 2: MATTER (Interactive / Latency Sensitive)
# ========================================================= # =========================================================
var run_matter = false var run_matter = false
for f in fibers: for i in 0..<fibers.len:
let idx = (matter_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Matter: if f != nil and f.getSpectrum() == Spectrum.Matter:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true matter_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_matter = true run_matter = true
if run_matter: return true if run_matter: return true
@ -89,11 +113,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 3: GRAVITY (Throughput / Background) # Phase 3: GRAVITY (Throughput / Background)
# ========================================================= # =========================================================
var run_gravity = false var run_gravity = false
for f in fibers: for i in 0..<fibers.len:
let idx = (gravity_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Gravity: if f != nil and f.getSpectrum() == Spectrum.Gravity:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true gravity_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_gravity = true run_gravity = true
if run_gravity: return true if run_gravity: return true
@ -101,11 +131,16 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# ========================================================= # =========================================================
# Phase 4: VOID (Scavenger) # Phase 4: VOID (Scavenger)
# ========================================================= # =========================================================
for f in fibers: for i in 0..<fibers.len:
let idx = (void_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Void: if f != nil and f.getSpectrum() == Spectrum.Void:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
void_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f) switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true return true
else: else:
return true return true
@ -128,10 +163,20 @@ proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
return min_wakeup return min_wakeup
# ========================================================= # =========================================================
# THE RATCHET (Post-Execution Analysis) # M4.5: Budget Defaults Per Spectrum Tier
# ========================================================= # =========================================================
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.} proc default_budget_for_spectrum*(s: Spectrum): uint64 =
## Return the default budget_ns for a given Spectrum tier
case s
of Spectrum.Photon: return 2_000_000'u64 # 2ms — hard real-time
of Spectrum.Matter: return 10_000_000'u64 # 10ms — interactive
of Spectrum.Gravity: return 50_000_000'u64 # 50ms — batch
of Spectrum.Void: return 0'u64 # unlimited — scavenger
# =========================================================
# THE RATCHET (Post-Execution Analysis)
# =========================================================
proc sched_log(msg: string) = proc sched_log(msg: string) =
if msg.len > 0: if msg.len > 0:
@ -169,7 +214,16 @@ proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64) =
# Budget enforcement # Budget enforcement
if f.budget_ns > 0 and burst_ns > f.budget_ns: if f.budget_ns > 0 and burst_ns > f.budget_ns:
f.violations += 1 f.violations += 1
console_write(cstring("[Ratchet] Violation: fiber exceeded budget\n"), 42) discard emit_policy_violation(f.id, f.budget_ns, burst_ns, f.violations, 0)
console_write(cstring("[Ratchet] Budget violation fiber=0x"), 33)
uart_print_hex(f.id)
console_write(cstring(" burst="), 7)
uart_print_hex(burst_ns)
console_write(cstring(" budget="), 8)
uart_print_hex(f.budget_ns)
console_write(cstring(" strikes="), 9)
uart_print_hex(uint64(f.violations))
console_write(cstring("\n"), 1)
if f.violations >= 3: if f.violations >= 3:
sched_demote(f) sched_demote(f)