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 cf93016bd4
commit 8a4c57b34a
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] 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
# execv(path, argv) - argv can be nil for now
if execv(cstring("/bin/mksh"), nil) < 0:
print(cstring("\x1b[1;31m[INIT] Failed to spawn shell! Entering fallback loop.\x1b[0m\n"))
let fiber_id = spawn_fiber(cstring("/bin/mksh"))
if fiber_id > 0:
print(cstring("[INIT] Spawned mksh fiber ID: "))
# 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:
# 🕵️ DIAGNOSTIC: BREATHER
# Sleep 1 second between checks
discard syscall(0x65, 1000000000'u64) # nanosleep: 1 second
pump_membrane_stack()
yield_fiber()

View File

@ -1,12 +1,7 @@
.section .text._start, "ax"
.global _start
_start:
# 🕵 DIAGNOSTIC: BREATHE
li t0, 0x10000000
li t1, 0x23 # '#'
sb t1, 0(t0)
# Clear BSS (64-bit aligned zeroing)
# 🕵 BSS Clearing
la t0, __bss_start
la t1, __bss_end
1: bge t0, t1, 2f
@ -16,11 +11,6 @@ _start:
2:
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
# 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"
last_burst_ns*: uint64 # Actual execution time of last run
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
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_size = size
f.sleep_until = 0
f.pty_id = -1
f.user_sp_init = 0
# Start at top of stack (using actual size)
var sp = cast[uint64](stack_base) + cast[uint64](size)

View File

@ -6,334 +6,135 @@
# See legal/LICENSE_SOVEREIGN.md for license terms.
## 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)
#
# 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)
import fiber # For yield
proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(n: uint64) {.importc, cdecl.}
# =========================================================
# SFS Configurations
# =========================================================
const SFS_MAGIC* = 0x31534653'u32
const
SEC_SB = 0
SEC_BAM = 1
SEC_DIR = 2
# Linked List Payload: 508 bytes data + 4 bytes next_sector
CHUNK_SIZE = 508
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 io_buffer: array[512, byte]
proc virtio_blk_read(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 =
# Simple allocator: Scan BAM for first 0 bit
virtio_blk_read(SEC_BAM, addr io_buffer[0])
for i in 0..<512:
if io_buffer[i] != 0xFF:
# Found a byte with free space
for b in 0..7:
if (io_buffer[i] and byte(1 shl b)) == 0:
# Found free bit
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)
virtio_blk_write(SEC_BAM, addr io_buffer[0])
return sec
return 0 # Error / Full
# =========================================================
# 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.")
return 0
proc sfs_mount*() =
kprintln("[SFS] Mounting System v2...")
# 1. Read Sector 0 (Superblock)
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
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
else:
kprint("[SFS] Mount FAILED. Invalid Magic/Ver. Found: ")
kprint_hex(cast[uint64](io_buffer[0]))
kprintln("")
sfs_mounted = false
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
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 file_exists = false
# 1. Find File or Free Slot
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var entry_name = ""
for i in 0..31:
if io_buffer[offset+i] == 0: break
entry_name.add(char(io_buffer[offset+i]))
if entry_name == $name:
if sfs_streq(name, cast[cstring](addr io_buffer[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
elif dir_offset == -1:
dir_offset = offset
elif dir_offset == -1: 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 data_ptr = 0
var first_sector = 0'u32
var current_sector = 0'u32
# For the first chunk
current_sector = sfs_alloc_sector()
if current_sector == 0:
kprintln("[SFS] Error: Disk Full.")
return
first_sector = current_sector
var data_addr = cast[uint64](data)
var current_sector = sfs_alloc_sector()
if current_sector == 0: return
let first_sector = current_sector
while remaining > 0:
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
if remaining > 0:
next_sector = sfs_alloc_sector()
if next_sector == 0:
next_sector = EOF_MARKER # Disk full, truncated
remaining = 0
if next_sector == 0: next_sector = EOF_MARKER
# Write Next Pointer
sector_buf[508] = byte(next_sector and 0xFF)
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
# Write next pointer at end of block
cast[ptr uint32](addr sector_buf[508])[] = next_sector
virtio_blk_write(uint64(current_sector), addr sector_buf[0])
current_sector = next_sector
if current_sector == EOF_MARKER: break
# 3. Update Directory Entry
# Need to read Dir again as buffer was used for BAM/Data
# Update Directory
virtio_blk_read(SEC_DIR, addr io_buffer[0])
let n_str = $name
for i in 0..31:
if i < n_str.len: io_buffer[dir_offset+i] = byte(n_str[i])
else: io_buffer[dir_offset+i] = 0
io_buffer[dir_offset+32] = byte(first_sector and 0xFF)
io_buffer[dir_offset+33] = byte((first_sector shr 8) and 0xFF)
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)
let nm = cast[ptr UncheckedArray[char]](name)
var i = 0
while nm[i] != '\0' and i < 31:
io_buffer[dir_offset + i] = byte(nm[i])
i += 1
io_buffer[dir_offset + i] = 0
cast[ptr uint32](addr io_buffer[dir_offset + 32])[] = first_sector
cast[ptr uint32](addr io_buffer[dir_offset + 36])[] = uint32(data_len)
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.} =
if not sfs_mounted: return -1
virtio_blk_read(SEC_DIR, addr io_buffer[0])
var start_sector = 0'u32
var file_size = 0'u32
var found = false
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var entry_name = ""
for i in 0..31:
if io_buffer[offset+i] == 0: break
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)
if sfs_streq(name, cast[cstring](addr io_buffer[offset])):
start_sector = cast[ptr uint32](addr io_buffer[offset + 32])[]
file_size = cast[ptr uint32](addr io_buffer[offset + 36])[]
found = true
break
if not found: return -1
# Read Chain
var current_sector = start_sector
var dest_addr = cast[int](dest)
var remaining = int(file_size)
if remaining > max_len: remaining = max_len
var total_read = 0
while remaining > 0 and current_sector != EOF_MARKER and current_sector != 0:
var dest_addr = cast[uint64](dest)
var remaining = if int(file_size) < max_len: int(file_size) else: max_len
var total = 0
while remaining > 0 and current_sector != EOF_MARKER:
var sector_buf: array[512, byte]
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
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))
proc sfs_get_files*(): cstring = return "boot.kdl\n" # Dummy

