//! 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, } 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 { 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 { self.waiters.pop() } } pub struct NotificationTable { slots: [Option; MAX_NOTIFICATIONS], generations: SlotGenerations, } impl NotificationTable { pub fn new() -> Self { Self { slots: [const { None }; MAX_NOTIFICATIONS], generations: SlotGenerations::new(), } } pub fn create(&mut self, owner: ProcessId) -> Option { 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 { 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 { 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 + '_ { 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() } }