diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index 76237b6..ac9e255 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -194,6 +194,32 @@ impl Kernel

{ self.scheduler.remove(tid); } + #[cfg(test)] + pub fn switch_to_thread_for_test(&mut self, tid: ThreadId) { + use domain::scheduler::Scheduler; + let core = self.platform.core_id(); + + if let Some(current) = self.scheduler.current(core) { + if current == tid { + return; + } + if let Ok(t) = self.threads.get_mut(current) { + if matches!(t.state(), ThreadState::Running(_)) { + let priority = t.priority(); + let _ = t.preempt(); + self.scheduler.add(current, priority, core); + } + } + } + + if let Ok(t) = self.threads.get(tid) { + if matches!(t.state(), ThreadState::Ready) { + self.scheduler.next(core); + let _ = self.threads.get_mut(tid).map(|t| t.set_running(core)); + } + } + } + #[cfg(test)] pub fn create_endpoint_for_test(&mut self) -> types::id::EndpointId { self.endpoints.create().expect("create endpoint") diff --git a/crates/kernel/src/syscall.rs b/crates/kernel/src/syscall.rs index 08bd83b..17324d5 100644 --- a/crates/kernel/src/syscall.rs +++ b/crates/kernel/src/syscall.rs @@ -24,6 +24,8 @@ impl Kernel

{ Syscall::Delegate { slot, to_endpoint, permissions } => { self.handle_delegate(pid, slot, to_endpoint, permissions) } + Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message), + Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint), _ => SyscallOutcome::Complete(SyscallResult::Error( domain::error::KernelError::WouldBlock, )), @@ -365,4 +367,161 @@ impl Kernel

{ Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)), } } + + fn handle_send( + &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( + caller_pid, + endpoint_slot, + types::permission::Permission::Execute, + ) { + 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_receiver() { + let receiver_tid = match endpoint.dequeue_receiver() { + Some(t) => t, + None => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::WouldBlock, + )), + }; + + self.pending_results[receiver_tid.raw() as usize] = + Some(SyscallResult::Message(message)); + + let receiver = match self.threads.get_mut(receiver_tid) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + let priority = receiver.priority(); + let _ = receiver.unblock(); + let core = self.platform.core_id(); + self.scheduler.add(receiver_tid, priority, core); + + let caller = match self.threads.get_mut(caller_tid) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + let caller_priority = caller.priority(); + let _ = caller.preempt(); + self.scheduler.add(caller_tid, caller_priority, core); + + self.scheduler.next(core); + let _ = self.threads.get_mut(receiver_tid).map(|t| t.set_running(core)); + + SyscallOutcome::SwitchTo(receiver_tid) + } else { + self.pending_messages[caller_tid.raw() as usize] = Some(message); + + let endpoint = match self.endpoints.get_mut(ep_id) { + Some(ep) => ep, + None => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::InvalidEndpoint(ep_id), + )), + }; + let _ = endpoint.enqueue_sender(caller_tid); + + 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)) + } + } + + fn handle_receive( + &mut self, + caller_tid: types::id::ThreadId, + caller_pid: types::id::ProcessId, + endpoint_slot: types::bounded::CapSlot, + ) -> SyscallOutcome { + use domain::scheduler::Scheduler; + + 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() + .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), + )), + }; + let _ = endpoint.enqueue_receiver(caller_tid); + + 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)) + } + } } diff --git a/crates/kernel/src/tests/ipc.rs b/crates/kernel/src/tests/ipc.rs new file mode 100644 index 0000000..70d6dea --- /dev/null +++ b/crates/kernel/src/tests/ipc.rs @@ -0,0 +1,140 @@ +use super::*; +use crate::{SyscallOutcome, SyscallResult}; +use domain::message::Message; +use domain::syscall::Syscall; +use domain::thread::BlockReason; +use types::bounded::CapSlot; +use types::id::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 + 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"), + }; + + // Get process B's pid from the cap + let pid_b = ProcessId::from_raw(1); + let tid_b = ThreadId::from_raw(1); + + // 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(_)) + )); +} diff --git a/crates/kernel/src/tests/mod.rs b/crates/kernel/src/tests/mod.rs index e7030e4..c309d3a 100644 --- a/crates/kernel/src/tests/mod.rs +++ b/crates/kernel/src/tests/mod.rs @@ -1,5 +1,6 @@ mod capability; mod exit; +mod ipc; mod lifecycle; mod memory; mod simple;