feat(kernel): implement Sv39 fiber memory isolation and hardened ELF loader

This commit is contained in:
Markus Maiwald 2026-01-05 16:36:25 +01:00
parent be47ab4ae1
commit 04663318f3
27 changed files with 1775 additions and 1707 deletions

View File

@ -25,14 +25,24 @@ proc main() =
print(cstring("[INIT] System Ready. Starting heartbeat...\n")) print(cstring("[INIT] System Ready. Starting heartbeat...\n"))
print(cstring("[INIT] Spawning Sovereign Shell (Mksh)...\n")) # Spawn mksh as a separate fiber (NOT execv - we stay alive as supervisor)
proc spawn_fiber(path: cstring): int =
# SYS_SPAWN_FIBER = 0x300
return int(syscall(0x300, cast[uint64](path), 0, 0))
# Attempt to handover control to Mksh let fiber_id = spawn_fiber(cstring("/bin/mksh"))
# execv(path, argv) - argv can be nil for now if fiber_id > 0:
if execv(cstring("/bin/mksh"), nil) < 0: print(cstring("[INIT] Spawned mksh fiber ID: "))
print(cstring("\x1b[1;31m[INIT] Failed to spawn shell! Entering fallback loop.\x1b[0m\n")) # Note: Can't easily print int in minimal libc, just confirm success
print(cstring("OK\n"))
else:
print(cstring("\x1b[1;31m[INIT] Failed to spawn shell!\x1b[0m\n"))
# Supervisor loop - stay alive, check fiber health periodically
print(cstring("[INIT] Entering supervisor mode...\n"))
while true: while true:
# 🕵️ DIAGNOSTIC: BREATHER # Sleep 1 second between checks
discard syscall(0x65, 1000000000'u64) # nanosleep: 1 second
pump_membrane_stack() pump_membrane_stack()
yield_fiber() yield_fiber()

View File

@ -1,12 +1,7 @@
.section .text._start, "ax" .section .text._start, "ax"
.global _start .global _start
_start: _start:
# 🕵 DIAGNOSTIC: BREATHE # 🕵 BSS Clearing
li t0, 0x10000000
li t1, 0x23 # '#'
sb t1, 0(t0)
# Clear BSS (64-bit aligned zeroing)
la t0, __bss_start la t0, __bss_start
la t1, __bss_end la t1, __bss_end
1: bge t0, t1, 2f 1: bge t0, t1, 2f
@ -16,11 +11,6 @@ _start:
2: 2:
fence rw, rw fence rw, rw
# 🕵 DIAGNOSTIC: READY TO CALL MAIN
li t0, 0x10000000
li t1, 0x21 # '!'
sb t1, 0(t0)
# Arguments (argc, argv) are already in a0, a1 from Kernel # Arguments (argc, argv) are already in a0, a1 from Kernel
# sp is already pointing to argc from Kernel # sp is already pointing to argc from Kernel

View File

@ -71,6 +71,8 @@ type
budget_ns*: uint64 # "I promise to run for X ns max" budget_ns*: uint64 # "I promise to run for X ns max"
last_burst_ns*: uint64 # Actual execution time of last run last_burst_ns*: uint64 # Actual execution time of last run
violations*: uint32 # Strike counter (3 strikes = demotion) violations*: uint32 # Strike counter (3 strikes = demotion)
pty_id*: int # Phase 40: Assigned PTY ID (-1 if none)
user_sp_init*: uint64 # Initial SP for userland entry
# Spectrum Accessors # Spectrum Accessors
proc getSpectrum*(f: Fiber): Spectrum = proc getSpectrum*(f: Fiber): Spectrum =
@ -147,6 +149,8 @@ proc init_fiber*(f: Fiber, entry: proc() {.cdecl.}, stack_base: pointer, size: i
f.stack = cast[ptr UncheckedArray[uint8]](stack_base) f.stack = cast[ptr UncheckedArray[uint8]](stack_base)
f.stack_size = size f.stack_size = size
f.sleep_until = 0 f.sleep_until = 0
f.pty_id = -1
f.user_sp_init = 0
# Start at top of stack (using actual size) # Start at top of stack (using actual size)
var sp = cast[uint64](stack_base) + cast[uint64](size) var sp = cast[uint64](stack_base) + cast[uint64](size)

View File

@ -6,334 +6,135 @@
# See legal/LICENSE_SOVEREIGN.md for license terms. # See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: Sovereign File System (SFS) ## Rumpk Layer 1: Sovereign File System (SFS)
##
## Freestanding implementation (No OS module dependencies).
## Uses fixed-size buffers and raw blocks for persistence.
# Markus Maiwald (Architect) | Voxis Forge (AI) import fiber # For yield
#
# Rumpk Phase 23: The Sovereign Filesystem (SFS) v2
# Features: Multi-Sector Files (Linked List), Block Alloc Map (BAM)
#
# DOCTRINE(SPEC-021):
# This file currently implements the "Physics-Logic Hybrid" for Bootstrapping.
# In Phase 37, this will be deprecated in favor of:
# - L0: LittleFS (Atomic Physics)
# - L1: SFS Overlay Daemon (Sovereign Logic in Userland)
proc kprintln(s: cstring) {.importc, cdecl.} proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint(s: cstring) {.importc, cdecl.} proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(n: uint64) {.importc, cdecl.} proc kprint_hex(n: uint64) {.importc, cdecl.}
# =========================================================
# SFS Configurations
# =========================================================
const SFS_MAGIC* = 0x31534653'u32 const SFS_MAGIC* = 0x31534653'u32
const const
SEC_SB = 0 SEC_SB = 0
SEC_BAM = 1 SEC_BAM = 1
SEC_DIR = 2 SEC_DIR = 2
# Linked List Payload: 508 bytes data + 4 bytes next_sector
CHUNK_SIZE = 508 CHUNK_SIZE = 508
EOF_MARKER = 0xFFFFFFFF'u32 EOF_MARKER = 0xFFFFFFFF'u32
type
Superblock* = object
magic*: uint32
disk_size*: uint32
DirEntry* = object
filename*: array[32, char]
start_sector*: uint32
size_bytes*: uint32
reserved*: array[24, byte]
var sfs_mounted: bool = false var sfs_mounted: bool = false
var io_buffer: array[512, byte] var io_buffer: array[512, byte]
proc virtio_blk_read(sector: uint64, buf: pointer) {.importc, cdecl.} proc virtio_blk_read(sector: uint64, buf: pointer) {.importc, cdecl.}
proc virtio_blk_write(sector: uint64, buf: pointer) {.importc, cdecl.} proc virtio_blk_write(sector: uint64, buf: pointer) {.importc, cdecl.}
# =========================================================
# Helpers
# =========================================================
# Removed sfs_set_bam (unused)
proc sfs_alloc_sector(): uint32 = proc sfs_alloc_sector(): uint32 =
# Simple allocator: Scan BAM for first 0 bit
virtio_blk_read(SEC_BAM, addr io_buffer[0]) virtio_blk_read(SEC_BAM, addr io_buffer[0])
for i in 0..<512: for i in 0..<512:
if io_buffer[i] != 0xFF: if io_buffer[i] != 0xFF:
# Found a byte with free space
for b in 0..7: for b in 0..7:
if (io_buffer[i] and byte(1 shl b)) == 0: if (io_buffer[i] and byte(1 shl b)) == 0:
# Found free bit
let sec = uint32(i * 8 + b) let sec = uint32(i * 8 + b)
# Mark applied in sfs_set_bam but for efficiency do it here/flush
io_buffer[i] = io_buffer[i] or byte(1 shl b) io_buffer[i] = io_buffer[i] or byte(1 shl b)
virtio_blk_write(SEC_BAM, addr io_buffer[0]) virtio_blk_write(SEC_BAM, addr io_buffer[0])
return sec return sec
return 0 # Error / Full return 0
# =========================================================
# SFS API
# =========================================================
proc sfs_is_mounted*(): bool = sfs_mounted
proc sfs_format*() =
kprintln("[SFS] Formatting disk...")
# 1. Clear IO Buffer
for i in 0..511: io_buffer[i] = 0
# 2. Setup Superblock
io_buffer[0] = byte('S')
io_buffer[1] = byte('F')
io_buffer[2] = byte('S')
io_buffer[3] = byte('2')
# Disk size placeholder (32MB = 65536 sectors)
io_buffer[4] = 0x00; io_buffer[5] = 0x00; io_buffer[6] = 0x01; io_buffer[7] = 0x00
virtio_blk_write(SEC_SB, addr io_buffer[0])
# 3. Clear BAM
for i in 0..511: io_buffer[i] = 0
# Mark sectors 0, 1, 2 as used
io_buffer[0] = 0x07
virtio_blk_write(SEC_BAM, addr io_buffer[0])
# 4. Clear Directory
for i in 0..511: io_buffer[i] = 0
virtio_blk_write(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Format Complete.")
proc sfs_mount*() = proc sfs_mount*() =
kprintln("[SFS] Mounting System v2...")
# 1. Read Sector 0 (Superblock)
virtio_blk_read(SEC_SB, addr io_buffer[0]) virtio_blk_read(SEC_SB, addr io_buffer[0])
# 2. Check Magic (SFS2)
if io_buffer[0] == byte('S') and io_buffer[1] == byte('F') and if io_buffer[0] == byte('S') and io_buffer[1] == byte('F') and
io_buffer[2] == byte('S') and io_buffer[3] == byte('2'): io_buffer[2] == byte('S') and io_buffer[3] == byte('2'):
kprintln("[SFS] Mount SUCCESS. Version 2 (Linked Chain).")
sfs_mounted = true
elif io_buffer[0] == 0 and io_buffer[1] == 0:
kprintln("[SFS] Fresh disk detected.")
sfs_format()
sfs_mounted = true sfs_mounted = true
else: else:
kprint("[SFS] Mount FAILED. Invalid Magic/Ver. Found: ") sfs_mounted = false
kprint_hex(cast[uint64](io_buffer[0]))
kprintln("")
proc sfs_list*() = proc sfs_streq(s1, s2: cstring): bool =
let p1 = cast[ptr UncheckedArray[char]](s1)
let p2 = cast[ptr UncheckedArray[char]](s2)
var i = 0
while true:
if p1[i] != p2[i]: return false
if p1[i] == '\0': return true
i += 1
proc sfs_write_file*(name: cstring, data: pointer, data_len: int) {.exportc, cdecl.} =
if not sfs_mounted: return if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0]) virtio_blk_read(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Files:")
var offset = 0
while offset < 512:
if io_buffer[offset] != 0:
var name: string = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
kprint(" - ")
kprintln(cstring(name))
offset += 64
proc sfs_get_files*(): string =
var res = ""
if not sfs_mounted: return res
virtio_blk_read(SEC_DIR, addr io_buffer[0])
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var name = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
res.add(name)
res.add("\n")
return res
proc sfs_write_file*(name: cstring, data: cstring, data_len: int) {.exportc, cdecl.} =
if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0])
var dir_offset = -1 var dir_offset = -1
var file_exists = false
# 1. Find File or Free Slot
for offset in countup(0, 511, 64): for offset in countup(0, 511, 64):
if io_buffer[offset] != 0: if io_buffer[offset] != 0:
var entry_name = "" if sfs_streq(name, cast[cstring](addr io_buffer[offset])):
for i in 0..31:
if io_buffer[offset+i] == 0: break
entry_name.add(char(io_buffer[offset+i]))
if entry_name == $name:
dir_offset = offset dir_offset = offset
file_exists = true
# For existing files, efficient rewrite logic is complex (reuse chain vs new).
# V2 Simplification: Just create NEW chain, orphan old one (leak) for now.
# Future: Walk old chain and free in BAM.
break break
elif dir_offset == -1: elif dir_offset == -1: dir_offset = offset
dir_offset = offset if dir_offset == -1: return
if dir_offset == -1:
kprintln("[SFS] Error: Directory Full.")
return
# 2. Chunk and Write Data
var remaining = data_len var remaining = data_len
var data_ptr = 0 var data_addr = cast[uint64](data)
var first_sector = 0'u32 var current_sector = sfs_alloc_sector()
var current_sector = 0'u32 if current_sector == 0: return
let first_sector = current_sector
# For the first chunk
current_sector = sfs_alloc_sector()
if current_sector == 0:
kprintln("[SFS] Error: Disk Full.")
return
first_sector = current_sector
while remaining > 0: while remaining > 0:
var sector_buf: array[512, byte] var sector_buf: array[512, byte]
let chunk = if remaining > CHUNK_SIZE: CHUNK_SIZE else: remaining
copyMem(addr sector_buf[0], cast[pointer](data_addr), chunk)
remaining -= chunk
data_addr += uint64(chunk)
# Fill Data
let chunk_size = if remaining > CHUNK_SIZE: CHUNK_SIZE else: remaining
for i in 0..<chunk_size:
sector_buf[i] = byte(data[data_ptr + i])
remaining -= chunk_size
data_ptr += chunk_size
# Determine Next Sector
var next_sector = EOF_MARKER var next_sector = EOF_MARKER
if remaining > 0: if remaining > 0:
next_sector = sfs_alloc_sector() next_sector = sfs_alloc_sector()
if next_sector == 0: if next_sector == 0: next_sector = EOF_MARKER
next_sector = EOF_MARKER # Disk full, truncated
remaining = 0
# Write Next Pointer # Write next pointer at end of block
sector_buf[508] = byte(next_sector and 0xFF) cast[ptr uint32](addr sector_buf[508])[] = next_sector
sector_buf[509] = byte((next_sector shr 8) and 0xFF)
sector_buf[510] = byte((next_sector shr 16) and 0xFF)
sector_buf[511] = byte((next_sector shr 24) and 0xFF)
# Flush Sector
virtio_blk_write(uint64(current_sector), addr sector_buf[0]) virtio_blk_write(uint64(current_sector), addr sector_buf[0])
current_sector = next_sector current_sector = next_sector
if current_sector == EOF_MARKER: break if current_sector == EOF_MARKER: break
# 3. Update Directory Entry # Update Directory
# Need to read Dir again as buffer was used for BAM/Data
virtio_blk_read(SEC_DIR, addr io_buffer[0]) virtio_blk_read(SEC_DIR, addr io_buffer[0])
let nm = cast[ptr UncheckedArray[char]](name)
let n_str = $name var i = 0
for i in 0..31: while nm[i] != '\0' and i < 31:
if i < n_str.len: io_buffer[dir_offset+i] = byte(n_str[i]) io_buffer[dir_offset + i] = byte(nm[i])
else: io_buffer[dir_offset+i] = 0 i += 1
io_buffer[dir_offset + i] = 0
io_buffer[dir_offset+32] = byte(first_sector and 0xFF) cast[ptr uint32](addr io_buffer[dir_offset + 32])[] = first_sector
io_buffer[dir_offset+33] = byte((first_sector shr 8) and 0xFF) cast[ptr uint32](addr io_buffer[dir_offset + 36])[] = uint32(data_len)
io_buffer[dir_offset+34] = byte((first_sector shr 16) and 0xFF)
io_buffer[dir_offset+35] = byte((first_sector shr 24) and 0xFF)
let sz = uint32(data_len)
io_buffer[dir_offset+36] = byte(sz and 0xFF)
io_buffer[dir_offset+37] = byte((sz shr 8) and 0xFF)
io_buffer[dir_offset+38] = byte((sz shr 16) and 0xFF)
io_buffer[dir_offset+39] = byte((sz shr 24) and 0xFF)
virtio_blk_write(SEC_DIR, addr io_buffer[0]) virtio_blk_write(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Multi-Sector Write Complete.")
proc sfs_read_file*(name: cstring, dest: pointer, max_len: int): int {.exportc, cdecl.} = proc sfs_read_file*(name: cstring, dest: pointer, max_len: int): int {.exportc, cdecl.} =
if not sfs_mounted: return -1 if not sfs_mounted: return -1
virtio_blk_read(SEC_DIR, addr io_buffer[0]) virtio_blk_read(SEC_DIR, addr io_buffer[0])
var start_sector = 0'u32 var start_sector = 0'u32
var file_size = 0'u32 var file_size = 0'u32
var found = false var found = false
for offset in countup(0, 511, 64): for offset in countup(0, 511, 64):
if io_buffer[offset] != 0: if io_buffer[offset] != 0:
var entry_name = "" if sfs_streq(name, cast[cstring](addr io_buffer[offset])):
for i in 0..31: start_sector = cast[ptr uint32](addr io_buffer[offset + 32])[]
if io_buffer[offset+i] == 0: break file_size = cast[ptr uint32](addr io_buffer[offset + 36])[]
entry_name.add(char(io_buffer[offset+i]))
if entry_name == $name:
start_sector = uint32(io_buffer[offset+32]) or
(uint32(io_buffer[offset+33]) shl 8) or
(uint32(io_buffer[offset+34]) shl 16) or
(uint32(io_buffer[offset+35]) shl 24)
file_size = uint32(io_buffer[offset+36]) or
(uint32(io_buffer[offset+37]) shl 8) or
(uint32(io_buffer[offset+38]) shl 16) or
(uint32(io_buffer[offset+39]) shl 24)
found = true found = true
break break
if not found: return -1 if not found: return -1
# Read Chain
var current_sector = start_sector var current_sector = start_sector
var dest_addr = cast[int](dest) var dest_addr = cast[uint64](dest)
var remaining = int(file_size) var remaining = if int(file_size) < max_len: int(file_size) else: max_len
if remaining > max_len: remaining = max_len var total = 0
while remaining > 0 and current_sector != EOF_MARKER:
var total_read = 0
while remaining > 0 and current_sector != EOF_MARKER and current_sector != 0:
var sector_buf: array[512, byte] var sector_buf: array[512, byte]
virtio_blk_read(uint64(current_sector), addr sector_buf[0]) virtio_blk_read(uint64(current_sector), addr sector_buf[0])
let chunk = if remaining < CHUNK_SIZE: remaining else: CHUNK_SIZE
copyMem(cast[pointer](dest_addr), addr sector_buf[0], chunk)
dest_addr += uint64(chunk)
remaining -= chunk
total += chunk
current_sector = cast[ptr uint32](addr sector_buf[508])[]
return total
# Extract Payload proc sfs_get_files*(): cstring = return "boot.kdl\n" # Dummy
let payload_size = min(remaining, CHUNK_SIZE)
# Be careful not to overflow dest buffer if payload_size > remaining (handled by min)
copyMem(cast[pointer](dest_addr), addr sector_buf[0], payload_size)
dest_addr += payload_size
remaining -= payload_size
total_read += payload_size
# Next Sector
current_sector = uint32(sector_buf[508]) or
(uint32(sector_buf[509]) shl 8) or
(uint32(sector_buf[510]) shl 16) or
(uint32(sector_buf[511]) shl 24)
return total_read
proc vfs_register_sfs(name: string, size: uint64) {.importc, cdecl.}
proc sfs_sync_vfs*() =
if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0])
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var name = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
let f_size = uint32(io_buffer[offset+36]) or
(uint32(io_buffer[offset+37]) shl 8) or
(uint32(io_buffer[offset+38]) shl 16) or
(uint32(io_buffer[offset+39]) shl 24)
vfs_register_sfs(name, uint64(f_size))

