Files
k-os/crates/domain/src/notification/mod.rs
Gabriel Kaszewski 76f10ff1f1 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
2026-10-26 20:27:38 +01:00

187 lines
4.9 KiB
Rust

//! Asynchronous signals: a word of bits, a FIFO of waiters and at most one
//! bound thread. See docs/ipc.md.
use types::{
badge::Badge,
generation::Generation,
id::{NotificationId, ProcessId, ThreadId},
};
use crate::{endpoint::wait_queue::WaitQueue, generations::SlotGenerations};
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
#[cfg(kani)]
#[path = "proofs.rs"]
mod proofs;
pub const MAX_NOTIFICATIONS: usize = 128;
/// What the kernel must do after a signal.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalEffect {
/// Wake this blocked `Wait`er with these bits.
WakeWaiter(ThreadId, usize),
/// Wake the bound thread out of its `Receive` with these bits.
WakeBound(ThreadId, usize),
/// Nobody to wake; the bits were stored.
Stored,
}
pub struct Notification {
id: NotificationId,
owner: ProcessId,
bits: usize,
waiters: WaitQueue,
bound: Option<ThreadId>,
}
impl Notification {
pub fn new(id: NotificationId, owner: ProcessId) -> Self {
Self {
id,
owner,
bits: 0,
waiters: WaitQueue::new(),
bound: None,
}
}
pub fn id(&self) -> NotificationId {
self.id
}
pub fn owner(&self) -> ProcessId {
self.owner
}
pub fn bound(&self) -> Option<ThreadId> {
self.bound
}
/// Delivers a signal. `bound_is_receiving` says whether the bound
/// thread is currently blocked in `Receive` and can be woken.
pub fn signal(&mut self, badge: Badge, bound_is_receiving: bool) -> SignalEffect {
let bits = if badge.is_none() { 1 } else { badge.raw() };
if let Some(waiter) = self.waiters.pop() {
return SignalEffect::WakeWaiter(waiter, bits);
}
if let (Some(bound), true) = (self.bound, bound_is_receiving) {
return SignalEffect::WakeBound(bound, bits);
}
self.bits |= bits;
SignalEffect::Stored
}
/// Returns and clears the pending bits.
pub fn take_bits(&mut self) -> usize {
core::mem::take(&mut self.bits)
}
pub fn has_bits(&self) -> bool {
self.bits != 0
}
pub fn is_waiter(&self, thread: ThreadId) -> bool {
self.waiters.contains(thread)
}
pub fn add_waiter(&mut self, thread: ThreadId) -> bool {
self.waiters.push(thread).is_some()
}
pub fn bind(&mut self, thread: ThreadId) -> bool {
if self.bound.is_some() {
return false;
}
self.bound = Some(thread);
true
}
pub fn unbind(&mut self, thread: ThreadId) {
if self.bound == Some(thread) {
self.bound = None;
}
}
/// Forgets `thread` as waiter and as bound thread.
pub fn remove_thread(&mut self, thread: ThreadId) {
self.waiters.remove_thread(thread);
self.unbind(thread);
}
/// Drains every waiter, e.g. when the notification is destroyed.
pub fn pop_waiter(&mut self) -> Option<ThreadId> {
self.waiters.pop()
}
}
pub struct NotificationTable {
slots: [Option<Notification>; MAX_NOTIFICATIONS],
generations: SlotGenerations<MAX_NOTIFICATIONS>,
}
impl NotificationTable {
pub fn new() -> Self {
Self {
slots: [const { None }; MAX_NOTIFICATIONS],
generations: SlotGenerations::new(),
}
}
pub fn create(&mut self, owner: ProcessId) -> Option<NotificationId> {
let idx = (0..MAX_NOTIFICATIONS)
.find(|&i| self.slots[i].is_none() && self.generations.is_usable(i))?;
let id = NotificationId::from_raw(idx as u16);
self.slots[idx] = Some(Notification::new(id, owner));
Some(id)
}
pub fn get(&self, id: NotificationId) -> Option<&Notification> {
self.slots.get(id.raw() as usize)?.as_ref()
}
pub fn get_mut(&mut self, id: NotificationId) -> Option<&mut Notification> {
self.slots.get_mut(id.raw() as usize)?.as_mut()
}
pub fn generation(&self, id: NotificationId) -> Option<Generation> {
self.get(id)?;
self.generations.current(id.raw() as usize)
}
pub fn revoke(&mut self, id: NotificationId) {
if self.get(id).is_some() {
self.generations.advance(id.raw() as usize);
}
}
pub fn destroy(&mut self, id: NotificationId) -> Option<Notification> {
let n = self.slots.get_mut(id.raw() as usize)?.take()?;
self.generations.advance(id.raw() as usize);
Some(n)
}
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
self.slots
.iter()
.flatten()
.filter(move |n| n.owner == owner)
.map(|n| n.id)
}
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
for n in self.slots.iter_mut().flatten() {
n.remove_thread(thread);
}
}
}
impl Default for NotificationTable {
fn default() -> Self {
Self::new()
}
}