Files
k-os/crates/kernel/src/tests/ipc.rs

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(_))
));
}