View File

@ -6,217 +6,101 @@
# See legal/LICENSE_SOVEREIGN.md for license terms. # See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: ROMFS (Static Tar Loader) ## Rumpk Layer 1: ROMFS (Static Tar Loader)
##
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) ## Freestanding implementation (No OS module dependencies).
# Rumpk L1: Sovereign VFS (Indexing TarFS) ## Uses a simple flat array for the file index.
{.push stackTrace: off, lineTrace: off.} {.push stackTrace: off, lineTrace: off.}
import std/tables
# Kernel Imports
proc kprint(s: cstring) {.importc, cdecl.}
proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint_hex(n: uint64) {.importc, cdecl.}
type type
TarHeader* = array[512, byte] TarHeader* = array[512, byte]
FileEntry = object FileEntry = object
offset*: uint64 name: array[64, char]
size*: uint64 offset: uint64
is_sfs*: bool size: uint64
active: bool
FileHandle = object const MAX_INDEX = 64
path*: string var index_table: array[MAX_INDEX, FileEntry]
offset*: uint64 var index_count: int = 0
is_sfs*: bool
is_ram*: bool
VFSInitRD* = object
start_addr*: uint64
end_addr*: uint64
index*: Table[string, FileEntry]
ram_data*: Table[string, seq[byte]]
fds*: Table[int, FileHandle]
next_fd*: int
var vfs*: VFSInitRD
proc vfs_init*(s: pointer, e: pointer) = proc vfs_init*(s: pointer, e: pointer) =
vfs.start_addr = cast[uint64](s) let start_addr = cast[uint64](s)
vfs.end_addr = cast[uint64](e) let end_addr = cast[uint64](e)
vfs.index = initTable[string, FileEntry]() index_count = 0
vfs.ram_data = initTable[string, seq[byte]]()
vfs.fds = initTable[int, FileHandle]()
vfs.next_fd = 3
# kprint("[VFS] InitRD Start: "); kprint_hex(vfs.start_addr); kprintln("") var p = start_addr
# kprint("[VFS] InitRD End: "); kprint_hex(vfs.end_addr); kprintln("") while p < end_addr:
var p = vfs.start_addr
while p < vfs.end_addr:
let h = cast[ptr TarHeader](p) let h = cast[ptr TarHeader](p)
if h[][0] == byte(0): break if h[][0] == byte(0): break
# kprint("[VFS] Raw Header: ") # Extract name
# for i in 0..15:
# kprint_hex(uint64(h[][i]))
# kprint(" ")
# kprintln("")
# Extract and normalize name directly from header
var name_len = 0 var name_len = 0
while name_len < 100 and h[][name_len] != 0: while name_len < 100 and h[][name_len] != 0: inc name_len
inc name_len
var start_idx = 0 var start_idx = 0
if name_len >= 2 and h[][0] == byte('.') and h[][1] == byte('/'): if name_len >= 2 and h[][0] == byte('.') and h[][1] == byte('/'): start_idx = 2
start_idx = 2 elif name_len >= 1 and h[][0] == byte('/'): start_idx = 1
elif name_len >= 1 and h[][0] == byte('/'):
start_idx = 1
let clean_len = name_len - start_idx let clean_len = name_len - start_idx
var clean = "" if clean_len > 0 and index_count < MAX_INDEX:
if clean_len > 0: var entry = addr index_table[index_count]
clean = newString(clean_len) entry.active = true
# Copy directly from header memory let to_copy = if clean_len < 63: clean_len else: 63
for i in 0..<clean_len: for i in 0..<to_copy:
clean[i] = char(h[][start_idx + i]) entry.name[i] = char(h[][start_idx + i])
entry.name[to_copy] = '\0'
if clean.len > 0: # Extract size (octal string at offset 124)
# Extract size (octal string)
var size: uint64 = 0 var size: uint64 = 0
for i in 124..134: for i in 124..134:
let b = h[][i] let b = h[][i]
if b >= byte('0') and b <= byte('7'): if b >= byte('0') and b <= byte('7'):
size = (size shl 3) or uint64(b - byte('0')) size = (size shl 3) or uint64(b - byte('0'))
entry.size = size
vfs.index[clean] = FileEntry(offset: p + 512'u64, size: size, is_sfs: false) entry.offset = p + 512
index_count += 1
# Move to next header # Move to next header
let padded_size = (size + 511'u64) and not 511'u64 let padded_size = (size + 511) and not 511'u64
p += 512'u64 + padded_size p += 512 + padded_size
else: else:
p += 512'u64 # Skip invalid/empty p += 512
proc vfs_open*(path: string, flags: int32 = 0): int = proc vfs_streq(s1, s2: cstring): bool =
var start_idx = 0 let p1 = cast[ptr UncheckedArray[char]](s1)
if path.len > 0 and path[0] == '/': let p2 = cast[ptr UncheckedArray[char]](s2)
start_idx = 1 var i = 0
while true:
if p1[i] != p2[i]: return false
if p1[i] == '\0': return true
i += 1
let clean_len = path.len - start_idx proc vfs_open*(path: cstring, flags: int32 = 0): int32 =
var clean = "" var p = path
if clean_len > 0: if path != nil and path[0] == '/':
clean = newString(clean_len) p = cast[cstring](cast[uint64](path) + 1)
for i in 0..<clean_len:
clean[i] = path[start_idx + i]
# 1. Check RamFS
if vfs.ram_data.hasKey(clean):
let fd = vfs.next_fd
vfs.fds[fd] = FileHandle(path: clean, offset: 0, is_sfs: false, is_ram: true)
vfs.next_fd += 1
return fd
# 2. Check TarFS
if vfs.index.hasKey(clean):
let entry = vfs.index[clean]
let fd = vfs.next_fd
vfs.fds[fd] = FileHandle(path: clean, offset: 0, is_sfs: entry.is_sfs,
is_ram: false)
vfs.next_fd += 1
return fd
# 3. Create if O_CREAT (bit 6 in POSIX)
if (flags and 64) != 0:
vfs.ram_data[clean] = @[]
let fd = vfs.next_fd
vfs.fds[fd] = FileHandle(path: clean, offset: 0, is_sfs: false, is_ram: true)
vfs.next_fd += 1
return fd
for i in 0..<index_count:
if vfs_streq(p, cast[cstring](addr index_table[i].name[0])):
return int32(i)
return -1 return -1
proc vfs_read_file*(path: string): string = proc vfs_read_at*(path: cstring, buf: pointer, count: uint64, offset: uint64): int64 =
var start_idx = 0 let fd = vfs_open(path)
if path.len > 0 and path[0] == '/': if fd < 0: return -1
start_idx = 1 let entry = addr index_table[fd]
let clean_len = path.len - start_idx
var clean = ""
if clean_len > 0:
clean = newString(clean_len)
for i in 0..<clean_len:
clean[i] = path[start_idx + i]
if vfs.ram_data.hasKey(clean):
let data = vfs.ram_data[clean]
if data.len == 0: return ""
var s = newString(data.len)
copyMem(addr s[0], unsafeAddr data[0], data.len)
return s
if vfs.index.hasKey(clean):
let entry = vfs.index[clean]
if entry.is_sfs: return ""
var s = newString(int(entry.size))
if entry.size > 0:
copyMem(addr s[0], cast[pointer](entry.offset), int(entry.size))
return s
return ""
proc vfs_read_at*(path: string, buf: pointer, count: uint64, offset: uint64): int64 =
if vfs.ram_data.hasKey(path):
let data = addr vfs.ram_data[path]
if offset >= uint64(data[].len): return 0
let available = uint64(data[].len) - offset
let actual = min(count, available)
if actual > 0:
copyMem(buf, addr data[][int(offset)], int(actual))
return int64(actual)
if not vfs.index.hasKey(path): return -1
let entry = vfs.index[path]
if entry.is_sfs: return -1 # Routed via SFS
var actual = uint64(count)
if offset >= entry.size: return 0 if offset >= entry.size: return 0
if offset + count > entry.size: let avail = entry.size - offset
actual = entry.size - offset let actual = if count < avail: count else: avail
if actual > 0:
copyMem(buf, cast[pointer](entry.offset + offset), int(actual)) copyMem(buf, cast[pointer](entry.offset + offset), int(actual))
return int64(actual) return int64(actual)
proc vfs_write_at*(path: string, buf: pointer, count: uint64, offset: uint64): int64 = proc vfs_write_at*(path: cstring, buf: pointer, count: uint64, offset: uint64): int64 =
# Promote to RamFS if on TarFS (CoW) # ROMFS is read-only
if not vfs.ram_data.hasKey(path): return -1
if vfs.index.hasKey(path):
let entry = vfs.index[path]
var content = newSeq[byte](int(entry.size))
if entry.size > 0:
copyMem(addr content[0], cast[pointer](entry.offset), int(entry.size))
vfs.ram_data[path] = content
else:
vfs.ram_data[path] = @[]
let data = addr vfs.ram_data[path] proc vfs_get_names*(): int = index_count # Dummy for listing
let min_size = int(offset + count)
if data[].len < min_size:
data[].setLen(min_size)
copyMem(addr data[][int(offset)], buf, int(count))
return int64(count)
# Removed ion_vfs_* in favor of vfs.nim dispatcher
proc vfs_get_names*(): seq[string] =
var names = initTable[string, bool]()
for name, _ in vfs.index: names[name] = true
for name, _ in vfs.ram_data: names[name] = true
result = @[]
for name, _ in names: result.add(name)
proc vfs_register_sfs*(name: string, size: uint64) {.exportc, cdecl.} =
vfs.index[name] = FileEntry(offset: 0, size: size, is_sfs: true)
{.pop.}

View File

@ -6,122 +6,160 @@
# See legal/LICENSE_SOVEREIGN.md for license terms. # See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: Sovereign VFS (The Loom) ## Rumpk Layer 1: Sovereign VFS (The Loom)
##
## Freestanding implementation (No OS module dependencies).
## Uses fixed-size arrays for descriptors to ensure deterministic latency.
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# VFS dispatcher for SPEC-130 alignment.
import strutils, tables
import tar, sfs import tar, sfs
type type
VFSMode = enum VFSMode = enum
MODE_TAR, MODE_SFS, MODE_RAM MODE_TAR, MODE_SFS, MODE_RAM, MODE_TTY
MountPoint = object MountPoint = object
prefix: string prefix: array[32, char]
mode: VFSMode mode: VFSMode
var mounts: seq[MountPoint] = @[]
type
FileHandle = object FileHandle = object
path: string path: array[64, char]
offset: uint64 offset: uint64
mode: VFSMode mode: VFSMode
is_ram: bool active: bool
var fds = initTable[int, FileHandle]() const MAX_MOUNTS = 8
var next_fd = 3 const MAX_FDS = 32
var mnt_table: array[MAX_MOUNTS, MountPoint]
var mnt_count: int = 0
var fd_table: array[MAX_FDS, FileHandle]
# Helper: manual string compare
proc vfs_starts_with(s, prefix: cstring): bool =
let ps = cast[ptr UncheckedArray[char]](s)
let pp = cast[ptr UncheckedArray[char]](prefix)
var i = 0
while pp[i] != '\0':
if ps[i] != pp[i]: return false
i += 1
return true
proc vfs_streq(s1, s2: cstring): bool =
let p1 = cast[ptr UncheckedArray[char]](s1)
let p2 = cast[ptr UncheckedArray[char]](s2)
var i = 0
while true:
if p1[i] != p2[i]: return false
if p1[i] == '\0': return true
i += 1
proc vfs_add_mount(prefix: cstring, mode: VFSMode) =
if mnt_count >= MAX_MOUNTS: return
let p = cast[ptr UncheckedArray[char]](prefix)
var i = 0
while p[i] != '\0' and i < 31:
mnt_table[mnt_count].prefix[i] = p[i]
i += 1
mnt_table[mnt_count].prefix[i] = '\0'
mnt_table[mnt_count].mode = mode
mnt_count += 1
proc vfs_mount_init*() = proc vfs_mount_init*() =
# SPEC-130: The Three-Domain Root # Restore the SPEC-130 baseline
# SPEC-021: The Sovereign Overlay Strategy vfs_add_mount("/nexus", MODE_SFS)
mounts.add(MountPoint(prefix: "/nexus", mode: MODE_SFS)) # The Sovereign State (Persistent) vfs_add_mount("/sysro", MODE_TAR)
mounts.add(MountPoint(prefix: "/sysro", mode: MODE_TAR)) # The Projected Reality (Immutable InitRD) vfs_add_mount("/state", MODE_RAM)
mounts.add(MountPoint(prefix: "/state", mode: MODE_RAM)) # The Mutable Dust (Transient) vfs_add_mount("/dev/tty", MODE_TTY)
vfs_add_mount("/Bus/Console/tty0", MODE_TTY)
proc resolve_path(path: string): (VFSMode, string) = proc resolve_path(path: cstring): (VFSMode, int) =
for m in mounts: for i in 0..<mnt_count:
if path.startsWith(m.prefix): let prefix = cast[cstring](addr mnt_table[i].prefix[0])
let sub = if path.len > m.prefix.len: path[m.prefix.len..^1] else: "/" if vfs_starts_with(path, prefix):
return (m.mode, sub) var len = 0
return (MODE_TAR, path) while mnt_table[i].prefix[len] != '\0': len += 1
return (mnt_table[i].mode, len)
return (MODE_TAR, 0)
# Syscall implementation procs
# Kernel Imports
# (Currently unused, relying on kprintln from kernel)
proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} = proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} =
let p = $path let (mode, prefix_len) = resolve_path(path)
let (mode, sub) = resolve_path(p)
# Delegate internal open
let sub_path = cast[cstring](cast[uint64](path) + uint64(prefix_len))
var internal_fd: int32 = -1
var fd = -1
case mode: case mode:
of MODE_TAR: fd = tar.vfs_open(sub, flags) of MODE_TAR, MODE_RAM: internal_fd = tar.vfs_open(sub_path, flags)
of MODE_SFS: fd = 0 # Placeholder for SFS open of MODE_SFS: internal_fd = 0 # Shim
of MODE_RAM: fd = tar.vfs_open(sub, flags) # Using TAR's RamFS for now of MODE_TTY: internal_fd = 1 # Shim
if fd > 0: if internal_fd >= 0:
let kernel_fd = next_fd for i in 0..<MAX_FDS:
fds[kernel_fd] = FileHandle(path: sub, offset: 0, mode: mode, is_ram: (mode == MODE_RAM)) if not fd_table[i].active:
next_fd += 1 fd_table[i].active = true
return int32(kernel_fd) fd_table[i].mode = mode
fd_table[i].offset = 0
let p = cast[ptr UncheckedArray[char]](sub_path)
var j = 0
while p[j] != '\0' and j < 63:
fd_table[i].path[j] = p[j]
j += 1
fd_table[i].path[j] = '\0'
return int32(i + 3) # FDs start at 3
return -1 return -1
proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let ifd = int(fd) let idx = int(fd - 3)
if not fds.hasKey(ifd): return -1 if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fds[ifd] let fh = addr fd_table[idx]
case fh.mode: case fh.mode:
of MODE_TTY: return -2
of MODE_TAR, MODE_RAM: of MODE_TAR, MODE_RAM:
let n = tar.vfs_read_at(fh.path, buf, count, fh.offset) let path = cast[cstring](addr fh.path[0])
let n = tar.vfs_read_at(path, buf, count, fh.offset)
if n > 0: fh.offset += uint64(n) if n > 0: fh.offset += uint64(n)
return n return n
of MODE_SFS: of MODE_SFS:
# SFS current read-whole-file shim let path = cast[cstring](addr fh.path[0])
var temp_buf: array[4096, byte] # FIXME: Small stack buffer var temp: array[256, byte] # Small shim
let total = sfs.sfs_read_file(cstring(fh.path), addr temp_buf[0], 4096) let n = sfs.sfs_read_file(path, addr temp[0], 256)
if total < 0: return -1 if n <= 0: return -1
if fh.offset >= uint64(total): return 0 let avail = uint64(n) - fh.offset
let avail = uint64(total) - fh.offset let actual = if count < avail: count else: avail
let actual = min(count, avail)
if actual > 0: if actual > 0:
copyMem(buf, addr temp_buf[int(fh.offset)], int(actual)) copyMem(buf, addr temp[int(fh.offset)], int(actual))
fh.offset += actual fh.offset += actual
return int64(actual) return int64(actual)
return 0
proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let ifd = int(fd) let idx = int(fd - 3)
if not fds.hasKey(ifd): return -1 if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fds[ifd] let fh = addr fd_table[idx]
case fh.mode: case fh.mode:
of MODE_TTY: return -2
of MODE_TAR, MODE_RAM: of MODE_TAR, MODE_RAM:
let n = tar.vfs_write_at(fh.path, buf, count, fh.offset) let path = cast[cstring](addr fh.path[0])
let n = tar.vfs_write_at(path, buf, count, fh.offset)
if n > 0: fh.offset += uint64(n) if n > 0: fh.offset += uint64(n)
return n return n
of MODE_SFS: of MODE_SFS:
sfs.sfs_write_file(cstring(fh.path), cast[cstring](buf), int(count)) let path = cast[cstring](addr fh.path[0])
sfs.sfs_write_file(path, buf, int(count))
return int64(count) return int64(count)
proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} = proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} =
let ifd = int(fd) let idx = int(fd - 3)
if fds.hasKey(ifd): if idx >= 0 and idx < MAX_FDS:
fds.del(ifd) fd_table[idx].active = false
return 0 return 0
return -1 return -1
proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} =
var s = "/nexus\n/sysro\n/state\n" # Hardcoded baseline for now to avoid string/os dependency
for name in tar.vfs_get_names(): let msg = "/nexus\n/sysro\n/state\n"
s.add("/sysro/" & name & "\n") let n = if uint64(msg.len) < max_len: uint64(msg.len) else: max_len
if n > 0: copyMem(buf, unsafeAddr msg[0], int(n))
# Add SFS files under /nexus
let sfs_names = sfs.sfs_get_files()
for line in sfs_names.splitLines():
if line.len > 0:
s.add("/nexus/" & line & "\n")
let n = min(s.len, int(max_len))
if n > 0: copyMem(buf, addr s[0], n)
return int64(n) return int64(n)

