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");
}
/// 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 MAX_FILE_SIZE: usize = 64 * 1024;
#[derive(Clone, Copy)]
pub struct LoadedProcess {
pub pid: ProcessId,
pub tid: ThreadId,
}
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()
.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) {

View File

@@ -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<kernel::BoardCaps>],
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

View File

@@ -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");

View File

@@ -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,
};

View File

@@ -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();
}

View File

@@ -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,
}

View File

@@ -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,
},
}

View File

@@ -62,6 +62,15 @@ pub fn system_control() -> Option<CapSlot> {
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.
pub fn discover() -> Devices {
let info = boot_info();

View File

@@ -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()?;

View File

@@ -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");
}

View File

@@ -103,6 +103,7 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
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<Syscall> {
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<Syscall> {
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]),

View File

@@ -20,6 +20,13 @@ pub struct BoardDevice {
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.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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.
/// 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<BoardCaps>],
) -> Result<CapSlot, KernelError> {
) -> Result<BootCaps, KernelError> {
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<P: Platform> Kernel<P> {
*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`.

View File

@@ -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<P: Platform> Kernel<P> {
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()),
}
}

View File

@@ -43,12 +43,11 @@ impl<P: Platform> Kernel<P> {
};
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(

View File

@@ -9,12 +9,14 @@ impl<P: Platform> Kernel<P> {
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<P: Platform> Kernel<P> {
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<P: Platform> Kernel<P> {
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

View File

@@ -80,7 +80,9 @@ impl<P: Platform> Kernel<P> {
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<P: Platform> Kernel<P> {
}
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<P: Platform> Kernel<P> {
process,
entry,
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
}
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

View File

@@ -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)));
}

View File

@@ -50,7 +50,7 @@ fn delegate_with_valid_cap_succeeds() {
});
assert!(matches!(
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 {
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]

View File

@@ -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(())
}

View File

@@ -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")]

View File

@@ -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

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 ----
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<CapSlot> {
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`

View File

@@ -46,18 +46,19 @@ pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
.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]);
}

View File

@@ -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],
}

View File

@@ -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;

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
(`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