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

@@ -75,7 +75,6 @@ _restore_smode:
ld x1, 8(sp) ld x1, 8(sp)
ld x3, 24(sp) ld x3, 24(sp)
ld x4, 32(sp) ld x4, 32(sp)
ld x5, 40(sp)
ld x6, 48(sp) ld x6, 48(sp)
ld x7, 56(sp) ld x7, 56(sp)
ld x8, 64(sp) ld x8, 64(sp)
@@ -103,13 +102,19 @@ _restore_smode:
ld x30, 240(sp) ld x30, 240(sp)
ld x31, 248(sp) ld x31, 248(sp)
addi sp, sp, 272 # Every GPR except t0 (x5) is restored. Decide where we return to while
# t0 is still free, and restore it last on each path; using t0 after
# its restore would corrupt the interrupted program's t0.
csrr t0, sstatus csrr t0, sstatus
andi t0, t0, 0x100 andi t0, t0, 0x100
bnez t0, _sret_smode bnez t0, _sret_smode
ld x5, 40(sp)
addi sp, sp, 272
csrrw sp, sscratch, sp csrrw sp, sscratch, sp
sret
_sret_smode: _sret_smode:
ld x5, 40(sp)
addi sp, sp, 272
sret sret
"# "#
); );
@@ -139,156 +144,138 @@ pub fn set_kernel_stack(kernel_sp: usize) {
} }
} }
/// Decodes the syscall in `frame`, or `None` for an unknown number or an
/// argument that is not representable (e.g. a slot out of range).
fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> { fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
use domain::message::Message; use domain::message::Message;
use domain::syscall::{Syscall, WaitSet}; use domain::syscall::Syscall;
use types::address::{DataRef, EntryPoint, StackPointer, VirtualAddress};
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount};
use types::permission::PermissionSet;
use types::syscall_abi::*; use types::syscall_abi::*;
let nr = frame.regs[17]; // a7 let nr = frame.regs[17]; // a7
let a0 = frame.regs[10]; let a = |i: usize| frame.regs[10 + i];
let a1 = frame.regs[11]; let slot = |i: usize| CapSlot::new(u8::try_from(a(i)).ok()?);
let a2 = frame.regs[12]; let message = || -> Option<Message> {
let a3 = frame.regs[13]; let msg = Message::from_words([a(1), a(2), a(3), a(4)]);
let a4 = frame.regs[14]; Some(match a(5) {
NONE => msg,
_ => msg.with_cap(slot(5)?),
})
};
match nr { Some(match nr {
SYS_EXIT => Some(Syscall::Exit), SYS_EXIT => Syscall::Exit,
SYS_HALT => Some(Syscall::Halt), SYS_HALT => Syscall::Halt,
SYS_EXIT_THREAD => Some(Syscall::ExitThread), SYS_EXIT_THREAD => Syscall::ExitThread,
SYS_YIELD => Some(Syscall::Yield), SYS_YIELD => Syscall::Yield,
SYS_GET_TIME => Some(Syscall::GetTime), SYS_GET_TIME => Syscall::GetTime,
SYS_GET_PROCESS_ID => Some(Syscall::GetProcessId), SYS_GET_PROCESS_ID => Syscall::GetProcessId,
SYS_SEND => { SYS_SEND => Syscall::Send {
let slot = types::bounded::CapSlot::new(a0 as u8)?; endpoint: slot(0)?,
let msg = Message::from_words([a1, a2, a3, a4]); message: message()?,
Some(Syscall::Send { },
endpoint: slot, SYS_CALL => Syscall::Call {
message: msg, endpoint: slot(0)?,
message: message()?,
},
SYS_RECEIVE => Syscall::Receive { endpoint: slot(0)? },
SYS_REPLY => Syscall::Reply {
message: message()?,
},
SYS_REPLY_RECV => Syscall::ReplyRecv {
endpoint: slot(0)?,
message: message()?,
},
SYS_DELEGATE => Syscall::Delegate {
slot: slot(0)?,
to_endpoint: slot(1)?,
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
},
SYS_MINT => Syscall::Mint {
slot: slot(0)?,
badge: Badge::new(a(1)),
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
},
SYS_CREATE_NOTIFICATION => Syscall::CreateNotification,
SYS_SIGNAL => Syscall::Signal {
notification: slot(0)?,
},
SYS_WAIT => Syscall::Wait {
notification: slot(0)?,
},
SYS_POLL => Syscall::Poll {
notification: slot(0)?,
},
SYS_BIND_NOTIFICATION => Syscall::BindNotification {
notification: slot(0)?,
},
SYS_SET_IRQ_NOTIFICATION => Syscall::SetIrqNotification {
irq: slot(0)?,
notification: slot(1)?,
},
SYS_ACKNOWLEDGE_INTERRUPT => Syscall::AcknowledgeInterrupt { irq: slot(0)? },
SYS_CREATE_PROCESS => Syscall::CreateProcess {
create_cap: slot(0)?,
image: DataRef::empty(),
},
SYS_KILL => Syscall::Kill { process: slot(0)? },
SYS_REVOKE => Syscall::Revoke { slot: slot(0)? },
SYS_DROP_CAP => Syscall::DropCap { slot: slot(0)? },
SYS_SPAWN_THREAD => Syscall::SpawnThread {
entry: EntryPoint::from_raw(a(0)),
stack: StackPointer::from_raw(a(1)),
},
SYS_REQUEST_PAGES => Syscall::RequestPages {
count: PageCount::new(u16::try_from(a(0)).ok()?)?,
},
SYS_RELEASE_PAGES => Syscall::ReleasePages {
addr: VirtualAddress::from_raw(a(0)),
count: PageCount::new(u16::try_from(a(1)).ok()?)?,
},
SYS_SPAWN_THREAD_IN => Syscall::SpawnThreadIn {
process: slot(0)?,
entry: EntryPoint::from_raw(a(1)),
stack: StackPointer::from_raw(a(2)),
},
SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint,
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a(0) as u8 },
SYS_MAP_PAGE => Syscall::MapPage {
process: slot(0)?,
src_va: VirtualAddress::from_raw(a(1)),
dst_va: VirtualAddress::from_raw(a(2)),
executable: a(3) != 0,
},
_ => return None,
}) })
}
SYS_RECEIVE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Receive { endpoint: slot })
}
SYS_DELEGATE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
let to = types::bounded::CapSlot::new(a1 as u8)?;
let perms = types::permission::PermissionSet::from_bits(a2 as u8);
Some(Syscall::Delegate {
slot,
to_endpoint: to,
permissions: perms,
})
}
SYS_WAIT_FOR_INTERRUPT => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::WaitForInterrupt { irq: slot })
}
SYS_ACKNOWLEDGE_INTERRUPT => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::AcknowledgeInterrupt { irq: slot })
}
SYS_WAIT_ANY => {
let ep0 = if a0 != usize::MAX {
types::bounded::CapSlot::new(a0 as u8)
} else {
None
};
let ep1 = if a1 != usize::MAX {
types::bounded::CapSlot::new(a1 as u8)
} else {
None
};
let ep2 = if a2 != usize::MAX {
types::bounded::CapSlot::new(a2 as u8)
} else {
None
};
let ep3 = if a3 != usize::MAX {
types::bounded::CapSlot::new(a3 as u8)
} else {
None
};
let sources = WaitSet::new([ep0, ep1, ep2, ep3], [None, None, None, None]);
Some(Syscall::WaitAny { sources })
}
SYS_CREATE_PROCESS => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::CreateProcess {
create_cap: slot,
image: types::address::DataRef::empty(),
})
}
SYS_KILL => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Kill { process: slot })
}
SYS_REVOKE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Revoke { slot })
}
SYS_SPAWN_THREAD => Some(Syscall::SpawnThread {
entry: types::address::EntryPoint::from_raw(a0),
stack: types::address::StackPointer::from_raw(a1),
}),
SYS_REQUEST_PAGES => {
let count = types::bounded::PageCount::new(a0 as u16)?;
Some(Syscall::RequestPages { count })
}
SYS_RELEASE_PAGES => {
let count = types::bounded::PageCount::new(a1 as u16)?;
Some(Syscall::ReleasePages {
addr: types::address::VirtualAddress::from_raw(a0),
count,
})
}
SYS_SPAWN_THREAD_IN => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::SpawnThreadIn {
process: slot,
entry: types::address::EntryPoint::from_raw(a1),
stack: types::address::StackPointer::from_raw(a2),
})
}
SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint),
SYS_DROP_CAP => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::DropCap { slot })
}
SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }),
SYS_MAP_PAGE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::MapPage {
process: slot,
src_va: types::address::VirtualAddress::from_raw(a1),
dst_va: types::address::VirtualAddress::from_raw(a2),
executable: a3 != 0,
})
}
_ => None,
}
} }
fn write_result(frame: &mut TrapFrame, outcome: &kernel::SyscallOutcome) { /// Writes a completed syscall's result per the register ABI: status in
use kernel::{SyscallOutcome, SyscallResult}; /// `a0`, results in `a1..`.
fn write_result(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
use kernel::SyscallResult;
use kernel::syscall::DeliveryKind;
use types::syscall_abi::{KIND_CALL, KIND_MESSAGE, KIND_SIGNAL, NONE};
match outcome { let r = &mut frame.regs;
SyscallOutcome::Complete(result) => match result { r[10] = 0;
SyscallResult::Ok => frame.regs[10] = 0, match result {
SyscallResult::Value(v) => frame.regs[10] = *v, SyscallResult::Ok => {}
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize, SyscallResult::Value(v) => r[11] = *v,
SyscallResult::Message(m) => { SyscallResult::Slot(s) => r[11] = s.value() as usize,
frame.regs[10] = m.word(0); SyscallResult::Received(d) => {
frame.regs[11] = m.word(1); r[11] = d.badge;
frame.regs[12] = m.word(2); r[12..16].copy_from_slice(&d.words);
frame.regs[13] = m.word(3); r[16] = d.cap.map_or(NONE, |s| s.value() as usize);
r[17] = match d.kind {
DeliveryKind::Message => KIND_MESSAGE,
DeliveryKind::Call => KIND_CALL,
DeliveryKind::Signal => KIND_SIGNAL,
};
} }
SyscallResult::Error(_) => frame.regs[10] = usize::MAX, SyscallResult::Error(e) => r[10] = kernel::syscall::error_code(e) as usize,
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
},
SyscallOutcome::BlockCaller(_) => {}
SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0,
SyscallOutcome::TerminateCaller => {}
} }
} }
@@ -310,24 +297,7 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) { fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
let kern = unsafe { &mut *crate::kernel_ptr() }; let kern = unsafe { &mut *crate::kernel_ptr() };
if let Some(result) = kern.take_pending_result(tid) { if let Some(result) = kern.take_pending_result(tid) {
write_result_direct(frame, &result); write_result(frame, &result);
}
}
fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
use kernel::SyscallResult;
match result {
SyscallResult::Ok => frame.regs[10] = 0,
SyscallResult::Value(v) => frame.regs[10] = *v,
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
SyscallResult::Message(m) => {
frame.regs[10] = m.word(0);
frame.regs[11] = m.word(1);
frame.regs[12] = m.word(2);
frame.regs[13] = m.word(3);
}
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
} }
} }
@@ -441,14 +411,17 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
CAUSE_ECALL_U => { CAUSE_ECALL_U => {
frame.sepc += 4; frame.sepc += 4;
if let Some(syscall) = decode_syscall(frame) { let Some(syscall) = decode_syscall(frame) else {
frame.regs[10] = types::syscall_abi::ErrorCode::UnknownSyscall as usize;
return;
};
{
let kern = unsafe { &mut *crate::kernel_ptr() }; let kern = unsafe { &mut *crate::kernel_ptr() };
let current_tid = kern.current_thread(); let current_tid = kern.current_thread();
let outcome = kern.handle_syscall(syscall); let outcome = kern.handle_syscall(syscall);
write_result(frame, &outcome);
match outcome { match outcome {
kernel::SyscallOutcome::Complete(_) => {} kernel::SyscallOutcome::Complete(result) => write_result(frame, &result),
kernel::SyscallOutcome::BlockCaller(_) => { kernel::SyscallOutcome::BlockCaller(_) => {
if let Some(tid) = current_tid { if let Some(tid) = current_tid {
switch_to_next(frame, tid); switch_to_next(frame, tid);

View File

@@ -1,4 +1,4 @@
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet}; use types::{badge::Badge, bounded::CapSlot, generation::Generation, permission::PermissionSet};
pub mod authority; pub mod authority;
@@ -11,10 +11,12 @@ mod tests;
// Sized so that every representable `CapSlot` indexes a real slot. // Sized so that every representable `CapSlot` indexes a real slot.
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1; pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
#[derive(Clone)]
pub struct Capability { pub struct Capability {
resource: Resource, resource: Resource,
permissions: PermissionSet, permissions: PermissionSet,
generation: Generation, generation: Generation,
badge: Badge,
} }
impl Capability { impl Capability {
@@ -23,9 +25,20 @@ impl Capability {
resource, resource,
permissions, permissions,
generation, generation,
badge: Badge::NONE,
} }
} }
/// The same capability with `badge` stamped on it.
pub fn with_badge(mut self, badge: Badge) -> Self {
self.badge = badge;
self
}
pub fn badge(&self) -> Badge {
self.badge
}
pub fn resource(&self) -> &Resource { pub fn resource(&self) -> &Resource {
&self.resource &self.resource
} }

View File

@@ -57,6 +57,14 @@ impl Endpoint {
self.waiting_receivers.pop() self.waiting_receivers.pop()
} }
pub fn is_sender(&self, thread: ThreadId) -> bool {
self.waiting_senders.contains(thread)
}
pub fn is_receiver(&self, thread: ThreadId) -> bool {
self.waiting_receivers.contains(thread)
}
pub fn has_waiting_sender(&self) -> bool { pub fn has_waiting_sender(&self) -> bool {
!self.waiting_senders.is_empty() !self.waiting_senders.is_empty()
} }

View File

@@ -19,21 +19,21 @@ fn test_endpoint() -> Endpoint {
#[test] #[test]
fn empty_queue_pop_returns_none() { fn empty_queue_pop_returns_none() {
let mut queue = WaitQueue::new(); let mut queue: WaitQueue = WaitQueue::new();
assert!(queue.pop().is_none()); assert!(queue.pop().is_none());
assert!(queue.is_empty()); assert!(queue.is_empty());
} }
#[test] #[test]
fn push_then_pop_returns_same_thread() { fn push_then_pop_returns_same_thread() {
let mut queue = WaitQueue::new(); let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap(); queue.push(thread(1)).unwrap();
assert_eq!(queue.pop(), Some(thread(1))); assert_eq!(queue.pop(), Some(thread(1)));
} }
#[test] #[test]
fn pop_order_is_fifo() { fn pop_order_is_fifo() {
let mut queue = WaitQueue::new(); let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap(); queue.push(thread(1)).unwrap();
queue.push(thread(2)).unwrap(); queue.push(thread(2)).unwrap();
queue.push(thread(3)).unwrap(); queue.push(thread(3)).unwrap();
@@ -46,7 +46,7 @@ fn pop_order_is_fifo() {
#[test] #[test]
fn full_queue_rejects_push() { fn full_queue_rejects_push() {
let mut queue = WaitQueue::new(); let mut queue: WaitQueue = WaitQueue::new();
for i in 0..MAX_WAITERS { for i in 0..MAX_WAITERS {
assert!(queue.push(thread(i as u16)).is_some()); assert!(queue.push(thread(i as u16)).is_some());
} }
@@ -55,7 +55,7 @@ fn full_queue_rejects_push() {
#[test] #[test]
fn is_empty_after_drain() { fn is_empty_after_drain() {
let mut queue = WaitQueue::new(); let mut queue: WaitQueue = WaitQueue::new();
queue.push(thread(1)).unwrap(); queue.push(thread(1)).unwrap();
queue.push(thread(2)).unwrap(); queue.push(thread(2)).unwrap();
queue.pop(); queue.pop();
@@ -219,3 +219,40 @@ fn table_destroy_reuses_slot() {
let new_id = table.create(owner()).unwrap(); let new_id = table.create(owner()).unwrap();
assert_eq!(id, new_id); assert_eq!(id, new_id);
} }
#[test]
fn wait_queue_is_fifo_even_after_a_pop_leaves_a_hole() {
let mut q: WaitQueue = WaitQueue::new();
q.push(thread(1)).unwrap();
q.push(thread(2)).unwrap();
assert_eq!(q.pop(), Some(thread(1)));
q.push(thread(3)).unwrap();
assert_eq!(q.pop(), Some(thread(2)), "thread 3 jumped the queue");
assert_eq!(q.pop(), Some(thread(3)));
}
#[test]
fn wait_queue_keeps_order_around_a_removed_thread() {
let mut q: WaitQueue = WaitQueue::new();
for n in 1..=3 {
q.push(thread(n)).unwrap();
}
assert!(q.remove_thread(thread(2)));
q.push(thread(4)).unwrap();
assert_eq!(q.pop(), Some(thread(1)));
assert_eq!(q.pop(), Some(thread(3)));
assert_eq!(q.pop(), Some(thread(4)));
assert_eq!(q.pop(), None);
}
#[test]
fn wait_queue_kani_trace_push_push_remove() {
let mut q: WaitQueue<3> = WaitQueue::new();
q.push(thread(3)).unwrap();
q.push(thread(2)).unwrap();
assert!(q.remove_thread(thread(3)));
assert!(!q.remove_thread(thread(0)));
assert!(!q.remove_thread(thread(3)));
assert_eq!(q.pop(), Some(thread(2)));
assert_eq!(q.pop(), None);
}

View File

@@ -2,51 +2,127 @@ use types::id::ThreadId;
pub const MAX_WAITERS: usize = 16; pub const MAX_WAITERS: usize = 16;
pub struct WaitQueue { /// Bounded FIFO of blocked threads. Order is preserved across pops and
entries: [Option<ThreadId>; MAX_WAITERS], /// removals, so no waiter can be overtaken.
count: u8, pub struct WaitQueue<const N: usize = MAX_WAITERS> {
entries: [Option<ThreadId>; N],
len: usize,
} }
impl WaitQueue { impl<const N: usize> WaitQueue<N> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
entries: [None; MAX_WAITERS], entries: [None; N],
count: 0, len: 0,
} }
} }
pub fn push(&mut self, thread: ThreadId) -> Option<()> { pub fn push(&mut self, thread: ThreadId) -> Option<()> {
let slot = self.entries.iter_mut().find(|s| s.is_none())?; if self.len == N || self.contains(thread) {
*slot = Some(thread); return None;
self.count += 1; }
self.entries[self.len] = Some(thread);
self.len += 1;
Some(()) Some(())
} }
pub fn pop(&mut self) -> Option<ThreadId> { pub fn pop(&mut self) -> Option<ThreadId> {
let slot = self.entries.iter_mut().find(|s| s.is_some())?; let head = self.entries[0]?;
let thread = slot.take(); self.close_gap(0);
self.count -= 1; Some(head)
thread
} }
pub fn remove_thread(&mut self, thread: ThreadId) -> bool { pub fn remove_thread(&mut self, thread: ThreadId) -> bool {
for slot in &mut self.entries { match self.entries[..self.len]
if *slot == Some(thread) { .iter()
*slot = None; .position(|e| *e == Some(thread))
self.count -= 1; {
return true; Some(i) => {
self.close_gap(i);
true
}
None => false,
} }
} }
false
pub fn contains(&self, thread: ThreadId) -> bool {
self.entries[..self.len].contains(&Some(thread))
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.count == 0 self.len == 0
}
fn close_gap(&mut self, i: usize) {
for j in i..self.len - 1 {
self.entries[j] = self.entries[j + 1];
}
self.len -= 1;
self.entries[self.len] = None;
} }
} }
impl Default for WaitQueue { impl<const N: usize> Default for WaitQueue<N> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
#[cfg(kani)]
mod proofs {
use super::*;
/// Against a simple reference model (a plain array kept in arrival
/// order), any sequence of pushes, pops and removals behaves the same:
/// FIFO order, no duplicates, nothing lost.
#[kani::proof]
#[kani::unwind(7)]
fn behaves_like_a_fifo_without_duplicates() {
const CAP: usize = 3;
let mut q: WaitQueue<CAP> = WaitQueue::new();
let mut model = [0u16; CAP];
let mut len = 0usize;
for _ in 0..5 {
let t: u16 = kani::any::<u16>() % 4;
match kani::any::<u8>() % 3 {
0 => {
let ok = q.push(ThreadId::from_raw(t)).is_some();
let dup = model[..len].contains(&t);
assert!(ok == (!dup && len < CAP));
if ok {
model[len] = t;
len += 1;
}
}
1 => {
let got = q.pop();
if len == 0 {
assert!(got.is_none());
} else {
assert!(got == Some(ThreadId::from_raw(model[0])));
for j in 0..len - 1 {
model[j] = model[j + 1];
}
len -= 1;
}
}
_ => {
let removed = q.remove_thread(ThreadId::from_raw(t));
match model[..len].iter().position(|&m| m == t) {
Some(i) => {
assert!(removed);
for j in i..len - 1 {
model[j] = model[j + 1];
}
len -= 1;
}
None => assert!(!removed),
}
}
}
assert!(q.len == len);
assert!(q.is_empty() == (len == 0));
}
}
}

View File

@@ -33,4 +33,9 @@ pub enum KernelError {
InvalidRegion, InvalidRegion,
/// The object a capability would refer to no longer exists. /// The object a capability would refer to no longer exists.
NoSuchObject, NoSuchObject,
/// The capability names the wrong kind of object for this operation.
WrongObject,
/// `Reply` with an empty reply slot, or a call whose reply was dropped.
NoReply,
InvalidArgument,
} }

View File

@@ -1,6 +1,8 @@
use types::id::{IrqId, ThreadId}; use types::{
badge::Badge,
use crate::error::KernelError; generation::Generation,
id::{IrqId, NotificationId},
};
#[cfg(test)] #[cfg(test)]
#[path = "tests.rs"] #[path = "tests.rs"]
@@ -8,47 +10,47 @@ mod tests;
const MAX_IRQS: usize = 256; const MAX_IRQS: usize = 256;
/// Where an interrupt is delivered: a signal on a notification, through a
/// capability snapshot (generation and badge) taken when the route was set.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IrqRoute {
pub notification: NotificationId,
pub generation: Generation,
pub badge: Badge,
}
pub struct IrqRoutingTable { pub struct IrqRoutingTable {
owners: [Option<ThreadId>; MAX_IRQS], routes: [Option<IrqRoute>; MAX_IRQS],
} }
impl IrqRoutingTable { impl IrqRoutingTable {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
owners: [None; MAX_IRQS], routes: [None; MAX_IRQS],
} }
} }
pub fn register(&mut self, irq: IrqId, thread: ThreadId) -> Result<(), KernelError> { /// Routes `irq`, replacing any previous route. Fails for lines beyond
let slot = self /// the table.
.owners pub fn set(&mut self, irq: IrqId, route: IrqRoute) -> bool {
.get_mut(irq.raw() as usize) match self.routes.get_mut(irq.raw() as usize) {
.ok_or(KernelError::InvalidIrq(irq))?; Some(slot) => {
if slot.is_some() { *slot = Some(route);
return Err(KernelError::IrqAlreadyOwned(irq)); true
}
None => false,
} }
*slot = Some(thread);
Ok(())
} }
pub fn unregister(&mut self, irq: IrqId) { pub fn clear(&mut self, irq: IrqId) {
if let Some(slot) = self.owners.get_mut(irq.raw() as usize) { if let Some(slot) = self.routes.get_mut(irq.raw() as usize) {
*slot = None; *slot = None;
} }
} }
/// Drops every route to `thread`, e.g. when it dies. /// The route for `irq`; `None` for unknown or unrouted lines.
pub fn unregister_thread(&mut self, thread: ThreadId) { pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
for slot in &mut self.owners { *self.routes.get(irq.raw() as usize)?
if *slot == Some(thread) {
*slot = None;
}
}
}
/// The thread an interrupt is routed to; `None` for unknown lines.
pub fn owner(&self, irq: IrqId) -> Option<ThreadId> {
*self.owners.get(irq.raw() as usize)?
} }
} }

View File

@@ -1,50 +1,43 @@
use super::*; use super::*;
use types::id::{IrqId, ThreadId}; use types::badge::Badge;
use types::generation::Generation;
use types::id::{IrqId, NotificationId};
fn irq(id: u16) -> IrqId { fn route(n: u16) -> IrqRoute {
IrqId::from_raw(id) IrqRoute {
} notification: NotificationId::from_raw(n),
generation: Generation::initial(),
fn thread(id: u16) -> ThreadId { badge: Badge::new(1 << n),
ThreadId::from_raw(id) }
} }
#[test] #[test]
fn new_table_has_no_owners() { fn new_table_routes_nothing() {
let table = IrqRoutingTable::new(); let t = IrqRoutingTable::new();
assert!(table.owner(irq(0)).is_none()); assert_eq!(t.route(IrqId::from_raw(10)), None);
assert!(table.owner(irq(10)).is_none());
} }
#[test] #[test]
fn register_and_lookup() { fn set_then_route() {
let mut table = IrqRoutingTable::new(); let mut t = IrqRoutingTable::new();
table.register(irq(5), thread(1)).unwrap(); assert!(t.set(IrqId::from_raw(10), route(3)));
assert_eq!(table.owner(irq(5)), Some(thread(1))); assert_eq!(t.route(IrqId::from_raw(10)), Some(route(3)));
} }
#[test] #[test]
fn register_already_owned_fails() { fn set_replaces_and_clear_removes() {
let mut table = IrqRoutingTable::new(); let mut t = IrqRoutingTable::new();
table.register(irq(5), thread(1)).unwrap(); t.set(IrqId::from_raw(10), route(3));
assert!(table.register(irq(5), thread(2)).is_err()); t.set(IrqId::from_raw(10), route(4));
assert_eq!(t.route(IrqId::from_raw(10)), Some(route(4)));
t.clear(IrqId::from_raw(10));
assert_eq!(t.route(IrqId::from_raw(10)), None);
} }
#[test] #[test]
fn unregister_then_reregister() { fn lines_beyond_the_table_are_rejected_without_panicking() {
let mut table = IrqRoutingTable::new(); let mut t = IrqRoutingTable::new();
table.register(irq(5), thread(1)).unwrap(); assert!(!t.set(IrqId::from_raw(1000), route(1)));
table.unregister(irq(5)); assert_eq!(t.route(IrqId::from_raw(1000)), None);
assert!(table.owner(irq(5)).is_none()); t.clear(IrqId::from_raw(1000));
table.register(irq(5), thread(2)).unwrap();
assert_eq!(table.owner(irq(5)), Some(thread(2)));
}
#[test]
fn different_irqs_independent() {
let mut table = IrqRoutingTable::new();
table.register(irq(0), thread(10)).unwrap();
table.register(irq(1), thread(20)).unwrap();
assert_eq!(table.owner(irq(0)), Some(thread(10)));
assert_eq!(table.owner(irq(1)), Some(thread(20)));
} }

View File

@@ -11,6 +11,7 @@ pub mod generations;
pub mod irq; pub mod irq;
pub mod memory; pub mod memory;
pub mod message; pub mod message;
pub mod notification;
pub mod process; pub mod process;
pub mod resource; pub mod resource;
pub mod scheduler; pub mod scheduler;

View File

@@ -5,26 +5,26 @@ use types::bounded::CapSlot;
mod tests; mod tests;
pub const MAX_MESSAGE_WORDS: usize = 4; pub const MAX_MESSAGE_WORDS: usize = 4;
pub const MAX_CAP_TRANSFERS: usize = 4;
/// Four words and at most one capability (named by the sender's slot).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Message { pub struct Message {
words: [usize; MAX_MESSAGE_WORDS], words: [usize; MAX_MESSAGE_WORDS],
caps: [Option<CapSlot>; MAX_CAP_TRANSFERS], cap: Option<CapSlot>,
} }
impl Message { impl Message {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self::from_words([0; MAX_MESSAGE_WORDS])
words: [0; MAX_MESSAGE_WORDS],
caps: [None; MAX_CAP_TRANSFERS],
}
} }
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self { pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
Self { Self { words, cap: None }
words,
caps: [None; MAX_CAP_TRANSFERS],
} }
pub fn with_cap(mut self, slot: CapSlot) -> Self {
self.cap = Some(slot);
self
} }
pub fn words(&self) -> &[usize; MAX_MESSAGE_WORDS] { pub fn words(&self) -> &[usize; MAX_MESSAGE_WORDS] {
@@ -35,42 +35,7 @@ impl Message {
self.words[index] self.words[index]
} }
pub fn caps(&self) -> &[Option<CapSlot>; MAX_CAP_TRANSFERS] { pub fn cap(&self) -> Option<CapSlot> {
&self.caps self.cap
}
pub fn cap(&self, index: usize) -> Option<CapSlot> {
self.caps[index]
}
pub fn builder() -> MessageBuilder {
MessageBuilder {
message: Message::empty(),
}
}
}
pub struct MessageBuilder {
message: Message,
}
impl MessageBuilder {
pub fn word(mut self, index: usize, value: usize) -> Self {
self.message.words[index] = value;
self
}
pub fn words(mut self, words: [usize; MAX_MESSAGE_WORDS]) -> Self {
self.message.words = words;
self
}
pub fn cap(mut self, index: usize, slot: CapSlot) -> Self {
self.message.caps[index] = Some(slot);
self
}
pub fn build(self) -> Message {
self.message
} }
} }

