#![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 { 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; MAX_PROCESSES], regions: RegionTable, irqs: IrqRoutingTable, notifications: NotificationTable, ipc: [syscall::IpcState; MAX_THREADS], pending_results: [Option; 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], /// Who to signal when each process dies (see syscall/ipc/events.rs). death_watchers: [[Option; domain::timer::MAX_WATCHERS]; MAX_PROCESSES], timers: domain::timer::Timers, } impl Kernel

{ 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], death_watchers: [[None; domain::timer::MAX_WATCHERS]; MAX_PROCESSES], timers: domain::timer::Timers::new(), } } /// 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]); addr_of_mut!((*slot).death_watchers) .write([[None; domain::timer::MAX_WATCHERS]; MAX_PROCESSES]); addr_of_mut!((*slot).timers).write(domain::timer::Timers::new()); } } 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 { 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 { self.threads.get(tid).ok().map(|t| t.process()) } pub fn current_thread(&self) -> Option { let core = self.platform.core_id(); self.scheduler.current(core) } pub fn pick_next_thread(&mut self) -> Option { 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 { self.new_process(Some(parent), limits).ok() } pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option { 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 { 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 { 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(¤t) => 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 { 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 { let generation = self .resource_generation(&resource) .ok_or(KernelError::NoSuchObject)?; let cap = Capability::new(resource, permissions, generation); self.insert_derived(to, cap, None) } }