Files
k-os/crates/kernel/src/lib.rs

323 lines
11 KiB
Rust

#![no_std]
// A kernel panic takes the whole system down, so kernel code may not
// contain any way to panic. Tests and proofs are exempt.
#![cfg_attr(
not(any(test, kani)),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::todo,
clippy::unimplemented,
clippy::indexing_slicing
)
)]
pub mod abi;
mod board;
mod budget;
mod captree;
pub mod interrupt;
mod memory;
#[cfg(kani)]
pub mod proofs;
mod smp;
pub mod syscall;
mod teardown;
pub use board::{BoardCaps, BoardDevice, BootCaps};
pub use syscall::{SyscallOutcome, SyscallResult};
#[cfg(test)]
#[path = "tests/mod.rs"]
mod tests;
use domain::{
budget::{Budget, Limits},
capability::Capability,
endpoint::EndpointTable,
error::KernelError,
irq::IrqRoutingTable,
memory::{RegionTable, frame::FrameTable},
notification::NotificationTable,
process::{MAX_PROCESSES, ProcessTable},
resource::Resource,
scheduler::{Scheduler, multi_core::MultiCoreScheduler},
thread::{MAX_THREADS, ThreadState, ThreadTable},
};
use platform::Platform;
use types::{
address::PhysicalAddress,
bounded::{CapSlot, Priority},
generation::Generation,
id::{ProcessId, ThreadId},
permission::{Permission, PermissionSet},
time::TimeSlice,
};
pub struct Kernel<P: Platform> {
platform: P,
processes: ProcessTable,
threads: ThreadTable,
endpoints: EndpointTable,
scheduler: MultiCoreScheduler,
frames: FrameTable,
budgets: [Budget; MAX_PROCESSES],
/// Whose budget each process was carved from (see budget.rs).
parents: [Option<ProcessId>; MAX_PROCESSES],
regions: RegionTable,
irqs: IrqRoutingTable,
notifications: NotificationTable,
ipc: [syscall::IpcState; MAX_THREADS],
pending_results: [Option<SyscallResult>; MAX_THREADS],
/// The core each thread runs on (see smp.rs).
homes: [types::id::CoreId; MAX_THREADS],
/// The capability derivation tree as links (see captree.rs).
tree: domain::capability::tree::CapTree<{ captree::ALL_CAPS }>,
/// Where each process's next heap search starts (see memory.rs).
heap_cursors: [usize; MAX_PROCESSES],
}
impl<P: Platform> Kernel<P> {
pub fn new(platform: P, pool_base: PhysicalAddress, pool_pages: usize) -> Self {
let num_cores = platform.num_cores();
Self {
platform,
processes: ProcessTable::new(),
threads: ThreadTable::new(),
endpoints: EndpointTable::new(),
scheduler: MultiCoreScheduler::new(num_cores),
frames: FrameTable::new(pool_base, pool_pages),
budgets: [Budget::EMPTY; MAX_PROCESSES],
parents: [None; MAX_PROCESSES],
regions: RegionTable::new(),
irqs: IrqRoutingTable::new(),
notifications: NotificationTable::new(),
ipc: [syscall::IpcState::default(); MAX_THREADS],
pending_results: [const { None }; MAX_THREADS],
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
tree: domain::capability::tree::CapTree::new(),
heap_cursors: [0; MAX_PROCESSES],
}
}
/// Builds the kernel in place, one table at a time, to stay off the boot stack.
///
/// # Safety
/// `slot` must be valid for writes and aligned; the old value is overwritten without drop.
pub unsafe fn init_in_place(
slot: *mut Self,
platform: P,
pool_base: PhysicalAddress,
pool_pages: usize,
) {
use core::ptr::addr_of_mut;
let num_cores = platform.num_cores();
// SAFETY: the caller guarantees `slot` is valid and aligned; each
// field is written exactly once.
unsafe {
addr_of_mut!((*slot).platform).write(platform);
addr_of_mut!((*slot).processes).write(ProcessTable::new());
addr_of_mut!((*slot).threads).write(ThreadTable::new());
addr_of_mut!((*slot).endpoints).write(EndpointTable::new());
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
addr_of_mut!((*slot).frames).write(FrameTable::new(pool_base, pool_pages));
addr_of_mut!((*slot).budgets).write([Budget::EMPTY; MAX_PROCESSES]);
addr_of_mut!((*slot).parents).write([None; MAX_PROCESSES]);
addr_of_mut!((*slot).regions).write(RegionTable::new());
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
addr_of_mut!((*slot).tree).write(domain::capability::tree::CapTree::new());
addr_of_mut!((*slot).heap_cursors).write([0; MAX_PROCESSES]);
}
}
pub fn boot(
&mut self,
priority: Priority,
time_slice: TimeSlice,
) -> Result<(ProcessId, ThreadId), KernelError> {
// Init holds every frame and every kernel object to start with.
let everything = Limits {
pages: self.frames.free_count(),
endpoints: domain::endpoint::MAX_ENDPOINTS,
notifications: domain::notification::MAX_NOTIFICATIONS,
regions: domain::memory::MAX_REGIONS,
};
let pid = self.new_process(None, everything)?;
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
self.processes.get_mut(pid)?.add_thread(tid)?;
let cap = Capability::new(
Resource::ProcessCreate,
PermissionSet::empty().with(Permission::Execute),
Generation::initial(),
);
self.insert_derived(pid, cap, None)?;
let core = self.platform.core_id();
self.set_home(tid, core);
self.scheduler.add(tid, priority, core);
self.scheduler.next(core);
self.threads.get_mut(tid)?.set_running(core)?;
Ok((pid, tid))
}
pub fn platform_mut(&mut self) -> &mut P {
&mut self.platform
}
pub fn pending_result(&self, thread: ThreadId) -> Option<&SyscallResult> {
self.pending_results.get(thread.raw() as usize)?.as_ref()
}
pub fn take_pending_result(&mut self, thread: ThreadId) -> Option<SyscallResult> {
self.pending_results.get_mut(thread.raw() as usize)?.take()
}
pub fn set_thread_running(
&mut self,
tid: ThreadId,
core: types::id::CoreId,
) -> Result<(), domain::error::KernelError> {
self.threads.get_mut(tid)?.set_running(core)
}
pub fn process_exists(&self, pid: ProcessId) -> bool {
self.processes.get(pid).is_ok()
}
pub fn thread_context(&self, tid: ThreadId) -> Option<&domain::thread::SavedContext> {
self.threads.get(tid).ok().map(|t| t.saved_context())
}
pub fn thread_context_mut(
&mut self,
tid: ThreadId,
) -> Option<&mut domain::thread::SavedContext> {
self.threads
.get_mut(tid)
.ok()
.map(|t| t.saved_context_mut())
}
pub fn thread_process(&self, tid: ThreadId) -> Option<ProcessId> {
self.threads.get(tid).ok().map(|t| t.process())
}
pub fn current_thread(&self) -> Option<ThreadId> {
let core = self.platform.core_id();
self.scheduler.current(core)
}
pub fn pick_next_thread(&mut self) -> Option<ThreadId> {
use domain::scheduler::Scheduler;
let core = self.platform.core_id();
self.scheduler.next(core)
}
/// A process for boot code to load, its `limits` carved from `parent`.
pub fn create_process_internal(
&mut self,
parent: ProcessId,
limits: Limits,
) -> Option<ProcessId> {
self.new_process(Some(parent), limits).ok()
}
pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option<ThreadId> {
let tid = self
.threads
.spawn(pid, Priority::DEFAULT, TimeSlice::DEFAULT, None)
.ok()?;
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
self.place(tid);
self.make_ready(tid, Priority::DEFAULT);
Some(tid)
}
/// Creates an endpoint owned by `owner`; it is destroyed with `owner`.
pub fn create_endpoint(
&mut self,
owner: ProcessId,
) -> Result<types::id::EndpointId, KernelError> {
self.processes.get(owner)?;
self.charge_object(owner, budget::ENDPOINTS)?;
self.endpoints
.create(owner)
.ok_or(KernelError::NoFreeSlot)
.inspect_err(|_| self.uncharge_object(owner, budget::ENDPOINTS))
}
pub fn grant_capability(
&mut self,
to: ProcessId,
resource: Resource,
permissions: PermissionSet,
) -> Result<CapSlot, KernelError> {
self.grant_cap(to, resource, permissions)
}
fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> {
let core = self.platform.core_id();
let tid = self
.scheduler
.current(core)
.ok_or(KernelError::WouldBlock)?;
let thread = self.threads.get(tid)?;
match thread.state() {
ThreadState::Running(_) => Ok((tid, thread.process())),
_ => Err(KernelError::WouldBlock),
}
}
fn check_cap(
&self,
owner: ProcessId,
slot: CapSlot,
needed: Permission,
) -> Result<&Capability, KernelError> {
let process = self.processes.get(owner)?;
let cap = process.capabilities().get(slot)?;
if !cap.permissions().allows(needed) {
return Err(KernelError::PermissionDenied { slot, needed });
}
match self.resource_generation(cap.resource()) {
Some(current) if cap.generation().is_valid_against(&current) => Ok(cap),
_ => Err(KernelError::RevokedCapability(slot)),
}
}
/// The generation a capability to `resource` must carry, or `None` if
/// the object no longer exists.
fn resource_generation(&self, resource: &Resource) -> Option<Generation> {
match resource {
Resource::Endpoint(id) => self.endpoints.generation(*id),
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
| Resource::Console => Some(Generation::initial()),
}
}
fn grant_cap(
&mut self,
to: ProcessId,
resource: Resource,
permissions: PermissionSet,
) -> Result<CapSlot, KernelError> {
let generation = self
.resource_generation(&resource)
.ok_or(KernelError::NoSuchObject)?;
let cap = Capability::new(resource, permissions, generation);
self.insert_derived(to, cap, None)
}
}