refactor(kernel): split ipc/memory/lib/thread by concern; trim comments

This commit is contained in:
2026-10-27 13:21:53 +01:00
parent b814bd9570
commit 95dbe7b9d5
34 changed files with 1239 additions and 1231 deletions

View File

@@ -1,121 +1,22 @@
//! Endpoint IPC, notifications and interrupt delivery. docs/ipc.md is the
//! specification; the comments below only point at the non-obvious rules.
//! Endpoint send/receive, call/reply, capability transfer and minting.
use domain::{
capability::Capability,
error::KernelError,
message::Message,
notification::SignalEffect,
resource::Resource,
scheduler::Scheduler,
thread::{BlockReason, ThreadState},
capability::Capability, error::KernelError, message::Message, resource::Resource,
thread::BlockReason,
};
use platform::Platform;
use types::{
badge::Badge,
bounded::CapSlot,
generation::Generation,
id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId},
id::{EndpointId, ProcessId, ThreadId},
permission::{Permission, PermissionSet},
};
use super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
use super::super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
use super::Outgoing;
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 ipc(&self, tid: ThreadId) -> Option<&IpcState> {
self.ipc.get(tid.raw() as usize)
}
pub(crate) fn ipc_mut(&mut self, tid: ThreadId) -> Option<&mut IpcState> {
self.ipc.get_mut(tid.raw() as usize)
}
pub(crate) fn set_pending_result(&mut self, tid: ThreadId, result: Option<SyscallResult>) {
if let Some(r) = self.pending_results.get_mut(tid.raw() as usize) {
*r = result;
}
}
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.set_pending_result(tid, Some(result));
self.make_ready(tid, priority);
}
}
/// 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,
@@ -133,22 +34,8 @@ impl<P: Platform> Kernel<P> {
}
}
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(
pub(super) fn transfer_cap(
&mut self,
from: ProcessId,
slot: Option<CapSlot>,
@@ -169,7 +56,7 @@ impl<P: Platform> Kernel<P> {
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
/// reply slot now names the sender.
fn deliver(
pub(super) fn deliver(
&mut self,
sender: ThreadId,
out: Outgoing,
@@ -413,157 +300,4 @@ impl<P: Platform> Kernel<P> {
}
}
}
// ---- notifications ----
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.leave_queue(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 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))
}
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)
}
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).is_none_or(|s| s.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);
}
if let Some(s) = self.ipc_mut(tid) {
s.bound = Some(id);
}
Ok(())
}
// ---- interrupts ----
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),
}
}
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 {
Err(KernelError::InvalidIrq(irq))
}
}
pub(crate) fn ack_interrupt(
&mut self,
pid: ProcessId,
slot: CapSlot,
) -> Result<(), KernelError> {
let irq = self.interrupt_cap(pid, slot)?;
self.platform.unmask_irq(irq);
Ok(())
}
/// 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;
};
// 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;
}
self.signal_notification(route.notification, route.badge);
}
}

View File

@@ -0,0 +1,67 @@
//! Interrupt routing to notifications.
use domain::{error::KernelError, resource::Resource};
use platform::Platform;
use types::{
bounded::CapSlot,
id::{IrqId, ProcessId},
permission::Permission,
};
use crate::Kernel;
impl<P: Platform> Kernel<P> {
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),
}
}
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 {
Err(KernelError::InvalidIrq(irq))
}
}
pub(crate) fn ack_interrupt(
&mut self,
pid: ProcessId,
slot: CapSlot,
) -> Result<(), KernelError> {
let irq = self.interrupt_cap(pid, slot)?;
self.platform.unmask_irq(irq);
Ok(())
}
/// 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;
};
// 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;
}
self.signal_notification(route.notification, route.badge);
}
}

View File

@@ -0,0 +1,109 @@
//! Endpoint IPC, notifications and interrupt delivery (see docs/ipc.md).
mod endpoint;
mod irq;
mod notification;
use domain::{
error::KernelError,
message::Message,
scheduler::Scheduler,
thread::{BlockReason, ThreadState},
};
use platform::Platform;
use types::{
badge::Badge,
generation::Generation,
id::{NotificationId, ThreadId},
};
use super::{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>,
}
impl<P: Platform> Kernel<P> {
pub(crate) fn ipc(&self, tid: ThreadId) -> Option<&IpcState> {
self.ipc.get(tid.raw() as usize)
}
pub(crate) fn ipc_mut(&mut self, tid: ThreadId) -> Option<&mut IpcState> {
self.ipc.get_mut(tid.raw() as usize)
}
pub(crate) fn set_pending_result(&mut self, tid: ThreadId, result: Option<SyscallResult>) {
if let Some(r) = self.pending_results.get_mut(tid.raw() as usize) {
*r = result;
}
}
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.set_pending_result(tid, Some(result));
self.make_ready(tid, priority);
}
}
/// 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));
}
}
}

View File

@@ -0,0 +1,133 @@
//! Notification objects: signal, wait, poll, bind.
use domain::{
error::KernelError, notification::SignalEffect, resource::Resource, thread::BlockReason,
};
use platform::Platform;
use types::{
badge::Badge,
bounded::CapSlot,
generation::Generation,
id::{NotificationId, ProcessId, ThreadId},
permission::{Permission, PermissionSet},
};
use super::super::{Delivery, SyscallOutcome, SyscallResult};
use crate::Kernel;
fn rw_delegate() -> PermissionSet {
PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Write)
.with(Permission::Delegate)
}
impl<P: Platform> Kernel<P> {
pub(super) 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),
}
}
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.
pub(super) 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.leave_queue(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 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))
}
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)
}
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).is_none_or(|s| s.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);
}
if let Some(s) = self.ipc_mut(tid) {
s.bound = Some(id);
}
Ok(())
}
}

View File

@@ -51,9 +51,7 @@ impl<P: Platform> Kernel<P> {
)
}
/// Moves one of the caller's pages into the process named by
/// `process_cap`. Executable pages are mapped read-execute, others
/// read-write.
/// Moves one of the caller's pages into `process_cap`'s process (exec pages RX, others RW).
pub(crate) fn handle_map_page(
&mut self,
caller_pid: ProcessId,
@@ -99,9 +97,7 @@ impl<P: Platform> Kernel<P> {
self.grant_cap(pid, Resource::Memory(id), all)
}
/// Maps the region behind `slot` into the caller. The capability must
/// allow every access `perms` grants; the mapping lasts as long as
/// the capability.
/// Maps the region behind `slot` into the caller; the cap must allow `perms`, mapping lasts as long as the cap.
pub(crate) fn map_region_cap(
&mut self,
pid: ProcessId,
@@ -125,9 +121,7 @@ impl<P: Platform> Kernel<P> {
self.map_region_via(id, pid, va, perms, via)
}
/// The physical base of the region behind `slot`. Needs Read on the
/// capability; pool regions are physically contiguous, so the base is
/// enough to program a device.
/// Physical base of the region behind `slot` (needs Read); pool regions are contiguous.
pub(crate) fn region_address_cap(
&self,
pid: ProcessId,