View File

@ -6,217 +6,101 @@
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: ROMFS (Static Tar Loader)
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Rumpk L1: Sovereign VFS (Indexing TarFS)
##
## Freestanding implementation (No OS module dependencies).
## Uses a simple flat array for the file index.
{.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
TarHeader* = array[512, byte]
FileEntry = object
offset*: uint64
size*: uint64
is_sfs*: bool
name: array[64, char]
offset: uint64
size: uint64
active: bool
FileHandle = object
path*: string
offset*: uint64
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
const MAX_INDEX = 64
var index_table: array[MAX_INDEX, FileEntry]
var index_count: int = 0
proc vfs_init*(s: pointer, e: pointer) =
vfs.start_addr = cast[uint64](s)
vfs.end_addr = cast[uint64](e)
vfs.index = initTable[string, FileEntry]()
vfs.ram_data = initTable[string, seq[byte]]()
vfs.fds = initTable[int, FileHandle]()
vfs.next_fd = 3
let start_addr = cast[uint64](s)
let end_addr = cast[uint64](e)
index_count = 0
# kprint("[VFS] InitRD Start: "); kprint_hex(vfs.start_addr); kprintln("")
# kprint("[VFS] InitRD End: "); kprint_hex(vfs.end_addr); kprintln("")
var p = vfs.start_addr
while p < vfs.end_addr:
var p = start_addr
while p < end_addr:
let h = cast[ptr TarHeader](p)
if h[][0] == byte(0): break
# kprint("[VFS] Raw Header: ")
# for i in 0..15:
# kprint_hex(uint64(h[][i]))
# kprint(" ")
# kprintln("")
# Extract and normalize name directly from header
# Extract name
var name_len = 0
while name_len < 100 and h[][name_len] != 0:
inc name_len
while name_len < 100 and h[][name_len] != 0: inc name_len
var start_idx = 0
if name_len >= 2 and h[][0] == byte('.') and h[][1] == byte('/'):
start_idx = 2
elif name_len >= 1 and h[][0] == byte('/'):
start_idx = 1
if name_len >= 2 and h[][0] == byte('.') and h[][1] == byte('/'): start_idx = 2
elif name_len >= 1 and h[][0] == byte('/'): start_idx = 1
let clean_len = name_len - start_idx
var clean = ""
if clean_len > 0:
clean = newString(clean_len)
# Copy directly from header memory
for i in 0..<clean_len:
clean[i] = char(h[][start_idx + i])
if clean_len > 0 and index_count < MAX_INDEX:
var entry = addr index_table[index_count]
entry.active = true
let to_copy = if clean_len < 63: clean_len else: 63
for i in 0..<to_copy:
entry.name[i] = char(h[][start_idx + i])
entry.name[to_copy] = '\0'
if clean.len > 0:
# Extract size (octal string)
# Extract size (octal string at offset 124)
var size: uint64 = 0
for i in 124..134:
let b = h[][i]
if b >= byte('0') and b <= byte('7'):
size = (size shl 3) or uint64(b - byte('0'))
vfs.index[clean] = FileEntry(offset: p + 512'u64, size: size, is_sfs: false)
entry.size = size
entry.offset = p + 512
index_count += 1
# Move to next header
let padded_size = (size + 511'u64) and not 511'u64
p += 512'u64 + padded_size
let padded_size = (size + 511) and not 511'u64
p += 512 + padded_size
else:
p += 512'u64 # Skip invalid/empty
p += 512
proc vfs_open*(path: string, flags: int32 = 0): int =
var start_idx = 0
if path.len > 0 and path[0] == '/':
start_idx = 1
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
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]
# 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
proc vfs_open*(path: cstring, flags: int32 = 0): int32 =
var p = path
if path != nil and path[0] == '/':
p = cast[cstring](cast[uint64](path) + 1)
for i in 0..<index_count:
if vfs_streq(p, cast[cstring](addr index_table[i].name[0])):
return int32(i)
return -1
proc vfs_read_file*(path: string): string =
var start_idx = 0
if path.len > 0 and path[0] == '/':
start_idx = 1
proc vfs_read_at*(path: cstring, buf: pointer, count: uint64, offset: uint64): int64 =
let fd = vfs_open(path)
if fd < 0: return -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 + count > entry.size:
actual = entry.size - offset
let avail = entry.size - offset
let actual = if count < avail: count else: avail
if actual > 0:
copyMem(buf, cast[pointer](entry.offset + offset), int(actual))
return int64(actual)
proc vfs_write_at*(path: string, buf: pointer, count: uint64, offset: uint64): int64 =
# Promote to RamFS if on TarFS (CoW)
if not vfs.ram_data.hasKey(path):
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] = @[]
proc vfs_write_at*(path: cstring, buf: pointer, count: uint64, offset: uint64): int64 =
# ROMFS is read-only
return -1
let data = addr vfs.ram_data[path]
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.}
proc vfs_get_names*(): int = index_count # Dummy for listing

