Compare commits

..

52 Commits

Author SHA1 Message Date
Markus Maiwald eaf000e5ec
chore: add .gitignore
Rumpk CI / Build RISC-V 64 (push) Failing after 5s Details
Rumpk CI / Security Scan (push) Successful in 4s Details
2026-02-15 20:04:48 +01:00
Markus Maiwald 225d08908b
ci: add Forgejo Actions workflow 2026-02-15 20:04:42 +01:00
Markus Maiwald e3246e8509
chore: remove build artifacts — initrd.tar, compiled binaries, temp ELFs 2026-02-15 20:04:27 +01:00
Markus Maiwald e8b58f375a chore: add operational notes and test shell source 2026-02-15 20:00:00 +01:00
Markus Maiwald 84c3345595 feat(userland): NipBox LITE subject binary, ARM64 init support 2026-02-15 19:59:30 +01:00
Markus Maiwald a38bc523a8 feat(build): dual-arch build system — build_nim.sh, build_full.sh, run_aarch64.sh 2026-02-15 19:59:26 +01:00
Markus Maiwald 49c58fbd94 feat(membrane): dual-arch membrane, freestanding stubs, Libertaria LWF integration 2026-02-15 19:59:20 +01:00
Markus Maiwald 8d64fe2180 feat(fs): LittleFS integration — VFS, HAL bridge, persistent /nexus 2026-02-15 19:59:13 +01:00
Markus Maiwald 0c598ce0bd feat(core): M4 security — CSpace, Pledge, STL, budget enforcement, BKDL manifests 2026-02-15 19:59:07 +01:00
Markus Maiwald 8d4b581519 feat(hal): ARM64 port, VirtIO MMIO, dual-arch HAL (M3.1-M3.3) 2026-02-15 19:58:51 +01:00
Markus Maiwald 011e0b699e fix(rumpk): enable user stack access and repair boot process
- Enabled SUM (Supervisor Access to User Memory) in riscv_init to allow kernel loader to write to user stacks.
- Removed dangerous 'csrc sstatus' in kload_phys that revoked access.
- Aligned global fiber stacks to 4096 bytes to prevent unmapped page faults at stack boundaries.
- Restored 'boot.o' linking to fix silent boot failure.
- Implemented 'fiber_can_run_on_channels' stub to satisfy Membrane linking.
- Defined kernel stack in header.zig to fix '__stack_top' undefined symbol.
- Resolved duplicate symbols in overrides.c and nexshell.
2026-01-08 21:38:14 +01:00
Markus Maiwald 7207282236 feat(tinybox): graft toybox integration and build system automation
- Integrated ToyBox as git submodule
- Added src/nexus/builder/toybox.nim for automated cross-compilation
- Updated InitRD builder to support symlinks
- Refactored Kernel builder to fix duplicate symbol and path issues
- Modified forge.nim to orchestrate TinyBox synthesis (mksh + toybox)
- Updated SPEC-006-TinyBox.md with complete architecture
- Added mksh binary to initrd graft source
2026-01-08 21:18:08 +01:00
Markus Maiwald 11db62ea8c feat(tinybox): Graft ToyBox utilities into initrd
- Add ToyBox as git submodule (vendor/toybox)
- Build ToyBox for RISC-V 64-bit using zig cc
- Integrate 200+ Unix utilities (ls, cat, grep, etc.)
- Create symlinks for common commands
- Rebuild initrd.tar with ToyBox (2.6MB)
- TinyBox now complete per SPEC-006

ToyBox provides BusyBox-like functionality with:
- 0BSD license (permissive)
- 678KB binary size
- Full POSIX compatibility via Membrane
- Multi-call binary with symlinks

Closes: TinyBox integration
Refs: SPEC-006-STRATEGY-TINYBOX-CONCEPT.md
2026-01-08 19:56:29 +01:00
Markus Maiwald 5c57341b81 fix(rumpk): Fix LwIP kernel build for RISC-V freestanding
- Rebuild liblwip.a from clean sources (removed initrd.o contamination)
- Add switch.o to provide cpu_switch_to symbol
- Add sys_arch.o to provide sys_now and nexus_lwip_panic
- Add freestanding defines to cc.h (LWIP_NO_CTYPE_H, etc.)
- Compile sys_arch.c with -mcmodel=medany for RISC-V

Fixes duplicate symbol errors and undefined reference errors.
Kernel now builds successfully with: zig build -Dtarget=riscv64-freestanding
2026-01-08 19:21:02 +01:00
Markus Maiwald f5f9f0bf6d feat(network): Ratify SPEC-701 & SPEC-093 - Helios TCP Probe SUCCESS. Full TCP connectivity verified. 2026-01-08 13:01:47 +01:00
Markus Maiwald 1f164eca59 feat(lwip): LwIP pool bypass - Complete pool bypass
BREAKTHROUGH: memp_malloc crashes ELIMINATED

HEPHAESTUS NUCLEAR PROTOCOL:
- Completely bypass memp_pools array in MEMP_MEM_MALLOC mode
- All allocations go through do_memp_malloc_pool(NULL) with 1024-byte fallback
- Added SYS_LIGHTWEIGHT_PROT=0 for NO_SYS mode
- Surgical DNS PCB override remains operational

VALIDATION:
 memp_malloc no longer crashes
 DNS query successfully enqueues
 Heap allocations confirmed working (0x400 + 0x70 bytes)
 Surgical fix validated

REMAINING:
Secondary crash in dns_send/udp_sendto at 0x80212C44
This is a DIFFERENT issue - likely UDP packet construction

The forge has tempered the steel.
 + kernel: cc112403
2026-01-08 09:41:03 +01:00
Markus Maiwald 6bc5804e48 feat(dns): Surgical DNS PCB override
BREAKTHROUGH: Manual DNS PCB initialization now succeeds!

CRITICAL FIXES:
- Exposed dns_pcbs[] and dns_recv() for external manual setup
- Implemented Surgical override in net_glue.nim
  * Manually allocates UDP PCB after heap is stable
  * Properly binds and configures receive callback
  * Successfully injects into dns_pcbs[0]

VALIDATION:
 kernel override executes successfully
 udp_new() returns valid 48-byte PCB
 udp_bind() succeeds
 Callback configured
 DNS PCB injected

REMAINING ISSUE:
Secondary crash during DNS query enqueue/send phase
Requires further investigation of memp_malloc calls during resolution

 + kernel: The forge burns bright.
2026-01-08 09:27:28 +01:00
Markus Maiwald eaf753c70c feat(membrane): Hardened LwIP memory manager & stabilized DHCP/DNS
PROBLEM RESOLVED: memp_malloc NULL pointer crashes (0x18/0x20 offsets)

CRITICAL FIXES:
- Nuclear fail-safe in memp.c for mission-critical protocol objects
  * Direct heap fallback for UDP_PCB, TCP_PCB, PBUF, SYS_TMR pools
  * Handles ABI/relocation failures in memp_pools[] descriptor array
  * Prevents ALL NULL dereferences in protocol allocation paths

- Iteration-based network heartbeat in net_glue.nim
  * Drives LwIP state machines independent of system clock
  * Resolves DHCP/DNS timeout issues in QEMU/freestanding environments
  * Ensures consistent protocol advancement even with time dilation

- Unified heap configuration (MEMP_MEM_MALLOC=1, LWIP_TIMERS=1)
  * 2MB heap for network operations
  * Disabled LwIP stats to avoid descriptor corruption
  * Increased pool sizes for robustness

VERIFICATION:
 DHCP: Reliable IP acquisition (10.0.2.15)
 ICMP: Full Layer 2 connectivity confirmed
 DNS: Query enqueuing operational (secondary crash isolated)
 VirtIO: 12-byte header alignment maintained

NEXT: Final DNS request table hardening for complete resolution

 Signature: CORRECTNESS > SPEED
2026-01-07 23:47:04 +01:00
Markus Maiwald 0949ea1187 test(network): added DNS resolution verification and extended test script
- Updated init.nim with post-fix DNS resolution test (google.com).
- Added test_network_extended.sh with 120s timeout to allow full DHCP/DNS cycle.
- Validates the fix for the UDP PCB pool exhaustion crash.
2026-01-07 21:28:18 +01:00
Markus Maiwald fd8e3beb84 fix(dns): resolved NULL pointer crash by increasing UDP PCB pool
Fixed critical kernel trap (Page Fault at 0x20) occurring during DNS queries.

Root Cause:
- dns_gethostbyname() crashed when accessing NULL udp_pcb pointer
- udp_new_ip_type() failed due to memory pool exhaustion
- MEMP_NUM_UDP_PCB=8 was insufficient (DHCP=1, DNS=1, others=6)

Solution:
- Increased MEMP_NUM_UDP_PCB from 8 to 16 in lwipopts.h
- Added DNS initialization check function in net_glue.nim
- Documented root cause analysis in DNS_NULL_CRASH_RCA.md

Impact:
- System now boots without crashes
- DNS infrastructure stable and ready for queries
- Network stack remains operational under load

Verified: No kernel traps during 60s test run with DHCP + network activity.

Next: Debug DNS query resolution (separate from crash fix).
2026-01-07 21:16:02 +01:00
Markus Maiwald 49dd5382b9 feat(network): established full bidirectional IP connectivity via LwIP
Established stable network link between NexusOS and QEMU/SLIRP gateway.
Resolved critical packet corruption and state machine failures.

Key fixes:
- VIRTIO: Aligned header size to 12 bytes (VIRTIO_NET_F_MRG_RXBUF modern compliance).
- LWIP: Enabled LWIP_TIMERS=1 to drive internal DHCP/DNS state machines.
- KERNEL: Adjusted NetSwitch polling to 10ms to prevent fiber starvation.
- MEMBRANE: Corrected TX packet offset and fixed comment syntax.
- INIT: Verified ICMP Echo Request/Reply (10.0.2.15 <-> 10.0.2.2).

Physically aligned. Logically sovereign.
Fixed by the  & kernel Forge.
2026-01-07 20:19:15 +01:00
Markus Maiwald b0e2dfa20e test(utcp): Root cause analysis - QEMU hostfwd requires listening socket
Documented why UDP/9999 packets don't reach Fast Path. QEMU's NAT drops packets without listening socket. Proposed TAP networking solution for Phase 38.
2026-01-07 17:04:51 +01:00
Markus Maiwald eedf05fadf feat(utcp): UTCP Protocol Implementation (SPEC-093)
Implemented UtcpHeader (46 bytes) with CellID-based routing. Integrated UTCP handler into NetSwitch Fast Path. UDP/9999 tunnel packets now route to utcp_handle_packet().
2026-01-07 16:45:06 +01:00
Markus Maiwald b480f14bb5 feat(net): Fast Path/Zero-Copy Bypass & Network Stack Documentation
Implemented Fast Path filter for UDP/9999 UTCP tunnel traffic, bypassing LwIP stack. Added zero-copy header stripping in fastpath.nim. Documented full network stack architecture in docs/NETWORK_STACK.md. Verified ICMP ping and LwIP graft functionality.
2026-01-07 16:29:15 +01:00
Markus Maiwald 4c91aa7f14 Network: Phase 36 Component (DHCP, VirtIO 12B, Hardened Logs) 2026-01-07 14:48:40 +01:00
Markus Maiwald 77b4cb55c7 feat(hal/core): implement heartbeat of iron (real-time SBI timer driver)
- Implemented RISC-V SBI timer driver in HAL (entry_riscv.zig).

- Integrated timer into the Harmonic Scheduler (kernel.nim/sched.nim).

- Re-enabled the Silence Doctrine: system now enters low-power WFI state during idle.

- Confirmed precise nanosecond wakeup and LwIP pump loop stability.

- Updated kernel version to v1.1.2.
2026-01-06 20:54:22 +01:00
Markus Maiwald 96ee0a0112 docs(core): add Network Membrane technical documentation 2026-01-06 18:40:30 +01:00
Markus Maiwald 068fc732a6 feat(core): fix userland network init, implement syscalls, bump v1.1.1
- Fix init crash by implementing SYS_WAIT_MULTI and valid hex printing.

- Fix Supervisor Mode hang using busy-wait loop (bypassing missing timer).

- Confirm LwIP Egress transmission and Timer functionality.

- Update kernel version to v1.1.1.
2026-01-06 18:31:32 +01:00
Markus Maiwald a59a4cf9db fix(virtio): overcome capability probe hang with paging enabled
- Fixes VirtIO-PCI capability probing logic to handle invalid BAR indices gracefully.
- Enables defensive programming in virtio_pci.zig loop.
- Implements Typed Channel Multiplexing (0x500/0x501) for NetSwitch.
- Grants networking capabilities to Subject/Userland.
- Refactors NexShell to use reactive I/O (ion_wait_multi).
- Bumps version to 2026.1.1 (Patch 1).
2026-01-06 13:39:40 +01:00
Markus Maiwald 8b109652ab feat(nexshell): implement Visual Causal Graph Viewer
- Added 'stl graph' command to NexShell for ASCII causal visualization
- Integrated Causal Graph Audit into kernel boot summary
- Optimized STL list command to show absolute event IDs
- Fixed Nim kernel crashes by avoiding dynamic string allocations in STL summary
- Hardened HAL-to-NexShell interface with proper extern declarations
2026-01-06 10:13:59 +01:00
Markus Maiwald 3779197eb9 feat(kernel): implement System Truth Ledger and Causal Trace
- Implemented System Ontology (SPEC-060) and STL (SPEC-061) in Zig HAL
- Created Nim bindings and high-level event emission API
- Integrated STL into kernel boot sequence (SystemBoot, FiberSpawn, CapGrant)
- Implemented Causal Graph Engine (SPEC-062) for lineage tracing
- Verified self-aware causal auditing in boot logs
- Optimized Event structure to 58 bytes for cache efficiency
2026-01-06 03:37:53 +01:00
Markus Maiwald bf427290f1 feat(kernel): implement Sv39 fiber memory isolation and hardened ELF loader 2026-01-05 16:36:25 +01:00
Markus Maiwald 72891287fb feat(rumpk): Implement PTY subsystem for terminal semantics
Phase 40: The Soul Bridge

IMPLEMENTED:
- PTY subsystem with master/slave fd pairs (100-107 / 200-207)
- Ring buffer-based bidirectional I/O (4KB each direction)
- Line discipline (CANON/RAW modes, echo support)
- Integration with FB terminal renderer

CHANGES:
- [NEW] core/pty.nim - Complete PTY implementation
- [MODIFY] kernel.nim - Wire PTY to syscalls, add pty_init() to boot

DATA FLOW:
Keyboard → ION chan_input → pty_push_input → master_to_slave buffer
→ pty_read_slave → mksh stdin → mksh stdout → pty_write_slave
→ term_putc/term_render → Framebuffer

VERIFICATION:
[PTY] Subsystem Initialized
[PTY] Allocated ID=0x0000000000000000
[PTY] Console PTY Allocated

REMAINING: /dev/tty device node for full TTY support

Co-authored-by:  <voxis@nexus-os.org>
2026-01-05 01:39:53 +01:00
Markus Maiwald 4cec2d8c25 feat(rumpk): Achieve interactive Mksh shell & formalize Sovereign FSH
CHECKPOINT 7: Nuke LwIP, Fix Stack

🎯 PRIMARY ACHIEVEMENTS:
-  Interactive Mksh shell successfully boots and accepts input
-  Kernel-side LwIP networking disabled (moved to userland intent)
-  C-ABI handover fully operational (argc, argv, environ)
-  SPEC-130: Sovereign Filesystem Hierarchy formalized

🔧 KERNEL FIXES:
1. **Nuked Kernel LwIP**
   - Disabled membrane_init() in kernel.nim
   - Prevented automatic DHCP/IP acquisition
   - Network stack deferred to userland control

2. **Fixed C-ABI Stack Handover**
   - Updated rumpk_enter_userland signature: (entry, argc, argv, sp)
   - Kernel prepares userland stack at 0x8FFFFFE0 (top of user RAM)
   - Stack layout: [argc][argv[0]][argv[1]=NULL][envp[0]=NULL][string data]
   - Preserved kernel-passed arguments through subject_entry.S

3. **Fixed Trap Return Stack Switching**
   - Added sscratch swap before sret in entry_riscv.zig
   - Properly restores user stack and preserves kernel stack pointer
   - Fixes post-syscall instruction page fault

4. **Rebuilt Mksh with Fixed Runtime**
   - subject_entry.S no longer zeros a0/a1
   - Arguments flow: Kernel -> switch.S -> subject_entry.S -> main()

📐 ARCHITECTURAL SPECS:
- **SPEC-130: Sovereign Filesystem Hierarchy**
  - Tri-State (+1) Storage Model: /sysro, /etc, /run, /state
  - Declarative Stateless Doctrine (inspired by Clear Linux/Silverblue)
  - Ghost Writer Pattern: KDL recipes -> /etc generation
  - Bind-Mount Strategy for legacy app grafting
  - Database Contract for /state (transactional, encrypted)

🛠️ DEVELOPER EXPERIENCE:
- Fixed filesystem.nim to fallback to .nexus/ for local builds
- Prevents permission errors during development

🧪 VERIFICATION:

Syscalls confirmed working: write (0x200, 0x204), read (0x203)

NEXT: Implement proper TTY/PTY subsystem for full job control

Co-authored-by:  <voxis@nexus-os.org>
2026-01-05 01:14:24 +01:00
Markus Maiwald 6e78b7f458 Rumpk Stability, NipBox Boot, and Repository Cleanup
- Fixed Rumpk RISC-V Trap Handler (SSCRATCH swap, align(4), SUM bit) to prevent double faults.

- Stabilized Userland Transition (fence.i, MMU activation) allowing NipBox execution.

- Restored Forge pipeline to build NipBox from source.

- Documented critical RISC-V trap mechanics in .agent/tips.

- Committed pending repository cleanup (obsolete websites) and new core modules.
2026-01-04 21:39:06 +01:00
Markus Maiwald bd03bed91f Phase 37 FINAL: Memory Isolation & STDIN Infrastructure Complete
Infrastructure for interactive shell is ready and verified.
Memory isolation (Sv39 'Glass Cage') is stable and operational.

Summary of Phase 37 accomplishments:
1. Increased DRAM to 256MB to accommodate expanding userland.
2. Expanded User RAM to 64MB in Linker and HAL Memory Maps.
3. Implemented Sv39 Page Tables with full isolation for worker fibers.
4. Fixed NipBox BSS overflow by eliminating transitively imported kernel memory pools.
5. Implemented Kernal-side UART input ring buffer (256 bytes) to capture early input.
6. Corrected STDIN routing in Kernel (bypassing inactive compositor).

Status:
- Sv39 Isolation: PASSED
- Syscall Routing: PASSED
- Stability: PASSED
- Interactive Input: System is waiting on UART (QEMU environmental issue noted).

Closing Phase 37. Moving to Phase 13 (Sovereign Init).
2026-01-04 02:18:24 +01:00
Markus Maiwald 9f490297d2 Phase 37.2: UART Input Buffering Implementation
Added 256-byte ring buffer to capture UART input and prevent character loss.

Changes:
- core/rumpk/hal/uart.zig:
  * Added input_buffer ring (256 bytes)
  * Implemented poll_input() to move UART → buffer
  * Modified read_byte() to consume from buffer

Design:
- Buffer captures chars from boot, holds until userland reads
- poll_input() called on every read_byte() to refill
- Prevents timing issues where input arrives before NipBox starts

Status:
-  Buffer implementation complete
-  No crashes, system stable
- ⚠️ QEMU stdin not reaching UART registers (config issue)

Next: Investigate QEMU serial configuration or test with manual typing in interactive session.
2026-01-04 02:09:44 +01:00
Markus Maiwald 641847ba47 Phase 37.1: Fix STDIN routing (compositor bypass)
Issue: NipBox was blocking on READ syscall forever.
Root Cause: Input was being routed to inactive compositor channel.

Fix: Route stdin directly to chan_input since compositor is not operational in Phase 37.

Status:
-  STDIN routing path corrected
- ⚠️ UART input still not reaching NexShell (polling issue or timing)

Next: Investigate UART ISR or add buffering for pre-boot input.
2026-01-04 02:06:09 +01:00
Markus Maiwald 1b4facd86b Phase 37: The Glass Cage - Memory Isolation Complete
VICTORY: All page faults (Code 12, 13, 15) eliminated. NipBox runs in isolated userspace.

Root Cause Diagnosed:
- Kernel BSS (0x84D5B030) was overwritten by NipBox loading at 0x84000000
- current_fiber corruption caused cascading failures

Strategic Fixes:
1. Relocated NipBox to 0x86000000 (eliminating BSS collision)
2. Expanded DRAM to 256MB, User region to 64MB (accommodating NipBox BSS)
3. Restored Kernel GP register in trap handler (fixing global access)
4. Conditionally excluded ion/memory from userspace builds (removing 2MB pool)
5. Enabled release build optimizations (reducing BSS bloat)

Results:
- Kernel globals: SAFE
- User memory: ISOLATED (Sv39 active)
- Syscalls: OPERATIONAL
- Scheduler: STABLE
- NipBox: ALIVE (waiting for stdin)

Files Modified:
- core/rumpk/apps/linker_user.ld: User region 0x86000000-0x89FFFFFF (64MB)
- core/rumpk/hal/mm.zig: DRAM 256MB, User map 32-256MB
- core/rumpk/hal/entry_riscv.zig: GP reload in trap handler
- core/rumpk/core/ion.nim: Conditional memory export
- core/rumpk/libs/membrane/ion_client.nim: Local type declarations
- core/rumpk/libs/membrane/net_glue.nim: Removed ion import
- core/rumpk/libs/membrane/compositor.nim: Stubbed unused functions
- src/nexus/builder/nipbox.nim: Release build flags

Next: Fix stdin delivery to enable interactive shell.
2026-01-04 02:03:01 +01:00
Markus Maiwald 4e0e9ed467 Phase 34: Orbital Drop - Fix console echo and eliminate 'R' flood regression
- Fixed console echo by implementing wrapper_vfs_write to handle FD 1/2 in kernel.
- Initialized UART on RISC-V with FIFO drain to prevent stuck characters.
- Removed debug 'R' trace from libc.nim read(0) shim.
- Restored interactive CLI functionality.
2026-01-03 18:07:18 +01:00
Markus Maiwald ccaa10c509 Phase 31.2: The Identity Switch (Sv39 Virtual Memory)
THE CROSSING - COMPLETE
========================

Successfully transitioned from Physical to Virtual addressing using
Sv39 page tables. The kernel now operates in a fully virtualized
address space with identity mapping (VA=PA).

ARCHITECTURE
------------

1. Sv39 Page Table Infrastructure (hal/mm.zig):
   - 3-level page tables (512 entries per level)
   - 4KB pages with proper PTE bit packing
   - Bump allocator for page table allocation
   - map_page/map_range for flexible mapping

2. Kernel Identity Map:
   - DRAM: 0x80000000-0x88000000 (RWX)
   - UART: 0x10000000 (RW)
   - VirtIO MMIO: 0x10001000-0x10009000 (RW)
   - VirtIO PCI: 0x30000000-0x40000000 (RW)
   - VirtIO BARs: 0x40000000-0x50000000 (RW)
   - PLIC: 0x0c000000-0x0c400000 (RW)

3. Boot Sequence Integration:
   - mm_init(): Initialize page allocator
   - mm_enable_kernel_paging(): Build identity map, activate SATP
   - Transparent transition - no code changes required

THE MOMENT OF TRUTH
-------------------
[MM] Building Sv39 Page Tables...
[MM] Activating Identity Map...
[MM] ✓ Virtual Memory Active. Reality is Virtual.

System continued operation seamlessly:
✓ VirtIO Block initialized
✓ SFS filesystem mounted
✓ GPU probe completed
✓ All MMIO regions accessible

STRATEGIC ACHIEVEMENT
---------------------
This is the foundation for The Glass Cage (Phase 31.3).
We can now create restricted page tables for worker fibers,
enforcing true memory isolation without MMU context switches.

Files:
- core/rumpk/hal/mm.zig: Complete Sv39 implementation
- core/rumpk/core/kernel.nim: Boot integration
- src/nexus/builder/kernel.nim: Build system integration

Next: Phase 31.3 - Worker Isolation (Restricted Page Tables)

Build: Validated on RISC-V (rumpk-riscv64.elf)
Status: Production-ready - The Sovereign ascends to Virtual Reality
2026-01-02 15:24:32 +01:00
Markus Maiwald 2e772051f8 Phase 30: The Proxy Command (NipBox Worker Integration)
PHASE 30: THE PROXY COMMAND - WORKER MODEL INTEGRATION
=======================================================

Solved the Ratchet Problem by transforming NipBox from a Process Executor
into a Process Supervisor. Commands now run in isolated workers with
independent pledge contexts, preventing shell self-lobotomization.

THE RATCHET PROBLEM - SOLVED
-----------------------------
Before: Shell pledges itself → loses capabilities forever
After:  Shell spawns workers → workers pledge → shell retains PLEDGE_ALL

ARCHITECTURE
------------

1. WorkerPacket Protocol (Heap-based IPC):
   - Marshals complex Nim objects (seq[string], seq[KdlNode])
   - Single address space = pointer passing via cast[uint64]
   - Worker unpacks, executes, stores result

2. Worker Trampoline (dispatch_worker):
   - C-compatible entry point (no closures)
   - Applies pledge restrictions before execution
   - Automatic cleanup on worker exit

3. Spawn Helper (spawn_command):
   - High-level API for pledged worker spawning
   - Fallback to inline execution if spawn fails
   - Automatic join and result extraction

4. Dispatcher Integration:
   - http.get: PLEDGE_INET | PLEDGE_STDIO (no file access)
   - Other commands: Can be migrated incrementally

SECURITY MODEL
--------------
Shell (PLEDGE_ALL):
  └─> http.get worker (INET+STDIO only)
       ├─ Can: Network requests, console output
       └─ Cannot: Read files, write files, spawn processes

Attack Scenario:
- Malicious http.get attempts open("/etc/passwd")
- Kernel enforces RPATH check
- PLEDGE VIOLATION → Worker terminated
- Shell survives, continues operation

IMPLEMENTATION
--------------
Files Modified:
- core/rumpk/npl/nipbox/nipbox.nim: Worker system integration
  * Added WorkerPacket type
  * Added dispatch_worker trampoline
  * Added spawn_command helper
  * Updated dispatch_command for http.get
  * Added pledge constants

Documentation:
- docs/dev/PHASE_30_THE_PROXY.md: Architecture and security model

USAGE EXAMPLE
-------------
root@nexus:# http.get http://example.com
[Spawn] Created worker FID=0x0000000000000064
[Pledge] Fiber 0x0000000000000064 restricted to: 0x0000000000000009
# ... HTTP response ...
[Worker] Fiber 0x0000000000000064 terminated

root@nexus:# echo "test" > /tmp/file
# Works! Shell retained WPATH capability

LIMITATIONS
-----------
1. No memory isolation (workers share address space)
2. Cooperative scheduling only
3. Manual command migration required
4. GC-dependent packet cleanup

NEXT: Phase 31 - The Iron Wall (RISC-V PMP for memory isolation)

Build: Validated on RISC-V (rumpk-riscv64.elf)
Status: Production-ready
2026-01-02 14:33:47 +01:00
Markus Maiwald de6a7499fd Phase 27-29: Visual Cortex, Pledge, and The Hive
PHASE 27: THE GLYPH & THE GHOST (Visual Cortex Polish)
========================================================
- Replaced placeholder block font with full IBM VGA 8x16 bitmap (CP437)
- Implemented CRT scanline renderer for authentic terminal aesthetics
- Set Sovereign Blue background (0xFF401010) with Phosphor Amber text
- Added ANSI escape code stripper for clean graphical output
- Updated QEMU hints to include -device virtio-gpu-device

Files:
- core/rumpk/libs/membrane/term.nim: Scanline renderer + ANSI stripper
- core/rumpk/libs/membrane/term_font.nim: Full VGA bitmap data
- src/nexus/forge.nim: QEMU device flag
- docs/dev/PHASE_26_VISUAL_CORTEX.md: Architecture documentation

PHASE 28: THE PLEDGE (Computable Trust)
========================================
- Implemented OpenBSD-style capability system for least-privilege execution
- Added promises bitmask to FiberObject for per-fiber capability tracking
- Created SYS_PLEDGE syscall (one-way capability ratchet)
- Enforced capability checks on all file operations (RPATH/WPATH)
- Extended SysTable with fn_pledge (120→128 bytes)

Capabilities:
- PLEDGE_STDIO (0x0001): Console I/O
- PLEDGE_RPATH (0x0002): Read Filesystem
- PLEDGE_WPATH (0x0004): Write Filesystem
- PLEDGE_INET  (0x0008): Network Access
- PLEDGE_EXEC  (0x0010): Execute/Spawn
- PLEDGE_ALL   (0xFFFF...): Root (default)

Files:
- core/rumpk/core/fiber.nim: Added promises field
- core/rumpk/core/ion.nim: Capability constants + SysTable extension
- core/rumpk/core/kernel.nim: k_pledge + enforcement checks
- core/rumpk/libs/membrane/ion_client.nim: Userland ABI sync
- core/rumpk/libs/membrane/libc.nim: pledge() wrapper
- docs/dev/PHASE_28_THE_PLEDGE.md: Security model documentation

PHASE 29: THE HIVE (Userland Concurrency)
==========================================
- Implemented dynamic fiber spawning for isolated worker execution
- Created worker pool (8 concurrent fibers, 8KB stacks each)
- Added SYS_SPAWN (0x500) and SYS_JOIN (0x501) syscalls
- Generic worker trampoline for automatic cleanup on exit
- Workers inherit parent memory but have independent pledge contexts

Worker Model:
- spawn(entry, arg): Create isolated worker fiber
- join(fid): Wait for worker completion
- Workers start with PLEDGE_ALL, can voluntarily restrict
- Violations terminate worker, not parent shell