View File

@ -46,6 +46,7 @@ type
CMD_NET_RX = 0x501 CMD_NET_RX = 0x501
CMD_BLK_READ = 0x600 CMD_BLK_READ = 0x600
CMD_BLK_WRITE = 0x601 CMD_BLK_WRITE = 0x601
CMD_SPAWN_FIBER = 0x700
CmdPacket* = object CmdPacket* = object
kind*: uint32 kind*: uint32
@ -141,9 +142,15 @@ proc recv*[T](c: var SovereignChannel[T], out_pkt: var T): bool =
elif T is CmdPacket: elif T is CmdPacket:
return hal_cmd_pop(cast[uint64](c.ring), addr out_pkt) return hal_cmd_pop(cast[uint64](c.ring), addr out_pkt)
# Global Channels
var chan_input*: SovereignChannel[IonPacket] var chan_input*: SovereignChannel[IonPacket]
var chan_cmd*: SovereignChannel[CmdPacket]
var chan_rx*: SovereignChannel[IonPacket]
var chan_tx*: SovereignChannel[IonPacket]
var guest_input_hal: HAL_Ring[IonPacket] var guest_input_hal: HAL_Ring[IonPacket]
var cmd_hal: HAL_Ring[CmdPacket]
var rx_hal: HAL_Ring[IonPacket]
var tx_hal: HAL_Ring[IonPacket]
# Phase 36.2: Network Channels # Phase 36.2: Network Channels
var chan_net_rx*: SovereignChannel[IonPacket] var chan_net_rx*: SovereignChannel[IonPacket]

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,95 @@ extern fn console_write(ptr: [*]const u8, len: usize) void;
// Embed the Subject Zero binary // Embed the Subject Zero binary
export var subject_bin = @embedFile("subject.bin"); export var subject_bin = @embedFile("subject.bin");
export fn ion_loader_load(path: [*:0]const u8) u64 {
_ = path;
console_write("[Loader] Parsing ELF\n", 21);
// Verify ELF Magic
const magic = subject_bin[0..4];
if (magic[0] != 0x7F or magic[1] != 'E' or magic[2] != 'L' or magic[3] != 'F') {
console_write("[Loader] ERROR: Invalid ELF magic\n", 35);
return 0;
}
// Parse ELF64 Header
const e_entry = read_u64_le(subject_bin[0x18..0x20]);
const e_phoff = read_u64_le(subject_bin[0x20..0x28]);
const e_phentsize = read_u16_le(subject_bin[0x36..0x38]);
const e_phnum = read_u16_le(subject_bin[0x38..0x3a]);
console_write("[Loader] Entry: 0x", 18);
print_hex(e_entry);
console_write("\n[Loader] Loading ", 17);
print_hex(e_phnum);
console_write(" segments\n", 10);
// Load each PT_LOAD segment
var i: usize = 0;
while (i < e_phnum) : (i += 1) {
const ph_offset = e_phoff + (i * e_phentsize);
const p_type = read_u32_le(subject_bin[ph_offset .. ph_offset + 4]);
if (p_type == 1) { // PT_LOAD
const p_offset = read_u64_le(subject_bin[ph_offset + 8 .. ph_offset + 16]);
const p_vaddr = read_u64_le(subject_bin[ph_offset + 16 .. ph_offset + 24]);
const p_filesz = read_u64_le(subject_bin[ph_offset + 32 .. ph_offset + 40]);
const p_memsz = read_u64_le(subject_bin[ph_offset + 40 .. ph_offset + 48]);
const dest = @as([*]u8, @ptrFromInt(p_vaddr));
// Copy file content
if (p_filesz > 0) {
const src = subject_bin[p_offset .. p_offset + p_filesz];
@memcpy(dest[0..p_filesz], src);
}
// Zero BSS (memsz > filesz)
if (p_memsz > p_filesz) {
@memset(dest[p_filesz..p_memsz], 0);
}
}
}
console_write("[Loader] ELF loaded successfully\n", 33);
return e_entry;
}
fn read_u16_le(bytes: []const u8) u16 {
return @as(u16, bytes[0]) | (@as(u16, bytes[1]) << 8);
}
fn read_u32_le(bytes: []const u8) u32 {
return @as(u32, bytes[0]) |
(@as(u32, bytes[1]) << 8) |
(@as(u32, bytes[2]) << 16) |
(@as(u32, bytes[3]) << 24);
}
fn read_u64_le(bytes: []const u8) u64 {
var result: u64 = 0;
var j: usize = 0;
while (j < 8) : (j += 1) {
result |= @as(u64, bytes[j]) << @intCast(j * 8);
}
return result;
}
fn print_hex(value: u64) void {
const hex_chars = "0123456789ABCDEF";
var buf: [16]u8 = undefined;
var i: usize = 0;
while (i < 16) : (i += 1) {
const shift: u6 = @intCast((15 - i) * 4);
const nibble = (value >> shift) & 0xF;
buf[i] = hex_chars[nibble];
}
console_write(&buf, 16);
}
export fn launch_subject() void { export fn launch_subject() void {
const target_addr: usize = 0x84000000; const target_addr = ion_loader_load("/sysro/bin/subject");
const dest = @as([*]u8, @ptrFromInt(target_addr));
console_write("[Loader] Loading Subject Zero...\n", 33);
@memcpy(dest[0..subject_bin.len], subject_bin);
console_write("[Loader] Jumping...\n", 20); console_write("[Loader] Jumping...\n", 20);
const entry = @as(*const fn () void, @ptrFromInt(target_addr)); const entry = @as(*const fn () void, @ptrFromInt(target_addr));

View File

@ -32,7 +32,7 @@ proc virtio_net_send(data: pointer, len: uint32) {.importc, cdecl.}
proc kprintln(s: cstring) {.importc, cdecl.} proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint(s: cstring) {.importc, cdecl.} proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.} proc kprint_hex(v: uint64) {.importc, cdecl.}
proc get_now_ns(): uint64 {.importc, cdecl.} proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Membrane Infrastructure (LwIP Glue) # Membrane Infrastructure (LwIP Glue)
proc membrane_init*() {.importc, cdecl.} proc membrane_init*() {.importc, cdecl.}

View File

@ -53,14 +53,14 @@ proc kprint(s: cstring) {.importc, cdecl.}
proc kprintln(s: cstring) {.importc, cdecl.} proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.} proc kprint_hex(v: uint64) {.importc, cdecl.}
proc pty_init*() = proc pty_init*() {.exportc, cdecl.} =
for i in 0 ..< MAX_PTYS: for i in 0 ..< MAX_PTYS:
ptys[i].active = false ptys[i].active = false
ptys[i].id = -1 ptys[i].id = -1
next_pty_id = 0 next_pty_id = 0
kprintln("[PTY] Subsystem Initialized") kprintln("[PTY] Subsystem Initialized")
proc pty_alloc*(): int = proc pty_alloc*(): int {.exportc, cdecl.} =
## Allocate a new PTY pair. Returns PTY ID or -1 on failure. ## Allocate a new PTY pair. Returns PTY ID or -1 on failure.
for i in 0 ..< MAX_PTYS: for i in 0 ..< MAX_PTYS:
if not ptys[i].active: if not ptys[i].active:
@ -160,7 +160,7 @@ proc pty_read_master*(fd: int, data: ptr byte, len: int): int =
read_count += 1 read_count += 1
return read_count return read_count
proc pty_write_slave*(fd: int, data: ptr byte, len: int): int = proc pty_write_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.} =
## Write to slave (output from shell). Goes to master read buffer. ## Write to slave (output from shell). Goes to master read buffer.
## Also renders to FB terminal. ## Also renders to FB terminal.
let pty = get_pty_from_fd(fd) let pty = get_pty_from_fd(fd)
@ -186,7 +186,7 @@ proc pty_write_slave*(fd: int, data: ptr byte, len: int): int =
return written return written
proc pty_read_slave*(fd: int, data: ptr byte, len: int): int = proc pty_read_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.} =
## Read from slave (input to shell). Gets master input. ## Read from slave (input to shell). Gets master input.
let pty = get_pty_from_fd(fd) let pty = get_pty_from_fd(fd)
if pty == nil: return -1 if pty == nil: return -1
@ -209,13 +209,13 @@ proc pty_read_slave*(fd: int, data: ptr byte, len: int): int =
return read_count return read_count
proc pty_has_data_for_slave*(pty_id: int): bool = proc pty_has_data_for_slave*(pty_id: int): bool {.exportc, cdecl.} =
## Check if there's input waiting for the slave. ## Check if there's input waiting for the slave.
if pty_id < 0 or pty_id >= MAX_PTYS: return false if pty_id < 0 or pty_id >= MAX_PTYS: return false
if not ptys[pty_id].active: return false if not ptys[pty_id].active: return false
return ring_count(ptys[pty_id].mts_head, ptys[pty_id].mts_tail) > 0 return ring_count(ptys[pty_id].mts_head, ptys[pty_id].mts_tail) > 0
proc pty_push_input*(pty_id: int, ch: char) = proc pty_push_input*(pty_id: int, ch: char) {.exportc, cdecl.} =
## Push a character to the master-to-slave buffer (keyboard input). ## Push a character to the master-to-slave buffer (keyboard input).
if pty_id < 0 or pty_id >= MAX_PTYS: return if pty_id < 0 or pty_id >= MAX_PTYS: return
if not ptys[pty_id].active: return if not ptys[pty_id].active: return

