From 7415bab7f5a213604d8976ed17a497eff28e235f Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 27 Oct 2026 14:54:36 +0100 Subject: [PATCH] feat(kernel): console cap gates DebugPutchar, no uart fast path; spawn start arg in a0; delegate returns slot --- crates/adapter-riscv/src/boot/caps.rs | 15 +++++++ crates/adapter-riscv/src/boot/loader.rs | 8 ++-- crates/adapter-riscv/src/devices/mod.rs | 6 +-- crates/adapter-riscv/src/main.rs | 15 +++---- crates/adapter-riscv/src/trap/fast.rs | 6 +-- crates/apps/smptest/src/main.rs | 2 +- crates/domain/src/resource.rs | 2 + crates/domain/src/syscall.rs | 5 +++ crates/init/src/devices.rs | 9 +++++ crates/init/src/lib.rs | 15 ++++++- crates/init/src/selftest.rs | 5 ++- crates/kernel/src/abi.rs | 7 +++- crates/kernel/src/board.rs | 17 ++++++-- crates/kernel/src/lib.rs | 9 +++-- crates/kernel/src/syscall/capability.rs | 9 ++--- crates/kernel/src/syscall/lifecycle.rs | 8 +++- crates/kernel/src/syscall/mod.rs | 12 ++++-- crates/kernel/src/syscall/simple.rs | 13 ++++++- crates/kernel/src/tests/board.rs | 52 ++++++++++++++++++++++++- crates/kernel/src/tests/capability.rs | 2 +- crates/kernel/src/tests/lifecycle.rs | 9 ++++- crates/libkos/src/io.rs | 7 +++- crates/libkos/src/lib.rs | 1 + crates/libkos/src/rt.rs | 2 + crates/libkos/src/start.rs | 36 +++++++++++++++++ crates/libkos/src/syscall/caps.rs | 9 +++-- crates/libkos/src/syscall/process.rs | 13 ++++--- crates/types/src/bootinfo.rs | 1 + crates/types/src/lib.rs | 1 + crates/types/src/start_arg.rs | 43 ++++++++++++++++++++ docs/ipc.md | 15 ++++++- 31 files changed, 290 insertions(+), 64 deletions(-) create mode 100644 crates/libkos/src/start.rs create mode 100644 crates/types/src/start_arg.rs diff --git a/crates/adapter-riscv/src/boot/caps.rs b/crates/adapter-riscv/src/boot/caps.rs index f7d5018..628b798 100644 --- a/crates/adapter-riscv/src/boot/caps.rs +++ b/crates/adapter-riscv/src/boot/caps.rs @@ -105,3 +105,18 @@ pub fn grant_all( 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 15f9ad8..70a11bf 100644 --- a/crates/adapter-riscv/src/boot/loader.rs +++ b/crates/adapter-riscv/src/boot/loader.rs @@ -11,8 +11,10 @@ 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 { @@ -35,7 +37,7 @@ fn elf_buffer() -> &'static mut [u8] { } } -pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> (LoadedProcess, ThreadId) { +pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess { let (pid, tid) = kern() .boot( types::bounded::Priority::DEFAULT, @@ -47,7 +49,7 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> (LoadedProcess, ThreadId) setup_thread(tid, entry); uart.write_str("k-os: init loaded\n"); - (LoadedProcess { pid }, tid) + LoadedProcess { pid, tid } } pub fn load_server( @@ -68,7 +70,7 @@ pub fn load_server( uart.write_str(label); uart.write_str(" loaded\n"); - LoadedProcess { pid } + LoadedProcess { pid, tid } } fn setup_thread(tid: ThreadId, entry: usize) { diff --git a/crates/adapter-riscv/src/devices/mod.rs b/crates/adapter-riscv/src/devices/mod.rs index 00d6863..75143c1 100644 --- a/crates/adapter-riscv/src/devices/mod.rs +++ b/crates/adapter-riscv/src/devices/mod.rs @@ -3,7 +3,6 @@ use crate::hw::uart::Uart; use types::address::VirtualAddress; use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT}; -use types::bounded::CapSlot; use types::id::ProcessId; use types::mapping::PagePerms; @@ -29,7 +28,7 @@ pub(crate) fn boot_info( board: &[kernel::BoardDevice], kinds: &[u64], placed: &[Option], - system_control: CapSlot, + boot_caps: kernel::BootCaps, ) { let empty = BootDevice { kind: 0, @@ -42,7 +41,8 @@ pub(crate) fn boot_info( let mut info = BootInfo { magic: MAGIC, device_count: 0, - system_control_slot: system_control.value() as u64, + system_control_slot: boot_caps.system_control.value() as u64, + console_slot: boot_caps.console.value() as u64, devices: [empty; MAX_BOOT_DEVICES], }; for (((d, kind), caps), out) in board diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index 89a932e..d754e8d 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -39,7 +39,7 @@ extern "C" fn kmain(hart: usize) -> ! { let mut blk = boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device")); - let (init, _) = boot::loader::load_init(&uart, &mut blk); + let init = boot::loader::load_init(&uart, &mut blk); let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage"); let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display"); let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input"); @@ -50,19 +50,14 @@ extern "C" fn kmain(hart: usize) -> ! { .unwrap_or_else(|_| halt(&uart, "board registration failed")); devices::display_quota(&uart, display.pid); 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]); // After grant_all: init already relies on those slots. let mut placed = [None; board::BOARD_DEVICES]; - let system_control = kern_mut() + let boot_caps = kern_mut() .grant_boot_authority(init.pid, &board, &mut placed) .unwrap_or_else(|_| halt(&uart, "boot authority failed")); - devices::boot_info( - &uart, - init.pid, - &board, - &board::kinds(), - &placed, - system_control, - ); + devices::boot_info(&uart, init.pid, &board, &board::kinds(), &placed, boot_caps); start_other_harts(&uart, hart); uart.write_str("k-os: entering userspace\n"); diff --git a/crates/adapter-riscv/src/trap/fast.rs b/crates/adapter-riscv/src/trap/fast.rs index 400ce7e..25d791b 100644 --- a/crates/adapter-riscv/src/trap/fast.rs +++ b/crates/adapter-riscv/src/trap/fast.rs @@ -3,7 +3,7 @@ use super::{TrapFrame, write_result}; /// Syscalls answered without the kernel lock because they touch nothing it /// guards. A pending IPI or reschedule request forces the slow path. pub(super) fn fast_syscall(frame: &mut TrapFrame) -> bool { - use types::syscall_abi::{SYS_DEBUG_PUTCHAR, SYS_GET_PROCESS_ID, SYS_GET_TIME, SYS_YIELD}; + use types::syscall_abi::{SYS_GET_PROCESS_ID, SYS_GET_TIME, SYS_YIELD}; if crate::smp::has_pending_request() { return false; } @@ -13,10 +13,6 @@ pub(super) fn fast_syscall(frame: &mut TrapFrame) -> bool { Some(pid) => kernel::SyscallResult::Value(pid), None => return false, }, - SYS_DEBUG_PUTCHAR => { - crate::hw::uart::Uart::new().put_char(frame.regs[10] as u8); - kernel::SyscallResult::Ok - } SYS_YIELD if crate::smp::yield_is_noop() => kernel::SyscallResult::Ok, _ => return false, }; diff --git a/crates/apps/smptest/src/main.rs b/crates/apps/smptest/src/main.rs index 92da515..ad2c8b2 100644 --- a/crates/apps/smptest/src/main.rs +++ b/crates/apps/smptest/src/main.rs @@ -28,7 +28,7 @@ extern "C" fn main() { break; }; let top = StackPointer::from_raw(stack.raw() + 4096); - syscall::spawn_thread(EntryPoint::from_raw(spin as *const () as usize), top); + syscall::spawn_thread(EntryPoint::from_raw(spin as *const () as usize), top, 0); } spin(); } diff --git a/crates/domain/src/resource.rs b/crates/domain/src/resource.rs index df3a5f2..afcdfaa 100644 --- a/crates/domain/src/resource.rs +++ b/crates/domain/src/resource.rs @@ -10,4 +10,6 @@ pub enum Resource { ProcessCreate, /// Machine-wide controls such as halting; given only to init at boot. SystemControl, + /// The debug console (UART); held by the log server. + Console, } diff --git a/crates/domain/src/syscall.rs b/crates/domain/src/syscall.rs index 7253d50..2f55070 100644 --- a/crates/domain/src/syscall.rs +++ b/crates/domain/src/syscall.rs @@ -93,9 +93,11 @@ pub enum Syscall { DropCap { slot: CapSlot, }, + /// `arg` arrives in the new thread's `a0`. SpawnThread { entry: EntryPoint, stack: StackPointer, + arg: usize, }, ExitThread, RequestPages { @@ -114,7 +116,9 @@ pub enum Syscall { control: CapSlot, }, CreateEndpoint, + /// Writes one byte to the console; needs a Console capability. DebugPutchar { + console: CapSlot, byte: u8, }, MapPage { @@ -127,5 +131,6 @@ pub enum Syscall { process: CapSlot, entry: EntryPoint, stack: StackPointer, + arg: usize, }, } diff --git a/crates/init/src/devices.rs b/crates/init/src/devices.rs index a48eed4..58534fe 100644 --- a/crates/init/src/devices.rs +++ b/crates/init/src/devices.rs @@ -62,6 +62,15 @@ pub fn system_control() -> Option { slot(info.system_control_slot) } +/// The console capability, until init hands it to the log server. +pub fn console() -> Option { + let info = boot_info(); + if info.magic != types::bootinfo::MAGIC { + return None; + } + slot(info.console_slot) +} + /// Sorts the board's devices out. pub fn discover() -> Devices { let info = boot_info(); diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 499fa45..d831eae 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -111,6 +111,7 @@ const SERVERS: &[ServerInfo] = &[ ]; 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: @@ -151,7 +152,7 @@ pub fn run() -> ! { if let Some(ep_cap) = syscall::create_endpoint() { syscall::delegate(ep_cap, loaded.process_cap, ipc_perms); - syscall::spawn_thread_in(loaded.process_cap, loaded.entry_point, USER_STACK_TOP); + start(&loaded, true); } libkos::println!("ok (entry={:#x})", loaded.entry_point.raw()); @@ -171,7 +172,7 @@ pub fn run() -> ! { let client = PermissionSet::empty().with(Permission::Execute); syscall::delegate(INPUT_EP, snake.process_cap, client); syscall::delegate(DISPLAY_EP, snake.process_cap, client); - syscall::spawn_thread_in(snake.process_cap, snake.entry_point, USER_STACK_TOP); + start(&snake, false); libkos::println!("ok"); } else { libkos::println!("SKIP"); @@ -182,6 +183,16 @@ pub fn run() -> ! { 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); +} + /// 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()?; diff --git a/crates/init/src/selftest.rs b/crates/init/src/selftest.rs index fc00b4d..1f045e2 100644 --- a/crates/init/src/selftest.rs +++ b/crates/init/src/selftest.rs @@ -45,10 +45,11 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re .with(Permission::Read) .with(Permission::Write) .with(Permission::Delegate); - if !syscall::delegate(page, spinner.process_cap, rw) { + // The page first: the spinner expects it in slot 0. + if syscall::delegate(page, spinner.process_cap, rw).is_none() { return Err("cannot give the spinner its page"); } - syscall::spawn_thread_in(spinner.process_cap, spinner.entry_point, crate::USER_STACK_TOP); + crate::start(&spinner, true); if !wait_until(3000, || counters().iter().all(|c| *c > 0)) { return Err("spinner threads did not all start"); } diff --git a/crates/kernel/src/abi.rs b/crates/kernel/src/abi.rs index 1a83b5e..9b64e8b 100644 --- a/crates/kernel/src/abi.rs +++ b/crates/kernel/src/abi.rs @@ -103,6 +103,7 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option { SYS_SPAWN_THREAD => Syscall::SpawnThread { entry: EntryPoint::from_raw(a[0]), stack: StackPointer::from_raw(a[1]), + arg: a[2], }, SYS_REQUEST_PAGES => Syscall::RequestPages { count: pages(a[0])?, @@ -115,6 +116,7 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option { process: slot(a[0])?, entry: EntryPoint::from_raw(a[1]), stack: StackPointer::from_raw(a[2]), + arg: a[3], }, SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint, SYS_CREATE_REGION => Syscall::CreateRegion { @@ -128,7 +130,10 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option { SYS_REGION_ADDRESS => Syscall::RegionAddress { region: slot(a[0])?, }, - SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a[0] as u8 }, + SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { + console: slot(a[0])?, + byte: a[1] as u8, + }, SYS_MAP_PAGE => Syscall::MapPage { process: slot(a[0])?, src_va: VirtualAddress::from_raw(a[1]), diff --git a/crates/kernel/src/board.rs b/crates/kernel/src/board.rs index 5699c2c..a487a1d 100644 --- a/crates/kernel/src/board.rs +++ b/crates/kernel/src/board.rs @@ -20,6 +20,13 @@ pub struct BoardDevice { pub irq: Option, } +/// Where `grant_boot_authority` put init's machine-wide capabilities. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BootCaps { + pub system_control: CapSlot, + pub console: CapSlot, +} + /// Where `grant_boot_authority` put one device's capabilities in init. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BoardCaps { @@ -46,13 +53,14 @@ impl Kernel

{ } /// Gives `init` (with Delegate) caps to every device region and IRQ, then SystemControl. - /// Writes each device's slots into `placed` in order; returns the SystemControl slot. + /// Writes each device's slots into `placed` in order; returns the SystemControl + /// and Console slots. pub fn grant_boot_authority( &mut self, init: ProcessId, devices: &[BoardDevice], placed: &mut [Option], - ) -> Result { + ) -> Result { let delegate = PermissionSet::empty().with(Permission::Delegate); let control = delegate.with(Permission::Execute); let mmio = delegate.with(Permission::Read).with(Permission::Write); @@ -70,7 +78,10 @@ impl Kernel

{ *slot = Some(BoardCaps { region, irq }); } } - self.grant_cap(init, Resource::SystemControl, control) + Ok(BootCaps { + system_control: self.grant_cap(init, Resource::SystemControl, control)?, + console: self.grant_cap(init, Resource::Console, delegate.with(Permission::Write))?, + }) } /// The device region that starts at `base`. diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index bf23e29..8bd6af6 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -25,7 +25,7 @@ mod smp; pub mod syscall; mod teardown; -pub use board::{BoardCaps, BoardDevice}; +pub use board::{BoardCaps, BoardDevice, BootCaps}; pub use syscall::{SyscallOutcome, SyscallResult}; #[cfg(test)] @@ -287,9 +287,10 @@ impl Kernel

{ Resource::Memory(id) => self.regions.generation(*id), Resource::Notification(id) => self.notifications.generation(*id), Resource::Process(pid) => self.processes.generation(*pid), - Resource::Interrupt(_) | Resource::ProcessCreate | Resource::SystemControl => { - Some(Generation::initial()) - } + Resource::Interrupt(_) + | Resource::ProcessCreate + | Resource::SystemControl + | Resource::Console => Some(Generation::initial()), } } diff --git a/crates/kernel/src/syscall/capability.rs b/crates/kernel/src/syscall/capability.rs index 9181512..9759d9b 100644 --- a/crates/kernel/src/syscall/capability.rs +++ b/crates/kernel/src/syscall/capability.rs @@ -43,12 +43,11 @@ impl Kernel