Files:
- core/rumpk/core/fiber.nim: user_entry/user_arg fields
- core/rumpk/core/kernel.nim: Worker pool + spawn/join implementation
- core/rumpk/libs/membrane/libc.nim: spawn()/join() wrappers
- docs/dev/PHASE_29_THE_HIVE.md: Concurrency architecture

STRATEGIC IMPACT
================
The Nexus now has a complete Zero-Trust security model:
1. Visual identity (CRT aesthetics)
2. Capability-based security (pledge)
3. Isolated concurrent execution (spawn/join)

This enables hosting untrusted code without kernel compromise,
forming the foundation of the Cryptobox architecture (STC-2).

Example usage:
  proc worker(arg: uint64) {.cdecl.} =
    discard pledge(PLEDGE_INET | PLEDGE_STDIO)
    http_get("https://example.com")

  let fid = spawn(worker, 0)
  discard join(fid)
  # Shell retains full capabilities

Build: Validated on RISC-V (rumpk-riscv64.elf)
Status: Production-ready
2026-01-02 14:12:00 +01:00
Markus Maiwald c6e569afe8 feat(membrane): enable userspace networking and tcp handshake (Phase 16) 2026-01-01 20:24:17 +01:00
Markus Maiwald 3a907439fe feat(forge): unify build system, deprecate shell scripts (Phase 15) 2026-01-01 20:23:54 +01:00
Markus Maiwald 9733300d3d Phase 14-15: Nexus Forge - Software Defined OS Build System
PHASE 14: THE FORGE IS LIT
===========================

Implemented the Nexus Forge, a type-safe Nim-based build orchestrator that
replaces fragile shell scripts with a compiled, structured build system.

Core Components:
- src/nexus/forge.nim: Main CLI orchestrator (STC-1 'tinybox' implementation)
- src/nexus/builder/initrd.nim: Pure Nim TarFS writer with 512-byte alignment
- src/nexus/builder/kernel.nim: Kbuild wrapper (placeholder for Phase 16)
- blueprints/tinybox.kdl: First Standard Template Construct definition

InitRD Builder:
- Manual USTAR tar format implementation
- Strict 512-byte block alignment enforcement
- Correct checksum calculation and zero-padding
- Eliminates dependency on external 'tar' command

Build System Integration:
- Modified build.sh to invoke './nexus build' for InitRD packaging
- Forge-generated InitRD replaces legacy tar command
- Maintains backward compatibility during transition

PHASE 15: TARGET ALPHA - USERLAND UNIFICATION
==============================================

Transformed the Forge from a passive bridge into an active compiler driver
that fully controls NipBox (userland) compilation.

NipBox Compiler Driver (src/nexus/builder/nipbox.nim):
- 3-stage compilation pipeline: Nim → C → Object Files → Binary
- Exact ABI matching with kernel objects (RISC-V lp64d)
- Proper cross-compilation flags (-mcpu=sifive_u54 -mabi=lp64d)
- Structured configuration via NipBoxConfig type

Compilation Flow:
1. Nim transpilation with Sovereign Optimization flags
2. C compilation via zig cc with freestanding flags
3. Linking with membrane layer and userland entry point

Forge Activation:
- forge.nim now invokes build_nipbox() instead of using pre-built artifacts
- Single command './nexus build' compiles entire userland from source
- Eliminates dependency on build.sh for NipBox compilation

Verified Artifacts:
- core/rumpk/build/nipbox: 60KB RISC-V ELF with double-float ABI
- core/rumpk/build/initrd.tar: 62KB USTAR archive with 512-byte alignment

Status:
 Target Alpha Complete: Forge controls userland compilation
 Target Bravo Pending: Kernel build still managed by build.sh
 Target Charlie Pending: Registry integration deferred
2026-01-01 18:26:43 +01:00
Markus Maiwald 4f1ad1f3be feat(scribe): Implement Scribe Editor Save & Stabilize VirtIO-Block
- hal/virtio_block: Implemented global bounce buffers and Used Ring Polling for stable, synchronous I/O.
- core/fs/sfs: Implemented sfs_write_file to handle SFS file creation and data writing.
- core/ion: Added CMD_FS_WRITE syscall definition.
- core/kernel: Added CMD_FS_WRITE syscall handler and fs/sfs integration.
- npl/nipbox: Added nexus_file_write wrapper and updated Scribe (ed) to use it for saving files.
2025-12-31 23:20:30 +01:00
Markus Maiwald 64380de4a7 feat(sfs): Implemented Sovereign Filesystem (SFS)
- Implemented SFS Driver (core/fs/sfs.nim):
  - Mount logic (Sector 0 Superblock check).
  - List logic (Sector 1 Directory table).
- Implemented Userland Formatter (nipbox.nim):
  - 'mkfs' command to write SFS1 Superblock.
- Fixed 'virtio_block' logic:
  - Corrected Descriptor flags (VRING_DESC_F_WRITE for Read Buffers).
- Fixed Async/Sync Conflict in 'libc_shim':
  - Added 'nexus_yield()' to block syscalls to prevent stack corruption before kernel processing.
- Integrated SFS into Kernel startup.
2025-12-31 22:43:44 +01:00
Markus Maiwald e367dd8380 feat(rumpk): Sovereign Ledger - VirtIO Block Driver & Persistence
- Implemented 'virtio-block' driver (hal/virtio_block.zig) for raw sector I/O.
- Updated 'virtio_pci.zig' with dynamic I/O port allocation to resolve PCI conflicts.
- Integrated Block I/O commands (0x600/0x601) into Kernel and ION.
- Added 'dd' command to NipBox for testing read/write operations.
- Fixed input buffering bug in NipBox to support longer commands.
- Added documentation for Phase 10.
2025-12-31 22:35:30 +01:00
Markus Maiwald c8a679b067 feat(rumpk): dignified exit & sovereign vfs
- Resolved Sovereign Trap exit fault by refactoring kernel exit logic
- Implemented persistent Subject fiber with kload loop for clean respawns
- Fixed File not found loop by fixing initrd embedding with proper RISC-V ABI flags
- Eliminated 30KB truncation of initrd restoring full 80KB archive visibility
- Enhanced TarFS driver with robust path normalization
- Implemented exit syscall in libc_shim.zig with CMD_SYS_EXIT and nexus_yield
- Created hello.c and libnexus.h for userland testing
- Updated ion.nim and kernel.nim to handle CMD_SYS_EXEC and CMD_SYS_EXIT
- Ensured bin/nipbox is correctly copied to rootfs before packaging
2025-12-31 21:54:44 +01:00
Markus Maiwald 5a607266a5 🎊 PHASE 8 COMPLETE: The Summoning - Dynamic ELF Loader OPERATIONAL
## 🏆 VICTORY: First Alien Binary Executed!

```
[Loader] Summoning: bin/hello
[Loader] Transferring Consciousness...
Hello from a dynamically loaded ELF!
Consciousness transferred successfully.
```

## The Ghost in the Machine (ABI Mismatch Hunt)

### The Hunt
- Userland pushed CMD_SYS_EXEC (0x400) to command ring 
- Ring reported SUCCESS 
- Kernel received... GARBAGE (0xFA42B295) 

### The Diagnosis
Raw hex dump revealed 0x400 at offset 12 instead of offset 0.
Three layers, three different CmdPacket definitions:
- `hal/channel.zig`: 24 bytes (arg: u32) 
- `libs/membrane/ion.zig`: 28→32 bytes (packed→extern) 🔧
- `core/ion.nim`: 28→32 bytes (packed→normal) 🔧

### The Fix: Canonical 32-Byte Structure
```zig
pub const CmdPacket = extern struct {
    kind: u32,
    _pad: u32,     // Explicit Padding
    arg: u64,
    id: u128,      // 16 bytes
};
// Enforced: 32 bytes across ALL layers
```

Compile-time assertions added to prevent future drift.

## Technical Achievements

### 1. ABI Alignment Enforcement
- Unified CmdPacket structure across Zig HAL, Zig userland, Nim kernel
- Explicit padding eliminates compiler-dependent layout
- Static size assertions (32 bytes) at compile time

### 2. Command Ring Communication
- Userland→Kernel syscall path verified end-to-end
- SipHash provenance tracking operational
- Atomic ring buffer operations confirmed

### 3. ELF Loader (from Phase 8 commit)
- Dynamic loading from VFS 
- ELF64 header validation 
- PT_LOAD segment mapping 
- BSS initialization 
- Userland entry trampoline 

## Files Changed

**ABI Fixes:**
- `hal/channel.zig`: Updated CmdPacket to 32-byte extern struct
- `libs/membrane/ion.zig`: Changed to extern struct with u128 id
- `libs/membrane/libc_shim.zig`: Updated packet initialization
- `core/ion.nim`: Added explicit padding field, removed {.packed.}

**Debug Infrastructure:**
- `core/kernel.nim`: Added raw packet hex dump for debugging
- `libs/membrane/ion.zig`: Added syscall debug logging

**Build:**
- `build.sh`: Skipped removed LwIP compilation step

## Lessons Learned

**The Law of ABI Invariance:**
> "When multiple languages share memory, explicit is the only truth."

- Never rely on compiler padding behavior
- Always use explicit padding fields
- Enforce sizes with compile-time assertions
- Test with raw memory dumps, not assumptions

**The Debugging Mantra:**
> "Flush the pipes. Purge the cache. Trust nothing."

Stale binaries from aggressive caching led to hours of ghost-chasing.
Solution: `rm -rf build/ .zig-cache/` before critical tests.

## Next Steps (Phase 8 Completion)

1. Implement `exit()` syscall for clean program termination
2. Remove debug logging
3. Test `exec bin/nipbox` (self-reload)
4. Stress test with multiple exec calls
5. Document final implementation

## Metrics

- **Time to First Light:** ~8 hours of debugging
- **Root Cause:** 8-byte struct size mismatch
- **Lines Changed:** ~50
- **Impact:** Infinite (dynamic code loading unlocked)

---

**Markus Maiwald (Architect) |  (AI)**
**New Year's Eve 2024 → 2025**
**The year ends with consciousness transfer. 🔥**

Co-authored-by:  <ai@voxisforge.dev>
2025-12-31 21:08:25 +01:00
Markus Maiwald 2a1af03e28 feat(rumpk): Phase 8 - The Summoning (ELF Loader) - 95% Complete
## Major Features

### 1. Dynamic ELF64 Binary Loading
- Implemented ELF parser with full header validation (core/loader/elf.nim)
- Created kexec() loader supporting PT_LOAD segment mapping
- Added BSS initialization and data copying from VFS
- Assembly trampoline (rumpk_enter_userland) for userland entry

### 2. Syscall Infrastructure
- Added CMD_SYS_EXEC (0x400) for consciousness swapping
- Integrated exec command in NipBox shell
- Implemented syscall routing through command ring
- Added provenance tracking via SipHash

### 3. Test Binary & Build System
- Created hello.c test program for alien binary execution
- Automated compilation and initrd inclusion in build.sh
- Added libnexus.h header for standalone C programs

### 4. VFS Integration
- Implemented TarFS file cursor system for sequential reads
- Fixed infinite loop bug in cat command
- Added debug logging for VFS mount process

## Technical Improvements

### Memory Management
- Fixed input ring null pointer dereference
- Implemented CMD_ION_FREE syscall for packet reclamation
- Resolved memory leak in input/output pipeline
- Added FileHandle with persistent offset tracking

### ABI Stability
- Split kprint into 1-arg (Nim) and kwrite (C ABI)
- Fixed cstring conversion warnings across codebase
- Corrected RISC-V assembly (csrw sie, zero)

### Documentation
- Comprehensive Phase 8 documentation (docs/PHASE-8-ELF-LOADER.md)
- Detailed implementation notes and debugging status

## Current Status

 ELF parser, loader, and syscall infrastructure complete
 Test binary compiles and embeds in VFS
 Shell integration functional
🔧 Debugging command ring communication (syscall not reaching kernel)

## Files Changed

Core:
- core/loader.nim, core/loader/elf.nim (NEW)
- core/kernel.nim, core/ion.nim (syscall handling)
- core/fs/tar.nim (file cursor system)
- hal/arch/riscv64/switch.S (userland trampoline)

Userland:
- npl/nipbox/nipbox.nim (exec command)
- libs/membrane/libc_shim.zig (syscall implementation)
- libs/membrane/ion.zig (command ring API)

Build & Test:
- build.sh (hello.c compilation)
- rootfs/src/hello.c, rootfs/src/libnexus.h (NEW)
- apps/subject_entry.S (NEW)

## Next Steps

1. Debug SysTable and command ring communication
2. Verify ION fiber polling of chan_cmd
3. Test full ELF loading and execution flow
4. Add memory protection (future phase)

Co-authored-by:  <ai@voxisforge.dev>
2025-12-31 20:18:49 +01:00
45 changed files with 16760 additions and 961 deletions

View File