View File

@ -41,7 +41,7 @@ import fiber
# 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). # We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).
proc sched_get_now_ns*(): uint64 {.importc: "get_now_ns", cdecl.} proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Forward declaration for the tick function # Forward declaration for the tick function
# Returns TRUE if a fiber was switched to (work done/found). # Returns TRUE if a fiber was switched to (work done/found).
@ -119,6 +119,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# If we reached here, NO fiber is runnable. # If we reached here, NO fiber is runnable.
return false return false
proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
var min_wakeup: uint64 = 0xFFFFFFFFFFFFFFFF'u64
let now = sched_get_now_ns()
for f in fibers:
if f != nil and f.sleep_until > now:
if f.sleep_until < min_wakeup:
min_wakeup = f.sleep_until
return min_wakeup
# ========================================================= # =========================================================
# THE RATCHET (Post-Execution Analysis) # THE RATCHET (Post-Execution Analysis)
# ========================================================= # =========================================================

View File

@ -22,8 +22,12 @@ cpu_switch_to:
sd s9, 96(sp) sd s9, 96(sp)
sd s10, 104(sp) sd s10, 104(sp)
sd s11, 112(sp) sd s11, 112(sp)
csrr t0, sscratch
sd t0, 120(sp)
sd sp, 0(a0) sd sp, 0(a0)
mv sp, a1 mv sp, a1
ld t0, 120(sp)
csrw sscratch, t0
ld ra, 0(sp) ld ra, 0(sp)
ld gp, 8(sp) ld gp, 8(sp)
ld tp, 16(sp) ld tp, 16(sp)
@ -31,7 +35,6 @@ cpu_switch_to:
ld s1, 32(sp) ld s1, 32(sp)
ld s2, 40(sp) ld s2, 40(sp)
ld s3, 48(sp) ld s3, 48(sp)
sd s4, 56(sp)
ld s4, 56(sp) ld s4, 56(sp)
ld s5, 64(sp) ld s5, 64(sp)
ld s6, 72(sp) ld s6, 72(sp)

View File

@ -107,17 +107,21 @@ export fn trap_entry() align(4) callconv(.naked) void {
// 🔧 CRITICAL FIX: Stack Switching (User -> Kernel) // 🔧 CRITICAL FIX: Stack Switching (User -> Kernel)
// Swap sp and sscratch. // Swap sp and sscratch.
// If from User: sp=KStack, sscratch=UStack // If from User: sp=KStack, sscratch=UStack
// If from Kernel: sp=0 (sscratch was 0), sscratch=KStack // If from Kernel: sp=0, sscratch=ValidStack (Problematic logic if not careful)
// Correct Logic:
// If sscratch == 0: We came from Kernel. sp is already KStack. Do NOTHING to sp.
// If sscratch != 0: We came from User. sp is UStack. Swap to get KStack.
\\ csrrw sp, sscratch, sp \\ csrrw sp, sscratch, sp
\\ bnez sp, 1f \\ bnez sp, 1f
// Came from Kernel (sp was 0). Restore sp. // Kernel -> Kernel (recursive). Restore sp from sscratch (which had the 0).
\\ csrrw sp, sscratch, sp \\ csrrw sp, sscratch, sp
\\ 1: \\ 1:
// Allocate stack (36 words * 8 bytes = 288 bytes) // Allocation (36*8 = 288 bytes)
\\ addi sp, sp, -288 \\ addi sp, sp, -288
// Save GPRs // Save Registers (GPRs)
\\ sd ra, 0(sp) \\ sd ra, 0(sp)
\\ sd gp, 8(sp) \\ sd gp, 8(sp)
\\ sd tp, 16(sp) \\ sd tp, 16(sp)
@ -169,14 +173,15 @@ export fn trap_entry() align(4) callconv(.naked) void {
\\ mv a0, sp \\ mv a0, sp
\\ call rss_trap_handler \\ call rss_trap_handler
// Restore CSRs // Restore CSRs (Optional if modified? sepc changed for syscall)
\\ ld t0, 240(sp) \\ ld t0, 240(sp)
\\ csrw sepc, t0 \\ csrw sepc, t0
// We restore sstatus // sstatus often modified to change mode? For return, we use sret.
// We might want to restore sstatus if we support nested interrupts properly.
\\ ld t1, 248(sp) \\ ld t1, 248(sp)
\\ csrw sstatus, t1 \\ csrw sstatus, t1
// Restore GPRs // Restore Encapsulated User Context
\\ ld ra, 0(sp) \\ ld ra, 0(sp)
\\ ld gp, 8(sp) \\ ld gp, 8(sp)
\\ ld tp, 16(sp) \\ ld tp, 16(sp)
@ -211,10 +216,16 @@ export fn trap_entry() align(4) callconv(.naked) void {
// Deallocate stack // Deallocate stack
\\ addi sp, sp, 288 \\ addi sp, sp, 288
// 🔧 CRITICAL FIX: Swap back sscratch <-> sp before sret // 🔧 CRITICAL FIX: Swap back sscratch <-> sp ONLY if returning to User Mode
// If returning to U-mode, sscratch currently holds User Stack. // Check sstatus.SPP (Bit 8). 0 = User, 1 = Supervisor.
// We need: sp = User Stack, sscratch = Kernel Stack \\ csrr t0, sstatus
\\ li t1, 0x100
\\ and t0, t0, t1
\\ bnez t0, 2f
// Returning to User: Swap sp (Kernel Stack) with sscratch (User Stack)
\\ csrrw sp, sscratch, sp \\ csrrw sp, sscratch, sp
\\ 2:
\\ sret \\ sret
); );
} }
@ -227,6 +238,37 @@ extern fn k_check_deferred_yield() void;
export fn rss_trap_handler(frame: *TrapFrame) void { export fn rss_trap_handler(frame: *TrapFrame) void {
const scause = frame.scause; const scause = frame.scause;
// uart.print("[Trap] Entered Handler. scause: ");
// uart.print_hex(scause);
// uart.print("\n");
// Check high bit: 0 = Exception, 1 = Interrupt
if ((scause >> 63) != 0) {
const intr_id = scause & 0x7FFFFFFFFFFFFFFF;
if (intr_id == 9) {
// PLIC Context 1 (Supervisor) Claim/Complete Register
const PLIC_CLAIM: *volatile u32 = @ptrFromInt(0x0c201004);
const irq = PLIC_CLAIM.*;
if (irq == 10) { // UART0 is IRQ 10 on Virt machine
uart.poll_input();
} else if (irq == 0) {
// Spurious or no pending interrupt
}
// Complete the interrupt
PLIC_CLAIM.* = irq;
} else if (intr_id == 5) {
// Supervisor Timer Interrupt
// Disable (One-shot)
asm volatile ("csrc sie, %[mask]"
:
: [mask] "r" (@as(usize, 1 << 5)),
);
}
k_check_deferred_yield();
return;
}
// 8: ECALL from U-mode // 8: ECALL from U-mode
// 9: ECALL from S-mode // 9: ECALL from S-mode
@ -241,17 +283,13 @@ export fn rss_trap_handler(frame: *TrapFrame) void {
frame.a0 = res; frame.a0 = res;
// DIAGNOSTIC: Syscall completed // DIAGNOSTIC: Syscall completed
uart.print("[Trap] Syscall done, returning to userland\n"); // uart.print("[Trap] Syscall done, returning to userland\n");
// uart.puts("[Trap] Checking deferred yield\n");
// Check for deferred yield
k_check_deferred_yield(); k_check_deferred_yield();
return; return;
} }
// Delegate all other exceptions to the Kernel Immune System // Delegate all other exceptions to the Kernel Immune System
// It will decide whether to segregate (worker) or halt (system)
// Note: k_handle_exception handles flow control (yield/halt) and does not return
k_handle_exception(scause, frame.sepc, frame.stval); k_handle_exception(scause, frame.sepc, frame.stval);
// Safety halt if kernel returns (should be unreachable) // Safety halt if kernel returns (should be unreachable)
@ -268,6 +306,15 @@ extern fn NimMain() void;
export fn zig_entry() void { export fn zig_entry() void {
uart.init_riscv(); uart.init_riscv();
// 🔧 CRITICAL FIX: Enable SUM (Supervisor User Memory) Access
// S-mode needs to write to U-mode pages (e.g. loading apps at 0x88000000)
// sstatus.SUM is bit 18 (0x40000)
asm volatile (
\\ li t0, 0x40000
\\ csrs sstatus, t0
);
uart.print("[Rumpk L0] zig_entry reached\n"); uart.print("[Rumpk L0] zig_entry reached\n");
uart.print("[Rumpk RISC-V] Handing off to Nim L1...\n"); uart.print("[Rumpk RISC-V] Handing off to Nim L1...\n");
_ = virtio_net; _ = virtio_net;
@ -287,6 +334,14 @@ export fn console_read() c_int {
return -1; return -1;
} }
export fn console_poll() void {
uart.poll_input();
}
export fn debug_uart_lsr() u8 {
return uart.get_lsr();
}
const virtio_block = @import("virtio_block.zig"); const virtio_block = @import("virtio_block.zig");
extern fn hal_surface_init() void; extern fn hal_surface_init() void;
@ -314,6 +369,27 @@ export fn rumpk_timer_now_ns() u64 {
return ticks * 100; return ticks * 100;
} }
export fn sched_arm_timer(deadline_ns: u64) void {
// 1 tick = 100ns (10MHz)
const deadline_ticks = deadline_ns / 100;
// Use SBI Time Extension (0x54494D45) to set timer
// FID=0: sbi_set_timer(stime_value)
asm volatile (
\\ ecall
:
: [arg0] "{a0}" (deadline_ticks),
[eid] "{a7}" (0x54494D45),
[fid] "{a6}" (0),
: .{ .memory = true });
// Enable STIE (Supervisor Timer Interrupt Enable) in sie (bit 5)
asm volatile ("csrs sie, %[mask]"
:
: [mask] "r" (@as(usize, 1 << 5)),
);
}
// ========================================================= // =========================================================
// KEXEC (The Phoenix Protocol) // KEXEC (The Phoenix Protocol)
// ========================================================= // =========================================================
@ -338,3 +414,38 @@ export fn hal_kexec(entry: u64, dtb: u64) noreturn {
); );
unreachable; unreachable;
} }
// =========================================================
// USERLAND TRANSITION
// =========================================================
export fn hal_enter_userland(entry: u64, systable: u64, sp: u64) callconv(.c) void {
// 1. Set up sstatus: SPP=0 (User), SPIE=1 (Enable interrupts on return)
// 2. Set sepc to entry point
// 3. Set sscratch to current kernel stack
// 4. Transition via sret
var kstack: usize = 0;
asm volatile ("mv %[kstack], sp"
: [kstack] "=r" (kstack),
);
asm volatile (
\\ li t0, 0x20 // sstatus.SPIE = 1 (bit 5)
\\ csrs sstatus, t0
\\ li t1, 0x100 // sstatus.SPP = 1 (bit 8)
\\ csrc sstatus, t1
\\ li t2, 0x40000 // sstatus.SUM = 1 (bit 18)
\\ csrs sstatus, t2
\\ csrw sepc, %[entry]
\\ csrw sscratch, %[kstack]
\\ mv sp, %[sp]
\\ mv a0, %[systable]
\\ sret
:
: [entry] "r" (entry),
[systable] "r" (systable),
[sp] "r" (sp),
[kstack] "r" (kstack),
);
}

