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

@@ -1,10 +1,11 @@
use types::{
bounded::{CapSlot, Priority},
id::{CoreId, ProcessId, ThreadId},
bounded::Priority,
generation::Generation,
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
time::TimeSlice,
};
use crate::error::KernelError;
use crate::{error::KernelError, generations::SlotGenerations};
#[cfg(test)]
#[path = "tests.rs"]
@@ -80,10 +81,16 @@ impl SavedContext {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockReason {
Sending(CapSlot),
Receiving(CapSlot),
WaitingForInterrupt(CapSlot),
WaitingAny,
/// Queued as a sender on the endpoint.
Sending(EndpointId),
/// Queued as a `Call`er on the endpoint; will then await the reply.
Calling(EndpointId),
/// Queued as a receiver on the endpoint.
Receiving(EndpointId),
/// Delivered a `Call`; waiting for the server's reply.
AwaitingReply,
/// Blocked in `Wait` on the notification.
WaitingNotification(NotificationId),
}
impl Thread {
@@ -180,6 +187,17 @@ impl Thread {
}
}
/// Keeps a blocked thread blocked, waiting for something else.
pub fn rewait(&mut self, reason: BlockReason) -> Result<(), KernelError> {
match self.state {
ThreadState::Blocked(_) => {
self.state = ThreadState::Blocked(reason);
Ok(())
}
_ => Err(KernelError::InvalidStateTransition(self.id)),
}
}
pub fn unblock(&mut self) -> Result<(), KernelError> {
match self.state {
ThreadState::Blocked(_) => {
@@ -215,15 +233,28 @@ pub const MAX_THREADS: usize = 256;
pub struct ThreadTable {
threads: [Option<Thread>; MAX_THREADS],
generations: SlotGenerations<MAX_THREADS>,
}
impl ThreadTable {
pub fn new() -> Self {
Self {
threads: [const { None }; MAX_THREADS],
generations: SlotGenerations::new(),
}
}
/// Current generation of a live thread, so references to it (reply
/// slots) can detect that the id was reused.
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
self.get(id).ok()?;
self.generations.current(id.raw() as usize)
}
fn free_slot(&self) -> Option<usize> {
(0..MAX_THREADS).find(|&i| self.threads[i].is_none() && self.generations.is_usable(i))
}
pub fn spawn(
&mut self,
process: ProcessId,
@@ -231,11 +262,7 @@ impl ThreadTable {
time_slice: TimeSlice,
core_affinity: Option<CoreId>,
) -> Result<ThreadId, KernelError> {
let idx = self
.threads
.iter()
.position(|s| s.is_none())
.ok_or(KernelError::TooManyThreads)?;
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::new(
@@ -256,11 +283,7 @@ impl ThreadTable {
core_affinity: Option<CoreId>,
context: SavedContext,
) -> Result<ThreadId, KernelError> {
let idx = self
.threads
.iter()
.position(|s| s.is_none())
.ok_or(KernelError::TooManyThreads)?;
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::with_context(
@@ -292,10 +315,13 @@ impl ThreadTable {
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
let idx = id.raw() as usize;
self.threads
let thread = self
.threads
.get_mut(idx)
.and_then(|s| s.take())
.ok_or(KernelError::InvalidThread(id))
.ok_or(KernelError::InvalidThread(id))?;
self.generations.advance(idx);
Ok(thread)
}
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {

View File

@@ -1,6 +1,6 @@
use super::*;
use types::bounded::Priority;
use types::id::{CoreId, ProcessId, ThreadId};
use types::id::{CoreId, EndpointId, ProcessId, ThreadId};
use types::time::TimeSlice;
fn thread(id: u16) -> ThreadId {
@@ -65,10 +65,10 @@ fn ready_to_running() {
fn running_to_blocked() {
let mut t = test_thread(0, 0);
t.set_running(core(0)).unwrap();
t.block(BlockReason::WaitingAny).unwrap();
t.block(BlockReason::AwaitingReply).unwrap();
assert!(matches!(
t.state(),
ThreadState::Blocked(BlockReason::WaitingAny)
ThreadState::Blocked(BlockReason::AwaitingReply)
));
}
@@ -76,7 +76,7 @@ fn running_to_blocked() {
fn blocked_to_ready() {
let mut t = test_thread(0, 0);
t.set_running(core(0)).unwrap();
t.block(BlockReason::WaitingAny).unwrap();
t.block(BlockReason::AwaitingReply).unwrap();
t.unblock().unwrap();
assert!(matches!(t.state(), ThreadState::Ready));
}
@@ -105,7 +105,7 @@ fn dead_cannot_transition() {
t.set_running(core(0)).unwrap();
t.exit().unwrap();
assert!(t.set_running(core(0)).is_err());
assert!(t.block(BlockReason::WaitingAny).is_err());
assert!(t.block(BlockReason::AwaitingReply).is_err());
assert!(t.unblock().is_err());
assert!(t.preempt().is_err());
assert!(t.exit().is_err());
@@ -114,14 +114,14 @@ fn dead_cannot_transition() {
#[test]
fn ready_cannot_block() {
let mut t = test_thread(0, 0);
assert!(t.block(BlockReason::WaitingAny).is_err());
assert!(t.block(BlockReason::AwaitingReply).is_err());
}
#[test]
fn blocked_cannot_run_directly() {
let mut t = test_thread(0, 0);
t.set_running(core(0)).unwrap();
t.block(BlockReason::WaitingAny).unwrap();
t.block(BlockReason::AwaitingReply).unwrap();
assert!(t.set_running(core(0)).is_err());
}
@@ -141,7 +141,7 @@ fn ready_cannot_exit() {
fn blocked_cannot_preempt() {
let mut t = test_thread(0, 0);
t.set_running(core(0)).unwrap();
t.block(BlockReason::WaitingAny).unwrap();
t.block(BlockReason::AwaitingReply).unwrap();
assert!(t.preempt().is_err());
}
@@ -237,3 +237,32 @@ fn table_destroy_reuses_slot() {
.unwrap();
assert_eq!(id, new_id);
}
#[test]
fn a_blocked_thread_can_change_what_it_waits_for() {
let mut t = Thread::new(
ThreadId::from_raw(1),
ProcessId::from_raw(0),
Priority::DEFAULT,
TimeSlice::DEFAULT,
None,
);
t.set_running(CoreId::from_raw(0)).unwrap();
t.block(BlockReason::Calling(EndpointId::from_raw(0)))
.unwrap();
t.rewait(BlockReason::AwaitingReply).unwrap();
assert_eq!(*t.state(), ThreadState::Blocked(BlockReason::AwaitingReply));
}
#[test]
fn only_blocked_threads_can_rewait() {
let mut t = Thread::new(
ThreadId::from_raw(1),
ProcessId::from_raw(0),
Priority::DEFAULT,
TimeSlice::DEFAULT,
None,
);
assert!(t.rewait(BlockReason::AwaitingReply).is_err());
assert_eq!(*t.state(), ThreadState::Ready);
}