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 72891287fb
commit bf427290f1
27 changed files with 1775 additions and 1707 deletions

View File

@ -25,16 +25,26 @@ proc main() =
print(cstring("[INIT] System Ready. Starting heartbeat...\n"))
print(cstring("[INIT] Spawning Sovereign Shell (Mksh)...\n"))
# 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"))
while true:
# 🕵️ DIAGNOSTIC: BREATHER
pump_membrane_stack()
yield_fiber()
# 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))
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:
# Sleep 1 second between checks
discard syscall(0x65, 1000000000'u64) # nanosleep: 1 second
pump_membrane_stack()
yield_fiber()
when isMainModule:
main()

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
# 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
if next_sector == 0: next_sector = EOF_MARKER
# 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 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
var total_read = 0
while remaining > 0 and current_sector != EOF_MARKER and current_sector != 0:
var sector_buf: array[512, byte]
virtio_blk_read(uint64(current_sector), addr sector_buf[0])
# 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
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_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_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
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)
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]
if offset >= entry.size: return 0
if offset + count > entry.size:
actual = entry.size - offset
copyMem(buf, cast[pointer](entry.offset + offset), int(actual))
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
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)
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 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))
fh.offset += actual
return int64(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,30 +107,34 @@ 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
\\ sd ra, 0(sp)
\\ sd gp, 8(sp)
\\ sd tp, 16(sp)
\\ sd t0, 24(sp)
\\ sd t1, 32(sp)
\\ sd t2, 40(sp)
\\ sd s0, 48(sp)
\\ sd s1, 56(sp)
\\ sd a0, 64(sp)
\\ sd a1, 72(sp)
\\ sd a2, 80(sp)
\\ sd a3, 88(sp)
\\ sd a4, 96(sp)
// Save Registers (GPRs)
\\ sd ra, 0(sp)
\\ sd gp, 8(sp)
\\ sd tp, 16(sp)
\\ sd t0, 24(sp)
\\ sd t1, 32(sp)
\\ sd t2, 40(sp)
\\ sd s0, 48(sp)
\\ sd s1, 56(sp)
\\ sd a0, 64(sp)
\\ sd a1, 72(sp)
\\ sd a2, 80(sp)
\\ sd a3, 88(sp)
\\ sd a4, 96(sp)
\\ sd a5, 104(sp)
\\ sd a6, 112(sp)
\\ sd a7, 120(sp)
@ -169,27 +173,28 @@ 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
\\ ld ra, 0(sp)
\\ ld gp, 8(sp)
\\ ld tp, 16(sp)
\\ ld t0, 24(sp)
\\ ld t1, 32(sp)
\\ ld t2, 40(sp)
\\ ld s0, 48(sp)
\\ ld s1, 56(sp)
\\ ld a0, 64(sp)
\\ ld a1, 72(sp)
\\ ld a2, 80(sp)
\\ ld a3, 88(sp)
\\ ld a4, 96(sp)
// Restore Encapsulated User Context
\\ ld ra, 0(sp)
\\ ld gp, 8(sp)
\\ ld tp, 16(sp)
\\ ld t0, 24(sp)
\\ ld t1, 32(sp)
\\ ld t2, 40(sp)
\\ ld s0, 48(sp)
\\ ld s1, 56(sp)
\\ ld a0, 64(sp)
\\ ld a1, 72(sp)
\\ ld a2, 80(sp)
\\ ld a3, 88(sp)
\\ ld a4, 96(sp)
\\ ld a5, 104(sp)
\\ ld a6, 112(sp)
\\ ld a7, 120(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,13 +11,191 @@ 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
# ANSI State Machine
type AnsiState = enum
Normal, Escape, CSI, Param
var cur_state: AnsiState = Normal
var ansi_params: array[8, int]
var cur_param_idx: int
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] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
cursor_x = 0
cursor_y = 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] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
term_dirty = true
proc term_putc*(ch: char) =
case cur_state
of Normal:
if ch == '\x1b':
cur_state = Escape
return
if ch == '\n':
cursor_x = 0
cursor_y += 1
elif ch == '\r':
cursor_x = 0
elif ch >= ' ':
if cursor_x >= TERM_COLS:
cursor_x = 0
cursor_y += 1
if cursor_y >= TERM_ROWS:
term_scroll()
cursor_y = TERM_ROWS - 1
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, cell: Cell) =
if fb_ptr == nil: return
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:
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
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
let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0
if is_pixel:
fb_ptr[pixel_idx] = if is_scanline: (fg and 0xFFE0E0E0'u32) else: fg
else:
fb_ptr[pixel_idx] = if is_scanline: COLOR_SCANLINE_DIM else: bg
proc term_render*() =
if fb_ptr == nil or not term_dirty: return
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
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)
@ -36,104 +205,22 @@ proc term_init*() =
fb_stride = int(sys.fb_stride)
cursor_x = 0
cursor_y = 0
ansi_state = 0
cur_state = Normal
term_dirty = true
# Initialize Grid
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] = ' '
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
# Force initial color compliance
color_fg = COLOR_PHOSPHOR_AMBER
color_bg = COLOR_SOVEREIGN_BLUE
proc term_clear*() =
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
grid[row][col] = ' '
cursor_x = 0
cursor_y = 0
ansi_state = 0
proc term_scroll() =
for row in 0..<(TERM_ROWS-1):
grid[row] = grid[row + 1]
for col in 0..<TERM_COLS:
grid[TERM_ROWS-1][col] = ' '
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
if ch == '\x1b':
ansi_state = 1
return
if ch == '\n':
cursor_x = 0
cursor_y += 1
elif ch == '\r':
cursor_x = 0
elif ch >= ' ':
if cursor_x >= TERM_COLS:
cursor_x = 0
cursor_y += 1
if cursor_y >= TERM_ROWS:
term_scroll()
cursor_y = TERM_ROWS - 1
grid[cursor_y][cursor_x] = ch
cursor_x += 1
# --- THE GHOST RENDERER ---
proc draw_char(cx, cy: int, c: char, fg: uint32, bg: uint32) =
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 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
else:
fb_ptr[pixel_idx] = fg
else:
if is_scanline:
fb_ptr[pixel_idx] = COLOR_SCANLINE_DIM
else:
fb_ptr[pixel_idx] = bg
proc term_render*() =
if fb_ptr == nil: return
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
draw_char(col, row, grid[row][col], color_fg, color_bg)
# 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);
}