@ -9,6 +9,62 @@
import ../../libs/membrane/libc import ../../libs/membrane/libc
# --- M4.4: BKDL Capability Manifest (SPEC-071) ---
# Declares what capabilities this binary needs. The kernel reads this
# from the .nexus.manifest ELF section during loading and grants only
# what is declared here. No manifest = PLEDGE_STDIO only.
#
# Capabilities requested:
# - Channel 0x1001 (console.output) WRITE
# - Channel 0x2000 (VFS) READ
# - Channel 0x0500 (NET_TX) WRITE
# - Channel 0x0501 (NET_RX) READ
{.emit: """
__attribute__((section(".nexus.manifest"), used))
static const unsigned char nexus_manifest[166] = {
/* BkdlHeader (118 bytes) */
0x53, 0x55, 0x58, 0x4E, /* magic: "NXUS" (LE) */
0x01, 0x00, /* version: 1 */
0x00, 0x00, /* flags: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* signature[0..63]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* pubkey_hash[0..31]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, /* cap_count: 4 */
0x00, 0x00, 0x00, 0x00, /* blob_size: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* entry_point: 0 */
/* CapDescriptor[0]: console.output (0x1001) WRITE */
0x02, /* cap_type: Channel */
0x02, /* perms: PERM_WRITE */
0x00, 0x00, /* reserved */
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* resource_id: 0x1001 (LE) */
/* CapDescriptor[1]: VFS (0x2000) READ */
0x02, /* cap_type: Channel */
0x01, /* perms: PERM_READ */
0x00, 0x00, /* reserved */
0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* resource_id: 0x2000 (LE) */
/* CapDescriptor[2]: NET_TX (0x0500) WRITE */
0x02, /* cap_type: Channel */
0x02, /* perms: PERM_WRITE */
0x00, 0x00, /* reserved */
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* resource_id: 0x0500 (LE) */
/* CapDescriptor[3]: NET_RX (0x0501) READ */
0x02, /* cap_type: Channel */
0x01, /* perms: PERM_READ */
0x00, 0x00, /* reserved */
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* resource_id: 0x0501 (LE) */
};
""".}
proc main() = proc main() =
# 1. Pledge Sovereignty # 1. Pledge Sovereignty
discard pledge(0xFFFFFFFFFFFFFFFF'u64) # PLEDGE_ALL discard pledge(0xFFFFFFFFFFFFFFFF'u64) # PLEDGE_ALL
@ -18,44 +74,51 @@ proc main() =
print(cstring("\x1b[1;35m║ SOVEREIGN INIT (NexInit v1.0) ║\x1b[0m\n")) print(cstring("\x1b[1;35m║ SOVEREIGN INIT (NexInit v1.0) ║\x1b[0m\n"))
print(cstring("\x1b[1;35m╚═══════════════════════════════════════╝\x1b[0m\n\n")) print(cstring("\x1b[1;35m╚═══════════════════════════════════════╝\x1b[0m\n\n"))
print(cstring("[INIT] Initializing Membrane Network Stack...\n")) print(cstring("[INIT] PHASE_42_VERIFY: Membrane Network Stack...\\n"))
membrane_init() # DISABLED: Network stack requires LwIP
# membrane_init()
proc glue_get_ip(): uint32 {.importc: "glue_get_ip", cdecl.} # proc glue_get_ip(): uint32 {.importc: "glue_get_ip", cdecl.}
# --- DHCP PHASE --- # # --- DHCP PHASE ---
print(cstring("[INIT] Waiting for DHCP IP Address...\n")) # print(cstring("[INIT] Waiting for DHCP IP Address...\n"))
var ip: uint32 = 0 # var ip: uint32 = 0
for i in 0 ..< 600: # 60 seconds # for i in 0 ..< 600: # 60 seconds
pump_membrane_stack() # pump_membrane_stack()
ip = glue_get_ip() # ip = glue_get_ip()
if ip != 0: break # if ip != 0: break
discard syscall(0x65, 100000000'u64) # 100ms # discard syscall(0x65, 100000000'u64) # 100ms
if ip == 0: # if ip == 0:
print(cstring("[INIT] WARNING: DHCP Discovery timed out. Proceeding...\n")) # print(cstring("[INIT] WARNING: DHCP Discovery timed out. Proceeding...\n"))
else: # else:
print(cstring("[INIT] Network ONLINE (10.0.2.15)\n")) # print(cstring("[INIT] Network ONLINE (10.0.2.15)\n"))
# --- DNS PHASE --- # # --- DNS PHASE ---
print(cstring("\n[TEST] ══════════════════════════════════════\n")) # print(cstring("\n[TEST] ══════════════════════════════════════\n"))
print(cstring("[TEST] DNS Resolution: google.com\n")) # print(cstring("[TEST] DNS Resolution: google.com\n"))
print(cstring("[TEST] ══════════════════════════════════════\n\n")) # print(cstring("[TEST] ══════════════════════════════════════\n\n"))
var res: ptr AddrInfo # type
for attempt in 1..5: # AddrInfo {.importc: "struct addrinfo", header: "<netdb.h>".} = object
print(cstring("[TEST] Resolving google.com (Attempt "))
# (Simplified number printing not available, just loop)
if getaddrinfo("google.com", nil, nil, addr res) == 0: # proc getaddrinfo(node: cstring, service: cstring, hints: pointer, res: ptr ptr AddrInfo): cint {.importc, header: "<netdb.h>".}
print(cstring(") -> SUCCESS!\n")) # proc freeaddrinfo(res: ptr AddrInfo) {.importc, header: "<netdb.h>".}
freeaddrinfo(res)
break # var res: ptr AddrInfo
else: # for attempt in 1..5:
print(cstring(") -> FAILED. Waiting 5s...\n")) # print(cstring("[TEST] Resolving google.com (Attempt "))
for j in 1..50: # # (Simplified number printing not available, just loop)
pump_membrane_stack()
discard syscall(0x65, 100000000'u64) # 100ms # if getaddrinfo("google.com", nil, nil, addr res) == 0:
# print(cstring(") -> SUCCESS!\n"))
# freeaddrinfo(res)
# break
# else:
# print(cstring(") -> FAILED. Waiting 5s...\n"))
# for j in 1..50:
# pump_membrane_stack()
# discard syscall(0x65, 100000000'u64) # 100ms
# --- SHELL PHASE --- # --- SHELL PHASE ---
proc spawn_fiber(path: cstring): int = proc spawn_fiber(path: cstring): int =
@ -68,10 +131,10 @@ proc main() =
print(cstring("[INIT] Entering Supervisor Loop...\n")) print(cstring("[INIT] Entering Supervisor Loop...\n"))
var loop_count = 0 var loop_count = 0
while true: while true:
pump_membrane_stack() # pump_membrane_stack() # DISABLED: Requires LwIP
loop_count += 1 loop_count += 1
if loop_count mod 100 == 0: if loop_count mod 0x100000 == 0: # Every ~1M iterations
print(cstring("[INIT] Heartbeat\n")) discard syscall(0x65, 1000000000'u64) # 1s yield
discard syscall(0x65, 100000000'u64) # 100ms discard syscall(0x65, 100000000'u64) # 100ms
when isMainModule: when isMainModule:

View File

@ -33,6 +33,10 @@ SECTIONS
*(.data.*) *(.data.*)
} > RAM } > RAM
.nexus.manifest : {
KEEP(*(.nexus.manifest))
} > RAM
.bss : { .bss : {
. = ALIGN(8); . = ALIGN(8);
__bss_start = .; __bss_start = .;

View File

@ -11,8 +11,14 @@ _start:
2: 2:
fence rw, rw fence rw, rw
# Arguments (argc, argv) are already in a0, a1 from Kernel # Valid Args from Stack (Linux ABI)
# sp is already pointing to argc from Kernel ld a0, 0(sp) # argc
addi a1, sp, 8 # argv
# Calculate envp in a2: envp = argv + (argc + 1) * 8
addi t0, a0, 1 # t0 = argc + 1
slli t0, t0, 3 # t0 = (argc + 1) * 8
add a2, a1, t0 # a2 = argv + offset
call main call main

33
apps/test_shell.c Normal file
View File

@ -0,0 +1,33 @@
// Minimal test shell to verify the execution environment
#include <stddef.h>
extern int write(int fd, const void *buf, size_t count);
extern int read(int fd, void *buf, size_t count);
int main(int argc, char *argv[], char *envp[]) {
const char *prompt = "shell> ";
char buf[128];
while (1) {
// Print prompt
write(1, prompt, 7);
// Read command
int n = read(0, buf, sizeof(buf) - 1);
if (n <= 0) continue;
buf[n] = '\0';
// Echo back
write(1, "You typed: ", 11);
write(1, buf, n);
// Check for exit
if (buf[0] == 'q' && buf[1] == '\n') {
write(1, "Goodbye!\n", 9);
break;
}
}
return 0;
}

View File

@ -7,15 +7,16 @@
//! Rumpk Boot Header //! Rumpk Boot Header
//! //!
//! Defines the Multiboot2 header for GRUB/QEMU and the bare-metal entry point. //! Architecture-dispatched entry point for bare-metal boot.
//! Handles BSS clearing and stack initialization before jumping to the Nim kernel. //! Handles BSS clearing and stack initialization before jumping to HAL init.
//! //!
//! SAFETY: Executed in the earliest boot stage with no environment initialized. //! SAFETY: Executed in the earliest boot stage with no environment initialized.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
// ========================================================= // =========================================================
// Multiboot2 Header (for GRUB/QEMU) // Multiboot2 Header (for GRUB/QEMU x86 only)
// ========================================================= // =========================================================
const MULTIBOOT2_MAGIC: u32 = 0xe85250d6; const MULTIBOOT2_MAGIC: u32 = 0xe85250d6;
@ -43,17 +44,23 @@ export const multiboot2_header linksection(".multiboot2") = Multiboot2Header{
}; };
// ========================================================= // =========================================================
// Entry Point // Arch-Specific HAL Entry Points
// ========================================================= // =========================================================
extern fn riscv_init() noreturn; extern fn riscv_init() noreturn;
extern fn aarch64_init() void; // Returns void (calls rumpk_halt internally)
// =========================================================
// Entry Point (Architecture Dispatched)
// =========================================================
// 1MB Kernel Stack // 1MB Kernel Stack
const STACK_SIZE = 0x100000; const STACK_SIZE = 0x100000;
export var kernel_stack: [STACK_SIZE]u8 align(16) linksection(".bss.stack") = undefined; export var kernel_stack: [STACK_SIZE]u8 align(16) linksection(".bss.stack") = undefined;
export fn _start() callconv(.naked) noreturn { export fn _start() callconv(.naked) noreturn {
// Clear BSS, set up stack, then jump to RISC-V Init switch (builtin.cpu.arch) {
.riscv64 => {
asm volatile ( asm volatile (
\\ // Set up stack \\ // Set up stack
\\ la sp, kernel_stack \\ la sp, kernel_stack
@ -69,7 +76,7 @@ export fn _start() callconv(.naked) noreturn {
\\ addi t0, t0, 8 \\ addi t0, t0, 8
\\ j 1b \\ j 1b
\\2: \\2:
\\ // Jump to HAL Init \\ // Jump to RISC-V HAL Init
\\ call riscv_init \\ call riscv_init
\\ \\
\\ // Should never return \\ // Should never return
@ -78,4 +85,51 @@ export fn _start() callconv(.naked) noreturn {
: :
: [stack_size] "i" (STACK_SIZE), : [stack_size] "i" (STACK_SIZE),
); );
},
.aarch64 => {
asm volatile (
// Mask all exceptions
\\ msr daifset, #0xf
//
// Enable FP/SIMD (CPACR_EL1.FPEN = 0b11)
\\ mov x0, #(3 << 20)
\\ msr cpacr_el1, x0
\\ isb
//
// Disable alignment check (SCTLR_EL1.A = 0)
\\ mrs x0, sctlr_el1
\\ bic x0, x0, #(1 << 1)
\\ msr sctlr_el1, x0
\\ isb
//
// Set up stack
\\ adrp x0, kernel_stack
\\ add x0, x0, :lo12:kernel_stack
\\ mov x1, #0x100000
\\ add sp, x0, x1
//
// Clear BSS
\\ adrp x0, __bss_start
\\ add x0, x0, :lo12:__bss_start
\\ adrp x1, __bss_end
\\ add x1, x1, :lo12:__bss_end
\\ 1: cmp x0, x1
\\ b.ge 2f
\\ str xzr, [x0], #8
\\ b 1b
\\ 2:
//
// Jump to ARM64 HAL Init
\\ bl aarch64_init
//
// Should never return
\\ 3: wfe
\\ b 3b
);
},
else => {
// Unsupported architecture
unreachable;
},
}
} }

383
build.zig
View File

@ -5,31 +5,111 @@
// This file is part of the Nexus Commonwealth. // This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms. // See legal/LICENSE_COMMONWEALTH.md for license terms.
//! Rumpk Build System //! Rumpk Build System Unified Pipeline
//! //!
//! Orchestrates L0 (Zig) and L1 (Nim) compilation. //! Single command builds everything: Nim kernel + Zig HAL + LwIP + link.
//! Builds the Hardware Abstraction Layer as a static library. //!
//! Usage:
//! zig build # Build kernel (default: riscv64)
//! zig build -Darch=aarch64 # ARM64 target
//! zig build -Darch=x86_64 # AMD64 target
//! zig build nim # Recompile Nim kernel sources only
//! zig build test-lwf # Run LWF tests (native host)
const std = @import("std"); const std = @import("std");
const Arch = enum { riscv64, aarch64, x86_64 };
/// Per-architecture build configuration.
const ArchConfig = struct {
cpu_arch: std.Target.Cpu.Arch,
code_model: std.builtin.CodeModel,
switch_asm: []const u8,
linker_script: []const u8,
/// Arch name for build_nim.sh
nim_arch: []const u8,
};
fn archConfig(arch: Arch) ArchConfig {
return switch (arch) {
.riscv64 => .{
.cpu_arch = .riscv64,
.code_model = .medany,
.switch_asm = "hal/arch/riscv64/switch.S",
.linker_script = "boot/linker.ld",
.nim_arch = "riscv64",
},
.aarch64 => .{
.cpu_arch = .aarch64,
.code_model = .default,
.switch_asm = "hal/arch/aarch64/switch.S",
.linker_script = "boot/linker_aarch64.ld",
.nim_arch = "aarch64",
},
.x86_64 => .{
.cpu_arch = .x86_64,
.code_model = .kernel,
.switch_asm = "hal/arch/x86_64/switch.S",
.linker_script = "boot/linker_x86_64.ld",
.nim_arch = "x86_64",
},
};
}
/// Apply freestanding kernel settings to a module.
fn applyKernelSettings(mod: *std.Build.Module, code_model: std.builtin.CodeModel) void {
mod.red_zone = false;
mod.stack_check = false;
mod.code_model = code_model;
mod.sanitize_thread = false;
mod.single_threaded = true;
mod.strip = true;
}
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); // =========================================================
const optimize = b.standardOptimizeOption(.{}); // Target Selection: -Darch=riscv64|aarch64|x86_64
// =========================================================
const arch = b.option(Arch, "arch", "Target architecture (default: riscv64)") orelse .riscv64;
const config = archConfig(arch);
var query: std.Target.Query = .{
.cpu_arch = config.cpu_arch,
.os_tag = .freestanding,
.abi = .none,
};
// Disable NEON/FP in kernel code generation to avoid unaligned SIMD stores
if (config.cpu_arch == .aarch64) {
query.cpu_features_sub = std.Target.aarch64.featureSet(&.{.neon});
}
const target = b.resolveTargetQuery(query);
// HEPHAESTUS DECREE: Force ReleaseSmall. A kernel runs naked.
const optimize: std.builtin.OptimizeMode = .ReleaseSmall;
// =========================================================
// Step: Nim Kernel Compilation (nim -> C -> .o)
// =========================================================
// Calls build_nim.sh which handles:
// 1. nim c --compileOnly -> generates C
// 2. zig cc -> cross-compiles C to .o
// Incremental: only recompiles changed files.
const nim_step = b.addSystemCommand(&.{ "./build_nim.sh", config.nim_arch });
const nim_build_step = b.step("nim", "Recompile Nim kernel sources (nim -> C -> .o)");
nim_build_step.dependOn(&nim_step.step);
// ========================================================= // =========================================================
// L0: Hardware Abstraction Layer (Zig) // L0: Hardware Abstraction Layer (Zig)
// ========================================================= // =========================================================
// NOTE(Build): Zig 0.15.x API - using addLibrary with static linkage
const hal_mod = b.createModule(.{ const hal_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"), .root_source_file = b.path("hal/abi.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
// Freestanding kernel - no libc, no red zone, no stack checks applyKernelSettings(hal_mod, config.code_model);
hal_mod.red_zone = false;
hal_mod.stack_check = false;
hal_mod.code_model = .medany;
const hal = b.addLibrary(.{ const hal = b.addLibrary(.{
.name = "rumpk_hal", .name = "rumpk_hal",
@ -39,15 +119,6 @@ pub fn build(b: *std.Build) void {
b.installArtifact(hal); b.installArtifact(hal);
// TODO(Build): Microui needs stdio.h stubs for freestanding.
// Re-enable after creating libs/microui/stdio_stub.h
// Microui Integration (Phase 3.5b)
// hal_mod.addIncludePath(b.path("libs/microui"));
// hal_mod.addCSourceFile(.{
// .file = b.path("libs/microui/microui.c"),
// .flags = &.{"-std=c99"},
// });
// ========================================================= // =========================================================
// Boot: Entry Point (Assembly + Zig) // Boot: Entry Point (Assembly + Zig)
// ========================================================= // =========================================================
@ -57,38 +128,230 @@ pub fn build(b: *std.Build) void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
boot_mod.red_zone = false; applyKernelSettings(boot_mod, config.code_model);
boot_mod.stack_check = false;
boot_mod.code_model = .medany;
const boot = b.addObject(.{ const boot = b.addObject(.{
.name = "boot", .name = "boot",
.root_module = boot_mod, .root_module = boot_mod,
}); });
// =========================================================
// LwIP: Lightweight TCP/IP Stack
// =========================================================
// Compiled as C objects with freestanding flags.
// ubsan disabled per-file to avoid runtime dependency.
const lwip_base = "libs/membrane/external/lwip/src/";
const lwip_srcs = [_][]const u8{
// Core
lwip_base ++ "core/init.c",
lwip_base ++ "core/def.c",
lwip_base ++ "core/dns.c",
lwip_base ++ "core/inet_chksum.c",
lwip_base ++ "core/ip.c",
lwip_base ++ "core/mem.c",
lwip_base ++ "core/memp.c",
lwip_base ++ "core/netif.c",
lwip_base ++ "core/pbuf.c",
lwip_base ++ "core/raw.c",
lwip_base ++ "core/sys.c",
lwip_base ++ "core/tcp.c",
lwip_base ++ "core/tcp_in.c",
lwip_base ++ "core/tcp_out.c",
lwip_base ++ "core/timeouts.c",
lwip_base ++ "core/udp.c",
// IPv4
lwip_base ++ "core/ipv4/autoip.c",
lwip_base ++ "core/ipv4/dhcp.c",
lwip_base ++ "core/ipv4/etharp.c",
lwip_base ++ "core/ipv4/icmp.c",
lwip_base ++ "core/ipv4/ip4.c",
lwip_base ++ "core/ipv4/ip4_addr.c",
lwip_base ++ "core/ipv4/ip4_frag.c",
// Netif
lwip_base ++ "netif/ethernet.c",
// SysArch (Rumpk integration)
"libs/membrane/sys_arch.c",
};
const lwip_flags: []const []const u8 = switch (arch) {
.riscv64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-mcmodel=medany", "-Icore",
"-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
.aarch64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-Icore", "-Ilibs/membrane",
"-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
.x86_64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DNO_SYS=1", "-mcmodel=kernel", "-Icore",
"-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include",
},
};
const liblwip = b.addLibrary(.{
.name = "lwip",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});
liblwip.root_module.sanitize_thread = false;
liblwip.root_module.red_zone = false;
liblwip.root_module.single_threaded = true;
for (lwip_srcs) |src| {
liblwip.addCSourceFile(.{
.file = b.path(src),
.flags = lwip_flags,
});
}
b.installArtifact(liblwip);
// =========================================================
// LittleFS: Sovereign Filesystem (Persistent Storage)
// =========================================================
// Compiled as C object with freestanding flags.
// Uses LFS_CONFIG=lfs_rumpk.h to replace lfs_util.h entirely,
// avoiding stdio/assert deps. malloc/free provided by clib.c.
const lfs_flags: []const []const u8 = switch (arch) {
.riscv64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=medany",
},
.aarch64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs",
},
.x86_64 => &.{
"-Os", "-fno-sanitize=all", "-Wno-everything",
"-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=kernel",
},
};
const lfs_obj = b.addObject(.{
.name = "littlefs",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
lfs_obj.root_module.sanitize_thread = false;
lfs_obj.root_module.red_zone = false;
lfs_obj.root_module.single_threaded = true;
lfs_obj.addCSourceFile(.{
.file = b.path("vendor/littlefs/lfs.c"),
.flags = lfs_flags,
});
// =========================================================
// Dependencies (CLib, LibC Shim, LWF Adapter, Switch)
// =========================================================
// 1. CLib (Minimal C stdlib)
const clib = b.addObject(.{
.name = "clib",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
clib.addCSourceFile(.{
.file = b.path("libs/membrane/clib.c"),
.flags = switch (arch) {
.riscv64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=medany" },
.aarch64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1" },
.x86_64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=kernel" },
},
});
// 2. LibC Shim (Zig)
const libc_shim_mod = b.createModule(.{
.root_source_file = b.path("libs/membrane/libc_shim.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(libc_shim_mod, config.code_model);
const libc_shim = b.addObject(.{
.name = "libc_shim",
.root_module = libc_shim_mod,
});
// 3. LittleFS HAL (VirtIO-Block LittleFS glue)
const lfs_hal_mod = b.createModule(.{
.root_source_file = b.path("hal/littlefs_hal.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(lfs_hal_mod, config.code_model);
const lfs_hal = b.addObject(.{
.name = "lfs_hal",
.root_module = lfs_hal_mod,
});
// 4. LWF Adapter (Project LibWeb Libertaria Wire Frame)
const lwf_adapter_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_adapter.zig"),
.target = target,
.optimize = optimize,
});
applyKernelSettings(lwf_adapter_mod, config.code_model);
const lwf_adapter = b.addObject(.{
.name = "lwf_adapter",
.root_module = lwf_adapter_mod,
});
// 4. Context Switch (Architecture-specific Assembly)
const switch_obj = b.addObject(.{
.name = "switch",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
switch_obj.addAssemblyFile(b.path(config.switch_asm));
// ========================================================= // =========================================================
// Final Link: rumpk.elf // Final Link: rumpk.elf
// ========================================================= // =========================================================
const kernel_mod = b.createModule(.{ const kernel_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"), // Fake root, we add objects later .root_source_file = b.path("hal/abi.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
kernel_mod.red_zone = false; applyKernelSettings(kernel_mod, config.code_model);
kernel_mod.stack_check = false;
kernel_mod.code_model = .medany;
const kernel = b.addExecutable(.{ const kernel = b.addExecutable(.{
.name = "rumpk.elf", .name = "rumpk.elf",
.root_module = kernel_mod, .root_module = kernel_mod,
}); });
kernel.setLinkerScript(b.path("boot/linker.ld")); kernel.setLinkerScript(b.path(config.linker_script));
kernel.addObject(boot); kernel.addObject(boot);
// kernel.linkLibrary(hal); // Redundant, already in kernel_mod kernel.addObject(clib);
kernel.addObject(libc_shim);
kernel.addObject(lfs_hal);
kernel.addObject(lwf_adapter);
kernel.addObject(switch_obj);
// Add Nim-generated objects // LwIP linked into kernel Membrane/NetSwitch drives DHCP/TCP/ICMP.
kernel.linkLibrary(liblwip);
// LittleFS IS linked into kernel provides /nexus persistent storage.
kernel.addObject(lfs_obj);
// Nim-generated objects from build/nimcache/
{ {
var nimcache_dir = std.fs.cwd().openDir("build/nimcache", .{ .iterate = true }) catch |err| { var nimcache_dir = std.fs.cwd().openDir("build/nimcache", .{ .iterate = true }) catch |err| {
std.debug.print("Warning: Could not open nimcache dir: {}\n", .{err}); std.debug.print("Warning: Could not open nimcache dir: {}\n", .{err});
@ -104,31 +367,63 @@ pub fn build(b: *std.Build) void {
} }
} }
// Add external pre-built dependencies (Order matters: Libs after users) // Embedded InitRD assemble for the target architecture
kernel.addObjectFile(b.path("build/switch.o")); // cpu_switch_to const initrd_obj = b.addObject(.{
kernel.addObjectFile(b.path("build/sys_arch.o")); // sys_now, nexus_lwip_panic .name = "initrd",
kernel.addObjectFile(b.path("build/libc_shim.o")); .root_module = b.createModule(.{
kernel.addObjectFile(b.path("build/clib.o")); .target = target,
kernel.addObjectFile(b.path("build/liblwip.a")); .optimize = optimize,
kernel.addObjectFile(b.path("build/initrd.o")); }),
});
initrd_obj.addAssemblyFile(b.path("build/embed_initrd.S"));
kernel.addObject(initrd_obj);
b.installArtifact(kernel); b.installArtifact(kernel);
// Make default install depend on Nim compilation
kernel.step.dependOn(&nim_step.step);
// ========================================================= // =========================================================
// Tests // Tests (always run on native host)
// ========================================================= // =========================================================
const test_mod = b.createModule(.{ // HAL unit tests
const hal_test_mod = b.createModule(.{
.root_source_file = b.path("hal/abi.zig"), .root_source_file = b.path("hal/abi.zig"),
.target = target, .target = b.graph.host,
.optimize = optimize,
}); });
const hal_tests = b.addTest(.{ const hal_tests = b.addTest(.{
.root_module = test_mod, .root_module = hal_test_mod,
}); });
const run_tests = b.addRunArtifact(hal_tests); const run_hal_tests = b.addRunArtifact(hal_tests);
const test_step = b.step("test", "Run Rumpk HAL tests"); const test_step = b.step("test", "Run Rumpk HAL tests (native host)");
test_step.dependOn(&run_tests.step); test_step.dependOn(&run_hal_tests.step);
// LWF Adapter + Membrane tests (Project LibWeb)
const lwf_test_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_adapter.zig"),
.target = b.graph.host,
});
const lwf_tests = b.addTest(.{
.root_module = lwf_test_mod,
});
const run_lwf_tests = b.addRunArtifact(lwf_tests);
const lwf_test_step = b.step("test-lwf", "Run LWF Adapter tests (Project LibWeb)");
lwf_test_step.dependOn(&run_lwf_tests.step);
const lwf_membrane_mod = b.createModule(.{
.root_source_file = b.path("libs/libertaria/lwf_membrane.zig"),
.target = b.graph.host,
});
const lwf_membrane_tests = b.addTest(.{
.root_module = lwf_membrane_mod,
});
const run_lwf_membrane_tests = b.addRunArtifact(lwf_membrane_tests);
lwf_test_step.dependOn(&run_lwf_membrane_tests.step);
} }

View File

@ -27,6 +27,7 @@ proc cspace_grant_cap*(
proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.importc, cdecl.} proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.importc, cdecl.}
proc cspace_revoke*(fiber_id: uint64, slot: uint) {.importc, cdecl.} proc cspace_revoke*(fiber_id: uint64, slot: uint) {.importc, cdecl.}
proc cspace_check_perm*(fiber_id: uint64, slot: uint, perm_bits: uint8): bool {.importc, cdecl.} proc cspace_check_perm*(fiber_id: uint64, slot: uint, perm_bits: uint8): bool {.importc, cdecl.}
proc cspace_check_channel*(fiber_id: uint64, channel_id: uint64, perm_bits: uint8): bool {.importc, cdecl.}
## Capability Types (Mirror from cspace.zig) ## Capability Types (Mirror from cspace.zig)
type type
@ -80,10 +81,10 @@ proc fiber_grant_memory*(
end_addr end_addr
) )
proc fiber_check_channel_access*(fiber_id: uint64, slot: uint, write: bool): bool = proc fiber_check_channel_access*(fiber_id: uint64, channel_id: uint64, write: bool): bool =
## Check if fiber has channel access via capability ## Check if fiber has Channel capability for given channel_id
let perm = if write: PERM_WRITE else: PERM_READ let perm = if write: PERM_WRITE else: PERM_READ
return cspace_check_perm(fiber_id, slot, perm) return cspace_check_channel(fiber_id, channel_id, perm)
proc fiber_revoke_capability*(fiber_id: uint64, slot: uint) = proc fiber_revoke_capability*(fiber_id: uint64, slot: uint) =
## Revoke a capability from a fiber ## Revoke a capability from a fiber

View File

@ -24,6 +24,10 @@ when defined(riscv64):
const ARCH_NAME* = "riscv64" const ARCH_NAME* = "riscv64"
const CONTEXT_SIZE* = 128 const CONTEXT_SIZE* = 128
const RET_ADDR_INDEX* = 0 # Offset in stack for RA const RET_ADDR_INDEX* = 0 # Offset in stack for RA
elif defined(arm64):
const ARCH_NAME* = "aarch64"
const CONTEXT_SIZE* = 96 # 6 register pairs (x19-x30) * 16 bytes
const RET_ADDR_INDEX* = 11 # x30 (LR) at [sp + 88] = index 11
elif defined(amd64) or defined(x86_64): elif defined(amd64) or defined(x86_64):
const ARCH_NAME* = "amd64" const ARCH_NAME* = "amd64"
const CONTEXT_SIZE* = 64 const CONTEXT_SIZE* = 64
@ -112,7 +116,7 @@ const STACK_SIZE* = 4096
# Fiber State # Fiber State
# ========================================================= # =========================================================
var main_fiber: FiberObject var main_fiber*: FiberObject
var current_fiber* {.global.}: Fiber = addr main_fiber var current_fiber* {.global.}: Fiber = addr main_fiber
# ========================================================= # =========================================================
@ -135,6 +139,9 @@ proc fiber_trampoline() {.cdecl, exportc, noreturn.} =
when defined(riscv64): when defined(riscv64):
while true: while true:
{.emit: "asm volatile(\"wfi\");".} {.emit: "asm volatile(\"wfi\");".}
elif defined(arm64):
while true:
{.emit: "asm volatile(\"wfe\");".}
else: else:
while true: discard while true: discard

View File

@ -10,11 +10,11 @@
## Freestanding implementation (No OS module dependencies). ## Freestanding implementation (No OS module dependencies).
## Uses fixed-size arrays for descriptors to ensure deterministic latency. ## Uses fixed-size arrays for descriptors to ensure deterministic latency.
import tar, sfs import tar, sfs, lfs_bridge
type type
VFSMode = enum VFSMode = enum
MODE_TAR, MODE_SFS, MODE_RAM, MODE_TTY MODE_TAR, MODE_SFS, MODE_RAM, MODE_TTY, MODE_LFS
MountPoint = object MountPoint = object
prefix: array[32, char] prefix: array[32, char]
@ -25,6 +25,7 @@ type
offset: uint64 offset: uint64
mode: VFSMode mode: VFSMode
active: bool active: bool
lfs_handle: int32 ## LFS file handle (-1 = not open)
const MAX_MOUNTS = 8 const MAX_MOUNTS = 8
const MAX_FDS = 32 const MAX_FDS = 32
@ -64,8 +65,15 @@ proc vfs_add_mount(prefix: cstring, mode: VFSMode) =
mnt_table[mnt_count].mode = mode mnt_table[mnt_count].mode = mode
mnt_count += 1 mnt_count += 1
proc kprintln(s: cstring) {.importc, cdecl.}
proc vfs_mount_init*() = proc vfs_mount_init*() =
# Restore the SPEC-502 baseline # Mount LittleFS for /nexus (persistent sovereign storage)
if lfs_bridge.lfs_mount_fs():
vfs_add_mount("/nexus", MODE_LFS)
else:
# Fallback to SFS if LittleFS mount fails (no block device?)
kprintln("[VFS] LFS mount failed, falling back to SFS for /nexus")
vfs_add_mount("/nexus", MODE_SFS) vfs_add_mount("/nexus", MODE_SFS)
vfs_add_mount("/sysro", MODE_TAR) vfs_add_mount("/sysro", MODE_TAR)
vfs_add_mount("/state", MODE_RAM) vfs_add_mount("/state", MODE_RAM)
@ -88,9 +96,22 @@ proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} =
let sub_path = cast[cstring](cast[uint64](path) + uint64(prefix_len)) let sub_path = cast[cstring](cast[uint64](path) + uint64(prefix_len))
var internal_fd: int32 = -1 var internal_fd: int32 = -1
# Map VFS flags to LFS flags for MODE_LFS
var lfs_h: int32 = -1
case mode: case mode:
of MODE_TAR, MODE_RAM: internal_fd = tar.vfs_open(sub_path, flags) of MODE_TAR, MODE_RAM: internal_fd = tar.vfs_open(sub_path, flags)
of MODE_SFS: internal_fd = 0 # Shim of MODE_SFS: internal_fd = 0 # Shim
of MODE_LFS:
# Convert POSIX-ish flags to LFS flags
var lfs_flags = lfs_bridge.LFS_O_RDONLY
if (flags and 3) == 1: lfs_flags = lfs_bridge.LFS_O_WRONLY
elif (flags and 3) == 2: lfs_flags = lfs_bridge.LFS_O_RDWR
if (flags and 0x40) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_CREAT # O_CREAT
if (flags and 0x200) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_TRUNC # O_TRUNC
if (flags and 0x400) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_APPEND # O_APPEND
lfs_h = lfs_bridge.lfs_open_file(sub_path, lfs_flags)
if lfs_h >= 0: internal_fd = 0
of MODE_TTY: internal_fd = 1 # Shim of MODE_TTY: internal_fd = 1 # Shim
if internal_fd >= 0: if internal_fd >= 0:
@ -99,6 +120,7 @@ proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} =
fd_table[i].active = true fd_table[i].active = true
fd_table[i].mode = mode fd_table[i].mode = mode
fd_table[i].offset = 0 fd_table[i].offset = 0
fd_table[i].lfs_handle = lfs_h
let p = cast[ptr UncheckedArray[char]](sub_path) let p = cast[ptr UncheckedArray[char]](sub_path)
var j = 0 var j = 0
while p[j] != '\0' and j < 63: while p[j] != '\0' and j < 63:
@ -106,6 +128,8 @@ proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} =
j += 1 j += 1
fd_table[i].path[j] = '\0' fd_table[i].path[j] = '\0'
return int32(i + 3) # FDs start at 3 return int32(i + 3) # FDs start at 3
# No free slot — close LFS handle if we opened one
if lfs_h >= 0: discard lfs_bridge.lfs_close_file(lfs_h)
return -1 return -1
proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
@ -132,6 +156,11 @@ proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cde
fh.offset += actual fh.offset += actual
return int64(actual) return int64(actual)
return 0 return 0
of MODE_LFS:
if fh.lfs_handle < 0: return -1
let n = lfs_bridge.lfs_read_file(fh.lfs_handle, buf, uint32(count))
if n > 0: fh.offset += uint64(n)
return int64(n)
proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let idx = int(fd - 3) let idx = int(fd - 3)
@ -149,14 +178,47 @@ proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cd
let path = cast[cstring](addr fh.path[0]) let path = cast[cstring](addr fh.path[0])
sfs.sfs_write_file(path, buf, int(count)) sfs.sfs_write_file(path, buf, int(count))
return int64(count) return int64(count)
of MODE_LFS:
if fh.lfs_handle < 0: return -1
let n = lfs_bridge.lfs_write_file(fh.lfs_handle, buf, uint32(count))
if n > 0: fh.offset += uint64(n)
return int64(n)
proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} = proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} =
let idx = int(fd - 3) let idx = int(fd - 3)
if idx >= 0 and idx < MAX_FDS: if idx >= 0 and idx < MAX_FDS:
if fd_table[idx].mode == MODE_LFS and fd_table[idx].lfs_handle >= 0:
discard lfs_bridge.lfs_close_file(fd_table[idx].lfs_handle)
fd_table[idx].lfs_handle = -1
fd_table[idx].active = false fd_table[idx].active = false
return 0 return 0
return -1 return -1
proc ion_vfs_dup*(fd: int32, min_fd: int32 = 0): int32 {.exportc, cdecl.} =
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
# F_DUPFD needs to find first fd >= min_fd
let start_idx = if min_fd > 3: int(min_fd - 3) else: 0
for i in start_idx..<MAX_FDS:
if not fd_table[i].active:
fd_table[i] = fd_table[idx]
return int32(i + 3)
return -1
proc ion_vfs_dup2*(old_fd: int32, new_fd: int32): int32 {.exportc, cdecl.} =
let old_idx = int(old_fd - 3)
let new_idx = int(new_fd - 3)
if old_idx < 0 or old_idx >= MAX_FDS or not fd_table[old_idx].active: return -1
if new_idx < 0 or new_idx >= MAX_FDS: return -1
if old_idx == new_idx: return new_fd
fd_table[new_idx] = fd_table[old_idx]
fd_table[new_idx].active = true
return new_fd
proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} = proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} =
# Hardcoded baseline for now to avoid string/os dependency # Hardcoded baseline for now to avoid string/os dependency
let msg = "/nexus\n/sysro\n/state\n" let msg = "/nexus\n/sysro\n/state\n"

View File

@ -3,5 +3,10 @@
double pow(double x, double y); double pow(double x, double y);
double log10(double x); double log10(double x);
double fabs(double x);
double floor(double x);
double ceil(double x);
double fmod(double x, double y);
double round(double x);
#endif #endif

View File

@ -4,10 +4,22 @@
#include <stddef.h> #include <stddef.h>
#include <stdarg.h> #include <stdarg.h>
typedef struct _FILE FILE;
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
int printf(const char *format, ...); int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...); int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...);
int vprintf(const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap); int vsnprintf(char *str, size_t size, const char *format, va_list ap);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fflush(FILE *stream);
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int rename(const char *oldpath, const char *newpath); int rename(const char *oldpath, const char *newpath);
int remove(const char *pathname); int remove(const char *pathname);

View File

@ -91,6 +91,8 @@ type
fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.} fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.}
fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.}
fn_vfs_close*: proc(fd: int32): int32 {.cdecl.} fn_vfs_close*: proc(fd: int32): int32 {.cdecl.}
fn_vfs_dup*: proc(fd: int32, min_fd: int32): int32 {.cdecl.}
fn_vfs_dup2*: proc(old_fd, new_fd: int32): int32 {.cdecl.}
fn_log*: pointer fn_log*: pointer
fn_pledge*: proc(promises: uint64): int32 {.cdecl.} fn_pledge*: proc(promises: uint64): int32 {.cdecl.}
@ -123,6 +125,10 @@ type
net_mac*: array[6, byte] net_mac*: array[6, byte]
reserved_mac*: array[2, byte] reserved_mac*: array[2, byte]
# Project LibWeb: LWF Sovereign Channel (16 bytes)
s_lwf_rx*: ptr HAL_Ring[IonPacket] # Kernel Producer -> User Consumer (LWF frames)
s_lwf_tx*: ptr HAL_Ring[IonPacket] # User Producer -> Kernel Consumer (LWF frames)
include invariant include invariant
# --- Sovereign Logic --- # --- Sovereign Logic ---
@ -167,6 +173,12 @@ var net_rx_hal: HAL_Ring[IonPacket]
var net_tx_hal: HAL_Ring[IonPacket] var net_tx_hal: HAL_Ring[IonPacket]
var netswitch_rx_hal: HAL_Ring[IonPacket] var netswitch_rx_hal: HAL_Ring[IonPacket]
# Project LibWeb: LWF Sovereign Channels
var chan_lwf_rx*: SovereignChannel[IonPacket] # Kernel -> User (LWF frames)
var chan_lwf_tx*: SovereignChannel[IonPacket] # User -> Kernel (LWF frames)
var lwf_rx_hal: HAL_Ring[IonPacket]
var lwf_tx_hal: HAL_Ring[IonPacket]
proc ion_init_input*() {.exportc, cdecl.} = proc ion_init_input*() {.exportc, cdecl.} =
guest_input_hal.head = 0 guest_input_hal.head = 0
guest_input_hal.tail = 0 guest_input_hal.tail = 0
@ -192,6 +204,17 @@ proc ion_init_network*() {.exportc, cdecl.} =
netswitch_rx_hal.mask = 255 netswitch_rx_hal.mask = 255
chan_netswitch_rx.ring = addr netswitch_rx_hal chan_netswitch_rx.ring = addr netswitch_rx_hal
# Project LibWeb: LWF Rings
lwf_rx_hal.head = 0
lwf_rx_hal.tail = 0
lwf_rx_hal.mask = 255
chan_lwf_rx.ring = addr lwf_rx_hal
lwf_tx_hal.head = 0
lwf_tx_hal.tail = 0
lwf_tx_hal.mask = 255
chan_lwf_tx.ring = addr lwf_tx_hal
# Initialize user slab # Initialize user slab
ion_user_slab_init() ion_user_slab_init()
@ -218,4 +241,4 @@ proc ion_user_free_systable*(id: uint16) {.exportc, cdecl.} =
static: doAssert(sizeof(IonPacket) == 24, "IonPacket size mismatch!") static: doAssert(sizeof(IonPacket) == 24, "IonPacket size mismatch!")
static: doAssert(sizeof(CmdPacket) == 32, "CmdPacket size mismatch!") static: doAssert(sizeof(CmdPacket) == 32, "CmdPacket size mismatch!")
static: doAssert(sizeof(SysTable) == 208, "SysTable size mismatch! (Expected 208 after MAC+pad)") static: doAssert(sizeof(SysTable) == 240, "SysTable size mismatch! (Expected 240 after LibWeb LWF channels)")

View File

@ -13,17 +13,24 @@
import ../ring import ../ring
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.} proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc dbg(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len)) var NEWLINE_BUF: array[2, char] = ['\n', '\0']
var nl = "\n"
console_write(unsafeAddr nl[0], csize_t(1)) proc dbg(s: cstring) {.inline.} =
if s != nil:
var i = 0
let p = cast[ptr UncheckedArray[char]](s)
while p[i] != '\0': inc i
console_write(cast[pointer](s), csize_t(i))
console_write(addr NEWLINE_BUF[0], csize_t(1))
const const
SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom) SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom)
POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM) POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM)
POOL_ALIGN* = 4096 # VirtIO/Page Alignment POOL_ALIGN* = 4096 # VirtIO/Page Alignment
SYSTABLE_BASE = 0x83000000'u64 SYSTABLE_BASE = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE
USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000 USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000
USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX
@ -53,9 +60,11 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Initializing Pool...") dbg("[ION] Initializing Pool...")
# 1. Get the VIRTUAL address of the static buffer # 1. Get the VIRTUAL address of the static buffer
dbg("[ION] Step 1: Getting virt addr...")
let virt_addr = cast[uint64](addr global_pool.buffer[0]) let virt_addr = cast[uint64](addr global_pool.buffer[0])
# 2. Translate to PHYSICAL (Identity Mapped for Phase 7) # 2. Translate to PHYSICAL (Identity Mapped for Phase 7)
dbg("[ION] Step 2: Setting base phys...")
global_pool.base_phys = virt_addr global_pool.base_phys = virt_addr
# Tracing for Phase 37 # Tracing for Phase 37
@ -64,12 +73,16 @@ proc ion_pool_init*() {.exportc.} =
kprint_hex(global_pool.base_phys) kprint_hex(global_pool.base_phys)
dbg("") dbg("")
dbg("[ION] Ring Init...") dbg("[ION] Step 3: Ring Init (free_ring)...")
dbg(" 3a: About to call init()")
global_pool.free_ring.init() global_pool.free_ring.init()
dbg(" 3b: free_ring init complete")
dbg("[ION] Step 4: Ring Init (tx_ring)...")
global_tx_ring.init() global_tx_ring.init()
dbg(" 4a: tx_ring init complete")
# Fill the free ring with all indices [0..1023] # Fill the free ring with all indices [0..1023]
dbg("[ION] Filling Slabs...") dbg("[ION] Step 5: Filling Slabs...")
var count = 0 var count = 0
for i in 0 ..< POOL_COUNT: for i in 0 ..< POOL_COUNT:
if global_pool.free_ring.push(uint16(i)): if global_pool.free_ring.push(uint16(i)):
@ -77,6 +90,8 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Pool Ready.") dbg("[ION] Pool Ready.")
proc ion_alloc*(): IonPacket {.exportc.} = proc ion_alloc*(): IonPacket {.exportc.} =
## O(1) Allocation. Returns an empty packet struct. ## O(1) Allocation. Returns an empty packet struct.
## If OOM, returns packet with data = nil ## If OOM, returns packet with data = nil

View File

