feat(kernel): console cap gates DebugPutchar, no uart fast path; spawn start arg in a0; delegate returns slot

This commit is contained in:
2026-10-27 14:54:36 +01:00
parent 33e1c849d1
commit 7415bab7f5
31 changed files with 290 additions and 64 deletions

View File

@@ -105,3 +105,18 @@ pub fn grant_all(
uart.write_str("k-os: capabilities granted\n"); 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);
}
}
}

View File

@@ -11,8 +11,10 @@ const USER_VA_BASE: usize = 0x10000;
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1; const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
const MAX_FILE_SIZE: usize = 64 * 1024; const MAX_FILE_SIZE: usize = 64 * 1024;
#[derive(Clone, Copy)]
pub struct LoadedProcess { pub struct LoadedProcess {
pub pid: ProcessId, pub pid: ProcessId,
pub tid: ThreadId,
} }
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> { fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
@@ -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() let (pid, tid) = kern()
.boot( .boot(
types::bounded::Priority::DEFAULT, types::bounded::Priority::DEFAULT,
@@ -47,7 +49,7 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> (LoadedProcess, ThreadId)
setup_thread(tid, entry); setup_thread(tid, entry);
uart.write_str("k-os: init loaded\n"); uart.write_str("k-os: init loaded\n");
(LoadedProcess { pid }, tid) LoadedProcess { pid, tid }
} }
pub fn load_server( pub fn load_server(
@@ -68,7 +70,7 @@ pub fn load_server(
uart.write_str(label); uart.write_str(label);
uart.write_str(" loaded\n"); uart.write_str(" loaded\n");
LoadedProcess { pid } LoadedProcess { pid, tid }
} }
fn setup_thread(tid: ThreadId, entry: usize) { fn setup_thread(tid: ThreadId, entry: usize) {

View File

@@ -3,7 +3,6 @@
use crate::hw::uart::Uart; use crate::hw::uart::Uart;
use types::address::VirtualAddress; use types::address::VirtualAddress;
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT}; use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
use types::bounded::CapSlot;
use types::id::ProcessId; use types::id::ProcessId;
use types::mapping::PagePerms; use types::mapping::PagePerms;
@@ -29,7 +28,7 @@ pub(crate) fn boot_info(
board: &[kernel::BoardDevice], board: &[kernel::BoardDevice],
kinds: &[u64], kinds: &[u64],
placed: &[Option<kernel::BoardCaps>], placed: &[Option<kernel::BoardCaps>],
system_control: CapSlot, boot_caps: kernel::BootCaps,
) { ) {
let empty = BootDevice { let empty = BootDevice {
kind: 0, kind: 0,
@@ -42,7 +41,8 @@ pub(crate) fn boot_info(
let mut info = BootInfo { let mut info = BootInfo {
magic: MAGIC, magic: MAGIC,
device_count: 0, 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], devices: [empty; MAX_BOOT_DEVICES],
}; };
for (((d, kind), caps), out) in board for (((d, kind), caps), out) in board

View File

@@ -39,7 +39,7 @@ extern "C" fn kmain(hart: usize) -> ! {
let mut blk = let mut blk =
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device")); 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 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 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"); 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")); .unwrap_or_else(|_| halt(&uart, "board registration failed"));
devices::display_quota(&uart, display.pid); devices::display_quota(&uart, display.pid);
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.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. // After grant_all: init already relies on those slots.
let mut placed = [None; board::BOARD_DEVICES]; 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) .grant_boot_authority(init.pid, &board, &mut placed)
.unwrap_or_else(|_| halt(&uart, "boot authority failed")); .unwrap_or_else(|_| halt(&uart, "boot authority failed"));
devices::boot_info( devices::boot_info(&uart, init.pid, &board, &board::kinds(), &placed, boot_caps);
&uart,
init.pid,
&board,
&board::kinds(),
&placed,
system_control,
);
start_other_harts(&uart, hart); start_other_harts(&uart, hart);
uart.write_str("k-os: entering userspace\n"); uart.write_str("k-os: entering userspace\n");

View File

@@ -3,7 +3,7 @@ use super::{TrapFrame, write_result};
/// Syscalls answered without the kernel lock because they touch nothing it /// Syscalls answered without the kernel lock because they touch nothing it
/// guards. A pending IPI or reschedule request forces the slow path. /// guards. A pending IPI or reschedule request forces the slow path.
pub(super) fn fast_syscall(frame: &mut TrapFrame) -> bool { 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() { if crate::smp::has_pending_request() {
return false; return false;
} }
@@ -13,10 +13,6 @@ pub(super) fn fast_syscall(frame: &mut TrapFrame) -> bool {
Some(pid) => kernel::SyscallResult::Value(pid), Some(pid) => kernel::SyscallResult::Value(pid),
None => return false, 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, SYS_YIELD if crate::smp::yield_is_noop() => kernel::SyscallResult::Ok,
_ => return false, _ => return false,
}; };

View File

@@ -28,7 +28,7 @@ extern "C" fn main() {
break; break;
}; };
let top = StackPointer::from_raw(stack.raw() + 4096); 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(); spin();
} }

View File

@@ -10,4 +10,6 @@ pub enum Resource {
ProcessCreate, ProcessCreate,
/// Machine-wide controls such as halting; given only to init at boot. /// Machine-wide controls such as halting; given only to init at boot.
SystemControl, SystemControl,
/// The debug console (UART); held by the log server.
Console,
} }

View File

@@ -93,9 +93,11 @@ pub enum Syscall {
DropCap { DropCap {
slot: CapSlot, slot: CapSlot,
}, },
/// `arg` arrives in the new thread's `a0`.
SpawnThread { SpawnThread {
entry: EntryPoint, entry: EntryPoint,
stack: StackPointer, stack: StackPointer,
arg: usize,
}, },
ExitThread, ExitThread,
RequestPages { RequestPages {
@@ -114,7 +116,9 @@ pub enum Syscall {
control: CapSlot, control: CapSlot,
}, },
CreateEndpoint, CreateEndpoint,
/// Writes one byte to the console; needs a Console capability.
DebugPutchar { DebugPutchar {
console: CapSlot,
byte: u8, byte: u8,
}, },
MapPage { MapPage {
@@ -127,5 +131,6 @@ pub enum Syscall {
process: CapSlot, process: CapSlot,
entry: EntryPoint, entry: EntryPoint,
stack: StackPointer, stack: StackPointer,
arg: usize,
}, },
} }

View File

@@ -62,6 +62,15 @@ pub fn system_control() -> Option<CapSlot> {
slot(info.system_control_slot) slot(info.system_control_slot)
} }
/// The console capability, until init hands it to the log server.
pub fn console() -> Option<CapSlot> {
let info = boot_info();
if info.magic != types::bootinfo::MAGIC {
return None;
}
slot(info.console_slot)
}
/// Sorts the board's devices out. /// Sorts the board's devices out.
pub fn discover() -> Devices { pub fn discover() -> Devices {
let info = boot_info(); let info = boot_info();

View File

@@ -111,6 +111,7 @@ const SERVERS: &[ServerInfo] = &[
]; ];
pub fn run() -> ! { pub fn run() -> ! {
libkos::start::set_console(devices::console());
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw()); libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
// Drivers wait for their device before serving, so this comes first: // 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() { if let Some(ep_cap) = syscall::create_endpoint() {
syscall::delegate(ep_cap, loaded.process_cap, ipc_perms); 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()); libkos::println!("ok (entry={:#x})", loaded.entry_point.raw());
@@ -171,7 +172,7 @@ pub fn run() -> ! {
let client = PermissionSet::empty().with(Permission::Execute); let client = PermissionSet::empty().with(Permission::Execute);
syscall::delegate(INPUT_EP, snake.process_cap, client); syscall::delegate(INPUT_EP, snake.process_cap, client);
syscall::delegate(DISPLAY_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"); libkos::println!("ok");
} else { } else {
libkos::println!("SKIP"); libkos::println!("SKIP");
@@ -182,6 +183,16 @@ pub fn run() -> ! {
idle(); 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. /// A region of init's own to read program files into.
fn scratch() -> Option<&'static mut [u8]> { fn scratch() -> Option<&'static mut [u8]> {
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?; let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;

View File

@@ -45,10 +45,11 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
.with(Permission::Read) .with(Permission::Read)
.with(Permission::Write) .with(Permission::Write)
.with(Permission::Delegate); .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"); 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)) { if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
return Err("spinner threads did not all start"); return Err("spinner threads did not all start");
} }

View File

@@ -103,6 +103,7 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
SYS_SPAWN_THREAD => Syscall::SpawnThread { SYS_SPAWN_THREAD => Syscall::SpawnThread {
entry: EntryPoint::from_raw(a[0]), entry: EntryPoint::from_raw(a[0]),
stack: StackPointer::from_raw(a[1]), stack: StackPointer::from_raw(a[1]),
arg: a[2],
}, },
SYS_REQUEST_PAGES => Syscall::RequestPages { SYS_REQUEST_PAGES => Syscall::RequestPages {
count: pages(a[0])?, count: pages(a[0])?,
@@ -115,6 +116,7 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
process: slot(a[0])?, process: slot(a[0])?,
entry: EntryPoint::from_raw(a[1]), entry: EntryPoint::from_raw(a[1]),
stack: StackPointer::from_raw(a[2]), stack: StackPointer::from_raw(a[2]),
arg: a[3],
}, },
SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint, SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint,
SYS_CREATE_REGION => Syscall::CreateRegion { SYS_CREATE_REGION => Syscall::CreateRegion {
@@ -128,7 +130,10 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
SYS_REGION_ADDRESS => Syscall::RegionAddress { SYS_REGION_ADDRESS => Syscall::RegionAddress {
region: slot(a[0])?, 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 { SYS_MAP_PAGE => Syscall::MapPage {
process: slot(a[0])?, process: slot(a[0])?,
src_va: VirtualAddress::from_raw(a[1]), src_va: VirtualAddress::from_raw(a[1]),

View File

@@ -20,6 +20,13 @@ pub struct BoardDevice {
pub irq: Option<IrqId>, pub irq: Option<IrqId>,
} }
/// 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. /// Where `grant_boot_authority` put one device's capabilities in init.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BoardCaps { pub struct BoardCaps {
@@ -46,13 +53,14 @@ impl<P: Platform> Kernel<P> {
} }
/// Gives `init` (with Delegate) caps to every device region and IRQ, then SystemControl. /// 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( pub fn grant_boot_authority(
&mut self, &mut self,
init: ProcessId, init: ProcessId,
devices: &[BoardDevice], devices: &[BoardDevice],
placed: &mut [Option<BoardCaps>], placed: &mut [Option<BoardCaps>],
) -> Result<CapSlot, KernelError> { ) -> Result<BootCaps, KernelError> {
let delegate = PermissionSet::empty().with(Permission::Delegate); let delegate = PermissionSet::empty().with(Permission::Delegate);
let control = delegate.with(Permission::Execute); let control = delegate.with(Permission::Execute);
let mmio = delegate.with(Permission::Read).with(Permission::Write); let mmio = delegate.with(Permission::Read).with(Permission::Write);
@@ -70,7 +78,10 @@ impl<P: Platform> Kernel<P> {
*slot = Some(BoardCaps { region, irq }); *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`. /// The device region that starts at `base`.

View File

@@ -25,7 +25,7 @@ mod smp;
pub mod syscall; pub mod syscall;
mod teardown; mod teardown;
pub use board::{BoardCaps, BoardDevice}; pub use board::{BoardCaps, BoardDevice, BootCaps};
pub use syscall::{SyscallOutcome, SyscallResult}; pub use syscall::{SyscallOutcome, SyscallResult};
#[cfg(test)] #[cfg(test)]
@@ -287,9 +287,10 @@ impl<P: Platform> Kernel<P> {
Resource::Memory(id) => self.regions.generation(*id), Resource::Memory(id) => self.regions.generation(*id),
Resource::Notification(id) => self.notifications.generation(*id), Resource::Notification(id) => self.notifications.generation(*id),
Resource::Process(pid) => self.processes.generation(*pid), Resource::Process(pid) => self.processes.generation(*pid),
Resource::Interrupt(_) | Resource::ProcessCreate | Resource::SystemControl => { Resource::Interrupt(_)
Some(Generation::initial()) | Resource::ProcessCreate
} | Resource::SystemControl
| Resource::Console => Some(Generation::initial()),
} }
} }

View File

@@ -43,12 +43,11 @@ impl<P: Platform> Kernel<P> {
}; };
let parent = self.cap_ref(caller_pid, slot); let parent = self.cap_ref(caller_pid, slot);
let inserted = self.insert_derived(target_pid, copy, parent); // The slot it landed in, so the delegator can tell the target.
if let Err(e) = inserted { match self.insert_derived(target_pid, copy, parent) {
return SyscallOutcome::Complete(SyscallResult::Error(e)); Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)),
} }
SyscallOutcome::Complete(SyscallResult::Ok)
} }
pub(crate) fn handle_revoke( pub(crate) fn handle_revoke(

View File

@@ -9,12 +9,14 @@ impl<P: Platform> Kernel<P> {
pid: types::id::ProcessId, pid: types::id::ProcessId,
entry: types::address::EntryPoint, entry: types::address::EntryPoint,
stack: types::address::StackPointer, stack: types::address::StackPointer,
arg: usize,
) -> SyscallOutcome { ) -> SyscallOutcome {
use domain::thread::SavedContext; use domain::thread::SavedContext;
let priority = types::bounded::Priority::DEFAULT; let priority = types::bounded::Priority::DEFAULT;
let time_slice = types::time::TimeSlice::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 let tid = match self
.threads .threads
@@ -87,6 +89,7 @@ impl<P: Platform> Kernel<P> {
process_cap: types::bounded::CapSlot, process_cap: types::bounded::CapSlot,
entry: types::address::EntryPoint, entry: types::address::EntryPoint,
stack: types::address::StackPointer, stack: types::address::StackPointer,
arg: usize,
) -> SyscallOutcome { ) -> SyscallOutcome {
use domain::thread::SavedContext; use domain::thread::SavedContext;
@@ -111,7 +114,8 @@ impl<P: Platform> Kernel<P> {
let priority = types::bounded::Priority::DEFAULT; let priority = types::bounded::Priority::DEFAULT;
let time_slice = types::time::TimeSlice::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 let tid = match self
.threads .threads

View File

@@ -80,7 +80,9 @@ impl<P: Platform> Kernel<P> {
Syscall::ExitThread => self.handle_exit_thread(tid, pid), Syscall::ExitThread => self.handle_exit_thread(tid, pid),
Syscall::RequestPages { count } => self.handle_request_pages(pid, count), Syscall::RequestPages { count } => self.handle_request_pages(pid, count),
Syscall::ReleasePages { addr, count } => self.handle_release_pages(pid, addr, 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 } => { Syscall::CreateProcess { create_cap, image } => {
self.handle_create_process(pid, create_cap, image) self.handle_create_process(pid, create_cap, image)
} }
@@ -144,7 +146,10 @@ impl<P: Platform> Kernel<P> {
} }
Syscall::Halt { control } => self.handle_halt(pid, control), Syscall::Halt { control } => self.handle_halt(pid, control),
Syscall::CreateEndpoint => self.handle_create_endpoint(pid), 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 { Syscall::MapPage {
process, process,
src_va, src_va,
@@ -155,7 +160,8 @@ impl<P: Platform> Kernel<P> {
process, process,
entry, entry,
stack, stack,
} => self.handle_spawn_thread_in(pid, process, entry, stack), arg,
} => self.handle_spawn_thread_in(pid, process, entry, stack, arg),
} }
} }
} }

View File

@@ -84,9 +84,18 @@ impl<P: Platform> Kernel<P> {
SyscallOutcome::TerminateCaller 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); self.platform.debug_putchar(byte);
SyscallOutcome::Complete(SyscallResult::Ok) Ok(())
} }
/// Halts the machine for a holder of a SystemControl capability with /// Halts the machine for a holder of a SystemControl capability with

View File

@@ -154,7 +154,8 @@ fn boot_authority_keeps_existing_slots() {
for (b, a) in before.iter().zip(after.iter()) { for (b, a) in before.iter().zip(after.iter()) {
assert!(!b || *a, "an existing slot was disturbed"); 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] #[test]
@@ -342,5 +343,52 @@ fn boot_authority_reports_where_each_cap_went() {
Some(slot_holding(&k, init, Resource::Interrupt(d.irq.unwrap()))) 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)));
} }

View File

@@ -50,7 +50,7 @@ fn delegate_with_valid_cap_succeeds() {
}); });
assert!(matches!( assert!(matches!(
outcome, outcome,
SyscallOutcome::Complete(SyscallResult::Ok) SyscallOutcome::Complete(SyscallResult::Slot(_))
)); ));
} }

View File

@@ -11,6 +11,7 @@ fn spawn_thread_creates_ready_thread() {
let outcome = k.handle_syscall(Syscall::SpawnThread { let outcome = k.handle_syscall(Syscall::SpawnThread {
entry: EntryPoint::from_raw(0x1000), entry: EntryPoint::from_raw(0x1000),
stack: StackPointer::from_raw(0x2000), stack: StackPointer::from_raw(0x2000),
arg: 0,
}); });
assert!(matches!( assert!(matches!(
outcome, outcome,
@@ -19,11 +20,12 @@ fn spawn_thread_creates_ready_thread() {
} }
#[test] #[test]
fn spawn_thread_preserves_entry_and_stack() { fn spawn_thread_preserves_entry_stack_and_arg() {
let (mut k, _) = kernel_with_process(); let (mut k, _) = kernel_with_process();
let tid = match k.handle_syscall(Syscall::SpawnThread { let tid = match k.handle_syscall(Syscall::SpawnThread {
entry: EntryPoint::from_raw(0xDEAD), entry: EntryPoint::from_raw(0xDEAD),
stack: StackPointer::from_raw(0xBEEF), stack: StackPointer::from_raw(0xBEEF),
arg: 0x5107,
}) { }) {
SyscallOutcome::Complete(SyscallResult::Value(v)) => ThreadId::from_raw(v as u16), SyscallOutcome::Complete(SyscallResult::Value(v)) => ThreadId::from_raw(v as u16),
_ => panic!("expected Value"), _ => panic!("expected Value"),
@@ -31,6 +33,11 @@ fn spawn_thread_preserves_entry_and_stack() {
let ctx = k.thread_context(tid).unwrap(); let ctx = k.thread_context(tid).unwrap();
assert_eq!(ctx.program_counter(), 0xDEAD); assert_eq!(ctx.program_counter(), 0xDEAD);
assert_eq!(ctx.registers()[2], 0xBEEF); assert_eq!(ctx.registers()[2], 0xBEEF);
assert_eq!(
ctx.registers()[10],
0x5107,
"the start argument arrives in a0"
);
} }
#[test] #[test]

View File

@@ -4,8 +4,11 @@ struct Writer;
impl fmt::Write for Writer { impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result { fn write_str(&mut self, s: &str) -> fmt::Result {
for &b in s.as_bytes() { // Without a console capability, output goes nowhere.
crate::syscall::debug_putchar(b); if let Some(console) = crate::start::console() {
for &b in s.as_bytes() {
crate::syscall::debug_putchar(console, b);
}
} }
Ok(()) Ok(())
} }

View File

@@ -10,6 +10,7 @@ pub mod fs;
pub mod input; pub mod input;
pub mod io; pub mod io;
mod rt; mod rt;
pub mod start;
pub mod syscall; pub mod syscall;
#[cfg(target_os = "none")] #[cfg(target_os = "none")]

View File

@@ -7,6 +7,8 @@ core::arch::global_asm!(
.section .text.entry .section .text.entry
.globl _start .globl _start
_start: _start:
lla t0, LIBKOS_START_ARG
sd a0, 0(t0)
call main call main
li a7, 16 li a7, 16
ecall ecall

View File

@@ -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<CapSlot> {
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<CapSlot>) {
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<CapSlot> {
start_arg::log(arg())
}

View File

@@ -8,8 +8,9 @@ use types::syscall_abi::*;
// ---- capabilities ---- // ---- capabilities ----
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool { /// Returns the slot the copy landed in, in `to`'s table.
raw( pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> Option<CapSlot> {
slot_of(raw(
SYS_DELEGATE, SYS_DELEGATE,
[ [
slot.value() as usize, slot.value() as usize,
@@ -19,8 +20,8 @@ pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool
0, 0,
0, 0,
], ],
) ))
.is_ok() .ok()
} }
/// Copies an unbadged endpoint or notification capability with `badge` /// Copies an unbadged endpoint or notification capability with `badge`

View File

@@ -46,18 +46,19 @@ pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
.ok() .ok()
} }
pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { /// `arg` arrives in the new thread's `a0` (see `libkos::start`).
raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1]) 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( raw(
SYS_SPAWN_THREAD_IN, SYS_SPAWN_THREAD_IN,
[ [
process_cap.value() as usize, process_cap.value() as usize,
entry.raw(), entry.raw(),
stack.raw(), stack.raw(),
0, arg,
0, 0,
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() raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok()
} }
pub fn debug_putchar(byte: u8) { pub fn debug_putchar(console: CapSlot, byte: u8) {
let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]); let _ = raw(SYS_DEBUG_PUTCHAR, [console.value() as usize, byte as usize, 0, 0, 0, 0]);
} }

View File

@@ -34,6 +34,7 @@ pub struct BootInfo {
pub magic: u64, pub magic: u64,
pub device_count: u64, pub device_count: u64,
pub system_control_slot: u64, pub system_control_slot: u64,
pub console_slot: u64,
pub devices: [BootDevice; MAX_BOOT_DEVICES], pub devices: [BootDevice; MAX_BOOT_DEVICES],
} }

View File

@@ -12,6 +12,7 @@ pub mod mapping;
pub mod permission; pub mod permission;
#[cfg(kani)] #[cfg(kani)]
pub mod proofs; pub mod proofs;
pub mod start_arg;
pub mod storage; pub mod storage;
pub mod syscall_abi; pub mod syscall_abi;
pub mod time; pub mod time;

View File

@@ -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<CapSlot> {
let v = (arg >> shift) & 0xFF;
CapSlot::new(u8::try_from(v.checked_sub(1)?).ok()?)
}
fn put(slot: Option<CapSlot>, shift: u32) -> usize {
slot.map_or(0, |s| (s.value() as usize + 1) << shift)
}
pub fn encode(console: Option<CapSlot>, log: Option<CapSlot>) -> usize {
put(console, CONSOLE_SHIFT) | put(log, LOG_SHIFT)
}
pub fn console(arg: usize) -> Option<CapSlot> {
field(arg, CONSOLE_SHIFT)
}
pub fn log(arg: usize) -> Option<CapSlot> {
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);
}
}

View File

@@ -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 - for every device in the board table, a capability to its MMIO region
(`Read`, `Write`, `Delegate`; never `Execute`) and, if it interrupts, one (`Read`, `Write`, `Delegate`; never `Execute`) and, if it interrupts, one
to its IRQ line (`Execute`, `Delegate`); 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 The board capabilities come after the capabilities boot code hands out
for the built-in servers, so those slots are unchanged. Device regions 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` 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 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. It is the only page boot writes for userspace.
## Devices ## Devices