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:
2026-10-26 20:27:38 +01:00
parent d43421e33e
commit 76f10ff1f1
46 changed files with 3339 additions and 1296 deletions

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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),