diff --git a/crates/adapter-riscv/src/boot/caps.rs b/crates/adapter-riscv/src/boot/caps.rs index 628b798..2945356 100644 --- a/crates/adapter-riscv/src/boot/caps.rs +++ b/crates/adapter-riscv/src/boot/caps.rs @@ -5,13 +5,9 @@ fn kern() -> &'static mut kernel::Kernel { unsafe { &mut *crate::kernel_ptr() } } -pub fn grant_all( - uart: &Uart, - init_pid: ProcessId, - storage_pid: ProcessId, - display_pid: ProcessId, - input_pid: ProcessId, -) { +/// Init gets storage's process and endpoint (slots 1 and 2), storage its +/// endpoint (slot 0); everything else init starts itself. +pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) { let all_perms = types::permission::PermissionSet::empty() .with(types::permission::Permission::Read) .with(types::permission::Permission::Write) @@ -49,74 +45,5 @@ pub fn grant_all( ) .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - let display_ep = kern() - .create_endpoint(display_pid) - .unwrap_or_else(|_| crate::halt(uart, "display endpoint failed")); - - kern() - .grant_capability( - display_pid, - domain::resource::Resource::Endpoint(display_ep), - ipc_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - - kern() - .grant_capability( - init_pid, - domain::resource::Resource::Endpoint(display_ep), - all_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - kern() - .grant_capability( - init_pid, - domain::resource::Resource::Process(display_pid), - all_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - - let input_ep = kern() - .create_endpoint(input_pid) - .unwrap_or_else(|_| crate::halt(uart, "input endpoint failed")); - - kern() - .grant_capability( - input_pid, - domain::resource::Resource::Endpoint(input_ep), - ipc_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - - kern() - .grant_capability( - init_pid, - domain::resource::Resource::Endpoint(input_ep), - all_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - kern() - .grant_capability( - init_pid, - domain::resource::Resource::Process(input_pid), - all_perms, - ) - .unwrap_or_else(|_| crate::halt(uart, "grant failed")); - uart.write_str("k-os: capabilities granted\n"); } - -/// Until the log server owns the console, the servers loaded here print -/// through a console cap of their own, its slot passed in `a0`. -pub fn grant_console(uart: &Uart, servers: &[super::loader::LoadedProcess]) { - let write = - types::permission::PermissionSet::empty().with(types::permission::Permission::Write); - for server in servers { - let slot = kern() - .grant_capability(server.pid, domain::resource::Resource::Console, write) - .unwrap_or_else(|_| crate::halt(uart, "console grant failed")); - if let Some(ctx) = kern().thread_context_mut(server.tid) { - ctx.registers_mut()[10] = types::start_arg::encode(Some(slot), None); - } - } -} diff --git a/crates/adapter-riscv/src/boot/loader.rs b/crates/adapter-riscv/src/boot/loader.rs index e9d3d3b..30c5482 100644 --- a/crates/adapter-riscv/src/boot/loader.rs +++ b/crates/adapter-riscv/src/boot/loader.rs @@ -12,10 +12,8 @@ const USER_VA_BASE: usize = 0x10000; const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1; const MAX_FILE_SIZE: usize = 64 * 1024; -#[derive(Clone, Copy)] pub struct LoadedProcess { pub pid: ProcessId, - pub tid: ThreadId, } fn kern() -> &'static mut kernel::Kernel { @@ -50,30 +48,17 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess { setup_thread(tid, entry); uart.write_str("k-os: init loaded\n"); - LoadedProcess { pid, tid } + LoadedProcess { pid } } -/// The servers boot loads, their budgets carved from init's. -pub const STORAGE: Limits = Limits { +/// Storage's budget, carved from init's. +const STORAGE: Limits = Limits { pages: 256, endpoints: 2, notifications: 2, // A buffer per client (8) and the disk's DMA. regions: 12, }; -/// The framebuffer alone is ~1000 pages at 1280x800. -pub const DISPLAY: Limits = Limits { - pages: 1280, - endpoints: 2, - notifications: 2, - regions: 4, -}; -pub const INPUT: Limits = Limits { - pages: 64, - endpoints: 2, - notifications: 2, - regions: 4, -}; pub fn load_server( uart: &Uart, @@ -81,10 +66,9 @@ pub fn load_server( init: ProcessId, filename: &[u8], label: &str, - limits: Limits, ) -> LoadedProcess { let pid = kern() - .create_process_internal(init, limits) + .create_process_internal(init, STORAGE) .unwrap_or_else(|| crate::halt(uart, "process create failed")); let entry = load_elf(uart, blk, pid, filename); let tid = kern() @@ -95,7 +79,7 @@ pub fn load_server( uart.write_str(label); uart.write_str(" loaded\n"); - LoadedProcess { pid, tid } + LoadedProcess { pid } } fn setup_thread(tid: ThreadId, entry: usize) { diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index 19f3b54..05a7dbd 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -40,32 +40,14 @@ extern "C" fn kmain(hart: usize) -> ! { boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device")); let init = boot::loader::load_init(&uart, &mut blk); - use boot::loader::{DISPLAY, INPUT, STORAGE, load_server}; - let storage = load_server( - &uart, - &mut blk, - init.pid, - b"storage.elf", - "storage", - STORAGE, - ); - let display = load_server( - &uart, - &mut blk, - init.pid, - b"display.elf", - "display", - DISPLAY, - ); - let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT); + // Init needs storage to load anything else; the rest is init's to start. + let storage = boot::loader::load_server(&uart, &mut blk, init.pid, b"storage.elf", "storage"); let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table")); kern_mut() .register_board(init.pid, &board) .unwrap_or_else(|_| halt(&uart, "board registration failed")); - boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid); - // Last, so the servers' fixed slots stay where they expect them. - boot::caps::grant_console(&uart, &[storage, display, input]); + boot::caps::grant_all(&uart, init.pid, storage.pid); // After grant_all: init already relies on those slots. let mut placed = [None; board::BOARD_DEVICES]; let boot_caps = kern_mut() diff --git a/crates/init/src/launch.rs b/crates/init/src/launch.rs new file mode 100644 index 0000000..7e2e794 --- /dev/null +++ b/crates/init/src/launch.rs @@ -0,0 +1,115 @@ +//! Starting a manifest program: load it, hand it what the manifest says +//! and nothing else, run it. + +use libkos::device::Device as BoardDevice; +use libkos::fs::Storage; +use libkos::syscall; +use types::address::StackPointer; +use types::bounded::CapSlot; +use types::permission::{Permission, PermissionSet}; + +use crate::devices::Devices; +use crate::load::{self, LoadedProcess}; +use crate::manifest::{Device, Program}; + +const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000); +const MAX_SERVED: usize = 8; + +fn perms(list: &[Permission]) -> PermissionSet { + list.iter().fold(PermissionSet::empty(), |s, p| s.with(*p)) +} + +pub(crate) struct Launcher<'a> { + storage: &'a mut Storage, + scratch: &'a mut [u8], + devices: Devices, + /// The endpoint of every server started so far, by name. + served: [Option<(&'static str, CapSlot)>; MAX_SERVED], +} + +impl<'a> Launcher<'a> { + pub(crate) fn new(storage: &'a mut Storage, scratch: &'a mut [u8], devices: Devices) -> Self { + Self { storage, scratch, devices, served: [None; MAX_SERVED] } + } + + pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) { + (self.storage, self.scratch) + } + + fn endpoint(&self, name: &str) -> Option { + self.served.iter().flatten().find(|(n, _)| *n == name).map(|(_, ep)| *ep) + } + + /// What became of the program's device, if it has one. + pub(crate) fn launch(&mut self, p: &Program) -> Result<&'static str, &'static str> { + let loaded = + load::load_binary(self.storage, self.scratch, p.file, p.limits).ok_or("cannot load")?; + let child = loaded.process_cap; + let ep = match p.serves { + true => { + let ep = syscall::create_endpoint().ok_or("no endpoint")?; + // Read to receive on it; init keeps the original. + let server = perms(&[Permission::Read, Permission::Execute]); + syscall::delegate(ep, child, server).ok_or("cannot give its endpoint")?; + Some(ep) + } + false => None, + }; + // A client only calls: Execute, never Read, so it cannot pose as the + // server and take other clients' requests. + for name in p.uses { + let ep = self.endpoint(name).ok_or("needs a server that is not running")?; + syscall::delegate(ep, child, perms(&[Permission::Execute])).ok_or("cannot give an endpoint")?; + } + let console = libkos::start::console() + .filter(|_| p.console) + .and_then(|c| syscall::delegate(c, child, perms(&[Permission::Write]))); + start(&loaded, console); + + let mut note = ""; + if let Some(ep) = ep { + if let Some(free) = self.served.iter_mut().find(|s| s.is_none()) { + *free = Some((p.name, ep)); + } + if let Some(device) = p.device { + note = self.give(device, ep); + } + if console.is_some() { + hand_over_console(ep); + note = ", console handed over"; + } + } + Ok(note) + } + + /// The driver waits for its device before serving, and runs without + /// one (headless) if the board has none. + fn give(&self, device: Device, ep: CapSlot) -> &'static str { + let (dev, given, none): (BoardDevice, _, _) = match device { + Device::Gpu => (self.devices.gpu, ", gpu handed over", ", no gpu"), + Device::Input => (self.devices.input, ", input device handed over", ", no input device"), + }; + match libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some() { + true => given, + false => none, + } + } +} + +/// From here on init prints through the log server, like everyone else, +/// and holds no console of its own. +fn hand_over_console(log: CapSlot) { + if let Some(console) = libkos::start::console() { + syscall::drop_cap(console); + } + libkos::start::set_console(None); + libkos::start::set_log(Some(log)); +} + +/// Starts `program` with `console` and, once there is one, the log. +pub(crate) fn start(program: &LoadedProcess, console: Option) { + let log = libkos::start::log() + .and_then(|l| syscall::delegate(l, program.process_cap, perms(&[Permission::Execute]))); + let arg = types::start_arg::encode(console, log); + syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg); +} diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 7db7514..628f557 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -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 { - 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 { - 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 diff --git a/crates/init/src/load.rs b/crates/init/src/load.rs new file mode 100644 index 0000000..bd416a7 --- /dev/null +++ b/crates/init/src/load.rs @@ -0,0 +1,106 @@ +//! Reading program files from storage and loading them into new processes. + +use libkos::fs::Storage; +use libkos::syscall; +use types::address::{EntryPoint, VirtualAddress}; +use types::bounded::{CapSlot, PageCount}; +use types::budget::Limits; + +const PAGE_SIZE: usize = 4096; +const USER_CODE_BASE: usize = 0x10000; +const USER_STACK_PAGES: usize = 4; +const CREATE_CAP: CapSlot = CapSlot::const_new(0); +/// 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; + +pub(crate) struct LoadedProcess { + pub(crate) process_cap: CapSlot, + pub(crate) entry_point: EntryPoint, +} + +/// 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 { + 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) fn load_binary( + storage: &mut Storage, + scratch: &mut [u8], + filename: &[u8], + limits: Limits, +) -> Option { + 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), + }) +} + +/// A region of init's own to read program files into. +pub(crate) 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) }) +} diff --git a/crates/init/src/manifest.rs b/crates/init/src/manifest.rs new file mode 100644 index 0000000..4807d48 --- /dev/null +++ b/crates/init/src/manifest.rs @@ -0,0 +1,55 @@ +//! What init starts, in order: the system's boot policy, as data. + +use types::budget::Limits; + +/// A board device init hands to a program over its endpoint. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Device { + Gpu, + Input, +} + +pub struct Program { + pub name: &'static str, + pub file: &'static [u8], + pub limits: Limits, + /// Serves an endpoint, which it finds in slot 0. + pub serves: bool, + pub device: Option, + /// Earlier programs' endpoints it may call, in its next slots. + pub uses: &'static [&'static str], + /// Takes the console over from init; everyone else prints through it. + pub console: bool, +} + +/// Enough for a small program: its image, stack and a little heap. +pub const SMALL: Limits = Limits { + pages: 64, + endpoints: 2, + notifications: 2, + regions: 2, +}; + +const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Program { + Program { name, file, limits, serves: true, device: None, uses: &[], console: false } +} + +/// Started on every boot. The log server comes first so the rest can print. +pub const SERVERS: &[Program] = &[ + Program { console: true, ..server("log", b"log.elf", SMALL) }, + Program { + device: Some(Device::Gpu), + // The framebuffer alone is ~1000 pages at 1280x800. + ..server("display", b"display.elf", Limits { pages: 1280, regions: 4, ..SMALL }) + }, + Program { device: Some(Device::Input), ..server("input", b"input.elf", Limits { regions: 4, ..SMALL }) }, + server("permis", b"permis.elf", SMALL), + server("session", b"session.elf", SMALL), +]; + +/// The desktop; the test image runs the SMP self-test instead. +pub const APPS: &[Program] = &[Program { + serves: false, + uses: &["input", "display"], + ..server("snake", b"snake.elf", Limits { pages: 128, ..SMALL }) +}]; diff --git a/crates/init/src/selftest.rs b/crates/init/src/selftest.rs index 47ddd61..ae3afda 100644 --- a/crates/init/src/selftest.rs +++ b/crates/init/src/selftest.rs @@ -40,7 +40,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re for i in 0..=THREADS { word(i).store(0, Ordering::SeqCst); } - let spinner = crate::load_binary(storage, scratch, b"smptest.elf", crate::SMALL).ok_or("cannot load smptest.elf")?; + let spinner = crate::load::load_binary(storage, scratch, b"smptest.elf", crate::manifest::SMALL).ok_or("cannot load smptest.elf")?; let rw = PermissionSet::empty() .with(Permission::Read) .with(Permission::Write) @@ -49,7 +49,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re if syscall::delegate(page, spinner.process_cap, rw).is_none() { return Err("cannot give the spinner its page"); } - crate::start(&spinner, true); + crate::launch::start(&spinner, None); if !wait_until(3000, || counters().iter().all(|c| *c > 0)) { return Err("spinner threads did not all start"); } diff --git a/crates/libkos/src/io.rs b/crates/libkos/src/io.rs index 4c841b3..a3d3c61 100644 --- a/crates/libkos/src/io.rs +++ b/crates/libkos/src/io.rs @@ -4,8 +4,13 @@ struct Writer; impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { - // Without a console capability, output goes nowhere. - if let Some(console) = crate::start::console() { + // The log server first; a process without either prints nothing. + // A call, not a send: the text is out before the caller goes on. + if let Some(log) = crate::start::log() { + for chunk in s.as_bytes().chunks(types::log::TEXT_BYTES) { + let _ = crate::syscall::call(log, types::log::text_words(chunk), None); + } + } else if let Some(console) = crate::start::console() { for &b in s.as_bytes() { crate::syscall::debug_putchar(console, b); } diff --git a/crates/libkos/src/start.rs b/crates/libkos/src/start.rs index f423795..6575b97 100644 --- a/crates/libkos/src/start.rs +++ b/crates/libkos/src/start.rs @@ -8,8 +8,9 @@ use types::start_arg; #[unsafe(no_mangle)] static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0); -/// Overrides the start argument's console once set; `NONE` drops output. +/// Override the start argument's slots once set; `NONE` means none. static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET); +static LOG: AtomicUsize = AtomicUsize::new(UNSET); const UNSET: usize = usize::MAX; const NONE: usize = usize::MAX - 1; @@ -19,18 +20,31 @@ fn arg() -> usize { /// The console capability this process prints through, if any. pub fn console() -> Option { - match CONSOLE.load(Ordering::Relaxed) { - UNSET => start_arg::console(arg()), - v => CapSlot::new(u8::try_from(v).ok()?), - } + read(&CONSOLE, start_arg::console) } /// For a process that gets its console some other way (init, from BootInfo). pub fn set_console(slot: Option) { - CONSOLE.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed); + write(&CONSOLE, slot); } -/// The log server endpoint this process was given, if any. +/// The log server endpoint this process prints to, if any. pub fn log() -> Option { - start_arg::log(arg()) + read(&LOG, start_arg::log) +} + +/// For a process given its log endpoint after it started. +pub fn set_log(slot: Option) { + write(&LOG, slot); +} + +fn read(cell: &AtomicUsize, from_arg: fn(usize) -> Option) -> Option { + match cell.load(Ordering::Relaxed) { + UNSET => from_arg(arg()), + v => CapSlot::new(u8::try_from(v).ok()?), + } +} + +fn write(cell: &AtomicUsize, slot: Option) { + cell.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed); } diff --git a/crates/servers/log-server/src/lib.rs b/crates/servers/log-server/src/lib.rs index 186ec60..51598f0 100644 --- a/crates/servers/log-server/src/lib.rs +++ b/crates/servers/log-server/src/lib.rs @@ -8,7 +8,14 @@ pub mod server; mod tests; pub fn run() -> ! { - let mut srv = server::LogServer::new(); + let console = libkos::start::console(); + let mut srv = server::LogServer::with_output(|bytes: &[u8]| { + if let Some(console) = console { + for &b in bytes { + libkos::syscall::debug_putchar(console, b); + } + } + }); let ep = types::bounded::CapSlot::new(0).unwrap(); server_framework::serve(&mut srv, ep) } diff --git a/crates/servers/log-server/src/protocol.rs b/crates/servers/log-server/src/protocol.rs index 916e128..84254d6 100644 --- a/crates/servers/log-server/src/protocol.rs +++ b/crates/servers/log-server/src/protocol.rs @@ -13,6 +13,8 @@ pub enum LogRequest { Write { level: LogLevel, tag: u16, payload: usize }, GetCount, GetEntry { index: usize }, + /// Console text (`types::log`): `len` bytes of `bytes`. + Text { bytes: [u8; types::log::TEXT_BYTES], len: usize }, } #[derive(Debug, PartialEq)] @@ -50,6 +52,7 @@ impl Encode for LogRequest { } LogRequest::GetCount => [1, 0, 0, 0], LogRequest::GetEntry { index } => [2, *index, 0, 0], + LogRequest::Text { bytes, len } => types::log::text_words(&bytes[..*len]), } } } @@ -64,7 +67,7 @@ impl Decode for LogRequest { } 1 => Some(LogRequest::GetCount), 2 => Some(LogRequest::GetEntry { index: words[1] }), - _ => None, + _ => types::log::text_of(words).map(|(bytes, len)| LogRequest::Text { bytes, len }), } } } diff --git a/crates/servers/log-server/src/server.rs b/crates/servers/log-server/src/server.rs index 635f02c..0c0ead8 100644 --- a/crates/servers/log-server/src/server.rs +++ b/crates/servers/log-server/src/server.rs @@ -10,18 +10,28 @@ struct LogEntry { payload: usize, } -pub struct LogServer { +/// Holds the console: every process's text goes out through `out`. +pub struct LogServer { entries: [Option; MAX_ENTRIES], count: usize, write_pos: usize, + out: O, } impl LogServer { + /// Text goes nowhere. pub fn new() -> Self { + Self::with_output(|_| {}) + } +} + +impl LogServer { + pub fn with_output(out: O) -> Self { Self { entries: [const { None }; MAX_ENTRIES], count: 0, write_pos: 0, + out, } } @@ -30,7 +40,7 @@ impl LogServer { } } -impl Server for LogServer { +impl Server for LogServer { type Request = LogRequest; type Response = LogResponse; @@ -44,6 +54,10 @@ impl Server for LogServer { } LogResponse::Ok } + LogRequest::Text { bytes, len } => { + (self.out)(&bytes[..len.min(bytes.len())]); + LogResponse::Ok + } LogRequest::GetCount => LogResponse::Count(self.count), LogRequest::GetEntry { index } => { if index >= self.count { diff --git a/crates/servers/log-server/src/tests/server.rs b/crates/servers/log-server/src/tests/server.rs index f0921c4..874f598 100644 --- a/crates/servers/log-server/src/tests/server.rs +++ b/crates/servers/log-server/src/tests/server.rs @@ -1,6 +1,6 @@ use crate::protocol::*; use crate::server::LogServer; -use server_framework::Server; +use server_framework::{Decode, Server}; #[test] fn empty_server_has_zero_count() { @@ -41,3 +41,20 @@ fn ring_buffer_wraps() { } assert_eq!(server.count(), 128); } + +#[test] +fn text_goes_out_through_the_console() { + let mut seen = [0u8; 64]; + let mut n = 0; + { + let mut server = LogServer::with_output(|b: &[u8]| { + seen[n..n + b.len()].copy_from_slice(b); + n += b.len(); + }); + for chunk in [&b"init: "[..], b"started\n"] { + let words = types::log::text_words(chunk); + server.handle(LogRequest::decode(words).unwrap()); + } + } + assert_eq!(&seen[..n], b"init: started\n"); +} diff --git a/crates/servers/storage-server/src/protocol.rs b/crates/servers/storage-server/src/protocol.rs index 755bfb0..0d5e4c0 100644 --- a/crates/servers/storage-server/src/protocol.rs +++ b/crates/servers/storage-server/src/protocol.rs @@ -23,6 +23,8 @@ pub enum StorageRequest { Create { path_len: usize }, Delete { path_len: usize }, Mkdir { path_len: usize }, + /// Carries the endpoint to print to. + SetLog, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -61,6 +63,7 @@ impl Encode for StorageRequest { Create { path_len } => [op::CREATE, path_len, 0, 0], Delete { path_len } => [op::DELETE, path_len, 0, 0], Mkdir { path_len } => [op::MKDIR, path_len, 0, 0], + SetLog => [op::SET_LOG, 0, 0, 0], } } } @@ -79,6 +82,7 @@ impl Decode for StorageRequest { op::CREATE => Create { path_len: w[1] }, op::DELETE => Delete { path_len: w[1] }, op::MKDIR => Mkdir { path_len: w[1] }, + op::SET_LOG => SetLog, _ => return None, }) } diff --git a/crates/servers/storage-server/src/server.rs b/crates/servers/storage-server/src/server.rs index 0ecf5dc..f32e3e0 100644 --- a/crates/servers/storage-server/src/server.rs +++ b/crates/servers/storage-server/src/server.rs @@ -31,6 +31,7 @@ pub struct StorageServer { clients: [Option; MAX_CLIENTS], /// The badge of the client that opened each handle. owners: [Option; MAX_HANDLES], + log: Option, } impl StorageServer { @@ -40,6 +41,26 @@ impl StorageServer { buffers, clients: [const { None }; MAX_CLIENTS], owners: [None; MAX_HANDLES], + log: None, + } + } + + /// Where this server prints, once init has said. + pub fn log(&self) -> Option { + self.log + } + + /// Clients hold badged copies of the endpoint, so the unbadged sender is + /// whoever was given the endpoint itself: init. + fn set_log(&mut self, badge: usize, cap: Option) -> StorageResponse { + match (badge, cap, self.log) { + (0, Some(cap), None) => { + self.log = Some(cap); + libkos::start::set_log(Some(cap)); + libkos::println!("storage: printing through the log server"); + StorageResponse::Ok + } + _ => StorageResponse::Error, } } @@ -117,7 +138,7 @@ impl StorageServer { None => R::NotFound, }; let response = match request { - Attach => R::Error, + Attach | SetLog => R::Error, Open { path_len } => { let h = fs.open(&buf[path(path_len)]); opened(owners, h) @@ -195,9 +216,10 @@ impl Server for StorageServer { &mut self, badge: usize, words: [usize; 4], - _cap: Option, + cap: Option, ) -> ([usize; 4], Option) { match StorageRequest::decode(words) { + Some(StorageRequest::SetLog) => (self.set_log(badge, cap).encode(), None), Some(request) => { let (response, cap) = self.serve(badge, request); (response.encode(), cap) diff --git a/crates/servers/storage-server/src/tests/server.rs b/crates/servers/storage-server/src/tests/server.rs index 8c7d8fc..b044e2c 100644 --- a/crates/servers/storage-server/src/tests/server.rs +++ b/crates/servers/storage-server/src/tests/server.rs @@ -180,3 +180,18 @@ fn garbage_gets_a_malformed_reply() { let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None); assert_eq!((words[0], cap), (MALFORMED, None)); } + +#[test] +fn only_the_unbadged_holder_sets_the_log_and_only_once() { + let mut s = server(); + let log = CapSlot::new(7); + let set = |s: &mut Srv, badge| { + let (words, _) = s.handle_call(badge, StorageRequest::SetLog.encode(), log); + StorageResponse::decode(words).unwrap() + }; + assert_eq!(set(&mut s, 5), StorageResponse::Error); + assert_eq!(s.log(), None); + assert_eq!(set(&mut s, 0), StorageResponse::Ok); + assert_eq!(s.log(), log); + assert_eq!(set(&mut s, 0), StorageResponse::Error); +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index e9e817c..682f4dd 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -8,6 +8,7 @@ pub mod budget; pub mod display; pub mod generation; pub mod id; +pub mod log; pub mod macros; pub mod mapping; pub mod permission; diff --git a/crates/types/src/log.rs b/crates/types/src/log.rs new file mode 100644 index 0000000..c1c9626 --- /dev/null +++ b/crates/types/src/log.rs @@ -0,0 +1,49 @@ +//! The log protocol's text message, shared by the log server and +//! `libkos::io` so the two cannot drift apart: word 0 is `TEXT` with the +//! length in bits 8..16, words 1..4 carry up to `TEXT_BYTES` bytes. + +pub const TEXT: usize = 3; +pub const TEXT_BYTES: usize = 24; + +/// `chunk` past `TEXT_BYTES` is cut off. +pub fn text_words(chunk: &[u8]) -> [usize; 4] { + let len = chunk.len().min(TEXT_BYTES); + let mut bytes = [0u8; TEXT_BYTES]; + bytes[..len].copy_from_slice(&chunk[..len]); + let mut words = [TEXT | (len << 8), 0, 0, 0]; + for (w, b) in words[1..].iter_mut().zip(bytes.as_chunks::<8>().0) { + *w = u64::from_le_bytes(*b) as usize; + } + words +} + +/// The bytes of a text message and how many of them count. +pub fn text_of(words: [usize; 4]) -> Option<([u8; TEXT_BYTES], usize)> { + if words[0] & 0xFF != TEXT { + return None; + } + let len = (words[0] >> 8) & 0xFF; + if len > TEXT_BYTES { + return None; + } + let mut bytes = [0u8; TEXT_BYTES]; + for (b, w) in bytes.as_chunks_mut::<8>().0.iter_mut().zip(&words[1..]) { + *b = (*w as u64).to_le_bytes(); + } + Some((bytes, len)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn text_round_trips_and_is_cut_at_the_limit() { + let (bytes, len) = text_of(text_words(b"init: hello")).unwrap(); + assert_eq!(&bytes[..len], b"init: hello"); + let long = [b'x'; 30]; + assert_eq!(text_of(text_words(&long)).unwrap().1, TEXT_BYTES); + assert_eq!(text_of([TEXT | (25 << 8), 0, 0, 0]), None); + assert_eq!(text_of([0, 0, 0, 0]), None); + } +} diff --git a/crates/types/src/storage.rs b/crates/types/src/storage.rs index 455e045..4ecf308 100644 --- a/crates/types/src/storage.rs +++ b/crates/types/src/storage.rs @@ -25,6 +25,8 @@ pub mod op { pub const CREATE: usize = 7; pub const DELETE: usize = 8; pub const MKDIR: usize = 9; + /// Carries the log endpoint; only from the unbadged holder (init). + pub const SET_LOG: usize = 10; } /// Reply tags (word 0). diff --git a/docs/ipc.md b/docs/ipc.md index 1cf4366..f8cd535 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -67,17 +67,24 @@ No process is privileged by its id. At boot the kernel gives init: - one `Console` capability (`Write`, `Delegate`). `DebugPutchar(console, byte)` needs a `Console` capability with `Write`; -there is no other way to reach the UART. Init lends its console to the -servers it starts (snake gets none). Until the log server owns the console, -boot code also grants one to the servers it loads itself. +there is no other way to reach the UART. Init prints with it only until it +has started the log server, then hands it over and drops its own; from then +on the log server is the only holder, and every other process prints by +calling it (`types::log`) if init gave it a log endpoint. A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's `arg` in `a0`. A process's first thread reads it as `types::start_arg`: the slots of the console and log capabilities it was handed. -The board capabilities come after the capabilities boot code hands out -for the built-in servers, so those slots are unchanged. Device regions -never overlap each other or the frame pool, and each device has exactly +Boot loads only init and storage (init needs it to read anything else): +init gets storage's process and endpoint in slots 1 and 2, before the +board capabilities. Everything else init starts from its manifest +(`init/src/manifest.rs`), handing each program only the endpoints, device +and budget listed there. Storage, started before the log server, receives +its log endpoint from init later (`SET_LOG`, accepted only from the +unbadged holder of its endpoint). + +Device regions never overlap each other or the frame pool, and each device has exactly one region, so delegating or revoking it means the same thing everywhere. `RegionAddress(cap)` returns a region's physical base so a driver can