View File

@@ -1,78 +1,23 @@
use super::*; use super::*;
use types::bounded::CapSlot; use types::bounded::CapSlot;
fn slot(n: u8) -> CapSlot {
CapSlot::new(n).unwrap()
}
// --- Construction ---
#[test] #[test]
fn empty_message_has_zeroed_words_and_no_caps() { fn empty_message_has_zeroed_words_and_no_cap() {
let msg = Message::empty(); let msg = Message::empty();
assert_eq!(msg.words(), &[0; MAX_MESSAGE_WORDS]); assert_eq!(msg.words(), &[0; MAX_MESSAGE_WORDS]);
for i in 0..MAX_CAP_TRANSFERS { assert!(msg.cap().is_none());
assert!(msg.cap(i).is_none());
}
} }
#[test] #[test]
fn from_words_sets_words_and_no_caps() { fn from_words_keeps_every_word() {
let msg = Message::from_words([10, 20, 30, 40]); let msg = Message::from_words([10, 20, 30, 40]);
assert_eq!(msg.word(0), 10); assert_eq!(msg.words(), &[10, 20, 30, 40]);
assert_eq!(msg.word(1), 20); assert!(msg.cap().is_none());
assert_eq!(msg.word(2), 30);
assert_eq!(msg.word(3), 40);
assert!(msg.cap(0).is_none());
}
// --- Accessors ---
#[test]
fn words_returns_full_array() {
let msg = Message::from_words([1, 2, 3, 4]);
assert_eq!(msg.words(), &[1, 2, 3, 4]);
} }
#[test] #[test]
fn caps_returns_full_array() { fn with_cap_attaches_one_capability() {
let msg = Message::empty(); let msg = Message::from_words([1, 2, 3, 4]).with_cap(CapSlot::new(5).unwrap());
assert_eq!(msg.caps(), &[None; MAX_CAP_TRANSFERS]); assert_eq!(msg.cap(), CapSlot::new(5));
} assert_eq!(msg.word(3), 4);
// --- Builder ---
#[test]
fn builder_sets_individual_words() {
let msg = Message::builder().word(0, 42).word(2, 99).build();
assert_eq!(msg.word(0), 42);
assert_eq!(msg.word(1), 0);
assert_eq!(msg.word(2), 99);
assert_eq!(msg.word(3), 0);
}
#[test]
fn builder_sets_caps() {
let msg = Message::builder().cap(0, slot(5)).cap(1, slot(10)).build();
assert_eq!(msg.cap(0), Some(slot(5)));
assert_eq!(msg.cap(1), Some(slot(10)));
assert!(msg.cap(2).is_none());
}
#[test]
fn builder_sets_words_and_caps_together() {
let msg = Message::builder()
.word(0, 100)
.word(1, 200)
.cap(0, slot(3))
.build();
assert_eq!(msg.word(0), 100);
assert_eq!(msg.word(1), 200);
assert_eq!(msg.cap(0), Some(slot(3)));
}
#[test]
fn builder_with_all_words() {
let msg = Message::builder().words([11, 22, 33, 44]).build();
assert_eq!(msg.words(), &[11, 22, 33, 44]);
} }

