331 lines
12 KiB
Rust
331 lines
12 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],
|
|
/// Who to signal when each process dies (see syscall/ipc/events.rs).
|
|
death_watchers: [[Option<domain::timer::Target>; domain::timer::MAX_WATCHERS]; MAX_PROCESSES],
|
|
timers: domain::timer::Timers,
|
|
}
|
|
|
|
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],
|
|
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<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(¤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<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)
|
|
}
|
|
}
|