- Driver now drains SYN/repeat/unknown events in a loop instead of
returning None on the first non-key event
- Snake drains all pending input events per tick, applies only the
latest direction change (prevents stale event accumulation)
- Add VirtIO input device driver (device ID 18) reading from eventq
- Translate Linux keycodes (WASD, arrows) to InputEvent
- Kernel probes for VirtIO input device, falls back to UART
- Add -device virtio-keyboard-device to run-gui Makefile target
- input-qemu tries VirtIO first, falls back to UART
- Fix swapped command IDs: attach_backing was 0x0104 (FLUSH),
resource_flush was 0x0106 (ATTACH_BACKING). Now correct.
- Increase process stack from 4 pages (16 KB) to 16 pages (64 KB)
to prevent stack overflow in display process.
- Restructure display-qemu to avoid MockFramebuffer's 32 KB array
inflating the VirtIO path's stack frame.
- Wire snake app to render through display server via IPC instead
of UART escape codes. Uses existing renderer.rs pixel renderer.
- Init now delegates display endpoint to snake process.
VirtIO GPU:
- Removed QEMU 11.x command type swap hack (0x0104↔0x0106).
The hack was wrong — swapping types without swapping data formats
meant handlers read wrong struct layouts. Commands returned OK
but did nothing.
- Restored correct VirtIO spec command types.
- Removed broken test pattern (fill + flush that never displayed).
- GPU setup now reports success/failure without crashing.
Display server resilience:
- VirtioFramebuffer::new() → try_new() -> Option<Self>.
Returns None if phys-info page is unmapped or has zero dimensions.
- bins/display-qemu: tries VirtioFramebuffer, falls back to
MockFramebuffer. Display server runs headless instead of crashing.
- No more page faults when GPU is absent.
The GPU driver code is architecturally correct (proper VirtIO
commands, proper data formats) but doesn't produce visible output
on QEMU 11.x due to a suspected QEMU legacy MMIO bug where
transfer_to_host_2d succeeds but doesn't transfer data. The driver
is ready for when this is fixed or a different QEMU version is used.
TimeSlice::DEFAULT and Priority::DEFAULT already exist with the
same values. Using them makes the intent clear and centralizes
the default values.
TimeSlice::new(2) in tick test is intentional — tests preemption
after 2 ticks specifically.
CRITICAL — MapPage privilege escalation:
handle_map_page allowed mapping kernel memory (VA >= 0x80000000)
into userspace. Any process with a Process capability could read/
write kernel data structures. Fixed: reject src_va >= USER_VA_LIMIT.
HIGH — resource leaks:
handle_kill: leaked pages (used cleanup_threads directly instead
of destroy_process). Now uses destroy_process for full cleanup.
handle_create_process: orphaned process on cap grant failure.
Now destroys process on rollback.
handle_create_endpoint: orphaned endpoint on cap grant failure.
Now destroys endpoint on rollback.
Thread ID array: hardcoded [None; 4] replaced with
[None; MAX_THREADS_PER_PROCESS] slice. cleanup_threads now
takes &[Option<ThreadId>] slice.
MEDIUM — quality fixes:
handle_request_pages: replaced unreachable!() with proper error
return + page pool rollback.
Page lifecycle:
- ProcessMemory now tracks (PhysicalAddress, count) pairs for each
allocation (up to 16). destroy_process iterates and frees all
pages back to the pool. No more permanent page leaks on exit/fault.
- RequestPages re-adds MemoryRegion entries so ReleasePages works.
Release test un-ignored, passes.
- RequestPages: VA allocation moved after pool allocation succeeds,
preventing VA leak on OOM.
IPC hardening:
- WaitQueue overflow (>16 waiters): returns WouldBlock error instead
of silently dropping the thread. Thread is not blocked if enqueue
fails.
- debug_assert on pending_messages[sender].take() — invariant that
a sender in wait queue must have a pending message.
Scheduler:
- remove() now clears current thread if the removed thread was
current on its core.
- add() asserts push succeeded; bitmap bit only set if thread was
actually added to RunList.
Cleanup:
- Priority::DEFAULT replaces Priority::new(16).unwrap() in
spawn_thread and spawn_thread_in.
36 kernel tests pass, 0 ignored.
Critical: kernel_ref() UB eliminated
- kernel_ref() returned &'static mut, creating aliasing mutable
references across trap handler + boot code (13+ call sites)
- Replaced with kernel_ptr() -> *mut Kernel, each use site does
explicit unsafe { &mut *kernel_ptr() }
- KernelCell: removed get_mut(), added as_ptr() -> *mut T
- UB is gone: raw pointer access makes the single-core invariant
explicit at every use site
Important: process cleanup on exit/fault
- New destroy_process() method consolidates cleanup:
- Removes all threads from scheduler and endpoints
- Clears pending messages and results
- Destroys process and frees ProcessMemory
- handle_exit and handle_fault both use destroy_process()
- handle_exit_thread uses destroy_process when last thread exits
IPC hardening:
- Added invariant comment on pending_messages[sender].take()
(sender in wait queue must have a pending message)
handle_release_pages:
- Now uses translate_va to convert VA→PA (was treating VA as PA)
Tests:
- IPC tests: spawn thread B explicitly (CreateProcess no longer
spawns threads since the boot logic fix)
- Memory tests: RequestPages now returns Value(va) not Slot(cap)
- MockPlatform: tracks VA→PA mappings for translate_va
- ReleasePages test: ignored (RequestPages no longer creates
regions — ReleasePages needs redesign)
CapSlot::const_new(value):
- const fn that panics at compile time for invalid values
- Replaces CapSlot::new(N).unwrap() in init, echo, snake
- Cap slot constants are now compile-time verified
Also fixes handle_release_pages to use translate_va instead of
treating VA as PA directly.
35 kernel tests pass, 1 ignored.
Snake renders using ANSI escape codes via debug_putchar syscall,
bypassing the VirtIO GPU (which has a QEMU 11.x legacy MMIO bug
where transfer_to_host_2d succeeds but doesn't transfer data).
Full microkernel stack running end-to-end:
adapter → init → storage-server IPC → load snake from disk →
input-server IPC (UART keyboard) → snake game logic →
debug_putchar syscall → UART → terminal display
Controls: WASD to steer, runs at 0.2s tick rate.
Green O=head, o=body, red @=food, #=border.
Every syscall stub now uses proper types from types/ — CapSlot,
EntryPoint, StackPointer, VirtualAddress, PageCount, ProcessId,
PermissionSet, Ticks. The compiler prevents swapping entry/stack,
passing a ProcessId where a CapSlot is expected, or using an
address as a page count.
New display types in types/display.rs:
- Color(u32) with rgb() constructor
- ScreenCoord(u16) for x/y positions
- Dimension(u16) for width/height
Updated throughout:
- userspace/syscall.rs: all params typed, returns Option instead
of sentinel usize::MAX
- server-framework: endpoint param is CapSlot
- port-framebuffer: trait uses Color, ScreenCoord, Dimension
- hw-virtio: DmaAddrs uses PhysicalAddress/VirtualAddress,
GPU commands use Dimension
- All servers, drivers, bins, apps updated for new signatures
- permission-manager: process field uses ProcessId
- PermissionSet: added bits() accessor
89 tests pass, full QEMU boot verified.
Separate abstractions from implementations following DIP:
servers/ drivers/
│ │
▼ ▼
ports/ (trait-only crates, zero deps)
New crates:
ports/ (abstractions):
- port-framebuffer: Framebuffer trait
- port-block-device: BlockDevice trait + SectorId
- port-input-device: InputDevice trait + InputEvent types
drivers/ (implementations depend on ports/):
- driver-framebuffer-virtio: VirtIO GPU framebuffer
- driver-framebuffer-mock: test double
- driver-block-virtio: VirtIO block device
bins/ (composition roots wire server + driver):
- display-qemu: DisplayServer<VirtioFramebuffer>
- storage-qemu: storage server with VirtIO block
Servers no longer own their hardware traits or driver code.
Neither server nor driver depends on the other — both point
inward to the port abstraction.
Complete microkernel boot pipeline: adapter loads only init + storage + display
from disk. Init bootstraps all other servers by reading binaries from the
storage server via IPC, parsing ELF, creating processes, mapping pages, and
spawning threads.
New syscalls:
- CreateEndpoint: userspace endpoint creation with full capability grant
- DebugPutchar: debug output to UART from userspace
- MapPage: cross-process page mapping (init loads binaries into children)
- SpawnThreadIn: spawn thread in another process via capability
Key fixes:
- Per-process L1 page tables for VPN[2]=0 (prevents cross-process corruption)
- Separate kernel_pt from page_tables[0] (init pid=0 overwrote kernel PT ref)
- Thread Running state set in switch_to_next (syscalls rejected without it)
- Pending IPC results applied during context switch
- SIE disabled during boot (timer can't preempt into unready processes)
- User heap VA starts at 0x40000 (avoids shared buffer at 0x30000)
- RequestPages maps at user VAs via per-process heap allocator
VirtIO GPU:
- Adapter probes for GPU device, initializes resource/scanout/backing
- Display server writes to mapped framebuffer, flushes via GPU virtqueue
- 640x480 BGRA framebuffer with transfer-to-host and resource-flush
Storage server:
- Userspace VirtIO block driver using pre-mapped MMIO and DMA pages
- FAT16 filesystem reader serving file-read requests via IPC
- Shared buffer protocol: init writes filename, storage writes file data
Echo app for IPC testing (adapter-loaded echo proved the pipeline end-to-end).
- CreateProcess only creates process + grants cap, no thread spawning
- boot::boot takes &mut BootResult instead of returning it (fixes -> ! sret convention issue)
- Init creates 6 server processes, sets up supervisor, enters receive loop
- No faults on QEMU with full boot sequence
- Userspace linker script: apps/servers link at 0x10000 (user VA)
- Adapter linker script: kernel links at 0x80200000 (S-mode)
- Each uses build.rs to emit its own -T flag, global config only sets panic=abort
- ELF loader: page-aligns segment mappings, handles non-aligned vaddrs
- Stack: 4 pages (16K) mapped below program
- SP set to top of stack (0x10000), not bottom
- FAT16 disk image forced (mformat was creating FAT32)
- End-to-end verified: VirtIO → FAT16 → ELF → userspace → ecall → exit
VirtIO now properly scans 8 MMIO slots, detects legacy vs modern mode,
uses correct virtqueue page alignment for legacy, and reads sectors.
Fixed disk image script to force FAT16 (mformat was creating FAT32).
init.elf successfully loaded from FAT16 disk via VirtIO on QEMU.
Every server now has lib.rs (testable) + main.rs (deployable binary).
Binary is 3 lines: extern crate userspace_rt + call run().
Hardware servers use feature flags for adapter selection.
9 RISC-V binaries ready for the disk image.
Adapter probes for VirtIO block device, reads init.elf from FAT16 disk,
parses ELF, maps into userspace, and launches. Falls back to embedded
minimal init if no disk is present.
Also: VirtIO MMIO driver, boot FAT16 reader, disk image build script,
Makefile targets for build/run/run-disk.
Adapter now: parses init binary (ELF or raw), allocates pages, creates
user page table, sets thread entry point in SavedContext, enters U-mode.
Init takes over from there — no more IPC demo hardcoded in the adapter.
- Halt syscall: only pid 0 (init) can halt, adapter uses SBI SRST
- Fault notification: kernel delivers fault info to process fault handler endpoint before killing
- Supervisor: tracks restart counts, back-off up to 5 restarts
- Shutdown: notifies servers, waits with timeout, calls halt
- Trap handler: faults now call kernel.handle_fault instead of halting