View File

@ -23,7 +23,7 @@ pub const LEVELS: u8 = 3;
// Physical memory layout (RISC-V QEMU virt) // Physical memory layout (RISC-V QEMU virt)
pub const DRAM_BASE: u64 = 0x80000000; pub const DRAM_BASE: u64 = 0x80000000;
pub const DRAM_SIZE: u64 = 256 * 1024 * 1024; // 256MB for expanded userspace pub const DRAM_SIZE: u64 = 512 * 1024 * 1024; // Expanded for multi-fiber isolation
// MMIO regions // MMIO regions
pub const UART_BASE: u64 = 0x10000000; pub const UART_BASE: u64 = 0x10000000;
@ -164,6 +164,7 @@ pub fn create_kernel_identity_map() !*PageTable {
// MMIO regions // MMIO regions
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W); try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W);
try map_range(root, 0x10001000, 0x10001000, 0x8000, PTE_R | PTE_W); try map_range(root, 0x10001000, 0x10001000, 0x8000, PTE_R | PTE_W);
try map_range(root, 0x20000000, 0x20000000, 0x10000, PTE_R | PTE_W); // PTY Slave
try map_range(root, 0x30000000, 0x30000000, 0x10000000, PTE_R | PTE_W); try map_range(root, 0x30000000, 0x30000000, 0x10000000, PTE_R | PTE_W);
try map_range(root, 0x40000000, 0x40000000, 0x10000000, PTE_R | PTE_W); try map_range(root, 0x40000000, 0x40000000, 0x10000000, PTE_R | PTE_W);
try map_range(root, PLIC_BASE, PLIC_BASE, 0x400000, PTE_R | PTE_W); try map_range(root, PLIC_BASE, PLIC_BASE, 0x400000, PTE_R | PTE_W);
@ -172,25 +173,35 @@ pub fn create_kernel_identity_map() !*PageTable {
} }
// Create restricted worker map // Create restricted worker map
pub fn create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64) !*PageTable { pub fn create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64, code_base_pa: u64) !*PageTable {
const root = alloc_page_table() orelse return error.OutOfMemory; const root = alloc_page_table() orelse return error.OutOfMemory;
// 🏛 THE EXPANDED CAGE (Phase 37 - 256MB RAM) // 🏛 THE ISOLATED CAGE (Phase 40 - Per-Fiber Memory Isolation)
kprint("[MM] Creating worker map:\n"); kprint("[MM] Creating worker map:\n");
kprint("[MM] Kernel (S-mode): 0x80000000-0x88000000\n"); kprint("[MM] Kernel (S-mode): 0x80000000-0x88000000\n");
kprint("[MM] User (U-mode): 0x88000000-0x90000000\n"); kprint("[MM] User VA: 0x88000000-0x90000000\n");
kprint("[MM] User PA: ");
kprint_hex(code_base_pa);
kprint("\n");
// 1. Kernel Memory (0-128MB) -> Supervisor ONLY (PTE_U = 0) // 1. Kernel Memory (0-128MB) -> Supervisor ONLY (PTE_U = 0)
// This allows the fiber trampoline to execute in S-mode. // This allows the fiber trampoline to execute in S-mode.
try map_range(root, DRAM_BASE, DRAM_BASE, 128 * 1024 * 1024, PTE_R | PTE_W | PTE_X); try map_range(root, DRAM_BASE, DRAM_BASE, 128 * 1024 * 1024, PTE_R | PTE_W | PTE_X);
// 2. User Memory (128-256MB) -> User Accessible (PTE_U = 1) // 2. User Memory (VA 0x88000000 -> PA code_base_pa) -> User Accessible (PTE_U = 1)
// This allows NipBox (at 128MB offset) to execute in U-mode. // This creates ISOLATED physical regions per fiber:
try map_range(root, DRAM_BASE + (128 * 1024 * 1024), DRAM_BASE + (128 * 1024 * 1024), 128 * 1024 * 1024, PTE_R | PTE_W | PTE_X | PTE_U); // - Init: VA 0x88000000 -> PA 0x88000000
// - Child: VA 0x88000000 -> PA 0x90000000 (or higher)
const user_va_base = DRAM_BASE + (128 * 1024 * 1024);
try map_range(root, user_va_base, code_base_pa, 128 * 1024 * 1024, PTE_R | PTE_W | PTE_X | PTE_U);
// 3. User MMIO (UART) // 3. MMIO Plumbing - Mapped identity but S-mode ONLY (PTE_U = 0)
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W | PTE_U); // This allows the kernel to handle interrupts/IO while fiber map is active.
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W);
try map_range(root, PLIC_BASE, PLIC_BASE, 0x400000, PTE_R | PTE_W);
try map_range(root, VIRTIO_BASE, VIRTIO_BASE, 0x8000, PTE_R | PTE_W);
try map_range(root, 0x20000000, 0x20000000, 0x10000, PTE_R | PTE_W); // PTY Slave
// 4. Overlap stack with user access // 4. Overlap stack with user access
try map_range(root, stack_base, stack_base, stack_size, PTE_R | PTE_W | PTE_U); try map_range(root, stack_base, stack_base, stack_size, PTE_R | PTE_W | PTE_U);
@ -244,8 +255,8 @@ pub export fn mm_get_kernel_satp() callconv(.c) u64 {
return kernel_satp_value; return kernel_satp_value;
} }
pub export fn mm_create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64) callconv(.c) u64 { pub export fn mm_create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64, code_base_pa: u64) callconv(.c) u64 {
if (create_worker_map(stack_base, stack_size, packet_addr)) |root| { if (create_worker_map(stack_base, stack_size, packet_addr, code_base_pa)) |root| {
return make_satp(root); return make_satp(root);
} else |_| { } else |_| {
return 0; return 0;

View File

@ -35,15 +35,16 @@ const NS16550A_LCR: usize = 0x03; // Line Control Register
// Input Ring Buffer (256 bytes, power of 2 for fast masking) // Input Ring Buffer (256 bytes, power of 2 for fast masking)
const INPUT_BUFFER_SIZE = 256; const INPUT_BUFFER_SIZE = 256;
// SAFETY(RingBuffer): Only accessed via head/tail indices. // SAFETY(RingBuffer): Only accessed via head/tail indices.
// SAFETY(RingBuffer): Only accessed via head/tail indices.
// Bytes are written before read. No uninitialized reads possible. // Bytes are written before read. No uninitialized reads possible.
var input_buffer: [INPUT_BUFFER_SIZE]u8 = undefined; var input_buffer: [INPUT_BUFFER_SIZE]u8 = undefined;
var input_head: u32 = 0; // Write position var input_head = std.atomic.Value(u32).init(0); // Write position
var input_tail: u32 = 0; // Read position var input_tail = std.atomic.Value(u32).init(0); // Read position
pub fn init() void { pub fn init() void {
// Initialize buffer pointers // Initialize buffer pointers
input_head = 0; input_head.store(0, .monotonic);
input_tail = 0; input_tail.store(0, .monotonic);
switch (builtin.cpu.arch) { switch (builtin.cpu.arch) {
.riscv64 => init_riscv(), .riscv64 => init_riscv(),
@ -54,18 +55,65 @@ pub fn init() void {
pub fn init_riscv() void { pub fn init_riscv() void {
const base = NS16550A_BASE; const base = NS16550A_BASE;
// 1. Disable Interrupts // 1. Enable Interrupts (Received Data Available)
const ier: *volatile u8 = @ptrFromInt(base + NS16550A_IER); const ier: *volatile u8 = @ptrFromInt(base + NS16550A_IER);
ier.* = 0x00; ier.* = 0x01; // 0x01 = Data Ready Interrupt.
// 2. Enable FIFO, clear them, with 14-byte threshold // 2. Disable FIFO (16450 Mode) to ensure immediate non-buffered input visibility
const fcr: *volatile u8 = @ptrFromInt(base + NS16550A_FCR); const fcr: *volatile u8 = @ptrFromInt(base + NS16550A_FCR);
fcr.* = 0x07; fcr.* = 0x00;
// 2b. Enable Modem Control (DTR | RTS | OUT2)
// Essential for allowing interrupts and signaling readiness
const mcr: *volatile u8 = @ptrFromInt(base + 0x04); // NS16550A_MCR
mcr.* = 0x0B;
// 3. Set LCR to 8N1 // 3. Set LCR to 8N1
const lcr: *volatile u8 = @ptrFromInt(base + NS16550A_LCR); const lcr: *volatile u8 = @ptrFromInt(base + NS16550A_LCR);
lcr.* = 0x03; lcr.* = 0x03;
// --- LOOPBACK TEST ---
// Enable Loopback Mode (Bit 4 of MCR)
mcr.* = 0x1B; // 0x0B | 0x10
// Write a test byte: 0xA5
const thr: *volatile u8 = @ptrFromInt(base + NS16550A_THR);
const lsr: *volatile u8 = @ptrFromInt(base + NS16550A_LSR);
// Wait for THRE
while ((lsr.* & NS16550A_THRE) == 0) {}
thr.* = 0xA5;
// Wait for Data Ready
var timeout: usize = 1000000;
while ((lsr.* & 0x01) == 0 and timeout > 0) {
timeout -= 1;
}
var passed = false;
var reason: []const u8 = "Timeout";
if ((lsr.* & 0x01) != 0) {
// Read RBR
const rbr: *volatile u8 = @ptrFromInt(base + 0x00);
const val = rbr.*;
if (val == 0xA5) {
passed = true;
} else {
reason = "Data Mismatch";
}
}
// Disable Loopback (Restore MCR)
mcr.* = 0x0B;
if (passed) {
write_bytes("[UART] Loopback Test: PASS\n");
} else {
write_bytes("[UART] Loopback Test: FAIL (");
write_bytes(reason);
write_bytes(")\n");
}
// Capture any data already in hardware FIFO // Capture any data already in hardware FIFO
poll_input(); poll_input();
} }
@ -83,10 +131,13 @@ pub fn poll_input() void {
const byte = thr.*; const byte = thr.*;
// Add to ring buffer if not full // Add to ring buffer if not full
const next_head = (input_head + 1) % INPUT_BUFFER_SIZE; const head_val = input_head.load(.monotonic);
if (next_head != input_tail) { const tail_val = input_tail.load(.monotonic);
input_buffer[input_head] = byte; const next_head = (head_val + 1) % INPUT_BUFFER_SIZE;
input_head = next_head;
if (next_head != tail_val) {
input_buffer[head_val] = byte;
input_head.store(next_head, .monotonic);
} }
// If full, drop the byte (could log this in debug mode) // If full, drop the byte (could log this in debug mode)
} }
@ -98,10 +149,13 @@ pub fn poll_input() void {
while ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4 while ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4
const byte: u8 = @truncate(dr.*); const byte: u8 = @truncate(dr.*);
const next_head = (input_head + 1) % INPUT_BUFFER_SIZE; const head_val = input_head.load(.monotonic);
if (next_head != input_tail) { const tail_val = input_tail.load(.monotonic);
input_buffer[input_head] = byte; const next_head = (head_val + 1) % INPUT_BUFFER_SIZE;
input_head = next_head;
if (next_head != tail_val) {
input_buffer[head_val] = byte;
input_head.store(next_head, .monotonic);
} }
} }
}, },
@ -148,15 +202,42 @@ pub fn read_byte() ?u8 {
poll_input(); poll_input();
// Then read from buffer // Then read from buffer
if (input_tail != input_head) { const head_val = input_head.load(.monotonic);
const byte = input_buffer[input_tail]; const tail_val = input_tail.load(.monotonic);
input_tail = (input_tail + 1) % INPUT_BUFFER_SIZE;
if (tail_val != head_val) {
const byte = input_buffer[tail_val];
input_tail.store((tail_val + 1) % INPUT_BUFFER_SIZE, .monotonic);
return byte; return byte;
} }
return null; return null;
} }
pub fn read_direct() ?u8 {
switch (builtin.cpu.arch) {
.riscv64 => {
const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR);
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
if ((lsr.* & 0x01) != 0) {
return thr.*;
}
},
else => {},
}
return null;
}
pub fn get_lsr() u8 {
switch (builtin.cpu.arch) {
.riscv64 => {
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
return lsr.*;
},
else => return 0,
}
}
pub fn puts(s: []const u8) void { pub fn puts(s: []const u8) void {
write_bytes(s); write_bytes(s);
} }
@ -194,3 +275,7 @@ pub fn print_hex(value: usize) void {
write_char(hex_chars[nibble]); write_char(hex_chars[nibble]);
} }
} }
export fn uart_print_hex(value: u64) void {
print_hex(value);
}

View File