View File

@ -6,122 +6,160 @@
# See legal/LICENSE_SOVEREIGN.md for license terms.
## 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
type
VFSMode = enum
MODE_TAR, MODE_SFS, MODE_RAM
MODE_TAR, MODE_SFS, MODE_RAM, MODE_TTY
MountPoint = object
prefix: string
prefix: array[32, char]
mode: VFSMode
var mounts: seq[MountPoint] = @[]
type
FileHandle = object
path: string
path: array[64, char]
offset: uint64
mode: VFSMode
is_ram: bool
active: bool
var fds = initTable[int, FileHandle]()
var next_fd = 3
const MAX_MOUNTS = 8
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*() =
# SPEC-130: The Three-Domain Root
# SPEC-021: The Sovereign Overlay Strategy
mounts.add(MountPoint(prefix: "/nexus", mode: MODE_SFS)) # The Sovereign State (Persistent)
mounts.add(MountPoint(prefix: "/sysro", mode: MODE_TAR)) # The Projected Reality (Immutable InitRD)
mounts.add(MountPoint(prefix: "/state", mode: MODE_RAM)) # The Mutable Dust (Transient)
# Restore the SPEC-130 baseline
vfs_add_mount("/nexus", MODE_SFS)
vfs_add_mount("/sysro", MODE_TAR)
vfs_add_mount("/state", MODE_RAM)
vfs_add_mount("/dev/tty", MODE_TTY)
vfs_add_mount("/Bus/Console/tty0", MODE_TTY)
proc resolve_path(path: string): (VFSMode, string) =
for m in mounts:
if path.startsWith(m.prefix):
let sub = if path.len > m.prefix.len: path[m.prefix.len..^1] else: "/"
return (m.mode, sub)
return (MODE_TAR, path)
proc resolve_path(path: cstring): (VFSMode, int) =
for i in 0..<mnt_count:
let prefix = cast[cstring](addr mnt_table[i].prefix[0])
if vfs_starts_with(path, prefix):
var len = 0
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.} =
let p = $path
let (mode, sub) = resolve_path(p)
let (mode, prefix_len) = resolve_path(path)
# Delegate internal open
let sub_path = cast[cstring](cast[uint64](path) + uint64(prefix_len))
var internal_fd: int32 = -1
var fd = -1
case mode:
of MODE_TAR: fd = tar.vfs_open(sub, flags)
of MODE_SFS: fd = 0 # Placeholder for SFS open
of MODE_RAM: fd = tar.vfs_open(sub, flags) # Using TAR's RamFS for now
of MODE_TAR, MODE_RAM: internal_fd = tar.vfs_open(sub_path, flags)
of MODE_SFS: internal_fd = 0 # Shim
of MODE_TTY: internal_fd = 1 # Shim
if fd > 0:
let kernel_fd = next_fd
fds[kernel_fd] = FileHandle(path: sub, offset: 0, mode: mode, is_ram: (mode == MODE_RAM))
next_fd += 1
return int32(kernel_fd)
if internal_fd >= 0:
for i in 0..<MAX_FDS:
if not fd_table[i].active:
fd_table[i].active = true
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
proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let ifd = int(fd)
if not fds.hasKey(ifd): return -1
let fh = addr fds[ifd]
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fd_table[idx]
case fh.mode:
of MODE_TTY: return -2
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)
return n
of MODE_SFS:
# SFS current read-whole-file shim
var temp_buf: array[4096, byte] # FIXME: Small stack buffer
let total = sfs.sfs_read_file(cstring(fh.path), addr temp_buf[0], 4096)
if total < 0: return -1
if fh.offset >= uint64(total): return 0
let avail = uint64(total) - fh.offset
let actual = min(count, avail)
let path = cast[cstring](addr fh.path[0])
var temp: array[256, byte] # Small shim
let n = sfs.sfs_read_file(path, addr temp[0], 256)
if n <= 0: return -1
let avail = uint64(n) - fh.offset
let actual = if count < avail: count else: avail
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
return int64(actual)
return 0
proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let ifd = int(fd)
if not fds.hasKey(ifd): return -1
let fh = addr fds[ifd]
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fd_table[idx]
case fh.mode:
of MODE_TTY: return -2
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)
return n
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)
proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} =
let ifd = int(fd)
if fds.hasKey(ifd):
fds.del(ifd)
let idx = int(fd - 3)
if idx >= 0 and idx < MAX_FDS:
fd_table[idx].active = false
return 0
return -1
proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} =
var s = "/nexus\n/sysro\n/state\n"
for name in tar.vfs_get_names():
s.add("/sysro/" & name & "\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)
# Hardcoded baseline for now to avoid string/os dependency
let msg = "/nexus\n/sysro\n/state\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))
return int64(n)

View File

@ -46,6 +46,7 @@ type
CMD_NET_RX = 0x501
CMD_BLK_READ = 0x600
CMD_BLK_WRITE = 0x601
CMD_SPAWN_FIBER = 0x700
CmdPacket* = object
kind*: uint32
@ -141,9 +142,15 @@ proc recv*[T](c: var SovereignChannel[T], out_pkt: var T): bool =
elif T is CmdPacket:
return hal_cmd_pop(cast[uint64](c.ring), addr out_pkt)
# Global Channels
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 cmd_hal: HAL_Ring[CmdPacket]
var rx_hal: HAL_Ring[IonPacket]
var tx_hal: HAL_Ring[IonPacket]
# Phase 36.2: Network Channels
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
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 {
const target_addr: usize = 0x84000000;
const dest = @as([*]u8, @ptrFromInt(target_addr));
console_write("[Loader] Loading Subject Zero...\n", 33);
@memcpy(dest[0..subject_bin.len], subject_bin);
const target_addr = ion_loader_load("/sysro/bin/subject");
console_write("[Loader] Jumping...\n", 20);
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 kprint(s: cstring) {.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)
proc membrane_init*() {.importc, cdecl.}

View File

@ -53,14 +53,14 @@ proc kprint(s: cstring) {.importc, cdecl.}
proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.}
proc pty_init*() =
proc pty_init*() {.exportc, cdecl.} =
for i in 0 ..< MAX_PTYS:
ptys[i].active = false
ptys[i].id = -1
next_pty_id = 0
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.
for i in 0 ..< MAX_PTYS:
if not ptys[i].active:
@ -160,7 +160,7 @@ proc pty_read_master*(fd: int, data: ptr byte, len: int): int =
read_count += 1
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.
## Also renders to FB terminal.
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
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.
let pty = get_pty_from_fd(fd)
if pty == nil: return -1
@ -209,13 +209,13 @@ proc pty_read_slave*(fd: int, data: ptr byte, len: int): int =
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.
if pty_id < 0 or pty_id >= MAX_PTYS: return false
if not ptys[pty_id].active: return false
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).
if pty_id < 0 or pty_id >= MAX_PTYS: return
if not ptys[pty_id].active: return