View File

@@ -0,0 +1,186 @@
//! 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()
}
}

View File

@@ -0,0 +1,43 @@
//! Signals are never lost: every signal either wakes exactly one thread
//! with its bits, or leaves those bits set for the next `Wait`.
use super::*;
use types::badge::Badge;
use types::id::{NotificationId, ProcessId, ThreadId};
#[kani::proof]
#[kani::unwind(5)]
fn a_signal_is_delivered_or_stored() {
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
n.bits = kani::any();
if kani::any() {
n.add_waiter(ThreadId::from_raw(kani::any::<u16>() % 4));
}
if kani::any() {
n.bind(ThreadId::from_raw(kani::any::<u16>() % 4));
}
let before = n.bits;
let badge = Badge::new(kani::any());
let expected = if badge.is_none() { 1 } else { badge.raw() };
match n.signal(badge, kani::any()) {
SignalEffect::WakeWaiter(_, bits) | SignalEffect::WakeBound(_, bits) => {
assert!(bits == expected);
assert!(n.bits == before, "woken signals are not also stored");
}
SignalEffect::Stored => {
assert!(n.bits == before | expected);
assert!(n.bits & expected == expected);
}
}
}
#[kani::proof]
#[kani::unwind(5)]
fn take_bits_returns_everything_and_clears() {
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
let bits: usize = kani::any();
n.bits = bits;
assert!(n.take_bits() == bits);
assert!(n.bits == 0);
}

View File

@@ -0,0 +1,116 @@
use super::*;
use types::badge::Badge;
use types::id::{NotificationId, ProcessId, ThreadId};
fn ntfn() -> Notification {
Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0))
}
fn t(n: u16) -> ThreadId {
ThreadId::from_raw(n)
}
#[test]
fn signal_with_nobody_waiting_stores_the_badge() {
let mut n = ntfn();
assert_eq!(n.signal(Badge::new(0b100), false), SignalEffect::Stored);
assert_eq!(n.signal(Badge::new(0b001), false), SignalEffect::Stored);
assert_eq!(n.take_bits(), 0b101);
assert_eq!(n.take_bits(), 0, "taking clears");
}
#[test]
fn unbadged_signal_sets_bit_zero() {
let mut n = ntfn();
n.signal(Badge::NONE, false);
assert_eq!(n.take_bits(), 1);
}
#[test]
fn signal_wakes_the_first_waiter_with_its_badge() {
let mut n = ntfn();
assert!(n.add_waiter(t(1)));
assert!(n.add_waiter(t(2)));
assert_eq!(
n.signal(Badge::new(8), false),
SignalEffect::WakeWaiter(t(1), 8)
);
assert_eq!(n.take_bits(), 0, "delivered bits are not also stored");
assert_eq!(
n.signal(Badge::new(4), false),
SignalEffect::WakeWaiter(t(2), 4)
);
}
#[test]
fn signal_wakes_a_receiving_bound_thread_when_no_waiter() {
let mut n = ntfn();
assert!(n.bind(t(5)));
assert_eq!(
n.signal(Badge::new(2), true),
SignalEffect::WakeBound(t(5), 2)
);
assert_eq!(n.signal(Badge::new(2), false), SignalEffect::Stored);
}
#[test]
fn waiters_take_priority_over_the_bound_thread() {
let mut n = ntfn();
n.bind(t(5));
n.add_waiter(t(1));
assert_eq!(
n.signal(Badge::new(2), true),
SignalEffect::WakeWaiter(t(1), 2)
);
}
#[test]
fn only_one_thread_can_be_bound() {
let mut n = ntfn();
assert!(n.bind(t(1)));
assert!(!n.bind(t(2)));
n.unbind(t(1));
assert!(n.bind(t(2)));
}
#[test]
fn removed_waiter_is_not_woken() {
let mut n = ntfn();
n.add_waiter(t(1));
n.remove_thread(t(1));
assert_eq!(n.signal(Badge::new(1), false), SignalEffect::Stored);
}
#[test]
fn remove_thread_also_unbinds() {
let mut n = ntfn();
n.bind(t(1));
n.remove_thread(t(1));
assert_eq!(n.bound(), None);
}
#[test]
fn table_generations_follow_slot_reuse() {
let mut table = NotificationTable::new();
let owner = ProcessId::from_raw(3);
let id = table.create(owner).unwrap();
let g = table.generation(id).unwrap();
assert!(table.destroy(id).is_some());
assert_eq!(table.generation(id), None);
let again = table.create(owner).unwrap();
assert_eq!(again, id);
assert!(!g.is_valid_against(&table.generation(again).unwrap()));
}
#[test]
fn table_lists_notifications_by_owner() {
let mut table = NotificationTable::new();
let a = table.create(ProcessId::from_raw(1)).unwrap();
table.create(ProcessId::from_raw(2)).unwrap();
let mut n = 0;
for id in table.ids_owned_by(ProcessId::from_raw(1)) {
assert_eq!(id, a);
n += 1;
}
assert_eq!(n, 1);
}

View File

@@ -22,7 +22,7 @@ fn dead_thread_rejects_all_transitions() {
assert!(matches!(thread.state(), ThreadState::Dead)); assert!(matches!(thread.state(), ThreadState::Dead));
assert!(thread.set_running(CoreId::from_raw(0)).is_err()); assert!(thread.set_running(CoreId::from_raw(0)).is_err());
assert!(thread.block(BlockReason::WaitingAny).is_err()); assert!(thread.block(BlockReason::AwaitingReply).is_err());
assert!(thread.unblock().is_err()); assert!(thread.unblock().is_err());
assert!(thread.preempt().is_err()); assert!(thread.preempt().is_err());
assert!(thread.exit().is_err()); assert!(thread.exit().is_err());
@@ -36,7 +36,7 @@ fn only_running_thread_can_block() {
let mut thread = test_thread(Priority::new(prio_val).unwrap()); let mut thread = test_thread(Priority::new(prio_val).unwrap());
assert!(matches!(thread.state(), ThreadState::Ready)); assert!(matches!(thread.state(), ThreadState::Ready));
assert!(thread.block(BlockReason::WaitingAny).is_err()); assert!(thread.block(BlockReason::AwaitingReply).is_err());
} }
#[kani::proof] #[kani::proof]
@@ -44,7 +44,7 @@ fn blocked_thread_cannot_run_directly() {
let mut thread = test_thread(Priority::new(16).unwrap()); let mut thread = test_thread(Priority::new(16).unwrap());
thread.set_running(CoreId::from_raw(0)).unwrap(); thread.set_running(CoreId::from_raw(0)).unwrap();
thread.block(BlockReason::WaitingAny).unwrap(); thread.block(BlockReason::AwaitingReply).unwrap();
assert!(thread.set_running(CoreId::from_raw(0)).is_err()); assert!(thread.set_running(CoreId::from_raw(0)).is_err());
} }

View File

@@ -1,9 +1,10 @@
use types::id::{EndpointId, IrqId, ProcessId, RegionId}; use types::id::{EndpointId, IrqId, NotificationId, ProcessId, RegionId};
#[derive(Clone)] #[derive(Clone)]
pub enum Resource { pub enum Resource {
Memory(RegionId), Memory(RegionId),
Endpoint(EndpointId), Endpoint(EndpointId),
Notification(NotificationId),
Process(ProcessId), Process(ProcessId),
Interrupt(IrqId), Interrupt(IrqId),
ProcessCreate, ProcessCreate,

View File

@@ -30,6 +30,13 @@ impl MultiCoreScheduler {
} }
} }
/// Whether `thread` waits in some core's run queue.
pub fn is_queued(&self, thread: ThreadId) -> bool {
self.cores[..self.num_cores as usize]
.iter()
.any(|c| c.is_queued(thread))
}
pub fn current(&self, core: CoreId) -> Option<ThreadId> { pub fn current(&self, core: CoreId) -> Option<ThreadId> {
self.cores[core.raw() as usize].current() self.cores[core.raw() as usize].current()
} }

View File

