feat(kernel): add WaitForInterrupt, AcknowledgeInterrupt, and WaitAny syscalls
This commit is contained in:
@@ -11,6 +11,28 @@ pub struct WaitSet {
|
||||
interrupts: [Option<CapSlot>; 4],
|
||||
}
|
||||
|
||||
impl WaitSet {
|
||||
pub fn new(
|
||||
endpoints: [Option<CapSlot>; 4],
|
||||
interrupts: [Option<CapSlot>; 4],
|
||||
) -> Self {
|
||||
Self { endpoints, interrupts }
|
||||
}
|
||||
|
||||
pub fn endpoints(&self) -> &[Option<CapSlot>; 4] {
|
||||
&self.endpoints
|
||||
}
|
||||
|
||||
pub fn interrupts(&self) -> &[Option<CapSlot>; 4] {
|
||||
&self.interrupts
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.endpoints.iter().all(|s| s.is_none())
|
||||
&& self.interrupts.iter().all(|s| s.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WakeReason {
|
||||
MessageReady(CapSlot),
|
||||
InterruptFired(CapSlot),
|
||||
|
||||
@@ -21,6 +21,7 @@ use domain::{
|
||||
process::{ProcessTable, MAX_PROCESSES},
|
||||
resource::Resource,
|
||||
scheduler::{multi_core::MultiCoreScheduler, Scheduler},
|
||||
syscall::WaitSet,
|
||||
thread::{ThreadTable, ThreadState, MAX_THREADS},
|
||||
};
|
||||
use platform::Platform;
|
||||
@@ -60,6 +61,7 @@ pub struct Kernel<P: Platform> {
|
||||
irqs: IrqRoutingTable,
|
||||
pending_messages: [Option<Message>; MAX_THREADS],
|
||||
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
||||
wait_sets: [Option<WaitSet>; MAX_THREADS],
|
||||
}
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
@@ -77,6 +79,7 @@ impl<P: Platform> Kernel<P> {
|
||||
irqs: IrqRoutingTable::new(),
|
||||
pending_messages: [const { None }; MAX_THREADS],
|
||||
pending_results: [const { None }; MAX_THREADS],
|
||||
wait_sets: [const { None }; MAX_THREADS],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ impl<P: Platform> Kernel<P> {
|
||||
}
|
||||
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,
|
||||
)),
|
||||
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
||||
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
||||
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,4 +524,141 @@ impl<P: Platform> Kernel<P> {
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Receiving(endpoint_slot))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_wait_for_interrupt(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
irq_slot: types::bounded::CapSlot,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
|
||||
let irq_id = match self.check_cap(
|
||||
caller_pid,
|
||||
irq_slot,
|
||||
types::permission::Permission::Read,
|
||||
) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Interrupt(id) => *id,
|
||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
||||
)),
|
||||
},
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
let _ = self.irqs.register(irq_id, 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::WaitingForInterrupt(irq_slot));
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingForInterrupt(irq_slot))
|
||||
}
|
||||
|
||||
fn handle_ack_interrupt(
|
||||
&mut self,
|
||||
caller_pid: types::id::ProcessId,
|
||||
irq_slot: types::bounded::CapSlot,
|
||||
) -> SyscallOutcome {
|
||||
let irq_id = match self.check_cap(
|
||||
caller_pid,
|
||||
irq_slot,
|
||||
types::permission::Permission::Write,
|
||||
) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Interrupt(id) => *id,
|
||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
||||
)),
|
||||
},
|
||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||
};
|
||||
|
||||
self.platform.unmask_irq(irq_id);
|
||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||
}
|
||||
|
||||
fn handle_wait_any(
|
||||
&mut self,
|
||||
caller_tid: types::id::ThreadId,
|
||||
caller_pid: types::id::ProcessId,
|
||||
sources: domain::syscall::WaitSet,
|
||||
) -> SyscallOutcome {
|
||||
use domain::scheduler::Scheduler;
|
||||
|
||||
if sources.is_empty() {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::WouldBlock,
|
||||
));
|
||||
}
|
||||
|
||||
for ep_slot in sources.endpoints() {
|
||||
if let Some(slot) = ep_slot {
|
||||
let ep_id = match self.check_cap(
|
||||
caller_pid,
|
||||
*slot,
|
||||
types::permission::Permission::Read,
|
||||
) {
|
||||
Ok(cap) => match cap.resource() {
|
||||
domain::resource::Resource::Endpoint(id) => *id,
|
||||
_ => continue,
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(ep_id) {
|
||||
if ep.has_waiting_sender() {
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::MessageReady(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for irq_slot in sources.interrupts() {
|
||||
if let Some(slot) = irq_slot {
|
||||
if let Ok(cap) = self.check_cap(
|
||||
caller_pid,
|
||||
*slot,
|
||||
types::permission::Permission::Read,
|
||||
) {
|
||||
if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() {
|
||||
if self.irqs.owner(*irq_id).is_some() {
|
||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ep_slot in sources.endpoints() {
|
||||
if let Some(slot) = ep_slot {
|
||||
if let Ok(cap) = self.check_cap(caller_pid, *slot, types::permission::Permission::Read) {
|
||||
if let domain::resource::Resource::Endpoint(id) = cap.resource() {
|
||||
if let Some(ep) = self.endpoints.get_mut(*id) {
|
||||
let _ = ep.enqueue_receiver(caller_tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.wait_sets[caller_tid.raw() as usize] = Some(sources);
|
||||
|
||||
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::WaitingAny);
|
||||
self.scheduler.remove(caller_tid);
|
||||
|
||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingAny)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::*;
|
||||
use crate::{SyscallOutcome, SyscallResult};
|
||||
use domain::message::Message;
|
||||
use domain::syscall::Syscall;
|
||||
use domain::syscall::{Syscall, WaitSet, WakeReason};
|
||||
use domain::thread::BlockReason;
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::ThreadId;
|
||||
use types::id::{IrqId, ThreadId};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
fn setup_ipc_kernel() -> (
|
||||
@@ -138,3 +138,96 @@ fn send_checks_endpoint_capability() {
|
||||
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(_))
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user