View File

@ -41,7 +41,7 @@ import fiber
# Let's define the Harmonic logic.
# We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).
proc sched_get_now_ns*(): uint64 {.importc: "get_now_ns", cdecl.}
proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Forward declaration for the tick function
# 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.
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)
# =========================================================

View File

@ -22,8 +22,12 @@ cpu_switch_to:
sd s9, 96(sp)
sd s10, 104(sp)
sd s11, 112(sp)
csrr t0, sscratch
sd t0, 120(sp)
sd sp, 0(a0)
mv sp, a1
ld t0, 120(sp)
csrw sscratch, t0
ld ra, 0(sp)
ld gp, 8(sp)
ld tp, 16(sp)
@ -31,7 +35,6 @@ cpu_switch_to:
ld s1, 32(sp)
ld s2, 40(sp)
ld s3, 48(sp)
sd s4, 56(sp)
ld s4, 56(sp)
ld s5, 64(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)
// Swap sp and sscratch.
// 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
\\ 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
\\ 1:
// Allocate stack (36 words * 8 bytes = 288 bytes)
// Allocation (36*8 = 288 bytes)
\\ addi sp, sp, -288
// Save GPRs
// Save Registers (GPRs)
\\ sd ra, 0(sp)
\\ sd gp, 8(sp)
\\ sd tp, 16(sp)
@ -169,14 +173,15 @@ export fn trap_entry() align(4) callconv(.naked) void {
\\ mv a0, sp
\\ call rss_trap_handler
// Restore CSRs
// Restore CSRs (Optional if modified? sepc changed for syscall)
\\ ld t0, 240(sp)
\\ 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)
\\ csrw sstatus, t1
// Restore GPRs
// Restore Encapsulated User Context
\\ ld ra, 0(sp)
\\ ld gp, 8(sp)
\\ ld tp, 16(sp)
@ -211,10 +216,16 @@ export fn trap_entry() align(4) callconv(.naked) void {
// Deallocate stack
\\ addi sp, sp, 288
// 🔧 CRITICAL FIX: Swap back sscratch <-> sp before sret
// If returning to U-mode, sscratch currently holds User Stack.
// We need: sp = User Stack, sscratch = Kernel Stack
// 🔧 CRITICAL FIX: Swap back sscratch <-> sp ONLY if returning to User Mode
// Check sstatus.SPP (Bit 8). 0 = User, 1 = Supervisor.
\\ 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
\\ 2:
\\ sret
);
}
@ -227,6 +238,37 @@ extern fn k_check_deferred_yield() void;
export fn rss_trap_handler(frame: *TrapFrame) void {
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
// 9: ECALL from S-mode
@ -241,17 +283,13 @@ export fn rss_trap_handler(frame: *TrapFrame) void {
frame.a0 = res;
// 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();
return;
}
// 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);
// Safety halt if kernel returns (should be unreachable)
@ -268,6 +306,15 @@ extern fn NimMain() void;
export fn zig_entry() void {
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 RISC-V] Handing off to Nim L1...\n");
_ = virtio_net;
@ -287,6 +334,14 @@ export fn console_read() c_int {
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");
extern fn hal_surface_init() void;
@ -314,6 +369,27 @@ export fn rumpk_timer_now_ns() u64 {
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)
// =========================================================
@ -338,3 +414,38 @@ export fn hal_kexec(entry: u64, dtb: u64) noreturn {
);
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)
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
pub const UART_BASE: u64 = 0x10000000;
@ -164,6 +164,7 @@ pub fn create_kernel_identity_map() !*PageTable {
// MMIO regions
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, 0x20000000, 0x20000000, 0x10000, PTE_R | PTE_W); // PTY Slave
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, PLIC_BASE, PLIC_BASE, 0x400000, PTE_R | PTE_W);
@ -172,25 +173,35 @@ pub fn create_kernel_identity_map() !*PageTable {
}
// 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;
// 🏛 THE EXPANDED CAGE (Phase 37 - 256MB RAM)
// 🏛 THE ISOLATED CAGE (Phase 40 - Per-Fiber Memory Isolation)
kprint("[MM] Creating worker map:\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)
// 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);
// 2. User Memory (128-256MB) -> User Accessible (PTE_U = 1)
// This allows NipBox (at 128MB offset) to execute in U-mode.
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);
// 2. User Memory (VA 0x88000000 -> PA code_base_pa) -> User Accessible (PTE_U = 1)
// This creates ISOLATED physical regions per fiber:
// - 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)
try map_range(root, UART_BASE, UART_BASE, PAGE_SIZE, PTE_R | PTE_W | PTE_U);
// 3. MMIO Plumbing - Mapped identity but S-mode ONLY (PTE_U = 0)
// 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
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;
}
pub export fn mm_create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64) callconv(.c) u64 {
if (create_worker_map(stack_base, stack_size, packet_addr)) |root| {
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, code_base_pa)) |root| {
return make_satp(root);
} else |_| {
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)
const INPUT_BUFFER_SIZE = 256;
// 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.
var input_buffer: [INPUT_BUFFER_SIZE]u8 = undefined;
var input_head: u32 = 0; // Write position
var input_tail: u32 = 0; // Read position
var input_head = std.atomic.Value(u32).init(0); // Write position
var input_tail = std.atomic.Value(u32).init(0); // Read position
pub fn init() void {
// Initialize buffer pointers
input_head = 0;
input_tail = 0;
input_head.store(0, .monotonic);
input_tail.store(0, .monotonic);
switch (builtin.cpu.arch) {
.riscv64 => init_riscv(),
@ -54,18 +55,65 @@ pub fn init() void {
pub fn init_riscv() void {
const base = NS16550A_BASE;
// 1. Disable Interrupts
// 1. Enable Interrupts (Received Data Available)
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);
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
const lcr: *volatile u8 = @ptrFromInt(base + NS16550A_LCR);
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
poll_input();
}
@ -83,10 +131,13 @@ pub fn poll_input() void {
const byte = thr.*;
// Add to ring buffer if not full
const next_head = (input_head + 1) % INPUT_BUFFER_SIZE;
if (next_head != input_tail) {
input_buffer[input_head] = byte;
input_head = next_head;
const head_val = input_head.load(.monotonic);
const tail_val = input_tail.load(.monotonic);
const next_head = (head_val + 1) % INPUT_BUFFER_SIZE;
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)
}
@ -98,10 +149,13 @@ pub fn poll_input() void {
while ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4
const byte: u8 = @truncate(dr.*);
const next_head = (input_head + 1) % INPUT_BUFFER_SIZE;
if (next_head != input_tail) {
input_buffer[input_head] = byte;
input_head = next_head;
const head_val = input_head.load(.monotonic);
const tail_val = input_tail.load(.monotonic);
const next_head = (head_val + 1) % INPUT_BUFFER_SIZE;
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();
// Then read from buffer
if (input_tail != input_head) {
const byte = input_buffer[input_tail];
input_tail = (input_tail + 1) % INPUT_BUFFER_SIZE;
const head_val = input_head.load(.monotonic);
const tail_val = input_tail.load(.monotonic);
if (tail_val != head_val) {
const byte = input_buffer[tail_val];
input_tail.store((tail_val + 1) % INPUT_BUFFER_SIZE, .monotonic);
return byte;
}
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 {
write_bytes(s);
}
@ -194,3 +275,7 @@ pub fn print_hex(value: usize) void {
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) {
// Phase 7: Direct UART access for Proof of Life
volatile char *uart = (volatile char *)0x10000000;
const char *buf = (const char *)p;
for (size_t i = 0; i < len; i++) {
if (buf[i] == '\n') *uart = '\r';
*uart = buf[i];
}
// Phase 11: Real Syscalls only. No direct MMIO.
write(1, p, len);
}
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.} =
let sys = get_sys_table()
discard sys
# Use raw C write to avoid Nim string issues before init
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
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:
if g_fd_table[i].kind == FD_NONE:
g_fd_table[i].kind = FD_FILE
let p_str = $path
let to_copy = min(p_str.len, 63)
for j in 0..<to_copy:
g_fd_table[i].path[j] = p_str[j]
g_fd_table[i].path[to_copy] = '\0'
let p = cast[ptr UncheckedArray[char]](path)
var j = 0
while p[j] != '\0' and j < 63:
g_fd_table[i].path[j] = p[j]
j += 1
g_fd_table[i].path[j] = '\0'
return i
return -1

View File

@ -52,11 +52,13 @@ export fn fputc(c: i32, stream: ?*anyopaque) i32 {
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 {
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 {

View File

@ -1,13 +1,4 @@
# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Nexus Membrane: Virtual Terminal Emulator
# Phase 27 Part 2: The CRT Scanline Renderer
# Nexus Membrane: Virtual Terminal Emulator
import term_font
import ion_client
@ -20,58 +11,93 @@ const
COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'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 color_fg: uint32 = COLOR_PHOSPHOR_AMBER
var color_bg: uint32 = COLOR_SOVEREIGN_BLUE
var term_dirty*: bool = true
var fb_ptr: ptr UncheckedArray[uint32]
var fb_w, fb_h, fb_stride: int
var ansi_state: int = 0
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
ansi_state = 0
# ANSI State Machine
type AnsiState = enum
Normal, Escape, CSI, Param
# Initialize Grid
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
grid[row][col] = ' '
var cur_state: AnsiState = Normal
var ansi_params: array[8, int]
var cur_param_idx: int
# 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_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*() =
for row in 0..<TERM_ROWS:
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_y = 0
ansi_state = 0
cur_state = Normal
term_dirty = true
proc term_scroll() =
for row in 0..<(TERM_ROWS-1):
grid[row] = grid[row + 1]
for col in 0..<TERM_COLS: grid[row][col].dirty = true
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) =
# ANSI Stripper
if ansi_state == 1:
if ch == '[': ansi_state = 2
else: ansi_state = 0
return
if ansi_state == 2:
if ch >= '@' and ch <= '~': ansi_state = 0
return
case cur_state
of Normal:
if ch == '\x1b':
ansi_state = 1
cur_state = Escape
return
if ch == '\n':
@ -86,54 +112,115 @@ proc term_putc*(ch: char) =
if cursor_y >= TERM_ROWS:
term_scroll()
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
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 ---
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
# Safe Font Mapping
var glyph_idx = int(c) - 32
if glyph_idx < 0 or glyph_idx >= 96: glyph_idx = 0 # Space default
let glyph_idx = uint8(cell.ch)
let fg = cell.fg
let bg = cell.bg
let px_start = cx * 8
let py_start = cy * 16
for y in 0..15:
# Scanline Logic: Every 4th line
let is_scanline = (y mod 4) == 3
let row_bits = FONT_BITMAP[glyph_idx][y]
let screen_y = py_start + y
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)
for x in 0..7:
let screen_x = px_start + x
if screen_x >= fb_w: break
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
if is_pixel:
if is_scanline:
fb_ptr[pixel_idx] = fg and 0xFFE0E0E0'u32
fb_ptr[pixel_idx] = if is_scanline: (fg and 0xFFE0E0E0'u32) else: fg
else:
fb_ptr[pixel_idx] = fg
else:
if is_scanline:
fb_ptr[pixel_idx] = COLOR_SCANLINE_DIM
else:
fb_ptr[pixel_idx] = bg
fb_ptr[pixel_idx] = if is_scanline: COLOR_SCANLINE_DIM else: bg
proc term_render*() =
if fb_ptr == nil: return
if fb_ptr == nil or not term_dirty: return
for row in 0..<TERM_ROWS:
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
# 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 Dispatcher
## 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
# Source: CP437 Standard
# Exported for Renderer Access
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]
]
const FONT_WIDTH* = profile.FONT_WIDTH
const FONT_HEIGHT* = profile.FONT_HEIGHT
const FONT_BITMAP* = profile.FONT_BITMAP

View File

@ -63,7 +63,8 @@ export fn nexshell_main() void {
print("║ Command Plane: READY ║\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;
// SAFETY(NexShell): Input buffer initialized to `undefined` for performance.
@ -73,6 +74,7 @@ export fn nexshell_main() void {
var loop_count: usize = 0;
var poll_pulse: usize = 0;
var last_lsr: u8 = 0;
print("[NexShell] Entering main loop...\n");
while (true) {
loop_count += 1;
@ -89,28 +91,40 @@ export fn nexshell_main() void {
poll_pulse = 0;
}
// 1. Process Telemetry Events
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];
print("\n[NexShell] ALERT | EventID: ");
if (pkt.id == 777) {
print("777 (SECURITY_HEARTBEAT)\n");
} else {
print("GENERIC\n");
}
@atomicStore(u32, &event_ring.tail, tail + 1, .release);
}
// TEMPORARILY DISABLED: event_ring causes page fault (NULL pointer?)
// 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];
// print("\n[NexShell] ALERT | EventID: ");
// if (pkt.id == 777) {
// print("777 (SECURITY_HEARTBEAT)\n");
// } else {
// print("GENERIC\n");
// }
// @atomicStore(u32, &event_ring.tail, tail + 1, .release);
// }
// 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();
if (c != -1) {
print("[GOT]");
const byte = @as(u8, @intCast(c));
const char_buf = [1]u8{byte};
print("[NexShell] Got char: '");
print(&char_buf);
print("'\n");
// print("[NexShell] Got char\n");
if (forward_mode) {
// Check for escape: Ctrl+K (11)
@ -138,13 +152,26 @@ export fn nexshell_main() void {
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();
}
}
var forward_mode: bool = true;
var forward_mode: bool = false;
fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void {
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")) {
print("[NexShell] Disengaging Matrix Protocol...\n");
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")) {
print("[NexShell] Kernel Commands: io stop, matrix on/off, matrix status, subject, help\n");
} else if (std.mem.eql(u8, cmd_text, "matrix status")) {
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 {
print("[NexShell] Unknown Kernel Command: ");
print(cmd_text);
@ -211,11 +273,27 @@ fn push_cmd(ring: *RingBuffer(CmdPacket), kind: u32, arg: u64) void {
}
// 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_poll() 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 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 {
_ = write(1, text.ptr, text.len);
_ = kernel_write(1, text.ptr, text.len);
}