@@ -29,6 +29,10 @@ impl PerCoreScheduler {
self.queue.add(thread, priority); self.queue.add(thread, priority);
} }
pub fn is_queued(&self, thread: ThreadId) -> bool {
self.queue.contains(thread)
}
pub fn remove(&mut self, thread: ThreadId) { pub fn remove(&mut self, thread: ThreadId) {
self.queue.remove(thread); self.queue.remove(thread);
} }

View File

@@ -1,42 +1,12 @@
use types::{ use types::{
address::{DataRef, EntryPoint, StackPointer, VirtualAddress}, address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
badge::Badge,
bounded::{CapSlot, PageCount}, bounded::{CapSlot, PageCount},
permission::PermissionSet, permission::PermissionSet,
}; };
use crate::message::Message; use crate::message::Message;
pub struct WaitSet {
endpoints: [Option<CapSlot>; 4],
interrupts: [Option<CapSlot>; 4],
}
impl WaitSet {
pub fn new(endpoints: [Option<CapSlot>; 4], interrupts: [Option<CapSlot>; 4]) -> Self {
Self {
endpoints,
interrupts,
}
}
pub fn endpoints(&self) -> &[Option<CapSlot>; 4] {
&self.endpoints
}
pub fn interrupts(&self) -> &[Option<CapSlot>; 4] {
&self.interrupts
}
pub fn is_empty(&self) -> bool {
self.endpoints.iter().all(|s| s.is_none()) && self.interrupts.iter().all(|s| s.is_none())
}
}
pub enum WakeReason {
MessageReady(CapSlot),
InterruptFired(CapSlot),
}
pub enum Syscall { pub enum Syscall {
Send { Send {
endpoint: CapSlot, endpoint: CapSlot,
@@ -50,15 +20,47 @@ pub enum Syscall {
to_endpoint: CapSlot, to_endpoint: CapSlot,
permissions: PermissionSet, permissions: PermissionSet,
}, },
WaitForInterrupt { /// Send, then block for the reply (docs/ipc.md).
Call {
endpoint: CapSlot,
message: Message,
},
/// Answer the thread in the caller's reply slot.
Reply {
message: Message,
},
/// Reply (if a reply is pending), then receive.
ReplyRecv {
endpoint: CapSlot,
message: Message,
},
/// Copy an unbadged endpoint/notification cap with a badge and
/// narrower permissions into the caller's own table.
Mint {
slot: CapSlot,
badge: Badge,
permissions: PermissionSet,
},
CreateNotification,
Signal {
notification: CapSlot,
},
Wait {
notification: CapSlot,
},
Poll {
notification: CapSlot,
},
BindNotification {
notification: CapSlot,
},
SetIrqNotification {
irq: CapSlot, irq: CapSlot,
notification: CapSlot,
}, },
AcknowledgeInterrupt { AcknowledgeInterrupt {
irq: CapSlot, irq: CapSlot,
}, },
WaitAny {
sources: WaitSet,
},
CreateProcess { CreateProcess {
create_cap: CapSlot, create_cap: CapSlot,
image: DataRef, image: DataRef,

View File

@@ -1,10 +1,11 @@
use types::{ use types::{
bounded::{CapSlot, Priority}, bounded::Priority,
id::{CoreId, ProcessId, ThreadId}, generation::Generation,
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
time::TimeSlice, time::TimeSlice,
}; };
use crate::error::KernelError; use crate::{error::KernelError, generations::SlotGenerations};
#[cfg(test)] #[cfg(test)]
#[path = "tests.rs"] #[path = "tests.rs"]
@@ -80,10 +81,16 @@ impl SavedContext {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockReason { pub enum BlockReason {
Sending(CapSlot), /// Queued as a sender on the endpoint.
Receiving(CapSlot), Sending(EndpointId),
WaitingForInterrupt(CapSlot), /// Queued as a `Call`er on the endpoint; will then await the reply.
WaitingAny, 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 { 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> { pub fn unblock(&mut self) -> Result<(), KernelError> {
match self.state { match self.state {
ThreadState::Blocked(_) => { ThreadState::Blocked(_) => {
@@ -215,15 +233,28 @@ pub const MAX_THREADS: usize = 256;
pub struct ThreadTable { pub struct ThreadTable {
threads: [Option<Thread>; MAX_THREADS], threads: [Option<Thread>; MAX_THREADS],
generations: SlotGenerations<MAX_THREADS>,
} }
impl ThreadTable { impl ThreadTable {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
threads: [const { None }; MAX_THREADS], 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( pub fn spawn(
&mut self, &mut self,
process: ProcessId, process: ProcessId,
@@ -231,11 +262,7 @@ impl ThreadTable {
time_slice: TimeSlice, time_slice: TimeSlice,
core_affinity: Option<CoreId>, core_affinity: Option<CoreId>,
) -> Result<ThreadId, KernelError> { ) -> Result<ThreadId, KernelError> {
let idx = self let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
.threads
.iter()
.position(|s| s.is_none())
.ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16); let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::new( self.threads[idx] = Some(Thread::new(
@@ -256,11 +283,7 @@ impl ThreadTable {
core_affinity: Option<CoreId>, core_affinity: Option<CoreId>,
context: SavedContext, context: SavedContext,
) -> Result<ThreadId, KernelError> { ) -> Result<ThreadId, KernelError> {
let idx = self let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
.threads
.iter()
.position(|s| s.is_none())
.ok_or(KernelError::TooManyThreads)?;
let id = ThreadId::from_raw(idx as u16); let id = ThreadId::from_raw(idx as u16);
self.threads[idx] = Some(Thread::with_context( self.threads[idx] = Some(Thread::with_context(
@@ -292,10 +315,13 @@ impl ThreadTable {
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> { pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
let idx = id.raw() as usize; let idx = id.raw() as usize;
self.threads let thread = self
.threads
.get_mut(idx) .get_mut(idx)
.and_then(|s| s.take()) .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> { pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {

View File

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

View File

@@ -1,6 +1,6 @@
use domain::scheduler::{Scheduler, SchedulerDecision}; use domain::scheduler::{Scheduler, SchedulerDecision};
use platform::Platform; use platform::Platform;
use types::id::{CoreId, IrqId, ThreadId}; use types::id::CoreId;
use crate::Kernel; use crate::Kernel;
@@ -53,22 +53,4 @@ impl<P: Platform> Kernel<P> {
self.scheduler.tick(core, current_tid) self.scheduler.tick(core, current_tid)
} }
} }
pub fn handle_interrupt(&mut self, irq: IrqId) -> Option<ThreadId> {
self.platform.mask_irq(irq);
let tid = self.irqs.owner(irq)?;
let thread = self.threads.get_mut(tid).ok()?;
if !matches!(thread.state(), domain::thread::ThreadState::Blocked(_)) {
return None;
}
let priority = thread.priority();
let core = self.platform.core_id();
let _ = thread.unblock();
self.scheduler.add(tid, priority, core);
Some(tid)
}
} }

View File

@@ -18,11 +18,10 @@ use domain::{
error::KernelError, error::KernelError,
irq::IrqRoutingTable, irq::IrqRoutingTable,
memory::{RegionTable, frame::FrameTable, quota::Quota}, memory::{RegionTable, frame::FrameTable, quota::Quota},
message::Message, notification::{MAX_NOTIFICATIONS, NotificationTable},
process::{MAX_PROCESSES, ProcessTable}, process::{MAX_PROCESSES, ProcessTable},
resource::Resource, resource::Resource,
scheduler::{Scheduler, multi_core::MultiCoreScheduler}, scheduler::{Scheduler, multi_core::MultiCoreScheduler},
syscall::WaitSet,
thread::{MAX_THREADS, ThreadState, ThreadTable}, thread::{MAX_THREADS, ThreadState, ThreadTable},
}; };
use platform::Platform; use platform::Platform;
@@ -45,9 +44,9 @@ pub struct Kernel<P: Platform> {
quotas: [Quota; MAX_PROCESSES], quotas: [Quota; MAX_PROCESSES],
regions: RegionTable, regions: RegionTable,
irqs: IrqRoutingTable, irqs: IrqRoutingTable,
pending_messages: [Option<Message>; MAX_THREADS], notifications: NotificationTable,
ipc: [syscall::IpcState; MAX_THREADS],
pending_results: [Option<SyscallResult>; MAX_THREADS], pending_results: [Option<SyscallResult>; MAX_THREADS],
wait_sets: [Option<WaitSet>; MAX_THREADS],
} }
impl<P: Platform> Kernel<P> { impl<P: Platform> Kernel<P> {
@@ -63,9 +62,9 @@ impl<P: Platform> Kernel<P> {
quotas: [Quota::default(); MAX_PROCESSES], quotas: [Quota::default(); MAX_PROCESSES],
regions: RegionTable::new(), regions: RegionTable::new(),
irqs: IrqRoutingTable::new(), irqs: IrqRoutingTable::new(),
pending_messages: [const { None }; MAX_THREADS], notifications: NotificationTable::new(),
ipc: [syscall::IpcState::default(); MAX_THREADS],
pending_results: [const { None }; MAX_THREADS], pending_results: [const { None }; MAX_THREADS],
wait_sets: [const { None }; MAX_THREADS],
} }
} }
@@ -95,9 +94,9 @@ impl<P: Platform> Kernel<P> {
addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]); addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]);
addr_of_mut!((*slot).regions).write(RegionTable::new()); addr_of_mut!((*slot).regions).write(RegionTable::new());
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new()); addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
addr_of_mut!((*slot).pending_messages).write([const { None }; MAX_THREADS]); addr_of_mut!((*slot).notifications).write(NotificationTable::new());
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]); addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
addr_of_mut!((*slot).wait_sets).write([const { None }; MAX_THREADS]);
} }
} }
@@ -280,6 +279,7 @@ impl<P: Platform> Kernel<P> {
self.cleanup_threads(&thread_ids); self.cleanup_threads(&thread_ids);
self.destroy_endpoints_owned_by(pid); self.destroy_endpoints_owned_by(pid);
self.destroy_notifications_owned_by(pid);
self.teardown_memory(pid); self.teardown_memory(pid);
let _ = self.processes.destroy(pid); let _ = self.processes.destroy(pid);
@@ -289,10 +289,12 @@ impl<P: Platform> Kernel<P> {
use domain::scheduler::Scheduler; use domain::scheduler::Scheduler;
for tid in thread_ids.iter().flatten() { for tid in thread_ids.iter().flatten() {
self.endpoints.remove_thread_from_all(*tid); self.endpoints.remove_thread_from_all(*tid);
self.cleanup_wait_any(*tid); self.notifications.remove_thread_from_all(*tid);
self.irqs.unregister_thread(*tid); let ipc = core::mem::take(&mut self.ipc[tid.raw() as usize]);
if let Some(caller) = ipc.reply_to {
self.release_caller(caller);
}
self.scheduler.remove(*tid); self.scheduler.remove(*tid);
self.pending_messages[tid.raw() as usize] = None;
self.pending_results[tid.raw() as usize] = None; self.pending_results[tid.raw() as usize] = None;
let _ = self.threads.destroy(*tid); let _ = self.threads.destroy(*tid);
} }
@@ -315,28 +317,31 @@ impl<P: Platform> Kernel<P> {
} }
} }
/// Makes a blocked thread runnable with `error` as its syscall result. /// Destroys every notification `owner` created, waking its waiters
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) { /// with an error and unbinding its bound thread.
self.cleanup_wait_any(tid); pub(crate) fn destroy_notifications_owned_by(&mut self, owner: ProcessId) {
self.pending_messages[tid.raw() as usize] = None; let mut owned = [None; MAX_NOTIFICATIONS];
let Ok(thread) = self.threads.get_mut(tid) else { for (slot, id) in owned.iter_mut().zip(self.notifications.ids_owned_by(owner)) {
return; *slot = Some(id);
}; }
if !matches!(thread.state(), ThreadState::Blocked(_)) { for id in owned.into_iter().flatten() {
return; let Some(mut n) = self.notifications.destroy(id) else {
continue;
};
if let Some(bound) = n.bound() {
self.ipc[bound.raw() as usize].bound = None;
}
while let Some(tid) = n.pop_waiter() {
self.wake_with_error(tid, KernelError::NoSuchObject);
} }
let priority = thread.priority();
if thread.unblock().is_ok() {
self.pending_results[tid.raw() as usize] = Some(SyscallResult::Error(error));
let core = self.platform.core_id();
self.scheduler.add(tid, priority, core);
} }
} }
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) { /// Makes a blocked thread runnable with `error` as its syscall result,
if self.wait_sets[tid.raw() as usize].take().is_some() { /// dropping whatever it was about to send.
self.endpoints.remove_thread_from_all(tid); fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
} self.ipc[tid.raw() as usize].outgoing = None;
self.wake(tid, SyscallResult::Error(error));
} }
fn check_cap( fn check_cap(
@@ -362,6 +367,7 @@ impl<P: Platform> Kernel<P> {
match resource { match resource {
Resource::Endpoint(id) => self.endpoints.generation(*id), Resource::Endpoint(id) => self.endpoints.generation(*id),
Resource::Memory(id) => self.regions.generation(*id), Resource::Memory(id) => self.regions.generation(*id),
Resource::Notification(id) => self.notifications.generation(*id),
Resource::Process(pid) => self.processes.generation(*pid), Resource::Process(pid) => self.processes.generation(*pid),
Resource::Interrupt(_) | Resource::ProcessCreate => Some(Generation::initial()), Resource::Interrupt(_) | Resource::ProcessCreate => Some(Generation::initial()),
} }
@@ -371,6 +377,7 @@ impl<P: Platform> Kernel<P> {
match resource { match resource {
Resource::Endpoint(id) => self.endpoints.revoke(*id), Resource::Endpoint(id) => self.endpoints.revoke(*id),
Resource::Memory(id) => self.regions.revoke(*id), Resource::Memory(id) => self.regions.revoke(*id),
Resource::Notification(id) => self.notifications.revoke(*id),
_ => {} _ => {}
} }
} }

View File

@@ -11,14 +11,18 @@ impl<P: Platform> Kernel<P> {
to_endpoint: types::bounded::CapSlot, to_endpoint: types::bounded::CapSlot,
restrict: types::permission::PermissionSet, restrict: types::permission::PermissionSet,
) -> SyscallOutcome { ) -> SyscallOutcome {
let (resource, permissions) = // A copy of the capability itself, so its badge and generation
match self.check_cap(caller_pid, slot, types::permission::Permission::Delegate) { // travel with it; only the permissions narrow.
Ok(cap) => (cap.resource().clone(), *cap.permissions()), 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)), Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
}; };
let restricted = permissions.restrict_to(&restrict);
let target_pid = match self.check_cap( let target_pid = match self.check_cap(
caller_pid, caller_pid,
to_endpoint, to_endpoint,
@@ -38,7 +42,11 @@ impl<P: Platform> Kernel<P> {
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), 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)); return SyscallOutcome::Complete(SyscallResult::Error(e));
} }

View File

@@ -1,47 +1,42 @@
use domain::{fault::Fault, message::Message};
use platform::Platform; use platform::Platform;
use types::{id::ThreadId, permission::Permission};
use super::{Delivery, DeliveryKind, SyscallResult};
use crate::Kernel; use crate::Kernel;
impl<P: Platform> Kernel<P> { impl<P: Platform> Kernel<P> {
pub fn handle_fault( /// A thread faulted. Report it to the process's fault endpoint (if one
&mut self, /// is set, valid and has a receiver waiting), then destroy the process.
tid: types::id::ThreadId, pub fn handle_fault(&mut self, tid: ThreadId, fault: Fault, fault_pc: usize) {
fault: domain::fault::Fault, let Some(pid) = self.thread_process(tid) else {
fault_pc: usize, return;
) {
let pid = match self.threads.get(tid) {
Ok(t) => t.process(),
Err(_) => return,
}; };
let handler = self
// Try to deliver fault to a fault handler endpoint (if configured) .processes
if let Ok(process) = self.processes.get(pid) .get(pid)
&& let Some(handler_slot) = process.fault_handler() .ok()
&& let Ok(cap) = process.capabilities().get(handler_slot) .and_then(|p| p.fault_handler())
&& let domain::resource::Resource::Endpoint(ep_id) = cap.resource() .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([ let report =
pid.raw() as usize, Message::from_words([pid.raw() as usize, fault.code(), fault.address(), fault_pc]);
fault.code(), let delivery = Delivery {
fault.address(), kind: DeliveryKind::Message,
fault_pc, badge: badge.raw(),
]); words: *report.words(),
cap: None,
if let Some(ep) = self.endpoints.get_mut(*ep_id) };
&& ep.has_waiting_receiver() self.wake(receiver, SyscallResult::Received(delivery));
&& 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);
}
}
} }
self.destroy_process(pid); 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; 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> { 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, &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 { ) -> 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, Ok(id) => id,
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
}; };
let perms = PermissionSet::empty()
let all_perms = types::permission::PermissionSet::empty() .with(Permission::Read)
.with(types::permission::Permission::Read) .with(Permission::Execute)
.with(types::permission::Permission::Write) .with(Permission::Delegate);
.with(types::permission::Permission::Execute) match self.grant_cap(pid, Resource::Endpoint(id), perms) {
.with(types::permission::Permission::Delegate);
match self.grant_cap(
caller_pid,
domain::resource::Resource::Endpoint(ep_id),
all_perms,
) {
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)), Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
Err(e) => { Err(e) => {
let _ = self.endpoints.destroy(ep_id); self.endpoints.destroy(id);
SyscallOutcome::Complete(SyscallResult::Error(e)) SyscallOutcome::Complete(SyscallResult::Error(e))
} }
} }
} }
pub(crate) fn handle_send( // ---- 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, &mut self,
caller_tid: types::id::ThreadId, pid: ProcessId,
caller_pid: types::id::ProcessId, ) -> Result<CapSlot, KernelError> {
endpoint_slot: types::bounded::CapSlot, let id = self.create_notification(pid)?;
message: domain::message::Message, self.grant_cap(pid, Resource::Notification(id), rw_delegate())
) -> SyscallOutcome { .inspect_err(|_| {
use domain::scheduler::Scheduler; self.notifications.destroy(id);
})
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),
),
));
} }
},
/// 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)), Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
}; };
let Some(ntfn) = self.notifications.get_mut(id) else {
let endpoint = match self.endpoints.get_mut(ep_id) { return SyscallOutcome::Complete(SyscallResult::Error(KernelError::NoSuchObject));
Some(ep) => ep, };
None => { if ntfn.has_bits() {
return SyscallOutcome::Complete(SyscallResult::Error( let bits = ntfn.take_bits();
domain::error::KernelError::InvalidEndpoint(ep_id), return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
));
} }
}; if !ntfn.add_waiter(tid) {
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
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,
));
} }
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.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(())
}
// ---- 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) {
self.pending_results[receiver_tid.raw() as usize] = Ok(())
Some(SyscallResult::Message(message));
let core = self.platform.core_id();
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);
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)
} else { } else {
self.pending_messages[caller_tid.raw() as usize] = Some(message); Err(KernelError::InvalidIrq(irq))
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))
} }
} }
pub(crate) fn handle_receive( pub(crate) fn ack_interrupt(
&mut self, &mut self,
caller_tid: types::id::ThreadId, pid: ProcessId,
caller_pid: types::id::ProcessId, slot: CapSlot,
endpoint_slot: types::bounded::CapSlot, ) -> Result<(), KernelError> {
) -> SyscallOutcome { let irq = self.interrupt_cap(pid, slot)?;
use domain::scheduler::Scheduler; self.platform.unmask_irq(irq);
Ok(())
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)), /// 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
let endpoint = match self.endpoints.get_mut(ep_id) { // to a notification whose slot was reused, is dead.
Some(ep) => ep, if self.notifications.generation(route.notification) != Some(route.generation) {
None => { self.irqs.clear(irq);
return SyscallOutcome::Complete(SyscallResult::Error( return;
domain::error::KernelError::InvalidEndpoint(ep_id),
));
} }
}; self.signal_notification(route.notification, route.badge);
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))
}
}
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)),
};
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,
));
}
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)
} }
} }

View File

@@ -5,28 +5,95 @@ mod lifecycle;
mod memory; mod memory;
mod simple; mod simple;
use domain::{error::KernelError, message::Message, syscall::Syscall}; use domain::{error::KernelError, syscall::Syscall};
use platform::Platform; use platform::Platform;
use types::{bounded::CapSlot, id::ThreadId}; use types::{bounded::CapSlot, id::ThreadId, syscall_abi::ErrorCode};
use crate::Kernel; use crate::Kernel;
pub use ipc::{IpcState, ThreadRef};
pub enum SyscallOutcome { pub enum SyscallOutcome {
Complete(SyscallResult), Complete(SyscallResult),
BlockCaller(domain::thread::BlockReason), BlockCaller(domain::thread::BlockReason),
/// The caller blocked and `ThreadId` should run next.
SwitchTo(ThreadId), SwitchTo(ThreadId),
TerminateCaller, 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 { pub enum SyscallResult {
Ok, Ok,
Value(usize), Value(usize),
Slot(CapSlot), Slot(CapSlot),
Message(Message), Received(Delivery),
WakeReason(domain::syscall::WakeReason),
Error(KernelError), 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> { impl<P: Platform> Kernel<P> {
pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome { pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome {
let (tid, pid) = match self.caller() { let (tid, pid) = match self.caller() {
@@ -54,11 +121,45 @@ impl<P: Platform> Kernel<P> {
to_endpoint, to_endpoint,
permissions, permissions,
} => self.handle_delegate(pid, slot, to_endpoint, permissions), } => self.handle_delegate(pid, slot, to_endpoint, permissions),
Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message), Syscall::Mint {
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint), slot,
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq), badge,
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq), permissions,
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources), } => 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::Halt => self.handle_halt(pid),
Syscall::CreateEndpoint => self.handle_create_endpoint(pid), Syscall::CreateEndpoint => self.handle_create_endpoint(pid),
Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte), Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte),

View File

@@ -5,7 +5,7 @@ use platform::Platform;
use types::{ use types::{
address::PhysicalAddress, address::PhysicalAddress,
bounded::{CapSlot, Priority}, bounded::{CapSlot, Priority},
id::{EndpointId, IrqId, ProcessId, ThreadId}, id::{EndpointId, ProcessId, ThreadId},
permission::PermissionSet, permission::PermissionSet,
time::TimeSlice, time::TimeSlice,
}; };
@@ -41,10 +41,6 @@ impl Kernel<MockPlatform> {
tid tid
} }
pub fn register_irq_for_test(&mut self, irq: IrqId, thread: ThreadId) {
let _ = self.irqs.register(irq, thread);
}
pub fn block_thread_for_test(&mut self, tid: ThreadId, reason: domain::thread::BlockReason) { pub fn block_thread_for_test(&mut self, tid: ThreadId, reason: domain::thread::BlockReason) {
let core = self.platform.core_id(); let core = self.platform.core_id();
if let Ok(t) = self.threads.get_mut(tid) { if let Ok(t) = self.threads.get_mut(tid) {
@@ -128,6 +124,10 @@ impl Kernel<MockPlatform> {
} }
} }
pub fn destroy_process_notifications_for_test(&mut self, owner: ProcessId) {
self.destroy_notifications_owned_by(owner);
}
pub fn thread_state_for_test(&self, tid: ThreadId) -> Option<ThreadState> { pub fn thread_state_for_test(&self, tid: ThreadId) -> Option<ThreadState> {
self.threads.get(tid).ok().map(|t| *t.state()) self.threads.get(tid).ok().map(|t| *t.state())
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
//! Randomized check of the IPC state machine. Six threads in three
//! processes send, call, receive, reply and signal at random over two
//! endpoints and a notification; one process is periodically killed and
//! re-created. After every step the kernel's queues, block reasons, reply
//! slots and run queue must agree, and no message may be duplicated.
use super::*;
use crate::syscall::{Delivery, DeliveryKind};
use crate::{SyscallOutcome, SyscallResult};
use domain::message::Message;
use domain::resource::Resource;
use domain::syscall::Syscall;
use domain::thread::{BlockReason, ThreadState};
use types::bounded::CapSlot;
use types::id::{EndpointId, NotificationId, ThreadId};
use types::permission::{Permission, PermissionSet};
const STEPS: usize = 4000;
const THREADS_PER_PROC: usize = 2;
const PROCS: usize = 3;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, n: usize) -> usize {
(self.next() % n as u64) as usize
}
}
fn all_perms() -> PermissionSet {
PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Write)
.with(Permission::Execute)
.with(Permission::Delegate)
}
struct Proc {
pid: ProcessId,
threads: [ThreadId; THREADS_PER_PROC],
eps: [CapSlot; 2],
ntfn: CapSlot,
}
struct World {
k: Kernel<MockPlatform>,
procs: [Proc; PROCS],
eps: [EndpointId; 2],
ntfn: NotificationId,
next_id: usize,
delivered: [bool; 2 * STEPS + 16],
sent: [bool; 2 * STEPS + 16],
calls_answered: usize,
signals_seen: usize,
ran: usize,
rebirths: usize,
}
impl World {
fn new() -> Self {
let (mut k, p0) = kernel_with_process();
let t0 = k.current_thread().unwrap();
let p1 = k.create_process_internal().unwrap();
let p2 = k.create_process_internal().unwrap();
// Endpoints and the notification belong to init, which never dies.
let eps = [
k.create_endpoint(p0).unwrap(),
k.create_endpoint(p0).unwrap(),
];
let ntfn = k.create_notification(p0).unwrap();
let mut procs = [p0, p1, p2].map(|pid| Proc {
pid,
threads: [t0; THREADS_PER_PROC],
eps: [CapSlot::new(0).unwrap(); 2],
ntfn: CapSlot::new(0).unwrap(),
});
for (i, p) in procs.iter_mut().enumerate() {
Self::equip(&mut k, p, eps, ntfn, i == 0, t0);
}
Self {
k,
procs,
eps,
ntfn,
next_id: 1,
delivered: [false; 2 * STEPS + 16],
sent: [false; 2 * STEPS + 16],
calls_answered: 0,
signals_seen: 0,
ran: 0,
rebirths: 0,
}
}
fn equip(
k: &mut Kernel<MockPlatform>,
p: &mut Proc,
eps: [EndpointId; 2],
ntfn: NotificationId,
is_init: bool,
init_thread: ThreadId,
) {
for j in 0..THREADS_PER_PROC {
p.threads[j] = if is_init && j == 0 {
init_thread
} else {
k.spawn_thread_for_test(p.pid, Priority::DEFAULT, TimeSlice::DEFAULT)
};
}
for (e, ep) in eps.iter().enumerate() {
p.eps[e] = k.grant_cap_for_test(p.pid, Resource::Endpoint(*ep), all_perms());
}
p.ntfn = k.grant_cap_for_test(p.pid, Resource::Notification(ntfn), all_perms());
// The second thread of every process gets the notification bound.
k.switch_to_thread_for_test(p.threads[1]);
let _ = k.handle_syscall(Syscall::BindNotification {
notification: p.ntfn,
});
}
fn fresh_id(&mut self) -> usize {
let id = self.next_id;
self.next_id += 1;
self.sent[id] = true;
id
}
fn record(&mut self, d: &Delivery) {
match d.kind {
DeliveryKind::Signal => self.signals_seen += 1,
_ => {
let id = d.words[0];
assert!(
id < self.sent.len() && self.sent[id],
"delivered id {id} was never sent"
);
assert!(!self.delivered[id], "message {id} delivered twice");
self.delivered[id] = true;
}
}
}
fn record_outcome(&mut self, outcome: &SyscallOutcome) {
if let SyscallOutcome::Complete(SyscallResult::Received(d)) = outcome {
let d = *d;
self.record(&d);
}
}
fn runnable(&self) -> impl Iterator<Item = (usize, ThreadId)> + '_ {
self.procs.iter().enumerate().flat_map(move |(pi, p)| {
p.threads
.into_iter()
.filter(|&t| {
matches!(
self.k.thread_state_for_test(t),
Some(ThreadState::Ready | ThreadState::Running(_))
)
})
.map(move |t| (pi, t))
})
}
/// Kills process `pi` and brings it back with fresh threads and caps.
fn reincarnate(&mut self, pi: usize) {
self.k.destroy_process(self.procs[pi].pid);
self.procs[pi].pid = self.k.create_process_internal().unwrap();
let t0 = self.procs[0].threads[0];
Self::equip(
&mut self.k,
&mut self.procs[pi],
self.eps,
self.ntfn,
false,
t0,
);
self.rebirths += 1;
}
fn step(&mut self, rng: &mut Rng) {
let count = self.runnable().count();
if count == 0 || rng.below(100) == 0 {
// Random clients deadlock; killing someone is what breaks it.
self.reincarnate(1 + rng.below(PROCS - 1));
return;
}
let pick = rng.below(count);
let (pi, tid) = self.runnable().nth(pick).unwrap();
self.ran += 1;
self.k.switch_to_thread_for_test(tid);
// What the adapter would hand the thread when it resumes.
if let Some(SyscallResult::Received(d)) = self.k.take_pending_result(tid) {
self.record(&d);
}
let ep = self.procs[pi].eps[rng.below(2)];
let ntfn = self.procs[pi].ntfn;
let call = match rng.below(9) {
0 => Syscall::Send {
endpoint: ep,
message: Message::from_words([self.fresh_id(), 0, 0, 0]),
},
1 | 2 => Syscall::Call {
endpoint: ep,
message: Message::from_words([self.fresh_id(), 1, 0, 0]),
},
3 | 4 => Syscall::Receive { endpoint: ep },
5 => Syscall::Reply {
message: Message::from_words([self.fresh_id(), 2, 0, 0]),
},
6 => Syscall::ReplyRecv {
endpoint: ep,
message: Message::from_words([self.fresh_id(), 2, 0, 0]),
},
7 => Syscall::Signal { notification: ntfn },
_ => match rng.below(2) {
0 => Syscall::Wait { notification: ntfn },
_ => Syscall::Poll { notification: ntfn },
},
};
let is_reply = matches!(call, Syscall::Reply { .. } | Syscall::ReplyRecv { .. });
let had_reply = self.k.ipc[tid.raw() as usize].reply_to.is_some();
let outcome = self.k.handle_syscall(call);
if is_reply && had_reply {
self.calls_answered += 1;
}
self.record_outcome(&outcome);
}
fn check(&self) {
let k = &self.k;
for p in &self.procs {
for &t in &p.threads {
let Some(state) = k.thread_state_for_test(t) else {
continue;
};
let ipc = &k.ipc[t.raw() as usize];
let queued_on = |e: EndpointId| {
let ep = k.endpoints.get(e).unwrap();
(ep.is_sender(t), ep.is_receiver(t))
};
let in_any_queue = self.eps.iter().any(|&e| {
let (s, r) = queued_on(e);
s || r
}) || k.notifications.get(self.ntfn).unwrap().is_waiter(t);
match state {
ThreadState::Blocked(BlockReason::Sending(e) | BlockReason::Calling(e)) => {
assert_eq!(
queued_on(e),
(true, false),
"{t:?} blocked sending but not queued"
);
assert!(
ipc.outgoing.is_some(),
"{t:?} queued sender without a message"
);
}
ThreadState::Blocked(BlockReason::Receiving(e)) => {
assert_eq!(
queued_on(e),
(false, true),
"{t:?} blocked receiving but not queued"
);
}
ThreadState::Blocked(BlockReason::WaitingNotification(n)) => {
assert!(k.notifications.get(n).unwrap().is_waiter(t));
}
ThreadState::Blocked(BlockReason::AwaitingReply) => {
assert!(!in_any_queue, "{t:?} awaiting reply but still queued");
let me = crate::syscall::ThreadRef {
tid: t,
generation: k.threads.generation(t).unwrap(),
};
let holders = self
.procs
.iter()
.flat_map(|p| p.threads)
.filter(|&s| k.thread_state_for_test(s).is_some())
.filter(|s| k.ipc[s.raw() as usize].reply_to == Some(me))
.count();
assert_eq!(holders, 1, "{t:?} awaits a reply nobody can send");
}
ThreadState::Ready | ThreadState::Running(_) | ThreadState::Dead => {
assert!(!in_any_queue, "{t:?} runnable but queued on an IPC object");
assert!(ipc.outgoing.is_none());
}
}
let blocked = matches!(state, ThreadState::Blocked(_));
assert_eq!(
k.scheduler.is_queued(t),
matches!(state, ThreadState::Ready),
"{t:?} in state {state:?} has wrong run-queue membership"
);
if blocked {
assert_ne!(k.current_thread(), Some(t));
}
// A live reply slot always points at a thread waiting for it.
if let Some(r) = ipc.reply_to
&& k.threads.generation(r.tid) == Some(r.generation)
{
assert_eq!(
k.thread_state_for_test(r.tid),
Some(ThreadState::Blocked(BlockReason::AwaitingReply)),
"{t:?} holds a reply for {:?} which is not waiting",
r.tid
);
}
}
}
for &e in &self.eps {
let ep = k.endpoints.get(e).unwrap();
assert!(!(ep.has_waiting_sender() && ep.has_waiting_receiver()));
}
}
}
#[test]
fn ipc_state_machine_holds_under_random_operations() {
for seed in [3u64, 0xC0FFEE, 0xFEED_FACE, 99, 123_456_789] {
let mut world = World::new();
let mut rng = Rng(seed);
for _ in 0..STEPS {
world.step(&mut rng);
world.check();
}
assert!(
world.calls_answered > 20,
"seed {seed:#x}: too few replies: answered={} signals={} delivered={} ran={}",
world.calls_answered,
world.signals_seen,
world.delivered.iter().filter(|d| **d).count(),
world.ran
);
assert!(
world.rebirths > 5,
"seed {seed:#x}: kill/re-create barely exercised"
);
assert!(world.signals_seen > 20, "seed {seed:#x}: too few signals");
assert!(world.delivered.iter().filter(|d| **d).count() > 200);
}
}

View File

@@ -6,10 +6,10 @@ use crate::{SyscallOutcome, SyscallResult};
use domain::message::Message; use domain::message::Message;
use domain::resource::Resource; use domain::resource::Resource;
use domain::syscall::Syscall; use domain::syscall::Syscall;
use domain::thread::{BlockReason, ThreadState}; use domain::thread::ThreadState;
use types::address::DataRef; use types::address::DataRef;
use types::bounded::CapSlot; use types::bounded::CapSlot;
use types::id::{IrqId, ThreadId}; use types::id::ThreadId;
use types::permission::{Permission, PermissionSet}; use types::permission::{Permission, PermissionSet};
fn create_process(k: &mut Kernel<MockPlatform>) -> CapSlot { fn create_process(k: &mut Kernel<MockPlatform>) -> CapSlot {
@@ -177,57 +177,6 @@ fn endpoint_slots_are_reclaimed_when_processes_die() {
} }
} }
#[test]
fn interrupt_routing_does_not_survive_the_owning_thread() {
let (mut k, pid_a) = kernel_with_process();
let tid_a = k.current_thread().unwrap();
let cap_b = create_process(&mut k);
let pid_b = ProcessId::from_raw(1);
let tid_b = k.spawn_thread_for_test(pid_b, Priority::DEFAULT, TimeSlice::DEFAULT);
let irq = IrqId::from_raw(10);
k.register_irq_for_test(irq, tid_b);
k.switch_to_thread_for_test(tid_a);
k.handle_syscall(Syscall::Kill { process: cap_b });
// A new thread reuses B's thread id and happens to be blocked.
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
assert_eq!(reused, tid_b);
k.block_thread_for_test(reused, BlockReason::Receiving(CapSlot::new(1).unwrap()));
assert_eq!(
k.handle_interrupt(irq),
None,
"IRQ delivered to an unrelated thread"
);
}
#[test]
fn interrupt_numbers_beyond_the_table_are_ignored() {
let (mut k, _) = kernel_with_process();
// The PLIC can report sources up to 1023.
assert_eq!(k.handle_interrupt(IrqId::from_raw(1000)), None);
}
#[test]
fn exiting_a_thread_releases_its_interrupt_route() {
let (mut k, pid_a) = kernel_with_process();
let tid_a = k.current_thread().unwrap();
let irq = IrqId::from_raw(10);
k.register_irq_for_test(irq, tid_a);
k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
assert!(matches!(
k.handle_syscall(Syscall::ExitThread),
SyscallOutcome::TerminateCaller
));
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
assert_eq!(reused, tid_a);
k.block_thread_for_test(reused, BlockReason::Receiving(CapSlot::new(1).unwrap()));
assert_eq!(k.handle_interrupt(irq), None);
}
#[test] #[test]
fn drop_cap_frees_the_slot() { fn drop_cap_frees_the_slot() {
let (mut k, pid_a) = kernel_with_process(); let (mut k, pid_a) = kernel_with_process();
@@ -253,3 +202,43 @@ fn killing_yourself_terminates_the_caller() {
assert!(matches!(outcome, SyscallOutcome::TerminateCaller)); assert!(matches!(outcome, SyscallOutcome::TerminateCaller));
assert!(!k.process_exists(pid_a)); assert!(!k.process_exists(pid_a));
} }
#[test]
fn exiting_a_thread_unbinds_its_notification() {
let (mut k, pid_a) = kernel_with_process();
let tid_a = k.current_thread().unwrap();
let n = k.create_notification(pid_a).unwrap();
let rw = PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Write);
let ncap = k.grant_cap_for_test(pid_a, Resource::Notification(n), rw);
assert!(!is_error(&k.handle_syscall(Syscall::BindNotification {
notification: ncap
})));
let other = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
assert!(matches!(
k.handle_syscall(Syscall::ExitThread),
SyscallOutcome::TerminateCaller
));
// A new thread reuses the id and blocks in an unrelated receive.
let reused = k.spawn_thread_for_test(pid_a, Priority::DEFAULT, TimeSlice::DEFAULT);
assert_eq!(reused, tid_a);
let ep = k.create_endpoint_for_test();
let recv = k.grant_cap_for_test(pid_a, Resource::Endpoint(ep), ipc_perms());
k.switch_to_thread_for_test(reused);
assert!(matches!(
k.handle_syscall(Syscall::Receive { endpoint: recv }),
SyscallOutcome::BlockCaller(_)
));
k.switch_to_thread_for_test(other);
k.handle_syscall(Syscall::Signal { notification: ncap });
assert!(
matches!(
k.thread_state_for_test(reused),
Some(ThreadState::Blocked(_))
),
"signal reached a thread that never bound the notification"
);
}

View File

@@ -5,6 +5,7 @@ mod capability;
mod exit; mod exit;
mod invariants; mod invariants;
mod ipc; mod ipc;
mod ipc_invariants;
mod isolation; mod isolation;
mod lifecycle; mod lifecycle;
mod lifetimes; mod lifetimes;

View File

@@ -1,6 +1,6 @@
use super::*; use super::*;
use domain::scheduler::SchedulerDecision; use domain::scheduler::SchedulerDecision;
use types::id::{CoreId, IrqId, ThreadId}; use types::id::CoreId;
use types::time::TimeSlice; use types::time::TimeSlice;
#[test] #[test]
@@ -30,26 +30,3 @@ fn tick_preempts_when_time_expired_and_other_thread_ready() {
let d2 = k.handle_tick(CoreId::from_raw(0)); let d2 = k.handle_tick(CoreId::from_raw(0));
assert!(matches!(d2, SchedulerDecision::Preempt(_))); assert!(matches!(d2, SchedulerDecision::Preempt(_)));
} }
#[test]
fn interrupt_unknown_irq_returns_none() {
let (mut k, _) = kernel_with_process();
assert!(k.handle_interrupt(IrqId::from_raw(99)).is_none());
}
#[test]
fn interrupt_unblocks_registered_thread() {
let (mut k, _) = kernel_with_process();
let tid = ThreadId::from_raw(0);
let irq = IrqId::from_raw(5);
// Register and block the thread on this IRQ
k.register_irq_for_test(irq, tid);
k.block_thread_for_test(
tid,
domain::thread::BlockReason::WaitingForInterrupt(types::bounded::CapSlot::new(0).unwrap()),
);
let result = k.handle_interrupt(irq);
assert_eq!(result, Some(tid));
}

View File

@@ -80,13 +80,17 @@ impl AllocatorInner {
if prev.is_null() { if prev.is_null() {
self.free_list = new_block; self.free_list = new_block;
} else { } else {
unsafe { (*prev).next = new_block; } unsafe {
(*prev).next = new_block;
}
} }
} else { } else {
if prev.is_null() { if prev.is_null() {
self.free_list = block.next; self.free_list = block.next;
} else { } else {
unsafe { (*prev).next = block.next; } unsafe {
(*prev).next = block.next;
}
} }
} }

