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

@@ -1,18 +1,174 @@
#![no_std]
pub mod boot;
pub mod manifest;
pub mod protocol_helpers;
pub mod shutdown;
pub mod supervisor;
use userspace::syscall;
pub fn run() -> ! {
let create_cap = types::bounded::CapSlot::new(0)
.unwrap_or_else(|| unreachable!());
const PAGE_SIZE: usize = 4096;
const USER_CODE_BASE: usize = 0x10000;
const USER_STACK_TOP: usize = USER_CODE_BASE;
const USER_STACK_PAGES: usize = 4;
const CMD_READ_FILE: usize = 1;
let mut result = boot::BootResult::empty();
boot::boot(&manifest::SNAKE_MANIFEST, create_cap, &mut result);
const CREATE_CAP: usize = 0;
const STORAGE_EP_CAP: usize = 2;
let mut sup = boot::create_supervisor(&manifest::SNAKE_MANIFEST, &result);
sup.run(create_cap);
fn debug_print(s: &[u8]) {
for &b in s {
syscall::debug_putchar(b);
}
}
fn debug_hex(mut val: usize) {
debug_print(b"0x");
if val == 0 {
syscall::debug_putchar(b'0');
return;
}
let mut buf = [0u8; 16];
let mut i = 0;
while val > 0 {
let d = (val & 0xF) as u8;
buf[i] = if d < 10 { b'0' + d } else { b'a' + d - 10 };
val >>= 4;
i += 1;
}
while i > 0 {
i -= 1;
syscall::debug_putchar(buf[i]);
}
}
fn request_file(shared_buf: *mut u8, filename: &[u8]) -> usize {
unsafe {
for (i, &b) in filename.iter().enumerate() {
core::ptr::write_volatile(shared_buf.add(i), b);
}
}
syscall::send(STORAGE_EP_CAP, CMD_READ_FILE, filename.len(), 0, 0);
let (file_size, _, _, _) = syscall::receive(STORAGE_EP_CAP);
file_size
}
struct LoadedProcess {
process_cap: usize,
entry_point: usize,
}
fn load_binary(shared_buf: *mut u8, filename: &[u8]) -> Option<LoadedProcess> {
let file_size = request_file(shared_buf, filename);
if file_size == 0 {
return None;
}
let elf_data = unsafe { core::slice::from_raw_parts(shared_buf as *const u8, file_size) };
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
Err(_) => return None,
};
let process_cap = syscall::create_process(CREATE_CAP);
if process_cap == usize::MAX {
debug_print(b"create_process failed\n");
return None;
}
for segment in elf_info.segments() {
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let total_size = offset_in_page + segment.mem_size;
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
let local_va = syscall::request_pages(seg_pages);
if local_va == usize::MAX {
debug_print(b"request_pages failed\n");
return None;
}
let local_ptr = local_va as *mut u8;
unsafe {
core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE);
}
let copy_len = segment.file_size.min(segment.mem_size);
if copy_len > 0 {
let src = segment.file_offset;
if src + copy_len <= file_size {
unsafe {
core::ptr::copy_nonoverlapping(
shared_buf.add(src),
local_ptr.add(offset_in_page),
copy_len,
);
}
}
}
for p in 0..seg_pages {
let src_page = local_va + p * PAGE_SIZE;
let dst_page = page_start + p * PAGE_SIZE;
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
}
for i in 0..USER_STACK_PAGES {
let stack_va = syscall::request_pages(1);
if stack_va == usize::MAX { return None; }
let dst = USER_STACK_TOP - (USER_STACK_PAGES - i) * PAGE_SIZE;
syscall::map_page(process_cap, stack_va, dst, false);
}
Some(LoadedProcess {
process_cap,
entry_point: elf_info.entry_point,
})
}
struct ServerInfo {
filename: &'static [u8],
name: &'static [u8],
}
// display is loaded by the adapter (needs GPU MMIO access)
const SERVERS: &[ServerInfo] = &[
ServerInfo { filename: b"input.elf", name: b"input" },
ServerInfo { filename: b"log.elf", name: b"log" },
ServerInfo { filename: b"permis.elf", name: b"permis" },
ServerInfo { filename: b"session.elf", name: b"session" },
ServerInfo { filename: b"snake.elf", name: b"snake" },
];
pub fn run(shared_buf_va: usize) -> ! {
let shared_buf = shared_buf_va as *mut u8;
debug_print(b"init: started (pid=");
debug_hex(syscall::get_process_id());
debug_print(b")\n");
for server in SERVERS {
debug_print(b"init: loading ");
debug_print(server.name);
debug_print(b"... ");
let loaded = match load_binary(shared_buf, server.filename) {
Some(l) => l,
None => {
debug_print(b"SKIP\n");
continue;
}
};
let ep_cap = syscall::create_endpoint();
let read_execute: usize = (1 << 0) | (1 << 2);
syscall::delegate(ep_cap, loaded.process_cap, read_execute);
syscall::spawn_thread_in(loaded.process_cap, loaded.entry_point, USER_STACK_TOP);
debug_print(b"ok (entry=");
debug_hex(loaded.entry_point);
debug_print(b")\n");
}
debug_print(b"init: all loaded, supervising\n");
loop {
syscall::yield_now();
}
}