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:
@@ -6,10 +6,10 @@ use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::message::Message;
|
||||
use domain::resource::Resource;
|
||||
use domain::syscall::Syscall;
|
||||
use domain::thread::{BlockReason, ThreadState};
|
||||
use domain::thread::ThreadState;
|
||||
use types::address::DataRef;
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::{IrqId, ThreadId};
|
||||
use types::id::ThreadId;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
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]
|
||||
fn drop_cap_frees_the_slot() {
|
||||
let (mut k, pid_a) = kernel_with_process();
|
||||
@@ -253,3 +202,43 @@ fn killing_yourself_terminates_the_caller() {
|
||||
assert!(matches!(outcome, SyscallOutcome::TerminateCaller));
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user