From 0c598ce0bdd28c148ddfe8842eb07871dd05fcd6 Mon Sep 17 00:00:00 2001 From: Markus Maiwald Date: Sun, 15 Feb 2026 19:59:07 +0100 Subject: [PATCH] =?UTF-8?q?feat(core):=20M4=20security=20=E2=80=94=20CSpac?= =?UTF-8?q?e,=20Pledge,=20STL,=20budget=20enforcement,=20BKDL=20manifests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/cspace.nim | 7 +- core/fiber.nim | 11 +- core/ion.nim | 39 ++- core/ion/memory.nim | 33 +- core/kernel.nim | 745 ++++++++++++++++++++++++++++++++------------ core/loader.nim | 70 +++++ core/loader/elf.nim | 40 +++ core/netswitch.nim | 56 +++- core/ontology.nim | 32 +- core/pty.nim | 7 + core/sched.nim | 100 ++++-- 11 files changed, 872 insertions(+), 268 deletions(-) diff --git a/core/cspace.nim b/core/cspace.nim index 6e828cb..1279ab2 100644 --- a/core/cspace.nim +++ b/core/cspace.nim @@ -27,6 +27,7 @@ proc cspace_grant_cap*( proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.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_channel*(fiber_id: uint64, channel_id: uint64, perm_bits: uint8): bool {.importc, cdecl.} ## Capability Types (Mirror from cspace.zig) type @@ -80,10 +81,10 @@ proc fiber_grant_memory*( end_addr ) -proc fiber_check_channel_access*(fiber_id: uint64, slot: uint, write: bool): bool = - ## Check if fiber has channel access via capability +proc fiber_check_channel_access*(fiber_id: uint64, channel_id: uint64, write: bool): bool = + ## Check if fiber has Channel capability for given channel_id 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) = ## Revoke a capability from a fiber diff --git a/core/fiber.nim b/core/fiber.nim index 47f70ce..5e2914a 100644 --- a/core/fiber.nim +++ b/core/fiber.nim @@ -9,7 +9,7 @@ # MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) # Rumpk Phase 10: Multitasking & Context Switching -# +# # Responsibilities: # - Define the Fiber abstraction (Hardware Context + Stack) # - Abstract the ISA-specific context switch mechanism @@ -24,6 +24,10 @@ when defined(riscv64): const ARCH_NAME* = "riscv64" const CONTEXT_SIZE* = 128 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): const ARCH_NAME* = "amd64" const CONTEXT_SIZE* = 64 @@ -112,7 +116,7 @@ const STACK_SIZE* = 4096 # Fiber State # ========================================================= -var main_fiber: FiberObject +var main_fiber*: FiberObject var current_fiber* {.global.}: Fiber = addr main_fiber # ========================================================= @@ -135,6 +139,9 @@ proc fiber_trampoline() {.cdecl, exportc, noreturn.} = when defined(riscv64): while true: {.emit: "asm volatile(\"wfi\");".} + elif defined(arm64): + while true: + {.emit: "asm volatile(\"wfe\");".} else: while true: discard diff --git a/core/ion.nim b/core/ion.nim index 7711558..f6ea209 100644 --- a/core/ion.nim +++ b/core/ion.nim @@ -37,7 +37,7 @@ type CMD_GET_GPU_STATUS = 0x102 CMD_FS_OPEN = 0x200 CMD_FS_READ = 0x201 - CMD_FS_READDIR = 0x202 + CMD_FS_READDIR = 0x202 CMD_FS_WRITE = 0x203 CMD_FS_MOUNT = 0x204 CMD_ION_FREE = 0x300 @@ -79,7 +79,7 @@ type SysTable* = object magic*: uint32 # 0x4E585553 - reserved*: uint32 + reserved*: uint32 s_rx*: ptr HAL_Ring[IonPacket] s_tx*: ptr HAL_Ring[IonPacket] s_event*: ptr HAL_Ring[IonPacket] @@ -91,9 +91,11 @@ type 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_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_pledge*: proc(promises: uint64): int32 {.cdecl.} - + # Framebuffer fb_addr*: uint64 fb_width*: uint32 @@ -115,14 +117,18 @@ type # Phase 36.3: Shared ION (16 bytes) fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.} fn_ion_free*: proc(id: uint16) {.cdecl.} - + # Phase 36.4: I/O Multiplexing (8 bytes) fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.} - + # Phase 36.5: Network Hardware Info (8 bytes) net_mac*: array[6, 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 # --- Sovereign Logic --- @@ -167,6 +173,12 @@ var net_rx_hal: HAL_Ring[IonPacket] var net_tx_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.} = guest_input_hal.head = 0 guest_input_hal.tail = 0 @@ -181,7 +193,7 @@ proc ion_init_network*() {.exportc, cdecl.} = net_rx_hal.tail = 0 net_rx_hal.mask = 255 chan_net_rx.ring = addr net_rx_hal - + net_tx_hal.head = 0 net_tx_hal.tail = 0 net_tx_hal.mask = 255 @@ -191,7 +203,18 @@ proc ion_init_network*() {.exportc, cdecl.} = netswitch_rx_hal.tail = 0 netswitch_rx_hal.mask = 255 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 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(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)") diff --git a/core/ion/memory.nim b/core/ion/memory.nim index 4f9493b..e027642 100644 --- a/core/ion/memory.nim +++ b/core/ion/memory.nim @@ -13,17 +13,24 @@ 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)) + +var NEWLINE_BUF: array[2, char] = ['\n', '\0'] + +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 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 + SYSTABLE_BASE = when defined(arm64): 0x50000000'u64 + else: 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 @@ -53,9 +60,11 @@ proc ion_pool_init*() {.exportc.} = dbg("[ION] Initializing Pool...") # 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]) # 2. Translate to PHYSICAL (Identity Mapped for Phase 7) + dbg("[ION] Step 2: Setting base phys...") global_pool.base_phys = virt_addr # Tracing for Phase 37 @@ -64,12 +73,16 @@ proc ion_pool_init*() {.exportc.} = kprint_hex(global_pool.base_phys) dbg("") - dbg("[ION] Ring Init...") + dbg("[ION] Step 3: Ring Init (free_ring)...") + dbg(" 3a: About to call init()") global_pool.free_ring.init() + dbg(" 3b: free_ring init complete") + dbg("[ION] Step 4: Ring Init (tx_ring)...") global_tx_ring.init() + dbg(" 4a: tx_ring init complete") # Fill the free ring with all indices [0..1023] - dbg("[ION] Filling Slabs...") + dbg("[ION] Step 5: Filling Slabs...") var count = 0 for i in 0 ..< POOL_COUNT: if global_pool.free_ring.push(uint16(i)): @@ -77,6 +90,8 @@ proc ion_pool_init*() {.exportc.} = dbg("[ION] Pool Ready.") + + proc ion_alloc*(): IonPacket {.exportc.} = ## O(1) Allocation. Returns an empty packet struct. ## If OOM, returns packet with data = nil @@ -197,7 +212,7 @@ proc ion_user_slab_init*() {.exportc.} = 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: @@ -207,7 +222,7 @@ proc ion_alloc_shared*(out_id: ptr uint16): uint64 {.exportc, cdecl.} = 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 diff --git a/core/kernel.nim b/core/kernel.nim index 9abd9b5..1ac2f0b 100644 --- a/core/kernel.nim +++ b/core/kernel.nim @@ -12,11 +12,22 @@ import fiber, ion, sched, pty, cspace, ontology, fastpath, utcp import fs/vfs, fs/tar import loader/elf import ../libs/membrane/term +import ../libs/membrane/net_glue const MAX_WORKERS* = 8 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) proc rumpk_timer_handler() {.exportc, cdecl, used.} = @@ -35,6 +46,7 @@ proc nexshell_main() {.importc, cdecl.} proc console_poll() {.importc, cdecl.} proc ion_get_virt(id: uint16): uint64 {.importc, cdecl.} + # InitRD Symbols var initrd_start {.importc: "_initrd_start" .}: byte var initrd_end {.importc: "_initrd_end" .}: byte @@ -49,7 +61,7 @@ var # Trace logic proc kprint*(s: cstring) {.exportc, cdecl.} = - if s != nil: + if s != nil: var i = 0 let p = cast[ptr UncheckedArray[char]](s) while p[i] != '\0': i += 1 @@ -92,23 +104,23 @@ proc k_starts_with(s, prefix: cstring): bool = proc k_zero_mem(p: pointer, size: uint64) = var addr_val = cast[uint64](p) var remaining = size - + # 1. Handle unaligned leading bytes while (addr_val mod 8 != 0) and (remaining > 0): cast[ptr byte](addr_val)[] = 0 addr_val += 1 remaining -= 1 - + # 2. Optimized 64-bit stores for the bulk let count = remaining div 8 if count > 0: let p64 = cast[ptr UncheckedArray[uint64]](addr_val) for i in 0 ..< int(count): p64[i] = 0 - + addr_val += uint64(count * 8) remaining -= uint64(count * 8) - + # 3. Handle trailing bytes while remaining > 0: cast[ptr byte](addr_val)[] = 0 @@ -121,13 +133,13 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 = if fd < 0: kprint("[Loader] Error: Could not open '"); kprint(path); kprintln("'") return 0 - + var ehdr: Elf64_Ehdr if ion_vfs_read(fd, addr ehdr, uint64(sizeof(ehdr))) != int64(sizeof(ehdr)): kprintln("[Loader] Error: ELF header read failed") discard ion_vfs_close(fd) return 0 - + if ehdr.e_ident[0] != 0x7F or ehdr.e_ident[1] != 'E'.uint8: kprintln("[Loader] Error: Invalid ELF magic") discard ion_vfs_close(fd) @@ -151,48 +163,190 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 = if phdr.p_type == PT_LOAD: # 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) if phdr.p_filesz > 0: if tar.vfs_read_at(tar_path, dest, phdr.p_filesz, phdr.p_offset) != int64(phdr.p_filesz): kprintln("[Loader] Error: Segment load failed") - + if phdr.p_memsz > phdr.p_filesz: let bss_start = phdr.p_vaddr + phdr.p_filesz let bss_len = phdr.p_memsz - phdr.p_filesz kprint(" - Zeroing BSS: VA="); kprint_hex(bss_start); kprint(" Len="); kprint_hex(bss_len); kprintln("") k_zero_mem(cast[pointer](bss_start), bss_len) - + # {.emit: """asm volatile ("li t1, 0x40000; csrc sstatus, t1" : : : "t1");""" .} - + # ⚡ 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) 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 --- proc subject_fiber_entry() {.cdecl.} = let fid = current_fiber.id kprint("[Subject:"); kprint_hex(fid); kprint("] Fiber Entry reached. PA_Offset="); kprint_hex(current_fiber.phys_offset); kprintln("") - + # Use new robust loader kprint("[Loader:"); kprint_hex(fid); kprint("] Loading: "); kprintln(cast[cstring](addr subject_loading_path[0])) - + # Load into Cellular Slot (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: kprint("[Subject:"); kprint_hex(fid); kprint("] Entering Payload at: "); kprint_hex(entry_addr); kprintln("") proc hal_enter_userland(entry, systable, sp: uint64) {.importc, cdecl.} var sp = current_fiber.user_sp_init if sp == 0: # Fallback (Legacy/Init) - Top of the 64MB Sentinel Cell - sp = 0x8BFFFFF0'u64 - + sp = USER_SP_FALLBACK + kprintln("╔════════════════════════════════════════════════════╗") kprintln("║ PRE-FLIGHT: USERLAND TRANSITION ║") kprintln("╚════════════════════════════════════════════════════╝") @@ -202,19 +356,19 @@ proc subject_fiber_entry() {.cdecl.} = kprint(" SATP: "); kprint_hex(current_fiber.satp_value); kprintln("") kprint(" Phys Off: "); kprint_hex(current_fiber.phys_offset); kprintln("") kprintln("") - + # 🔥 CRITICAL: Activate worker page table BEFORE entering userland! # Without this, userland executes with kernel identity map → instant page fault if current_fiber.satp_value != 0: proc mm_activate_satp(satp_val: uint64) {.importc, cdecl.} kprint("[Subject:"); kprint_hex(fid); kprint("] Activating worker page table: "); kprint_hex(current_fiber.satp_value); kprintln("") mm_activate_satp(current_fiber.satp_value) - + hal_enter_userland(entry_addr, SYSTABLE_BASE, sp) else: kprint("[Subject:"); kprint_hex(fid); kprintln("] Loader failed to find/load payload!") while true: fiber_sleep(1000) - + proc nexshell_fiber_entry() {.cdecl.} = kprintln("[NexShell] Interactive Fiber Online") while true: @@ -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 setup_mksh_stack(stack_base: pointer, stack_size: int): uint64 = - var sp = cast[uint64](stack_base) + cast[uint64](stack_size) - sp = sp and not 15'u64 # Align 16 - - # Term String - let term_str = "TERM=nexus\0" - sp -= uint64(term_str.len) - copyMem(cast[pointer](sp), unsafeAddr term_str[0], term_str.len) - let term_addr = sp - - # Path String - let path_str = "/bin/mksh\0" - sp -= uint64(path_str.len) - copyMem(cast[pointer](sp), unsafeAddr path_str[0], path_str.len) - let path_addr = sp - - sp = sp and not 15'u64 # Align 16 - +proc setup_cellular_stack(phys_base: uint64, slot_size: uint64, user_base: uint64): uint64 = + # Stack grows down from the top of the cell + var phys_top = phys_base + slot_size + var user_sp = user_base + slot_size + + # Align 16 + phys_top = phys_top and not 15'u64 + user_sp = user_sp and not 15'u64 + + # Helper to push data + template push_str(s: string): uint64 = + let s_val = s + let l = uint64(s_val.len) + 1 # + null terminator + 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 + + # 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) - sp -= 16 - cast[ptr uint64](sp)[] = 0 - cast[ptr uint64](sp+8)[] = 0 - - # Envp (term, NULL) - sp -= 16 - cast[ptr uint64](sp)[] = term_addr - cast[ptr uint64](sp+8)[] = 0 - - # Argv (path, NULL) - sp -= 16 - cast[ptr uint64](sp)[] = path_addr - cast[ptr uint64](sp+8)[] = 0 - + push_u64(0) + push_u64(0) + + # Envp (NULL, TERM, HOME) - Reverse order of push + push_u64(0) + push_u64(term_addr) + push_u64(home_addr) + + # Argv (NULL, path) + push_u64(0) + push_u64(path_addr) + # Argc (1) - sp -= 8 - cast[ptr uint64](sp)[] = 1 - - return sp + push_u64(1) + + kprint("[Stack] Final SP: "); kprint_hex(user_sp); kprint("\n") + + return user_sp proc ion_fiber_entry() {.cdecl.} = kprintln("[ION] Fiber Entry reached.") while true: var pkt: CmdPacket 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): of CMD_SYS_EXIT: kprintln("[ION] Restarting Subject...") @@ -286,35 +460,45 @@ proc ion_fiber_entry() {.cdecl.} = of CMD_GPU_MATRIX: matrix_enabled = (pkt.arg != 0) of CMD_SPAWN_FIBER: - # Fiber spawn requested + # Fiber spawn requested # Hardcoded for verification to confirm isolation let target = "/sysro/bin/mksh" kprint("[ION] Spawning child fiber for: "); kprintln(target) - + # Copy into loading path copyMem(addr subject_loading_path[0], unsafeAddr target[0], target.len + 1) - + # Allocate PTY let pid = pty_alloc() if pid < 0: kprintln("[ION] Failed to allocate PTY for child!") - + # 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)) - - # 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) - # Sentinel (Init) takes 0x88000000 - 0x8C000000 (64MB) - # Mksh (First Child) starts in the 'Wild' at 0x8C000000 - let cell_base = 0x8C000000'u64 - fiber_child.phys_offset = cell_base - - # Allocate 64MB Slot for Mksh (Needs >32MB for BSS) + # Sentinel (Init) takes Cell 0, Mksh (First Child) takes Cell 1 + let cell_base = CELL1_BASE + let user_base = USER_VA_BASE let cell_size = 64 * 1024 * 1024'u64 - fiber_child.satp_value = mm_create_worker_map(cast[uint64](addr stack_child[0]), uint64(sizeof(stack_child)), SYSTABLE_BASE, cell_base, cell_size) + + # Phase 40: Set PTY & User Stack + fiber_child.pty_id = pid + fiber_child.phys_offset = cell_base + + # Setup User Stack in CELLULAR Memory (User Top) + # We write to Physical Address (cell_base + size), but Mksh sees (user_base + 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") else: discard else: @@ -326,26 +510,34 @@ proc rumpk_yield_internal*() {.exportc, cdecl.} = switch(active_fibers_arr[6]) proc fiber_yield*() {.exportc, cdecl.} = - current_fiber.wants_yield = true - rumpk_yield_internal() + # Return to the dispatcher context (main_fiber) + # 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.} = kprintln("[NetSwitch] Traffic Engine Online") - + # Iron Firewall: Verify channel sovereignty before operation if chan_netswitch_rx.ring == nil: kprintln("[CRITICAL] NetSwitch RX channel uninitialized - HALTING") while true: fiber_yield() - + if chan_net_rx.ring == nil or chan_net_tx.ring == nil: kprintln("[CRITICAL] Global net rings uninitialized - HALTING") while true: fiber_yield() - + kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.") - + + # Initialize LwIP Membrane (DHCP, Netif, DNS) + membrane_init() + kprintln("[NetSwitch] Membrane initialized — DHCP running") + while true: var pkt: IonPacket - + # INGRESS: Driver -> NetSwitch -> Fast Path Filter if chan_netswitch_rx.recv(pkt): # SPEC-700: Fast Path Bypass for UTCP Tunnel (UDP/9999) @@ -363,49 +555,71 @@ proc fiber_netswitch_entry() {.cdecl.} = # SLOW PATH: Route to LwIP via chan_net_rx if not chan_net_rx.send(pkt): ion_free_raw(pkt.id) - + # EGRESS: Subject (chan_net_tx) -> NetSwitch -> Driver (ion_tx_push) if chan_net_tx.recv(pkt): kprintln("[NetSwitch] Forwarding Egress") var res = ion_tx_push(pkt) if not res: kprintln("[NetSwitch] Drop (TX Full)") - + + # Drive LwIP timers + RX ingestion (DHCP, ARP, TCP, ICMP) + pump_membrane_stack() + # Poll Network virtio_net_poll() - + # Poll UART (Backup/Primary Polling Mode) {.emit: "extern void uart_poll_input(void); uart_poll_input();".} - + # Prevent Starvation 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.} = ## Handle packet from Network Driver # Get actual physical address and apply driver offset let base_phys = ion_get_phys(id) let base_virt = ion_get_virt(id) - + # Create packet with pointers pointing DIRECTLY to the Ethernet frame let pkt = IonPacket( - id: id, - len: len, + id: id, + len: len, phys: base_phys + uint64(offset), data: cast[ptr UncheckedArray[byte]](cast[uint64](base_virt) + uint64(offset)) ) - + if not chan_netswitch_rx.send(pkt): ion_free_raw(id) + +# --- SCHEDULER STATE --- +# --- SCHEDULER STATE --- + proc ion_push_stdin*(p: pointer, len: csize_t) {.exportc, cdecl.} = if chan_input.ring == nil: return + var pkt = ion_alloc() - if pkt.data == nil: return - let to_copy = if int(len) < 2048: int(len) else: 2048 - copyMem(pkt.data, p, to_copy) - pkt.len = uint16(to_copy) - if fiber_subject.sleep_until == 0xFFFFFFFFFFFFFFFF'u64: fiber_subject.sleep_until = 0 - discard chan_input.send(pkt) + if pkt.data == nil: + # kprintln("[ION Push] CRITICAL: Slab allocation failed!") + return + + pkt.data[0] = cast[ptr byte](p)[] + 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.} = if current_fiber != nil and current_fiber.wants_yield: @@ -417,7 +631,7 @@ proc k_handle_exception*(scause, sepc, stval: uint) {.exportc, cdecl.} = kprintln("╔════════════════════════════════════════════════════╗") kprintln("║ KERNEL IMMUNE SYSTEM: EXCEPTION DETECTED ║") kprintln("╚════════════════════════════════════════════════════╝") - + # Decode scause let cause_code = scause and 0x7FFFFFFFFFFFFFFF'u64 kprint(" SCAUSE: "); kprint_hex(scause); kprint(" (") @@ -437,20 +651,26 @@ proc k_handle_exception*(scause, sepc, stval: uint) {.exportc, cdecl.} = of 15: kprint("Store/AMO page fault") else: kprint("Unknown exception") kprintln(")") - + kprint(" SEPC: "); kprint_hex(sepc); kprintln(" (Faulting PC)") kprint(" STVAL: "); kprint_hex(stval); kprintln(" (Fault address/info)") - + # Dump current fiber context if current_fiber != nil: kprint(" Fiber: "); kprint_hex(current_fiber.id) kprint(" SATP: "); kprint_hex(current_fiber.satp_value) kprintln("") - + kprintln("") kprintln("[IMMUNE] System HALTING (Trap Loop Prevention).") - while true: - {.emit: "asm volatile(\"wfi\");".} + when defined(riscv64): + 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.} = 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.} = 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 --- 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: 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 fiber_yield() 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) current_fiber.blocked_on_mask = a0 current_fiber.is_blocked = true fiber_yield() return 0 of 0x200: # OPEN - # return uint(libc_impl.libc_impl_open(cast[cstring](a0), int(a1))) - return 0 + # M4: Check VFS capability (channel 0x2000) + 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 - # return uint(libc_impl.libc_impl_close(int(a0))) - return 0 + kprint("[CLOSE] fd="); kprint_hex(a0); kprint("\n") + return uint(ion_vfs_close(int32(a0))) 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))) + 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 # TODO: Implement getaddrinfo kernel integration return 0 # Not implemented yet of 0x203: # READ - # kprint("[Syscall] READ(fd="); kprint_hex(a0); kprint(")\n") - var vres = -2 + # M4: Check console.input capability for stdin (fd 0) + 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: let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0 while true: if pty_has_data_for_slave(pid): - var buf: array[1, byte] - let n = pty_read_slave(PTY_SLAVE_BASE + pid, addr buf[0], 1) - if n > 0: - # kprint("[Kernel] READ delivered PTY byte: "); kprint_hex8(buf[0]); kprint("\n") - cast[ptr UncheckedArray[byte]](a1)[0] = buf[0] - return 1 + var buf: array[1, byte] + let n = pty_read_slave(int(PTY_SLAVE_BASE + pid), addr buf[0], 1) + if n > 0: + cast[ptr UncheckedArray[byte]](a1)[0] = buf[0] + return 1 + var pkt: IonPacket if chan_input.recv(pkt): - kprint("[Kernel] Got Input Packet of len: "); kprint_hex(uint64(pkt.len)); kprint("\n") - let n = if uint64(pkt.len) < a2: uint64(pkt.len) else: a2 - if n > 0: - 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 + for i in 0..= 0: current_fiber.pty_id else: 0 discard pty_write_slave(PTY_SLAVE_BASE + pid, cast[ptr byte](a1), int(a2)) return a2 - # var vres = libc_impl.libc_impl_write(int(a0), cast[pointer](a1), uint64(a2)) - var vres = -1 - return uint(vres) - of 0x205: return 0 # IOCTL stub + elif a0 >= 3: + # M4: Check VFS write capability (channel 0x2000) + if current_fiber != nil and current_fiber.cspace_id < 16: + 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) # Manual copy path to subject_loading_path let p = cast[ptr UncheckedArray[char]](a0) @@ -563,13 +867,13 @@ proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} = # Re-initialize fiber_subject with new binary # The ION fiber will pick this up and restart the Subject fiber var pkt = CmdPacket(kind: uint32(CMD_SPAWN_FIBER), arg: 0) - + # DEBUG: Check if send works if chan_cmd.send(pkt): kprintln("[Kernel] CMD_SPAWN_FIBER sent to ION.") else: kprintln("[Kernel] CRITICAL: Failed to send CMD_SPAWN_FIBER to ION!") - + # Return fiber ID (always 4 for Subject currently) return 4 else: return 0 @@ -585,30 +889,28 @@ proc ion_wait_multi*(mask: uint64): int32 {.exportc, cdecl.} = proc kmain() {.exportc, cdecl.} = 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...") - + # HAL Hardware Inits # rumpk_net_init() -- Moved below - + ion_pool_init() proc mm_init() {.importc, cdecl.} proc mm_enable_kernel_paging() {.importc, cdecl.} mm_init() mm_enable_kernel_paging() - + # HAL Hardware Inits (Moved after ION/MM init) rumpk_net_init() - + # Ground Zero Phase 1: Initialize Capability System (SPEC-051) init_cspace_subsystem() kprintln("[CSpace] Capability system initialized") - + # Ground Zero Phase 2: Initialize System Truth Ledger (SPEC-060) init_stl_subsystem() let boot_id = emit_system_boot() - + ion_init_input() ion_init_network() # Initialize net rings early hal_io_init() @@ -618,14 +920,14 @@ proc kmain() {.exportc, cdecl.} = vfs_init(addr initrd_start, addr initrd_end) vfs_mount_init() - + # DEBUG: List Files kprintln("[VFS] Boot Inventory:") var buf: array[512, byte] let n = ion_vfs_list(addr buf[0], 512) if n > 0: kprintln(cast[cstring](addr buf[0])) - + # Set initial loading path for Init (Subject) let initial_path = "/sysro/init" copyMem(addr subject_loading_path[0], unsafeAddr initial_path[0], initial_path.len + 1) @@ -636,11 +938,13 @@ proc kmain() {.exportc, cdecl.} = sys.fn_vfs_read = ion_vfs_read sys.fn_vfs_list = ion_vfs_list 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 # Point to user slab allocator (shared memory) instead of kernel pool sys.fn_ion_alloc = ion_user_alloc_systable sys.fn_ion_free = ion_user_free_systable - + # Populate Network MAC proc virtio_net_get_mac(out_mac: ptr byte) {.importc, cdecl.} virtio_net_get_mac(addr sys.net_mac[0]) @@ -664,16 +968,21 @@ proc kmain() {.exportc, cdecl.} = let ring_event = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0x6000) chan_cmd.ring = cast[ptr HAL_Ring[CmdPacket]](SYSTABLE_BASE + 0x8000) chan_input.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xA000) - + # Network Rings (Shared with Userland) chan_net_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xC000) 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 chan_rx.ring.mask = 255; chan_tx.ring.mask = 255 ring_event.mask = 255; chan_cmd.ring.mask = 255 chan_input.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 chan_rx.ring.head = 0; chan_rx.ring.tail = 0 chan_tx.ring.head = 0; chan_tx.ring.tail = 0 @@ -682,14 +991,20 @@ proc kmain() {.exportc, cdecl.} = chan_input.ring.head = 0; chan_input.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_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_cmd = chan_cmd.ring; sys.s_input = chan_input.ring - + # Map Network Rings (Now in shared memory) sys.s_net_rx = chan_net_rx.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.fb_addr = fb_kern_get_addr() sys.fb_width = 1920; sys.fb_height = 1080; sys.fb_stride = 1920 * 4; sys.fb_bpp = 32 @@ -697,108 +1012,128 @@ proc kmain() {.exportc, cdecl.} = # Spawn Fibers fiber_ion.id = 1; fiber_nexshell.id = 2; fiber_compositor.id = 3 fiber_subject.id = 4; fiber_child.id = 5; fiber_netswitch.id = 6 - + init_fiber(addr fiber_ion, ion_fiber_entry, addr stack_ion[0], sizeof(stack_ion)) let ion_spawn_id = emit_fiber_spawn(1, 0, boot_id) # ION fiber discard ion_spawn_id - + init_fiber(addr fiber_compositor, compositor_fiber_entry, addr stack_compositor[0], sizeof(stack_compositor)) let compositor_spawn_id = emit_fiber_spawn(3, 0, boot_id) # Compositor fiber discard compositor_spawn_id - + init_fiber(addr fiber_nexshell, nexshell_fiber_entry, addr stack_nexshell[0], sizeof(stack_nexshell)) let shell_spawn_id = emit_fiber_spawn(2, 0, boot_id) # NexShell fiber - + # NetSwitch Spawn init_fiber(addr fiber_netswitch, fiber_netswitch_entry, addr stack_netswitch[0], sizeof(stack_netswitch)) let netswitch_spawn_id = emit_fiber_spawn(6, 0, boot_id) # NetSwitch fiber discard netswitch_spawn_id - + init_fiber(addr fiber_subject, subject_fiber_entry, addr stack_subject[0], sizeof(stack_subject)) let subject_spawn_id = emit_fiber_spawn(4, 0, boot_id) # Subject fiber - + # Ground Zero Phase 1: Grant Initial Capabilities (SPEC-051) # Grant console I/O to NexShell (fiber 2) discard fiber_grant_channel(2, 0x1000, PERM_READ or PERM_WRITE) # console.input discard emit_capability_grant(2, 2, 0x1000, 0, shell_spawn_id) # Log event - + discard fiber_grant_channel(2, 0x1001, PERM_READ or PERM_WRITE) # console.output discard emit_capability_grant(2, 2, 0x1001, 1, shell_spawn_id) # Log event kprintln("[CSpace] Granted console capabilities to NexShell") - - # Grant console output to Subject (fiber 4) - discard fiber_grant_channel(4, 0x1001, PERM_WRITE) # console.output (write-only) - discard emit_capability_grant(4, 2, 0x1001, 0, subject_spawn_id) # Log event - kprintln("[CSpace] Granted output capability to Subject") - # Grant Network I/O (RX/TX) + # M4.4: Subject (fiber 4) capabilities are now manifest-driven. + # The BKDL manifest in the ELF binary declares what it needs. + # Grants are applied in subject_fiber_entry via kload_manifest_tar + apply_manifest. + kprintln("[CSpace] Subject capabilities deferred to BKDL manifest") + + # Grant Network I/O (RX/TX) — kernel fibers only # 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, 0x501, PERM_READ or PERM_WRITE) # CMD_NET_RX - - # Subject (Fiber 4): Needs to READ RX (0x501) and WRITE TX (0x500) - discard fiber_grant_channel(4, 0x500, PERM_WRITE) # Can send packets - discard fiber_grant_channel(4, 0x501, PERM_READ) # Can receive packets - kprintln("[CSpace] Granted network capabilities to NetSwitch and Subject") - - # Init (Subject) lives in Cell 0 (0x88000000) - Needs 64MB for large BSS - fiber_subject.phys_offset = 0x88000000'u64 + kprintln("[CSpace] Granted network capabilities to NetSwitch") + + # M4: Grant VFS capabilities — kernel fibers only + discard fiber_grant_channel(1, 0x2000, PERM_READ or PERM_WRITE) # ION: VFS (ELF loading) + discard fiber_grant_channel(1, 0x1000, PERM_READ or PERM_WRITE) # ION: console.input + 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 — Needs 64MB for large BSS + fiber_subject.phys_offset = CELL0_BASE 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) - # Interrupt Setup - asm "csrsi sstatus, 2" - {.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".} - let plic_base = 0x0c000000'u64 - # Priority (each IRQ has a 4-byte priority register) - cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40) - # cast[ptr uint32](plic_base + 128)[] = 1 # VirtIO-Net (IRQ 32: 32*4 = 128) - # 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) - # IRQs 0-31 (Enable IRQ 10 = UART) - 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 - cast[ptr uint32](plic_base + 0x201000)[] = 0 - + # Interrupt Setup (Architecture-specific) + when defined(riscv64): + asm "csrsi sstatus, 2" + {.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".} + let plic_base = 0x0c000000'u64 + # Priority (each IRQ has a 4-byte priority register) + cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40) + + # Enable (Supervisor Context 1) + # IRQs 0-31 (Enable IRQ 10 = UART) + cast[ptr uint32](plic_base + 0x2000 + 0x80)[] = (1'u32 shl 10) + + # Threshold + 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[2] = addr fiber_compositor; active_fibers_arr[3] = addr fiber_netswitch active_fibers_arr[4] = addr fiber_subject active_fibers_arr[5] = addr fiber_child active_fibers_arr[6] = current_fiber - + # Set Spectrums (Priorities) (addr fiber_ion).setSpectrum(Spectrum.Photon) (addr fiber_compositor).setSpectrum(Spectrum.Photon) (addr fiber_netswitch).setSpectrum(Spectrum.Photon) (addr fiber_nexshell).setSpectrum(Spectrum.Matter) # Interactive - (addr fiber_subject).setSpectrum(Spectrum.Void) # Untrusted Background - (addr fiber_child).setSpectrum(Spectrum.Void) # Child process (spawned) + (addr fiber_subject).setSpectrum(Spectrum.Matter) # Elevated from Void + (addr fiber_child).setSpectrum(Spectrum.Matter) # Elevated from Void 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 stl_print_summary() kprintln("[Rumpk] Multi-Fiber Dispatcher starting...") switch(addr fiber_ion) + # Exported from Zig + proc uart_poll_input() {.importc, cdecl.} + while true: - if not sched_tick_spectrum(active_fibers_arr.toOpenArray(0, 5)): - # The Silence Doctrine: Wait for Interrupt - let next_wake = sched_get_next_wakeup(active_fibers_arr.toOpenArray(0, 5)) - let now = sched_get_now_ns() + # ⌨️ Hardware Input Driver (Polling fallback) + uart_poll_input() - if next_wake > now and next_wake != 0xFFFFFFFFFFFFFFFF'u64: - proc rumpk_timer_set_ns(ns: uint64) {.importc, cdecl.} - # kprint("Interval: "); kprint_hex(next_wake - now); kprintln("") - 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" + if not sched_tick_spectrum(active_fibers_arr): + # Wait for data or timeout + fiber_sleep(1) diff --git a/core/loader.nim b/core/loader.nim index c82c7ba..f85f6ab 100644 --- a/core/loader.nim +++ b/core/loader.nim @@ -67,6 +67,76 @@ proc kload*(path: string): uint64 = 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) = let entry = kload(path) if entry != 0: diff --git a/core/loader/elf.nim b/core/loader/elf.nim index d4a7952..2ad6bee 100644 --- a/core/loader/elf.nim +++ b/core/loader/elf.nim @@ -37,8 +37,48 @@ type p_memsz*: 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 PT_LOAD* = 1 + PT_NOTE* = 4 PF_X* = 1 PF_W* = 2 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 diff --git a/core/netswitch.nim b/core/netswitch.nim index b3d59ea..8ada2c5 100644 --- a/core/netswitch.nim +++ b/core/netswitch.nim @@ -9,7 +9,7 @@ # core/rumpk/core/netswitch.nim # Phase 36.2: The Traffic Cop (Network Membrane Layer 2 Switch) -# +# # Responsibilities: # - Poll VirtIO-Net hardware for incoming packets # - Route RX packets to s_net_rx ring (Kernel -> Userland) @@ -34,6 +34,11 @@ proc kprint(s: cstring) {.importc, cdecl.} proc kprint_hex(v: uint64) {.importc, 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) proc membrane_init*() {.importc, cdecl.} proc pump_membrane_stack*() {.importc, cdecl.} @@ -73,19 +78,39 @@ proc netswitch_process_packet(pkt: IonPacket): bool = case etype: 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 if not chan_net_rx.send(pkt): # Ring full (Backpressure) ion_free(pkt) return false return true - + of 0x88B5: # Sovereign UTCP (SPEC-700) # TODO: Route to dedicated UTCP channel # kprintln("[NetSwitch] UTCP Sovereign Packet Identified") ion_free(pkt) 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: # Drop unknown EtherTypes (Security/Sovereignty) ion_free(pkt) @@ -93,30 +118,30 @@ proc netswitch_process_packet(pkt: IonPacket): bool = proc fiber_netswitch_entry*() {.cdecl.} = kprintln("[NetSwitch] Fiber Entry - The Traffic Cop is ON DUTY") - + var rx_activity: bool = false var tx_activity: bool = false var rx_count: uint64 = 0 var tx_count: uint64 = 0 var last_stat_print: uint64 = 0 - + while true: rx_activity = false tx_activity = false - + # 1. Drive the hardware poll (fills chan_netswitch_rx) virtio_net_poll() - + # [Cleaned] Driven by Userland now - + # 2. Consume from the Driver -> Switch internal ring var raw_pkt: IonPacket while chan_netswitch_rx.recv(raw_pkt): if netswitch_process_packet(raw_pkt): rx_activity = true inc rx_count - - # 3. TX PATH: Userland -> Hardware + + # 3. TX PATH: Userland -> Hardware (Legacy/LwIP) var tx_pkt: IonPacket while chan_net_tx.recv(tx_pkt): if tx_pkt.data != nil and tx_pkt.len > 0: @@ -124,7 +149,16 @@ proc fiber_netswitch_entry*() {.cdecl.} = inc tx_count ion_free(tx_pkt) 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 let now = get_now_ns() if now - last_stat_print > 5000000000'u64: # 5 seconds diff --git a/core/ontology.nim b/core/ontology.nim index e6e16b2..729ac9c 100644 --- a/core/ontology.nim +++ b/core/ontology.nim @@ -161,6 +161,24 @@ proc emit_access_denied*( 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 proc init_stl_subsystem*() = ## Initialize the STL subsystem (call from kmain) @@ -180,24 +198,24 @@ proc stl_print_summary*() {.exportc, cdecl.} = kprint("[STL] I/O & Channels: "); kprint_hex(uint64(stats.io_events)); kprintln("") kprint("[STL] Memory: "); kprint_hex(uint64(stats.mem_events)); kprintln("") kprint("[STL] Security/Policy: "); kprint_hex(uint64(stats.security_events)); kprintln("") - + # Demonstrate Causal Graph for the last event if stats.total_events > 0: - let last_id = uint64(stats.total_events - 1) + let last_id = uint64(stats.total_events - 1) var lineage: LineageResult stl_trace_lineage(last_id, lineage) - + kprintln("\n[STL] Causal Graph Audit:"); kprint("[STL] Target: "); kprint_hex(last_id); kprintln("") - + for i in 0.. 0: kprintln(" |") - + kprint(" +-- ["); kprint_hex(eid); kprint("] ") - + if ev_ptr != nil: # Kind is at offset 0 (2 bytes) let kind_val = cast[ptr uint16](ev_ptr)[] diff --git a/core/pty.nim b/core/pty.nim index d2d0040..edaacf2 100644 --- a/core/pty.nim +++ b/core/pty.nim @@ -175,10 +175,17 @@ 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): 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 term_putc(char(b)) else: break + # Render frame after batch write if written > 0: diff --git a/core/sched.nim b/core/sched.nim index d2f78de..56bc657 100644 --- a/core/sched.nim +++ b/core/sched.nim @@ -22,26 +22,38 @@ import fiber # We need a centralized registry or a way to iterate. # # For the first pass, we can replicate the logic in kernel.nim which explicitly checks -# the known fibers, but structured as the Spectrum loop. +# the known fibers, but structured as the Spectrum loop. # Or we can make kernel.nim pass the fibers to us. # # Let's keep it simple and stateless in sched.nim if possible, or have it manage the queue. -# Since kernel.nim holds the variables, sched.nim should probably define the *Strategy* +# Since kernel.nim holds the variables, sched.nim should probably define the *Strategy* # and kernel.nim calls it, OR sched.nim should import kernel (circular!). # -# Better: fiber.nim holds a linked list of fibers? +# Better: fiber.nim holds a linked list of fibers? # Or sched.nim is just a helper module that kernel.nim uses. # Let's define the Strategy here. -# To avoid circular imports, kernel.nim will likely INCLUDE sched.nim or sched.nim -# will act on a passed context. +# To avoid circular imports, kernel.nim will likely INCLUDE sched.nim or sched.nim +# will act on a passed context. # BUT, SPEC-102 implies sched.nim *is* the logic. # -# Let's define the Harmonic logic. +# Let's define the Harmonic logic. # 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 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) proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.} @@ -58,16 +70,22 @@ proc is_runnable(f: ptr FiberObject, now: uint64): bool = proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool = let now = sched_get_now_ns() - + # ========================================================= # Phase 1: PHOTON (Hard Real-Time / Hardware Driven) # ========================================================= var run_photon = false - for f in fibers: + for i in 0.. now: if f.sleep_until < min_wakeup: min_wakeup = f.sleep_until - + return min_wakeup +# ========================================================= +# M4.5: Budget Defaults Per Spectrum Tier +# ========================================================= + +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 console_write(p: pointer, len: csize_t) {.importc, cdecl.} - proc sched_log(msg: string) = if msg.len > 0: console_write(unsafeAddr msg[0], csize_t(msg.len)) @@ -157,20 +202,29 @@ proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64) = ## Analyze the burst duration of a fiber after it yields/switches ## Implements the 3-strike rule for budget violations if f == nil: return - + f.last_burst_ns = burst_ns - + # Update moving average (exponential: 75% old, 25% new) if f.avg_burst == 0: f.avg_burst = burst_ns else: f.avg_burst = (f.avg_burst * 3 + burst_ns) div 4 - + # Budget enforcement if f.budget_ns > 0 and burst_ns > f.budget_ns: 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: sched_demote(f) f.violations = 0 # Reset after demotion