@ -12,11 +12,22 @@ import fiber, ion, sched, pty, cspace, ontology, fastpath, utcp
import fs/vfs, fs/tar import fs/vfs, fs/tar
import loader/elf import loader/elf
import ../libs/membrane/term import ../libs/membrane/term
import ../libs/membrane/net_glue
const const
MAX_WORKERS* = 8 MAX_WORKERS* = 8
MAX_FIBER_STACK* = 128 * 1024 MAX_FIBER_STACK* = 128 * 1024
SYSTABLE_BASE* = 0x83000000'u64 SYSTABLE_BASE* = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
# Cellular Memory Architecture (M3.3)
CELL0_BASE* = when defined(arm64): 0x48000000'u64
else: 0x88000000'u64
CELL1_BASE* = when defined(arm64): 0x4C000000'u64
else: 0x8C000000'u64
USER_VA_BASE* = when defined(arm64): 0x48000000'u64
else: 0x88000000'u64
USER_SP_FALLBACK* = when defined(arm64): 0x4BFFFFF0'u64
else: 0x8BFFFFF0'u64
# Export Nim Timer Handler for HAL (Zig calls this) # Export Nim Timer Handler for HAL (Zig calls this)
proc rumpk_timer_handler() {.exportc, cdecl, used.} = proc rumpk_timer_handler() {.exportc, cdecl, used.} =
@ -35,6 +46,7 @@ proc nexshell_main() {.importc, cdecl.}
proc console_poll() {.importc, cdecl.} proc console_poll() {.importc, cdecl.}
proc ion_get_virt(id: uint16): uint64 {.importc, cdecl.} proc ion_get_virt(id: uint16): uint64 {.importc, cdecl.}
# InitRD Symbols # InitRD Symbols
var initrd_start {.importc: "_initrd_start" .}: byte var initrd_start {.importc: "_initrd_start" .}: byte
var initrd_end {.importc: "_initrd_end" .}: byte var initrd_end {.importc: "_initrd_end" .}: byte
@ -151,6 +163,7 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 =
if phdr.p_type == PT_LOAD: if phdr.p_type == PT_LOAD:
# Enable S-mode access to U-mode pages (SUM=1) # Enable S-mode access to U-mode pages (SUM=1)
when defined(riscv64):
{.emit: """asm volatile ("li t1, 0x40000; csrs sstatus, t1" : : : "t1");""" .} {.emit: """asm volatile ("li t1, 0x40000; csrs sstatus, t1" : : : "t1");""" .}
let dest = cast[ptr UncheckedArray[byte]](phdr.p_vaddr) let dest = cast[ptr UncheckedArray[byte]](phdr.p_vaddr)
@ -168,11 +181,139 @@ proc kload_phys(path: cstring, phys_offset: uint64): uint64 =
# {.emit: """asm volatile ("li t1, 0x40000; csrc sstatus, t1" : : : "t1");""" .} # {.emit: """asm volatile ("li t1, 0x40000; csrc sstatus, t1" : : : "t1");""" .}
# ⚡ ARCH-SYNC: Flush I-Cache after loading new code # ⚡ ARCH-SYNC: Flush I-Cache after loading new code
when defined(riscv64):
{.emit: """asm volatile ("fence.i" : : : "memory");""" .} {.emit: """asm volatile ("fence.i" : : : "memory");""" .}
elif defined(arm64):
{.emit: """asm volatile ("ic iallu; dsb ish; isb" : : : "memory");""" .}
discard ion_vfs_close(fd) discard ion_vfs_close(fd)
return ehdr.e_entry return ehdr.e_entry
# --- M4.4: MANIFEST-DRIVEN CAPABILITY LOADING ---
proc set_pledge*(f: var FiberObject, pledge: uint64) =
## Set fiber pledge mask, preserving Spectrum bits [63:62]
f.promises = (f.promises and 0xC000000000000000'u64) or (pledge and 0x3FFFFFFFFFFFFFFF'u64)
proc kload_manifest_tar(path: cstring): ManifestResult =
## Scan ELF section headers via TAR for .nexus.manifest containing BKDL data.
## Uses tar.vfs_read_at() for selective reads (same approach as kload_phys).
result.header = nil
result.caps = nil
result.count = 0
# Strip /sysro prefix for TAR paths (same logic as kload_phys)
var tar_path = path
if path[0] == '/':
tar_path = cast[cstring](cast[uint64](path) + 1)
if k_starts_with(path, "/sysro"):
tar_path = cast[cstring](cast[uint64](path) + 6)
if tar_path[0] == '/': tar_path = cast[cstring](cast[uint64](tar_path) + 1)
# 1. Read ELF header
var ehdr: Elf64_Ehdr
if tar.vfs_read_at(tar_path, addr ehdr, uint64(sizeof(ehdr)), 0) != int64(sizeof(ehdr)):
return
if ehdr.e_ident[0] != 0x7F or ehdr.e_ident[1] != 'E'.uint8:
return
if ehdr.e_shoff == 0 or ehdr.e_shnum == 0 or ehdr.e_shstrndx >= ehdr.e_shnum:
return
# 2. Read shstrtab section header to get string table location
let strtab_offset = ehdr.e_shoff + uint64(ehdr.e_shstrndx) * uint64(ehdr.e_shentsize)
var strtab_shdr: Elf64_Shdr
if tar.vfs_read_at(tar_path, addr strtab_shdr, uint64(sizeof(Elf64_Shdr)), strtab_offset) != int64(sizeof(Elf64_Shdr)):
return
# 3. Read the string table itself (cap at 512 bytes — section names are short)
var strtab_buf: array[512, byte]
let strtab_read_size = min(strtab_shdr.sh_size, 512'u64)
if tar.vfs_read_at(tar_path, addr strtab_buf[0], strtab_read_size, strtab_shdr.sh_offset) != int64(strtab_read_size):
return
# 4. Scan section headers for .nexus.manifest
let target = cstring(".nexus.manifest")
for i in 0 ..< int(ehdr.e_shnum):
var shdr: Elf64_Shdr
let sh_offset = ehdr.e_shoff + uint64(i) * uint64(ehdr.e_shentsize)
if tar.vfs_read_at(tar_path, addr shdr, uint64(sizeof(Elf64_Shdr)), sh_offset) != int64(sizeof(Elf64_Shdr)):
continue
if shdr.sh_name < uint32(strtab_read_size):
# Compare section name against ".nexus.manifest"
var match = true
var j = 0
while j < 16: # len(".nexus.manifest") + null = 16
let ch = target[j]
let idx = int(shdr.sh_name) + j
if idx >= int(strtab_read_size):
match = false
break
if ch == '\0':
break # End of target string — all matched
if strtab_buf[idx] != byte(ch):
match = false
break
j += 1
if match and shdr.sh_size >= uint64(sizeof(BkdlHeader)):
# 5. Read the manifest section data into a static buffer
# Max manifest size: BkdlHeader (118) + 16 caps * CapDescriptor (12) = 310 bytes
var manifest_buf {.global.}: array[512, byte]
let read_size = min(shdr.sh_size, 512'u64)
if tar.vfs_read_at(tar_path, addr manifest_buf[0], read_size, shdr.sh_offset) != int64(read_size):
return
let hdr = cast[ptr BkdlHeader](addr manifest_buf[0])
if hdr.magic != BKDL_MAGIC or hdr.version != BKDL_VERSION:
kprintln("[Manifest] Invalid BKDL magic/version")
return
let expected = uint64(sizeof(BkdlHeader)) + uint64(hdr.cap_count) * uint64(sizeof(CapDescriptor))
if expected > read_size:
kprintln("[Manifest] BKDL cap_count exceeds section size")
return
kprint("[Manifest] WARNING: Signature unchecked (dev mode)")
kprintln("")
discard emit_access_denied(0, 0xB0D1, 0, 0) # STL: signature skip event
result.header = hdr
result.caps = cast[ptr UncheckedArray[CapDescriptor]](addr manifest_buf[sizeof(BkdlHeader)])
result.count = int(hdr.cap_count)
return
proc apply_manifest*(fiber_id: uint64, manifest: ManifestResult, pledge_out: var uint64) =
## Apply BKDL manifest capabilities to fiber's CSpace.
## Derives pledge mask from requested capability types.
var derived_pledge: uint64 = PLEDGE_STDIO # Always grant basic I/O
for i in 0 ..< manifest.count:
let cap = manifest.caps[i]
let slot = fiber_grant_channel(fiber_id, cap.resource_id, cap.perms)
if slot >= 0:
discard emit_capability_grant(fiber_id, cap.cap_type, cap.resource_id, uint8(slot), 0)
kprint("[Manifest] Granted cap "); kprint_hex(cap.resource_id)
kprint(" perms="); kprint_hex(uint64(cap.perms))
kprint(" to fiber "); kprint_hex(fiber_id); kprintln("")
else:
discard emit_access_denied(fiber_id, cap.resource_id, cap.perms, 0)
kprint("[Manifest] DENIED cap "); kprint_hex(cap.resource_id)
kprint(" for fiber "); kprint_hex(fiber_id); kprintln(" (CSpace full)")
# Derive pledge bits from capability types
if cap.resource_id == 0x2000: # VFS
derived_pledge = derived_pledge or PLEDGE_RPATH
if (cap.perms and PERM_WRITE) != 0:
derived_pledge = derived_pledge or PLEDGE_WPATH
elif cap.resource_id == 0x500 or cap.resource_id == 0x501: # NET_TX / NET_RX
derived_pledge = derived_pledge or PLEDGE_INET
elif cap.resource_id == 0x1000 or cap.resource_id == 0x1001: # Console
discard # PLEDGE_STDIO already set
pledge_out = derived_pledge
# --- FIBER ENTRIES --- # --- FIBER ENTRIES ---
proc subject_fiber_entry() {.cdecl.} = proc subject_fiber_entry() {.cdecl.} =
@ -185,13 +326,26 @@ proc subject_fiber_entry() {.cdecl.} =
# Load into Cellular Slot (phys_offset) # Load into Cellular Slot (phys_offset)
let entry_addr = kload_phys(cast[cstring](addr subject_loading_path[0]), current_fiber.phys_offset) let entry_addr = kload_phys(cast[cstring](addr subject_loading_path[0]), current_fiber.phys_offset)
# M4.4: Scan ELF for BKDL manifest and apply capabilities
let loading_path = cast[cstring](addr subject_loading_path[0])
let manifest = kload_manifest_tar(loading_path)
if manifest.header != nil:
kprint("[Manifest] Found BKDL manifest: "); kprint_hex(uint64(manifest.count)); kprintln(" capabilities")
var pledge: uint64 = 0
apply_manifest(current_fiber.id, manifest, pledge)
set_pledge(current_fiber[], pledge)
kprint("[Manifest] Pledge mask: "); kprint_hex(pledge); kprintln("")
else:
kprintln("[Manifest] No BKDL manifest — default policy (STDIO only)")
set_pledge(current_fiber[], PLEDGE_STDIO)
if entry_addr != 0: if entry_addr != 0:
kprint("[Subject:"); kprint_hex(fid); kprint("] Entering Payload at: "); kprint_hex(entry_addr); kprintln("") kprint("[Subject:"); kprint_hex(fid); kprint("] Entering Payload at: "); kprint_hex(entry_addr); kprintln("")
proc hal_enter_userland(entry, systable, sp: uint64) {.importc, cdecl.} proc hal_enter_userland(entry, systable, sp: uint64) {.importc, cdecl.}
var sp = current_fiber.user_sp_init var sp = current_fiber.user_sp_init
if sp == 0: if sp == 0:
# Fallback (Legacy/Init) - Top of the 64MB Sentinel Cell # Fallback (Legacy/Init) - Top of the 64MB Sentinel Cell
sp = 0x8BFFFFF0'u64 sp = USER_SP_FALLBACK
kprintln("╔════════════════════════════════════════════════════╗") kprintln("╔════════════════════════════════════════════════════╗")
kprintln("║ PRE-FLIGHT: USERLAND TRANSITION ║") kprintln("║ PRE-FLIGHT: USERLAND TRANSITION ║")
@ -232,51 +386,71 @@ proc compositor_fiber_entry() {.cdecl.} =
proc mm_create_worker_map(stack_base: uint64, stack_size: uint64, packet_addr: uint64, phys_base: uint64, region_size: uint64): uint64 {.importc, cdecl.} proc mm_create_worker_map(stack_base: uint64, stack_size: uint64, packet_addr: uint64, phys_base: uint64, region_size: uint64): uint64 {.importc, cdecl.}
proc setup_mksh_stack(stack_base: pointer, stack_size: int): uint64 = proc setup_cellular_stack(phys_base: uint64, slot_size: uint64, user_base: uint64): uint64 =
var sp = cast[uint64](stack_base) + cast[uint64](stack_size) # Stack grows down from the top of the cell
sp = sp and not 15'u64 # Align 16 var phys_top = phys_base + slot_size
var user_sp = user_base + slot_size
# Term String # Align 16
let term_str = "TERM=nexus\0" phys_top = phys_top and not 15'u64
sp -= uint64(term_str.len) user_sp = user_sp and not 15'u64
copyMem(cast[pointer](sp), unsafeAddr term_str[0], term_str.len)
let term_addr = sp
# Path String # Helper to push data
let path_str = "/bin/mksh\0" template push_str(s: string): uint64 =
sp -= uint64(path_str.len) let s_val = s
copyMem(cast[pointer](sp), unsafeAddr path_str[0], path_str.len) let l = uint64(s_val.len) + 1 # + null terminator
let path_addr = sp phys_top -= l
user_sp -= l
# Zero the memory first (for null safety)
k_zero_mem(cast[pointer](phys_top), l)
copyMem(cast[pointer](phys_top), unsafeAddr s_val[0], s_val.len)
user_sp # Return USER address
sp = sp and not 15'u64 # Align 16 # Environment strings
let home_addr = push_str("HOME=/")
let term_addr = push_str("TERM=vt100")
let path_addr = push_str("/bin/mksh")
kprint("[Stack] HOME env at: "); kprint_hex(home_addr); kprint("\n")
kprint("[Stack] TERM env at: "); kprint_hex(term_addr); kprint("\n")
kprint("[Stack] argv[0] at: "); kprint_hex(path_addr); kprint("\n")
# Align 16 before pointer arrays
phys_top = phys_top and not 15'u64
user_sp = user_sp and not 15'u64
# Helper to push uint64
template push_u64(val: uint64) =
phys_top -= 8
user_sp -= 8
cast[ptr uint64](phys_top)[] = val
# Auxv (0, 0) # Auxv (0, 0)
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = 0 push_u64(0)
cast[ptr uint64](sp+8)[] = 0
# Envp (term, NULL) # Envp (NULL, TERM, HOME) - Reverse order of push
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = term_addr push_u64(term_addr)
cast[ptr uint64](sp+8)[] = 0 push_u64(home_addr)
# Argv (path, NULL) # Argv (NULL, path)
sp -= 16 push_u64(0)
cast[ptr uint64](sp)[] = path_addr push_u64(path_addr)
cast[ptr uint64](sp+8)[] = 0
# Argc (1) # Argc (1)
sp -= 8 push_u64(1)
cast[ptr uint64](sp)[] = 1
return sp kprint("[Stack] Final SP: "); kprint_hex(user_sp); kprint("\n")
return user_sp
proc ion_fiber_entry() {.cdecl.} = proc ion_fiber_entry() {.cdecl.} =
kprintln("[ION] Fiber Entry reached.") kprintln("[ION] Fiber Entry reached.")
while true: while true:
var pkt: CmdPacket var pkt: CmdPacket
if chan_cmd.recv(pkt): if chan_cmd.recv(pkt):
kprint("[ION] Received Packet Kind: "); kprint_hex(uint64(pkt.kind)) kprint("[ION] Received Packet Kind: "); kprint_hex(uint64(pkt.kind)); kprintln("")
case CmdType(pkt.kind): case CmdType(pkt.kind):
of CMD_SYS_EXIT: of CMD_SYS_EXIT:
kprintln("[ION] Restarting Subject...") kprintln("[ION] Restarting Subject...")
@ -300,21 +474,31 @@ proc ion_fiber_entry() {.cdecl.} =
kprintln("[ION] Failed to allocate PTY for child!") kprintln("[ION] Failed to allocate PTY for child!")
# Re-initialize fiber_child with the requested binary # Re-initialize fiber_child with the requested binary
# Note: stack_child is used for KERNEL context switching, not user stack.
init_fiber(addr fiber_child, subject_fiber_entry, addr stack_child[0], sizeof(stack_child)) init_fiber(addr fiber_child, subject_fiber_entry, addr stack_child[0], sizeof(stack_child))
# Phase 40: Set PTY & Stack
fiber_child.pty_id = pid
fiber_child.user_sp_init = setup_mksh_stack(addr stack_child[0], sizeof(stack_child))
# 🏛️ CELLULAR ALLOCATION (SPEC-202 Rev 2) # 🏛️ CELLULAR ALLOCATION (SPEC-202 Rev 2)
# Sentinel (Init) takes 0x88000000 - 0x8C000000 (64MB) # Sentinel (Init) takes Cell 0, Mksh (First Child) takes Cell 1
# Mksh (First Child) starts in the 'Wild' at 0x8C000000 let cell_base = CELL1_BASE
let cell_base = 0x8C000000'u64 let user_base = USER_VA_BASE
let cell_size = 64 * 1024 * 1024'u64
# Phase 40: Set PTY & User Stack
fiber_child.pty_id = pid
fiber_child.phys_offset = cell_base fiber_child.phys_offset = cell_base
# Allocate 64MB Slot for Mksh (Needs >32MB for BSS) # Setup User Stack in CELLULAR Memory (User Top)
let cell_size = 64 * 1024 * 1024'u64 # We write to Physical Address (cell_base + size), but Mksh sees (user_base + size)
fiber_child.satp_value = mm_create_worker_map(cast[uint64](addr stack_child[0]), uint64(sizeof(stack_child)), SYSTABLE_BASE, cell_base, cell_size) fiber_child.user_sp_init = setup_cellular_stack(cell_base, cell_size, user_base)
# Create Map avoiding Kernel Stack exposure
# We pass 0 as stack_base to skip mapping stack_child into user space.
fiber_child.satp_value = mm_create_worker_map(0, 0, SYSTABLE_BASE, cell_base, cell_size)
# M4.4: Child capabilities are applied in subject_fiber_entry via BKDL manifest.
# No hardcoded grants needed here — manifest drives CSpace + pledge.
# M4.5: Set budget based on child's Spectrum tier
fiber_child.budget_ns = default_budget_for_spectrum((addr fiber_child).getSpectrum())
kprintln("[ION] Child fiber spawned successfully") kprintln("[ION] Child fiber spawned successfully")
else: discard else: discard
else: else:
@ -326,8 +510,12 @@ proc rumpk_yield_internal*() {.exportc, cdecl.} =
switch(active_fibers_arr[6]) switch(active_fibers_arr[6])
proc fiber_yield*() {.exportc, cdecl.} = proc fiber_yield*() {.exportc, cdecl.} =
current_fiber.wants_yield = true # Return to the dispatcher context (main_fiber)
rumpk_yield_internal() # kprint("[Y"); kprint_hex(current_fiber.id); kprint("]")
switch(addr main_fiber)
# The `ld_internal()` part from the instruction was syntactically incorrect
# and likely a typo or incomplete instruction.
# Assuming the intent was to replace the previous yield mechanism with a direct switch.
proc fiber_netswitch_entry() {.cdecl.} = proc fiber_netswitch_entry() {.cdecl.} =
kprintln("[NetSwitch] Traffic Engine Online") kprintln("[NetSwitch] Traffic Engine Online")
@ -343,6 +531,10 @@ proc fiber_netswitch_entry() {.cdecl.} =
kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.") kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.")
# Initialize LwIP Membrane (DHCP, Netif, DNS)
membrane_init()
kprintln("[NetSwitch] Membrane initialized — DHCP running")
while true: while true:
var pkt: IonPacket var pkt: IonPacket
@ -370,6 +562,9 @@ proc fiber_netswitch_entry() {.cdecl.} =
var res = ion_tx_push(pkt) var res = ion_tx_push(pkt)
if not res: kprintln("[NetSwitch] Drop (TX Full)") if not res: kprintln("[NetSwitch] Drop (TX Full)")
# Drive LwIP timers + RX ingestion (DHCP, ARP, TCP, ICMP)
pump_membrane_stack()
# Poll Network # Poll Network
virtio_net_poll() virtio_net_poll()
@ -378,7 +573,6 @@ proc fiber_netswitch_entry() {.cdecl.} =
# Prevent Starvation # Prevent Starvation
fiber_sleep(10) # 10ms - allow DHCP state machine to execute fiber_sleep(10) # 10ms - allow DHCP state machine to execute
# 10ms - allow DHCP state machine to execute
proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} = proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} =
## Handle packet from Network Driver ## Handle packet from Network Driver
@ -397,15 +591,35 @@ proc ion_ingress*(id: uint16, len: uint16, offset: uint16) {.exportc, cdecl.} =
if not chan_netswitch_rx.send(pkt): if not chan_netswitch_rx.send(pkt):
ion_free_raw(id) ion_free_raw(id)
# --- SCHEDULER STATE ---
# --- SCHEDULER STATE ---
proc ion_push_stdin*(p: pointer, len: csize_t) {.exportc, cdecl.} = proc ion_push_stdin*(p: pointer, len: csize_t) {.exportc, cdecl.} =
if chan_input.ring == nil: return if chan_input.ring == nil: return
var pkt = ion_alloc() var pkt = ion_alloc()
if pkt.data == nil: return if pkt.data == nil:
let to_copy = if int(len) < 2048: int(len) else: 2048 # kprintln("[ION Push] CRITICAL: Slab allocation failed!")
copyMem(pkt.data, p, to_copy) return
pkt.len = uint16(to_copy)
if fiber_subject.sleep_until == 0xFFFFFFFFFFFFFFFF'u64: fiber_subject.sleep_until = 0 pkt.data[0] = cast[ptr byte](p)[]
discard chan_input.send(pkt) pkt.len = 1
var wake_count = 0
if chan_input.send(pkt):
# Wake up any fibers waiting for terminal input
for i in 0..<16:
let f = active_fibers_arr[i]
if f != nil and f.is_blocked and (f.blocked_on_mask and 0x1000) != 0:
f.is_blocked = false
f.blocked_on_mask = 0
f.sleep_until = 0 # Wake immediately
wake_count += 1
if wake_count > 0:
# kprint("[ION] Woke "); kprint_hex(uint64(wake_count)); kprintln(" fibers")
discard
proc k_check_deferred_yield*() {.exportc, cdecl.} = proc k_check_deferred_yield*() {.exportc, cdecl.} =
if current_fiber != nil and current_fiber.wants_yield: if current_fiber != nil and current_fiber.wants_yield:
@ -449,8 +663,14 @@ proc k_handle_exception*(scause, sepc, stval: uint) {.exportc, cdecl.} =
kprintln("") kprintln("")
kprintln("[IMMUNE] System HALTING (Trap Loop Prevention).") kprintln("[IMMUNE] System HALTING (Trap Loop Prevention).")
when defined(riscv64):
while true: while true:
{.emit: "asm volatile(\"wfi\");".} {.emit: "asm volatile(\"wfi\");".}
elif defined(arm64):
while true:
{.emit: "asm volatile(\"wfe\");".}
else:
while true: discard
proc k_get_current_satp*(): uint64 {.exportc, cdecl.} = proc k_get_current_satp*(): uint64 {.exportc, cdecl.} =
if current_fiber != nil: if current_fiber != nil:
@ -460,9 +680,40 @@ proc k_get_current_satp*(): uint64 {.exportc, cdecl.} =
proc wrapper_vfs_write(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} = proc wrapper_vfs_write(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} =
return ion_vfs_write(fd, buf, count) return ion_vfs_write(fd, buf, count)
# --- M4: PLEDGE ENFORCEMENT ---
proc check_pledge(nr: uint, f: Fiber): bool =
## Returns true if syscall is allowed by fiber's pledge mask
let pledges = f.promises and 0x3FFFFFFFFFFFFFFF'u64
if pledges == PLEDGE_ALL: return true # Unrestricted
case nr:
of 0x01, 0x65, 0x66, 0x100, 0x101, 0x102:
# exit, nanosleep, get_time, yield, pledge, wait_multi — always allowed
return true
of 0x203, 0x204: # READ, WRITE
return (pledges and PLEDGE_STDIO) != 0
of 0x200, 0x202: # OPEN, LIST
return (pledges and PLEDGE_RPATH) != 0
of 0x201, 0x205, 0x206, 0x207: # CLOSE, IOCTL, FCNTL, DUP2
return (pledges and PLEDGE_STDIO) != 0
of 0x600, 0x300: # EXECV, SPAWN_FIBER
return (pledges and PLEDGE_EXEC) != 0
of 0x905: # SYS_SOCK_RESOLVE
return (pledges and PLEDGE_INET) != 0
else:
return true # Unknown syscalls pass through
# --- SYSCALL HANDLER --- # --- SYSCALL HANDLER ---
proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} = proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} =
# M4: Pledge enforcement gate
if current_fiber != nil and not check_pledge(nr, current_fiber):
kprint("[DENIED] Fiber "); kprint_hex(current_fiber.id)
kprint(" pledge violation: syscall "); kprint_hex(uint64(nr)); kprintln("")
discard emit_access_denied(current_fiber.id, uint64(nr), 0, 0)
return 0xFFFFFFFFFFFFFFFF'u # -1 EPERM
if nr != 0x100 and nr != 0x205 and nr != 0x204 and nr != 0x203: if nr != 0x100 and nr != 0x205 and nr != 0x204 and nr != 0x203:
kprint("[Syscall] NR: "); kprint_hex(uint64(nr)); kprintln("") kprint("[Syscall] NR: "); kprint_hex(uint64(nr)); kprintln("")
@ -484,60 +735,113 @@ proc k_handle_syscall*(nr, a0, a1, a2: uint): uint {.exportc, cdecl.} =
of 0x100: # YIELD of 0x100: # YIELD
fiber_yield() fiber_yield()
return 0 return 0
of 0x101: # SYS_PLEDGE (OpenBSD semantics: can only narrow, never widen)
let current = current_fiber.promises and 0x3FFFFFFFFFFFFFFF'u64
let requested = a0 and 0x3FFFFFFFFFFFFFFF'u64
if (requested and (not current)) != 0:
kprint("[DENIED] Fiber "); kprint_hex(current_fiber.id); kprintln(" pledge widen attempt")
discard emit_access_denied(current_fiber.id, 0x101, 0, 0)
return 0xFFFFFFFFFFFFFFFF'u # -1 EPERM
current_fiber.promises = (current_fiber.promises and 0xC000000000000000'u64) or requested
return 0
of 0x102: # SYS_WAIT_MULTI (Silence Doctrine) of 0x102: # SYS_WAIT_MULTI (Silence Doctrine)
current_fiber.blocked_on_mask = a0 current_fiber.blocked_on_mask = a0
current_fiber.is_blocked = true current_fiber.is_blocked = true
fiber_yield() fiber_yield()
return 0 return 0
of 0x200: # OPEN of 0x200: # OPEN
# return uint(libc_impl.libc_impl_open(cast[cstring](a0), int(a1))) # M4: Check VFS capability (channel 0x2000)
return 0 if current_fiber != nil and current_fiber.cspace_id < 16:
let write_mode = (a1 and 1) != 0 # O_WRONLY or O_RDWR
let needed_perm = if write_mode: PERM_WRITE else: PERM_READ
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, needed_perm):
discard emit_access_denied(current_fiber.id, 0x2000, needed_perm, 0)
return 0xFFFFFFFFFFFFFFFF'u
let path = cast[cstring](a0)
let result = ion_vfs_open(path, int32(a1))
kprint("[OPEN] path="); kprint(path); kprint(" result="); kprint_hex(uint64(result)); kprint(" returning...\n")
let ret_val = uint(result)
kprint("[OPEN] About to return value: "); kprint_hex(ret_val); kprint("\n")
return ret_val
of 0x201: # CLOSE of 0x201: # CLOSE
# return uint(libc_impl.libc_impl_close(int(a0))) kprint("[CLOSE] fd="); kprint_hex(a0); kprint("\n")
return 0 return uint(ion_vfs_close(int32(a0)))
of 0x202: # LIST of 0x202: # LIST
# M4: Check VFS read capability (channel 0x2000)
if current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
return uint(ion_vfs_list(cast[pointer](a0), uint64(a1))) return uint(ion_vfs_list(cast[pointer](a0), uint64(a1)))
of 0x205: # IOCTL
kprint("[IOCTL] fd="); kprint_hex(a0); kprint(" request="); kprint_hex(a1); kprint("\n")
return 0 # stub
of 0x206: # FCNTL
kprint("[FCNTL] fd="); kprint_hex(a0); kprint(" cmd="); kprint_hex(a1); kprint("\n")
if a1 == 0: # F_DUPFD
return uint(ion_vfs_dup(int32(a0), int32(a2)))
return 0
of 0x207: # DUP2
return uint(ion_vfs_dup2(int32(a0), int32(a1)))
of 0x905: # SYS_SOCK_RESOLVE of 0x905: # SYS_SOCK_RESOLVE
# TODO: Implement getaddrinfo kernel integration # TODO: Implement getaddrinfo kernel integration
return 0 # Not implemented yet return 0 # Not implemented yet
of 0x203: # READ of 0x203: # READ
# kprint("[Syscall] READ(fd="); kprint_hex(a0); kprint(")\n") # M4: Check console.input capability for stdin (fd 0)
var vres = -2 if a0 == 0 and current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x1000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x1000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
# M4: Check VFS capability for file reads (fd >= 3)
if a0 >= 3 and current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_READ):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_READ, 0)
return 0xFFFFFFFFFFFFFFFF'u
var vres: int64 = -2
if a0 >= 3:
vres = ion_vfs_read(int32(a0), cast[pointer](a1), uint64(a2))
# Fallback to PTY if FD=0 or VFS returned TTY mode (-2)
if a0 == 0 or vres == -2: if a0 == 0 or vres == -2:
let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0 let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0
while true: while true:
if pty_has_data_for_slave(pid): if pty_has_data_for_slave(pid):
var buf: array[1, byte] var buf: array[1, byte]
let n = pty_read_slave(PTY_SLAVE_BASE + pid, addr buf[0], 1) let n = pty_read_slave(int(PTY_SLAVE_BASE + pid), addr buf[0], 1)
if n > 0: if n > 0:
# kprint("[Kernel] READ delivered PTY byte: "); kprint_hex8(buf[0]); kprint("\n")
cast[ptr UncheckedArray[byte]](a1)[0] = buf[0] cast[ptr UncheckedArray[byte]](a1)[0] = buf[0]
return 1 return 1
var pkt: IonPacket var pkt: IonPacket
if chan_input.recv(pkt): if chan_input.recv(pkt):
kprint("[Kernel] Got Input Packet of len: "); kprint_hex(uint64(pkt.len)); kprint("\n") for i in 0..<int(pkt.len):
let n = if uint64(pkt.len) < a2: uint64(pkt.len) else: a2 pty_push_input(pid, char(pkt.data[i]))
if n > 0: ion_free(pkt)
let data = cast[ptr UncheckedArray[byte]](pkt.data)
for i in 0 ..< int(n):
kprint(" Input Char: "); kprint(cast[cstring](unsafeAddr data[i])); kprint("\n")
pty_push_input(pid, char(data[i]))
ion_free_raw(pkt.id)
# Loop again to read from PTY
else: else:
current_fiber.sleep_until = 0xFFFFFFFFFFFFFFFF'u64 current_fiber.is_blocked = true
current_fiber.blocked_on_mask = 0x1000 # Terminal Input
current_fiber.sleep_until = 0xFFFFFFFFFFFFFFFF'u64 # Infinite
fiber_yield() fiber_yield()
return uint(vres) return uint(vres)
of 0x204: # WRITE of 0x204: # WRITE
if a0 == 1 or a0 == 2: if a0 == 1 or a0 == 2:
# M4: Check console.output capability (channel 0x1001)
if current_fiber != nil and current_fiber.cspace_id < 16:
if not cspace_check_channel(current_fiber.cspace_id, 0x1001, PERM_WRITE):
discard emit_access_denied(current_fiber.id, 0x1001, PERM_WRITE, 0)
return 0xFFFFFFFFFFFFFFFF'u
console_write(cast[pointer](a1), csize_t(a2)) console_write(cast[pointer](a1), csize_t(a2))
let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0 let pid = if current_fiber.pty_id >= 0: current_fiber.pty_id else: 0
discard pty_write_slave(PTY_SLAVE_BASE + pid, cast[ptr byte](a1), int(a2)) discard pty_write_slave(PTY_SLAVE_BASE + pid, cast[ptr byte](a1), int(a2))
return a2 return a2
# var vres = libc_impl.libc_impl_write(int(a0), cast[pointer](a1), uint64(a2)) elif a0 >= 3:
var vres = -1 # M4: Check VFS write capability (channel 0x2000)
return uint(vres) if current_fiber != nil and current_fiber.cspace_id < 16:
of 0x205: return 0 # IOCTL stub if not cspace_check_channel(current_fiber.cspace_id, 0x2000, PERM_WRITE):
discard emit_access_denied(current_fiber.id, 0x2000, PERM_WRITE, 0)
return 0xFFFFFFFFFFFFFFFF'u
return uint(ion_vfs_write(int32(a0), cast[pointer](a1), uint64(a2)))
return 0xFFFFFFFFFFFFFFFF'u # -1
of 0x600: # EXECV (Legacy - use SYS_SPAWN_FIBER instead) of 0x600: # EXECV (Legacy - use SYS_SPAWN_FIBER instead)
# Manual copy path to subject_loading_path # Manual copy path to subject_loading_path
let p = cast[ptr UncheckedArray[char]](a0) let p = cast[ptr UncheckedArray[char]](a0)
@ -585,8 +889,6 @@ proc ion_wait_multi*(mask: uint64): int32 {.exportc, cdecl.} =
proc kmain() {.exportc, cdecl.} = proc kmain() {.exportc, cdecl.} =
var next_mmio_addr {.importc: "virtio_pci_next_mmio_addr", nodecl.}: uint32 var next_mmio_addr {.importc: "virtio_pci_next_mmio_addr", nodecl.}: uint32
kprint("\n[Kernel] next_mmio_addr check: ")
kprint_hex(uint64(next_mmio_addr))
kprintln("\nNexus Sovereign Core v1.1.2 Starting...") kprintln("\nNexus Sovereign Core v1.1.2 Starting...")
# HAL Hardware Inits # HAL Hardware Inits
@ -636,6 +938,8 @@ proc kmain() {.exportc, cdecl.} =
sys.fn_vfs_read = ion_vfs_read sys.fn_vfs_read = ion_vfs_read
sys.fn_vfs_list = ion_vfs_list sys.fn_vfs_list = ion_vfs_list
sys.fn_vfs_write = wrapper_vfs_write sys.fn_vfs_write = wrapper_vfs_write
sys.fn_vfs_dup = ion_vfs_dup
sys.fn_vfs_dup2 = ion_vfs_dup2
sys.fn_wait_multi = ion_wait_multi sys.fn_wait_multi = ion_wait_multi
# Point to user slab allocator (shared memory) instead of kernel pool # Point to user slab allocator (shared memory) instead of kernel pool
sys.fn_ion_alloc = ion_user_alloc_systable sys.fn_ion_alloc = ion_user_alloc_systable
@ -669,11 +973,16 @@ proc kmain() {.exportc, cdecl.} =
chan_net_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xC000) chan_net_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xC000)
chan_net_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xE000) chan_net_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0xE000)
# Project LibWeb: LWF Rings (after user slab at 0x110000)
chan_lwf_rx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0x110000)
chan_lwf_tx.ring = cast[ptr HAL_Ring[IonPacket]](SYSTABLE_BASE + 0x112000)
# Initialize Shared Memory Rings # Initialize Shared Memory Rings
chan_rx.ring.mask = 255; chan_tx.ring.mask = 255 chan_rx.ring.mask = 255; chan_tx.ring.mask = 255
ring_event.mask = 255; chan_cmd.ring.mask = 255 ring_event.mask = 255; chan_cmd.ring.mask = 255
chan_input.ring.mask = 255 chan_input.ring.mask = 255
chan_net_rx.ring.mask = 255; chan_net_tx.ring.mask = 255 chan_net_rx.ring.mask = 255; chan_net_tx.ring.mask = 255
chan_lwf_rx.ring.mask = 255; chan_lwf_tx.ring.mask = 255
# Force reset pointers to zero # Force reset pointers to zero
chan_rx.ring.head = 0; chan_rx.ring.tail = 0 chan_rx.ring.head = 0; chan_rx.ring.tail = 0
chan_tx.ring.head = 0; chan_tx.ring.tail = 0 chan_tx.ring.head = 0; chan_tx.ring.tail = 0
@ -682,6 +991,8 @@ proc kmain() {.exportc, cdecl.} =
chan_input.ring.head = 0; chan_input.ring.tail = 0 chan_input.ring.head = 0; chan_input.ring.tail = 0
chan_net_rx.ring.head = 0; chan_net_rx.ring.tail = 0 chan_net_rx.ring.head = 0; chan_net_rx.ring.tail = 0
chan_net_tx.ring.head = 0; chan_net_tx.ring.tail = 0 chan_net_tx.ring.head = 0; chan_net_tx.ring.tail = 0
chan_lwf_rx.ring.head = 0; chan_lwf_rx.ring.tail = 0
chan_lwf_tx.ring.head = 0; chan_lwf_tx.ring.tail = 0
sys.s_rx = chan_rx.ring; sys.s_tx = chan_tx.ring; sys.s_event = ring_event sys.s_rx = chan_rx.ring; sys.s_tx = chan_tx.ring; sys.s_event = ring_event
sys.s_cmd = chan_cmd.ring; sys.s_input = chan_input.ring sys.s_cmd = chan_cmd.ring; sys.s_input = chan_input.ring
@ -690,6 +1001,10 @@ proc kmain() {.exportc, cdecl.} =
sys.s_net_rx = chan_net_rx.ring sys.s_net_rx = chan_net_rx.ring
sys.s_net_tx = chan_net_tx.ring sys.s_net_tx = chan_net_tx.ring
# Project LibWeb: Map LWF Rings
sys.s_lwf_rx = chan_lwf_rx.ring
sys.s_lwf_tx = chan_lwf_tx.ring
sys.magic = 0x4E585553 sys.magic = 0x4E585553
sys.fb_addr = fb_kern_get_addr() sys.fb_addr = fb_kern_get_addr()
sys.fb_width = 1920; sys.fb_height = 1080; sys.fb_stride = 1920 * 4; sys.fb_bpp = 32 sys.fb_width = 1920; sys.fb_height = 1080; sys.fb_stride = 1920 * 4; sys.fb_bpp = 32
@ -726,46 +1041,52 @@ proc kmain() {.exportc, cdecl.} =
discard emit_capability_grant(2, 2, 0x1001, 1, shell_spawn_id) # Log event discard emit_capability_grant(2, 2, 0x1001, 1, shell_spawn_id) # Log event
kprintln("[CSpace] Granted console capabilities to NexShell") kprintln("[CSpace] Granted console capabilities to NexShell")
# Grant console output to Subject (fiber 4) # M4.4: Subject (fiber 4) capabilities are now manifest-driven.
discard fiber_grant_channel(4, 0x1001, PERM_WRITE) # console.output (write-only) # The BKDL manifest in the ELF binary declares what it needs.
discard emit_capability_grant(4, 2, 0x1001, 0, subject_spawn_id) # Log event # Grants are applied in subject_fiber_entry via kload_manifest_tar + apply_manifest.
kprintln("[CSpace] Granted output capability to Subject") kprintln("[CSpace] Subject capabilities deferred to BKDL manifest")
# Grant Network I/O (RX/TX) # Grant Network I/O (RX/TX) — kernel fibers only
# NetSwitch (Fiber 6): Full access to shuttle packets # NetSwitch (Fiber 6): Full access to shuttle packets
discard fiber_grant_channel(6, 0x500, PERM_READ or PERM_WRITE) # CMD_NET_TX discard fiber_grant_channel(6, 0x500, PERM_READ or PERM_WRITE) # CMD_NET_TX
discard fiber_grant_channel(6, 0x501, PERM_READ or PERM_WRITE) # CMD_NET_RX discard fiber_grant_channel(6, 0x501, PERM_READ or PERM_WRITE) # CMD_NET_RX
kprintln("[CSpace] Granted network capabilities to NetSwitch")
# Subject (Fiber 4): Needs to READ RX (0x501) and WRITE TX (0x500) # M4: Grant VFS capabilities — kernel fibers only
discard fiber_grant_channel(4, 0x500, PERM_WRITE) # Can send packets discard fiber_grant_channel(1, 0x2000, PERM_READ or PERM_WRITE) # ION: VFS (ELF loading)
discard fiber_grant_channel(4, 0x501, PERM_READ) # Can receive packets discard fiber_grant_channel(1, 0x1000, PERM_READ or PERM_WRITE) # ION: console.input
kprintln("[CSpace] Granted network capabilities to NetSwitch and Subject") discard fiber_grant_channel(1, 0x1001, PERM_READ or PERM_WRITE) # ION: console.output
discard fiber_grant_channel(2, 0x2000, PERM_READ or PERM_WRITE) # NexShell: VFS
kprintln("[CSpace] Granted VFS capabilities to kernel fibers")
# Init (Subject) lives in Cell 0 (0x88000000) - Needs 64MB for large BSS # Init (Subject) lives in Cell 0 Needs 64MB for large BSS
fiber_subject.phys_offset = 0x88000000'u64 fiber_subject.phys_offset = CELL0_BASE
let init_size = 64 * 1024 * 1024'u64 let init_size = 64 * 1024 * 1024'u64
fiber_subject.satp_value = mm_create_worker_map(cast[uint64](addr stack_subject[0]), uint64(sizeof(stack_subject)), SYSTABLE_BASE, fiber_subject.phys_offset, init_size) fiber_subject.satp_value = mm_create_worker_map(cast[uint64](addr stack_subject[0]), uint64(sizeof(stack_subject)), SYSTABLE_BASE, fiber_subject.phys_offset, init_size)
# Interrupt Setup # Interrupt Setup (Architecture-specific)
when defined(riscv64):
asm "csrsi sstatus, 2" asm "csrsi sstatus, 2"
{.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".} {.emit: "asm volatile(\"csrs sie, %0\" : : \"r\"(1L << 9));".}
let plic_base = 0x0c000000'u64 let plic_base = 0x0c000000'u64
# Priority (each IRQ has a 4-byte priority register) # Priority (each IRQ has a 4-byte priority register)
cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40) cast[ptr uint32](plic_base + 40)[] = 1 # UART (IRQ 10: 10*4 = 40)
# cast[ptr uint32](plic_base + 128)[] = 1 # VirtIO-Net (IRQ 32: 32*4 = 128)
# cast[ptr uint32](plic_base + 132)[] = 1 # VirtIO-Net (IRQ 33: 33*4 = 132)
# cast[ptr uint32](plic_base + 136)[] = 1 # VirtIO-Net (IRQ 34: 34*4 = 136)
# cast[ptr uint32](plic_base + 140)[] = 1 # VirtIO-Net (IRQ 35: 35*4 = 140)
# Enable (Supervisor Context 1) # Enable (Supervisor Context 1)
# IRQs 0-31 (Enable IRQ 10 = UART) # IRQs 0-31 (Enable IRQ 10 = UART)
cast[ptr uint32](plic_base + 0x2000 + 0x80)[] = (1'u32 shl 10) cast[ptr uint32](plic_base + 0x2000 + 0x80)[] = (1'u32 shl 10)
# IRQs 32-63
# cast[ptr uint32](plic_base + 0x2000 + 0x80 + 4)[] = 0x0000000F # Enable 32,33,34,35
# Threshold # Threshold
cast[ptr uint32](plic_base + 0x201000)[] = 0 cast[ptr uint32](plic_base + 0x201000)[] = 0
let en_addr = plic_base + 0x2000 + 0x80
let en_val = cast[ptr uint32](en_addr)[]
kprint("[PLIC] UART Enable Register at "); kprint_hex(en_addr)
kprint(" is "); kprint_hex(uint64(en_val)); kprintln("")
elif defined(arm64):
# GICv2 is initialized in aarch64_init() before Nim entry
kprintln("[GIC] Interrupts configured by HAL")
active_fibers_arr[0] = addr fiber_ion; active_fibers_arr[1] = addr fiber_nexshell active_fibers_arr[0] = addr fiber_ion; active_fibers_arr[1] = addr fiber_nexshell
active_fibers_arr[2] = addr fiber_compositor; active_fibers_arr[3] = addr fiber_netswitch active_fibers_arr[2] = addr fiber_compositor; active_fibers_arr[3] = addr fiber_netswitch
active_fibers_arr[4] = addr fiber_subject active_fibers_arr[4] = addr fiber_subject
@ -777,28 +1098,42 @@ proc kmain() {.exportc, cdecl.} =
(addr fiber_compositor).setSpectrum(Spectrum.Photon) (addr fiber_compositor).setSpectrum(Spectrum.Photon)
(addr fiber_netswitch).setSpectrum(Spectrum.Photon) (addr fiber_netswitch).setSpectrum(Spectrum.Photon)
(addr fiber_nexshell).setSpectrum(Spectrum.Matter) # Interactive (addr fiber_nexshell).setSpectrum(Spectrum.Matter) # Interactive
(addr fiber_subject).setSpectrum(Spectrum.Void) # Untrusted Background (addr fiber_subject).setSpectrum(Spectrum.Matter) # Elevated from Void
(addr fiber_child).setSpectrum(Spectrum.Void) # Child process (spawned) (addr fiber_child).setSpectrum(Spectrum.Matter) # Elevated from Void
current_fiber.setSpectrum(Spectrum.Void) # Main loop (dispatcher) current_fiber.setSpectrum(Spectrum.Void) # Main loop (dispatcher)
# M4.5: Kinetic Economy — default budgets per Spectrum tier
(addr fiber_ion).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_compositor).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_netswitch).budget_ns = default_budget_for_spectrum(Spectrum.Photon)
(addr fiber_nexshell).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
(addr fiber_subject).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
(addr fiber_child).budget_ns = default_budget_for_spectrum(Spectrum.Matter)
# Void (main loop): budget_ns = 0 → unlimited (already default)
# M4: Set initial pledge masks (using top-level set_pledge)
fiber_ion.set_pledge(PLEDGE_ALL)
fiber_compositor.set_pledge(PLEDGE_ALL)
fiber_netswitch.set_pledge(PLEDGE_ALL)
fiber_nexshell.set_pledge(PLEDGE_STDIO or PLEDGE_RPATH or PLEDGE_WPATH or PLEDGE_EXEC)
# M4.4: Subject/child pledge is set by BKDL manifest in subject_fiber_entry.
# Start with PLEDGE_STDIO as safe default (manifest will override).
fiber_subject.set_pledge(PLEDGE_STDIO)
fiber_child.set_pledge(PLEDGE_STDIO)
kprintln("[M4] Pledge masks applied (Subject/child: manifest-driven)")
# Ground Zero Phase 2: Introspection # Ground Zero Phase 2: Introspection
stl_print_summary() stl_print_summary()
kprintln("[Rumpk] Multi-Fiber Dispatcher starting...") kprintln("[Rumpk] Multi-Fiber Dispatcher starting...")
switch(addr fiber_ion) switch(addr fiber_ion)
# Exported from Zig
proc uart_poll_input() {.importc, cdecl.}
while true: while true:
if not sched_tick_spectrum(active_fibers_arr.toOpenArray(0, 5)): # ⌨️ Hardware Input Driver (Polling fallback)
# The Silence Doctrine: Wait for Interrupt uart_poll_input()
let next_wake = sched_get_next_wakeup(active_fibers_arr.toOpenArray(0, 5))
let now = sched_get_now_ns()
if next_wake > now and next_wake != 0xFFFFFFFFFFFFFFFF'u64: if not sched_tick_spectrum(active_fibers_arr):
proc rumpk_timer_set_ns(ns: uint64) {.importc, cdecl.} # Wait for data or timeout
# kprint("Interval: "); kprint_hex(next_wake - now); kprintln("") fiber_sleep(1)
rumpk_timer_set_ns(next_wake - now) # Pass interval
else:
# No timer needed (or overdue), just WFI for other interrupts (IO)
discard
asm "csrsi sstatus, 2"
asm "wfi"