View File

@@ -1,27 +1,4 @@
pub fn syscall0(_nr: usize) -> usize { /// Host builds have no kernel; every call reports success with zeroes.
0 pub fn syscall(_nr: usize, _args: [usize; 6]) -> [usize; 8] {
} [0; 8]
pub fn syscall1(_nr: usize, _a0: usize) -> usize {
0
}
pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize {
0
}
pub fn syscall3(_nr: usize, _a0: usize, _a1: usize, _a2: usize) -> usize {
0
}
pub fn syscall4(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize) -> usize {
0
}
pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize {
0
}
pub fn syscall_receive(_nr: usize, _a0: usize) -> (usize, usize, usize, usize) {
(0, 0, 0, 0)
} }

View File

@@ -1,106 +1,20 @@
/// One `ecall`: syscall number in `a7`, arguments in `a0..a5`. Returns the
/// eight result registers `a0..a7` (docs/ipc.md, register ABI).
#[inline(always)] #[inline(always)]
pub fn syscall0(nr: usize) -> usize { pub fn syscall(nr: usize, args: [usize; 6]) -> [usize; 8] {
let ret: usize; let mut r = [0usize; 8];
unsafe { unsafe {
core::arch::asm!( core::arch::asm!(
"ecall", "ecall",
in("a7") nr, inlateout("a0") args[0] => r[0],
lateout("a0") ret, inlateout("a1") args[1] => r[1],
inlateout("a2") args[2] => r[2],
inlateout("a3") args[3] => r[3],
inlateout("a4") args[4] => r[4],
inlateout("a5") args[5] => r[5],
lateout("a6") r[6],
inlateout("a7") nr => r[7],
); );
} }
ret r
}
#[inline(always)]
pub fn syscall1(nr: usize, a0: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
);
}
ret
}
#[inline(always)]
pub fn syscall2(nr: usize, a0: usize, a1: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
);
}
ret
}
#[inline(always)]
pub fn syscall3(nr: usize, a0: usize, a1: usize, a2: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
);
}
ret
}
#[inline(always)]
pub fn syscall4(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
in("a3") a3,
);
}
ret
}
#[inline(always)]
pub fn syscall5(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
in("a3") a3,
in("a4") a4,
);
}
ret
}
#[inline(always)]
pub fn syscall_receive(nr: usize, a0: usize) -> (usize, usize, usize, usize) {
let r0: usize;
let r1: usize;
let r2: usize;
let r3: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => r0,
lateout("a1") r1,
lateout("a2") r2,
lateout("a3") r3,
);
}
(r0, r1, r2, r3)
} }

View File

