feat(core): M4 security — CSpace, Pledge, STL, budget enforcement, BKDL manifests
This commit is contained in:
parent
8d4b581519
commit
0c598ce0bd
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
39
core/ion.nim
39
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)")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
745
core/kernel.nim
745
core/kernel.nim
File diff suppressed because it is too large
Load Diff
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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..<lineage.count:
|
||||
let eid = lineage.event_ids[i]
|
||||
let ev_ptr = stl_lookup(eid)
|
||||
|
||||
|
||||
if i > 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)[]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
100
core/sched.nim
100
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..<fibers.len:
|
||||
let idx = (photon_idx + i) mod fibers.len
|
||||
let f = fibers[idx]
|
||||
if f != nil and f.getSpectrum() == Spectrum.Photon:
|
||||
if is_runnable(f, now):
|
||||
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:
|
||||
run_photon = 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)
|
||||
# =========================================================
|
||||
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 is_runnable(f, now):
|
||||
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:
|
||||
run_matter = true
|
||||
if run_matter: return true
|
||||
|
|
@ -89,11 +113,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
|
|||
# Phase 3: GRAVITY (Throughput / Background)
|
||||
# =========================================================
|
||||
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 is_runnable(f, now):
|
||||
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:
|
||||
run_gravity = true
|
||||
if run_gravity: return true
|
||||
|
|
@ -101,15 +131,20 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
|
|||
# =========================================================
|
||||
# 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 is_runnable(f, now):
|
||||
if f != current_fiber:
|
||||
void_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:
|
||||
return true
|
||||
|
||||
|
||||
# =========================================================
|
||||
# THE SILENCE
|
||||
# =========================================================
|
||||
|
|
@ -124,15 +159,25 @@ proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
|
|||
if f != nil and f.sleep_until > 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue