feat(kernel): add WaitForInterrupt, AcknowledgeInterrupt, and WaitAny syscalls
This commit is contained in:
@@ -11,6 +11,28 @@ pub struct WaitSet {
|
|||||||
interrupts: [Option<CapSlot>; 4],
|
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 {
|
pub enum WakeReason {
|
||||||
MessageReady(CapSlot),
|
MessageReady(CapSlot),
|
||||||
InterruptFired(CapSlot),
|
InterruptFired(CapSlot),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use domain::{
|
|||||||
process::{ProcessTable, MAX_PROCESSES},
|
process::{ProcessTable, MAX_PROCESSES},
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
scheduler::{multi_core::MultiCoreScheduler, Scheduler},
|
scheduler::{multi_core::MultiCoreScheduler, Scheduler},
|
||||||
|
syscall::WaitSet,
|
||||||
thread::{ThreadTable, ThreadState, MAX_THREADS},
|
thread::{ThreadTable, ThreadState, MAX_THREADS},
|
||||||
};
|
};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
@@ -60,6 +61,7 @@ pub struct Kernel<P: Platform> {
|
|||||||
irqs: IrqRoutingTable,
|
irqs: IrqRoutingTable,
|
||||||
pending_messages: [Option<Message>; MAX_THREADS],
|
pending_messages: [Option<Message>; MAX_THREADS],
|
||||||
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
||||||
|
wait_sets: [Option<WaitSet>; MAX_THREADS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
@@ -77,6 +79,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
irqs: IrqRoutingTable::new(),
|
irqs: IrqRoutingTable::new(),
|
||||||
pending_messages: [const { None }; MAX_THREADS],
|
pending_messages: [const { None }; MAX_THREADS],
|
||||||
pending_results: [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::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message),
|
||||||
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint),
|
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint),
|
||||||
_ => SyscallOutcome::Complete(SyscallResult::Error(
|
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
||||||
domain::error::KernelError::WouldBlock,
|
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))
|
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 super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
use domain::message::Message;
|
use domain::message::Message;
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::{Syscall, WaitSet, WakeReason};
|
||||||
use domain::thread::BlockReason;
|
use domain::thread::BlockReason;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::id::ThreadId;
|
use types::id::{IrqId, ThreadId};
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
fn setup_ipc_kernel() -> (
|
fn setup_ipc_kernel() -> (
|
||||||
@@ -138,3 +138,96 @@ fn send_checks_endpoint_capability() {
|
|||||||
SyscallOutcome::Complete(SyscallResult::Error(_))
|
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