feat(kernel): seL4-style IPC, notifications, status ABI
- badges + Mint, Call/Reply/ReplyRecv w/ gen-checked reply slot - cap transfer (1/msg, copy, needs Delegate, rechecked at delivery) - notifications: signal/wait/poll/bind, irq -> notification routing - WaitAny/WaitForInterrupt removed; a0=status, ErrorCode - libkos: single ecall primitive, compat wrappers + typed new api - fix: trap return clobbered user t0 on every trap - fix: wait queues FIFO, delegate keeps badge, fault cap checked - docs/ipc.md spec, ipc tests, ipc fuzz, kani ntfn + waitqueue
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use domain::scheduler::{Scheduler, SchedulerDecision};
|
||||
use platform::Platform;
|
||||
use types::id::{CoreId, IrqId, ThreadId};
|
||||
use types::id::CoreId;
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
@@ -53,22 +53,4 @@ impl<P: Platform> Kernel<P> {
|
||||
self.scheduler.tick(core, current_tid)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_interrupt(&mut self, irq: IrqId) -> Option<ThreadId> {
|
||||
self.platform.mask_irq(irq);
|
||||
|
||||
let tid = self.irqs.owner(irq)?;
|
||||
let thread = self.threads.get_mut(tid).ok()?;
|
||||
|
||||
if !matches!(thread.state(), domain::thread::ThreadState::Blocked(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let priority = thread.priority();
|
||||
let core = self.platform.core_id();
|
||||
let _ = thread.unblock();
|
||||
self.scheduler.add(tid, priority, core);
|
||||
|
||||
Some(tid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ use domain::{
|
||||
error::KernelError,
|
||||
irq::IrqRoutingTable,
|
||||
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
||||
message::Message,
|
||||
notification::{MAX_NOTIFICATIONS, NotificationTable},
|
||||
process::{MAX_PROCESSES, ProcessTable},
|
||||
resource::Resource,
|
||||
scheduler::{Scheduler, multi_core::MultiCoreScheduler},
|
||||
syscall::WaitSet,
|
||||
thread::{MAX_THREADS, ThreadState, ThreadTable},
|
||||
};
|
||||
use platform::Platform;
|
||||
@@ -45,9 +44,9 @@ pub struct Kernel<P: Platform> {
|
||||
quotas: [Quota; MAX_PROCESSES],
|
||||
regions: RegionTable,
|
||||
irqs: IrqRoutingTable,
|
||||
pending_messages: [Option<Message>; MAX_THREADS],
|
||||
notifications: NotificationTable,
|
||||
ipc: [syscall::IpcState; MAX_THREADS],
|
||||
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
||||
wait_sets: [Option<WaitSet>; MAX_THREADS],
|
||||
}
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
@@ -63,9 +62,9 @@ impl<P: Platform> Kernel<P> {
|
||||
quotas: [Quota::default(); MAX_PROCESSES],
|
||||
regions: RegionTable::new(),
|
||||
irqs: IrqRoutingTable::new(),
|
||||
pending_messages: [const { None }; MAX_THREADS],
|
||||
notifications: NotificationTable::new(),
|
||||
ipc: [syscall::IpcState::default(); MAX_THREADS],
|
||||
pending_results: [const { None }; MAX_THREADS],
|
||||
wait_sets: [const { None }; MAX_THREADS],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +94,9 @@ impl<P: Platform> Kernel<P> {
|
||||
addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]);
|
||||
addr_of_mut!((*slot).regions).write(RegionTable::new());
|
||||
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
|
||||
addr_of_mut!((*slot).pending_messages).write([const { None }; MAX_THREADS]);
|
||||
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).wait_sets).write([const { None }; MAX_THREADS]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +279,7 @@ impl<P: Platform> Kernel<P> {
|
||||
|
||||
self.cleanup_threads(&thread_ids);
|
||||
self.destroy_endpoints_owned_by(pid);
|
||||
self.destroy_notifications_owned_by(pid);
|
||||
self.teardown_memory(pid);
|
||||
|
||||
let _ = self.processes.destroy(pid);
|
||||
@@ -289,10 +289,12 @@ impl<P: Platform> Kernel<P> {
|
||||
use domain::scheduler::Scheduler;
|
||||
for tid in thread_ids.iter().flatten() {
|
||||
self.endpoints.remove_thread_from_all(*tid);
|
||||
self.cleanup_wait_any(*tid);
|
||||
self.irqs.unregister_thread(*tid);
|
||||
self.notifications.remove_thread_from_all(*tid);
|
||||
let ipc = core::mem::take(&mut self.ipc[tid.raw() as usize]);
|
||||
if let Some(caller) = ipc.reply_to {
|
||||
self.release_caller(caller);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -315,28 +317,31 @@ impl<P: Platform> Kernel<P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a blocked thread runnable with `error` as its syscall result.
|
||||
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
|
||||
self.cleanup_wait_any(tid);
|
||||
self.pending_messages[tid.raw() as usize] = None;
|
||||
let Ok(thread) = self.threads.get_mut(tid) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(thread.state(), ThreadState::Blocked(_)) {
|
||||
return;
|
||||
/// Destroys every notification `owner` created, waking its waiters
|
||||
/// with an error and unbinding its bound thread.
|
||||
pub(crate) fn destroy_notifications_owned_by(&mut self, owner: ProcessId) {
|
||||
let mut owned = [None; MAX_NOTIFICATIONS];
|
||||
for (slot, id) in owned.iter_mut().zip(self.notifications.ids_owned_by(owner)) {
|
||||
*slot = Some(id);
|
||||
}
|
||||
let priority = thread.priority();
|
||||
if thread.unblock().is_ok() {
|
||||
self.pending_results[tid.raw() as usize] = Some(SyscallResult::Error(error));
|
||||
let core = self.platform.core_id();
|
||||
self.scheduler.add(tid, priority, core);
|
||||
for id in owned.into_iter().flatten() {
|
||||
let Some(mut n) = self.notifications.destroy(id) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(bound) = n.bound() {
|
||||
self.ipc[bound.raw() as usize].bound = None;
|
||||
}
|
||||
while let Some(tid) = n.pop_waiter() {
|
||||
self.wake_with_error(tid, KernelError::NoSuchObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
||||
if self.wait_sets[tid.raw() as usize].take().is_some() {
|
||||
self.endpoints.remove_thread_from_all(tid);
|
||||
}
|
||||
/// Makes a blocked thread runnable with `error` as its syscall result,
|
||||
/// dropping whatever it was about to send.
|
||||
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
|
||||
self.ipc[tid.raw() as usize].outgoing = None;
|
||||
self.wake(tid, SyscallResult::Error(error));
|
||||
}
|
||||
|
||||
fn check_cap(
|
||||
@@ -362,6 +367,7 @@ impl<P: Platform> Kernel<P> {
|
||||
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 => Some(Generation::initial()),
|
||||
}
|
||||
@@ -371,6 +377,7 @@ impl<P: Platform> Kernel<P> {
|
||||
match resource {
|
||||
Resource::Endpoint(id) => self.endpoints.revoke(*id),
|
||||
Resource::Memory(id) => self.regions.revoke(*id),
|
||||
Resource::Notification(id) => self.notifications.revoke(*id),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,17 @@ impl<P: Platform> Kernel<P> {
|
||||
to_endpoint: types::bounded::CapSlot,
|
||||
restrict: types::permission::PermissionSet,
|
||||
) -> SyscallOutcome {
|
||||
let (resource, permissions) =
|
||||
match self.check_cap(caller_pid, slot, types::permission::Permission::Delegate) {
|
||||
Ok(cap) => (cap.resource().clone(), *cap.permissions()),
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let restricted = permissions.restrict_to(&restrict);
|
||||
// A copy of the capability itself, so its badge and generation
|
||||
// travel with it; only the permissions narrow.
|
||||
let copy = match self.check_cap(caller_pid, slot, types::permission::Permission::Delegate) {
|
||||
Ok(cap) => domain::capability::Capability::new(
|
||||
cap.resource().clone(),
|
||||
cap.permissions().restrict_to(&restrict),
|
||||
*cap.generation(),
|
||||
)
|
||||
.with_badge(cap.badge()),
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let target_pid = match self.check_cap(
|
||||
caller_pid,
|
||||
@@ -38,7 +42,11 @@ impl<P: Platform> Kernel<P> {
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
if let Err(e) = self.grant_cap(target_pid, resource, restricted) {
|
||||
let inserted = self
|
||||
.processes
|
||||
.get_mut(target_pid)
|
||||
.and_then(|p| p.capabilities_mut().insert(copy));
|
||||
if let Err(e) = inserted {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,42 @@
|
||||
use domain::{fault::Fault, message::Message};
|
||||
use platform::Platform;
|
||||
use types::{id::ThreadId, permission::Permission};
|
||||
|
||||
use super::{Delivery, DeliveryKind, SyscallResult};
|
||||
use crate::Kernel;
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
pub fn handle_fault(
|
||||
&mut self,
|
||||
tid: types::id::ThreadId,
|
||||
fault: domain::fault::Fault,
|
||||
fault_pc: usize,
|
||||
) {
|
||||
let pid = match self.threads.get(tid) {
|
||||
Ok(t) => t.process(),
|
||||
Err(_) => return,
|
||||
/// A thread faulted. Report it to the process's fault endpoint (if one
|
||||
/// is set, valid and has a receiver waiting), then destroy the process.
|
||||
pub fn handle_fault(&mut self, tid: ThreadId, fault: Fault, fault_pc: usize) {
|
||||
let Some(pid) = self.thread_process(tid) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Try to deliver fault to a fault handler endpoint (if configured)
|
||||
if let Ok(process) = self.processes.get(pid)
|
||||
&& let Some(handler_slot) = process.fault_handler()
|
||||
&& let Ok(cap) = process.capabilities().get(handler_slot)
|
||||
&& let domain::resource::Resource::Endpoint(ep_id) = cap.resource()
|
||||
let handler = self
|
||||
.processes
|
||||
.get(pid)
|
||||
.ok()
|
||||
.and_then(|p| p.fault_handler())
|
||||
.and_then(|slot| self.check_cap(pid, slot, Permission::Execute).ok())
|
||||
.and_then(|cap| match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(ep) => Some((*ep, cap.badge())),
|
||||
_ => None,
|
||||
});
|
||||
if let Some((ep, badge)) = handler
|
||||
&& let Some(receiver) = self
|
||||
.endpoints
|
||||
.get_mut(ep)
|
||||
.and_then(|e| e.dequeue_receiver())
|
||||
{
|
||||
let msg = domain::message::Message::from_words([
|
||||
pid.raw() as usize,
|
||||
fault.code(),
|
||||
fault.address(),
|
||||
fault_pc,
|
||||
]);
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(*ep_id)
|
||||
&& ep.has_waiting_receiver()
|
||||
&& let Some(receiver_tid) = ep.dequeue_receiver()
|
||||
{
|
||||
self.pending_results[receiver_tid.raw() as usize] =
|
||||
Some(crate::SyscallResult::Message(msg));
|
||||
|
||||
if let Ok(receiver) = self.threads.get_mut(receiver_tid) {
|
||||
let priority = receiver.priority();
|
||||
let _ = receiver.unblock();
|
||||
let core = self.platform.core_id();
|
||||
use domain::scheduler::Scheduler;
|
||||
self.scheduler.add(receiver_tid, priority, core);
|
||||
}
|
||||
}
|
||||
let report =
|
||||
Message::from_words([pid.raw() as usize, fault.code(), fault.address(), fault_pc]);
|
||||
let delivery = Delivery {
|
||||
kind: DeliveryKind::Message,
|
||||
badge: badge.raw(),
|
||||
words: *report.words(),
|
||||
cap: None,
|
||||
};
|
||||
self.wake(receiver, SyscallResult::Received(delivery));
|
||||
}
|
||||
|
||||
self.destroy_process(pid);
|
||||
|
||||
@@ -1,346 +1,553 @@
|
||||
use platform::Platform;
|
||||
//! Endpoint IPC, notifications and interrupt delivery. docs/ipc.md is the
|
||||
//! specification; the comments below only point at the non-obvious rules.
|
||||
|
||||
use super::{SyscallOutcome, SyscallResult};
|
||||
use domain::{
|
||||
capability::Capability,
|
||||
error::KernelError,
|
||||
message::Message,
|
||||
notification::SignalEffect,
|
||||
resource::Resource,
|
||||
scheduler::Scheduler,
|
||||
thread::{BlockReason, ThreadState},
|
||||
};
|
||||
use platform::Platform;
|
||||
use types::{
|
||||
badge::Badge,
|
||||
bounded::CapSlot,
|
||||
generation::Generation,
|
||||
id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId},
|
||||
permission::{Permission, PermissionSet},
|
||||
};
|
||||
|
||||
use super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
||||
use crate::Kernel;
|
||||
|
||||
/// A thread named by id and generation, so a stale reference can be told
|
||||
/// apart from a new thread that reused the id.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ThreadRef {
|
||||
pub tid: ThreadId,
|
||||
pub generation: Generation,
|
||||
}
|
||||
|
||||
/// A message a blocked sender is waiting to hand over.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Outgoing {
|
||||
message: Message,
|
||||
badge: Badge,
|
||||
/// The sender's endpoint cap had `Delegate`, so a capability may travel.
|
||||
grant: bool,
|
||||
}
|
||||
|
||||
/// Per-thread IPC bookkeeping.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct IpcState {
|
||||
pub(crate) outgoing: Option<Outgoing>,
|
||||
/// The caller this thread owes a reply to.
|
||||
pub(crate) reply_to: Option<ThreadRef>,
|
||||
pub(crate) bound: Option<NotificationId>,
|
||||
}
|
||||
|
||||
fn rw_delegate() -> PermissionSet {
|
||||
PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Write)
|
||||
.with(Permission::Delegate)
|
||||
}
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
pub(crate) fn handle_create_endpoint(
|
||||
fn thread_ref(&self, tid: ThreadId) -> Option<ThreadRef> {
|
||||
Some(ThreadRef {
|
||||
tid,
|
||||
generation: self.threads.generation(tid)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_live(&self, r: ThreadRef) -> bool {
|
||||
self.threads.generation(r.tid) == Some(r.generation)
|
||||
}
|
||||
|
||||
fn is_blocked_as(&self, tid: ThreadId, f: impl Fn(&BlockReason) -> bool) -> bool {
|
||||
matches!(self.threads.get(tid).map(|t| *t.state()), Ok(ThreadState::Blocked(r)) if f(&r))
|
||||
}
|
||||
|
||||
/// Blocks the running caller.
|
||||
pub(crate) fn block_caller(&mut self, tid: ThreadId, reason: BlockReason) -> SyscallOutcome {
|
||||
if let Ok(t) = self.threads.get_mut(tid) {
|
||||
let _ = t.block(reason);
|
||||
}
|
||||
self.scheduler.remove(tid);
|
||||
SyscallOutcome::BlockCaller(reason)
|
||||
}
|
||||
|
||||
/// Makes a blocked thread runnable with `result` as its syscall result.
|
||||
pub(crate) fn wake(&mut self, tid: ThreadId, result: SyscallResult) {
|
||||
let Ok(thread) = self.threads.get_mut(tid) else {
|
||||
return;
|
||||
};
|
||||
let priority = thread.priority();
|
||||
if thread.unblock().is_ok() {
|
||||
self.pending_results[tid.raw() as usize] = Some(result);
|
||||
let core = self.platform.core_id();
|
||||
self.scheduler.add(tid, priority, core);
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a caller waiting on `reply` with `NoReply`, if it is still
|
||||
/// that caller and still waiting.
|
||||
pub(crate) fn release_caller(&mut self, reply: ThreadRef) {
|
||||
if self.is_live(reply)
|
||||
&& self.is_blocked_as(reply.tid, |r| *r == BlockReason::AwaitingReply)
|
||||
{
|
||||
self.wake(reply.tid, SyscallResult::Error(KernelError::NoReply));
|
||||
}
|
||||
}
|
||||
|
||||
fn endpoint_cap(
|
||||
&self,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
needed: Permission,
|
||||
) -> Result<(EndpointId, Badge, bool), KernelError> {
|
||||
let cap = self.check_cap(pid, slot, needed)?;
|
||||
match cap.resource() {
|
||||
Resource::Endpoint(id) => Ok((
|
||||
*id,
|
||||
cap.badge(),
|
||||
cap.permissions().allows(Permission::Delegate),
|
||||
)),
|
||||
_ => Err(KernelError::WrongObject),
|
||||
}
|
||||
}
|
||||
|
||||
fn notification_cap(
|
||||
&self,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
needed: Permission,
|
||||
) -> Result<(NotificationId, Badge, Generation), KernelError> {
|
||||
let cap = self.check_cap(pid, slot, needed)?;
|
||||
match cap.resource() {
|
||||
Resource::Notification(id) => Ok((*id, cap.badge(), *cap.generation())),
|
||||
_ => Err(KernelError::WrongObject),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the capability in `slot` of `from` into `to`'s table, if the
|
||||
/// transfer is allowed and still valid. Returns where it landed.
|
||||
fn transfer_cap(
|
||||
&mut self,
|
||||
caller_pid: types::id::ProcessId,
|
||||
from: ProcessId,
|
||||
slot: Option<CapSlot>,
|
||||
grant: bool,
|
||||
to: ProcessId,
|
||||
) -> Option<CapSlot> {
|
||||
let slot = slot?;
|
||||
if !grant {
|
||||
return None;
|
||||
}
|
||||
let copy: Capability = self
|
||||
.check_cap(from, slot, Permission::Delegate)
|
||||
.ok()?
|
||||
.clone();
|
||||
self.processes
|
||||
.get_mut(to)
|
||||
.ok()?
|
||||
.capabilities_mut()
|
||||
.insert(copy)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
|
||||
/// reply slot now names the sender.
|
||||
fn deliver(
|
||||
&mut self,
|
||||
sender: ThreadId,
|
||||
out: Outgoing,
|
||||
receiver: ThreadId,
|
||||
kind: DeliveryKind,
|
||||
) -> Delivery {
|
||||
let from = self.thread_process(sender);
|
||||
let to = self.thread_process(receiver);
|
||||
let cap = match (from, to) {
|
||||
(Some(from), Some(to)) => self.transfer_cap(from, out.message.cap(), out.grant, to),
|
||||
_ => None,
|
||||
};
|
||||
if kind == DeliveryKind::Call {
|
||||
self.ipc[receiver.raw() as usize].reply_to = self.thread_ref(sender);
|
||||
}
|
||||
Delivery {
|
||||
kind,
|
||||
badge: out.badge.raw(),
|
||||
words: *out.message.words(),
|
||||
cap,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- endpoints ----
|
||||
|
||||
pub(crate) fn send(
|
||||
&mut self,
|
||||
tid: ThreadId,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
message: Message,
|
||||
is_call: bool,
|
||||
) -> SyscallOutcome {
|
||||
let ep_id = match self.create_endpoint(caller_pid) {
|
||||
let (ep, badge, grant) = match self.endpoint_cap(pid, slot, Permission::Execute) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let out = Outgoing {
|
||||
message,
|
||||
badge,
|
||||
grant,
|
||||
};
|
||||
let kind = if is_call {
|
||||
DeliveryKind::Call
|
||||
} else {
|
||||
DeliveryKind::Message
|
||||
};
|
||||
|
||||
let receiver = self
|
||||
.endpoints
|
||||
.get_mut(ep)
|
||||
.and_then(|e| e.dequeue_receiver());
|
||||
if let Some(receiver) = receiver {
|
||||
let delivery = self.deliver(tid, out, receiver, kind);
|
||||
self.wake(receiver, SyscallResult::Received(delivery));
|
||||
if !is_call {
|
||||
return SyscallOutcome::Complete(SyscallResult::Ok);
|
||||
}
|
||||
self.block_caller(tid, BlockReason::AwaitingReply);
|
||||
// Run the server straight away instead of waiting for its turn.
|
||||
let core = self.platform.core_id();
|
||||
if self.scheduler.run_now(core, receiver)
|
||||
&& self
|
||||
.threads
|
||||
.get_mut(receiver)
|
||||
.and_then(|t| t.set_running(core))
|
||||
.is_ok()
|
||||
{
|
||||
return SyscallOutcome::SwitchTo(receiver);
|
||||
}
|
||||
return SyscallOutcome::BlockCaller(BlockReason::AwaitingReply);
|
||||
}
|
||||
|
||||
let queued = self
|
||||
.endpoints
|
||||
.get_mut(ep)
|
||||
.is_some_and(|e| e.enqueue_sender(tid).is_some());
|
||||
if !queued {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||
}
|
||||
self.ipc[tid.raw() as usize].outgoing = Some(out);
|
||||
let reason = if is_call {
|
||||
BlockReason::Calling(ep)
|
||||
} else {
|
||||
BlockReason::Sending(ep)
|
||||
};
|
||||
self.block_caller(tid, reason)
|
||||
}
|
||||
|
||||
/// `drop_reply`: a plain `Receive` abandons any pending reply.
|
||||
pub(crate) fn receive(
|
||||
&mut self,
|
||||
tid: ThreadId,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
drop_reply: bool,
|
||||
) -> SyscallOutcome {
|
||||
let (ep, _, _) = match self.endpoint_cap(pid, slot, Permission::Read) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
if drop_reply && let Some(pending) = self.ipc[tid.raw() as usize].reply_to.take() {
|
||||
self.release_caller(pending);
|
||||
}
|
||||
|
||||
if let Some(n) = self.ipc[tid.raw() as usize].bound
|
||||
&& let Some(ntfn) = self.notifications.get_mut(n)
|
||||
&& ntfn.has_bits()
|
||||
{
|
||||
let bits = ntfn.take_bits();
|
||||
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||
}
|
||||
|
||||
let sender = self.endpoints.get_mut(ep).and_then(|e| e.dequeue_sender());
|
||||
if let Some(sender) = sender {
|
||||
let is_call = self.is_blocked_as(sender, |r| matches!(r, BlockReason::Calling(_)));
|
||||
let Some(out) = self.ipc[sender.raw() as usize].outgoing.take() else {
|
||||
// A queued sender always has a message; treat a missing one
|
||||
// as a sender that vanished.
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::WouldBlock));
|
||||
};
|
||||
let kind = if is_call {
|
||||
DeliveryKind::Call
|
||||
} else {
|
||||
DeliveryKind::Message
|
||||
};
|
||||
let delivery = self.deliver(sender, out, tid, kind);
|
||||
if is_call {
|
||||
if let Ok(t) = self.threads.get_mut(sender) {
|
||||
let _ = t.rewait(BlockReason::AwaitingReply);
|
||||
}
|
||||
} else {
|
||||
self.wake(sender, SyscallResult::Ok);
|
||||
}
|
||||
return SyscallOutcome::Complete(SyscallResult::Received(delivery));
|
||||
}
|
||||
|
||||
let queued = self
|
||||
.endpoints
|
||||
.get_mut(ep)
|
||||
.is_some_and(|e| e.enqueue_receiver(tid).is_some());
|
||||
if !queued {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||
}
|
||||
self.block_caller(tid, BlockReason::Receiving(ep))
|
||||
}
|
||||
|
||||
pub(crate) fn reply(
|
||||
&mut self,
|
||||
tid: ThreadId,
|
||||
pid: ProcessId,
|
||||
message: Message,
|
||||
) -> Result<(), KernelError> {
|
||||
let caller = self.ipc[tid.raw() as usize]
|
||||
.reply_to
|
||||
.take()
|
||||
.ok_or(KernelError::NoReply)?;
|
||||
let waiting = self.is_live(caller)
|
||||
&& self.is_blocked_as(caller.tid, |r| *r == BlockReason::AwaitingReply);
|
||||
if !waiting {
|
||||
// The caller died; the reply has nowhere to go.
|
||||
return Ok(());
|
||||
}
|
||||
let to = self.thread_process(caller.tid);
|
||||
// A reply may always carry a capability the server can delegate.
|
||||
let cap = to.and_then(|to| self.transfer_cap(pid, message.cap(), true, to));
|
||||
let delivery = Delivery {
|
||||
kind: DeliveryKind::Message,
|
||||
badge: 0,
|
||||
words: *message.words(),
|
||||
cap,
|
||||
};
|
||||
self.wake(caller.tid, SyscallResult::Received(delivery));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn reply_recv(
|
||||
&mut self,
|
||||
tid: ThreadId,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
message: Message,
|
||||
) -> SyscallOutcome {
|
||||
if let Err(e) = self.endpoint_cap(pid, slot, Permission::Read) {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||
}
|
||||
if self.ipc[tid.raw() as usize].reply_to.is_some() {
|
||||
let _ = self.reply(tid, pid, message);
|
||||
}
|
||||
self.receive(tid, pid, slot, false)
|
||||
}
|
||||
|
||||
pub(crate) fn mint(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
badge: Badge,
|
||||
permissions: PermissionSet,
|
||||
) -> Result<CapSlot, KernelError> {
|
||||
let source = self.check_cap(pid, slot, Permission::Delegate)?;
|
||||
if !matches!(
|
||||
source.resource(),
|
||||
Resource::Endpoint(_) | Resource::Notification(_)
|
||||
) {
|
||||
return Err(KernelError::WrongObject);
|
||||
}
|
||||
if !source.badge().is_none() {
|
||||
return Err(KernelError::InvalidArgument);
|
||||
}
|
||||
let minted = Capability::new(
|
||||
source.resource().clone(),
|
||||
source.permissions().restrict_to(&permissions),
|
||||
*source.generation(),
|
||||
)
|
||||
.with_badge(badge);
|
||||
self.processes
|
||||
.get_mut(pid)?
|
||||
.capabilities_mut()
|
||||
.insert(minted)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_create_endpoint(&mut self, pid: ProcessId) -> SyscallOutcome {
|
||||
let id = match self.create_endpoint(pid) {
|
||||
Ok(id) => id,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let all_perms = types::permission::PermissionSet::empty()
|
||||
.with(types::permission::Permission::Read)
|
||||
.with(types::permission::Permission::Write)
|
||||
.with(types::permission::Permission::Execute)
|
||||
.with(types::permission::Permission::Delegate);
|
||||
|
||||
match self.grant_cap(
|
||||
caller_pid,
|
||||
domain::resource::Resource::Endpoint(ep_id),
|
||||
all_perms,
|
||||
) {
|
||||
let perms = PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Execute)
|
||||
.with(Permission::Delegate);
|
||||
match self.grant_cap(pid, Resource::Endpoint(id), perms) {
|
||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||
Err(e) => {
|
||||
let _ = self.endpoints.destroy(ep_id);
|
||||
self.endpoints.destroy(id);
|
||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_send(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
endpoint_slot: types::bounded::CapSlot,
|
||||
message: domain::message::Message,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
// ---- notifications ----
|
||||
|
||||
let ep_id = match self.check_cap(
|
||||
caller_pid,
|
||||
endpoint_slot,
|
||||
types::permission::Permission::Execute,
|
||||
) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(
|
||||
types::id::EndpointId::from_raw(0),
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||
self.processes.get(owner)?;
|
||||
self.notifications
|
||||
.create(owner)
|
||||
.ok_or(KernelError::NoFreeSlot)
|
||||
}
|
||||
|
||||
pub(crate) fn create_notification_cap(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
) -> Result<CapSlot, KernelError> {
|
||||
let id = self.create_notification(pid)?;
|
||||
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||
.inspect_err(|_| {
|
||||
self.notifications.destroy(id);
|
||||
})
|
||||
}
|
||||
|
||||
/// Signals `id` with `badge`; shared by `Signal` and interrupts.
|
||||
fn signal_notification(&mut self, id: NotificationId, badge: Badge) {
|
||||
let bound_receiving = self
|
||||
.notifications
|
||||
.get(id)
|
||||
.and_then(|n| n.bound())
|
||||
.is_some_and(|t| self.is_blocked_as(t, |r| matches!(r, BlockReason::Receiving(_))));
|
||||
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||
return;
|
||||
};
|
||||
match ntfn.signal(badge, bound_receiving) {
|
||||
SignalEffect::WakeWaiter(t, bits) => {
|
||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||
}
|
||||
SignalEffect::WakeBound(t, bits) => {
|
||||
self.endpoints.remove_thread_from_all(t);
|
||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||
}
|
||||
SignalEffect::Stored => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn signal(&mut self, pid: ProcessId, slot: CapSlot) -> Result<(), KernelError> {
|
||||
let (id, badge, _) = self.notification_cap(pid, slot, Permission::Write)?;
|
||||
self.signal_notification(id, badge);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn wait(&mut self, tid: ThreadId, pid: ProcessId, slot: CapSlot) -> SyscallOutcome {
|
||||
let id = match self.notification_cap(pid, slot, Permission::Read) {
|
||||
Ok((id, _, _)) => id,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||
Some(ep) => ep,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||
));
|
||||
}
|
||||
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::NoSuchObject));
|
||||
};
|
||||
if ntfn.has_bits() {
|
||||
let bits = ntfn.take_bits();
|
||||
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||
}
|
||||
if !ntfn.add_waiter(tid) {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||
}
|
||||
self.block_caller(tid, BlockReason::WaitingNotification(id))
|
||||
}
|
||||
|
||||
if endpoint.has_waiting_receiver() {
|
||||
let receiver_tid = match endpoint.dequeue_receiver() {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
}
|
||||
};
|
||||
pub(crate) fn poll(&mut self, pid: ProcessId, slot: CapSlot) -> Result<usize, KernelError> {
|
||||
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||
self.notifications
|
||||
.get_mut(id)
|
||||
.map(|n| n.take_bits())
|
||||
.ok_or(KernelError::NoSuchObject)
|
||||
}
|
||||
|
||||
self.pending_results[receiver_tid.raw() as usize] =
|
||||
Some(SyscallResult::Message(message));
|
||||
pub(crate) fn bind_notification(
|
||||
&mut self,
|
||||
tid: ThreadId,
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
) -> Result<(), KernelError> {
|
||||
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||
if self.ipc[tid.raw() as usize].bound.is_some() {
|
||||
return Err(KernelError::InvalidArgument);
|
||||
}
|
||||
let ntfn = self
|
||||
.notifications
|
||||
.get_mut(id)
|
||||
.ok_or(KernelError::NoSuchObject)?;
|
||||
if !ntfn.bind(tid) {
|
||||
return Err(KernelError::InvalidArgument);
|
||||
}
|
||||
self.ipc[tid.raw() as usize].bound = Some(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let core = self.platform.core_id();
|
||||
// ---- interrupts ----
|
||||
|
||||
let receiver = match self.threads.get_mut(receiver_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let _ = receiver.unblock();
|
||||
let _ = receiver.set_running(core);
|
||||
fn interrupt_cap(&self, pid: ProcessId, slot: CapSlot) -> Result<IrqId, KernelError> {
|
||||
match self.check_cap(pid, slot, Permission::Execute)?.resource() {
|
||||
Resource::Interrupt(irq) => Ok(*irq),
|
||||
_ => Err(KernelError::WrongObject),
|
||||
}
|
||||
}
|
||||
|
||||
let caller = match self.threads.get_mut(caller_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let caller_priority = caller.priority();
|
||||
let _ = caller.preempt();
|
||||
self.scheduler.add(caller_tid, caller_priority, core);
|
||||
|
||||
self.scheduler.set_current(core, receiver_tid);
|
||||
|
||||
SyscallOutcome::SwitchTo(receiver_tid)
|
||||
pub(crate) fn set_irq_notification(
|
||||
&mut self,
|
||||
pid: ProcessId,
|
||||
irq_slot: CapSlot,
|
||||
ntfn_slot: CapSlot,
|
||||
) -> Result<(), KernelError> {
|
||||
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||
let (notification, badge, generation) =
|
||||
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||
let route = domain::irq::IrqRoute {
|
||||
notification,
|
||||
generation,
|
||||
badge,
|
||||
};
|
||||
if self.irqs.set(irq, route) {
|
||||
Ok(())
|
||||
} else {
|
||||
self.pending_messages[caller_tid.raw() as usize] = Some(message);
|
||||
|
||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||
Some(ep) => ep,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||
));
|
||||
}
|
||||
};
|
||||
if endpoint.enqueue_sender(caller_tid).is_none() {
|
||||
self.pending_messages[caller_tid.raw() as usize] = None;
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
}
|
||||
|
||||
let caller = match self.threads.get_mut(caller_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let _ = caller.block(domain::thread::BlockReason::Sending(endpoint_slot));
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Sending(endpoint_slot))
|
||||
Err(KernelError::InvalidIrq(irq))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_receive(
|
||||
pub(crate) fn ack_interrupt(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
endpoint_slot: types::bounded::CapSlot,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
|
||||
let ep_id = match self.check_cap(
|
||||
caller_pid,
|
||||
endpoint_slot,
|
||||
types::permission::Permission::Read,
|
||||
) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(
|
||||
types::id::EndpointId::from_raw(0),
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||
Some(ep) => ep,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if endpoint.has_waiting_sender() {
|
||||
let sender_tid = match endpoint.dequeue_sender() {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let message = self.pending_messages[sender_tid.raw() as usize].take();
|
||||
debug_assert!(
|
||||
message.is_some(),
|
||||
"sender in wait queue had no pending message"
|
||||
);
|
||||
let message = message.unwrap_or_else(domain::message::Message::empty);
|
||||
|
||||
self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok);
|
||||
|
||||
let sender = match self.threads.get_mut(sender_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let priority = sender.priority();
|
||||
let _ = sender.unblock();
|
||||
let core = self.platform.core_id();
|
||||
self.scheduler.add(sender_tid, priority, core);
|
||||
|
||||
SyscallOutcome::Complete(SyscallResult::Message(message))
|
||||
} else {
|
||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
||||
Some(ep) => ep,
|
||||
None => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
||||
));
|
||||
}
|
||||
};
|
||||
if endpoint.enqueue_receiver(caller_tid).is_none() {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
}
|
||||
|
||||
let caller = match self.threads.get_mut(caller_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let _ = caller.block(domain::thread::BlockReason::Receiving(endpoint_slot));
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Receiving(endpoint_slot))
|
||||
}
|
||||
pid: ProcessId,
|
||||
slot: CapSlot,
|
||||
) -> Result<(), KernelError> {
|
||||
let irq = self.interrupt_cap(pid, slot)?;
|
||||
self.platform.unmask_irq(irq);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn handle_wait_for_interrupt(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
irq_slot: types::bounded::CapSlot,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
|
||||
let irq_id = match self.check_cap(caller_pid, irq_slot, types::permission::Permission::Read)
|
||||
{
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Interrupt(id) => *id,
|
||||
_ => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
/// An interrupt fired: mask the line and signal its notification. The
|
||||
/// line stays masked until the handler acknowledges it.
|
||||
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||
self.platform.mask_irq(irq);
|
||||
let Some(route) = self.irqs.route(irq) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = self.irqs.register(irq_id, caller_tid);
|
||||
|
||||
let caller = match self.threads.get_mut(caller_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let _ = caller.block(domain::thread::BlockReason::WaitingForInterrupt(irq_slot));
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingForInterrupt(irq_slot))
|
||||
}
|
||||
|
||||
pub(crate) fn handle_ack_interrupt(
|
||||
&mut self,
|
||||
caller_pid: types::id::ProcessId,
|
||||
irq_slot: types::bounded::CapSlot,
|
||||
) -> SyscallOutcome {
|
||||
let irq_id =
|
||||
match self.check_cap(caller_pid, irq_slot, types::permission::Permission::Write) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Interrupt(id) => *id,
|
||||
_ => {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
self.platform.unmask_irq(irq_id);
|
||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_wait_any(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
sources: domain::syscall::WaitSet,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
|
||||
if sources.is_empty() {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
// A route set through a capability that has since been revoked, or
|
||||
// to a notification whose slot was reused, is dead.
|
||||
if self.notifications.generation(route.notification) != Some(route.generation) {
|
||||
self.irqs.clear(irq);
|
||||
return;
|
||||
}
|
||||
|
||||
for slot in sources.endpoints().iter().flatten() {
|
||||
let ep_id = match self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
{
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => continue,
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(ep_id)
|
||||
&& ep.has_waiting_sender()
|
||||
{
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::MessageReady(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for irq_slot in sources.interrupts() {
|
||||
if let Some(slot) = irq_slot
|
||||
&& let Ok(cap) =
|
||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
&& let domain::resource::Resource::Interrupt(irq_id) = cap.resource()
|
||||
&& self.irqs.owner(*irq_id).is_some()
|
||||
{
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for ep_slot in sources.endpoints() {
|
||||
if let Some(slot) = ep_slot
|
||||
&& let Ok(cap) =
|
||||
self.check_cap(caller_pid, *slot, types::permission::Permission::Read)
|
||||
&& let domain::resource::Resource::Endpoint(id) = cap.resource()
|
||||
&& let Some(ep) = self.endpoints.get_mut(*id)
|
||||
{
|
||||
let _ = ep.enqueue_receiver(caller_tid);
|
||||
}
|
||||
}
|
||||
|
||||
self.wait_sets[caller_tid.raw() as usize] = Some(sources);
|
||||
|
||||
let caller = match self.threads.get_mut(caller_tid) {
|
||||
Ok(t) => t,
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
let _ = caller.block(domain::thread::BlockReason::WaitingAny);
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingAny)
|
||||
self.signal_notification(route.notification, route.badge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,28 +5,95 @@ mod lifecycle;
|
||||
mod memory;
|
||||
mod simple;
|
||||
|
||||
use domain::{error::KernelError, message::Message, syscall::Syscall};
|
||||
use domain::{error::KernelError, syscall::Syscall};
|
||||
use platform::Platform;
|
||||
use types::{bounded::CapSlot, id::ThreadId};
|
||||
use types::{bounded::CapSlot, id::ThreadId, syscall_abi::ErrorCode};
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
pub use ipc::{IpcState, ThreadRef};
|
||||
|
||||
pub enum SyscallOutcome {
|
||||
Complete(SyscallResult),
|
||||
BlockCaller(domain::thread::BlockReason),
|
||||
/// The caller blocked and `ThreadId` should run next.
|
||||
SwitchTo(ThreadId),
|
||||
TerminateCaller,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DeliveryKind {
|
||||
/// A `Send`; no reply expected.
|
||||
Message,
|
||||
/// A `Call`; the receiver's reply slot now names the caller.
|
||||
Call,
|
||||
/// Notification bits.
|
||||
Signal,
|
||||
}
|
||||
|
||||
/// What a receiving syscall hands back (docs/ipc.md, register ABI).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Delivery {
|
||||
pub kind: DeliveryKind,
|
||||
/// The sender's badge, or the signal bits.
|
||||
pub badge: usize,
|
||||
pub words: [usize; 4],
|
||||
/// Where a transferred capability landed in the receiver's table.
|
||||
pub cap: Option<CapSlot>,
|
||||
}
|
||||
|
||||
impl Delivery {
|
||||
pub fn signal(bits: usize) -> Self {
|
||||
Self {
|
||||
kind: DeliveryKind::Signal,
|
||||
badge: bits,
|
||||
words: [0; 4],
|
||||
cap: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SyscallResult {
|
||||
Ok,
|
||||
Value(usize),
|
||||
Slot(CapSlot),
|
||||
Message(Message),
|
||||
WakeReason(domain::syscall::WakeReason),
|
||||
Received(Delivery),
|
||||
Error(KernelError),
|
||||
}
|
||||
|
||||
/// The status code userspace sees for a kernel error.
|
||||
pub fn error_code(error: &KernelError) -> ErrorCode {
|
||||
use KernelError::*;
|
||||
match error {
|
||||
InvalidSlot(_) | RevokedCapability(_) | NoSuchObject | InvalidEndpoint(_) => {
|
||||
ErrorCode::InvalidCap
|
||||
}
|
||||
PermissionDenied { .. } | ProcessCreateDenied => ErrorCode::PermissionDenied,
|
||||
WrongObject => ErrorCode::WrongObject,
|
||||
NoFreeSlot => ErrorCode::NoFreeSlot,
|
||||
OutOfMemory => ErrorCode::OutOfMemory,
|
||||
InvalidAddress => ErrorCode::InvalidAddress,
|
||||
AddressInUse => ErrorCode::AddressInUse,
|
||||
NoReply => ErrorCode::NoReply,
|
||||
TooManyThreadsInProcess | TooManyProcesses | TooManyThreads | EndpointFull => {
|
||||
ErrorCode::LimitReached
|
||||
}
|
||||
InvalidProcess(_)
|
||||
| InvalidThread(_)
|
||||
| InvalidStateTransition(_)
|
||||
| InvalidIrq(_)
|
||||
| InvalidRegion
|
||||
| IrqAlreadyOwned(_)
|
||||
| WouldBlock
|
||||
| InvalidArgument => ErrorCode::InvalidArgument,
|
||||
}
|
||||
}
|
||||
|
||||
fn complete(result: Result<SyscallResult, KernelError>) -> SyscallOutcome {
|
||||
SyscallOutcome::Complete(result.unwrap_or_else(SyscallResult::Error))
|
||||
}
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome {
|
||||
let (tid, pid) = match self.caller() {
|
||||
@@ -54,11 +121,45 @@ impl<P: Platform> Kernel<P> {
|
||||
to_endpoint,
|
||||
permissions,
|
||||
} => self.handle_delegate(pid, slot, to_endpoint, permissions),
|
||||
Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message),
|
||||
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint),
|
||||
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
||||
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
||||
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources),
|
||||
Syscall::Mint {
|
||||
slot,
|
||||
badge,
|
||||
permissions,
|
||||
} => complete(
|
||||
self.mint(pid, slot, badge, permissions)
|
||||
.map(SyscallResult::Slot),
|
||||
),
|
||||
Syscall::Send { endpoint, message } => self.send(tid, pid, endpoint, message, false),
|
||||
Syscall::Call { endpoint, message } => self.send(tid, pid, endpoint, message, true),
|
||||
Syscall::Receive { endpoint } => self.receive(tid, pid, endpoint, true),
|
||||
Syscall::Reply { message } => {
|
||||
complete(self.reply(tid, pid, message).map(|()| SyscallResult::Ok))
|
||||
}
|
||||
Syscall::ReplyRecv { endpoint, message } => {
|
||||
self.reply_recv(tid, pid, endpoint, message)
|
||||
}
|
||||
Syscall::CreateNotification => {
|
||||
complete(self.create_notification_cap(pid).map(SyscallResult::Slot))
|
||||
}
|
||||
Syscall::Signal { notification } => {
|
||||
complete(self.signal(pid, notification).map(|()| SyscallResult::Ok))
|
||||
}
|
||||
Syscall::Wait { notification } => self.wait(tid, pid, notification),
|
||||
Syscall::Poll { notification } => complete(
|
||||
self.poll(pid, notification)
|
||||
.map(|bits| SyscallResult::Received(Delivery::signal(bits))),
|
||||
),
|
||||
Syscall::BindNotification { notification } => complete(
|
||||
self.bind_notification(tid, pid, notification)
|
||||
.map(|()| SyscallResult::Ok),
|
||||
),
|
||||
Syscall::SetIrqNotification { irq, notification } => complete(
|
||||
self.set_irq_notification(pid, irq, notification)
|
||||
.map(|()| SyscallResult::Ok),
|
||||
),
|
||||
Syscall::AcknowledgeInterrupt { irq } => {
|
||||
complete(self.ack_interrupt(pid, irq).map(|()| SyscallResult::Ok))
|
||||
}
|
||||
Syscall::Halt => self.handle_halt(pid),
|
||||
Syscall::CreateEndpoint => self.handle_create_endpoint(pid),
|
||||
Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte),
|
||||
|
||||
@@ -5,7 +5,7 @@ use platform::Platform;
|
||||
use types::{
|
||||
address::PhysicalAddress,
|
||||
bounded::{CapSlot, Priority},
|
||||
id::{EndpointId, IrqId, ProcessId, ThreadId},
|
||||
id::{EndpointId, ProcessId, ThreadId},
|
||||
permission::PermissionSet,
|
||||
time::TimeSlice,
|
||||
};
|
||||
@@ -41,10 +41,6 @@ impl Kernel<MockPlatform> {
|
||||
tid
|
||||
}
|
||||
|
||||
pub fn register_irq_for_test(&mut self, irq: IrqId, thread: ThreadId) {
|
||||
let _ = self.irqs.register(irq, thread);
|
||||
}
|
||||
|
||||
pub fn block_thread_for_test(&mut self, tid: ThreadId, reason: domain::thread::BlockReason) {
|
||||
let core = self.platform.core_id();
|
||||
if let Ok(t) = self.threads.get_mut(tid) {
|
||||
@@ -128,6 +124,10 @@ impl Kernel<MockPlatform> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_process_notifications_for_test(&mut self, owner: ProcessId) {
|
||||
self.destroy_notifications_owned_by(owner);
|
||||
}
|
||||
|
||||
pub fn thread_state_for_test(&self, tid: ThreadId) -> Option<ThreadState> {
|
||||
self.threads.get(tid).ok().map(|t| *t.state())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
350
crates/kernel/src/tests/ipc_invariants.rs
Normal file
350
crates/kernel/src/tests/ipc_invariants.rs
Normal file
@@ -0,0 +1,350 @@
|
||||
//! Randomized check of the IPC state machine. Six threads in three
|
||||
//! processes send, call, receive, reply and signal at random over two
|
||||
//! endpoints and a notification; one process is periodically killed and
|
||||
//! re-created. After every step the kernel's queues, block reasons, reply
|
||||
//! slots and run queue must agree, and no message may be duplicated.
|
||||
|
||||
use super::*;
|
||||
use crate::syscall::{Delivery, DeliveryKind};
|
||||
use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::message::Message;
|
||||
use domain::resource::Resource;
|
||||
use domain::syscall::Syscall;
|
||||
use domain::thread::{BlockReason, ThreadState};
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::{EndpointId, NotificationId, ThreadId};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
const STEPS: usize = 4000;
|
||||
const THREADS_PER_PROC: usize = 2;
|
||||
const PROCS: usize = 3;
|
||||
|
||||
struct Rng(u64);
|
||||
|
||||
impl Rng {
|
||||
fn next(&mut self) -> u64 {
|
||||
self.0 ^= self.0 << 13;
|
||||
self.0 ^= self.0 >> 7;
|
||||
self.0 ^= self.0 << 17;
|
||||
self.0
|
||||
}
|
||||
|
||||
fn below(&mut self, n: usize) -> usize {
|
||||
(self.next() % n as u64) as usize
|
||||
}
|
||||
}
|
||||
|
||||
fn all_perms() -> PermissionSet {
|
||||
PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Write)
|
||||
.with(Permission::Execute)
|
||||
.with(Permission::Delegate)
|
||||
}
|
||||
|
||||
struct Proc {
|
||||
pid: ProcessId,
|
||||
threads: [ThreadId; THREADS_PER_PROC],
|
||||
eps: [CapSlot; 2],
|
||||
ntfn: CapSlot,
|
||||
}
|
||||
|
||||
struct World {
|
||||
k: Kernel<MockPlatform>,
|
||||
procs: [Proc; PROCS],
|
||||
eps: [EndpointId; 2],
|
||||
ntfn: NotificationId,
|
||||
next_id: usize,
|
||||
delivered: [bool; 2 * STEPS + 16],
|
||||
sent: [bool; 2 * STEPS + 16],
|
||||
calls_answered: usize,
|
||||
signals_seen: usize,
|
||||
ran: usize,
|
||||
rebirths: usize,
|
||||
}
|
||||
|
||||
impl World {
|
||||
fn new() -> Self {
|
||||
let (mut k, p0) = kernel_with_process();
|
||||
let t0 = k.current_thread().unwrap();
|
||||
let p1 = k.create_process_internal().unwrap();
|
||||
let p2 = k.create_process_internal().unwrap();
|
||||
// Endpoints and the notification belong to init, which never dies.
|
||||
let eps = [
|
||||
k.create_endpoint(p0).unwrap(),
|
||||
k.create_endpoint(p0).unwrap(),
|
||||
];
|
||||
let ntfn = k.create_notification(p0).unwrap();
|
||||
let mut procs = [p0, p1, p2].map(|pid| Proc {
|
||||
pid,
|
||||
threads: [t0; THREADS_PER_PROC],
|
||||
eps: [CapSlot::new(0).unwrap(); 2],
|
||||
ntfn: CapSlot::new(0).unwrap(),
|
||||
});
|
||||
for (i, p) in procs.iter_mut().enumerate() {
|
||||
Self::equip(&mut k, p, eps, ntfn, i == 0, t0);
|
||||
}
|
||||
Self {
|
||||
k,
|
||||
procs,
|
||||
eps,
|
||||
ntfn,
|
||||
next_id: 1,
|
||||
delivered: [false; 2 * STEPS + 16],
|
||||
sent: [false; 2 * STEPS + 16],
|
||||
calls_answered: 0,
|
||||
signals_seen: 0,
|
||||
ran: 0,
|
||||
rebirths: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn equip(
|
||||
k: &mut Kernel<MockPlatform>,
|
||||
p: &mut Proc,
|
||||
eps: [EndpointId; 2],
|
||||
ntfn: NotificationId,
|
||||
is_init: bool,
|
||||
init_thread: ThreadId,
|
||||
) {
|
||||
for j in 0..THREADS_PER_PROC {
|
||||
p.threads[j] = if is_init && j == 0 {
|
||||
init_thread
|
||||
} else {
|
||||
k.spawn_thread_for_test(p.pid, Priority::DEFAULT, TimeSlice::DEFAULT)
|
||||
};
|
||||
}
|
||||
for (e, ep) in eps.iter().enumerate() {
|
||||
p.eps[e] = k.grant_cap_for_test(p.pid, Resource::Endpoint(*ep), all_perms());
|
||||
}
|
||||
p.ntfn = k.grant_cap_for_test(p.pid, Resource::Notification(ntfn), all_perms());
|
||||
// The second thread of every process gets the notification bound.
|
||||
k.switch_to_thread_for_test(p.threads[1]);
|
||||
let _ = k.handle_syscall(Syscall::BindNotification {
|
||||
notification: p.ntfn,
|
||||
});
|
||||
}
|
||||
|
||||
fn fresh_id(&mut self) -> usize {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.sent[id] = true;
|
||||
id
|
||||
}
|
||||
|
||||
fn record(&mut self, d: &Delivery) {
|
||||
match d.kind {
|
||||
DeliveryKind::Signal => self.signals_seen += 1,
|
||||
_ => {
|
||||
let id = d.words[0];
|
||||
assert!(
|
||||
id < self.sent.len() && self.sent[id],
|
||||
"delivered id {id} was never sent"
|
||||
);
|
||||
assert!(!self.delivered[id], "message {id} delivered twice");
|
||||
self.delivered[id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_outcome(&mut self, outcome: &SyscallOutcome) {
|
||||
if let SyscallOutcome::Complete(SyscallResult::Received(d)) = outcome {
|
||||
let d = *d;
|
||||
self.record(&d);
|
||||
}
|
||||
}
|
||||
|
||||
fn runnable(&self) -> impl Iterator<Item = (usize, ThreadId)> + '_ {
|
||||
self.procs.iter().enumerate().flat_map(move |(pi, p)| {
|
||||
p.threads
|
||||
.into_iter()
|
||||
.filter(|&t| {
|
||||
matches!(
|
||||
self.k.thread_state_for_test(t),
|
||||
Some(ThreadState::Ready | ThreadState::Running(_))
|
||||
)
|
||||
})
|
||||
.map(move |t| (pi, t))
|
||||
})
|
||||
}
|
||||
|
||||
/// Kills process `pi` and brings it back with fresh threads and caps.
|
||||
fn reincarnate(&mut self, pi: usize) {
|
||||
self.k.destroy_process(self.procs[pi].pid);
|
||||
self.procs[pi].pid = self.k.create_process_internal().unwrap();
|
||||
let t0 = self.procs[0].threads[0];
|
||||
Self::equip(
|
||||
&mut self.k,
|
||||
&mut self.procs[pi],
|
||||
self.eps,
|
||||
self.ntfn,
|
||||
false,
|
||||
t0,
|
||||
);
|
||||
self.rebirths += 1;
|
||||
}
|
||||
|
||||
fn step(&mut self, rng: &mut Rng) {
|
||||
let count = self.runnable().count();
|
||||
if count == 0 || rng.below(100) == 0 {
|
||||
// Random clients deadlock; killing someone is what breaks it.
|
||||
self.reincarnate(1 + rng.below(PROCS - 1));
|
||||
return;
|
||||
}
|
||||
let pick = rng.below(count);
|
||||
let (pi, tid) = self.runnable().nth(pick).unwrap();
|
||||
self.ran += 1;
|
||||
self.k.switch_to_thread_for_test(tid);
|
||||
// What the adapter would hand the thread when it resumes.
|
||||
if let Some(SyscallResult::Received(d)) = self.k.take_pending_result(tid) {
|
||||
self.record(&d);
|
||||
}
|
||||
let ep = self.procs[pi].eps[rng.below(2)];
|
||||
let ntfn = self.procs[pi].ntfn;
|
||||
|
||||
let call = match rng.below(9) {
|
||||
0 => Syscall::Send {
|
||||
endpoint: ep,
|
||||
message: Message::from_words([self.fresh_id(), 0, 0, 0]),
|
||||
},
|
||||
1 | 2 => Syscall::Call {
|
||||
endpoint: ep,
|
||||
message: Message::from_words([self.fresh_id(), 1, 0, 0]),
|
||||
},
|
||||
3 | 4 => Syscall::Receive { endpoint: ep },
|
||||
5 => Syscall::Reply {
|
||||
message: Message::from_words([self.fresh_id(), 2, 0, 0]),
|
||||
},
|
||||
6 => Syscall::ReplyRecv {
|
||||
endpoint: ep,
|
||||
message: Message::from_words([self.fresh_id(), 2, 0, 0]),
|
||||
},
|
||||
7 => Syscall::Signal { notification: ntfn },
|
||||
_ => match rng.below(2) {
|
||||
0 => Syscall::Wait { notification: ntfn },
|
||||
_ => Syscall::Poll { notification: ntfn },
|
||||
},
|
||||
};
|
||||
let is_reply = matches!(call, Syscall::Reply { .. } | Syscall::ReplyRecv { .. });
|
||||
let had_reply = self.k.ipc[tid.raw() as usize].reply_to.is_some();
|
||||
let outcome = self.k.handle_syscall(call);
|
||||
if is_reply && had_reply {
|
||||
self.calls_answered += 1;
|
||||
}
|
||||
self.record_outcome(&outcome);
|
||||
}
|
||||
|
||||
fn check(&self) {
|
||||
let k = &self.k;
|
||||
for p in &self.procs {
|
||||
for &t in &p.threads {
|
||||
let Some(state) = k.thread_state_for_test(t) else {
|
||||
continue;
|
||||
};
|
||||
let ipc = &k.ipc[t.raw() as usize];
|
||||
let queued_on = |e: EndpointId| {
|
||||
let ep = k.endpoints.get(e).unwrap();
|
||||
(ep.is_sender(t), ep.is_receiver(t))
|
||||
};
|
||||
let in_any_queue = self.eps.iter().any(|&e| {
|
||||
let (s, r) = queued_on(e);
|
||||
s || r
|
||||
}) || k.notifications.get(self.ntfn).unwrap().is_waiter(t);
|
||||
|
||||
match state {
|
||||
ThreadState::Blocked(BlockReason::Sending(e) | BlockReason::Calling(e)) => {
|
||||
assert_eq!(
|
||||
queued_on(e),
|
||||
(true, false),
|
||||
"{t:?} blocked sending but not queued"
|
||||
);
|
||||
assert!(
|
||||
ipc.outgoing.is_some(),
|
||||
"{t:?} queued sender without a message"
|
||||
);
|
||||
}
|
||||
ThreadState::Blocked(BlockReason::Receiving(e)) => {
|
||||
assert_eq!(
|
||||
queued_on(e),
|
||||
(false, true),
|
||||
"{t:?} blocked receiving but not queued"
|
||||
);
|
||||
}
|
||||
ThreadState::Blocked(BlockReason::WaitingNotification(n)) => {
|
||||
assert!(k.notifications.get(n).unwrap().is_waiter(t));
|
||||
}
|
||||
ThreadState::Blocked(BlockReason::AwaitingReply) => {
|
||||
assert!(!in_any_queue, "{t:?} awaiting reply but still queued");
|
||||
let me = crate::syscall::ThreadRef {
|
||||
tid: t,
|
||||
generation: k.threads.generation(t).unwrap(),
|
||||
};
|
||||
let holders = self
|
||||
.procs
|
||||
.iter()
|
||||
.flat_map(|p| p.threads)
|
||||
.filter(|&s| k.thread_state_for_test(s).is_some())
|
||||
.filter(|s| k.ipc[s.raw() as usize].reply_to == Some(me))
|
||||
.count();
|
||||
assert_eq!(holders, 1, "{t:?} awaits a reply nobody can send");
|
||||
}
|
||||
ThreadState::Ready | ThreadState::Running(_) | ThreadState::Dead => {
|
||||
assert!(!in_any_queue, "{t:?} runnable but queued on an IPC object");
|
||||
assert!(ipc.outgoing.is_none());
|
||||
}
|
||||
}
|
||||
let blocked = matches!(state, ThreadState::Blocked(_));
|
||||
assert_eq!(
|
||||
k.scheduler.is_queued(t),
|
||||
matches!(state, ThreadState::Ready),
|
||||
"{t:?} in state {state:?} has wrong run-queue membership"
|
||||
);
|
||||
if blocked {
|
||||
assert_ne!(k.current_thread(), Some(t));
|
||||
}
|
||||
|
||||
// A live reply slot always points at a thread waiting for it.
|
||||
if let Some(r) = ipc.reply_to
|
||||
&& k.threads.generation(r.tid) == Some(r.generation)
|
||||
{
|
||||
assert_eq!(
|
||||
k.thread_state_for_test(r.tid),
|
||||
Some(ThreadState::Blocked(BlockReason::AwaitingReply)),
|
||||
"{t:?} holds a reply for {:?} which is not waiting",
|
||||
r.tid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &e in &self.eps {
|
||||
let ep = k.endpoints.get(e).unwrap();
|
||||
assert!(!(ep.has_waiting_sender() && ep.has_waiting_receiver()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipc_state_machine_holds_under_random_operations() {
|
||||
for seed in [3u64, 0xC0FFEE, 0xFEED_FACE, 99, 123_456_789] {
|
||||
let mut world = World::new();
|
||||
let mut rng = Rng(seed);
|
||||
for _ in 0..STEPS {
|
||||
world.step(&mut rng);
|
||||
world.check();
|
||||
}
|
||||
assert!(
|
||||
world.calls_answered > 20,
|
||||
"seed {seed:#x}: too few replies: answered={} signals={} delivered={} ran={}",
|
||||
world.calls_answered,
|
||||
world.signals_seen,
|
||||
world.delivered.iter().filter(|d| **d).count(),
|
||||
world.ran
|
||||
);
|
||||
assert!(
|
||||
world.rebirths > 5,
|
||||
"seed {seed:#x}: kill/re-create barely exercised"
|
||||
);
|
||||
assert!(world.signals_seen > 20, "seed {seed:#x}: too few signals");
|
||||
assert!(world.delivered.iter().filter(|d| **d).count() > 200);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::message::Message;
|
||||
use domain::resource::Resource;
|
||||
use domain::syscall::Syscall;
|
||||
use domain::thread::{BlockReason, ThreadState};
|
||||
use domain::thread::ThreadState;
|
||||
use types::address::DataRef;
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::{IrqId, ThreadId};
|
||||
use types::id::ThreadId;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
fn create_process(k: &mut Kernel<MockPlatform>) -> CapSlot {
|
||||
@@ -177,57 +177,6 @@ fn endpoint_slots_are_reclaimed_when_processes_die() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_routing_does_not_survive_the_owning_thread() {
|
||||
let (mut k, pid_a) = kernel_with_process();
|
||||
let tid_a = k.current_thread().unwrap();
|
||||
let cap_b = create_process(&mut k);
|
||||
let pid_b = ProcessId::from_raw(1);
|
||||
let tid_b = k.spawn_thread_for_test(pid_b, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
let irq = IrqId::from_raw(10);
|
||||
k.register_irq_for_test(irq, tid_b);
|
||||
|
||||
k.switch_to_thread_for_test(tid_a);
|
||||
k.handle_syscall(Syscall::Kill { process: cap_b });
|
||||
|
||||
// A new thread reuses B's thread id and happens to be blocked.
|
||||
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
assert_eq!(reused, tid_b);
|
||||
k.block_thread_for_test(reused, BlockReason::Receiving(CapSlot::new(1).unwrap()));
|
||||
|
||||
assert_eq!(
|
||||
k.handle_interrupt(irq),
|
||||
None,
|
||||
"IRQ delivered to an unrelated thread"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_numbers_beyond_the_table_are_ignored() {
|
||||
let (mut k, _) = kernel_with_process();
|
||||
// The PLIC can report sources up to 1023.
|
||||
assert_eq!(k.handle_interrupt(IrqId::from_raw(1000)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exiting_a_thread_releases_its_interrupt_route() {
|
||||
let (mut k, pid_a) = kernel_with_process();
|
||||
let tid_a = k.current_thread().unwrap();
|
||||
let irq = IrqId::from_raw(10);
|
||||
k.register_irq_for_test(irq, tid_a);
|
||||
k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
|
||||
assert!(matches!(
|
||||
k.handle_syscall(Syscall::ExitThread),
|
||||
SyscallOutcome::TerminateCaller
|
||||
));
|
||||
|
||||
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
assert_eq!(reused, tid_a);
|
||||
k.block_thread_for_test(reused, BlockReason::Receiving(CapSlot::new(1).unwrap()));
|
||||
assert_eq!(k.handle_interrupt(irq), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_cap_frees_the_slot() {
|
||||
let (mut k, pid_a) = kernel_with_process();
|
||||
@@ -253,3 +202,43 @@ fn killing_yourself_terminates_the_caller() {
|
||||
assert!(matches!(outcome, SyscallOutcome::TerminateCaller));
|
||||
assert!(!k.process_exists(pid_a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exiting_a_thread_unbinds_its_notification() {
|
||||
let (mut k, pid_a) = kernel_with_process();
|
||||
let tid_a = k.current_thread().unwrap();
|
||||
let n = k.create_notification(pid_a).unwrap();
|
||||
let rw = PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Write);
|
||||
let ncap = k.grant_cap_for_test(pid_a, Resource::Notification(n), rw);
|
||||
assert!(!is_error(&k.handle_syscall(Syscall::BindNotification {
|
||||
notification: ncap
|
||||
})));
|
||||
let other = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
assert!(matches!(
|
||||
k.handle_syscall(Syscall::ExitThread),
|
||||
SyscallOutcome::TerminateCaller
|
||||
));
|
||||
|
||||
// A new thread reuses the id and blocks in an unrelated receive.
|
||||
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
assert_eq!(reused, tid_a);
|
||||
let ep = k.create_endpoint_for_test();
|
||||
let recv = k.grant_cap_for_test(pid_a, Resource::Endpoint(ep), ipc_perms());
|
||||
k.switch_to_thread_for_test(reused);
|
||||
assert!(matches!(
|
||||
k.handle_syscall(Syscall::Receive { endpoint: recv }),
|
||||
SyscallOutcome::BlockCaller(_)
|
||||
));
|
||||
|
||||
k.switch_to_thread_for_test(other);
|
||||
k.handle_syscall(Syscall::Signal { notification: ncap });
|
||||
assert!(
|
||||
matches!(
|
||||
k.thread_state_for_test(reused),
|
||||
Some(ThreadState::Blocked(_))
|
||||
),
|
||||
"signal reached a thread that never bound the notification"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ mod capability;
|
||||
mod exit;
|
||||
mod invariants;
|
||||
mod ipc;
|
||||
mod ipc_invariants;
|
||||
mod isolation;
|
||||
mod lifecycle;
|
||||
mod lifetimes;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use domain::scheduler::SchedulerDecision;
|
||||
use types::id::{CoreId, IrqId, ThreadId};
|
||||
use types::id::CoreId;
|
||||
use types::time::TimeSlice;
|
||||
|
||||
#[test]
|
||||
@@ -30,26 +30,3 @@ fn tick_preempts_when_time_expired_and_other_thread_ready() {
|
||||
let d2 = k.handle_tick(CoreId::from_raw(0));
|
||||
assert!(matches!(d2, SchedulerDecision::Preempt(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_unknown_irq_returns_none() {
|
||||
let (mut k, _) = kernel_with_process();
|
||||
assert!(k.handle_interrupt(IrqId::from_raw(99)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_unblocks_registered_thread() {
|
||||
let (mut k, _) = kernel_with_process();
|
||||
let tid = ThreadId::from_raw(0);
|
||||
let irq = IrqId::from_raw(5);
|
||||
|
||||
// Register and block the thread on this IRQ
|
||||
k.register_irq_for_test(irq, tid);
|
||||
k.block_thread_for_test(
|
||||
tid,
|
||||
domain::thread::BlockReason::WaitingForInterrupt(types::bounded::CapSlot::new(0).unwrap()),
|
||||
);
|
||||
|
||||
let result = k.handle_interrupt(irq);
|
||||
assert_eq!(result, Some(tid));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user