228 lines
6.8 KiB
Rust
228 lines
6.8 KiB
Rust
use super::*;
|
|
use crate::{SyscallOutcome, SyscallResult};
|
|
use domain::message::Message;
|
|
use domain::syscall::{Syscall, WaitSet, WakeReason};
|
|
use domain::thread::BlockReason;
|
|
use types::bounded::CapSlot;
|
|
use types::id::{IrqId, ThreadId};
|
|
use types::permission::{Permission, PermissionSet};
|
|
|
|
fn setup_ipc_kernel() -> (
|
|
crate::Kernel<MockPlatform>,
|
|
ProcessId, // process A
|
|
ProcessId, // process B
|
|
CapSlot, // endpoint cap for A (Execute perm)
|
|
CapSlot, // endpoint cap for B (Read perm)
|
|
ThreadId, // thread B id
|
|
) {
|
|
let (mut k, pid_a) = kernel_with_process();
|
|
|
|
// Create process B (CreateProcess no longer spawns threads)
|
|
let _proc_b_slot = match k.handle_syscall(Syscall::CreateProcess {
|
|
create_cap: CapSlot::new(0).unwrap(),
|
|
image: types::address::DataRef::empty(),
|
|
}) {
|
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
|
_ => panic!("expected slot"),
|
|
};
|
|
|
|
let pid_b = ProcessId::from_raw(1);
|
|
let tid_b = k.spawn_thread_for_test(pid_b, Priority::DEFAULT, TimeSlice::DEFAULT);
|
|
|
|
// Create an endpoint
|
|
let ep_id = k.create_endpoint_for_test();
|
|
|
|
// Grant endpoint caps to both processes
|
|
let cap_a = k.grant_cap_for_test(
|
|
pid_a,
|
|
domain::resource::Resource::Endpoint(ep_id),
|
|
PermissionSet::empty()
|
|
.with(Permission::Execute)
|
|
.with(Permission::Read),
|
|
);
|
|
|
|
let cap_b = k.grant_cap_for_test(
|
|
pid_b,
|
|
domain::resource::Resource::Endpoint(ep_id),
|
|
PermissionSet::empty()
|
|
.with(Permission::Execute)
|
|
.with(Permission::Read),
|
|
);
|
|
|
|
(k, pid_a, pid_b, cap_a, cap_b, tid_b)
|
|
}
|
|
|
|
#[test]
|
|
fn send_without_receiver_blocks_caller() {
|
|
let (mut k, _, _, cap_a, _, _) = setup_ipc_kernel();
|
|
|
|
let msg = Message::from_words([42, 0, 0, 0]);
|
|
let outcome = k.handle_syscall(Syscall::Send {
|
|
endpoint: cap_a,
|
|
message: msg,
|
|
});
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::BlockCaller(BlockReason::Sending(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn receive_without_sender_blocks_caller() {
|
|
let (mut k, _, pid_b, _, cap_b, tid_b) = setup_ipc_kernel();
|
|
|
|
// Switch to thread B to call Receive
|
|
k.switch_to_thread_for_test(tid_b);
|
|
|
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_b });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::BlockCaller(BlockReason::Receiving(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn send_with_waiting_receiver_delivers_and_switches() {
|
|
let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
|
let tid_a = ThreadId::from_raw(0);
|
|
|
|
// Thread B calls Receive first (blocks)
|
|
k.switch_to_thread_for_test(tid_b);
|
|
k.handle_syscall(Syscall::Receive { endpoint: cap_b });
|
|
|
|
// Thread A sends
|
|
k.switch_to_thread_for_test(tid_a);
|
|
let msg = Message::from_words([42, 0, 0, 0]);
|
|
let outcome = k.handle_syscall(Syscall::Send {
|
|
endpoint: cap_a,
|
|
message: msg,
|
|
});
|
|
|
|
// Should switch to the receiver
|
|
assert!(matches!(outcome, SyscallOutcome::SwitchTo(t) if t == tid_b));
|
|
|
|
// Receiver should have pending message
|
|
let result = k.pending_result(tid_b);
|
|
assert!(result.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn receive_with_waiting_sender_delivers_immediately() {
|
|
let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
|
let tid_a = ThreadId::from_raw(0);
|
|
|
|
// Thread A sends first (blocks)
|
|
let msg = Message::from_words([99, 0, 0, 0]);
|
|
k.handle_syscall(Syscall::Send {
|
|
endpoint: cap_a,
|
|
message: msg,
|
|
});
|
|
|
|
// Thread B calls Receive (should get message immediately)
|
|
k.switch_to_thread_for_test(tid_b);
|
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_b });
|
|
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::Complete(SyscallResult::Message(m)) if m.word(0) == 99
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn send_checks_endpoint_capability() {
|
|
let (mut k, _, _, _, _, _) = setup_ipc_kernel();
|
|
// slot 10 doesn't exist
|
|
let outcome = k.handle_syscall(Syscall::Send {
|
|
endpoint: CapSlot::new(10).unwrap(),
|
|
message: Message::empty(),
|
|
});
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
|
));
|
|
}
|
|
|
|
// --- WaitForInterrupt / AcknowledgeInterrupt ---
|
|
|
|
#[test]
|
|
fn wait_for_interrupt_blocks_caller() {
|
|
let (mut k, pid_a, _, _, _, _) = setup_ipc_kernel();
|
|
let irq = IrqId::from_raw(7);
|
|
let irq_slot = k.grant_cap_for_test(
|
|
pid_a,
|
|
domain::resource::Resource::Interrupt(irq),
|
|
PermissionSet::empty()
|
|
.with(Permission::Read)
|
|
.with(Permission::Write),
|
|
);
|
|
|
|
let outcome = k.handle_syscall(Syscall::WaitForInterrupt { irq: irq_slot });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::BlockCaller(BlockReason::WaitingForInterrupt(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn ack_interrupt_returns_ok() {
|
|
let (mut k, pid_a, _, _, _, _) = setup_ipc_kernel();
|
|
let irq = IrqId::from_raw(7);
|
|
let irq_slot = k.grant_cap_for_test(
|
|
pid_a,
|
|
domain::resource::Resource::Interrupt(irq),
|
|
PermissionSet::empty().with(Permission::Write),
|
|
);
|
|
|
|
let outcome = k.handle_syscall(Syscall::AcknowledgeInterrupt { irq: irq_slot });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
|
));
|
|
}
|
|
|
|
// --- WaitAny ---
|
|
|
|
#[test]
|
|
fn wait_any_with_ready_source_returns_immediately() {
|
|
let (mut k, pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel();
|
|
let tid_a = ThreadId::from_raw(0);
|
|
|
|
// Thread B sends a message (blocks since no receiver)
|
|
k.switch_to_thread_for_test(tid_b);
|
|
k.handle_syscall(Syscall::Send {
|
|
endpoint: cap_b,
|
|
message: Message::from_words([77, 0, 0, 0]),
|
|
});
|
|
|
|
// Thread A does WaitAny on the endpoint
|
|
k.switch_to_thread_for_test(tid_a);
|
|
let wait_set = WaitSet::new([Some(cap_a), None, None, None], [None, None, None, None]);
|
|
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::Complete(SyscallResult::WakeReason(WakeReason::MessageReady(_)))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn wait_any_with_no_ready_source_blocks() {
|
|
let (mut k, pid_a, _, cap_a, _, _) = setup_ipc_kernel();
|
|
let wait_set = WaitSet::new([Some(cap_a), None, None, None], [None, None, None, None]);
|
|
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::BlockCaller(BlockReason::WaitingAny)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn wait_any_empty_sources_returns_error() {
|
|
let (mut k, _, _, _, _, _) = setup_ipc_kernel();
|
|
let wait_set = WaitSet::new([None, None, None, None], [None, None, None, None]);
|
|
let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set });
|
|
assert!(matches!(
|
|
outcome,
|
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
|
));
|
|
}
|