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