{ }; let parent = self.cap_ref(caller_pid, slot); - let inserted = self.insert_derived(target_pid, copy, parent); - if let Err(e) = inserted { - return SyscallOutcome::Complete(SyscallResult::Error(e)); + // The slot it landed in, so the delegator can tell the target. + match self.insert_derived(target_pid, copy, parent) { + Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)), + Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)), } - - SyscallOutcome::Complete(SyscallResult::Ok) } pub(crate) fn handle_revoke( diff --git a/crates/kernel/src/syscall/lifecycle.rs b/crates/kernel/src/syscall/lifecycle.rs index 54829ac..e74bc85 100644 --- a/crates/kernel/src/syscall/lifecycle.rs +++ b/crates/kernel/src/syscall/lifecycle.rs @@ -9,12 +9,14 @@ impl Kernel

{ pid: types::id::ProcessId, entry: types::address::EntryPoint, stack: types::address::StackPointer, + arg: usize, ) -> SyscallOutcome { use domain::thread::SavedContext; let priority = types::bounded::Priority::DEFAULT; let time_slice = types::time::TimeSlice::DEFAULT; - let context = SavedContext::with_entry_and_stack(entry, stack); + let mut context = SavedContext::with_entry_and_stack(entry, stack); + context.registers_mut()[10] = arg; let tid = match self .threads @@ -87,6 +89,7 @@ impl Kernel

{ process_cap: types::bounded::CapSlot, entry: types::address::EntryPoint, stack: types::address::StackPointer, + arg: usize, ) -> SyscallOutcome { use domain::thread::SavedContext; @@ -111,7 +114,8 @@ impl Kernel

{ let priority = types::bounded::Priority::DEFAULT; let time_slice = types::time::TimeSlice::DEFAULT; - let context = SavedContext::with_entry_and_stack(entry, stack); + let mut context = SavedContext::with_entry_and_stack(entry, stack); + context.registers_mut()[10] = arg; let tid = match self .threads diff --git a/crates/kernel/src/syscall/mod.rs b/crates/kernel/src/syscall/mod.rs index 892540b..802345b 100644 --- a/crates/kernel/src/syscall/mod.rs +++ b/crates/kernel/src/syscall/mod.rs @@ -80,7 +80,9 @@ impl Kernel

{ Syscall::ExitThread => self.handle_exit_thread(tid, pid), Syscall::RequestPages { count } => self.handle_request_pages(pid, count), Syscall::ReleasePages { addr, count } => self.handle_release_pages(pid, addr, count), - Syscall::SpawnThread { entry, stack } => self.handle_spawn_thread(pid, entry, stack), + Syscall::SpawnThread { entry, stack, arg } => { + self.handle_spawn_thread(pid, entry, stack, arg) + } Syscall::CreateProcess { create_cap, image } => { self.handle_create_process(pid, create_cap, image) } @@ -144,7 +146,10 @@ impl Kernel

{ } Syscall::Halt { control } => self.handle_halt(pid, control), Syscall::CreateEndpoint => self.handle_create_endpoint(pid), - Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte), + Syscall::DebugPutchar { console, byte } => complete( + self.debug_putchar(pid, console, byte) + .map(|()| SyscallResult::Ok), + ), Syscall::MapPage { process, src_va, @@ -155,7 +160,8 @@ impl Kernel

{ process, entry, stack, - } => self.handle_spawn_thread_in(pid, process, entry, stack), + arg, + } => self.handle_spawn_thread_in(pid, process, entry, stack, arg), } } } diff --git a/crates/kernel/src/syscall/simple.rs b/crates/kernel/src/syscall/simple.rs index 9dac421..1d21054 100644 --- a/crates/kernel/src/syscall/simple.rs +++ b/crates/kernel/src/syscall/simple.rs @@ -84,9 +84,18 @@ impl Kernel

{ SyscallOutcome::TerminateCaller } - pub(crate) fn handle_debug_putchar(&self, byte: u8) -> SyscallOutcome { + /// Console output is a capability like any other hardware access. + pub(crate) fn debug_putchar( + &self, + pid: ProcessId, + console: CapSlot, + byte: u8, + ) -> Result<(), KernelError> { + if *self.check_cap(pid, console, Permission::Write)?.resource() != Resource::Console { + return Err(KernelError::WrongObject); + } self.platform.debug_putchar(byte); - SyscallOutcome::Complete(SyscallResult::Ok) + Ok(()) } /// Halts the machine for a holder of a SystemControl capability with diff --git a/crates/kernel/src/tests/board.rs b/crates/kernel/src/tests/board.rs index 0dc90af..b67caf8 100644 --- a/crates/kernel/src/tests/board.rs +++ b/crates/kernel/src/tests/board.rs @@ -154,7 +154,8 @@ fn boot_authority_keeps_existing_slots() { for (b, a) in before.iter().zip(after.iter()) { assert!(!b || *a, "an existing slot was disturbed"); } - assert_eq!(after.iter().filter(|s| **s).count(), 1 + 2 + 2 + 1); + // ProcessCreate, two devices with an IRQ each, SystemControl, Console. + assert_eq!(after.iter().filter(|s| **s).count(), 1 + 2 + 2 + 1 + 1); } #[test] @@ -342,5 +343,52 @@ fn boot_authority_reports_where_each_cap_went() { Some(slot_holding(&k, init, Resource::Interrupt(d.irq.unwrap()))) ); } - assert_eq!(control, slot_holding(&k, init, Resource::SystemControl)); + assert_eq!( + control.system_control, + slot_holding(&k, init, Resource::SystemControl) + ); + assert_eq!(control.console, slot_holding(&k, init, Resource::Console)); +} + +// ---- console ---- + +#[test] +fn printing_needs_a_console_cap() { + let (mut k, init) = kernel_with_process(); + let none = k.handle_syscall(Syscall::DebugPutchar { + console: CapSlot::new(9).unwrap(), + byte: b'x', + }); + assert!(matches!(error_of(none), KernelError::InvalidSlot(_))); + let not_console = k.grant_cap_for_test( + init, + Resource::SystemControl, + PermissionSet::empty().with(Permission::Write), + ); + let wrong = k.handle_syscall(Syscall::DebugPutchar { + console: not_console, + byte: b'x', + }); + assert!(matches!(error_of(wrong), KernelError::WrongObject)); + let read_only = k.grant_cap_for_test( + init, + Resource::Console, + PermissionSet::empty().with(Permission::Read), + ); + let ro = k.handle_syscall(Syscall::DebugPutchar { + console: read_only, + byte: b'x', + }); + assert!(matches!(error_of(ro), KernelError::PermissionDenied { .. })); +} + +#[test] +fn the_boot_console_cap_prints() { + let (mut k, init) = booted(); + let console = slot_holding(&k, init, Resource::Console); + let out = k.handle_syscall(Syscall::DebugPutchar { + console, + byte: b'x', + }); + assert!(matches!(out, SyscallOutcome::Complete(SyscallResult::Ok))); } diff --git a/crates/kernel/src/tests/capability.rs b/crates/kernel/src/tests/capability.rs index 5975fac..1a82f25 100644 --- a/crates/kernel/src/tests/capability.rs +++ b/crates/kernel/src/tests/capability.rs @@ -50,7 +50,7 @@ fn delegate_with_valid_cap_succeeds() { }); assert!(matches!( outcome, - SyscallOutcome::Complete(SyscallResult::Ok) + SyscallOutcome::Complete(SyscallResult::Slot(_)) )); } diff --git a/crates/kernel/src/tests/lifecycle.rs b/crates/kernel/src/tests/lifecycle.rs index 4bd1676..ced7b5a 100644 --- a/crates/kernel/src/tests/lifecycle.rs +++ b/crates/kernel/src/tests/lifecycle.rs @@ -11,6 +11,7 @@ fn spawn_thread_creates_ready_thread() { let outcome = k.handle_syscall(Syscall::SpawnThread { entry: EntryPoint::from_raw(0x1000), stack: StackPointer::from_raw(0x2000), + arg: 0, }); assert!(matches!( outcome, @@ -19,11 +20,12 @@ fn spawn_thread_creates_ready_thread() { } #[test] -fn spawn_thread_preserves_entry_and_stack() { +fn spawn_thread_preserves_entry_stack_and_arg() { let (mut k, _) = kernel_with_process(); let tid = match k.handle_syscall(Syscall::SpawnThread { entry: EntryPoint::from_raw(0xDEAD), stack: StackPointer::from_raw(0xBEEF), + arg: 0x5107, }) { SyscallOutcome::Complete(SyscallResult::Value(v)) => ThreadId::from_raw(v as u16), _ => panic!("expected Value"), @@ -31,6 +33,11 @@ fn spawn_thread_preserves_entry_and_stack() { let ctx = k.thread_context(tid).unwrap(); assert_eq!(ctx.program_counter(), 0xDEAD); assert_eq!(ctx.registers()[2], 0xBEEF); + assert_eq!( + ctx.registers()[10], + 0x5107, + "the start argument arrives in a0" + ); } #[test] diff --git a/crates/libkos/src/io.rs b/crates/libkos/src/io.rs index 1107e66..4c841b3 100644 --- a/crates/libkos/src/io.rs +++ b/crates/libkos/src/io.rs @@ -4,8 +4,11 @@ struct Writer; impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { - for &b in s.as_bytes() { - crate::syscall::debug_putchar(b); + // Without a console capability, output goes nowhere. + if let Some(console) = crate::start::console() { + for &b in s.as_bytes() { + crate::syscall::debug_putchar(console, b); + } } Ok(()) } diff --git a/crates/libkos/src/lib.rs b/crates/libkos/src/lib.rs index 3aed642..f783b68 100644 --- a/crates/libkos/src/lib.rs +++ b/crates/libkos/src/lib.rs @@ -10,6 +10,7 @@ pub mod fs; pub mod input; pub mod io; mod rt; +pub mod start; pub mod syscall; #[cfg(target_os = "none")] diff --git a/crates/libkos/src/rt.rs b/crates/libkos/src/rt.rs index b57ef14..956500d 100644 --- a/crates/libkos/src/rt.rs +++ b/crates/libkos/src/rt.rs @@ -7,6 +7,8 @@ core::arch::global_asm!( .section .text.entry .globl _start _start: + lla t0, LIBKOS_START_ARG + sd a0, 0(t0) call main li a7, 16 ecall diff --git a/crates/libkos/src/start.rs b/crates/libkos/src/start.rs new file mode 100644 index 0000000..f423795 --- /dev/null +++ b/crates/libkos/src/start.rs @@ -0,0 +1,36 @@ +//! What this process was handed at start (see `types::start_arg`). + +use core::sync::atomic::{AtomicUsize, Ordering}; +use types::bounded::CapSlot; +use types::start_arg; + +/// Set by `_start` from `a0` before `main` runs. +#[unsafe(no_mangle)] +static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0); + +/// Overrides the start argument's console once set; `NONE` drops output. +static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET); +const UNSET: usize = usize::MAX; +const NONE: usize = usize::MAX - 1; + +fn arg() -> usize { + LIBKOS_START_ARG.load(Ordering::Relaxed) +} + +/// 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()?), + } +} + +/// 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); +} + +/// The log server endpoint this process was given, if any. +pub fn log() -> Option { + start_arg::log(arg()) +} diff --git a/crates/libkos/src/syscall/caps.rs b/crates/libkos/src/syscall/caps.rs index 148e7aa..1e45f78 100644 --- a/crates/libkos/src/syscall/caps.rs +++ b/crates/libkos/src/syscall/caps.rs @@ -8,8 +8,9 @@ use types::syscall_abi::*; // ---- capabilities ---- -pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool { - raw( +/// Returns the slot the copy landed in, in `to`'s table. +pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> Option { + slot_of(raw( SYS_DELEGATE, [ slot.value() as usize, @@ -19,8 +20,8 @@ pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool 0, 0, ], - ) - .is_ok() + )) + .ok() } /// Copies an unbadged endpoint or notification capability with `badge` diff --git a/crates/libkos/src/syscall/process.rs b/crates/libkos/src/syscall/process.rs index 7753418..ac6212f 100644 --- a/crates/libkos/src/syscall/process.rs +++ b/crates/libkos/src/syscall/process.rs @@ -46,18 +46,19 @@ pub fn create_process(create_cap: CapSlot) -> Option { .ok() } -pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { - raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1]) +/// `arg` arrives in the new thread's `a0` (see `libkos::start`). +pub fn spawn_thread(entry: EntryPoint, stack: StackPointer, arg: usize) -> usize { + raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), arg, 0, 0, 0]).map_or(NONE, |r| r[1]) } -pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize { +pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer, arg: usize) -> usize { raw( SYS_SPAWN_THREAD_IN, [ process_cap.value() as usize, entry.raw(), stack.raw(), - 0, + arg, 0, 0, ], @@ -69,6 +70,6 @@ pub fn kill(process_cap: CapSlot) -> bool { raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok() } -pub fn debug_putchar(byte: u8) { - let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]); +pub fn debug_putchar(console: CapSlot, byte: u8) { + let _ = raw(SYS_DEBUG_PUTCHAR, [console.value() as usize, byte as usize, 0, 0, 0, 0]); } diff --git a/crates/types/src/bootinfo.rs b/crates/types/src/bootinfo.rs index 76b4cf9..a449747 100644 --- a/crates/types/src/bootinfo.rs +++ b/crates/types/src/bootinfo.rs @@ -34,6 +34,7 @@ pub struct BootInfo { pub magic: u64, pub device_count: u64, pub system_control_slot: u64, + pub console_slot: u64, pub devices: [BootDevice; MAX_BOOT_DEVICES], } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 304ea69..b2a15b3 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -12,6 +12,7 @@ pub mod mapping; pub mod permission; #[cfg(kani)] pub mod proofs; +pub mod start_arg; pub mod storage; pub mod syscall_abi; pub mod time; diff --git a/crates/types/src/start_arg.rs b/crates/types/src/start_arg.rs new file mode 100644 index 0000000..7be31de --- /dev/null +++ b/crates/types/src/start_arg.rs @@ -0,0 +1,43 @@ +//! The word a process's first thread gets in `a0`: the slots of the +//! capabilities any process may be handed at start (console, log endpoint). +//! Each field is slot + 1 in one byte; 0 means none. + +use crate::bounded::CapSlot; + +const CONSOLE_SHIFT: u32 = 0; +const LOG_SHIFT: u32 = 8; + +fn field(arg: usize, shift: u32) -> Option { + let v = (arg >> shift) & 0xFF; + CapSlot::new(u8::try_from(v.checked_sub(1)?).ok()?) +} + +fn put(slot: Option, shift: u32) -> usize { + slot.map_or(0, |s| (s.value() as usize + 1) << shift) +} + +pub fn encode(console: Option, log: Option) -> usize { + put(console, CONSOLE_SHIFT) | put(log, LOG_SHIFT) +} + +pub fn console(arg: usize) -> Option { + field(arg, CONSOLE_SHIFT) +} + +pub fn log(arg: usize) -> Option { + field(arg, LOG_SHIFT) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn slots_round_trip_and_zero_is_none() { + let (c, l) = (CapSlot::new(0), CapSlot::new(63)); + let arg = encode(c, l); + assert_eq!((console(arg), log(arg)), (c, l)); + assert_eq!((console(0), log(0)), (None, None)); + assert_eq!(log(encode(c, None)), None); + } +} diff --git a/docs/ipc.md b/docs/ipc.md index e30030d..d3fa6d1 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -63,7 +63,17 @@ No process is privileged by its id. At boot the kernel gives init: - for every device in the board table, a capability to its MMIO region (`Read`, `Write`, `Delegate`; never `Execute`) and, if it interrupts, one to its IRQ line (`Execute`, `Delegate`); -- one `SystemControl` capability (`Execute`, `Delegate`). +- one `SystemControl` capability (`Execute`, `Delegate`); +- 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. + +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 @@ -75,7 +85,8 @@ program a device's DMA; pool regions are physically contiguous. Boot also maps one read-only page into init at `types::bootinfo::BOOT_INFO_VA` listing, for each board device, its kind, physical base, IRQ line and the -slots holding its region and IRQ capabilities, plus the SystemControl slot. +slots holding its region and IRQ capabilities, plus the SystemControl and +Console slots. It is the only page boot writes for userspace. ## Devices