feat(kernel): ipc cap transfer drops Delegate; one owner per irq line, freed when owner dies
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use types::{
|
use types::{
|
||||||
badge::Badge,
|
badge::Badge,
|
||||||
generation::Generation,
|
generation::Generation,
|
||||||
id::{IrqId, NotificationId},
|
id::{IrqId, NotificationId, ProcessId},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -17,6 +17,8 @@ pub struct IrqRoute {
|
|||||||
pub notification: NotificationId,
|
pub notification: NotificationId,
|
||||||
pub generation: Generation,
|
pub generation: Generation,
|
||||||
pub badge: Badge,
|
pub badge: Badge,
|
||||||
|
/// The process that set the route, and so owns the line.
|
||||||
|
pub owner: ProcessId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IrqRoutingTable {
|
pub struct IrqRoutingTable {
|
||||||
@@ -53,6 +55,15 @@ impl IrqRoutingTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drops every route `owner` set, when it dies.
|
||||||
|
pub fn clear_owned_by(&mut self, owner: ProcessId) {
|
||||||
|
for slot in self.routes.iter_mut() {
|
||||||
|
if slot.is_some_and(|r| r.owner == owner) {
|
||||||
|
*slot = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The route for `irq`; `None` for unknown or unrouted lines.
|
/// The route for `irq`; `None` for unknown or unrouted lines.
|
||||||
pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
|
pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
|
||||||
*self.routes.get(irq.raw() as usize)?
|
*self.routes.get(irq.raw() as usize)?
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ fn route(n: u16) -> IrqRoute {
|
|||||||
notification: NotificationId::from_raw(n),
|
notification: NotificationId::from_raw(n),
|
||||||
generation: Generation::initial(),
|
generation: Generation::initial(),
|
||||||
badge: Badge::new(1 << n),
|
badge: Badge::new(1 << n),
|
||||||
|
owner: types::id::ProcessId::from_raw(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,15 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if !grant {
|
if !grant {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let copy: Capability = self
|
// The receiver gets the capability but not the right to pass it
|
||||||
.check_cap(from, slot, Permission::Delegate)
|
// on; a sender that wants that uses Delegate with explicit rights.
|
||||||
.ok()?
|
let source = self.check_cap(from, slot, Permission::Delegate).ok()?;
|
||||||
.clone();
|
let copy = Capability::new(
|
||||||
|
*source.resource(),
|
||||||
|
source.permissions().without(Permission::Delegate),
|
||||||
|
*source.generation(),
|
||||||
|
)
|
||||||
|
.with_badge(source.badge());
|
||||||
let parent = self.cap_ref(from, slot);
|
let parent = self.cap_ref(from, slot);
|
||||||
self.insert_derived(to, copy, parent).ok()
|
self.insert_derived(to, copy, parent).ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
let irq = self.interrupt_cap(pid, irq_slot)?;
|
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||||
let (notification, badge, generation) =
|
let (notification, badge, generation) =
|
||||||
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||||
|
if self.irq_owner(irq).is_some_and(|owner| owner != pid) {
|
||||||
|
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||||
|
}
|
||||||
let route = domain::irq::IrqRoute {
|
let route = domain::irq::IrqRoute {
|
||||||
notification,
|
notification,
|
||||||
generation,
|
generation,
|
||||||
badge,
|
badge,
|
||||||
|
owner: pid,
|
||||||
};
|
};
|
||||||
if self.irqs.set(irq, route) {
|
if self.irqs.set(irq, route) {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -45,10 +49,22 @@ impl<P: Platform> Kernel<P> {
|
|||||||
slot: CapSlot,
|
slot: CapSlot,
|
||||||
) -> Result<(), KernelError> {
|
) -> Result<(), KernelError> {
|
||||||
let irq = self.interrupt_cap(pid, slot)?;
|
let irq = self.interrupt_cap(pid, slot)?;
|
||||||
|
if self.irq_owner(irq).is_some_and(|owner| owner != pid) {
|
||||||
|
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||||
|
}
|
||||||
self.platform.unmask_irq(irq);
|
self.platform.unmask_irq(irq);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The process that routed `irq`, while its route still leads to a
|
||||||
|
/// live notification. One owner per line: other holders of the IRQ
|
||||||
|
/// capability cannot take it over.
|
||||||
|
fn irq_owner(&self, irq: IrqId) -> Option<ProcessId> {
|
||||||
|
let route = self.irqs.route(irq)?;
|
||||||
|
(self.notifications.generation(route.notification) == Some(route.generation))
|
||||||
|
.then_some(route.owner)
|
||||||
|
}
|
||||||
|
|
||||||
/// An interrupt fired: mask the line and signal its notification. The
|
/// An interrupt fired: mask the line and signal its notification. The
|
||||||
/// line stays masked until the handler acknowledges it.
|
/// line stays masked until the handler acknowledges it.
|
||||||
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.delete_all_caps(pid);
|
self.delete_all_caps(pid);
|
||||||
self.destroy_endpoints_owned_by(pid);
|
self.destroy_endpoints_owned_by(pid);
|
||||||
self.destroy_notifications_owned_by(pid);
|
self.destroy_notifications_owned_by(pid);
|
||||||
|
self.irqs.clear_owned_by(pid);
|
||||||
self.teardown_memory(pid);
|
self.teardown_memory(pid);
|
||||||
|
|
||||||
let _ = self.processes.destroy(pid);
|
let _ = self.processes.destroy(pid);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use domain::thread::ThreadState;
|
|||||||
use types::address::DataRef;
|
use types::address::DataRef;
|
||||||
use types::badge::Badge;
|
use types::badge::Badge;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::id::{EndpointId, IrqId, NotificationId, ThreadId};
|
use types::id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId};
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
fn perms(ps: &[Permission]) -> PermissionSet {
|
fn perms(ps: &[Permission]) -> PermissionSet {
|
||||||
@@ -869,3 +869,129 @@ fn interrupts_beyond_the_routing_table_are_ignored() {
|
|||||||
let mut t = Ipc::new();
|
let mut t = Ipc::new();
|
||||||
t.k.handle_interrupt(IrqId::from_raw(1000));
|
t.k.handle_interrupt(IrqId::from_raw(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_received_cap_cannot_be_passed_on() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (a, b, ta, tb, a_send, b_recv) = (t.a, t.b, t.ta, t.tb, t.a_send, t.b_recv);
|
||||||
|
let other = t.k.create_endpoint(a).unwrap();
|
||||||
|
let gift =
|
||||||
|
t.k.grant_cap_for_test(a, Resource::Endpoint(other), perms(SEND_GRANT));
|
||||||
|
t.run(
|
||||||
|
ta,
|
||||||
|
Syscall::Send {
|
||||||
|
endpoint: a_send,
|
||||||
|
message: msg([0; 4]).with_cap(gift),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let d = delivered(&t.run(tb, Syscall::Receive { endpoint: b_recv })).unwrap();
|
||||||
|
let got = d.cap.expect("the cap arrives");
|
||||||
|
let rights =
|
||||||
|
*t.k.processes
|
||||||
|
.get(b)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.get(got)
|
||||||
|
.unwrap()
|
||||||
|
.permissions();
|
||||||
|
assert!(
|
||||||
|
rights.allows(Permission::Execute),
|
||||||
|
"the right itself arrives"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!rights.allows(Permission::Delegate),
|
||||||
|
"but not the right to pass it on"
|
||||||
|
);
|
||||||
|
let mint = t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::Mint {
|
||||||
|
slot: got,
|
||||||
|
badge: Badge::new(1),
|
||||||
|
permissions: perms(SEND),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert!(matches!(
|
||||||
|
error(&mint),
|
||||||
|
Some(KernelError::PermissionDenied { .. })
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_irq_has_one_owner_until_it_dies() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (a, b, ta, tb) = (t.a, t.b, t.ta, t.tb);
|
||||||
|
let irq = IrqId::from_raw(12);
|
||||||
|
let route = |t: &mut Ipc, pid: ProcessId, tid: ThreadId| {
|
||||||
|
let irq_cap =
|
||||||
|
t.k.grant_cap_for_test(pid, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||||
|
let n = notification(t, pid);
|
||||||
|
let sig =
|
||||||
|
t.k.grant_cap_for_test(pid, Resource::Notification(n), perms(&[Permission::Write]));
|
||||||
|
(
|
||||||
|
irq_cap,
|
||||||
|
t.run(
|
||||||
|
tid,
|
||||||
|
Syscall::SetIrqNotification {
|
||||||
|
irq: irq_cap,
|
||||||
|
notification: sig,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (_, first) = route(&mut t, b, tb);
|
||||||
|
assert!(error(&first).is_none());
|
||||||
|
let (a_irq, stolen) = route(&mut t, a, ta);
|
||||||
|
assert!(matches!(
|
||||||
|
error(&stolen),
|
||||||
|
Some(KernelError::IrqAlreadyOwned(_))
|
||||||
|
));
|
||||||
|
let ack = t.run(ta, Syscall::AcknowledgeInterrupt { irq: a_irq });
|
||||||
|
assert!(
|
||||||
|
matches!(error(&ack), Some(KernelError::IrqAlreadyOwned(_))),
|
||||||
|
"nor unmask it"
|
||||||
|
);
|
||||||
|
let (_, again) = route(&mut t, b, tb);
|
||||||
|
assert!(
|
||||||
|
error(&again).is_none(),
|
||||||
|
"the owner may re-route its own line"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.k.destroy_process(b);
|
||||||
|
let (_, claimed) = route(&mut t, a, ta);
|
||||||
|
assert!(error(&claimed).is_none(), "a dead owner's line is free");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_dead_owner_frees_its_line_even_if_the_notification_lives_on() {
|
||||||
|
let mut t = Ipc::new();
|
||||||
|
let (a, b, ta, tb) = (t.a, t.b, t.ta, t.tb);
|
||||||
|
let irq = IrqId::from_raw(13);
|
||||||
|
// B routes the line to a notification A owns, then dies.
|
||||||
|
let b_irq =
|
||||||
|
t.k.grant_cap_for_test(b, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||||
|
let n = notification(&mut t, a);
|
||||||
|
let b_sig =
|
||||||
|
t.k.grant_cap_for_test(b, Resource::Notification(n), perms(&[Permission::Write]));
|
||||||
|
let routed = t.run(
|
||||||
|
tb,
|
||||||
|
Syscall::SetIrqNotification {
|
||||||
|
irq: b_irq,
|
||||||
|
notification: b_sig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert!(error(&routed).is_none());
|
||||||
|
t.k.destroy_process(b);
|
||||||
|
|
||||||
|
let a_irq =
|
||||||
|
t.k.grant_cap_for_test(a, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||||
|
let a_sig =
|
||||||
|
t.k.grant_cap_for_test(a, Resource::Notification(n), perms(&[Permission::Write]));
|
||||||
|
let claimed = t.run(
|
||||||
|
ta,
|
||||||
|
Syscall::SetIrqNotification {
|
||||||
|
irq: a_irq,
|
||||||
|
notification: a_sig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert!(error(&claimed).is_none());
|
||||||
|
}
|
||||||
|
|||||||
14
docs/ipc.md
14
docs/ipc.md
@@ -100,7 +100,9 @@ the UART) to their drivers before anything else (`libkos::device`):
|
|||||||
A message is 4 words and at most one capability. The capability is
|
A message is 4 words and at most one capability. The capability is
|
||||||
transferred only if the sender's endpoint capability has `Delegate` and the
|
transferred only if the sender's endpoint capability has `Delegate` and the
|
||||||
transferred capability itself has `Delegate`; it is **copied** (the sender
|
transferred capability itself has `Delegate`; it is **copied** (the sender
|
||||||
keeps its own) with its permissions and badge unchanged. If the receiver's
|
keeps its own) with the same badge and permissions minus `Delegate`: the
|
||||||
|
receiver can use it but not pass it on. To let a process pass a capability
|
||||||
|
on, give it with the `Delegate` syscall and explicit rights. If the receiver's
|
||||||
table is full, or the capability is no longer valid at delivery time, the
|
table is full, or the capability is no longer valid at delivery time, the
|
||||||
message is delivered without it.
|
message is delivered without it.
|
||||||
|
|
||||||
@@ -128,8 +130,10 @@ never reveals the sender's process or thread id.
|
|||||||
- `BindNotification(ntfn)`: bind to the calling thread. One binding per
|
- `BindNotification(ntfn)`: bind to the calling thread. One binding per
|
||||||
thread and per notification.
|
thread and per notification.
|
||||||
- `SetIrqNotification(irq, ntfn)`: deliver interrupt `irq` as `Signal`
|
- `SetIrqNotification(irq, ntfn)`: deliver interrupt `irq` as `Signal`
|
||||||
through the given (badged) notification capability.
|
through the given (badged) notification capability. The first process to
|
||||||
- `AckInterrupt(irq)`: unmask the line after handling.
|
route a line owns it; other holders of the IRQ capability get
|
||||||
|
`IrqAlreadyOwned` until the owner dies or its notification is destroyed.
|
||||||
|
- `AckInterrupt(irq)`: unmask the line after handling (owner only).
|
||||||
|
|
||||||
An interrupt masks its line and signals its notification. If no route is
|
An interrupt masks its line and signals its notification. If no route is
|
||||||
set it stays masked.
|
set it stays masked.
|
||||||
@@ -148,8 +152,8 @@ set it stays masked.
|
|||||||
## Death
|
## Death
|
||||||
|
|
||||||
Destroying an endpoint or notification wakes every thread blocked on it with
|
Destroying an endpoint or notification wakes every thread blocked on it with
|
||||||
`InvalidCap`. Destroying a thread removes it from every queue, binding,
|
`InvalidCap`. Destroying a thread removes it from every queue, binding and
|
||||||
reply slot and IRQ route.
|
reply slot; a dying process loses every IRQ route it set.
|
||||||
|
|
||||||
## Register ABI (RISC-V)
|
## Register ABI (RISC-V)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user