feat: proper IPC-based boot — init loads servers from storage via IPC, VirtIO GPU driver

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).
This commit is contained in:
2026-10-18 13:02:30 +02:00
parent ffde4065ba
commit 38f20cc3c4
41 changed files with 1676 additions and 808 deletions

View File

@@ -135,7 +135,7 @@ pub fn init() {
let addr = trap_vector as usize;
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
core::arch::asm!("csrw sscratch, zero");
core::arch::asm!("csrs sstatus, {0}", in(reg) 1usize << 1);
// SIE stays clear — interrupts are enabled only when entering userspace via sret
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
}
@@ -230,6 +230,25 @@ fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
count,
})
}
SYS_SPAWN_THREAD_IN => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::SpawnThreadIn {
process: slot,
entry: types::address::EntryPoint::from_raw(a1),
stack: types::address::StackPointer::from_raw(a2),
})
}
SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint),
SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }),
SYS_MAP_PAGE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::MapPage {
process: slot,
src_va: types::address::VirtualAddress::from_raw(a1),
dst_va: types::address::VirtualAddress::from_raw(a2),
executable: a3 != 0,
})
}
_ => None,
}
}
@@ -272,6 +291,30 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
frame.sstatus |= 1 << 5; // set SPIE
}
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
let kern = crate::kernel_ref();
if let Some(result) = kern.take_pending_result(tid) {
write_result_direct(frame, &result);
}
}
fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
use kernel::SyscallResult;
match result {
SyscallResult::Ok => frame.regs[10] = 0,
SyscallResult::Value(v) => frame.regs[10] = *v,
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
SyscallResult::Message(m) => {
frame.regs[10] = m.word(0);
frame.regs[11] = m.word(1);
frame.regs[12] = m.word(2);
frame.regs[13] = m.word(3);
}
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
}
}
fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, next_tid: types::id::ThreadId) {
let kern = crate::kernel_ref();
@@ -286,6 +329,8 @@ fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, nex
load_context_to_frame(ctx, frame);
}
apply_pending_result(frame, next_tid);
if next_pid != current_pid {
if let Some(pid) = next_pid {
let pt_addr = kern.platform_mut().page_table_for(pid);
@@ -308,10 +353,15 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
let next_pid = kern.thread_process(next_tid);
let current_pid = kern.thread_process(current_tid);
let core = types::id::CoreId::from_raw(0);
let _ = kern.set_thread_running(next_tid, core);
if let Some(ctx) = kern.thread_context(next_tid) {
load_context_to_frame(ctx, frame);
}
apply_pending_result(frame, next_tid);
if next_pid != current_pid {
if let Some(pid) = next_pid {
let pt_addr = kern.platform_mut().page_table_for(pid);
@@ -322,7 +372,6 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
}
}
} else {
// no runnable thread — idle
loop { unsafe { core::arch::asm!("wfi") }; }
}
}