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:
@@ -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 => {
|
SYS_RECEIVE => Syscall::Receive { endpoint: slot(0)? },
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
SYS_REPLY => Syscall::Reply {
|
||||||
Some(Syscall::Receive { endpoint: slot })
|
message: message()?,
|
||||||
}
|
},
|
||||||
SYS_DELEGATE => {
|
SYS_REPLY_RECV => Syscall::ReplyRecv {
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
endpoint: slot(0)?,
|
||||||
let to = types::bounded::CapSlot::new(a1 as u8)?;
|
message: message()?,
|
||||||
let perms = types::permission::PermissionSet::from_bits(a2 as u8);
|
},
|
||||||
Some(Syscall::Delegate {
|
SYS_DELEGATE => Syscall::Delegate {
|
||||||
slot,
|
slot: slot(0)?,
|
||||||
to_endpoint: to,
|
to_endpoint: slot(1)?,
|
||||||
permissions: perms,
|
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
|
||||||
})
|
},
|
||||||
}
|
SYS_MINT => Syscall::Mint {
|
||||||
SYS_WAIT_FOR_INTERRUPT => {
|
slot: slot(0)?,
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
badge: Badge::new(a(1)),
|
||||||
Some(Syscall::WaitForInterrupt { irq: slot })
|
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
|
||||||
}
|
},
|
||||||
SYS_ACKNOWLEDGE_INTERRUPT => {
|
SYS_CREATE_NOTIFICATION => Syscall::CreateNotification,
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
SYS_SIGNAL => Syscall::Signal {
|
||||||
Some(Syscall::AcknowledgeInterrupt { irq: slot })
|
notification: slot(0)?,
|
||||||
}
|
},
|
||||||
SYS_WAIT_ANY => {
|
SYS_WAIT => Syscall::Wait {
|
||||||
let ep0 = if a0 != usize::MAX {
|
notification: slot(0)?,
|
||||||
types::bounded::CapSlot::new(a0 as u8)
|
},
|
||||||
} else {
|
SYS_POLL => Syscall::Poll {
|
||||||
None
|
notification: slot(0)?,
|
||||||
};
|
},
|
||||||
let ep1 = if a1 != usize::MAX {
|
SYS_BIND_NOTIFICATION => Syscall::BindNotification {
|
||||||
types::bounded::CapSlot::new(a1 as u8)
|
notification: slot(0)?,
|
||||||
} else {
|
},
|
||||||
None
|
SYS_SET_IRQ_NOTIFICATION => Syscall::SetIrqNotification {
|
||||||
};
|
irq: slot(0)?,
|
||||||
let ep2 = if a2 != usize::MAX {
|
notification: slot(1)?,
|
||||||
types::bounded::CapSlot::new(a2 as u8)
|
},
|
||||||
} else {
|
SYS_ACKNOWLEDGE_INTERRUPT => Syscall::AcknowledgeInterrupt { irq: slot(0)? },
|
||||||
None
|
SYS_CREATE_PROCESS => Syscall::CreateProcess {
|
||||||
};
|
create_cap: slot(0)?,
|
||||||
let ep3 = if a3 != usize::MAX {
|
image: DataRef::empty(),
|
||||||
types::bounded::CapSlot::new(a3 as u8)
|
},
|
||||||
} else {
|
SYS_KILL => Syscall::Kill { process: slot(0)? },
|
||||||
None
|
SYS_REVOKE => Syscall::Revoke { slot: slot(0)? },
|
||||||
};
|
SYS_DROP_CAP => Syscall::DropCap { slot: slot(0)? },
|
||||||
let sources = WaitSet::new([ep0, ep1, ep2, ep3], [None, None, None, None]);
|
SYS_SPAWN_THREAD => Syscall::SpawnThread {
|
||||||
Some(Syscall::WaitAny { sources })
|
entry: EntryPoint::from_raw(a(0)),
|
||||||
}
|
stack: StackPointer::from_raw(a(1)),
|
||||||
SYS_CREATE_PROCESS => {
|
},
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
SYS_REQUEST_PAGES => Syscall::RequestPages {
|
||||||
Some(Syscall::CreateProcess {
|
count: PageCount::new(u16::try_from(a(0)).ok()?)?,
|
||||||
create_cap: slot,
|
},
|
||||||
image: types::address::DataRef::empty(),
|
SYS_RELEASE_PAGES => Syscall::ReleasePages {
|
||||||
})
|
addr: VirtualAddress::from_raw(a(0)),
|
||||||
}
|
count: PageCount::new(u16::try_from(a(1)).ok()?)?,
|
||||||
SYS_KILL => {
|
},
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
SYS_SPAWN_THREAD_IN => Syscall::SpawnThreadIn {
|
||||||
Some(Syscall::Kill { process: slot })
|
process: slot(0)?,
|
||||||
}
|
entry: EntryPoint::from_raw(a(1)),
|
||||||
SYS_REVOKE => {
|
stack: StackPointer::from_raw(a(2)),
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
},
|
||||||
Some(Syscall::Revoke { slot })
|
SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint,
|
||||||
}
|
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a(0) as u8 },
|
||||||
SYS_SPAWN_THREAD => Some(Syscall::SpawnThread {
|
SYS_MAP_PAGE => Syscall::MapPage {
|
||||||
entry: types::address::EntryPoint::from_raw(a0),
|
process: slot(0)?,
|
||||||
stack: types::address::StackPointer::from_raw(a1),
|
src_va: VirtualAddress::from_raw(a(1)),
|
||||||
}),
|
dst_va: VirtualAddress::from_raw(a(2)),
|
||||||
SYS_REQUEST_PAGES => {
|
executable: a(3) != 0,
|
||||||
let count = types::bounded::PageCount::new(a0 as u16)?;
|
},
|
||||||
Some(Syscall::RequestPages { count })
|
_ => return None,
|
||||||
}
|
})
|
||||||
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 {
|
||||||
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
|
DeliveryKind::Message => KIND_MESSAGE,
|
||||||
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
|
DeliveryKind::Call => KIND_CALL,
|
||||||
},
|
DeliveryKind::Signal => KIND_SIGNAL,
|
||||||
SyscallOutcome::BlockCaller(_) => {}
|
};
|
||||||
SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0,
|
}
|
||||||
SyscallOutcome::TerminateCaller => {}
|
SyscallResult::Error(e) => r[10] = kernel::syscall::error_code(e) as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
186
crates/domain/src/notification/mod.rs
Normal file
186
crates/domain/src/notification/mod.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
43
crates/domain/src/notification/proofs.rs
Normal file
43
crates/domain/src/notification/proofs.rs
Normal 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);
|
||||||
|
}
|
||||||
116
crates/domain/src/notification/tests.rs
Normal file
116
crates/domain/src/notification/tests.rs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(_)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let priority = thread.priority();
|
for id in owned.into_iter().flatten() {
|
||||||
if thread.unblock().is_ok() {
|
let Some(mut n) = self.notifications.destroy(id) else {
|
||||||
self.pending_results[tid.raw() as usize] = Some(SyscallResult::Error(error));
|
continue;
|
||||||
let core = self.platform.core_id();
|
};
|
||||||
self.scheduler.add(tid, priority, core);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ 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) {
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Ok(cap) => domain::capability::Capability::new(
|
||||||
};
|
cap.resource().clone(),
|
||||||
|
cap.permissions().restrict_to(&restrict),
|
||||||
let restricted = permissions.restrict_to(&restrict);
|
*cap.generation(),
|
||||||
|
)
|
||||||
|
.with_badge(cap.badge()),
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
|
||||||
let target_pid = match self.check_cap(
|
let target_pid = match self.check_cap(
|
||||||
caller_pid,
|
caller_pid,
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 ----
|
||||||
&mut self,
|
|
||||||
caller_tid: types::id::ThreadId,
|
|
||||||
caller_pid: types::id::ProcessId,
|
|
||||||
endpoint_slot: types::bounded::CapSlot,
|
|
||||||
message: domain::message::Message,
|
|
||||||
) -> SyscallOutcome {
|
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
|
|
||||||
let ep_id = match self.check_cap(
|
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||||
caller_pid,
|
self.processes.get(owner)?;
|
||||||
endpoint_slot,
|
self.notifications
|
||||||
types::permission::Permission::Execute,
|
.create(owner)
|
||||||
) {
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
Ok(cap) => match cap.resource() {
|
}
|
||||||
domain::resource::Resource::Endpoint(id) => *id,
|
|
||||||
_ => {
|
pub(crate) fn create_notification_cap(
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
&mut self,
|
||||||
domain::error::KernelError::InvalidEndpoint(
|
pid: ProcessId,
|
||||||
types::id::EndpointId::from_raw(0),
|
) -> Result<CapSlot, KernelError> {
|
||||||
),
|
let id = self.create_notification(pid)?;
|
||||||
));
|
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||||
}
|
.inspect_err(|_| {
|
||||||
},
|
self.notifications.destroy(id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals `id` with `badge`; shared by `Signal` and interrupts.
|
||||||
|
fn signal_notification(&mut self, id: NotificationId, badge: Badge) {
|
||||||
|
let bound_receiving = self
|
||||||
|
.notifications
|
||||||
|
.get(id)
|
||||||
|
.and_then(|n| n.bound())
|
||||||
|
.is_some_and(|t| self.is_blocked_as(t, |r| matches!(r, BlockReason::Receiving(_))));
|
||||||
|
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match ntfn.signal(badge, bound_receiving) {
|
||||||
|
SignalEffect::WakeWaiter(t, bits) => {
|
||||||
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
SignalEffect::WakeBound(t, bits) => {
|
||||||
|
self.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 => {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
if ntfn.has_bits() {
|
||||||
|
let bits = ntfn.take_bits();
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
if !ntfn.add_waiter(tid) {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||||
|
}
|
||||||
|
self.block_caller(tid, BlockReason::WaitingNotification(id))
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint.has_waiting_receiver() {
|
pub(crate) fn poll(&mut self, pid: ProcessId, slot: CapSlot) -> Result<usize, KernelError> {
|
||||||
let receiver_tid = match endpoint.dequeue_receiver() {
|
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||||
Some(t) => t,
|
self.notifications
|
||||||
None => {
|
.get_mut(id)
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
.map(|n| n.take_bits())
|
||||||
domain::error::KernelError::WouldBlock,
|
.ok_or(KernelError::NoSuchObject)
|
||||||
));
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pending_results[receiver_tid.raw() as usize] =
|
pub(crate) fn bind_notification(
|
||||||
Some(SyscallResult::Message(message));
|
&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(())
|
||||||
|
}
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
// ---- interrupts ----
|
||||||
|
|
||||||
let receiver = match self.threads.get_mut(receiver_tid) {
|
fn interrupt_cap(&self, pid: ProcessId, slot: CapSlot) -> Result<IrqId, KernelError> {
|
||||||
Ok(t) => t,
|
match self.check_cap(pid, slot, Permission::Execute)?.resource() {
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Resource::Interrupt(irq) => Ok(*irq),
|
||||||
};
|
_ => Err(KernelError::WrongObject),
|
||||||
let _ = receiver.unblock();
|
}
|
||||||
let _ = receiver.set_running(core);
|
}
|
||||||
|
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
pub(crate) fn set_irq_notification(
|
||||||
Ok(t) => t,
|
&mut self,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
pid: ProcessId,
|
||||||
};
|
irq_slot: CapSlot,
|
||||||
let caller_priority = caller.priority();
|
ntfn_slot: CapSlot,
|
||||||
let _ = caller.preempt();
|
) -> Result<(), KernelError> {
|
||||||
self.scheduler.add(caller_tid, caller_priority, core);
|
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||||
|
let (notification, badge, generation) =
|
||||||
self.scheduler.set_current(core, receiver_tid);
|
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||||
|
let route = domain::irq::IrqRoute {
|
||||||
SyscallOutcome::SwitchTo(receiver_tid)
|
notification,
|
||||||
|
generation,
|
||||||
|
badge,
|
||||||
|
};
|
||||||
|
if self.irqs.set(irq, route) {
|
||||||
|
Ok(())
|
||||||
} 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)),
|
|
||||||
};
|
|
||||||
|
|
||||||
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.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(
|
/// An interrupt fired: mask the line and signal its notification. The
|
||||||
&mut self,
|
/// line stays masked until the handler acknowledges it.
|
||||||
caller_tid: types::id::ThreadId,
|
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||||
caller_pid: types::id::ProcessId,
|
self.platform.mask_irq(irq);
|
||||||
irq_slot: types::bounded::CapSlot,
|
let Some(route) = self.irqs.route(irq) else {
|
||||||
) -> SyscallOutcome {
|
return;
|
||||||
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)),
|
|
||||||
};
|
};
|
||||||
|
// A route set through a capability that has since been revoked, or
|
||||||
let _ = self.irqs.register(irq_id, caller_tid);
|
// to a notification whose slot was reused, is dead.
|
||||||
|
if self.notifications.generation(route.notification) != Some(route.generation) {
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
self.irqs.clear(irq);
|
||||||
Ok(t) => t,
|
return;
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
self.signal_notification(route.notification, route.badge);
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
350
crates/kernel/src/tests/ipc_invariants.rs
Normal file
350
crates/kernel/src/tests/ipc_invariants.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
21
crates/types/src/badge.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
105
docs/ipc.md
Normal 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 |
|
||||||
Reference in New Issue
Block a user