@@ -1,13 +1,20 @@
use crate::syscall;
use types::bounded::CapSlot; use types::bounded::CapSlot;
use types::display::{Color, Dimension, ScreenCoord}; use types::display::{Color, Dimension, ScreenCoord};
use crate::syscall;
fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize, usize, usize) { fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize, usize, usize) {
syscall::send(ep, w0, w1, w2, w3); syscall::send(ep, w0, w1, w2, w3);
syscall::receive(ep) syscall::receive(ep)
} }
pub fn draw_rect(ep: CapSlot, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) { pub fn draw_rect(
ep: CapSlot,
x: ScreenCoord,
y: ScreenCoord,
w: Dimension,
h: Dimension,
color: Color,
) {
let xy = (x.raw() as usize) | ((y.raw() as usize) << 16); let xy = (x.raw() as usize) | ((y.raw() as usize) << 16);
let wh = (w.raw() as usize) | ((h.raw() as usize) << 16); let wh = (w.raw() as usize) | ((h.raw() as usize) << 16);
ipc(ep, 0, xy, wh, color.raw() as usize); ipc(ep, 0, xy, wh, color.raw() as usize);

View File

@@ -1,19 +1,24 @@
use types::bounded::CapSlot;
use crate::syscall; use crate::syscall;
use types::bounded::CapSlot;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileHandle(u16); pub struct FileHandle(u16);
impl FileHandle { impl FileHandle {
pub const fn raw(&self) -> u16 { self.0 } pub const fn raw(&self) -> u16 {
self.0
}
} }
fn pack_path(path: &[u8]) -> (usize, usize) { fn pack_path(path: &[u8]) -> (usize, usize) {
let mut w0 = [0u8; 8]; let mut w0 = [0u8; 8];
let mut w1 = [0u8; 8]; let mut w1 = [0u8; 8];
for (i, &b) in path.iter().enumerate() { for (i, &b) in path.iter().enumerate() {
if i < 8 { w0[i] = b; } if i < 8 {
else if i < 16 { w1[i - 8] = b; } w0[i] = b;
} else if i < 16 {
w1[i - 8] = b;
}
} }
(usize::from_le_bytes(w0), usize::from_le_bytes(w1)) (usize::from_le_bytes(w0), usize::from_le_bytes(w1))
} }
@@ -26,7 +31,11 @@ fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize
pub fn open(ep: CapSlot, path: &[u8]) -> Option<FileHandle> { pub fn open(ep: CapSlot, path: &[u8]) -> Option<FileHandle> {
let (p0, p1) = pack_path(path); let (p0, p1) = pack_path(path);
let (tag, handle, _, _) = ipc(ep, 0, p0, p1, 0); let (tag, handle, _, _) = ipc(ep, 0, p0, p1, 0);
if tag == 0 { Some(FileHandle(handle as u16)) } else { None } if tag == 0 {
Some(FileHandle(handle as u16))
} else {
None
}
} }
pub fn read(ep: CapSlot, handle: FileHandle, max_bytes: usize) -> Option<usize> { pub fn read(ep: CapSlot, handle: FileHandle, max_bytes: usize) -> Option<usize> {
@@ -52,7 +61,11 @@ pub fn list_dir(ep: CapSlot, path: &[u8]) -> Option<usize> {
pub fn create(ep: CapSlot, path: &[u8]) -> Option<FileHandle> { pub fn create(ep: CapSlot, path: &[u8]) -> Option<FileHandle> {
let (p0, p1) = pack_path(path); let (p0, p1) = pack_path(path);
let (tag, handle, _, _) = ipc(ep, 5, p0, p1, 0); let (tag, handle, _, _) = ipc(ep, 5, p0, p1, 0);
if tag == 0 { Some(FileHandle(handle as u16)) } else { None } if tag == 0 {
Some(FileHandle(handle as u16))
} else {
None
}
} }
pub fn delete(ep: CapSlot, path: &[u8]) -> bool { pub fn delete(ep: CapSlot, path: &[u8]) -> bool {

View File

@@ -1,5 +1,5 @@
use types::bounded::CapSlot;
use crate::syscall; use crate::syscall;
use types::bounded::CapSlot;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key { pub enum Key {

View File

@@ -2,14 +2,14 @@
extern crate alloc; extern crate alloc;
mod arch;
pub mod syscall;
pub mod alloc_impl; pub mod alloc_impl;
pub mod io; mod arch;
pub mod fs;
pub mod display; pub mod display;
pub mod fs;
pub mod input; pub mod input;
pub mod io;
mod rt; mod rt;
pub mod syscall;
#[cfg(target_os = "none")] #[cfg(target_os = "none")]
#[global_allocator] #[global_allocator]

View File

@@ -1,90 +1,335 @@
//! Thin wrappers over the kernel's register ABI (docs/ipc.md). Every call
//! returns a status in `a0`; results follow in `a1..`.
use crate::arch; use crate::arch;
use types::syscall_abi::*;
use types::address::{EntryPoint, StackPointer, VirtualAddress}; use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount}; use types::bounded::{CapSlot, PageCount};
use types::id::ProcessId; use types::id::ProcessId;
use types::permission::PermissionSet; use types::permission::PermissionSet;
use types::syscall_abi::*;
use types::time::Ticks; use types::time::Ticks;
pub use types::syscall_abi::ErrorCode as Error;
pub type Result<T> = core::result::Result<T, Error>;
fn raw(nr: usize, args: [usize; 6]) -> Result<[usize; 8]> {
let r = arch::syscall(nr, args);
match r[0] {
0 => Ok(r),
code => Err(Error::from_raw(code).unwrap_or(Error::InvalidArgument)),
}
}
fn call0(nr: usize) -> Result<[usize; 8]> {
raw(nr, [0; 6])
}
fn slot_arg(slot: Option<CapSlot>) -> usize {
slot.map_or(NONE, |s| s.value() as usize)
}
fn slot_result(r: usize) -> Option<CapSlot> {
if r == NONE {
None
} else {
CapSlot::new(r as u8)
}
}
fn slot_of(r: Result<[usize; 8]>) -> Result<CapSlot> {
r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument))
}
/// What a receiving call got.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Received {
/// A `Send`. `badge` identifies the sender's capability.
Message {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// A `Call`; answer it with `reply` or `reply_recv`.
Call {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// Bits from a notification.
Signal { bits: usize },
}
impl Received {
fn decode(r: [usize; 8]) -> Self {
let words = [r[2], r[3], r[4], r[5]];
let cap = slot_result(r[6]);
match r[7] {
KIND_CALL => Received::Call {
badge: r[1],
words,
cap,
},
KIND_SIGNAL => Received::Signal { bits: r[1] },
_ => Received::Message {
badge: r[1],
words,
cap,
},
}
}
pub fn words(&self) -> [usize; 4] {
match self {
Received::Message { words, .. } | Received::Call { words, .. } => *words,
Received::Signal { .. } => [0; 4],
}
}
}
fn message_args(ep: usize, words: [usize; 4], cap: Option<CapSlot>) -> [usize; 6] {
[ep, words[0], words[1], words[2], words[3], slot_arg(cap)]
}
// ---- threads, processes, time ----
pub fn exit() -> ! { pub fn exit() -> ! {
arch::syscall0(SYS_EXIT); let _ = call0(SYS_EXIT);
loop {} loop {}
} }
pub fn exit_thread() -> ! { pub fn exit_thread() -> ! {
arch::syscall0(SYS_EXIT_THREAD); let _ = call0(SYS_EXIT_THREAD);
loop {} loop {}
} }
pub fn yield_now() { pub fn yield_now() {
arch::syscall0(SYS_YIELD); let _ = call0(SYS_YIELD);
} }
pub fn halt() -> ! { pub fn halt() -> ! {
arch::syscall0(SYS_HALT); let _ = call0(SYS_HALT);
loop {} loop {}
} }
pub fn get_time() -> Ticks { pub fn get_time() -> Ticks {
Ticks::from_raw(arch::syscall0(SYS_GET_TIME) as u64) Ticks::from_raw(call0(SYS_GET_TIME).map_or(0, |r| r[1]) as u64)
} }
pub fn get_process_id() -> ProcessId { pub fn get_process_id() -> ProcessId {
ProcessId::from_raw(arch::syscall0(SYS_GET_PROCESS_ID) as u16) ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16)
} }
pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> { pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
let r = arch::syscall1(SYS_CREATE_PROCESS, create_cap.value() as usize); slot_of(raw(
if r == usize::MAX { None } else { CapSlot::new(r as u8) } SYS_CREATE_PROCESS,
} [create_cap.value() as usize, 0, 0, 0, 0, 0],
))
pub fn create_endpoint() -> Option<CapSlot> { .ok()
let r = arch::syscall0(SYS_CREATE_ENDPOINT);
if r == usize::MAX { None } else { CapSlot::new(r as u8) }
} }
pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize {
arch::syscall2(SYS_SPAWN_THREAD, entry.raw(), stack.raw()) raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1])
} }
pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize { pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize {
arch::syscall3(SYS_SPAWN_THREAD_IN, process_cap.value() as usize, entry.raw(), stack.raw()) raw(
} SYS_SPAWN_THREAD_IN,
[
pub fn request_pages(count: PageCount) -> Option<VirtualAddress> { process_cap.value() as usize,
let r = arch::syscall1(SYS_REQUEST_PAGES, count.value() as usize); entry.raw(),
if r == usize::MAX { None } else { Some(VirtualAddress::from_raw(r)) } stack.raw(),
} 0,
0,
pub fn map_page(process_cap: CapSlot, src_va: VirtualAddress, dst_va: VirtualAddress, executable: bool) -> bool { 0,
arch::syscall4(SYS_MAP_PAGE, process_cap.value() as usize, src_va.raw(), dst_va.raw(), executable as usize) == 0 ],
} )
.map_or(NONE, |r| r[1])
pub fn send(endpoint: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
arch::syscall5(SYS_SEND, endpoint.value() as usize, w0, w1, w2, w3)
}
pub fn receive(endpoint: CapSlot) -> (usize, usize, usize, usize) {
arch::syscall_receive(SYS_RECEIVE, endpoint.value() as usize)
}
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
arch::syscall3(SYS_DELEGATE, slot.value() as usize, to.value() as usize, permissions.bits() as usize) == 0
} }
pub fn kill(process_cap: CapSlot) -> bool { pub fn kill(process_cap: CapSlot) -> bool {
arch::syscall1(SYS_KILL, process_cap.value() as usize) == 0 raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
// ---- memory ----
pub fn request_pages(count: PageCount) -> Option<VirtualAddress> {
raw(SYS_REQUEST_PAGES, [count.value() as usize, 0, 0, 0, 0, 0])
.ok()
.map(|r| VirtualAddress::from_raw(r[1]))
}
pub fn map_page(
process_cap: CapSlot,
src_va: VirtualAddress,
dst_va: VirtualAddress,
executable: bool,
) -> bool {
raw(
SYS_MAP_PAGE,
[
process_cap.value() as usize,
src_va.raw(),
dst_va.raw(),
executable as usize,
0,
0,
],
)
.is_ok()
}
// ---- capabilities ----
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
raw(
SYS_DELEGATE,
[
slot.value() as usize,
to.value() as usize,
permissions.bits() as usize,
0,
0,
0,
],
)
.is_ok()
}
/// Copies an unbadged endpoint or notification capability with `badge`
/// and at most `permissions`.
pub fn mint(slot: CapSlot, badge: Badge, permissions: PermissionSet) -> Result<CapSlot> {
slot_of(raw(
SYS_MINT,
[
slot.value() as usize,
badge.raw(),
permissions.bits() as usize,
0,
0,
0,
],
))
} }
pub fn revoke(slot: CapSlot) -> bool { pub fn revoke(slot: CapSlot) -> bool {
arch::syscall1(SYS_REVOKE, slot.value() as usize) == 0 raw(SYS_REVOKE, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
} }
/// Removes a capability from this process's own table, freeing the slot. /// Removes a capability from this process's own table, freeing the slot.
pub fn drop_cap(slot: CapSlot) -> bool { pub fn drop_cap(slot: CapSlot) -> bool {
arch::syscall1(SYS_DROP_CAP, slot.value() as usize) == 0 raw(SYS_DROP_CAP, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
// ---- endpoints ----
pub fn create_endpoint() -> Option<CapSlot> {
slot_of(call0(SYS_CREATE_ENDPOINT)).ok()
}
/// Sends four words; returns 0 on success (kept for existing callers).
pub fn send(endpoint: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
match send_msg(endpoint, [w0, w1, w2, w3], None) {
Ok(()) => 0,
Err(e) => e as usize,
}
}
pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(
SYS_SEND,
message_args(endpoint.value() as usize, words, cap),
)
.map(|_| ())
}
/// Receives and returns just the words (kept for existing callers).
pub fn receive(endpoint: CapSlot) -> (usize, usize, usize, usize) {
let w = receive_msg(endpoint).map_or([0; 4], |r| r.words());
(w[0], w[1], w[2], w[3])
}
pub fn receive_msg(endpoint: CapSlot) -> Result<Received> {
raw(SYS_RECEIVE, [endpoint.value() as usize, 0, 0, 0, 0, 0]).map(Received::decode)
}
/// Sends and waits for the reply.
pub fn call(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_CALL,
message_args(endpoint.value() as usize, words, cap),
)
.map(Received::decode)
}
/// Answers the last `Call` this thread received.
pub fn reply(words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(SYS_REPLY, message_args(0, words, cap)).map(|_| ())
}
/// Replies to the pending `Call` (if any), then receives the next message.
pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_REPLY_RECV,
message_args(endpoint.value() as usize, words, cap),
)
.map(Received::decode)
}
// ---- notifications and interrupts ----
pub fn create_notification() -> Result<CapSlot> {
slot_of(call0(SYS_CREATE_NOTIFICATION))
}
pub fn signal(notification: CapSlot) -> Result<()> {
raw(SYS_SIGNAL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|_| ())
}
/// Blocks until the notification has bits, then returns and clears them.
pub fn wait(notification: CapSlot) -> Result<usize> {
raw(SYS_WAIT, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// Returns and clears the bits without blocking.
pub fn poll(notification: CapSlot) -> Result<usize> {
raw(SYS_POLL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// After this, `receive` also returns when the notification is signalled.
pub fn bind_notification(notification: CapSlot) -> Result<()> {
raw(
SYS_BIND_NOTIFICATION,
[notification.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
}
/// Delivers interrupt `irq` as a signal on `notification`.
pub fn set_irq_notification(irq: CapSlot, notification: CapSlot) -> Result<()> {
raw(
SYS_SET_IRQ_NOTIFICATION,
[
irq.value() as usize,
notification.value() as usize,
0,
0,
0,
0,
],
)
.map(|_| ())
}
pub fn ack_interrupt(irq: CapSlot) -> Result<()> {
raw(
SYS_ACKNOWLEDGE_INTERRUPT,
[irq.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
} }
pub fn debug_putchar(byte: u8) { pub fn debug_putchar(byte: u8) {
arch::syscall1(SYS_DEBUG_PUTCHAR, byte as usize); let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]);
} }

21
crates/types/src/badge.rs Normal file
View File

@@ -0,0 +1,21 @@
/// A word stamped on an endpoint or notification capability by `Mint`.
/// Receivers see the badge of the capability a sender used, which is how a
/// server tells its clients apart. Zero means "unbadged".
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Badge(usize);
impl Badge {
pub const NONE: Self = Self(0);
pub const fn new(value: usize) -> Self {
Self(value)
}
pub const fn raw(&self) -> usize {
self.0
}
pub const fn is_none(&self) -> bool {
self.0 == 0
}
}

View File

@@ -4,5 +4,6 @@ kernel_id!(ProcessId, u16);
kernel_id!(ThreadId, u16); kernel_id!(ThreadId, u16);
kernel_id!(EndpointId, u16); kernel_id!(EndpointId, u16);
kernel_id!(RegionId, u16); kernel_id!(RegionId, u16);
kernel_id!(NotificationId, u16);
kernel_id!(IrqId, u16); kernel_id!(IrqId, u16);
kernel_id!(CoreId, u8); kernel_id!(CoreId, u8);

View File

@@ -1,6 +1,7 @@
#![no_std] #![no_std]
pub mod address; pub mod address;
pub mod badge;
pub mod bounded; pub mod bounded;
pub mod display; pub mod display;
pub mod generation; pub mod generation;

View File

@@ -1,9 +1,12 @@
//! Register-level syscall contract shared by the kernel and libkos.
//! See docs/ipc.md for the meaning of each call.
pub const SYS_SEND: usize = 0; pub const SYS_SEND: usize = 0;
pub const SYS_RECEIVE: usize = 1; pub const SYS_RECEIVE: usize = 1;
pub const SYS_DELEGATE: usize = 2; pub const SYS_DELEGATE: usize = 2;
pub const SYS_WAIT_FOR_INTERRUPT: usize = 3; // 3 was WaitForInterrupt; interrupts are delivered through notifications.
pub const SYS_ACKNOWLEDGE_INTERRUPT: usize = 4; pub const SYS_ACKNOWLEDGE_INTERRUPT: usize = 4;
pub const SYS_WAIT_ANY: usize = 5; // 5 was WaitAny; superseded by notifications.
pub const SYS_CREATE_PROCESS: usize = 6; pub const SYS_CREATE_PROCESS: usize = 6;
pub const SYS_KILL: usize = 7; pub const SYS_KILL: usize = 7;
pub const SYS_REVOKE: usize = 8; pub const SYS_REVOKE: usize = 8;
@@ -21,3 +24,63 @@ pub const SYS_DEBUG_PUTCHAR: usize = 19;
pub const SYS_MAP_PAGE: usize = 20; pub const SYS_MAP_PAGE: usize = 20;
pub const SYS_SPAWN_THREAD_IN: usize = 21; pub const SYS_SPAWN_THREAD_IN: usize = 21;
pub const SYS_DROP_CAP: usize = 22; pub const SYS_DROP_CAP: usize = 22;
pub const SYS_CALL: usize = 23;
pub const SYS_REPLY: usize = 24;
pub const SYS_REPLY_RECV: usize = 25;
pub const SYS_MINT: usize = 26;
pub const SYS_CREATE_NOTIFICATION: usize = 27;
pub const SYS_SIGNAL: usize = 28;
pub const SYS_WAIT: usize = 29;
pub const SYS_POLL: usize = 30;
pub const SYS_BIND_NOTIFICATION: usize = 31;
pub const SYS_SET_IRQ_NOTIFICATION: usize = 32;
/// "No capability" in a capability argument or result register.
pub const NONE: usize = usize::MAX;
/// What a receiving syscall got, returned in `a7`.
pub const KIND_MESSAGE: usize = 0;
pub const KIND_CALL: usize = 1;
pub const KIND_SIGNAL: usize = 2;
/// Status returned in `a0`; `0` is success.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(usize)]
pub enum ErrorCode {
/// No capability in that slot, or it names a dead or revoked object.
InvalidCap = 1,
/// The capability lacks the permission the operation needs.
PermissionDenied = 2,
/// The capability names the wrong kind of object.
WrongObject = 3,
NoFreeSlot = 4,
OutOfMemory = 5,
InvalidAddress = 6,
AddressInUse = 7,
InvalidArgument = 8,
/// `Reply` with nothing to reply to, or a call whose reply was dropped.
NoReply = 9,
/// A table (threads, processes, queue) is full.
LimitReached = 10,
UnknownSyscall = 11,
}
impl ErrorCode {
pub fn from_raw(code: usize) -> Option<Self> {
use ErrorCode::*;
Some(match code {
1 => InvalidCap,
2 => PermissionDenied,
3 => WrongObject,
4 => NoFreeSlot,
5 => OutOfMemory,
6 => InvalidAddress,
7 => AddressInUse,
8 => InvalidArgument,
9 => NoReply,
10 => LimitReached,
11 => UnknownSyscall,
_ => return None,
})
}
}

105
docs/ipc.md Normal file
View File

@@ -0,0 +1,105 @@
# IPC
Synchronous message passing modelled on classic (non-MCS) seL4: endpoints
for rendezvous messages, notifications for asynchronous signals, an
implicit per-thread reply slot for `Call`. This document is the contract the
kernel tests and proofs check.
## Objects
**Endpoint.** A rendezvous point. Holds a FIFO of blocked senders and a
FIFO of blocked receivers; never both non-empty. Owned by its creator and
destroyed with it.
**Notification.** A word of signal bits plus a FIFO of blocked waiters and
at most one bound thread. Owned by its creator and destroyed with it.
Both are named by capabilities whose generation must match (see P2).
## Capabilities
A capability to an endpoint or notification carries a **badge** (a word;
`0` means unbadged). `Mint` copies an unbadged capability with a badge and
narrower permissions. A badge cannot be changed once set.
| Object | Permission | Allows |
|--------------|------------|--------------------------------------------|
| Endpoint | Execute | `Send`, `Call` |
| Endpoint | Read | `Receive`, `ReplyRecv` |
| Endpoint | Delegate | transfer a capability through it; `Mint` |
| Notification | Write | `Signal` |
| Notification | Read | `Wait`, `Poll`, `BindNotification` |
| Notification | Delegate | `Mint` |
| Interrupt | Execute | `SetIrqNotification`, `AckInterrupt` |
## Messages
A message is 4 words and at most one capability. The capability is
transferred only if the sender's endpoint capability has `Delegate` and the
transferred capability itself has `Delegate`; it is **copied** (the sender
keeps its own) with its permissions and badge unchanged. If the receiver's
table is full, or the capability is no longer valid at delivery time, the
message is delivered without it.
The receiver learns the badge of the capability the sender used. The kernel
never reveals the sender's process or thread id.
## Operations
- `Send(ep, msg)`: if a receiver waits, deliver and continue; otherwise block
until one arrives.
- `Receive(ep)`: if the caller has a bound notification with bits set,
return those bits (and clear them). Else if a sender waits, take its
message. Else block until a sender arrives or the bound notification is
signalled.
- `Call(ep, msg)`: like `Send`, but the caller then blocks awaiting a reply.
The receiving thread's reply slot now names the caller.
- `Reply(msg)`: deliver `msg` to the thread in the caller's reply slot and
clear the slot. Error if the slot is empty. Never blocks.
- `ReplyRecv(ep, msg)`: `Reply` if the slot is full, then `Receive(ep)`.
- `Signal(ntfn)`: let `b` be the capability's badge, or `1` if unbadged. If
a waiter is blocked, wake the first with `b`. Else if the bound thread is
blocked in `Receive`, wake it with `b`. Else `bits |= b`.
- `Wait(ntfn)`: if `bits != 0` return and clear them; else block.
- `Poll(ntfn)`: return and clear `bits` (possibly 0); never blocks.
- `BindNotification(ntfn)`: bind to the calling thread. One binding per
thread and per notification.
- `SetIrqNotification(irq, ntfn)`: deliver interrupt `irq` as `Signal`
through the given (badged) notification capability.
- `AckInterrupt(irq)`: unmask the line after handling.
An interrupt masks its line and signals its notification. If no route is
set it stays masked.
## Reply slot rules
- A server thread has at most one pending reply. `Receive` (not
`ReplyRecv`) with a pending reply drops it: the caller wakes with
`NoReply` instead of blocking forever.
- A reply slot names the caller by thread id **and generation**, so a reply
can never reach a different thread that reused the id.
- If the server thread dies, its pending caller wakes with `NoReply`.
- If the caller dies, the reply slot is simply stale and `Reply` is a
successful no-op.
## Death
Destroying an endpoint or notification wakes every thread blocked on it with
`InvalidCap`. Destroying a thread removes it from every queue, binding,
reply slot and IRQ route.
## Register ABI (RISC-V)
Inputs: `a7` = syscall number, arguments in `a0..a5`. A capability argument
of `NONE` (`usize::MAX`) means "no capability".
Outputs: `a0` = status (`0` = ok, else an `ErrorCode`), `a1..` = results.
Receiving operations (`Receive`, `Call`, `ReplyRecv`, `Wait`) return:
| Register | Value |
|----------|--------------------------------------------------------|
| a0 | status |
| a1 | badge (message) or signal bits (notification) |
| a2..a5 | message words 0..3 |
| a6 | received capability slot, or `NONE` |
| a7 | kind: 0 = message, 1 = call (reply expected), 2 = signal |