feat(userspace): log server sole console holder; init boots from manifest; adapter loads only init+storage

This commit is contained in:
2026-10-27 15:40:19 +01:00
parent 4049ba5007
commit a4f467f45d
21 changed files with 510 additions and 325 deletions

View File

@@ -3,224 +3,80 @@
extern crate alloc;
mod devices;
mod launch;
mod load;
mod manifest;
mod selftest;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::budget::Limits;
use types::permission::{Permission, PermissionSet};
use libkos::fs::Storage;
use libkos::syscall;
use types::badge::Badge;
use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
const PAGE_SIZE: usize = 4096;
const USER_CODE_BASE: usize = 0x10000;
pub(crate) const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
const USER_STACK_PAGES: usize = 4;
/// Where a program file is read to before it is loaded (16 pages: the
/// largest program init will load).
const SCRATCH_VA: usize = 0x30000;
const SCRATCH_PAGES: u16 = 16;
/// Where the buffer storage shares with init is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
/// Boot loads storage before init and leaves its endpoint here.
const STORAGE_EP: CapSlot = CapSlot::const_new(2);
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
const INPUT_EP: CapSlot = CapSlot::const_new(5);
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
/// missing, unreadable or larger than `scratch`.
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
let handle = storage.open(filename).ok()?;
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
let _ = storage.close(handle);
read.filter(|r| Some(*r) == size)
}
pub(crate) struct LoadedProcess {
pub(crate) process_cap: CapSlot,
pub(crate) entry_point: EntryPoint,
}
pub(crate) fn load_binary(
storage: &mut Storage,
scratch: &mut [u8],
filename: &[u8],
limits: Limits,
) -> Option<LoadedProcess> {
let file_size = read_file(storage, scratch, filename)?;
let elf_data = &scratch[..file_size];
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
Err(_) => return None,
};
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
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 page_count = PageCount::new(seg_pages as u16)?;
let local_va = syscall::request_pages(page_count)?;
let local_ptr = local_va.raw() 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);
let src = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
// bytes, which covers offset_in_page + copy_len.
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
}
}
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local_va.raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(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 page_count = PageCount::new(1)?;
let stack_va = syscall::request_pages(page_count)?;
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
syscall::map_page(process_cap, stack_va, dst, false);
}
Some(LoadedProcess {
process_cap,
entry_point: EntryPoint::from_raw(elf_info.entry_point),
})
}
struct ServerInfo {
filename: &'static [u8],
name: &'static str,
limits: Limits,
}
/// Enough for a small program: its image, stack and a little heap.
pub(crate) const SMALL: Limits = Limits {
pages: 64,
endpoints: 2,
notifications: 2,
regions: 2,
};
const SERVERS: &[ServerInfo] = &[
ServerInfo { filename: b"log.elf", name: "log", limits: SMALL },
ServerInfo { filename: b"permis.elf", name: "permis", limits: SMALL },
ServerInfo { filename: b"session.elf", name: "session", limits: SMALL },
];
const SNAKE: Limits = Limits { pages: 128, ..SMALL };
pub fn run() -> ! {
libkos::start::set_console(devices::console());
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
// Drivers wait for their device before serving, so this comes first:
// storage needs its disk before init can load anything.
// Storage waits for its disk before serving, so this comes first.
let found = devices::discover();
for (name, ep, dev) in [
("disk", STORAGE_EP, found.disk),
("gpu", DISPLAY_EP, found.gpu),
("input", INPUT_EP, found.input),
] {
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
}
let disk = libkos::device::give_device(STORAGE_EP, found.disk).is_ok() && found.disk.mmio.is_some();
libkos::println!("init: disk: {}", if disk { "handed to its driver" } else { "none" });
let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else {
libkos::println!("init: cannot reach storage, nothing to load");
idle();
};
let Some(scratch) = scratch() else {
// Storage has nothing to print with yet; init speaks for it.
libkos::println!("init: storage: up");
let Some(scratch) = load::scratch() else {
libkos::println!("init: no memory to load programs into");
idle();
};
let ipc_perms = PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Execute);
for server in SERVERS {
libkos::print!("init: loading {}... ", server.name);
let loaded = match load_binary(&mut storage, scratch, server.filename, server.limits) {
Some(l) => l,
None => {
libkos::println!("SKIP");
continue;
}
};
if let Some(ep_cap) = syscall::create_endpoint() {
syscall::delegate(ep_cap, loaded.process_cap, ipc_perms);
start(&loaded, true);
}
libkos::println!("ok (entry={:#x})", loaded.entry_point.raw());
}
let mut launcher = launch::Launcher::new(&mut storage, scratch, found);
launch_all(&mut launcher, manifest::SERVERS);
give_storage_the_log();
// The test image carries the SMP self-test; it runs instead of the
// desktop and halts the machine.
let (storage, scratch) = launcher.storage();
if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) {
libkos::println!("init: running the SMP self-test");
selftest::run(&mut storage, scratch);
}
libkos::print!("init: loading snake... ");
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf", SNAKE) {
// A client only calls: Execute, never Read, so it cannot pose as
// the server and take other clients' requests.
let client = PermissionSet::empty().with(Permission::Execute);
syscall::delegate(INPUT_EP, snake.process_cap, client);
syscall::delegate(DISPLAY_EP, snake.process_cap, client);
start(&snake, false);
libkos::println!("ok");
} else {
libkos::println!("SKIP");
selftest::run(storage, scratch);
}
launch_all(&mut launcher, manifest::APPS);
libkos::println!("init: all loaded, supervising");
idle();
}
/// Starts `program`, lending it init's console if `console`.
pub(crate) fn start(program: &LoadedProcess, console: bool) {
let write = PermissionSet::empty().with(Permission::Write);
let slot = libkos::start::console()
.filter(|_| console)
.and_then(|c| syscall::delegate(c, program.process_cap, write));
let arg = types::start_arg::encode(slot, None);
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg);
fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
for p in programs {
libkos::print!("init: loading {}... ", p.name);
match launcher.launch(p) {
Ok(note) => libkos::println!("ok{}", note),
Err(why) => libkos::println!("SKIP ({})", why),
}
}
}
/// A region of init's own to read program files into.
fn scratch() -> Option<&'static mut [u8]> {
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
syscall::map_region(
region,
VirtualAddress::from_raw(SCRATCH_VA),
types::mapping::PagePerms::ReadWrite,
)
.ok()?;
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
/// Storage started before the log server; it gets a call-only copy of
/// the log endpoint now (Delegate only to send it: transfer strips it).
fn give_storage_the_log() {
let Some(log) = libkos::start::log() else {
return;
};
let call_only = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
if let Ok(copy) = syscall::mint(log, Badge::NONE, call_only) {
let _ = syscall::call(STORAGE_EP, [types::storage::op::SET_LOG, 0, 0, 0], Some(copy));
syscall::drop_cap(copy);
}
}
/// Nothing to supervise yet: block on a notification nobody signals