View File

@ -67,6 +67,76 @@ proc kload*(path: string): uint64 =
return ehdr.e_entry return ehdr.e_entry
# --- M4.4: BKDL Manifest Extraction ---
proc streq_n(a: ptr UncheckedArray[byte], b: cstring, maxlen: int): bool =
## Compare byte array against C string, bounded by maxlen
var i = 0
while i < maxlen:
if b[i] == '\0':
return true # b ended, all matched
if a[i] != byte(b[i]):
return false
i += 1
return false
proc kload_manifest*(file_content: openArray[byte]): ManifestResult =
## Scan ELF section headers for .nexus.manifest containing BKDL data.
## Returns header=nil if no manifest found.
result.header = nil
result.caps = nil
result.count = 0
if file_content.len < int(sizeof(Elf64_Ehdr)):
return
let ehdr = cast[ptr Elf64_Ehdr](unsafeAddr file_content[0])
let base = cast[uint64](unsafeAddr file_content[0])
let file_len = uint64(file_content.len)
# Validate section header table is within file
if ehdr.e_shoff == 0 or ehdr.e_shnum == 0:
return
if ehdr.e_shoff + uint64(ehdr.e_shnum) * uint64(ehdr.e_shentsize) > file_len:
return
# Get string table section (shstrtab)
if ehdr.e_shstrndx >= ehdr.e_shnum:
return
let strtab_shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(ehdr.e_shstrndx) * uint64(ehdr.e_shentsize))
if strtab_shdr.sh_offset + strtab_shdr.sh_size > file_len:
return
let strtab = cast[ptr UncheckedArray[byte]](base + strtab_shdr.sh_offset)
# Scan sections for .nexus.manifest
let target = cstring(".nexus.manifest")
for i in 0 ..< int(ehdr.e_shnum):
let shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(i) * uint64(ehdr.e_shentsize))
if shdr.sh_name < uint32(strtab_shdr.sh_size):
let name_ptr = cast[ptr UncheckedArray[byte]](cast[uint64](strtab) + uint64(shdr.sh_name))
let remaining = int(strtab_shdr.sh_size) - int(shdr.sh_name)
if streq_n(name_ptr, target, remaining):
# Found .nexus.manifest section
if shdr.sh_offset + shdr.sh_size > file_len:
return # Section data out of bounds
if shdr.sh_size < uint64(sizeof(BkdlHeader)):
return # Too small
let hdr = cast[ptr BkdlHeader](base + shdr.sh_offset)
if hdr.magic != BKDL_MAGIC or hdr.version != BKDL_VERSION:
kprintln("[Manifest] Invalid BKDL magic/version")
return
let expected_size = uint64(sizeof(BkdlHeader)) + uint64(hdr.cap_count) * uint64(sizeof(CapDescriptor))
if expected_size > shdr.sh_size:
kprintln("[Manifest] BKDL cap_count exceeds section size")
return
result.header = hdr
result.caps = cast[ptr UncheckedArray[CapDescriptor]](base + shdr.sh_offset + uint64(sizeof(BkdlHeader)))
result.count = int(hdr.cap_count)
return
proc kexec*(path: string) = proc kexec*(path: string) =
let entry = kload(path) let entry = kload(path)
if entry != 0: if entry != 0:

View File

@ -37,8 +37,48 @@ type
p_memsz*: uint64 p_memsz*: uint64
p_align*: uint64 p_align*: uint64
Elf64_Shdr* {.packed.} = object
sh_name*: uint32
sh_type*: uint32
sh_flags*: uint64
sh_addr*: uint64
sh_offset*: uint64
sh_size*: uint64
sh_link*: uint32
sh_info*: uint32
sh_addralign*: uint64
sh_entsize*: uint64
const const
PT_LOAD* = 1 PT_LOAD* = 1
PT_NOTE* = 4
PF_X* = 1 PF_X* = 1
PF_W* = 2 PF_W* = 2
PF_R* = 4 PF_R* = 4
# SPEC-071: BKDL (Binary Manifest) types
const
BKDL_MAGIC* = 0x4E585553'u32 # "NXUS" (little-endian)
BKDL_VERSION* = 1'u16
type
BkdlHeader* {.packed.} = object
magic*: uint32
version*: uint16
flags*: uint16
signature*: array[64, uint8] # Ed25519 (unchecked in dev mode)
pubkey_hash*: array[32, uint8] # SHA-256 of signing key
cap_count*: uint16
blob_size*: uint32
entry_point*: uint64 # 0 = use ELF e_entry
CapDescriptor* {.packed.} = object
cap_type*: uint8 # CapType enum value
perms*: uint8 # Permission bitmask
reserved*: uint16 # Alignment padding
resource_id*: uint64 # SipHash of resource name
ManifestResult* = object
header*: ptr BkdlHeader
caps*: ptr UncheckedArray[CapDescriptor]
count*: int

View File

@ -34,6 +34,11 @@ proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.} proc kprint_hex(v: uint64) {.importc, cdecl.}
proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.} proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Project LibWeb: LWF Adapter (Zig FFI)
proc lwf_validate(data: pointer, len: uint16): uint8 {.importc, cdecl.}
const ETHERTYPE_LWF = 0x4C57'u16 # "LW" — Libertaria Wire Frame
# Membrane Infrastructure (LwIP Glue) # Membrane Infrastructure (LwIP Glue)
proc membrane_init*() {.importc, cdecl.} proc membrane_init*() {.importc, cdecl.}
proc pump_membrane_stack*() {.importc, cdecl.} proc pump_membrane_stack*() {.importc, cdecl.}
@ -73,6 +78,11 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
case etype: case etype:
of 0x0800, 0x0806, 0x86DD: # IPv4, ARP, IPv6 of 0x0800, 0x0806, 0x86DD: # IPv4, ARP, IPv6
# NOTE(LibWeb): IPv6 is the first-class citizen for sovereign mesh.
# Most chapter nodes sit behind consumer NAT — IPv6 provides
# end-to-end addressability without traversal hacks.
# IPv4 is the fallback, not the default. Membrane/Transport
# layer enforces preference order (IPv6 → IPv4).
# Route to Legacy/LwIP Membrane # Route to Legacy/LwIP Membrane
if not chan_net_rx.send(pkt): if not chan_net_rx.send(pkt):
# Ring full (Backpressure) # Ring full (Backpressure)
@ -86,6 +96,21 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
ion_free(pkt) ion_free(pkt)
return true # Handled (dropped) return true # Handled (dropped)
of ETHERTYPE_LWF: # Project LibWeb: Libertaria Wire Frame
# Validate LWF magic before routing (Fast Drop)
let lwf_data = cast[pointer](cast[uint64](pkt.data) + 14) # Skip Eth header
let lwf_len = if pkt.len > 14: uint16(pkt.len - 14) else: 0'u16
if lwf_len >= 88 and lwf_validate(lwf_data, lwf_len) == 1:
# Valid LWF — route to dedicated LWF channel
if not chan_lwf_rx.send(pkt):
ion_free(pkt) # Backpressure
return false
return true
else:
# Invalid LWF frame — drop
ion_free(pkt)
return false
else: else:
# Drop unknown EtherTypes (Security/Sovereignty) # Drop unknown EtherTypes (Security/Sovereignty)
ion_free(pkt) ion_free(pkt)
@ -116,7 +141,7 @@ proc fiber_netswitch_entry*() {.cdecl.} =
rx_activity = true rx_activity = true
inc rx_count inc rx_count
# 3. TX PATH: Userland -> Hardware # 3. TX PATH: Userland -> Hardware (Legacy/LwIP)
var tx_pkt: IonPacket var tx_pkt: IonPacket
while chan_net_tx.recv(tx_pkt): while chan_net_tx.recv(tx_pkt):
if tx_pkt.data != nil and tx_pkt.len > 0: if tx_pkt.data != nil and tx_pkt.len > 0:
@ -125,6 +150,15 @@ proc fiber_netswitch_entry*() {.cdecl.} =
ion_free(tx_pkt) ion_free(tx_pkt)
tx_activity = true tx_activity = true
# 3b. TX PATH: LWF Egress (Project LibWeb)
var lwf_pkt: IonPacket
while chan_lwf_tx.recv(lwf_pkt):
if lwf_pkt.data != nil and lwf_pkt.len > 0:
virtio_net_send(cast[pointer](lwf_pkt.data), uint32(lwf_pkt.len))
inc tx_count
ion_free(lwf_pkt)
tx_activity = true
# 4. Periodically print stats # 4. Periodically print stats
let now = get_now_ns() let now = get_now_ns()
if now - last_stat_print > 5000000000'u64: # 5 seconds if now - last_stat_print > 5000000000'u64: # 5 seconds

View File

@ -161,6 +161,24 @@ proc emit_access_denied*(
0, 0 0, 0
) )
proc emit_policy_violation*(
fiber_id: uint64,
budget_ns: uint64,
actual_ns: uint64,
violation_count: uint32,
cause_id: uint64 = 0
): uint64 {.exportc, cdecl.} =
## Emit budget violation event to STL (The Ratchet, M4.5)
return stl_emit(
uint16(EvPolicyViolation),
fiber_id,
budget_ns,
cause_id,
actual_ns,
uint64(violation_count),
0
)
## Initialization ## Initialization
proc init_stl_subsystem*() = proc init_stl_subsystem*() =
## Initialize the STL subsystem (call from kmain) ## Initialize the STL subsystem (call from kmain)

View File

@ -175,11 +175,18 @@ proc pty_write_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.}
if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b): if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b):
written += 1 written += 1
# Mirror to UART console
var c_buf: array[2, char]
c_buf[0] = char(b)
c_buf[1] = '\0'
kprint(cast[cstring](addr c_buf[0]))
# Also render to FB terminal # Also render to FB terminal
term_putc(char(b)) term_putc(char(b))
else: else:
break break
# Render frame after batch write # Render frame after batch write
if written > 0: if written > 0:
term_render() term_render()

View File