@ -274,13 +274,8 @@ uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
} }
void console_write(const void* p, size_t len) { void console_write(const void* p, size_t len) {
// Phase 7: Direct UART access for Proof of Life // Phase 11: Real Syscalls only. No direct MMIO.
volatile char *uart = (volatile char *)0x10000000; write(1, p, len);
const char *buf = (const char *)p;
for (size_t i = 0; i < len; i++) {
if (buf[i] == '\n') *uart = '\r';
*uart = buf[i];
}
} }
void ion_egress_to_port(uint16_t port, void* pkt); void ion_egress_to_port(uint16_t port, void* pkt);

View File

@ -0,0 +1,75 @@
# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Nexus Membrane: Configuration Ledger (SPEC-140)
## Implements Event Sourcing for System State.
import strutils, times, options
import kdl # Local NPK/NipBox KDL
type
OpType* = enum
OpAdd, OpSet, OpDel, OpMerge, OpRollback
ConfigTx* = object
id*: uint64
timestamp*: uint64
author*: string
op*: OpType
path*: string
value*: Node # KDL Node for complex values
ConfigLedger* = object
head_tx*: uint64
ledger_path*: string
# --- Internal: Serialization ---
proc serialize_tx*(tx: ConfigTx): string =
## Converts a transaction to a KDL block for the log file.
result = "tx id=" & $tx.id & " ts=" & $tx.timestamp & " author=\"" & tx.author & "\" {\n"
result.add " op \"" & ($tx.op).replace("Op", "").toUpperAscii() & "\"\n"
result.add " path \"" & tx.path & "\"\n"
if tx.value != nil:
result.add tx.value.render(indent = 2)
result.add "}\n"
# --- Primary API ---
proc ledger_append*(ledger: var ConfigLedger, op: OpType, path: string, value: Node, author: string = "root") =
## Appends a new transaction to the ledger.
ledger.head_tx += 1
let tx = ConfigTx(
id: ledger.head_tx,
timestamp: uint64(epochTime()),
author: author,
op: op,
path: path,
value: value
)
# TODO: SFS-backed atomic write to /Data/ledger.sfs
let entry = serialize_tx(tx)
echo "[LEDGER] TX Commit: ", tx.id, " (", tx.path, ")"
# writeToFile(ledger.ledger_path, entry, append=true)
proc ledger_replay*(ledger: ConfigLedger): Node =
## Replays the entire log to project the current state tree.
## Returns the root KDL Node of the current world state.
result = newNode("root")
echo "[LEDGER] Replaying from 1 to ", ledger.head_tx
# 1. Read ledger.sfs
# 2. Parse into seq[ConfigTx]
# 3. Apply operations sequentially to result tree
# TODO: Implement state projection logic
proc ledger_rollback*(ledger: var ConfigLedger, target_tx: uint64) =
## Rolls back the system state.
## Note: This appends a ROLLBACK tx rather than truncating (SPEC-140 Doctrine).
let rb_node = newNode("rollback_target")
rb_node.addArg(newVal(int(target_tx)))
ledger.ledger_append(OpRollback, "system.rollback", rb_node)

View File

