455 lines
16 KiB
Zig
455 lines
16 KiB
Zig
// 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 Immune System (NPL): The Voice & Command Plane
|
|
//!
|
|
//! Implemented as an NPL fiber, NexShell provides the interactive kernel shell.
|
|
//! Handles telemetry events, user input, and command dispatch to the ION layer.
|
|
//!
|
|
//! SAFETY: Interacts with the shared SysTable via volatile pointers and atomic operations.
|
|
|
|
const std = @import("std");
|
|
const ION_BASE = 0x83000000;
|
|
|
|
const IonPacket = extern struct {
|
|
data: u64,
|
|
phys: u64,
|
|
len: u16,
|
|
id: u16,
|
|
_pad: u32, // Match Nim's 24-byte alignment
|
|
};
|
|
|
|
const CmdPacket = extern struct {
|
|
kind: u32,
|
|
reserved: u32,
|
|
arg: u64,
|
|
id: [16]u8,
|
|
};
|
|
|
|
fn RingBuffer(comptime T: type) type {
|
|
return extern struct {
|
|
head: u32,
|
|
tail: u32,
|
|
mask: u32,
|
|
data: [256]T,
|
|
};
|
|
}
|
|
|
|
const SysTable = extern struct {
|
|
magic: u32,
|
|
reserved: u32,
|
|
s_rx: *RingBuffer(IonPacket),
|
|
s_tx: *RingBuffer(IonPacket),
|
|
s_event: *RingBuffer(IonPacket),
|
|
s_cmd: *RingBuffer(CmdPacket),
|
|
s_input: *RingBuffer(IonPacket),
|
|
};
|
|
|
|
const CMD_ION_STOP = 1;
|
|
const CMD_ION_START = 2;
|
|
const CMD_GPU_MATRIX = 0x100;
|
|
const CMD_GET_GPU_STATUS = 0x102;
|
|
|
|
// The Main Loop for the NexShell Fiber
|
|
export fn nexshell_main() void {
|
|
const sys = @as(*SysTable, @ptrFromInt(ION_BASE));
|
|
|
|
print("\n╔═══════════════════════════════════════╗\n");
|
|
print("║ NEXSHELL IMMUNE SYSTEM ACTIVE ║\n");
|
|
print("║ Command Plane: READY ║\n");
|
|
print("╚═══════════════════════════════════════╝\n");
|
|
|
|
// 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.
|
|
// Populated by char-by-char console reads before use.
|
|
var input_buffer: [64]u8 = undefined;
|
|
var input_idx: usize = 0;
|
|
|
|
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;
|
|
poll_pulse += 1;
|
|
|
|
// First iteration diagnostic
|
|
if (loop_count == 1) {
|
|
print("[NexShell] First iteration\n");
|
|
}
|
|
|
|
// Polling pulse every 100 to show activity
|
|
if (poll_pulse >= 100) {
|
|
print(".");
|
|
poll_pulse = 0;
|
|
}
|
|
// 1. Process Telemetry Events
|
|
// 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));
|
|
// print("[NexShell] Got char\n");
|
|
|
|
if (forward_mode) {
|
|
// Check for escape: Ctrl+K (11)
|
|
if (byte == 11) {
|
|
forward_mode = false;
|
|
print("\n[NexShell] RESUMING KERNEL CONTROL.\n");
|
|
} else {
|
|
const bs = [1]u8{byte};
|
|
ion_push_stdin(&bs, 1);
|
|
}
|
|
} else {
|
|
if (byte == '\r' or byte == '\n') {
|
|
print("\n");
|
|
process_command(input_buffer[0..input_idx], cmd_ring);
|
|
input_idx = 0;
|
|
} else if (byte == 0x7F or byte == 0x08) {
|
|
if (input_idx > 0) {
|
|
input_idx -= 1;
|
|
print("\x08 \x08"); // Backspace
|
|
}
|
|
} else if (input_idx < 63) {
|
|
input_buffer[input_idx] = byte;
|
|
input_idx += 1;
|
|
const bs = [1]u8{byte};
|
|
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 = false;
|
|
|
|
fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void {
|
|
if (cmd_text.len == 0) return;
|
|
|
|
if (forward_mode) {
|
|
const is_toggle = std.mem.eql(u8, cmd_text, "kernel") or std.mem.eql(u8, cmd_text, "exit");
|
|
|
|
// ALWAYS FORWARD TO USERLAND first so it can process its own exit
|
|
print("[NexShell] Forwarding to Subject...\n");
|
|
|
|
// Combine command + newline to avoid fragmentation
|
|
if (cmd_text.len > 0) {
|
|
// SAFETY(NexShell): Local forward buffer initialized to `undefined`.
|
|
// Immediately populated by `@memcpy` and newline before pushing to ION.
|
|
var forward_buf: [128]u8 = undefined;
|
|
const copy_len = if (cmd_text.len > 126) 126 else cmd_text.len;
|
|
@memcpy(forward_buf[0..copy_len], cmd_text[0..copy_len]);
|
|
forward_buf[copy_len] = '\n';
|
|
ion_push_stdin(forward_buf[0 .. copy_len + 1].ptr, copy_len + 1);
|
|
}
|
|
|
|
if (is_toggle) {
|
|
forward_mode = false;
|
|
print("[NexShell] Dropping to Kernel Debug Mode.\n");
|
|
}
|
|
} else {
|
|
if (std.mem.eql(u8, cmd_text, "subject") or std.mem.eql(u8, cmd_text, "nipbox")) {
|
|
forward_mode = true;
|
|
print("[NexShell] Resuming Subject Pipe.\n");
|
|
return;
|
|
}
|
|
|
|
if (std.mem.eql(u8, cmd_text, "io stop") or std.mem.eql(u8, cmd_text, "ion stop")) {
|
|
print("[NexShell] Pushing CMD_ION_STOP...\n");
|
|
push_cmd(cmd_ring, CMD_ION_STOP, 0);
|
|
} else if (std.mem.eql(u8, cmd_text, "matrix on")) {
|
|
print("[NexShell] Engaging Matrix Protocol (Emergency Override)...\n");
|
|
push_cmd(cmd_ring, CMD_GPU_MATRIX, 1);
|
|
} 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")) {
|
|
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, "stl summary")) {
|
|
stl_print_summary();
|
|
} else if (std.mem.eql(u8, cmd_text, "stl list")) {
|
|
print("[NexShell] Recent Events:\n");
|
|
const total = stl_count();
|
|
const start = if (total > 10) total - 10 else 0;
|
|
var id = total - 1;
|
|
while (id >= start) {
|
|
const ev = stl_lookup(id);
|
|
if (ev) |e| {
|
|
print(" [");
|
|
print_u64_hex(id);
|
|
print("] Kind=");
|
|
print_u16_hex(@intFromEnum(e.kind));
|
|
print(" Fiber=");
|
|
print_u16_hex(@as(u16, @intCast(e.fiber_id)));
|
|
print("\n");
|
|
}
|
|
if (id == 0) break;
|
|
id -= 1;
|
|
}
|
|
} else if (std.mem.startsWith(u8, cmd_text, "stl graph") or std.mem.eql(u8, cmd_text, "stl tree")) {
|
|
var lineage: LineageResult = undefined;
|
|
const total = stl_count();
|
|
if (total == 0) {
|
|
print("[NexShell] No events to graph.\n");
|
|
} else {
|
|
// Default to last event
|
|
const last_id = @as(u64, total - 1);
|
|
stl_trace_lineage(last_id, &lineage);
|
|
print("[NexShell] Causal Graph for Event ");
|
|
print_u64_hex(last_id);
|
|
print(":\n\n");
|
|
|
|
var i: u32 = 0;
|
|
while (i < lineage.count) : (i += 1) {
|
|
const eid = lineage.event_ids[i];
|
|
const ev = stl_lookup(eid);
|
|
|
|
if (i > 0) print(" |\n ▼\n");
|
|
|
|
print("[");
|
|
print_u64_hex(eid);
|
|
print("] ");
|
|
|
|
if (ev) |e| {
|
|
switch (e.kind) {
|
|
.SystemBoot => print("SystemBoot"),
|
|
.FiberSpawn => print("FiberSpawn"),
|
|
.CapabilityGrant => print("CapGrant"),
|
|
.AccessDenied => print("AccessDenied"),
|
|
else => {
|
|
print("Kind=");
|
|
print_u16_hex(@intFromEnum(e.kind));
|
|
},
|
|
}
|
|
} else {
|
|
print("???");
|
|
}
|
|
print("\n");
|
|
}
|
|
print("\n");
|
|
}
|
|
} else if (std.mem.eql(u8, cmd_text, "stl dump")) {
|
|
var dump_buf: [4096]u8 = undefined;
|
|
const written = stl_export_binary(&dump_buf, dump_buf.len);
|
|
if (written > 0) {
|
|
print("[NexShell] STL Binary Dump (");
|
|
print_u64_hex(written);
|
|
print(" bytes):\n");
|
|
var i: usize = 0;
|
|
while (i < written) : (i += 1) {
|
|
print_hex(dump_buf[i]);
|
|
if ((i + 1) % 32 == 0) print("\n");
|
|
}
|
|
print("\n[NexShell] Dump Complete.\n");
|
|
} else {
|
|
print("[NexShell] Dump Failed (Buffer too small or STL not ready)\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(" STL: stl summary, stl list, stl dump, stl graph\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);
|
|
print("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn push_cmd(ring: *RingBuffer(CmdPacket), kind: u32, arg: u64) void {
|
|
const head = @atomicLoad(u32, &ring.head, .acquire);
|
|
const tail = @atomicLoad(u32, &ring.tail, .monotonic);
|
|
|
|
const next = (head + 1) & ring.mask;
|
|
if (next == tail) {
|
|
print("[NexShell] CMD RING FULL!\n");
|
|
return;
|
|
}
|
|
|
|
ring.data[head & ring.mask] = .{ .kind = kind, .reserved = 0, .arg = arg, .id = [_]u8{0} ** 16 };
|
|
@atomicStore(u32, &ring.head, next, .release);
|
|
}
|
|
|
|
// OS Shims
|
|
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;
|
|
|
|
// STL Externs
|
|
extern fn stl_count() u32;
|
|
extern fn stl_print_summary() void;
|
|
extern fn stl_get_recent(max_count: u32, result: *QueryResult) void;
|
|
extern fn stl_export_binary(dest: [*]u8, max_size: usize) usize;
|
|
extern fn stl_trace_lineage(event_id: u64, result: *LineageResult) void;
|
|
extern fn stl_lookup(event_id: u64) ?*const Event;
|
|
|
|
const LineageResult = extern struct {
|
|
count: u32,
|
|
event_ids: [16]u64,
|
|
};
|
|
|
|
const EventKind = enum(u16) {
|
|
Null = 0,
|
|
SystemBoot = 1,
|
|
SystemShutdown = 2,
|
|
FiberSpawn = 3,
|
|
FiberTerminate = 4,
|
|
CapabilityGrant = 10,
|
|
CapabilityRevoke = 11,
|
|
CapabilityDelegate = 12,
|
|
ChannelOpen = 20,
|
|
ChannelClose = 21,
|
|
ChannelRead = 22,
|
|
ChannelWrite = 23,
|
|
MemoryAllocate = 30,
|
|
MemoryFree = 31,
|
|
MemoryMap = 32,
|
|
NetworkPacketRx = 40,
|
|
NetworkPacketTx = 41,
|
|
AccessDenied = 50,
|
|
PolicyViolation = 51,
|
|
};
|
|
|
|
const Event = packed struct {
|
|
kind: EventKind,
|
|
_reserved: u8 = 0,
|
|
timestamp_ns: u64,
|
|
fiber_id: u64,
|
|
entity_id: u64,
|
|
cause_id: u64,
|
|
data0: u64,
|
|
data1: u64,
|
|
data2: u64,
|
|
};
|
|
|
|
const QueryResult = extern struct {
|
|
count: u32,
|
|
events: [64]*const Event,
|
|
};
|
|
|
|
fn print_u64_hex(val: u64) void {
|
|
const chars = "0123456789ABCDEF";
|
|
var buf: [16]u8 = undefined;
|
|
var v = val;
|
|
var i: usize = 0;
|
|
while (i < 16) : (i += 1) {
|
|
buf[15 - i] = chars[v & 0xF];
|
|
v >>= 4;
|
|
}
|
|
print(&buf);
|
|
}
|
|
|
|
fn print_u16_hex(val: u16) void {
|
|
const chars = "0123456789ABCDEF";
|
|
const buf = [4]u8{
|
|
chars[(val >> 12) & 0xF],
|
|
chars[(val >> 8) & 0xF],
|
|
chars[(val >> 4) & 0xF],
|
|
chars[val & 0xF],
|
|
};
|
|
print(&buf);
|
|
}
|
|
|
|
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 {
|
|
_ = kernel_write(1, text.ptr, text.len);
|
|
}
|