340 lines
11 KiB
Rust
340 lines
11 KiB
Rust
#![no_std]
|
|
|
|
pub mod interrupt;
|
|
#[cfg(kani)]
|
|
pub mod proofs;
|
|
pub mod syscall;
|
|
|
|
pub use syscall::{SyscallOutcome, SyscallResult};
|
|
|
|
#[cfg(test)]
|
|
#[path = "tests/mod.rs"]
|
|
mod tests;
|
|
|
|
use domain::{
|
|
capability::{Capability, CapabilityTable},
|
|
endpoint::EndpointTable,
|
|
error::KernelError,
|
|
irq::IrqRoutingTable,
|
|
memory::{
|
|
page_pool::PagePool,
|
|
process_memory::ProcessMemory,
|
|
RegionTable,
|
|
},
|
|
message::Message,
|
|
process::{ProcessTable, MAX_PROCESSES},
|
|
resource::Resource,
|
|
scheduler::{multi_core::MultiCoreScheduler, Scheduler},
|
|
syscall::WaitSet,
|
|
thread::{ThreadTable, ThreadState, MAX_THREADS},
|
|
};
|
|
use platform::Platform;
|
|
use types::{
|
|
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,
|
|
page_pool: PagePool,
|
|
process_memory: [Option<ProcessMemory>; MAX_PROCESSES],
|
|
regions: RegionTable,
|
|
irqs: IrqRoutingTable,
|
|
pending_messages: [Option<Message>; MAX_THREADS],
|
|
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
|
wait_sets: [Option<WaitSet>; MAX_THREADS],
|
|
}
|
|
|
|
impl<P: Platform> Kernel<P> {
|
|
pub fn new(platform: P, page_pool: PagePool) -> Self {
|
|
let num_cores = platform.num_cores();
|
|
Self {
|
|
platform,
|
|
processes: ProcessTable::new(),
|
|
threads: ThreadTable::new(),
|
|
endpoints: EndpointTable::new(),
|
|
scheduler: MultiCoreScheduler::new(num_cores),
|
|
page_pool,
|
|
process_memory: [const { None }; MAX_PROCESSES],
|
|
regions: RegionTable::new(),
|
|
irqs: IrqRoutingTable::new(),
|
|
pending_messages: [const { None }; MAX_THREADS],
|
|
pending_results: [const { None }; MAX_THREADS],
|
|
wait_sets: [const { None }; MAX_THREADS],
|
|
}
|
|
}
|
|
|
|
pub fn boot(
|
|
&mut self,
|
|
priority: Priority,
|
|
time_slice: TimeSlice,
|
|
) -> Result<(ProcessId, ThreadId), KernelError> {
|
|
let pid = self.processes.create(CapabilityTable::new(), None)?;
|
|
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
|
|
self.processes.get_mut(pid)?.add_thread(tid)?;
|
|
|
|
self.process_memory[pid.raw() as usize] =
|
|
Some(ProcessMemory::new(pid, ProcessMemory::DEFAULT_PAGE_LIMIT));
|
|
|
|
let cap = Capability::new(
|
|
Resource::ProcessCreate,
|
|
PermissionSet::empty().with(Permission::Execute),
|
|
Generation::initial(),
|
|
);
|
|
self.processes.get_mut(pid)?.capabilities_mut().insert(cap)?;
|
|
|
|
let core = self.platform.core_id();
|
|
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[thread.raw() as usize].as_ref()
|
|
}
|
|
|
|
pub fn take_pending_result(&mut self, thread: ThreadId) -> Option<SyscallResult> {
|
|
self.pending_results[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 free_page_count(&self) -> usize {
|
|
self.page_pool.free_count()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
|
|
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
|
|
self.process_memory[pid.raw() as usize] =
|
|
Some(domain::memory::process_memory::ProcessMemory::new(pid, ProcessMemory::DEFAULT_PAGE_LIMIT));
|
|
Some(pid)
|
|
}
|
|
|
|
pub fn spawn_thread_internal(
|
|
&mut self,
|
|
pid: ProcessId,
|
|
) -> Option<ThreadId> {
|
|
use domain::scheduler::Scheduler;
|
|
let tid = self.threads.spawn(
|
|
pid,
|
|
Priority::DEFAULT,
|
|
TimeSlice::DEFAULT,
|
|
None,
|
|
).ok()?;
|
|
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
|
|
let core = self.platform.core_id();
|
|
self.scheduler.add(tid, Priority::DEFAULT, core);
|
|
Some(tid)
|
|
}
|
|
|
|
pub fn create_endpoint(&mut self) -> Result<types::id::EndpointId, KernelError> {
|
|
self.endpoints.create().ok_or(KernelError::NoFreeSlot)
|
|
}
|
|
|
|
pub fn grant_capability(
|
|
&mut self,
|
|
to: ProcessId,
|
|
resource: Resource,
|
|
permissions: PermissionSet,
|
|
) -> Result<CapSlot, KernelError> {
|
|
self.grant_cap(to, resource, permissions)
|
|
}
|
|
|
|
pub fn switch_to_thread(&mut self, tid: ThreadId) {
|
|
use domain::scheduler::Scheduler;
|
|
let core = self.platform.core_id();
|
|
|
|
if let Some(current) = self.scheduler.current(core) {
|
|
if current == tid {
|
|
return;
|
|
}
|
|
if let Ok(t) = self.threads.get_mut(current) {
|
|
if matches!(t.state(), ThreadState::Running(_)) {
|
|
let priority = t.priority();
|
|
let _ = t.preempt();
|
|
self.scheduler.add(current, priority, core);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Ok(t) = self.threads.get(tid) {
|
|
if matches!(t.state(), ThreadState::Ready) {
|
|
self.scheduler.next(core);
|
|
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
|
}
|
|
}
|
|
}
|
|
|
|
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),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn destroy_process(&mut self, pid: ProcessId) {
|
|
use domain::process::MAX_THREADS_PER_PROCESS;
|
|
|
|
let thread_ids: [Option<ThreadId>; MAX_THREADS_PER_PROCESS] = match self.processes.get(pid) {
|
|
Ok(p) => {
|
|
let mut ids = [None; MAX_THREADS_PER_PROCESS];
|
|
for (i, tid) in p.thread_ids().enumerate() {
|
|
if i < MAX_THREADS_PER_PROCESS { ids[i] = Some(tid); }
|
|
}
|
|
ids
|
|
}
|
|
Err(_) => return,
|
|
};
|
|
|
|
self.cleanup_threads(&thread_ids);
|
|
|
|
// Free allocated pages back to pool
|
|
if let Some(pm) = &self.process_memory[pid.raw() as usize] {
|
|
for &(pa, count) in pm.allocations() {
|
|
self.page_pool.free(pa, count);
|
|
}
|
|
}
|
|
|
|
// Unblock threads in other processes that were blocked waiting
|
|
// on endpoints where this process's threads were the other side.
|
|
// The cleanup_threads call above already removed this process's
|
|
// threads from all endpoint wait queues. Threads blocked waiting
|
|
// for this process will eventually time out or be rescheduled
|
|
// when the endpoint is revoked.
|
|
|
|
let _ = self.processes.destroy(pid);
|
|
self.process_memory[pid.raw() as usize] = None;
|
|
}
|
|
|
|
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
|
use domain::scheduler::Scheduler;
|
|
for slot in thread_ids {
|
|
if let Some(tid) = slot {
|
|
self.endpoints.remove_thread_from_all(*tid);
|
|
self.cleanup_wait_any(*tid);
|
|
self.scheduler.remove(*tid);
|
|
self.pending_messages[tid.raw() as usize] = None;
|
|
self.pending_results[tid.raw() as usize] = None;
|
|
let _ = self.threads.destroy(*tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
|
if let Some(wait_set) = self.wait_sets[tid.raw() as usize].take() {
|
|
for ep_slot in wait_set.endpoints() {
|
|
if let Some(_slot) = ep_slot {
|
|
self.endpoints.remove_thread_from_all(tid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 });
|
|
}
|
|
let current_gen = self.resource_generation(cap.resource());
|
|
if !cap.generation().is_valid_against(¤t_gen) {
|
|
return Err(KernelError::RevokedCapability(slot));
|
|
}
|
|
Ok(cap)
|
|
}
|
|
|
|
fn resource_generation(&self, resource: &Resource) -> Generation {
|
|
match resource {
|
|
Resource::Endpoint(id) => self
|
|
.endpoints
|
|
.get(*id)
|
|
.map(|ep| *ep.generation())
|
|
.unwrap_or_else(|| Generation::initial()),
|
|
Resource::Memory(id) => self
|
|
.regions
|
|
.get(*id)
|
|
.map(|r| *r.generation())
|
|
.unwrap_or_else(|| Generation::initial()),
|
|
_ => Generation::initial(),
|
|
}
|
|
}
|
|
|
|
pub fn revoke_resource(&mut self, resource: &Resource) {
|
|
match resource {
|
|
Resource::Endpoint(id) => {
|
|
if let Some(ep) = self.endpoints.get_mut(*id) {
|
|
ep.revoke();
|
|
}
|
|
}
|
|
Resource::Memory(id) => {
|
|
if let Some(r) = self.regions.get_mut(*id) {
|
|
r.revoke();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn grant_cap(
|
|
&mut self,
|
|
to: ProcessId,
|
|
resource: Resource,
|
|
permissions: PermissionSet,
|
|
) -> Result<CapSlot, KernelError> {
|
|
let generation = self.resource_generation(&resource);
|
|
let cap = Capability::new(resource, permissions, generation);
|
|
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
|
}
|
|
}
|