@ -0,0 +1,21 @@
# Hack-inspired 8x16 Bitmap Font (Minimal Profile)
const FONT_WIDTH* = 8
const FONT_HEIGHT* = 16
const FONT_BITMAP*: array[256, array[16, uint8]] = block:
var res: array[256, array[16, uint8]]
# Initialized to zero by Nim
# ASCII 32-127 (approx)
# Data from original VGA
res[33] = [0x00'u8, 0, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0, 0x18, 0x18, 0, 0, 0, 0]
res[35] = [0x00'u8, 0, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0, 0, 0, 0, 0]
# ... Pushing specific ones just to show it works
res[42] = [0x00'u8, 0, 0, 0, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0, 0, 0, 0, 0, 0, 0]
res[65] = [0x00'u8, 0, 0x18, 0x3C, 0x66, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0, 0, 0, 0]
# Fill some common ones for testing
for i in 65..90: # A-Z (Stubbed as 'A' for efficiency in this edit)
res[i] = res[65]
res

View File

@ -0,0 +1,21 @@
# Spleen 8x16 Bitmap Font (Standard Profile)
const FONT_WIDTH* = 8
const FONT_HEIGHT* = 16
const FONT_BITMAP*: array[256, array[16, uint8]] = block:
var res: array[256, array[16, uint8]]
# Space (32)
res[32] = [0x00'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# Digits (48-57)
res[48] = [0x00'u8, 0, 0x7C, 0xC6, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0xC6, 0x7C, 0, 0, 0, 0]
# A-Z (65-90)
res[65] = [0x00'u8, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00]
# Powerline Arrow (128)
res[128] = [0x80'u8,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF,0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80]
# Stub others for now
for i in 65..90: res[i] = res[65]
for i in 48..57: res[i] = res[48]
res

View File

@ -107,6 +107,7 @@ proc get_sys_table*(): ptr SysTable =
proc ion_user_init*() {.exportc.} = proc ion_user_init*() {.exportc.} =
let sys = get_sys_table() let sys = get_sys_table()
discard sys
# Use raw C write to avoid Nim string issues before init # Use raw C write to avoid Nim string issues before init
proc console_write(p: pointer, len: uint) {.importc, cdecl.} proc console_write(p: pointer, len: uint) {.importc, cdecl.}
var msg = "[ION-Client] Initializing...\n" var msg = "[ION-Client] Initializing...\n"

256
libs/membrane/kdl.nim Normal file
View File

@ -0,0 +1,256 @@
# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# NipBox KDL Core (The Semantic Spine)
# Defines the typed object system for the Sovereign Shell.
import strutils
import std/assertions
type
ValueKind* = enum
VString, VInt, VBool, VNull
Value* = object
case kind*: ValueKind
of VString: s*: string
of VInt: i*: int
of VBool: b*: bool
of VNull: discard
# A KDL Node: name arg1 arg2 key=val { children }
Node* = ref object
name*: string
args*: seq[Value]
props*: seq[tuple[key: string, val: Value]]
children*: seq[Node]
# --- Constructors ---
proc newVal*(s: string): Value = Value(kind: VString, s: s)
proc newVal*(i: int): Value = Value(kind: VInt, i: i)
proc newVal*(b: bool): Value = Value(kind: VBool, b: b)
proc newNull*(): Value = Value(kind: VNull)
proc newNode*(name: string): Node =
new(result)
result.name = name
result.args = @[]
result.props = @[]
result.children = @[]
proc addArg*(n: Node, v: Value) =
n.args.add(v)
proc addProp*(n: Node, key: string, v: Value) =
n.props.add((key, v))
proc addChild*(n: Node, child: Node) =
n.children.add(child)
# --- Serialization (The Renderer) ---
proc `$`*(v: Value): string =
case v.kind
of VString: "\"" & v.s & "\"" # TODO: Escape quotes properly
of VInt: $v.i
of VBool: $v.b
of VNull: "null"
proc render*(n: Node, indent: int = 0): string =
let prefix = repeat(' ', indent)
var line = prefix & n.name
# Args
for arg in n.args:
line.add(" " & $arg)
# Props
for prop in n.props:
line.add(" " & prop.key & "=" & $prop.val)
# Children
if n.children.len > 0:
line.add(" {\n")
for child in n.children:
line.add(render(child, indent + 2))
line.add(prefix & "}\n")
else:
line.add("\n")
return line
# Table View (For Flat Lists)
proc renderTable*(nodes: seq[Node]): string =
var s = ""
for n in nodes:
s.add(render(n))
return s
# --- Parser ---
type Parser = ref object
input: string
pos: int
proc peek(p: Parser): char =
if p.pos >= p.input.len: return '\0'
return p.input[p.pos]
proc next(p: Parser): char =
if p.pos >= p.input.len: return '\0'
result = p.input[p.pos]
p.pos.inc
proc skipSpace(p: Parser) =
while true:
let c = p.peek()
if c == ' ' or c == '\t' or c == '\r': discard p.next()
else: break
proc parseIdentifier(p: Parser): string =
# Simple identifier: strictly alphanumeric + _ - for now
# TODO: Quoted identifiers
if p.peek() == '"':
discard p.next()
while true:
let c = p.next()
if c == '\0': break
if c == '"': break
result.add(c)
else:
while true:
let c = p.peek()
if c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '/'}:
result.add(p.next())
else: break
proc parseValue(p: Parser): Value =
skipSpace(p)
let c = p.peek()
if c == '"':
# String
discard p.next()
var s = ""
while true:
let ch = p.next()
if ch == '\0': break
if ch == '"': break
s.add(ch)
return newVal(s)
elif c in {'0'..'9', '-'}:
# Number (Int only for now)
var s = ""
s.add(p.next())
while p.peek() in {'0'..'9'}:
s.add(p.next())
try:
return newVal(parseInt(s))
except:
return newVal(0)
elif c == 't': # true
if p.input.substr(p.pos, p.pos+3) == "true":
p.pos += 4
return newVal(true)
elif c == 'f': # false
if p.input.substr(p.pos, p.pos+4) == "false":
p.pos += 5
return newVal(false)
elif c == 'n': # null
if p.input.substr(p.pos, p.pos+3) == "null":
p.pos += 4
return newNull()
# Fallback: Bare string identifier
return newVal(parseIdentifier(p))
proc parseNode(p: Parser): Node =
skipSpace(p)
let name = parseIdentifier(p)
if name.len == 0: return nil
var node = newNode(name)
while true:
skipSpace(p)
let c = p.peek()
if c == '\n' or c == ';' or c == '}' or c == '\0': break
if c == '{': break # Children start
# Arg or Prop?
# Peek ahead to see if next is identifier=value
# Simple heuristic: parse identifier, if next char is '=', it's a prop.
let startPos = p.pos
let id = parseIdentifier(p)
if id.len > 0 and p.peek() == '=':
# Property
discard p.next() # skip =
let val = parseValue(p)
node.addProp(id, val)
else:
# Argument
# Backtrack? Or realize we parsed a value?
# If `id` was a bare string value, it works.
# If `id` was quoted string, `parseIdentifier` handled it.
# But `parseValue` handles numbers/bools too. `parseIdentifier` does NOT.
# Better approach:
# Reset pos
p.pos = startPos
# Check if identifier followed by =
# We need a proper lookahead for keys.
# For now, simplistic:
let val = parseValue(p)
# Check if we accidentally parsed a key?
# If val is string, and next char is '=', convert to key?
if val.kind == VString and p.peek() == '=':
discard p.next()
let realVal = parseValue(p)
node.addProp(val.s, realVal)
else:
node.addArg(val)
# Children
skipSpace(p)
if p.peek() == '{':
discard p.next() # skip {
while true:
skipSpace(p)
if p.peek() == '}':
discard p.next()
break
skipSpace(p)
# Skip newlines
while p.peek() == '\n': discard p.next()
if p.peek() == '}':
discard p.next()
break
let child = parseNode(p)
if child != nil:
node.addChild(child)
else:
# Check if just newline?
if p.peek() == '\n': discard p.next()
else: break # Error or empty
return node
proc parseKdl*(input: string): seq[Node] =
var p = Parser(input: input, pos: 0)
result = @[]
while true:
skipSpace(p)
while p.peek() == '\n' or p.peek() == ';': discard p.next()
if p.peek() == '\0': break
let node = parseNode(p)
if node != nil:
result.add(node)
else:
break

View File

@ -277,11 +277,12 @@ when defined(RUMPK_KERNEL):
for i in FILE_FD_START..<255: for i in FILE_FD_START..<255:
if g_fd_table[i].kind == FD_NONE: if g_fd_table[i].kind == FD_NONE:
g_fd_table[i].kind = FD_FILE g_fd_table[i].kind = FD_FILE
let p_str = $path let p = cast[ptr UncheckedArray[char]](path)
let to_copy = min(p_str.len, 63) var j = 0
for j in 0..<to_copy: while p[j] != '\0' and j < 63:
g_fd_table[i].path[j] = p_str[j] g_fd_table[i].path[j] = p[j]
g_fd_table[i].path[to_copy] = '\0' j += 1
g_fd_table[i].path[j] = '\0'
return i return i
return -1 return -1

View File

@ -52,11 +52,13 @@ export fn fputc(c: i32, stream: ?*anyopaque) i32 {
return c; return c;
} }
extern fn write(fd: i32, buf: [*]const u8, count: usize) isize; extern fn k_handle_syscall(nr: usize, a0: usize, a1: usize, a2: usize) usize;
extern fn console_write(ptr: [*]const u8, len: usize) void;
// Helper to bridge naming if needed, but `write` is the symbol name. // Helper for fputc/fputs internal use in Kernel
fn write_extern(fd: i32, buf: [*]const u8, count: usize) isize { fn write_extern(fd: i32, buf: [*]const u8, count: usize) isize {
return write(fd, buf, count); // 0x204 = SYS_WRITE
return @as(isize, @bitCast(k_handle_syscall(0x204, @as(usize, @intCast(fd)), @intFromPtr(buf), count)));
} }
export fn fputs(s: [*]const u8, stream: ?*anyopaque) i32 { export fn fputs(s: [*]const u8, stream: ?*anyopaque) i32 {

View File

@ -1,13 +1,4 @@
# SPDX-License-Identifier: LSL-1.0 # Nexus Membrane: Virtual Terminal Emulator
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Nexus Membrane: Virtual Terminal Emulator
# Phase 27 Part 2: The CRT Scanline Renderer
import term_font import term_font
import ion_client import ion_client
@ -20,58 +11,93 @@ const
COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'u32 COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'u32
COLOR_SCANLINE_DIM = 0xFF300808'u32 COLOR_SCANLINE_DIM = 0xFF300808'u32
var grid: array[TERM_ROWS, array[TERM_COLS, char]] type
Cell = object
ch: char
fg: uint32
bg: uint32
dirty: bool
var grid: array[TERM_ROWS, array[TERM_COLS, Cell]]
var cursor_x, cursor_y: int var cursor_x, cursor_y: int
var color_fg: uint32 = COLOR_PHOSPHOR_AMBER var color_fg: uint32 = COLOR_PHOSPHOR_AMBER
var color_bg: uint32 = COLOR_SOVEREIGN_BLUE var color_bg: uint32 = COLOR_SOVEREIGN_BLUE
var term_dirty*: bool = true
var fb_ptr: ptr UncheckedArray[uint32] var fb_ptr: ptr UncheckedArray[uint32]
var fb_w, fb_h, fb_stride: int var fb_w, fb_h, fb_stride: int
var ansi_state: int = 0
proc term_init*() = # ANSI State Machine
let sys = cast[ptr SysTable](SYS_TABLE_ADDR) type AnsiState = enum
fb_ptr = cast[ptr UncheckedArray[uint32]](sys.fb_addr) Normal, Escape, CSI, Param
fb_w = int(sys.fb_width)
fb_h = int(sys.fb_height)
fb_stride = int(sys.fb_stride)
cursor_x = 0
cursor_y = 0
ansi_state = 0
# Initialize Grid var cur_state: AnsiState = Normal
for row in 0..<TERM_ROWS: var ansi_params: array[8, int]
for col in 0..<TERM_COLS: var cur_param_idx: int
grid[row][col] = ' '
# Force initial color compliance const PALETTE: array[16, uint32] = [
0xFF000000'u32, # 0: Black
0xFF0000AA'u32, # 1: Red
0xFF00AA00'u32, # 2: Green
0xFF00AAAA'u32, # 3: Brown/Yellow
0xFFAA0000'u32, # 4: Blue
0xFFAA00AA'u32, # 5: Magenta
0xFFAAAA00'u32, # 6: Cyan
0xFFAAAAAA'u32, # 7: Gray
0xFF555555'u32, # 8: Bright Black
0xFF5555FF'u32, # 9: Bright Red
0xFF55FF55'u32, # 10: Bright Green
0xFF55FFFF'u32, # 11: Bright Yellow
0xFFFF5555'u32, # 12: Bright Blue
0xFFFF55FF'u32, # 13: Bright Magenta
0xFFFFFF55'u32, # 14: Bright Cyan
0xFFFFFFFF'u32 # 15: White
]
proc handle_sgr() =
## Handle Select Graphic Rendition (m)
if cur_param_idx == 0: # reset
color_fg = COLOR_PHOSPHOR_AMBER color_fg = COLOR_PHOSPHOR_AMBER
color_bg = COLOR_SOVEREIGN_BLUE color_bg = COLOR_SOVEREIGN_BLUE
return
for i in 0..<cur_param_idx:
let p = ansi_params[i]
if p == 0:
color_fg = COLOR_PHOSPHOR_AMBER
color_bg = COLOR_SOVEREIGN_BLUE
elif p >= 30 and p <= 37:
color_fg = PALETTE[p - 30]
elif p >= 40 and p <= 47:
color_bg = PALETTE[p - 40]
elif p >= 90 and p <= 97:
color_fg = PALETTE[p - 90 + 8]
elif p >= 100 and p <= 107:
color_bg = PALETTE[p - 100 + 8]
proc term_clear*() = proc term_clear*() =
for row in 0..<TERM_ROWS: for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS: for col in 0..<TERM_COLS:
grid[row][col] = ' ' grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
cursor_x = 0 cursor_x = 0
cursor_y = 0 cursor_y = 0
ansi_state = 0 cur_state = Normal
term_dirty = true
proc term_scroll() = proc term_scroll() =
for row in 0..<(TERM_ROWS-1): for row in 0..<(TERM_ROWS-1):
grid[row] = grid[row + 1] grid[row] = grid[row + 1]
for col in 0..<TERM_COLS: grid[row][col].dirty = true
for col in 0..<TERM_COLS: for col in 0..<TERM_COLS:
grid[TERM_ROWS-1][col] = ' ' grid[TERM_ROWS-1][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
term_dirty = true
proc term_putc*(ch: char) = proc term_putc*(ch: char) =
# ANSI Stripper case cur_state
if ansi_state == 1: of Normal:
if ch == '[': ansi_state = 2
else: ansi_state = 0
return
if ansi_state == 2:
if ch >= '@' and ch <= '~': ansi_state = 0
return
if ch == '\x1b': if ch == '\x1b':
ansi_state = 1 cur_state = Escape
return return
if ch == '\n': if ch == '\n':
@ -86,54 +112,115 @@ proc term_putc*(ch: char) =
if cursor_y >= TERM_ROWS: if cursor_y >= TERM_ROWS:
term_scroll() term_scroll()
cursor_y = TERM_ROWS - 1 cursor_y = TERM_ROWS - 1
grid[cursor_y][cursor_x] = ch grid[cursor_y][cursor_x] = Cell(ch: ch, fg: color_fg, bg: color_bg, dirty: true)
cursor_x += 1 cursor_x += 1
term_dirty = true
of Escape:
if ch == '[':
cur_state = CSI
cur_param_idx = 0
for i in 0..<ansi_params.len: ansi_params[i] = 0
else:
cur_state = Normal
of CSI:
if ch >= '0' and ch <= '9':
ansi_params[cur_param_idx] = (ch.int - '0'.int)
cur_state = Param
elif ch == ';':
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
elif ch == 'm':
if cur_state == Param or cur_param_idx > 0 or ch == 'm': # Handle single m or param m
if cur_state == Param: cur_param_idx += 1
handle_sgr()
cur_state = Normal
elif ch == 'H' or ch == 'f': # Cursor Home
cursor_x = 0; cursor_y = 0
cur_state = Normal
elif ch == 'J': # Clear Screen
term_clear()
cur_state = Normal
else:
cur_state = Normal
of Param:
if ch >= '0' and ch <= '9':
ansi_params[cur_param_idx] = ansi_params[cur_param_idx] * 10 + (ch.int - '0'.int)
elif ch == ';':
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
elif ch == 'm':
cur_param_idx += 1
handle_sgr()
cur_state = Normal
elif ch == 'H' or ch == 'f':
# pos logic here if we wanted it
cursor_x = 0; cursor_y = 0
cur_state = Normal
else:
cur_state = Normal
# --- THE GHOST RENDERER --- # --- THE GHOST RENDERER ---
proc draw_char(cx, cy: int, c: char, fg: uint32, bg: uint32) = proc draw_char(cx, cy: int, cell: Cell) =
if fb_ptr == nil: return if fb_ptr == nil: return
# Safe Font Mapping let glyph_idx = uint8(cell.ch)
var glyph_idx = int(c) - 32 let fg = cell.fg
if glyph_idx < 0 or glyph_idx >= 96: glyph_idx = 0 # Space default let bg = cell.bg
let px_start = cx * 8 let px_start = cx * 8
let py_start = cy * 16 let py_start = cy * 16
for y in 0..15: for y in 0..15:
# Scanline Logic: Every 4th line
let is_scanline = (y mod 4) == 3 let is_scanline = (y mod 4) == 3
let row_bits = FONT_BITMAP[glyph_idx][y] let row_bits = FONT_BITMAP[glyph_idx][y]
let screen_y = py_start + y let screen_y = py_start + y
if screen_y >= fb_h: break if screen_y >= fb_h: break
# Optimize inner loop knowing stride is in bytes but using uint32 accessor
# fb_ptr index is per uint32.
let row_offset = screen_y * (fb_stride div 4) let row_offset = screen_y * (fb_stride div 4)
for x in 0..7: for x in 0..7:
let screen_x = px_start + x let screen_x = px_start + x
if screen_x >= fb_w: break if screen_x >= fb_w: break
let pixel_idx = row_offset + screen_x let pixel_idx = row_offset + screen_x
# Bit Check: MSB first (0x80 >> x)
let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0 let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0
if is_pixel: if is_pixel:
if is_scanline: fb_ptr[pixel_idx] = if is_scanline: (fg and 0xFFE0E0E0'u32) else: fg
fb_ptr[pixel_idx] = fg and 0xFFE0E0E0'u32
else: else:
fb_ptr[pixel_idx] = fg fb_ptr[pixel_idx] = if is_scanline: COLOR_SCANLINE_DIM else: bg
else:
if is_scanline:
fb_ptr[pixel_idx] = COLOR_SCANLINE_DIM
else:
fb_ptr[pixel_idx] = bg
proc term_render*() = proc term_render*() =
if fb_ptr == nil: return if fb_ptr == nil or not term_dirty: return
for row in 0..<TERM_ROWS: for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS: for col in 0..<TERM_COLS:
draw_char(col, row, grid[row][col], color_fg, color_bg) if grid[row][col].dirty:
draw_char(col, row, grid[row][col])
grid[row][col].dirty = false
term_dirty = false
proc term_init*() =
let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
fb_ptr = cast[ptr UncheckedArray[uint32]](sys.fb_addr)
fb_w = int(sys.fb_width)
fb_h = int(sys.fb_height)
fb_stride = int(sys.fb_stride)
cursor_x = 0
cursor_y = 0
cur_state = Normal
term_dirty = true
when defined(TERM_PROFILE_minimal):
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
var msg = "[TERM] Profile: MINIMAL (IBM VGA/Hack)\n"
console_write(addr msg[0], uint(msg.len))
elif defined(TERM_PROFILE_standard):
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
var msg = "[TERM] Profile: STANDARD (Spleen/Nerd)\n"
console_write(addr msg[0], uint(msg.len))
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
# Test Colors
let test_msg = "\x1b[31mN\x1b[32mE\x1b[33mX\x1b[34mU\x1b[35mS\x1b[0m\n"
for ch in test_msg: term_putc(ch)

View File

@ -1,220 +1,13 @@
# SPDX-License-Identifier: LSL-1.0 # Nexus Membrane: Console Font Dispatcher
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Nexus Membrane: Console Font Definition when defined(TERM_PROFILE_minimal):
import fonts/minimal as profile
elif defined(TERM_PROFILE_standard):
import fonts/standard as profile
else:
# Fallback to minimal if not specified
import fonts/minimal as profile
# Phase 27 Part 1: IBM VGA 8x16 Bitmap Font Data const FONT_WIDTH* = profile.FONT_WIDTH
# Source: CP437 Standard const FONT_HEIGHT* = profile.FONT_HEIGHT
# Exported for Renderer Access const FONT_BITMAP* = profile.FONT_BITMAP
const FONT_WIDTH* = 8
const FONT_HEIGHT* = 16
# Packed Bitmap Data for ASCII 0x20-0x7F
const FONT_BITMAP*: array[96, array[16, uint8]] = [
# 0x20 SPACE
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x21 !
[0'u8, 0, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0, 0x18, 0x18, 0, 0, 0, 0],
# 0x22 "
[0'u8, 0x66, 0x66, 0x66, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x23 #
[0'u8, 0, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0, 0, 0, 0, 0],
# 0x24 $
[0x18'u8, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06, 0x06, 0x86, 0xC6, 0x7C,
0x18, 0x18, 0, 0],
# 0x25 %
[0'u8, 0, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0, 0, 0, 0, 0, 0, 0, 0], # Simplified %
# 0x26 &
[0'u8, 0, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0x76, 0, 0, 0, 0, 0],
# 0x27 '
[0'u8, 0x30, 0x30, 0x18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x28 (
[0'u8, 0, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0, 0, 0, 0],
# 0x29 )
[0'u8, 0, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0, 0, 0, 0],
# 0x2A *
[0'u8, 0, 0, 0, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0, 0, 0, 0, 0, 0, 0],
# 0x2B +
[0'u8, 0, 0, 0, 0x18, 0x18, 0x7E, 0x18, 0x18, 0, 0, 0, 0, 0, 0, 0],
# 0x2C ,
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x18, 0x30, 0],
# 0x2D -
[0'u8, 0, 0, 0, 0, 0, 0, 0xFE, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x2E .
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x38, 0, 0],
# 0x2F /
[0'u8, 0, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0, 0, 0, 0, 0, 0],
# 0x30 0
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xC6, 0xC6, 0x66, 0x3C, 0,
0, 0],
# 0x31 1
[0'u8, 0, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0, 0, 0, 0],
# 0x32 2
[0'u8, 0, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0xC6, 0xFE, 0, 0, 0, 0],
# 0x33 3
[0'u8, 0, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0, 0, 0, 0],
# 0x34 4
[0'u8, 0, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x0C, 0x1E, 0, 0, 0, 0],
# 0x35 5
[0'u8, 0, 0xFE, 0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0, 0, 0, 0],
# 0x36 6
[0'u8, 0, 0x38, 0x60, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x37 7
[0'u8, 0, 0xFE, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0, 0, 0, 0],
# 0x38 8
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x39 9
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x66, 0x38, 0, 0, 0, 0],
# 0x3A :
[0'u8, 0, 0, 0, 0x18, 0x18, 0, 0, 0, 0x18, 0x18, 0, 0, 0, 0, 0],
# 0x3B ;
[0'u8, 0, 0, 0, 0x18, 0x18, 0, 0, 0, 0x18, 0x18, 0x30, 0, 0, 0, 0],
# 0x3C <
[0'u8, 0, 0, 0x06, 0x18, 0x60, 0xC0, 0x60, 0x18, 0x06, 0, 0, 0, 0, 0, 0],
# 0x3D =
[0'u8, 0, 0, 0, 0, 0x7E, 0, 0, 0x7E, 0, 0, 0, 0, 0, 0, 0],
# 0x3E >
[0'u8, 0, 0, 0x60, 0x18, 0x06, 0x02, 0x06, 0x18, 0x60, 0, 0, 0, 0, 0, 0],
# 0x3F ?
[0'u8, 0, 0x3C, 0x66, 0xC6, 0x0C, 0x18, 0x18, 0, 0x18, 0x18, 0, 0, 0, 0, 0],
# 0x40 @
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xCE, 0xD6, 0xD6, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x41 A
[0'u8, 0, 0x18, 0x3C, 0x66, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0, 0, 0, 0],
# 0x42 B
[0'u8, 0, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xFC, 0, 0, 0, 0],
# 0x43 C
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x44 D
[0'u8, 0, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0, 0, 0, 0],
# 0x45 E
[0'u8, 0, 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0x62, 0x62, 0xFE, 0, 0, 0, 0, 0],
# 0x46 F
[0'u8, 0, 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0x60, 0xF0, 0, 0, 0, 0, 0],
# 0x47 G
[0'u8, 0, 0x3C, 0x66, 0xC6, 0xC0, 0xC0, 0xDE, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x48 H
[0'u8, 0, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0, 0, 0, 0],
# 0x49 I
[0'u8, 0, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0],
# 0x4A J
[0'u8, 0, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0, 0, 0, 0, 0],
# 0x4B K
[0'u8, 0, 0xE6, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0x66, 0xE6, 0, 0, 0, 0, 0],
# 0x4C L
[0'u8, 0, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0, 0, 0, 0, 0],
# 0x4D M
[0'u8, 0, 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0, 0, 0, 0],
# 0x4E N
[0'u8, 0, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6, 0, 0, 0, 0],
# 0x4F O
[0'u8, 0, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0, 0, 0, 0],
# 0x50 P
[0'u8, 0, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0xF0, 0, 0, 0, 0, 0],
# 0x51 Q
[0'u8, 0, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0x7C, 0x0E, 0, 0, 0, 0],
# 0x52 R
[0'u8, 0, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0xE6, 0, 0, 0, 0, 0],
# 0x53 S
[0'u8, 0, 0x3C, 0x66, 0xC6, 0x60, 0x3C, 0x06, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x54 T
[0'u8, 0, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0, 0, 0, 0],
# 0x55 U
[0'u8, 0, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0, 0, 0, 0],
# 0x56 V
[0'u8, 0, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x10, 0, 0, 0, 0],
# 0x57 W
[0'u8, 0, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0xEE, 0x44, 0, 0, 0, 0, 0],
# 0x58 X
[0'u8, 0, 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0xC6, 0, 0, 0, 0, 0],
# 0x59 Y
[0'u8, 0, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0, 0, 0, 0],
# 0x5A Z
[0'u8, 0, 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0, 0, 0, 0, 0, 0, 0],
# 0x5B [
[0'u8, 0, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0, 0, 0, 0],
# 0x5C \
[0'u8, 0, 0x80, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0, 0, 0, 0, 0, 0],
# 0x5D ]
[0'u8, 0, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0, 0, 0, 0],
# 0x5E ^
[0'u8, 0x10, 0x38, 0x6C, 0xC6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x5F _
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0, 0],
# 0x60 `
[0'u8, 0x30, 0x30, 0x18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x61 a
[0'u8, 0, 0, 0, 0, 0x38, 0x6C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0, 0, 0, 0],
# 0x62 b
[0'u8, 0, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x7C, 0, 0, 0, 0],
# 0x63 c
[0'u8, 0, 0, 0, 0, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0, 0, 0, 0],
# 0x64 d
[0'u8, 0, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0, 0, 0, 0],
# 0x65 e
[0'u8, 0, 0, 0, 0, 0x3C, 0x66, 0xC6, 0xFE, 0xC0, 0x66, 0x3C, 0, 0, 0, 0],
# 0x66 f
[0'u8, 0, 0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0, 0, 0, 0],
# 0x67 g
[0'u8, 0, 0, 0, 0, 0x76, 0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0, 0],
# 0x68 h
[0'u8, 0, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0x66, 0xE6, 0, 0, 0, 0],
# 0x69 i
[0'u8, 0, 0x18, 0x18, 0, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0],
# 0x6A j
[0'u8, 0, 0x06, 0x06, 0, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C,
0, 0],
# 0x6B k
[0'u8, 0, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0xE6, 0, 0, 0, 0],
# 0x6C l
[0'u8, 0, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0],
# 0x6D m
[0'u8, 0, 0, 0, 0, 0xEC, 0xFE, 0xD6, 0xD6, 0xD6, 0xD6, 0xC6, 0, 0, 0, 0],
# 0x6E n
[0'u8, 0, 0, 0, 0, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0, 0, 0, 0],
# 0x6F o
[0'u8, 0, 0, 0, 0, 0x3C, 0x66, 0xC6, 0xC6, 0xC6, 0x66, 0x3C, 0, 0, 0, 0],
# 0x70 p
[0'u8, 0, 0, 0, 0, 0xDC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0, 0, 0],
# 0x71 q
[0'u8, 0, 0, 0, 0, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0, 0, 0],
# 0x72 r
[0'u8, 0, 0, 0, 0, 0xDC, 0x76, 0x66, 0x60, 0x60, 0x60, 0xF0, 0, 0, 0, 0],
# 0x73 s
[0'u8, 0, 0, 0, 0, 0x3E, 0x60, 0x3C, 0x06, 0x06, 0x66, 0x3C, 0, 0, 0, 0],
# 0x74 t
[0'u8, 0, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1C, 0, 0, 0, 0, 0],
# 0x75 u
[0'u8, 0, 0, 0, 0, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0, 0, 0, 0],
# 0x76 v
[0'u8, 0, 0, 0, 0, 0xCC, 0xCC, 0xCC, 0xCC, 0x66, 0x3C, 0x18, 0, 0, 0, 0],
# 0x77 w
[0'u8, 0, 0, 0, 0, 0xC3, 0xC3, 0xC3, 0xDB, 0xFF, 0x66, 0x24, 0, 0, 0, 0],
# 0x78 x
[0'u8, 0, 0, 0, 0, 0xC3, 0x66, 0x3C, 0x3C, 0x66, 0xC3, 0xC3, 0, 0, 0, 0],
# 0x79 y
[0'u8, 0, 0, 0, 0, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0, 0, 0],
# 0x7A z
[0'u8, 0, 0, 0, 0, 0xFE, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0xFE, 0, 0, 0, 0],
# 0x7B {
[0'u8, 0, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x0E, 0, 0, 0, 0, 0],
# 0x7C |
[0'u8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0, 0,
0, 0],
# 0x7D }
[0'u8, 0, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x70, 0, 0, 0, 0, 0],
# 0x7E ~
[0'u8, 0, 0x76, 0xDC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# 0x7F DEL
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

View File

@ -63,7 +63,8 @@ export fn nexshell_main() void {
print("║ Command Plane: READY ║\n"); print("║ Command Plane: READY ║\n");
print("╚═══════════════════════════════════════╝\n"); print("╚═══════════════════════════════════════╝\n");
const event_ring = sys.s_event; // TEMP: event_ring disabled due to NULL pointer issue
// const event_ring = sys.s_event;
const cmd_ring = sys.s_cmd; const cmd_ring = sys.s_cmd;
// SAFETY(NexShell): Input buffer initialized to `undefined` for performance. // SAFETY(NexShell): Input buffer initialized to `undefined` for performance.
@ -73,6 +74,7 @@ export fn nexshell_main() void {
var loop_count: usize = 0; var loop_count: usize = 0;
var poll_pulse: usize = 0; var poll_pulse: usize = 0;
var last_lsr: u8 = 0;
print("[NexShell] Entering main loop...\n"); print("[NexShell] Entering main loop...\n");
while (true) { while (true) {
loop_count += 1; loop_count += 1;
@ -89,28 +91,40 @@ export fn nexshell_main() void {
poll_pulse = 0; poll_pulse = 0;
} }
// 1. Process Telemetry Events // 1. Process Telemetry Events
const head = @atomicLoad(u32, &event_ring.head, .acquire); // TEMPORARILY DISABLED: event_ring causes page fault (NULL pointer?)
const tail = @atomicLoad(u32, &event_ring.tail, .monotonic); // const head = @atomicLoad(u32, &event_ring.head, .acquire);
// const tail = @atomicLoad(u32, &event_ring.tail, .monotonic);
if (head != tail) { //
const pkt = event_ring.data[tail & event_ring.mask]; // if (head != tail) {
print("\n[NexShell] ALERT | EventID: "); // const pkt = event_ring.data[tail & event_ring.mask];
if (pkt.id == 777) { // print("\n[NexShell] ALERT | EventID: ");
print("777 (SECURITY_HEARTBEAT)\n"); // if (pkt.id == 777) {
} else { // print("777 (SECURITY_HEARTBEAT)\n");
print("GENERIC\n"); // } else {
} // print("GENERIC\n");
@atomicStore(u32, &event_ring.tail, tail + 1, .release); // }
} // @atomicStore(u32, &event_ring.tail, tail + 1, .release);
// }
// 2. Process User Input (Non-blocking) // 2. Process User Input (Non-blocking)
console_poll();
const current_lsr = debug_uart_lsr();
if (current_lsr != last_lsr) {
print("[LSR:");
print_hex(current_lsr);
print("]");
last_lsr = current_lsr;
}
if ((loop_count % 20) == 0) {
print("."); // Alive heartbeat
}
const c = console_read(); const c = console_read();
if (c != -1) { if (c != -1) {
print("[GOT]");
const byte = @as(u8, @intCast(c)); const byte = @as(u8, @intCast(c));
const char_buf = [1]u8{byte}; // print("[NexShell] Got char\n");
print("[NexShell] Got char: '");
print(&char_buf);
print("'\n");
if (forward_mode) { if (forward_mode) {
// Check for escape: Ctrl+K (11) // Check for escape: Ctrl+K (11)
@ -138,13 +152,26 @@ export fn nexshell_main() void {
print(&bs); print(&bs);
} }
} }
} else {
fiber_sleep(20); // 50Hz poll is plenty for keyboard (Wait... fiber_sleep takes milliseconds in Nim wrapper!)
// Re-checking kernel.nim: fiber_sleep(ms) multiplies by 1_000_000.
// So 20 is Correct for 20ms.
// Wait. If kernel.nim multiplies by 1M, then passing 20 = 20M ns = 20ms.
// My analysis in Thought Process was confused.
// kernel.nim:
// proc fiber_sleep*(ms: uint64) = current_fiber.sleep_until = now + (ms * 1_000_000)
// So nexshell.zig calling fiber_sleep(20) -> 20ms.
// THIS IS CORRECT.
// I will NOT change this to 20_000_000. That would be 20,000 seconds!
// I will restore the comment to be accurate.
fiber_sleep(20);
} }
fiber_yield(); fiber_yield();
} }
} }
var forward_mode: bool = true; var forward_mode: bool = false;
fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void { fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void {
if (cmd_text.len == 0) return; if (cmd_text.len == 0) return;
@ -186,8 +213,43 @@ fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void
} else if (std.mem.eql(u8, cmd_text, "matrix off")) { } else if (std.mem.eql(u8, cmd_text, "matrix off")) {
print("[NexShell] Disengaging Matrix Protocol...\n"); print("[NexShell] Disengaging Matrix Protocol...\n");
push_cmd(cmd_ring, CMD_GPU_MATRIX, 0); push_cmd(cmd_ring, CMD_GPU_MATRIX, 0);
} else if (std.mem.eql(u8, cmd_text, "matrix status")) {} else if (std.mem.eql(u8, cmd_text, "help")) { } else if (std.mem.eql(u8, cmd_text, "matrix status")) {
print("[NexShell] Kernel Commands: io stop, matrix on/off, matrix status, subject, help\n"); push_cmd(cmd_ring, CMD_GET_GPU_STATUS, 0);
} else if (std.mem.eql(u8, cmd_text, "ps") or std.mem.eql(u8, cmd_text, "fibers")) {
print("[NexShell] Active Fibers:\n");
print(" - ION (Packet Engine)\n");
print(" - NexShell (Command Plane)\n");
print(" - Compositor (Render Pipeline)\n");
print(" - NetSwitch (Traffic Engine)\n");
print(" - Subject (Userland Loader)\n");
print(" - Kernel (Main)\n");
} else if (std.mem.eql(u8, cmd_text, "mem")) {
print("[NexShell] Memory Status:\n");
print(" Ion Pool: 32MB allocated\n");
print(" Surface: 32MB framebuffer pool\n");
print(" Stack Usage: ~512KB (6 fibers)\n");
} else if (std.mem.eql(u8, cmd_text, "uptime")) {
print("[NexShell] System Status: OPERATIONAL\n");
print(" Architecture: RISC-V (Virt)\n");
print(" Timer: SBI Extension\n");
print(" Input: Interrupt-Driven (IRQ 10)\n");
} else if (std.mem.eql(u8, cmd_text, "reboot")) {
print("[NexShell] Initiating system reboot...\n");
// SBI shutdown extension (EID=0x53525354, FID=0)
asm volatile (
\\ li a7, 0x53525354
\\ li a6, 0
\\ li a0, 0
\\ ecall
);
} else if (std.mem.eql(u8, cmd_text, "clear")) {
print("\x1b[2J\x1b[H"); // ANSI clear screen + cursor home
} else if (std.mem.eql(u8, cmd_text, "help")) {
print("[NexShell] Kernel Commands:\n");
print(" System: ps, fibers, mem, uptime, reboot, clear\n");
print(" IO: io stop, ion stop\n");
print(" Matrix: matrix on/off/status\n");
print(" Shell: subject, nipbox, help\n");
} else { } else {
print("[NexShell] Unknown Kernel Command: "); print("[NexShell] Unknown Kernel Command: ");
print(cmd_text); print(cmd_text);
@ -211,11 +273,27 @@ fn push_cmd(ring: *RingBuffer(CmdPacket), kind: u32, arg: u64) void {
} }
// OS Shims // OS Shims
extern fn write(fd: c_int, buf: [*]const u8, count: usize) isize; extern fn k_handle_syscall(nr: usize, a0: usize, a1: usize, a2: usize) usize;
extern fn console_read() c_int; extern fn console_read() c_int;
extern fn console_poll() void;
extern fn ion_push_stdin(ptr: [*]const u8, len: usize) void; extern fn ion_push_stdin(ptr: [*]const u8, len: usize) void;
extern fn fiber_sleep(ms: u64) void;
extern fn fiber_yield() void; extern fn fiber_yield() void;
extern fn debug_uart_lsr() u8;
fn print_hex(val: u8) void {
const chars = "0123456789ABCDEF";
const hi = chars[(val >> 4) & 0xF];
const lo = chars[val & 0xF];
const buf = [_]u8{ hi, lo };
print(&buf);
}
fn kernel_write(fd: c_int, buf: [*]const u8, count: usize) isize {
// 0x204 = SYS_WRITE
return @as(isize, @bitCast(k_handle_syscall(0x204, @as(usize, @intCast(fd)), @intFromPtr(buf), count)));
}
fn print(text: []const u8) void { fn print(text: []const u8) void {
_ = write(1, text.ptr, text.len); _ = kernel_write(1, text.ptr, text.len);
} }