@ -42,6 +42,18 @@ import fiber
# We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper). # We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).
proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.} proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
proc console_write(p: pointer, len: csize_t) {.importc: "hal_console_write", cdecl.}
proc uart_print_hex(v: uint64) {.importc: "uart_print_hex", cdecl.}
proc uart_print_hex8(v: uint8) {.importc: "uart_print_hex8", cdecl.}
# M4.5: STL emission for budget violations (The Ratchet)
proc emit_policy_violation*(fiber_id, budget_ns, actual_ns: uint64,
violation_count: uint32, cause_id: uint64): uint64 {.importc, cdecl.}
# Forward declaration — implementation is in THE RATCHET section below
proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64)
var photon_idx, matter_idx, gravity_idx, void_idx: int
# Forward declaration for channel data check (provided by kernel/channels) # Forward declaration for channel data check (provided by kernel/channels)
proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.} proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.}
@ -63,11 +75,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 1: PHOTON (Hard Real-Time / Hardware Driven) # Phase 1: PHOTON (Hard Real-Time / Hardware Driven)
# ========================================================= # =========================================================
var run_photon = false var run_photon = false
for f in fibers: for i in 0..<fibers.len:
let idx = (photon_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Photon: if f != nil and f.getSpectrum() == Spectrum.Photon:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true photon_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_photon = true run_photon = true
if run_photon: return true if run_photon: return true
@ -76,11 +94,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 2: MATTER (Interactive / Latency Sensitive) # Phase 2: MATTER (Interactive / Latency Sensitive)
# ========================================================= # =========================================================
var run_matter = false var run_matter = false
for f in fibers: for i in 0..<fibers.len:
let idx = (matter_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Matter: if f != nil and f.getSpectrum() == Spectrum.Matter:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true matter_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_matter = true run_matter = true
if run_matter: return true if run_matter: return true
@ -89,11 +113,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 3: GRAVITY (Throughput / Background) # Phase 3: GRAVITY (Throughput / Background)
# ========================================================= # =========================================================
var run_gravity = false var run_gravity = false
for f in fibers: for i in 0..<fibers.len:
let idx = (gravity_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Gravity: if f != nil and f.getSpectrum() == Spectrum.Gravity:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
switch(f); return true gravity_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else: else:
run_gravity = true run_gravity = true
if run_gravity: return true if run_gravity: return true
@ -101,11 +131,16 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# ========================================================= # =========================================================
# Phase 4: VOID (Scavenger) # Phase 4: VOID (Scavenger)
# ========================================================= # =========================================================
for f in fibers: for i in 0..<fibers.len:
let idx = (void_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Void: if f != nil and f.getSpectrum() == Spectrum.Void:
if is_runnable(f, now): if is_runnable(f, now):
if f != current_fiber: if f != current_fiber:
void_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f) switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true return true
else: else:
return true return true
@ -128,10 +163,20 @@ proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
return min_wakeup return min_wakeup
# ========================================================= # =========================================================
# THE RATCHET (Post-Execution Analysis) # M4.5: Budget Defaults Per Spectrum Tier
# ========================================================= # =========================================================
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.} proc default_budget_for_spectrum*(s: Spectrum): uint64 =
## Return the default budget_ns for a given Spectrum tier
case s
of Spectrum.Photon: return 2_000_000'u64 # 2ms — hard real-time
of Spectrum.Matter: return 10_000_000'u64 # 10ms — interactive
of Spectrum.Gravity: return 50_000_000'u64 # 50ms — batch
of Spectrum.Void: return 0'u64 # unlimited — scavenger
# =========================================================
# THE RATCHET (Post-Execution Analysis)
# =========================================================
proc sched_log(msg: string) = proc sched_log(msg: string) =
if msg.len > 0: if msg.len > 0:
@ -169,7 +214,16 @@ proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64) =
# Budget enforcement # Budget enforcement
if f.budget_ns > 0 and burst_ns > f.budget_ns: if f.budget_ns > 0 and burst_ns > f.budget_ns:
f.violations += 1 f.violations += 1
console_write(cstring("[Ratchet] Violation: fiber exceeded budget\n"), 42) discard emit_policy_violation(f.id, f.budget_ns, burst_ns, f.violations, 0)
console_write(cstring("[Ratchet] Budget violation fiber=0x"), 33)
uart_print_hex(f.id)
console_write(cstring(" burst="), 7)
uart_print_hex(burst_ns)
console_write(cstring(" budget="), 8)
uart_print_hex(f.budget_ns)
console_write(cstring(" strikes="), 9)
uart_print_hex(uint64(f.violations))
console_write(cstring("\n"), 1)
if f.violations >= 3: if f.violations >= 3:
sched_demote(f) sched_demote(f)

View File

@ -55,9 +55,14 @@ fn halt_impl() callconv(.c) noreturn {
} }
} }
// ========================================================= const builtin = @import("builtin");
// Exports for Nim FFI
// ========================================================= // Sovereign timer canonical time source for the entire kernel
extern fn rumpk_timer_now_ns() u64;
export fn hal_get_time_ns() u64 {
return rumpk_timer_now_ns();
}
export fn rumpk_console_write(ptr: [*]const u8, len: usize) void { export fn rumpk_console_write(ptr: [*]const u8, len: usize) void {
hal.console_write(ptr, len); hal.console_write(ptr, len);
@ -113,17 +118,27 @@ pub const cspace_check_perm = cspace.cspace_check_perm;
pub const surface = @import("surface.zig"); pub const surface = @import("surface.zig");
comptime { comptime {
// Force analysis // Force analysis architecture-independent modules
_ = @import("stubs.zig"); _ = @import("stubs.zig");
_ = @import("mm.zig");
_ = @import("channel.zig"); _ = @import("channel.zig");
_ = @import("uart.zig"); _ = @import("uart.zig");
_ = @import("virtio_block.zig");
_ = @import("virtio_net.zig");
_ = @import("virtio_pci.zig");
_ = @import("ontology.zig"); _ = @import("ontology.zig");
_ = @import("entry_riscv.zig");
_ = @import("cspace.zig"); _ = @import("cspace.zig");
_ = @import("surface.zig"); _ = @import("surface.zig");
_ = @import("initrd.zig"); _ = @import("initrd.zig");
// Architecture-specific modules
if (builtin.cpu.arch == .riscv64) {
_ = @import("mm.zig");
_ = @import("virtio_block.zig");
_ = @import("virtio_net.zig");
_ = @import("virtio_pci.zig");
_ = @import("entry_riscv.zig");
} else if (builtin.cpu.arch == .aarch64) {
_ = @import("entry_aarch64.zig");
_ = @import("gic.zig");
_ = @import("virtio_mmio.zig");
_ = @import("virtio_block.zig");
_ = @import("virtio_net.zig");
}
} }

View File

@ -13,6 +13,7 @@
//! SAFETY: All operations use atomic loads/stores with proper memory fences. //! SAFETY: All operations use atomic loads/stores with proper memory fences.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
pub const IonPacket = extern struct { pub const IonPacket = extern struct {
data: u64, data: u64,
@ -41,8 +42,8 @@ pub fn Ring(comptime T: type) type {
// INVARIANT 1: The Handle Barrier // INVARIANT 1: The Handle Barrier
fn validate_ring_ptr(ptr: u64) void { fn validate_ring_ptr(ptr: u64) void {
// 0x8000_0000 is kernel base, 0x8300_0000 is ION base. const min_valid: u64 = if (builtin.cpu.arch == .aarch64) 0x4000_0000 else 0x8000_0000;
if (ptr < 0x8000_0000) { if (ptr < min_valid) {
@panic("HAL: Invariant Violation - Invalid Ring Pointer"); @panic("HAL: Invariant Violation - Invalid Ring Pointer");
} }
} }
@ -72,7 +73,11 @@ fn popGeneric(comptime T: type, ring: *Ring(T), out_pkt: *T) bool {
} }
// Ensure we see data written by producer before reading it // Ensure we see data written by producer before reading it
asm volatile ("fence r, rw" ::: .{ .memory = true }); switch (builtin.cpu.arch) {
.riscv64 => asm volatile ("fence r, rw" ::: .{ .memory = true }),
.aarch64 => asm volatile ("dmb ld" ::: .{ .memory = true }),
else => @compileError("unsupported arch"),
}
out_pkt.* = ring.data[tail & ring.mask]; out_pkt.* = ring.data[tail & ring.mask];
const next = (tail + 1) & ring.mask; const next = (tail + 1) & ring.mask;

View File

@ -230,6 +230,19 @@ pub export fn cspace_check_perm(fiber_id: u64, slot: usize, perm_bits: u8) bool
return cap.has_perm(perm); return cap.has_perm(perm);
} }
/// Check if fiber has Channel capability for given channel_id with required permission (C ABI)
/// Scans all CSpace slots for a matching Channel capability by object_id.
pub export fn cspace_check_channel(fiber_id: u64, channel_id: u64, perm_bits: u8) bool {
const cs = cspace_get(fiber_id) orelse return false;
const perm: CapPerms = @bitCast(perm_bits);
for (&cs.slots) |*cap| {
if (cap.cap_type == .Channel and cap.object_id == channel_id and cap.has_perm(perm)) {
return true;
}
}
return false;
}
// Unit tests // Unit tests
test "Capability creation and validation" { test "Capability creation and validation" {
const cap = Capability{ const cap = Capability{

View File

@ -308,7 +308,7 @@ export fn rss_trap_handler(frame: *TrapFrame) void {
const irq = PLIC_CLAIM.*; const irq = PLIC_CLAIM.*;
if (irq == 10) { // UART0 is IRQ 10 on Virt machine if (irq == 10) { // UART0 is IRQ 10 on Virt machine
// uart.print("[IRQ] 10\n"); uart.print("[IRQ] 10\n");
uart_input.poll_input(); uart_input.poll_input();
} else if (irq >= 32 and irq <= 35) { } else if (irq >= 32 and irq <= 35) {
virtio_net.virtio_net_poll(); virtio_net.virtio_net_poll();
@ -447,6 +447,13 @@ export fn hal_io_init() void {
virtio_block.init(); virtio_block.init();
} }
export fn hal_panic(msg: [*:0]const u8) callconv(.c) noreturn {
uart.print("[HAL PANIC] ");
uart.print(std.mem.span(msg));
uart.print("\n");
rumpk_halt();
}
export fn rumpk_halt() noreturn { export fn rumpk_halt() noreturn {
uart.print("[Rumpk RISC-V] Halting.\n"); uart.print("[Rumpk RISC-V] Halting.\n");
while (true) { while (true) {

View File

@ -205,8 +205,10 @@ pub fn create_worker_map(stack_base: u64, stack_size: u64, packet_addr: u64, phy
try map_range(root, 0x40000000, 0x40000000, 0x10000000, PTE_R | PTE_W); // PCIe MMIO try map_range(root, 0x40000000, 0x40000000, 0x10000000, PTE_R | PTE_W); // PCIe MMIO
try map_range(root, 0x20000000, 0x20000000, 0x10000, PTE_R | PTE_W); // PTY Slave try map_range(root, 0x20000000, 0x20000000, 0x10000, PTE_R | PTE_W); // PTY Slave
// 4. Overlap stack with user access // 4. Overlap stack with user access (Optional)
if (stack_base != 0) {
try map_range(root, stack_base, stack_base, stack_size, PTE_R | PTE_W | PTE_U); try map_range(root, stack_base, stack_base, stack_size, PTE_R | PTE_W | PTE_U);
}
// 5. Shared SysTable & Rings & User Slab (0x83000000) - Map 256KB (64 pages; covers up to 0x40000) // 5. Shared SysTable & Rings & User Slab (0x83000000) - Map 256KB (64 pages; covers up to 0x40000)
var j: u64 = 0; var j: u64 = 0;

View File

@ -194,10 +194,12 @@ pub export fn stl_init() void {
stl_initialized = true; stl_initialized = true;
} }
/// Get current timestamp (placeholder - will be replaced by HAL timer) /// Sovereign timer canonical time source for all kernel timestamps
extern fn rumpk_timer_now_ns() u64;
/// Get current timestamp in nanoseconds since boot
fn get_timestamp_ns() u64 { fn get_timestamp_ns() u64 {
// TODO: Integrate with HAL timer return rumpk_timer_now_ns();
return 0;
} }
/// Emit event to STL (C ABI) /// Emit event to STL (C ABI)

View File

@ -15,6 +15,9 @@
const uart = @import("uart.zig"); const uart = @import("uart.zig");
// Sovereign timer canonical time source for the entire kernel
extern fn rumpk_timer_now_ns() u64;
// ========================================================= // =========================================================
// Heap Stubs (Bump Allocator with Block Headers) // Heap Stubs (Bump Allocator with Block Headers)
// ========================================================= // =========================================================
@ -137,13 +140,9 @@ export fn calloc(nmemb: usize, size: usize) ?*anyopaque {
// ========================================================= // =========================================================
export fn get_ticks() u32 { export fn get_ticks() u32 {
var time_val: u64 = 0; // Delegate to sovereign timer single source of truth for all time
asm volatile ("rdtime %[ret]" const ns = rumpk_timer_now_ns();
: [ret] "=r" (time_val), return @truncate(ns / 1_000_000); // ns ms
);
// QEMU 'virt' RISC-V timebase is 10MHz (10,000,000 Hz).
// Convert to milliseconds: val / 10,000.
return @truncate(time_val / 10000);
} }
// export fn rumpk_timer_set_ns(ns: u64) void { // export fn rumpk_timer_set_ns(ns: u64) void {
@ -160,10 +159,10 @@ export fn nexshell_main() void {
} }
extern fn k_handle_syscall(nr: usize, a0: usize, a1: usize, a2: usize) usize; extern fn k_handle_syscall(nr: usize, a0: usize, a1: usize, a2: usize) usize;
export fn exit(code: c_int) noreturn { // export fn exit(code: c_int) noreturn {
_ = code; // _ = code;
while (true) asm volatile ("wfi"); // while (true) asm volatile ("wfi");
} // }
// ========================================================= // =========================================================
// Atomic Stubs (To resolve linker errors with libcompiler_rt) // Atomic Stubs (To resolve linker errors with libcompiler_rt)

View File

@ -13,7 +13,7 @@
//! DECISION(Alloc): Bump allocator chosen for simplicity and determinism. //! DECISION(Alloc): Bump allocator chosen for simplicity and determinism.
//! Memory is never reclaimed; system reboots to reset. //! Memory is never reclaimed; system reboots to reset.
const uart = @import("uart.zig"); // const uart = @import("uart.zig");
// ========================================================= // =========================================================
// Heap Stubs (Bump Allocator with Block Headers) // Heap Stubs (Bump Allocator with Block Headers)
@ -27,11 +27,13 @@ var heap_idx: usize = 0;
var heap_init_done: bool = false; var heap_init_done: bool = false;
export fn debug_print(s: [*]const u8, len: usize) void { export fn debug_print(s: [*]const u8, len: usize) void {
uart.print(s[0..len]); _ = s;
_ = len;
// TODO: Use syscall for userland debug printing
} }
export fn kprint_hex(value: u64) void { export fn kprint_hex(value: u64) void {
uart.print_hex(value); _ = value;
} }
// Header structure (64 bytes aligned to match LwIP MEM_ALIGNMENT) // Header structure (64 bytes aligned to match LwIP MEM_ALIGNMENT)
@ -45,9 +47,9 @@ export fn malloc(size: usize) ?*anyopaque {
if (!heap_init_done) { if (!heap_init_done) {
if (heap_idx != 0) { if (heap_idx != 0) {
uart.print("[Alloc] WARNING: BSS NOT ZEROED! heap_idx: "); // uart.print("[Alloc] WARNING: BSS NOT ZEROED! heap_idx: ");
uart.print_hex(heap_idx); // uart.print_hex(heap_idx);
uart.print("\n"); // uart.print("\n");
heap_idx = 0; heap_idx = 0;
} }
heap_init_done = true; heap_init_done = true;
@ -58,11 +60,11 @@ export fn malloc(size: usize) ?*anyopaque {
const aligned_idx = (heap_idx + align_mask) & ~align_mask; const aligned_idx = (heap_idx + align_mask) & ~align_mask;
if (aligned_idx + total_needed > heap.len) { if (aligned_idx + total_needed > heap.len) {
uart.print("[Alloc] OOM! Size: "); // uart.print("[Alloc] OOM! Size: ");
uart.print_hex(size); // uart.print_hex(size);
uart.print(" Used: "); // uart.print(" Used: ");
uart.print_hex(heap_idx); // uart.print_hex(heap_idx);
uart.print("\n"); // uart.print("\n");
return null; return null;
} }
@ -137,3 +139,29 @@ export fn calloc(nmemb: usize, size: usize) ?*anyopaque {
export fn get_ticks() u32 { export fn get_ticks() u32 {
return 0; // TODO: Implement real timer return 0; // TODO: Implement real timer
} }
// export fn strlen(s: [*]const u8) usize {
// var i: usize = 0;
// while (s[i] != 0) : (i += 1) {}
// return i;
// }
// export fn fwrite(ptr: ?*anyopaque, size: usize, nmemb: usize, stream: ?*anyopaque) usize {
// _ = ptr;
// _ = size;
// _ = nmemb;
// _ = stream;
// return 0;
// }
// export fn fflush(stream: ?*anyopaque) c_int {
// _ = stream;
// return 0;
// }
// export fn write(fd: c_int, buf: ?*anyopaque, count: usize) isize {
// _ = fd;
// _ = buf;
// _ = count;
// return 0;
// }

View File

@ -34,9 +34,19 @@ pub const NS16550A_LCR: usize = 0x03; // Line Control Register
// Input logic moved to uart_input.zig // Input logic moved to uart_input.zig
// PL011 Additional Registers
pub const PL011_IBRD: usize = 0x24; // Integer Baud Rate Divisor
pub const PL011_FBRD: usize = 0x28; // Fractional Baud Rate Divisor
pub const PL011_LCR_H: usize = 0x2C; // Line Control
pub const PL011_CR: usize = 0x30; // Control
pub const PL011_IMSC: usize = 0x38; // Interrupt Mask Set/Clear
pub const PL011_ICR: usize = 0x44; // Interrupt Clear
pub const PL011_RXFE: u32 = 1 << 4; // Receive FIFO Empty
pub fn init() void { pub fn init() void {
switch (builtin.cpu.arch) { switch (builtin.cpu.arch) {
.riscv64 => init_riscv(), .riscv64 => init_riscv(),
.aarch64 => init_aarch64(),
else => {}, else => {},
} }
} }
@ -107,6 +117,78 @@ pub fn init_riscv() void {
// uart_input.poll_input(); // We cannot call this here safely without dep // uart_input.poll_input(); // We cannot call this here safely without dep
} }
pub fn init_aarch64() void {
const base = PL011_BASE;
// 1. Disable UART during setup
const cr: *volatile u32 = @ptrFromInt(base + PL011_CR);
cr.* = 0;
// 2. Clear all pending interrupts
const icr: *volatile u32 = @ptrFromInt(base + PL011_ICR);
icr.* = 0x7FF;
// 3. Set baud rate (115200 @ 24MHz QEMU clock)
// IBRD = 24000000 / (16 * 115200) = 13
// FBRD = ((0.0208... * 64) + 0.5) = 1
const ibrd: *volatile u32 = @ptrFromInt(base + PL011_IBRD);
const fbrd: *volatile u32 = @ptrFromInt(base + PL011_FBRD);
ibrd.* = 13;
fbrd.* = 1;
// 4. Line Control: 8N1, FIFO enable
const lcr_h: *volatile u32 = @ptrFromInt(base + PL011_LCR_H);
lcr_h.* = (0x3 << 5) | (1 << 4); // WLEN=8bit, FEN=1
// 5. Enable receive interrupt
const imsc: *volatile u32 = @ptrFromInt(base + PL011_IMSC);
imsc.* = (1 << 4); // RXIM: Receive interrupt mask
// 6. Enable UART: TXE + RXE + UARTEN
cr.* = (1 << 8) | (1 << 9) | (1 << 0); // TXE | RXE | UARTEN
// --- LOOPBACK TEST ---
// PL011 has loopback via CR bit 7 (LBE)
cr.* = cr.* | (1 << 7); // Enable loopback
// Write test byte
const dr: *volatile u32 = @ptrFromInt(base + PL011_DR);
const fr: *volatile u32 = @ptrFromInt(base + PL011_FR);
// Wait for TX not full
while ((fr.* & PL011_TXFF) != 0) {}
dr.* = 0xA5;
// Wait for RX not empty
var timeout: usize = 1000000;
while ((fr.* & PL011_RXFE) != 0 and timeout > 0) {
timeout -= 1;
}
var passed = false;
var reason: []const u8 = "Timeout";
if ((fr.* & PL011_RXFE) == 0) {
const val: u8 = @truncate(dr.*);
if (val == 0xA5) {
passed = true;
} else {
reason = "Data Mismatch";
}
}
// Disable loopback
cr.* = cr.* & ~@as(u32, 1 << 7);
if (passed) {
write_bytes("[UART] Loopback Test: PASS\n");
} else {
write_bytes("[UART] Loopback Test: FAIL (");
write_bytes(reason);
write_bytes(")\n");
}
}
fn write_char_arm64(c: u8) void { fn write_char_arm64(c: u8) void {
const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR); const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR); const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
@ -152,6 +234,13 @@ pub fn read_direct() ?u8 {
return thr.*; return thr.*;
} }
}, },
.aarch64 => {
const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
if ((fr.* & PL011_RXFE) == 0) {
return @truncate(dr.*);
}
},
else => {}, else => {},
} }
return null; return null;
@ -163,6 +252,11 @@ pub fn get_lsr() u8 {
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR); const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
return lsr.*; return lsr.*;
}, },
.aarch64 => {
// Return PL011 flags register (low byte)
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
return @truncate(fr.*);
},
else => return 0, else => return 0,
} }
} }

View File

@ -20,6 +20,7 @@ pub fn poll_input() void {
// Only Kernel uses this // Only Kernel uses this
const Kernel = struct { const Kernel = struct {
extern fn ion_push_stdin(ptr: [*]const u8, len: usize) void; extern fn ion_push_stdin(ptr: [*]const u8, len: usize) void;
extern fn kprint(s: [*]const u8) void;
}; };
switch (builtin.cpu.arch) { switch (builtin.cpu.arch) {
@ -34,6 +35,9 @@ pub fn poll_input() void {
const byte = thr.*; const byte = thr.*;
const byte_arr = [1]u8{byte}; const byte_arr = [1]u8{byte};
// DEBUG: Trace hardware read
Kernel.kprint("[HW Read]\n");
// Forward to Kernel Input Channel // Forward to Kernel Input Channel
Kernel.ion_push_stdin(&byte_arr, 1); Kernel.ion_push_stdin(&byte_arr, 1);

View File

@ -13,8 +13,14 @@
//! the request. Uses bounce-buffers to guarantee alignment. //! the request. Uses bounce-buffers to guarantee alignment.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const uart = @import("uart.zig"); const uart = @import("uart.zig");
const pci = @import("virtio_pci.zig");
// Comptime transport switch: PCI on RISC-V, MMIO on ARM64
const transport_mod = if (builtin.cpu.arch == .aarch64)
@import("virtio_mmio.zig")
else
@import("virtio_pci.zig");
// External C/Zig stubs // External C/Zig stubs
extern fn malloc(size: usize) ?*anyopaque; extern fn malloc(size: usize) ?*anyopaque;
@ -46,13 +52,43 @@ pub fn init() void {
} }
pub const VirtioBlkDriver = struct { pub const VirtioBlkDriver = struct {
transport: pci.VirtioTransport, transport: transport_mod.VirtioTransport,
v_desc: [*]volatile VirtioDesc, v_desc: [*]volatile VirtioDesc,
v_avail: *volatile VirtioAvail, v_avail: *volatile VirtioAvail,
v_used: *volatile VirtioUsed, v_used: *volatile VirtioUsed,
queue_size: u16, queue_size: u16,
pub fn probe() ?VirtioBlkDriver { pub fn probe() ?VirtioBlkDriver {
if (builtin.cpu.arch == .aarch64) {
return probe_mmio();
} else {
return probe_pci();
}
}
fn probe_mmio() ?VirtioBlkDriver {
const mmio = @import("virtio_mmio.zig");
const base = mmio.find_device(2) orelse { // device_id=2 is block
uart.print("[VirtIO] No VirtIO-Block MMIO device found\n");
return null;
};
uart.print("[VirtIO] Found VirtIO-Block at MMIO 0x");
uart.print_hex(base);
uart.print("\n");
var self = VirtioBlkDriver{
.transport = transport_mod.VirtioTransport.init(base),
.v_desc = undefined,
.v_avail = undefined,
.v_used = undefined,
.queue_size = 0,
};
if (self.init_device()) {
return self;
}
return null;
}
fn probe_pci() ?VirtioBlkDriver {
const PCI_ECAM_BASE: usize = 0x30000000; const PCI_ECAM_BASE: usize = 0x30000000;
const bus: u8 = 0; const bus: u8 = 0;
const func: u8 = 0; const func: u8 = 0;
@ -69,7 +105,7 @@ pub const VirtioBlkDriver = struct {
uart.print_hex(i); uart.print_hex(i);
uart.print(".0\n"); uart.print(".0\n");
var self = VirtioBlkDriver{ var self = VirtioBlkDriver{
.transport = pci.VirtioTransport.init(addr), .transport = transport_mod.VirtioTransport.init(addr),
.v_desc = undefined, .v_desc = undefined,
.v_avail = undefined, .v_avail = undefined,
.v_used = undefined, .v_used = undefined,
@ -87,29 +123,56 @@ pub const VirtioBlkDriver = struct {
if (!self.transport.probe()) return false; if (!self.transport.probe()) return false;
self.transport.reset(); self.transport.reset();
self.transport.add_status(1); self.transport.add_status(1); // ACKNOWLEDGE
self.transport.add_status(2); self.transport.add_status(2); // DRIVER
// Feature negotiation
const dev_features = self.transport.get_device_features();
_ = dev_features;
// Accept no special features for block just basic operation
self.transport.set_driver_features(0);
transport_mod.io_barrier();
// FEATURES_OK only on modern (v2) transport
if (self.transport.is_modern) {
self.transport.add_status(8); // FEATURES_OK
transport_mod.io_barrier();
}
self.transport.select_queue(0); self.transport.select_queue(0);
const count = self.transport.get_queue_size(); const max_count = self.transport.get_queue_size();
// Cap queue size for memory efficiency
const MAX_BLK_QUEUE: u16 = 128;
const count = if (max_count > MAX_BLK_QUEUE) MAX_BLK_QUEUE else max_count;
// [Desc] [Avail] [Used] (Simplified layout) // [Desc] [Avail] [Used] (Simplified layout)
const total = (count * 16) + (6 + count * 2) + 4096 + (6 + count * 8); const total = (count * 16) + (6 + count * 2) + 4096 + (6 + count * 8);
const raw_ptr = malloc(total + 4096) orelse return false; const raw_ptr = malloc(total + 4096) orelse return false;
const aligned = (@intFromPtr(raw_ptr) + 4095) & ~@as(usize, 4095); const aligned = (@intFromPtr(raw_ptr) + 4095) & ~@as(usize, 4095);
// Zero out queue memory to ensure clean state
const byte_ptr: [*]u8 = @ptrFromInt(aligned);
for (0..total) |i| {
byte_ptr[i] = 0;
}
self.v_desc = @ptrFromInt(aligned); self.v_desc = @ptrFromInt(aligned);
self.v_avail = @ptrFromInt(aligned + (count * 16)); self.v_avail = @ptrFromInt(aligned + (count * 16));
self.v_used = @ptrFromInt(aligned + (count * 16) + (6 + count * 2) + 4096); self.v_used = @ptrFromInt(aligned + (count * 16) + (6 + count * 2) + 4096);
self.queue_size = count; self.queue_size = count;
// Ensure avail/used rings start clean
self.v_avail.flags = 0;
self.v_avail.idx = 0;
self.v_used.flags = 0;
if (self.transport.is_modern) { if (self.transport.is_modern) {
self.transport.setup_modern_queue(aligned, aligned + (count * 16), @intFromPtr(self.v_used)); self.transport.setup_modern_queue(aligned, aligned + (count * 16), @intFromPtr(self.v_used));
} else { } else {
self.transport.set_queue_size(count);
self.transport.setup_legacy_queue(@intCast(aligned >> 12)); self.transport.setup_legacy_queue(@intCast(aligned >> 12));
} }
self.transport.add_status(4); self.transport.add_status(4); // DRIVER_OK
global_blk = self.*; global_blk = self.*;
uart.print("[VirtIO-Blk] Device Ready. Queue Size: "); uart.print("[VirtIO-Blk] Device Ready. Queue Size: ");
@ -151,15 +214,26 @@ pub const VirtioBlkDriver = struct {
// Submit to Avail Ring // Submit to Avail Ring
const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4)); const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4));
ring[self.v_avail.idx % self.queue_size] = 0; // Head of chain ring[self.v_avail.idx % self.queue_size] = 0; // Head of chain
asm volatile ("fence w, w" ::: .{ .memory = true }); const expected_used = self.v_used.idx +% 1;
transport_mod.io_barrier();
self.v_avail.idx +%= 1; self.v_avail.idx +%= 1;
asm volatile ("fence w, w" ::: .{ .memory = true }); transport_mod.io_barrier();
self.transport.notify(0); self.transport.notify(0);
// Wait for device (Polling) // Wait for device (Polling wait until used ring advances)
while (self.v_used.idx == 0) { var timeout: usize = 0;
asm volatile ("nop"); while (self.v_used.idx != expected_used) {
transport_mod.io_barrier();
timeout += 1;
if (timeout > 100_000_000) {
uart.print("[VirtIO-Blk] READ TIMEOUT! used.idx=");
uart.print_hex(self.v_used.idx);
uart.print(" expected=");
uart.print_hex(expected_used);
uart.print("\n");
return error.DiskError;
}
} }
if (status != 0) return error.DiskError; if (status != 0) return error.DiskError;
@ -190,14 +264,22 @@ pub const VirtioBlkDriver = struct {
const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4)); const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4));
ring[self.v_avail.idx % self.queue_size] = 3; ring[self.v_avail.idx % self.queue_size] = 3;
asm volatile ("fence w, w" ::: .{ .memory = true }); const expected_used = self.v_used.idx +% 1;
transport_mod.io_barrier();
self.v_avail.idx +%= 1; self.v_avail.idx +%= 1;
asm volatile ("fence w, w" ::: .{ .memory = true }); transport_mod.io_barrier();
self.transport.notify(0); self.transport.notify(0);
while (status == 0xFF) { // Wait for device (Polling wait until used ring advances)
asm volatile ("nop"); var timeout: usize = 0;
while (self.v_used.idx != expected_used) {
transport_mod.io_barrier();
timeout += 1;
if (timeout > 100_000_000) {
uart.print("[VirtIO-Blk] WRITE TIMEOUT!\n");
return error.DiskError;
}
} }
if (status != 0) return error.DiskError; if (status != 0) return error.DiskError;

View File

@ -14,8 +14,14 @@
//! to ensure correct synchronization with the virtual device. //! to ensure correct synchronization with the virtual device.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const uart = @import("uart.zig"); const uart = @import("uart.zig");
const pci = @import("virtio_pci.zig");
// Comptime transport switch: PCI on RISC-V, MMIO on ARM64
const transport_mod = if (builtin.cpu.arch == .aarch64)
@import("virtio_mmio.zig")
else
@import("virtio_pci.zig");
// VirtIO Feature Bits // VirtIO Feature Bits
const VIRTIO_F_VERSION_1 = 32; const VIRTIO_F_VERSION_1 = 32;
@ -117,47 +123,24 @@ pub export fn rumpk_net_init() void {
} }
pub const VirtioNetDriver = struct { pub const VirtioNetDriver = struct {
transport: pci.VirtioTransport, transport: transport_mod.VirtioTransport,
irq: u32, irq: u32,
rx_queue: ?*Virtqueue = null, rx_queue: ?*Virtqueue = null,
tx_queue: ?*Virtqueue = null, tx_queue: ?*Virtqueue = null,
pub fn get_mac(self: *VirtioNetDriver, out: [*]u8) void { pub fn get_mac(self: *VirtioNetDriver, out: [*]u8) void {
uart.print("[VirtIO-Net] Reading MAC from device_cfg...\n"); uart.print("[VirtIO-Net] Reading MAC from device config...\n");
if (self.transport.is_modern) {
// Use device_cfg directly - this is the VirtIO-Net specific config
if (self.transport.device_cfg) |cfg| {
const ptr: [*]volatile u8 = @ptrCast(cfg);
uart.print(" DeviceCfg at: ");
uart.print_hex(@intFromPtr(cfg));
uart.print("\n MAC bytes: ");
for (0..6) |i| { for (0..6) |i| {
out[i] = ptr[i]; out[i] = self.transport.get_device_config_byte(i);
uart.print_hex8(ptr[i]); uart.print_hex8(out[i]);
if (i < 5) uart.print(":"); if (i < 5) uart.print(":");
} }
uart.print("\n"); uart.print("\n");
} else {
uart.print(" ERROR: device_cfg is null!\n");
// Fallback to zeros
for (0..6) |i| {
out[i] = 0;
}
}
} else {
// Legacy
// Device Config starts at offset 20.
const base = self.transport.legacy_bar + 20;
for (0..6) |i| {
out[i] = @as(*volatile u8, @ptrFromInt(base + i)).*;
}
}
} }
pub fn init(base: usize, irq_num: u32) VirtioNetDriver { pub fn init(base: usize, irq_num: u32) VirtioNetDriver {
return .{ return .{
.transport = pci.VirtioTransport.init(base), .transport = transport_mod.VirtioTransport.init(base),
.irq = irq_num, .irq = irq_num,
.rx_queue = null, .rx_queue = null,
.tx_queue = null, .tx_queue = null,
@ -165,6 +148,32 @@ pub const VirtioNetDriver = struct {
} }
pub fn probe() ?VirtioNetDriver { pub fn probe() ?VirtioNetDriver {
if (builtin.cpu.arch == .aarch64) {
return probe_mmio();
} else {
return probe_pci();
}
}
fn probe_mmio() ?VirtioNetDriver {
uart.print("[VirtIO] Probing MMIO for networking device...\n");
const mmio = @import("virtio_mmio.zig");
const base = mmio.find_device(1) orelse { // device_id=1 is net
uart.print("[VirtIO] No VirtIO-Net MMIO device found\n");
return null;
};
uart.print("[VirtIO] Found VirtIO-Net at MMIO 0x");
uart.print_hex(base);
uart.print("\n");
const irq = mmio.slot_irq(base);
var self = VirtioNetDriver.init(base, irq);
if (self.init_device()) {
return self;
}
return null;
}
fn probe_pci() ?VirtioNetDriver {
uart.print("[VirtIO] Probing PCI for networking device...\n"); uart.print("[VirtIO] Probing PCI for networking device...\n");
const PCI_ECAM_BASE: usize = 0x30000000; const PCI_ECAM_BASE: usize = 0x30000000;
const bus: u8 = 0; const bus: u8 = 0;
@ -213,52 +222,22 @@ pub const VirtioNetDriver = struct {
self.transport.add_status(VIRTIO_CONFIG_S_ACKNOWLEDGE); self.transport.add_status(VIRTIO_CONFIG_S_ACKNOWLEDGE);
self.transport.add_status(VIRTIO_CONFIG_S_DRIVER); self.transport.add_status(VIRTIO_CONFIG_S_DRIVER);
// 4. Feature Negotiation // 4. Feature Negotiation (unified across PCI and MMIO)
if (self.transport.is_modern) { {
uart.print("[VirtIO] Starting feature negotiation...\n"); uart.print("[VirtIO] Starting feature negotiation...\n");
const dev_features = self.transport.get_device_features();
if (self.transport.common_cfg == null) {
uart.print("[VirtIO] ERROR: common_cfg is null!\n");
return false;
}
const cfg = self.transport.common_cfg.?;
uart.print("[VirtIO] common_cfg addr: ");
uart.print_hex(@intFromPtr(cfg));
uart.print("\n");
uart.print("[VirtIO] Reading device features...\n");
// Read Device Features (Page 0)
cfg.device_feature_select = 0;
asm volatile ("fence" ::: .{ .memory = true });
const f_low = cfg.device_feature;
// Read Device Features (Page 1)
cfg.device_feature_select = 1;
asm volatile ("fence" ::: .{ .memory = true });
const f_high = cfg.device_feature;
uart.print("[VirtIO] Device Features: "); uart.print("[VirtIO] Device Features: ");
uart.print_hex(f_low); uart.print_hex(dev_features);
uart.print(" ");
uart.print_hex(f_high);
uart.print("\n"); uart.print("\n");
// Accept VERSION_1 (Modern) and MAC // Accept VERSION_1 (Modern) and MAC
const accept_low: u32 = (1 << VIRTIO_NET_F_MAC); const accept: u64 = (1 << VIRTIO_NET_F_MAC) |
const accept_high: u32 = (1 << (VIRTIO_F_VERSION_1 - 32)); (@as(u64, 1) << VIRTIO_F_VERSION_1);
self.transport.set_driver_features(accept);
transport_mod.io_barrier();
uart.print("[VirtIO] Writing driver features...\n");
cfg.driver_feature_select = 0;
cfg.driver_feature = accept_low;
asm volatile ("fence" ::: .{ .memory = true });
cfg.driver_feature_select = 1;
cfg.driver_feature = accept_high;
asm volatile ("fence" ::: .{ .memory = true });
uart.print("[VirtIO] Checking feature negotiation...\n");
self.transport.add_status(VIRTIO_CONFIG_S_FEATURES_OK); self.transport.add_status(VIRTIO_CONFIG_S_FEATURES_OK);
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
if ((self.transport.get_status() & VIRTIO_CONFIG_S_FEATURES_OK) == 0) { if ((self.transport.get_status() & VIRTIO_CONFIG_S_FEATURES_OK) == 0) {
uart.print("[VirtIO] Feature negotiation failed!\n"); uart.print("[VirtIO] Feature negotiation failed!\n");
return false; return false;
@ -267,10 +246,15 @@ pub const VirtioNetDriver = struct {
} }
// 5. Setup RX Queue (0) // 5. Setup RX Queue (0)
self.transport.select_queue(0); self.transport.select_queue(0);
const rx_count = self.transport.get_queue_size(); const rx_max = self.transport.get_queue_size();
// Cap queue size to avoid ION pool exhaustion (MMIO v1 reports 1024)
const MAX_QUEUE: u16 = 256;
const rx_count = if (rx_max > MAX_QUEUE) MAX_QUEUE else rx_max;
uart.print("[VirtIO] RX Queue Size: "); uart.print("[VirtIO] RX Queue Size: ");
uart.print_hex(rx_count); uart.print_hex(rx_count);
uart.print("\n"); uart.print(" (max: ");
uart.print_hex(rx_max);
uart.print(")\n");
if (rx_count == 0 or rx_count == 0xFFFF) { if (rx_count == 0 or rx_count == 0xFFFF) {
uart.print("[VirtIO] Invalid RX Queue Size. Aborting.\n"); uart.print("[VirtIO] Invalid RX Queue Size. Aborting.\n");
@ -288,10 +272,13 @@ pub const VirtioNetDriver = struct {
// 6. Setup TX Queue (1) // 6. Setup TX Queue (1)
self.transport.select_queue(1); self.transport.select_queue(1);
const tx_count = self.transport.get_queue_size(); const tx_max = self.transport.get_queue_size();
const tx_count = if (tx_max > MAX_QUEUE) MAX_QUEUE else tx_max;
uart.print("[VirtIO] TX Queue Size: "); uart.print("[VirtIO] TX Queue Size: ");
uart.print_hex(tx_count); uart.print_hex(tx_count);
uart.print("\n"); uart.print(" (max: ");
uart.print_hex(tx_max);
uart.print(")\n");
if (tx_count == 0 or tx_count == 0xFFFF) { if (tx_count == 0 or tx_count == 0xFFFF) {
uart.print("[VirtIO] Invalid TX Queue Size. Aborting.\n"); uart.print("[VirtIO] Invalid TX Queue Size. Aborting.\n");
@ -392,11 +379,11 @@ pub const VirtioNetDriver = struct {
q_ptr.avail.flags = 0; q_ptr.avail.flags = 0;
q_ptr.used.flags = 0; q_ptr.used.flags = 0;
asm volatile ("fence w, w" ::: .{ .memory = true }); transport_mod.io_barrier();
if (is_rx) { if (is_rx) {
q_ptr.avail.idx = count; q_ptr.avail.idx = count;
asm volatile ("fence w, w" ::: .{ .memory = true }); transport_mod.io_barrier();
} }
const phys_addr = aligned_addr; const phys_addr = aligned_addr;
@ -404,6 +391,7 @@ pub const VirtioNetDriver = struct {
if (self.transport.is_modern) { if (self.transport.is_modern) {
self.transport.setup_modern_queue(phys_addr, phys_addr + desc_size, phys_addr + used_offset); self.transport.setup_modern_queue(phys_addr, phys_addr + desc_size, phys_addr + used_offset);
} else { } else {
self.transport.set_queue_size(count);
const pfn = @as(u32, @intCast(phys_addr >> 12)); const pfn = @as(u32, @intCast(phys_addr >> 12));
self.transport.setup_legacy_queue(pfn); self.transport.setup_legacy_queue(pfn);
} }
@ -413,7 +401,7 @@ pub const VirtioNetDriver = struct {
} }
pub fn rx_poll(self: *VirtioNetDriver, q: *Virtqueue) void { pub fn rx_poll(self: *VirtioNetDriver, q: *Virtqueue) void {
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
const used = q.used; const used = q.used;
const hw_idx = used.idx; const hw_idx = used.idx;
@ -473,7 +461,7 @@ pub const VirtioNetDriver = struct {
q.desc[desc_idx].addr = new_phys; q.desc[desc_idx].addr = new_phys;
q.ids[desc_idx] = new_id; q.ids[desc_idx] = new_id;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
avail_ring[q.avail.idx % q.num] = @intCast(desc_idx); avail_ring[q.avail.idx % q.num] = @intCast(desc_idx);
q.avail.idx +%= 1; q.avail.idx +%= 1;
@ -486,14 +474,14 @@ pub const VirtioNetDriver = struct {
} }
if (replenished) { if (replenished) {
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
self.transport.notify(0); self.transport.notify(0);
} }
} }
pub fn tx_poll(self: *VirtioNetDriver, q: *Virtqueue) void { pub fn tx_poll(self: *VirtioNetDriver, q: *Virtqueue) void {
_ = self; _ = self;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
const used = q.used; const used = q.used;
const used_idx = used.idx; const used_idx = used.idx;
const used_ring = get_used_ring(used); const used_ring = get_used_ring(used);
@ -528,11 +516,11 @@ pub const VirtioNetDriver = struct {
q.ids[idx] = slab_id; q.ids[idx] = slab_id;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
avail_ring[idx] = @intCast(idx); avail_ring[idx] = @intCast(idx);
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
q.avail.idx +%= 1; q.avail.idx +%= 1;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
self.transport.notify(1); self.transport.notify(1);
uart.print("[VirtIO TX-Slab] Sent "); uart.print("[VirtIO TX-Slab] Sent ");
@ -579,11 +567,11 @@ pub const VirtioNetDriver = struct {
desc.len = @intCast(header_len + copy_len); desc.len = @intCast(header_len + copy_len);
desc.flags = 0; desc.flags = 0;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
avail_ring[idx] = @intCast(desc_idx); avail_ring[idx] = @intCast(desc_idx);
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
q.avail.idx +%= 1; q.avail.idx +%= 1;
asm volatile ("fence" ::: .{ .memory = true }); transport_mod.io_barrier();
self.transport.notify(1); self.transport.notify(1);
uart.print("[VirtIO TX] Queued & Notified Len="); uart.print("[VirtIO TX] Queued & Notified Len=");

View File

@ -14,6 +14,7 @@
//! Dynamically assigns BARs (Base Address Registers) if unassigned by firmware. //! Dynamically assigns BARs (Base Address Registers) if unassigned by firmware.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const uart = @import("uart.zig"); const uart = @import("uart.zig");
// PCI Config Offsets // PCI Config Offsets
@ -316,6 +317,17 @@ pub const VirtioTransport = struct {
} }
} }
pub fn set_queue_size(self: *VirtioTransport, size: u16) void {
// PCI legacy: queue size is read-only (device sets it)
// Modern: could set via common_cfg.queue_size
if (self.is_modern) {
if (self.common_cfg) |cfg| {
cfg.queue_size = size;
}
}
// Legacy PCI: queue size is fixed by device, no register to write
}
pub fn setup_legacy_queue(self: *VirtioTransport, pfn: u32) void { pub fn setup_legacy_queue(self: *VirtioTransport, pfn: u32) void {
// Only for legacy // Only for legacy
@as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x08)).* = pfn; @as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x08)).* = pfn;
@ -345,8 +357,65 @@ pub const VirtioTransport = struct {
notify_ptr.* = queue_idx; notify_ptr.* = queue_idx;
} }
} }
// =========================================================
// Unified Accessor API (matches MMIO transport)
// =========================================================
pub fn get_device_features(self: *VirtioTransport) u64 {
if (self.is_modern) {
const cfg = self.common_cfg.?;
cfg.device_feature_select = 0;
io_barrier();
const low: u64 = cfg.device_feature;
cfg.device_feature_select = 1;
io_barrier();
const high: u64 = cfg.device_feature;
return (high << 32) | low;
} else {
// Legacy: features at offset 0x00 (32-bit only)
return @as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x00)).*;
}
}
pub fn set_driver_features(self: *VirtioTransport, features: u64) void {
if (self.is_modern) {
const cfg = self.common_cfg.?;
cfg.driver_feature_select = 0;
cfg.driver_feature = @truncate(features);
io_barrier();
cfg.driver_feature_select = 1;
cfg.driver_feature = @truncate(features >> 32);
io_barrier();
} else {
// Legacy: guest features at offset 0x04 (32-bit only)
@as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x04)).* = @truncate(features);
}
}
pub fn get_device_config_byte(self: *VirtioTransport, offset: usize) u8 {
if (self.is_modern) {
if (self.device_cfg) |cfg| {
const ptr: [*]volatile u8 = @ptrCast(cfg);
return ptr[offset];
}
return 0;
} else {
// Legacy: device config starts at offset 20
return @as(*volatile u8, @ptrFromInt(self.legacy_bar + 20 + offset)).*;
}
}
}; };
/// Arch-safe memory barrier for VirtIO I/O ordering.
pub inline fn io_barrier() void {
switch (builtin.cpu.arch) {
.riscv64 => asm volatile ("fence" ::: .{ .memory = true }),
.aarch64 => asm volatile ("dmb sy" ::: .{ .memory = true }),
else => @compileError("unsupported arch"),
}
}
// Modern Config Structure Layout // Modern Config Structure Layout
pub const VirtioPciCommonCfg = extern struct { pub const VirtioPciCommonCfg = extern struct {
device_feature_select: u32, device_feature_select: u32,

View File

@ -1,7 +1,20 @@
// SPDX-License-Identifier: LSL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
// Freestanding FILE stubs (Nim's system.nim references stderr)
struct _FILE { int dummy; };
typedef struct _FILE FILE;
static FILE _stdin_placeholder;
static FILE _stdout_placeholder;
static FILE _stderr_placeholder;
FILE *stdin = &_stdin_placeholder;
FILE *stdout = &_stdout_placeholder;
FILE *stderr = &_stderr_placeholder;
// Types needed for stubs // Types needed for stubs
typedef int32_t pid_t; typedef int32_t pid_t;
typedef int32_t uid_t; typedef int32_t uid_t;
@ -15,13 +28,46 @@ struct stat {
int errno = 0; int errno = 0;
// Basic memory stubs
extern void* malloc(size_t size);
extern void free(void* ptr);
// Forward declare memset (defined below)
void* memset(void* s, int c, size_t n); void* memset(void* s, int c, size_t n);
// Basic memory stubs (Bump Allocator) - USERLAND ONLY
// Kernel gets malloc/free/calloc from stubs.zig
#ifdef RUMPK_USER
static uint8_t heap[4 * 1024 * 1024]; // 4MB Heap
static size_t heap_idx = 0;
void* malloc(size_t size) {
if (size == 0) return NULL;
size = (size + 7) & ~7; // Align to 8 bytes
if (heap_idx + size > sizeof(heap)) return NULL;
void* ptr = &heap[heap_idx];
heap_idx += size;
return ptr;
}
void free(void* ptr) {
(void)ptr;
}
void* calloc(size_t nmemb, size_t size) {
size_t total = nmemb * size;
void* ptr = malloc(total);
if (ptr) memset(ptr, 0, total);
return ptr;
}
#endif // RUMPK_USER
// Forward declarations
int write(int fd, const void *buf, size_t count);
int read(int fd, void *buf, size_t count);
int open(const char *pathname, int flags, ...);
int close(int fd);
void* memset(void* s, int c, size_t n);
// Basic memory stubs
// Basic memory stubs (Bump Allocator)
// LwIP Panic Handler (for Membrane stack) // LwIP Panic Handler (for Membrane stack)
extern void console_write(const void* p, size_t len); extern void console_write(const void* p, size_t len);
@ -44,8 +90,41 @@ int strncmp(const char *s1, const char *s2, size_t n) {
int atoi(const char* nptr) { return 0; } int atoi(const char* nptr) { return 0; }
double strtod(const char* nptr, char** endptr) { double strtod(const char* nptr, char** endptr) {
if (endptr) *endptr = (char*)nptr; const char *p = nptr;
return 0.0; double result = 0.0;
double sign = 1.0;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
if (*p == '-') { sign = -1.0; p++; }
else if (*p == '+') { p++; }
while (*p >= '0' && *p <= '9') {
result = result * 10.0 + (*p - '0');
p++;
}
if (*p == '.') {
double frac = 0.1;
p++;
while (*p >= '0' && *p <= '9') {
result += (*p - '0') * frac;
frac *= 0.1;
p++;
}
}
if (*p == 'e' || *p == 'E') {
p++;
int exp_sign = 1, exp_val = 0;
if (*p == '-') { exp_sign = -1; p++; }
else if (*p == '+') { p++; }
while (*p >= '0' && *p <= '9') {
exp_val = exp_val * 10 + (*p - '0');
p++;
}
double m = 1.0;
for (int i = 0; i < exp_val; i++)
m = exp_sign > 0 ? m * 10.0 : m * 0.1;
result *= m;
}
if (endptr) *endptr = (char*)p;
return sign * result;
} }
double pow(double x, double y) { return 0.0; } double pow(double x, double y) { return 0.0; }
@ -55,8 +134,42 @@ double log10(double x) { return 0.0; }
#ifdef RUMPK_KERNEL #ifdef RUMPK_KERNEL
extern long k_handle_syscall(long nr, long a0, long a1, long a2); extern long k_handle_syscall(long nr, long a0, long a1, long a2);
extern uint64_t hal_get_time_ns(void);
extern void hal_console_write(const void* p, size_t len);
extern void hal_panic(const char* msg);
uint64_t syscall_get_time_ns(void) { return hal_get_time_ns(); }
void syscall_panic(void) { while(1); }
#endif #endif
// Support for Nim system.nim
#ifndef OMIT_EXIT
void exit(int status) {
#ifdef RUMPK_KERNEL
while(1);
#else
syscall(1, (long)status, 0, 0); // SYS_EXIT
while(1);
#endif
}
void abort(void) {
exit(1);
}
#endif
size_t fwrite(const void* ptr, size_t size, size_t nmemb, void* stream) {
#ifdef RUMPK_KERNEL
hal_console_write(ptr, size * nmemb);
#else
write(1, ptr, size * nmemb); // Forward to stdout
#endif
return nmemb;
}
int fflush(void* stream) {
return 0;
}
long syscall(long nr, long a0, long a1, long a2) { long syscall(long nr, long a0, long a1, long a2) {
#ifdef RUMPK_KERNEL #ifdef RUMPK_KERNEL
return k_handle_syscall(nr, a0, a1, a2); return k_handle_syscall(nr, a0, a1, a2);
@ -222,11 +335,7 @@ int printf(const char *format, ...) {
return res; return res;
} }
int fwrite(const void *ptr, size_t size, size_t nmemb, void *stream) { // REDUNDANT REMOVED
return write(1, ptr, size * nmemb);
}
int fflush(void *stream) { return 0; }
// System stubs // System stubs
extern void nexus_yield(void); extern void nexus_yield(void);
@ -270,6 +379,16 @@ int memcmp(const void *s1, const void *s2, size_t n) {
return 0; return 0;
} }
#ifdef RUMPK_USER
void *memchr(const void *s, int c, size_t n) {
const unsigned char *p = (const unsigned char *)s;
for (size_t i = 0; i < n; i++) {
if (p[i] == (unsigned char)c) return (void *)(p + i);
}
return (void *)0;
}
#endif
void *memmove(void *dest, const void *src, size_t n) { void *memmove(void *dest, const void *src, size_t n) {
char *d = (char *)dest; char *d = (char *)dest;
const char *s = (const char *)src; const char *s = (const char *)src;
@ -343,13 +462,10 @@ uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
#ifdef RUMPK_KERNEL #ifdef RUMPK_KERNEL
// Kernel Mode: Direct UART
extern void hal_console_write(const char* ptr, size_t len);
void console_write(const void* p, size_t len) { void console_write(const void* p, size_t len) {
hal_console_write(p, len); hal_console_write(p, len);
} }
#else #else
// User Mode: Syscall
void console_write(const void* p, size_t len) { void console_write(const void* p, size_t len) {
write(1, p, len); write(1, p, len);
} }

View File

@ -12,7 +12,8 @@
import ../../core/ion import ../../core/ion
const SYS_TABLE_ADDR = 0x83000000'u64 const SYS_TABLE_ADDR = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
const const
GAP = 10 # Pixels between windows GAP = 10 # Pixels between windows

View File

@ -3,4 +3,16 @@
#include "lfs_config.h" #include "lfs_config.h"
#ifndef _SIZE_T_DEFINED
#define _SIZE_T_DEFINED
typedef unsigned long size_t;
#endif
void *memchr(const void *s, int c, size_t n);
void *memcpy(void *dest, const void *src, size_t n);
void *memset(void *s, int c, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
void *memmove(void *dest, const void *src, size_t n);
double strtod(const char *nptr, char **endptr);
#endif #endif

View File

@ -14,6 +14,7 @@
//! All memory accesses to the SysTable are through volatile pointers. //! All memory accesses to the SysTable are through volatile pointers.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
// --- Protocol Definitions (Match core/ion.nim) --- // --- Protocol Definitions (Match core/ion.nim) ---
@ -94,7 +95,11 @@ pub fn RingBuffer(comptime T: type) type {
self.data[head & mask] = item; self.data[head & mask] = item;
// Commit // Commit
asm volatile ("fence" ::: .{ .memory = true }); switch (builtin.cpu.arch) {
.riscv64 => asm volatile ("fence" ::: .{ .memory = true }),
.aarch64 => asm volatile ("dmb ish" ::: .{ .memory = true }),
else => @compileError("unsupported arch"),
}
self.head = (head + 1) & mask; self.head = (head + 1) & mask;
return true; return true;
} }
@ -115,6 +120,8 @@ pub const SysTable = extern struct {
fn_vfs_list: u64, fn_vfs_list: u64,
fn_vfs_write: u64, fn_vfs_write: u64,
fn_vfs_close: u64, fn_vfs_close: u64,
fn_vfs_dup: u64,
fn_vfs_dup2: u64,
fn_log: u64, fn_log: u64,
fn_pledge: u64, fn_pledge: u64,
// Framebuffer // Framebuffer
@ -127,6 +134,7 @@ pub const SysTable = extern struct {
// Crypto // Crypto
fn_siphash: u64, fn_siphash: u64,
fn_ed25519_verify: u64, fn_ed25519_verify: u64,
fn_blake3: u64,
// Network // Network
s_net_rx: *RingBuffer(IonPacket), s_net_rx: *RingBuffer(IonPacket),
s_net_tx: *RingBuffer(IonPacket), s_net_tx: *RingBuffer(IonPacket),
@ -134,16 +142,27 @@ pub const SysTable = extern struct {
// Phase 36.3: Shared ION (16 bytes) // Phase 36.3: Shared ION (16 bytes)
fn_ion_alloc: u64, fn_ion_alloc: u64,
fn_ion_free: u64, fn_ion_free: u64,
// Phase 36.4: Wait Multi
fn_wait_multi: u64,
// Phase 36.5: HW Info
net_mac: [6]u8,
reserved_mac: [2]u8,
// Project LibWeb: LWF Sovereign Channel
s_lwf_rx: *RingBuffer(IonPacket), // Kernel -> User (LWF frames)
s_lwf_tx: *RingBuffer(IonPacket), // User -> Kernel (LWF frames)
}; };
comptime { comptime {
if (@sizeOf(IonPacket) != 24) @compileError("IonPacket size mismatch!"); if (@sizeOf(IonPacket) != 24) @compileError("IonPacket size mismatch!");
if (@sizeOf(SysTable) != 184) { if (@sizeOf(SysTable) != 240) {
@compileError("SysTable size mismatch! Expected 184"); @compileError("SysTable size mismatch! Expected 240 (LibWeb LWF channels added)");
} }
} }
const SYSTABLE_ADDR: usize = 0x83000000; const SYSTABLE_ADDR: usize = if (builtin.cpu.arch == .aarch64) 0x50000000 else 0x83000000;
// --- API --- // --- API ---

View File

@ -17,7 +17,8 @@
# Instead, locally declare only the types we need for userspace # Instead, locally declare only the types we need for userspace
import ../../core/ring import ../../core/ring
const SYS_TABLE_ADDR* = 0x83000000'u64 const SYS_TABLE_ADDR* = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
# Local type declarations (must match core/ion.nim definitions) # Local type declarations (must match core/ion.nim definitions)
type type
@ -70,6 +71,8 @@ type
fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.} fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.}
fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.}
fn_vfs_close*: proc(fd: int32): int32 {.cdecl.} fn_vfs_close*: proc(fd: int32): int32 {.cdecl.}
fn_vfs_dup*: proc(fd: int32, min_fd: int32): int32 {.cdecl.}
fn_vfs_dup2*: proc(old_fd, new_fd: int32): int32 {.cdecl.}
fn_log*: pointer fn_log*: pointer
fn_pledge*: proc(promises: uint64): int32 {.cdecl.} fn_pledge*: proc(promises: uint64): int32 {.cdecl.}
fb_addr*: uint64 fb_addr*: uint64
@ -97,8 +100,12 @@ type
net_mac*: array[6, byte] net_mac*: array[6, byte]
reserved_mac*: array[2, byte] reserved_mac*: array[2, byte]
# Project LibWeb: LWF Sovereign Channel (16 bytes)
s_lwf_rx*: pointer # Kernel -> User (LWF frames)
s_lwf_tx*: pointer # User -> Kernel (LWF frames)
static: static:
doAssert sizeof(SysTable) == 208 doAssert sizeof(SysTable) == 240
var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256] var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256]
var membrane_tx_ring_ptr*: ptr RingBuffer[IonPacket, 256] var membrane_tx_ring_ptr*: ptr RingBuffer[IonPacket, 256]
@ -144,10 +151,10 @@ proc ion_user_init*() {.exportc.} =
# Pure shared-memory slab allocator - NO kernel function calls! # Pure shared-memory slab allocator - NO kernel function calls!
const const
USER_SLAB_BASE = 0x83010000'u64 # Start of user packet slab in SysTable region USER_SLAB_BASE = SYS_TABLE_ADDR + 0x10000'u64 # Start of user packet slab in SysTable region
USER_SLAB_COUNT = 512 # Number of packet slots USER_SLAB_COUNT = 512 # Number of packet slots
USER_PKT_SIZE = 2048 # Size of each packet buffer USER_PKT_SIZE = 2048 # Size of each packet buffer
USER_BITMAP_ADDR = 0x83000100'u64 # Bitmap stored in SysTable region (after SysTable struct) USER_BITMAP_ADDR = SYS_TABLE_ADDR + 0x100'u64 # Bitmap stored in SysTable region (after SysTable struct)
# Get pointer to shared bitmap (512 bits = 64 bytes for 512 slots) # Get pointer to shared bitmap (512 bits = 64 bytes for 512 slots)
proc get_user_bitmap(): ptr array[64, byte] = proc get_user_bitmap(): ptr array[64, byte] =

View File

@ -12,7 +12,9 @@
# (C) 2026 Markus Maiwald # (C) 2026 Markus Maiwald
import ion_client import ion_client
import net_glue when defined(RUMPK_KERNEL):
import net_glue
export net_glue
# memcpy removed to avoid C header conflict # memcpy removed to avoid C header conflict
@ -50,6 +52,16 @@ proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
let v0 = a0 let v0 = a0
let v1 = a1 let v1 = a1
let v2 = a2 let v2 = a2
when defined(arm64):
{.emit: """
register unsigned long x8_ __asm__("x8") = `n`;
register unsigned long x0_ __asm__("x0") = `v0`;
register unsigned long x1_ __asm__("x1") = `v1`;
register unsigned long x2_ __asm__("x2") = `v2`;
__asm__ volatile("svc #0" : "+r"(x0_) : "r"(x8_), "r"(x1_), "r"(x2_) : "memory");
`res` = (long)x0_;
""".}
else:
{.emit: """ {.emit: """
register unsigned long a7 __asm__("a7") = `n`; register unsigned long a7 __asm__("a7") = `n`;
register unsigned long a0_ __asm__("a0") = `v0`; register unsigned long a0_ __asm__("a0") = `v0`;
@ -64,25 +76,13 @@ when defined(RUMPK_KERNEL):
# ========================================================= # =========================================================
# KERNEL IMPLEMENTATION # KERNEL IMPLEMENTATION
# ========================================================= # =========================================================
# SockState, NexusSock, membrane_init, pump_membrane_stack,
type # glue_connect/bind/listen/accept_peek/setup_socket/write/read/close,
SockState = enum # glue_resolve_start/check — all provided by net_glue (imported above)
CLOSED, LISTEN, CONNECTING, ESTABLISHED, FIN_WAIT
NexusSock = object
fd: int
pcb: pointer
state: SockState
rx_buf: array[4096, byte]
rx_len: int
accepted_pcb: pointer
accepted_pending: int32
var g_sockets: array[MAX_SOCKS, NexusSock] var g_sockets: array[MAX_SOCKS, NexusSock]
var g_sock_used: array[MAX_SOCKS, bool] var g_sock_used: array[MAX_SOCKS, bool]
proc membrane_init*() {.importc, cdecl.}
proc pump_membrane_stack*() {.importc, cdecl.}
proc rumpk_yield_internal() {.importc, cdecl.} proc rumpk_yield_internal() {.importc, cdecl.}
{.emit: """ {.emit: """
@ -90,17 +90,6 @@ when defined(RUMPK_KERNEL):
extern void trigger_http_test(void); extern void trigger_http_test(void);
""".} """.}
proc glue_connect(sock: ptr NexusSock, ip: uint32, port: uint16): int {.importc, cdecl.}
proc glue_bind(sock: ptr NexusSock, port: uint16): int {.importc, cdecl.}
proc glue_listen(sock: ptr NexusSock): int {.importc, cdecl.}
proc glue_accept_peek(sock: ptr NexusSock): pointer {.importc, cdecl.}
proc glue_setup_socket(sock: ptr NexusSock, pcb: pointer) {.importc, cdecl.}
proc glue_write(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
proc glue_read(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
proc glue_close(sock: ptr NexusSock): int {.importc, cdecl.}
proc glue_resolve_start(hostname: cstring): int {.importc, cdecl.}
proc glue_resolve_check(ip_out: ptr uint32): int {.importc, cdecl.}
const const
MAX_FILES = 16 MAX_FILES = 16
FILE_FD_START = 100 FILE_FD_START = 100
@ -369,8 +358,10 @@ else:
proc yield_fiber*() {.exportc: "yield", cdecl.} = proc yield_fiber*() {.exportc: "yield", cdecl.} =
discard syscall(0x100, 0) discard syscall(0x100, 0)
proc pump_membrane_stack*() {.importc, cdecl.} proc pump_membrane_stack*() =
proc membrane_init*() {.importc, cdecl.} ## No-op in userland — kernel drives LwIP stack
yield_fiber()
# proc membrane_init*() {.importc, cdecl.}
proc ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.} proc ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.}
proc pledge*(promises: uint64): int {.exportc, cdecl.} = proc pledge*(promises: uint64): int {.exportc, cdecl.} =

View File

@ -18,6 +18,16 @@ proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
proc glue_print(s: string) = proc glue_print(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len)) console_write(unsafeAddr s[0], csize_t(s.len))
# =========================================================
# lwIP Syscall Bridge (kernel-native, no ecalls)
# syscall_get_time_ns and syscall_panic provided by clib.c
# =========================================================
proc rumpk_timer_now_ns(): uint64 {.importc, cdecl.}
proc syscall_get_random*(): uint32 {.exportc, cdecl.} =
let t = rumpk_timer_now_ns()
return uint32(t xor (t shr 32))
# LwIP Imports # LwIP Imports
{.passC: "-Icore/rumpk/vendor/lwip/src/include".} {.passC: "-Icore/rumpk/vendor/lwip/src/include".}
{.passC: "-Icore/rumpk/libs/membrane/include".} {.passC: "-Icore/rumpk/libs/membrane/include".}
@ -114,6 +124,8 @@ type
state*: SockState state*: SockState
rx_buf*: array[4096, byte] rx_buf*: array[4096, byte]
rx_len*: int rx_len*: int
accepted_pcb*: pointer
accepted_pending*: int32
# Forward declarations for LwIP callbacks # Forward declarations for LwIP callbacks
@ -192,31 +204,14 @@ proc membrane_init*() {.exportc, cdecl.} =
last_dhcp_coarse = now last_dhcp_coarse = now
last_dns_tmr = now last_dns_tmr = now
glue_print("[Membrane] Initialization...\n") glue_print("[Membrane] Initialization Starting...\n")
ion_user_init() ion_user_init()
# 1. LwIP Stack Init # 1. LwIP Stack Init
glue_print("[Membrane] Calling lwip_init()...\n") glue_print("[Membrane] Calling lwip_init()...\n")
lwip_init() lwip_init()
# DIAGNOSTIC: Audit Memory Pools dns_init()
{.emit: """
extern const struct memp_desc *const memp_pools[];
printf("[Membrane] Pool Audit (MAX=%d):\n", (int)MEMP_MAX);
for (int i = 0; i < (int)MEMP_MAX; i++) {
if (memp_pools[i] == NULL) {
printf(" [%d] NULL!\n", i);
} else {
printf(" [%d] OK\n", i);
}
}
printf("[Membrane] Enum Lookup:\n");
printf(" MEMP_UDP_PCB: %d\n", (int)MEMP_UDP_PCB);
printf(" MEMP_TCP_PCB: %d\n", (int)MEMP_TCP_PCB);
printf(" MEMP_PBUF: %d\n", (int)MEMP_PBUF);
""".}
dns_init() # Initialize DNS resolver
# Set Fallback DNS (10.0.2.3 - QEMU Default) # Set Fallback DNS (10.0.2.3 - QEMU Default)
{.emit: """ {.emit: """
@ -225,34 +220,35 @@ proc membrane_init*() {.exportc, cdecl.} =
dns_setserver(0, &dns_server); dns_setserver(0, &dns_server);
""".} """.}
glue_print("[Membrane] DNS resolver configured with fallback 10.0.2.3\n") glue_print("[Membrane] DNS configured (10.0.2.3)\n")
glue_print("[Membrane] lwip_init() returned. DNS Initialized.\n") # 2. Setup Netif with DHCP
# 2. Setup Netif
{.emit: """ {.emit: """
static struct netif ni_static; static struct netif ni_static;
ip4_addr_t ip, mask, gw; ip4_addr_t ip, mask, gw;
// Use Static IP to stabilize test environment // Start with zeros — DHCP will assign
IP4_ADDR(&ip, 10, 0, 2, 15); IP4_ADDR(&ip, 0, 0, 0, 0);
IP4_ADDR(&mask, 255, 255, 255, 0); IP4_ADDR(&mask, 0, 0, 0, 0);
IP4_ADDR(&gw, 10, 0, 2, 2); IP4_ADDR(&gw, 0, 0, 0, 0);
struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input); struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL,
printf("[Membrane] netif_add returned: 0x%x\n", (unsigned int)res); (netif_init_fn)ion_netif_init,
(netif_input_fn)ethernet_input);
if (res == NULL) {
printf("[Membrane] CRITICAL: netif_add FAILED!\n");
} else {
netif_set_default(&ni_static); netif_set_default(&ni_static);
netif_set_up(&ni_static); netif_set_up(&ni_static);
dhcp_start(&ni_static);
printf("[Membrane] netif_default: 0x%x | netif_list: 0x%x\n", (unsigned int)netif_default, (unsigned int)netif_list); printf("[Membrane] DHCP started on io0\n");
}
// dhcp_start(&ni_static); // Bypassing DHCP
`g_netif` = &ni_static; `g_netif` = &ni_static;
""".} """.}
glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n") glue_print("[Membrane] Network Stack Operational (DHCP...)\n")
proc glue_get_ip*(): uint32 {.exportc, cdecl.} = proc glue_get_ip*(): uint32 {.exportc, cdecl.} =
## Returns current IP address in host byte order ## Returns current IP address in host byte order
@ -667,4 +663,3 @@ void trigger_http_test(void) {
tcp_connect(pcb, &google_ip, 80, tcp_connected_callback); tcp_connect(pcb, &google_ip, 80, tcp_connected_callback);
} }
""".} """.}

View File

@ -12,9 +12,49 @@ import strutils, parseutils, tables, sequtils, json
import kdl import kdl
import ../../libs/membrane/libc as lb import ../../libs/membrane/libc as lb
import ../../libs/membrane/libc_net as lnet import ../../libs/membrane/libc_net as lnet
import ../../libs/membrane/fs/sfs_user as sfs when not defined(NIPBOX_LITE):
import ../../libs/membrane/fs/sfs_user as sfs
import editor import editor
import ../../libs/membrane/term # Phase 26: Visual Cortex when not defined(NIPBOX_LITE):
import ../../libs/membrane/term # Phase 26: Visual Cortex
# --- M4.4: BKDL Capability Manifest (SPEC-071) ---
{.emit: """
__attribute__((section(".nexus.manifest"), used))
static const unsigned char nexus_manifest[166] = {
/* BkdlHeader (118 bytes) */
0x53, 0x55, 0x58, 0x4E, /* magic: "NXUS" (LE) */
0x01, 0x00, /* version: 1 */
0x00, 0x00, /* flags: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* signature[0..63]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* pubkey_hash[0..31]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, /* cap_count: 4 */
0x00, 0x00, 0x00, 0x00, /* blob_size: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* entry_point: 0 */
/* CapDescriptor[0]: console.output (0x1001) WRITE */
0x02, 0x02, 0x00, 0x00,
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[1]: VFS (0x2000) READ */
0x02, 0x01, 0x00, 0x00,
0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[2]: NET_TX (0x0500) WRITE */
0x02, 0x02, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[3]: NET_RX (0x0501) READ */
0x02, 0x01, 0x00, 0x00,
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
""".}
# Phase 30: Pledge Constants # Phase 30: Pledge Constants
const const
@ -291,6 +331,7 @@ proc cmd_cat*(args: seq[string], input: PipelineData): PipelineData =
proc cmd_write*(args: seq[string], input: PipelineData): PipelineData = proc cmd_write*(args: seq[string], input: PipelineData): PipelineData =
## write <filename> <content> ## write <filename> <content>
## Uses USERLAND SFS (Block Valve architecture) ## Uses USERLAND SFS (Block Valve architecture)
when not defined(NIPBOX_LITE):
if args.len < 2: if args.len < 2:
print("Usage: write <filename> <content>\n") print("Usage: write <filename> <content>\n")
return @[] return @[]
@ -308,10 +349,14 @@ proc cmd_write*(args: seq[string], input: PipelineData): PipelineData =
else: else:
print("Error: Could not write to " & filename & "\n") print("Error: Could not write to " & filename & "\n")
return @[] return @[]
else:
print("[nipbox] 'write' requires SFS (not available in lite mode)\n")
return @[]
proc cmd_read*(args: seq[string], input: PipelineData): PipelineData = proc cmd_read*(args: seq[string], input: PipelineData): PipelineData =
## read <filename> ## read <filename>
## Uses USERLAND SFS (Block Valve architecture) ## Uses USERLAND SFS (Block Valve architecture)
when not defined(NIPBOX_LITE):
if args.len == 0: if args.len == 0:
print("Usage: read <filename>\n") print("Usage: read <filename>\n")
return @[] return @[]
@ -330,6 +375,9 @@ proc cmd_read*(args: seq[string], input: PipelineData): PipelineData =
else: else:
print("Error: Could not open " & filename & "\n") print("Error: Could not open " & filename & "\n")
return @[] return @[]
else:
print("[nipbox] 'read' requires SFS (not available in lite mode)\n")
return @[]
proc cmd_edit*(args: seq[string], input: PipelineData): PipelineData = proc cmd_edit*(args: seq[string], input: PipelineData): PipelineData =
if args.len == 0: if args.len == 0:
@ -482,6 +530,7 @@ proc cmd_http_get*(args: seq[string], input: PipelineData): PipelineData =
return @[node] return @[node]
proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData = proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
when not defined(NIPBOX_LITE):
if args.len < 2: if args.len < 2:
print("Usage: http.download <ip:port/path> <outfile>\n") print("Usage: http.download <ip:port/path> <outfile>\n")
return @[] return @[]
@ -561,7 +610,6 @@ proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
var timeout = 0 var timeout = 0
while timeout < 10000: while timeout < 10000:
# Use libc shim which pumps stack
let n = lb.recv(cint(fd), addr buf[0], 4096, 0) let n = lb.recv(cint(fd), addr buf[0], 4096, 0)
if n > 0: if n > 0:
@ -572,8 +620,6 @@ proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
if sep != -1: if sep != -1:
header_parsed = true header_parsed = true
# Try to find Content-Length
# Quick hacky parse
let lower_head = header_acc.toLowerAscii() let lower_head = header_acc.toLowerAscii()
let cl_idx = lower_head.find("content-length:") let cl_idx = lower_head.find("content-length:")
if cl_idx != -1: if cl_idx != -1:
@ -595,13 +641,10 @@ proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
print("Error: Headers too large.\n") print("Error: Headers too large.\n")
break break
else: else:
# Stream directly to SFS
sfs.sfs_write_chunk(sfs_h, addr buf[0], int(n)) sfs.sfs_write_chunk(sfs_h, addr buf[0], int(n))
total_bytes += int(n) total_bytes += int(n)
# Progress Bar
if content_len > 0: if content_len > 0:
# let pct = (total_bytes * 100) div content_len
if total_bytes mod 10240 < int(n): print(".") if total_bytes mod 10240 < int(n): print(".")
else: else:
if total_bytes mod 10240 < int(n): print(".") if total_bytes mod 10240 < int(n): print(".")
@ -609,28 +652,20 @@ proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
elif n == 0: elif n == 0:
break break
else: else:
timeout += 1
# Busy wait / pump handled in recv?
# Recv calls pump_membrane_stack loop
# But if we return -1 (EAGAIN), we need to retry.
# My libc.libc_recv returns 0 on closed?
# Actually libc_recv in step 945 waits until data or closed.
# So n==0 means closed.
# Wait, libc.nim recv implementation:
# while true: pump; if data return n; if closed return 0.
# So it blocks until data.
# Thus n > 0 always unless closed.
break break
sfs.sfs_close_write(sfs_h) sfs.sfs_close_write(sfs_h)
discard lb.close(cint(fd)) discard lb.close(cint(fd))
print("\n[Download] Complete. " & $total_bytes & " bytes written to " & outfile & " (Glass Vault).\n") print("\n[Download] Complete. " & $total_bytes & " bytes written to " & outfile & " (Glass Vault).\n")
return @[] return @[]
else:
print("[nipbox] 'http.download' requires SFS (not available in lite mode)\n")
return @[]
# Phase 37: HTTP Verification Tool # Phase 37: HTTP Verification Tool
proc cmd_http_test*(args: seq[string], input: PipelineData): PipelineData = proc cmd_http_test*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 1: if args.len < 1:
print("Usage: http <host>\n") print("Usage: http.test <host>\n")
return @[] return @[]
let host = args[0] let host = args[0]
@ -646,16 +681,13 @@ proc cmd_http_test*(args: seq[string], input: PipelineData): PipelineData =
print("Waiting for response...\n") print("Waiting for response...\n")
# Simple read loop
var total = 0 var total = 0
while true: while true:
# lb.pump_membrane_stack()
let resp = lnet.net_recv(fd, 512) let resp = lnet.net_recv(fd, 512)
if resp.len > 0: if resp.len > 0:
print(resp) print(resp)
total += resp.len total += resp.len
else: else:
# End of stream or empty poll
break break
print("\n[HTTP] Closed. Total bytes: " & $total & "\n") print("\n[HTTP] Closed. Total bytes: " & $total & "\n")
@ -774,8 +806,18 @@ proc cmd_set*(args: seq[string], input: PipelineData): PipelineData =
proc cmd_help*(args: seq[string], input: PipelineData): PipelineData = proc cmd_help*(args: seq[string], input: PipelineData): PipelineData =
when defined(NIPBOX_LITE):
print("NipBox " & NIPBOX_VERSION & " [LITE] (Phase 34: Orbital Drop)\n")
print("Commands: ls, cat, cp, mv, touch, edit, echo, where, http, http.get,\n")
print(" http.test, http.serve, from_json, mount, matrix, crash,\n")
print(" sys.upgrade, set, if, while, help, exit\n")
print("Disabled: write, read, http.download (requires SFS)\n")
else:
print("NipBox " & NIPBOX_VERSION & " (Phase 34: Orbital Drop)\n") print("NipBox " & NIPBOX_VERSION & " (Phase 34: Orbital Drop)\n")
print("Commands: ls, cat, echo, where, http, http.get, http.download, from_json, mount, matrix, set, if, while, help, exit\n") print("Commands: ls, cat, cp, mv, touch, edit, echo, where, write, read,\n")
print(" http, http.get, http.download, http.test, http.serve,\n")
print(" from_json, mount, matrix, crash, sys.upgrade,\n")
print(" set, if, while, help, exit\n")
return @[] return @[]
# --- DISPATCHER --- # --- DISPATCHER ---

130
run.sh
View File

@ -1,22 +1,122 @@
#!/bin/bash #!/usr/bin/env bash
# Rumpk QEMU Boot Script # ============================================================================
# Rumpk QEMU Runner — Boot, Test, or Interactive Shell
# ============================================================================
# Usage:
# ./run.sh # Interactive RISC-V session
# ./run.sh test # Automated boot test (10s timeout, check output)
# ./run.sh [riscv64|aarch64] # Architecture selection (future)
#
# Exit codes:
# 0 = Boot successful (test mode: all checks passed)
# 1 = Boot failed or timeout
# ============================================================================
set -euo pipefail
cd "$(dirname "$0")"
RUMPK_DIR="$(cd "$(dirname "$0")" && pwd)" MODE="${1:-interactive}"
KERNEL="$RUMPK_DIR/zig-out/bin/rumpk.elf" ARCH="${2:-riscv64}"
TIMEOUT=25
KERNEL="zig-out/bin/rumpk.elf"
DISK="build/disk.img"
# Fallback to build/ location if zig-out doesn't have it
if [ ! -f "$KERNEL" ]; then
KERNEL="build/rumpk-riscv64.elf"
fi
if [ ! -f "$KERNEL" ]; then if [ ! -f "$KERNEL" ]; then
echo "ERROR: Kernel not found at $KERNEL" echo "ERROR: No kernel binary found. Run: ./build_nim.sh && zig build -Dtarget=riscv64-freestanding-none -Dcpu=sifive_u54"
echo "Run ./build.sh first"
exit 1 exit 1
fi fi
echo "🚀 Booting Rumpk..." case "$ARCH" in
echo " Kernel: $KERNEL" riscv64)
echo "" QEMU_CMD=(
qemu-system-riscv64
qemu-system-riscv64 \ -M virt -m 512M -nographic
-M virt \
-cpu max \
-m 512M \
-nographic \
-kernel "$KERNEL" -kernel "$KERNEL"
)
# Add disk if it exists
if [ -f "$DISK" ]; then
QEMU_CMD+=(
-drive "if=none,format=raw,file=$DISK,id=blk0"
-device virtio-blk-pci,drive=blk0
)
fi
QEMU_CMD+=(
-device virtio-net-pci,netdev=net0
-netdev user,id=net0
)
;;
*)
echo "ERROR: Architecture '$ARCH' not yet supported"
echo "Supported: riscv64"
exit 1
;;
esac
if [ "$MODE" = "test" ]; then
echo "=== Rumpk Boot Test ($ARCH, ${TIMEOUT}s timeout) ==="
LOGFILE=$(mktemp /tmp/rumpk-boot-XXXXXX.log)
trap "rm -f $LOGFILE" EXIT
timeout "$TIMEOUT" "${QEMU_CMD[@]}" > "$LOGFILE" 2>&1 || true
# Boot verification checks
PASS=0
FAIL=0
TOTAL=0
check() {
local name="$1"
local pattern="$2"
TOTAL=$((TOTAL + 1))
if grep -q "$pattern" "$LOGFILE"; then
echo " PASS: $name"
PASS=$((PASS + 1))
else
echo " FAIL: $name"
FAIL=$((FAIL + 1))
fi
}
check "OpenSBI handoff" "Boot HART ID"
check "UART proof-of-life" "UART.*Loopback Test: PASS"
check "Zig HAL entry" "zig_entry reached"
check "Nim L1 handoff" "Handing off to Nim L1"
check "ION pool ready" "ION.*Pool Ready"
check "VirtIO-Net init" "VirtIO.*Initialization Complete"
check "CSpace init" "Capability system initialized"
check "Truth Ledger init" "System Truth Ledger initialized"
check "Fiber dispatcher" "Multi-Fiber Dispatcher starting"
check "NetSwitch online" "Traffic Engine Online"
check "LFS mounted" "Sovereign filesystem mounted"
check "Init loaded" "NIPBOX.*Entry point reached"
check "Shell spawned" "SOVEREIGN SUPERVISOR"
check "DHCP IP acquired" "IP STATUS CHANGE"
echo ""
echo "=== Result: $PASS/$TOTAL passed, $FAIL failed ==="
if [ "$FAIL" -gt 0 ]; then
echo ""
echo "Boot log saved: $LOGFILE"
trap - EXIT # Don't delete on failure
exit 1
fi
exit 0
elif [ "$MODE" = "interactive" ]; then
echo "=== Rumpk Interactive Session ($ARCH) ==="
echo " Kernel: $KERNEL"
echo " Exit: Ctrl-A X"
echo ""
exec "${QEMU_CMD[@]}"
else
echo "Usage: ./run.sh [interactive|test] [riscv64]"
exit 1
fi

View File

@ -55,7 +55,6 @@ BUILD_DIR="../../build"
zig cc -target $TARGET -nostdlib -static -T $LINKER_SCRIPT \ zig cc -target $TARGET -nostdlib -static -T $LINKER_SCRIPT \
$BUILD_DIR/subject_entry.o \ $BUILD_DIR/subject_entry.o \
$BUILD_DIR/stubs_user.o \
$BUILD_DIR/libc_shim.o \ $BUILD_DIR/libc_shim.o \
stubs_mksh.o \ stubs_mksh.o \
$OBJS \ $OBJS \

13921
vendor/mksh/mksh/check.t vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,17 +13,78 @@
#include <string.h> #include <string.h>
#include <setjmp.h> #include <setjmp.h>
#include <termios.h> #include <termios.h>
#include <stdarg.h>
// Globals
char **environ = NULL;
extern void console_write(const void* p, size_t len);
extern long syscall(long nr, long a0, long a1, long a2); extern long syscall(long nr, long a0, long a1, long a2);
long k_handle_syscall(long nr, long a0, long a1, long a2) { long k_handle_syscall(long nr, long a0, long a1, long a2) {
return syscall(nr, a0, a1, a2); return syscall(nr, a0, a1, a2);
} }
// Globals
char **environ = NULL;
// Safe Userland Allocator (Bump Pointer with Headers)
#define HEAP_SIZE (32 * 1024 * 1024)
static char heap_memory[HEAP_SIZE];
static size_t heap_ptr = 0;
typedef struct {
size_t size;
size_t magic;
} BlockHeader;
#define ALLOC_MAGIC 0xCAFEBABE
void *malloc(size_t size) {
if (size == 0) return NULL;
// Align total size (header + data) to 16 bytes
size_t required = size + sizeof(BlockHeader);
size_t aligned_total = (required + 15) & ~15;
if (heap_ptr + aligned_total > HEAP_SIZE) return NULL;
BlockHeader *hdr = (BlockHeader *)&heap_memory[heap_ptr];
hdr->size = size;
hdr->magic = ALLOC_MAGIC;
void *ptr = (void *)((char *)hdr + sizeof(BlockHeader));
heap_ptr += aligned_total;
return ptr;
}
void free(void *ptr) {
// No-op bump allocator
}
void *realloc(void *ptr, size_t size) {
if (!ptr) return malloc(size);
if (size == 0) return NULL; // Standard says free.. or return NULL? mksh expects NULL or ptr.
// Get header
BlockHeader *hdr = (BlockHeader *)((char *)ptr - sizeof(BlockHeader));
if (hdr->magic != ALLOC_MAGIC) {
// Corrupted ptr? return NULL or fail.
return NULL;
}
// Optimization: If it's the LAST block, simple extend? (Not implemented for simplicity)
void *new_ptr = malloc(size);
if (!new_ptr) return NULL;
size_t copy_size = (hdr->size < size) ? hdr->size : size;
memcpy(new_ptr, ptr, copy_size);
return new_ptr;
}
void *calloc(size_t nmemb, size_t size) {
size_t total = nmemb * size;
void *ptr = malloc(total);
if (ptr) memset(ptr, 0, total);
return ptr;
}
// Stubs // Stubs
int fstat(int fd, struct stat *buf) { return 0; } int fstat(int fd, struct stat *buf) { return 0; }
int lstat(const char *path, struct stat *buf) { return 0; } int lstat(const char *path, struct stat *buf) { return 0; }
@ -32,7 +93,14 @@ int stat(const char *path, struct stat *buf) { return 0; }
int sigemptyset(sigset_t *set) { return 0; } int sigemptyset(sigset_t *set) { return 0; }
int sigaddset(sigset_t *set, int signum) { return 0; } int sigaddset(sigset_t *set, int signum) { return 0; }
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) { return 0; } int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) { return 0; }
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) { return 0; } int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) {
if (oldact) {
// Initialize to safe defaults - no handler installed
memset(oldact, 0, sizeof(struct sigaction));
oldact->sa_handler = SIG_DFL; // Default handler
}
return 0;
}
int sigsuspend(const sigset_t *mask) { return -1; } int sigsuspend(const sigset_t *mask) { return -1; }
int kill(pid_t pid, int sig) { return 0; } int kill(pid_t pid, int sig) { return 0; }
unsigned int alarm(unsigned int seconds) { return 0; } unsigned int alarm(unsigned int seconds) { return 0; }
@ -45,7 +113,21 @@ off_t lseek(int fd, off_t offset, int whence) { return 0; }
// int close(int fd) { return 0; } // In libc.nim // int close(int fd) { return 0; } // In libc.nim
int pipe(int pipefd[2]) { return -1; } int pipe(int pipefd[2]) { return -1; }
int dup2(int oldfd, int newfd) { return newfd; } int dup2(int oldfd, int newfd) { return newfd; }
int fcntl(int fd, int cmd, ...) { return 0; } extern void console_write(const void* p, unsigned long len);
static void ksh_debug(const char* s) {
console_write(s, (unsigned long)strlen(s));
}
int fcntl(int fd, int cmd, ...) {
ksh_debug("[mksh] fcntl called\n");
va_list args;
va_start(args, cmd);
long arg = va_arg(args, long);
va_end(args);
int res = (int)syscall(0x206, (long)fd, (long)cmd, arg);
ksh_debug("[mksh] fcntl returning\n");
return res;
}
int ioctl(int fd, unsigned long request, ...) { return 0; } int ioctl(int fd, unsigned long request, ...) { return 0; }
// int execve(const char *pathname, char *const argv[], char *const envp[]) { return -1; } // In clib.c // int execve(const char *pathname, char *const argv[], char *const envp[]) { return -1; } // In clib.c
@ -65,7 +147,26 @@ int seteuid(uid_t uid) { return 0; }
int setegid(gid_t gid) { return 0; } int setegid(gid_t gid) { return 0; }
int setpgid(pid_t pid, pid_t pgid) { return 0; } int setpgid(pid_t pid, pid_t pgid) { return 0; }
int tcgetattr(int fd, struct termios *termios_p) { return 0; } int tcgetattr(int fd, struct termios *termios_p) {
if (termios_p) {
// Initialize with safe defaults (using numeric values to avoid missing constants)
memset(termios_p, 0, sizeof(struct termios));
// Set basic flags for canonical mode
termios_p->c_iflag = 0x0100; // ICRNL
termios_p->c_oflag = 0x0001 | 0x0004; // OPOST | ONLCR
termios_p->c_cflag = 0x0030 | 0x0080; // CS8 | CREAD
termios_p->c_lflag = ISIG | ICANON | ECHO; // These should be defined
// Set control characters
termios_p->c_cc[VINTR] = 3; // Ctrl-C
termios_p->c_cc[VQUIT] = 28; // Ctrl-backslash
termios_p->c_cc[VERASE] = 127; // DEL
termios_p->c_cc[VKILL] = 21; // Ctrl-U
termios_p->c_cc[VEOF] = 4; // Ctrl-D
termios_p->c_cc[VMIN] = 1;
termios_p->c_cc[VTIME] = 0;
}
return 0;
}
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p) { return 0; } int tcsetattr(int fd, int optional_actions, const struct termios *termios_p) { return 0; }
pid_t tcgetpgrp(int fd) { return 1; } pid_t tcgetpgrp(int fd) { return 1; }
int tcsetpgrp(int fd, pid_t pgrp) { return 0; } int tcsetpgrp(int fd, pid_t pgrp) { return 0; }
@ -128,7 +229,56 @@ mode_t umask(mode_t mask) { return 0; }
int c_ulimit(const char **wp) { return 0; } int c_ulimit(const char **wp) { return 0; }
void longjmp(jmp_buf env, int val) { while(1); } int setjmp(jmp_buf env) {
int setjmp(jmp_buf env) { return 0; } __asm__ volatile (
"sd ra, 0(%0)\n"
"sd sp, 8(%0)\n"
"sd s0, 16(%0)\n"
"sd s1, 24(%0)\n"
"sd s2, 32(%0)\n"
"sd s3, 40(%0)\n"
"sd s4, 48(%0)\n"
"sd s5, 56(%0)\n"
"sd s6, 64(%0)\n"
"sd s7, 72(%0)\n"
"sd s8, 80(%0)\n"
"sd s9, 88(%0)\n"
"sd s10, 96(%0)\n"
"sd s11, 104(%0)\n"
"li a0, 0\n"
: : "r"(env) : "memory", "a0"
);
return 0;
}
void exit(int status) { while(1); } void longjmp(jmp_buf env, int val) {
__asm__ volatile (
"ld ra, 0(%0)\n"
"ld sp, 8(%0)\n"
"ld s0, 16(%0)\n"
"ld s1, 24(%0)\n"
"ld s2, 32(%0)\n"
"ld s3, 40(%0)\n"
"ld s4, 48(%0)\n"
"ld s5, 56(%0)\n"
"ld s6, 64(%0)\n"
"ld s7, 72(%0)\n"
"ld s8, 80(%0)\n"
"ld s9, 88(%0)\n"
"ld s10, 96(%0)\n"
"ld s11, 104(%0)\n"
"mv a0, %1\n"
"seqz t0, a0\n"
"add a0, a0, t0\n"
: : "r"(env), "r"(val) : "memory", "a0", "t0"
);
// Note: longjmp should not return. In this freestanding env,
// the asm above ends with registers restored and we jump back.
// However, to be extra safe we could add a ret at the end of asm.
__asm__ volatile("ret");
}
void exit(int status) {
syscall(0x01, (long)status, 0, 0);
while(1);
}