refactor(kernel): split ipc/memory/lib/thread by concern; trim comments
This commit is contained in:
@@ -2,10 +2,7 @@
|
|||||||
//! capability is a node with at most one parent. Kept free of kernel
|
//! capability is a node with at most one parent. Kept free of kernel
|
||||||
//! types so the revocation rule can be checked on its own.
|
//! types so the revocation rule can be checked on its own.
|
||||||
|
|
||||||
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`.
|
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`. `parents` must be acyclic.
|
||||||
///
|
|
||||||
/// `parents` must be acyclic, which holds because a capability's parent
|
|
||||||
/// always exists before it does.
|
|
||||||
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
|
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
|
||||||
let mut marked = [false; N];
|
let mut marked = [false; N];
|
||||||
let Some(r) = marked.get_mut(root) else {
|
let Some(r) = marked.get_mut(root) else {
|
||||||
@@ -57,9 +54,7 @@ mod proofs {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On every acyclic forest of `N` capabilities, `descendants` marks
|
/// On every acyclic forest, `descendants` marks exactly the subtree under `root`.
|
||||||
/// exactly the nodes `root` is an ancestor of: revocation removes the
|
|
||||||
/// whole subtree and nothing else.
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
#[kani::unwind(6)]
|
#[kani::unwind(6)]
|
||||||
fn marks_exactly_the_subtree() {
|
fn marks_exactly_the_subtree() {
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ mod tests;
|
|||||||
// Sized so that every representable `CapSlot` indexes a real slot.
|
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||||
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||||
|
|
||||||
/// Names one capability slot at one point in time. Both generations must
|
/// One capability slot at one point in time; both generations must match to resolve.
|
||||||
/// still match for the reference to resolve, so a reused pid or slot is
|
|
||||||
/// never mistaken for the capability that used to live there.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct CapRef {
|
pub struct CapRef {
|
||||||
pub process: ProcessId,
|
pub process: ProcessId,
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
//! The capability derivation tree with explicit links, so deleting a
|
//! Derivation tree with explicit links: delete costs its children, revoke its subtree.
|
||||||
//! capability costs its number of children and revoking costs the size of
|
//! Nodes are `0..N`; `derivation::descendants` is the reference model it is checked against.
|
||||||
//! the subtree, instead of a scan of every capability in the system.
|
|
||||||
//!
|
|
||||||
//! Nodes are numbered `0..N` (the kernel flattens process and slot). Each
|
|
||||||
//! live node knows its parent, its first child and its siblings. The
|
|
||||||
//! fixpoint in `derivation::descendants` stays as the reference this tree
|
|
||||||
//! is checked against.
|
|
||||||
|
|
||||||
/// Longest walk any operation may take: every node visited once, plus the
|
/// Longest walk any operation may take: every node visited once, plus the
|
||||||
/// climb back up. Corrupted links can therefore never hang the kernel.
|
/// climb back up. Corrupted links can therefore never hang the kernel.
|
||||||
@@ -59,9 +53,7 @@ impl<const N: usize> CapTree<N> {
|
|||||||
self.node(i)?.parent.map(usize::from)
|
self.node(i)?.parent.map(usize::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds `i` as a child of `parent` (or as a root). Fails, changing
|
/// Adds `i` under `parent` (or as a root); fails without change if `i` is live or out of range, or `parent` is not live.
|
||||||
/// nothing, if `i` is already live or out of range, or `parent` is not
|
|
||||||
/// live.
|
|
||||||
pub fn attach(&mut self, i: usize, parent: Option<usize>) -> bool {
|
pub fn attach(&mut self, i: usize, parent: Option<usize>) -> bool {
|
||||||
let (Some(me), Some(node)) = (id(i), self.nodes.get(i)) else {
|
let (Some(me), Some(node)) = (id(i), self.nodes.get(i)) else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ impl<const N: usize> Default for WaitQueue<N> {
|
|||||||
mod proofs {
|
mod proofs {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Against a simple reference model (a plain array kept in arrival
|
/// Matches a plain arrival-ordered array model: FIFO, no duplicates, nothing lost.
|
||||||
/// order), any sequence of pushes, pops and removals behaves the same:
|
|
||||||
/// FIFO order, no duplicates, nothing lost.
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
#[kani::unwind(7)]
|
#[kani::unwind(7)]
|
||||||
fn behaves_like_a_fifo_without_duplicates() {
|
fn behaves_like_a_fifo_without_duplicates() {
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use types::generation::Generation;
|
|||||||
#[path = "generations_tests.rs"]
|
#[path = "generations_tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// A slot's generation changes every time the object in it is destroyed
|
/// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired.
|
||||||
/// or revoked, so capabilities minted earlier stop matching. A slot whose
|
|
||||||
/// counter runs out is retired for good rather than reused.
|
|
||||||
pub struct SlotGenerations<const N: usize> {
|
pub struct SlotGenerations<const N: usize> {
|
||||||
gens: [Option<Generation>; N],
|
gens: [Option<Generation>; N],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,9 +104,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
self.frames.get(self.index_of(pa)?)?.as_ref()
|
self.frames.get(self.index_of(pa)?)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Next-fit: searches from just past the previous allocation and wraps
|
/// Next-fit: searches from just past the previous allocation, wrapping once.
|
||||||
/// once, so a run of allocations costs O(1) each instead of rescanning
|
|
||||||
/// the allocated prefix every time.
|
|
||||||
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
||||||
if self.free == 0 {
|
if self.free == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -177,10 +175,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The frame mapped at `va` in `owner`, if `owner` holds it as a user page.
|
/// The perms of the frame at `pa` if it is `owner`'s user page at `va`; O(1), the caller supplies the frame.
|
||||||
/// The perms of the frame at `pa` if it is `owner`'s user page at
|
|
||||||
/// `va`; `None` for any other frame. O(1): the caller supplies the
|
|
||||||
/// frame, this only confirms it.
|
|
||||||
pub fn user_at(
|
pub fn user_at(
|
||||||
&self,
|
&self,
|
||||||
pa: PhysicalAddress,
|
pa: PhysicalAddress,
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ struct ListEnds {
|
|||||||
tail: Option<ThreadId>,
|
tail: Option<ThreadId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ready queue: one FIFO per priority level, threaded through an
|
/// Ready queue: one FIFO per priority in an intrusive list indexed by thread id; O(1) ops.
|
||||||
/// intrusive doubly linked list indexed by thread id. A thread is in at
|
|
||||||
/// most one list, so capacity never runs out and every op is O(1).
|
|
||||||
pub struct PriorityQueue {
|
pub struct PriorityQueue {
|
||||||
lists: [ListEnds; NUM_PRIORITIES],
|
lists: [ListEnds; NUM_PRIORITIES],
|
||||||
links: [Links; MAX_THREADS],
|
links: [Links; MAX_THREADS],
|
||||||
|
|||||||
51
crates/domain/src/thread/context.rs
Normal file
51
crates/domain/src/thread/context.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//! Saved register state of a thread.
|
||||||
|
|
||||||
|
pub struct SavedContext {
|
||||||
|
registers: [usize; 32],
|
||||||
|
pc: usize,
|
||||||
|
status: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SavedContext {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
registers: [0; 32],
|
||||||
|
pc: 0,
|
||||||
|
status: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_entry_and_stack(
|
||||||
|
entry: types::address::EntryPoint,
|
||||||
|
stack: types::address::StackPointer,
|
||||||
|
) -> Self {
|
||||||
|
let mut ctx = Self::empty();
|
||||||
|
ctx.pc = entry.raw();
|
||||||
|
ctx.registers[2] = stack.raw();
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn program_counter(&self) -> usize {
|
||||||
|
self.pc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pc(&mut self, pc: usize) {
|
||||||
|
self.pc = pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self) -> usize {
|
||||||
|
self.status
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_status(&mut self, status: usize) {
|
||||||
|
self.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registers(&self) -> &[usize; 32] {
|
||||||
|
&self.registers
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
||||||
|
&mut self.registers
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
use types::{
|
use types::{
|
||||||
bounded::Priority,
|
bounded::Priority,
|
||||||
generation::Generation,
|
|
||||||
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
|
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
|
||||||
time::TimeSlice,
|
time::TimeSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::KernelError, generations::SlotGenerations};
|
use crate::error::KernelError;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod table;
|
||||||
|
|
||||||
|
pub use context::SavedContext;
|
||||||
|
pub use table::{MAX_THREADS, ThreadTable};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
@@ -29,56 +34,6 @@ pub enum ThreadState {
|
|||||||
Dead,
|
Dead,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SavedContext {
|
|
||||||
registers: [usize; 32],
|
|
||||||
pc: usize,
|
|
||||||
status: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SavedContext {
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
registers: [0; 32],
|
|
||||||
pc: 0,
|
|
||||||
status: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_entry_and_stack(
|
|
||||||
entry: types::address::EntryPoint,
|
|
||||||
stack: types::address::StackPointer,
|
|
||||||
) -> Self {
|
|
||||||
let mut ctx = Self::empty();
|
|
||||||
ctx.pc = entry.raw();
|
|
||||||
ctx.registers[2] = stack.raw();
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn program_counter(&self) -> usize {
|
|
||||||
self.pc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_pc(&mut self, pc: usize) {
|
|
||||||
self.pc = pc;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status(&self) -> usize {
|
|
||||||
self.status
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_status(&mut self, status: usize) {
|
|
||||||
self.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn registers(&self) -> &[usize; 32] {
|
|
||||||
&self.registers
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
|
||||||
&mut self.registers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BlockReason {
|
pub enum BlockReason {
|
||||||
/// Queued as a sender on the endpoint.
|
/// Queued as a sender on the endpoint.
|
||||||
@@ -228,129 +183,3 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
|
||||||
|
|
||||||
pub struct ThreadTable {
|
|
||||||
threads: [Option<Thread>; MAX_THREADS],
|
|
||||||
generations: SlotGenerations<MAX_THREADS>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadTable {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
threads: [const { None }; MAX_THREADS],
|
|
||||||
generations: SlotGenerations::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Current generation of a live thread, so references to it (reply
|
|
||||||
/// slots) can detect that the id was reused.
|
|
||||||
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
|
||||||
self.get(id).ok()?;
|
|
||||||
self.generations.current(id.raw() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn free_slot(&self) -> Option<usize> {
|
|
||||||
self.threads
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(
|
|
||||||
&mut self,
|
|
||||||
process: ProcessId,
|
|
||||||
priority: Priority,
|
|
||||||
time_slice: TimeSlice,
|
|
||||||
core_affinity: Option<CoreId>,
|
|
||||||
) -> Result<ThreadId, KernelError> {
|
|
||||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
|
||||||
|
|
||||||
let id = ThreadId::from_raw(idx as u16);
|
|
||||||
*self
|
|
||||||
.threads
|
|
||||||
.get_mut(idx)
|
|
||||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
|
||||||
id,
|
|
||||||
process,
|
|
||||||
priority,
|
|
||||||
time_slice,
|
|
||||||
core_affinity,
|
|
||||||
));
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_with_context(
|
|
||||||
&mut self,
|
|
||||||
process: ProcessId,
|
|
||||||
priority: Priority,
|
|
||||||
time_slice: TimeSlice,
|
|
||||||
core_affinity: Option<CoreId>,
|
|
||||||
context: SavedContext,
|
|
||||||
) -> Result<ThreadId, KernelError> {
|
|
||||||
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
|
||||||
|
|
||||||
let id = ThreadId::from_raw(idx as u16);
|
|
||||||
*self
|
|
||||||
.threads
|
|
||||||
.get_mut(idx)
|
|
||||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
|
||||||
id,
|
|
||||||
process,
|
|
||||||
priority,
|
|
||||||
time_slice,
|
|
||||||
core_affinity,
|
|
||||||
context,
|
|
||||||
));
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every live thread with its id.
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (ThreadId, &Thread)> {
|
|
||||||
self.threads
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, t)| Some((ThreadId::from_raw(i as u16), t.as_ref()?)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, id: ThreadId) -> Result<&Thread, KernelError> {
|
|
||||||
let idx = id.raw() as usize;
|
|
||||||
self.threads
|
|
||||||
.get(idx)
|
|
||||||
.and_then(|s| s.as_ref())
|
|
||||||
.ok_or(KernelError::InvalidThread(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: ThreadId) -> Result<&mut Thread, KernelError> {
|
|
||||||
let idx = id.raw() as usize;
|
|
||||||
self.threads
|
|
||||||
.get_mut(idx)
|
|
||||||
.and_then(|s| s.as_mut())
|
|
||||||
.ok_or(KernelError::InvalidThread(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
|
|
||||||
let idx = id.raw() as usize;
|
|
||||||
let thread = self
|
|
||||||
.threads
|
|
||||||
.get_mut(idx)
|
|
||||||
.and_then(|s| s.take())
|
|
||||||
.ok_or(KernelError::InvalidThread(id))?;
|
|
||||||
self.generations.advance(idx);
|
|
||||||
Ok(thread)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
|
||||||
self.threads
|
|
||||||
.iter()
|
|
||||||
.filter_map(|s| s.as_ref())
|
|
||||||
.filter(move |t| t.process() == process)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ThreadTable {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
137
crates/domain/src/thread/table.rs
Normal file
137
crates/domain/src/thread/table.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
//! Fixed-capacity table of threads.
|
||||||
|
|
||||||
|
use types::{
|
||||||
|
bounded::Priority,
|
||||||
|
generation::Generation,
|
||||||
|
id::{CoreId, ProcessId, ThreadId},
|
||||||
|
time::TimeSlice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{SavedContext, Thread};
|
||||||
|
use crate::{error::KernelError, generations::SlotGenerations};
|
||||||
|
|
||||||
|
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
||||||
|
|
||||||
|
pub struct ThreadTable {
|
||||||
|
threads: [Option<Thread>; MAX_THREADS],
|
||||||
|
generations: SlotGenerations<MAX_THREADS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadTable {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
threads: [const { None }; MAX_THREADS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current generation of a live thread, so references to it (reply
|
||||||
|
/// slots) can detect that the id was reused.
|
||||||
|
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
||||||
|
self.get(id).ok()?;
|
||||||
|
self.generations.current(id.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_slot(&self) -> Option<usize> {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(
|
||||||
|
&mut self,
|
||||||
|
process: ProcessId,
|
||||||
|
priority: Priority,
|
||||||
|
time_slice: TimeSlice,
|
||||||
|
core_affinity: Option<CoreId>,
|
||||||
|
) -> Result<ThreadId, KernelError> {
|
||||||
|
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||||
|
|
||||||
|
let id = ThreadId::from_raw(idx as u16);
|
||||||
|
*self
|
||||||
|
.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
||||||
|
id,
|
||||||
|
process,
|
||||||
|
priority,
|
||||||
|
time_slice,
|
||||||
|
core_affinity,
|
||||||
|
));
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_with_context(
|
||||||
|
&mut self,
|
||||||
|
process: ProcessId,
|
||||||
|
priority: Priority,
|
||||||
|
time_slice: TimeSlice,
|
||||||
|
core_affinity: Option<CoreId>,
|
||||||
|
context: SavedContext,
|
||||||
|
) -> Result<ThreadId, KernelError> {
|
||||||
|
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||||
|
|
||||||
|
let id = ThreadId::from_raw(idx as u16);
|
||||||
|
*self
|
||||||
|
.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
||||||
|
id,
|
||||||
|
process,
|
||||||
|
priority,
|
||||||
|
time_slice,
|
||||||
|
core_affinity,
|
||||||
|
context,
|
||||||
|
));
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every live thread with its id.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (ThreadId, &Thread)> {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, t)| Some((ThreadId::from_raw(i as u16), t.as_ref()?)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: ThreadId) -> Result<&Thread, KernelError> {
|
||||||
|
let idx = id.raw() as usize;
|
||||||
|
self.threads
|
||||||
|
.get(idx)
|
||||||
|
.and_then(|s| s.as_ref())
|
||||||
|
.ok_or(KernelError::InvalidThread(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: ThreadId) -> Result<&mut Thread, KernelError> {
|
||||||
|
let idx = id.raw() as usize;
|
||||||
|
self.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.and_then(|s| s.as_mut())
|
||||||
|
.ok_or(KernelError::InvalidThread(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
|
||||||
|
let idx = id.raw() as usize;
|
||||||
|
let thread = self
|
||||||
|
.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.and_then(|s| s.take())
|
||||||
|
.ok_or(KernelError::InvalidThread(id))?;
|
||||||
|
self.generations.advance(idx);
|
||||||
|
Ok(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| s.as_ref())
|
||||||
|
.filter(move |t| t.process() == process)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThreadTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,9 +34,7 @@ fn message(a: &[usize; 6]) -> Option<Message> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes syscall `nr` with arguments `a0..a5`. `None` for an unknown
|
/// Decodes syscall `nr` with args `a0..a5`; `None` for an unknown number or meaningless argument.
|
||||||
/// number or an argument with no meaning (e.g. a slot out of range); the
|
|
||||||
/// caller then gets `UnknownSyscall` and nothing else happens.
|
|
||||||
pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
||||||
Some(match nr {
|
Some(match nr {
|
||||||
SYS_EXIT => Syscall::Exit,
|
SYS_EXIT => Syscall::Exit,
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
//! The authority init starts with. The board describes its devices; the
|
//! The authority init starts with: per-device MMIO and IRQ capabilities plus one SystemControl.
|
||||||
//! kernel turns each into capabilities (an MMIO region and an IRQ line)
|
|
||||||
//! and adds one SystemControl capability. Init then decides who gets what;
|
|
||||||
//! nothing else is privileged by its process id.
|
|
||||||
|
|
||||||
use domain::{error::KernelError, resource::Resource};
|
use domain::{error::KernelError, resource::Resource};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
@@ -31,9 +28,7 @@ pub struct BoardCaps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
/// Creates one device region per board device, owned by `init`. Fails
|
/// Creates one device region per board device, owned by `init`; fails on the first invalid one.
|
||||||
/// on the first device that is misaligned, overlaps the frame pool or
|
|
||||||
/// another device, or names an IRQ the kernel cannot route.
|
|
||||||
pub fn register_board(
|
pub fn register_board(
|
||||||
&mut self,
|
&mut self,
|
||||||
init: ProcessId,
|
init: ProcessId,
|
||||||
@@ -50,11 +45,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives `init` a capability to every registered device's region and
|
/// Gives `init` (with Delegate) caps to every device region and IRQ, then SystemControl.
|
||||||
/// IRQ line, then one SystemControl capability. All carry Delegate so
|
/// Writes each device's slots into `placed` in order; returns the SystemControl slot.
|
||||||
/// init can hand them on. Writes where each device's caps went into
|
|
||||||
/// `placed` (one entry per device, in order) and returns the
|
|
||||||
/// SystemControl slot, so boot can tell init.
|
|
||||||
pub fn grant_boot_authority(
|
pub fn grant_boot_authority(
|
||||||
&mut self,
|
&mut self,
|
||||||
init: ProcessId,
|
init: ProcessId,
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
//! The capability derivation tree. Every derived capability (Mint,
|
//! Derivation tree: every derived capability records its parent; `Revoke` deletes a subtree,
|
||||||
//! Delegate, IPC transfer) records its parent; `Revoke` deletes a subtree
|
//! deleting re-parents children. All capability insertion/removal goes through here so
|
||||||
//! and deleting a capability re-parents its children, so the tree can only
|
//! `Capability::parent` and `Kernel::tree` stay in step.
|
||||||
//! shrink by explicit deletion, never by a middleman stepping out.
|
|
||||||
//!
|
|
||||||
//! Each capability records its parent (`Capability::parent`), and
|
|
||||||
//! `Kernel::tree` holds the same relation as explicit links, so deletion
|
|
||||||
//! and revocation cost the subtree, not a scan of every capability. Every
|
|
||||||
//! insertion and removal of a capability goes through this module, which
|
|
||||||
//! keeps the two in step.
|
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
capability::{CapRef, Capability, MAX_CAPS_PER_PROCESS},
|
capability::{CapRef, Capability, MAX_CAPS_PER_PROCESS},
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ mod memory;
|
|||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
mod smp;
|
mod smp;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
mod teardown;
|
||||||
|
|
||||||
pub use board::{BoardCaps, BoardDevice};
|
pub use board::{BoardCaps, BoardDevice};
|
||||||
pub use syscall::{SyscallOutcome, SyscallResult};
|
pub use syscall::{SyscallOutcome, SyscallResult};
|
||||||
@@ -37,7 +38,7 @@ use domain::{
|
|||||||
error::KernelError,
|
error::KernelError,
|
||||||
irq::IrqRoutingTable,
|
irq::IrqRoutingTable,
|
||||||
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
||||||
notification::{MAX_NOTIFICATIONS, NotificationTable},
|
notification::NotificationTable,
|
||||||
process::{MAX_PROCESSES, ProcessTable},
|
process::{MAX_PROCESSES, ProcessTable},
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
scheduler::{Scheduler, multi_core::MultiCoreScheduler},
|
scheduler::{Scheduler, multi_core::MultiCoreScheduler},
|
||||||
@@ -96,12 +97,10 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the kernel directly in `slot`, one table at a time, so the
|
/// Builds the kernel in place, one table at a time, to stay off the boot stack.
|
||||||
/// whole structure never has to fit on the (small) boot stack.
|
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `slot` must be valid for writes and properly aligned. Any previous
|
/// `slot` must be valid for writes and aligned; the old value is overwritten without drop.
|
||||||
/// value is overwritten without being dropped.
|
|
||||||
pub unsafe fn init_in_place(
|
pub unsafe fn init_in_place(
|
||||||
slot: *mut Self,
|
slot: *mut Self,
|
||||||
platform: P,
|
platform: P,
|
||||||
@@ -263,123 +262,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn destroy_process(&mut self, pid: ProcessId) {
|
|
||||||
use domain::process::MAX_THREADS_PER_PROCESS;
|
|
||||||
|
|
||||||
let thread_ids: [Option<ThreadId>; MAX_THREADS_PER_PROCESS] = match self.processes.get(pid)
|
|
||||||
{
|
|
||||||
Ok(p) => {
|
|
||||||
let mut ids = [None; MAX_THREADS_PER_PROCESS];
|
|
||||||
for (slot, tid) in ids.iter_mut().zip(p.thread_ids()) {
|
|
||||||
*slot = Some(tid);
|
|
||||||
}
|
|
||||||
ids
|
|
||||||
}
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.cleanup_threads(&thread_ids);
|
|
||||||
self.delete_all_caps(pid);
|
|
||||||
self.destroy_endpoints_owned_by(pid);
|
|
||||||
self.destroy_notifications_owned_by(pid);
|
|
||||||
self.teardown_memory(pid);
|
|
||||||
|
|
||||||
let _ = self.processes.destroy(pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
for tid in thread_ids.iter().flatten() {
|
|
||||||
self.leave_queue(*tid);
|
|
||||||
if let Some(n) = self
|
|
||||||
.ipc(*tid)
|
|
||||||
.and_then(|s| s.bound)
|
|
||||||
.and_then(|n| self.notifications.get_mut(n))
|
|
||||||
{
|
|
||||||
n.remove_thread(*tid);
|
|
||||||
}
|
|
||||||
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
|
||||||
if let Some(caller) = ipc.reply_to {
|
|
||||||
self.release_caller(caller);
|
|
||||||
}
|
|
||||||
self.evict(*tid);
|
|
||||||
self.scheduler.remove(*tid);
|
|
||||||
self.set_pending_result(*tid, None);
|
|
||||||
let _ = self.threads.destroy(*tid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes a blocked thread off the one queue its block reason names, so
|
|
||||||
/// nothing needs to scan every endpoint and notification.
|
|
||||||
pub(crate) fn leave_queue(&mut self, tid: ThreadId) {
|
|
||||||
use domain::thread::BlockReason::*;
|
|
||||||
let Ok(ThreadState::Blocked(reason)) = self.threads.get(tid).map(|t| *t.state()) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
match reason {
|
|
||||||
Sending(ep) | Calling(ep) => {
|
|
||||||
self.endpoints.get_mut(ep).map(|e| e.remove_sender(tid));
|
|
||||||
}
|
|
||||||
Receiving(ep) => {
|
|
||||||
self.endpoints.get_mut(ep).map(|e| e.remove_receiver(tid));
|
|
||||||
}
|
|
||||||
WaitingNotification(n) => {
|
|
||||||
if let Some(n) = self.notifications.get_mut(n) {
|
|
||||||
n.remove_waiter(tid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AwaitingReply => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroys every endpoint `owner` created, waking anyone blocked on
|
|
||||||
/// them with an error instead of leaving them blocked forever.
|
|
||||||
pub(crate) fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
|
|
||||||
let mut owned = [None; domain::endpoint::MAX_ENDPOINTS];
|
|
||||||
for (slot, id) in owned.iter_mut().zip(self.endpoints.ids_owned_by(owner)) {
|
|
||||||
*slot = Some(id);
|
|
||||||
}
|
|
||||||
for id in owned.into_iter().flatten() {
|
|
||||||
let Some(mut ep) = self.endpoints.destroy(id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
while let Some(tid) = ep.dequeue_sender().or_else(|| ep.dequeue_receiver()) {
|
|
||||||
self.wake_with_error(tid, KernelError::InvalidEndpoint(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroys every notification `owner` created, waking its waiters
|
|
||||||
/// with an error and unbinding its bound thread.
|
|
||||||
pub(crate) fn destroy_notifications_owned_by(&mut self, owner: ProcessId) {
|
|
||||||
let mut owned = [None; MAX_NOTIFICATIONS];
|
|
||||||
for (slot, id) in owned.iter_mut().zip(self.notifications.ids_owned_by(owner)) {
|
|
||||||
*slot = Some(id);
|
|
||||||
}
|
|
||||||
for id in owned.into_iter().flatten() {
|
|
||||||
let Some(mut n) = self.notifications.destroy(id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let Some(bound) = n.bound()
|
|
||||||
&& let Some(s) = self.ipc_mut(bound)
|
|
||||||
{
|
|
||||||
s.bound = None;
|
|
||||||
}
|
|
||||||
while let Some(tid) = n.pop_waiter() {
|
|
||||||
self.wake_with_error(tid, KernelError::NoSuchObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a blocked thread runnable with `error` as its syscall result,
|
|
||||||
/// dropping whatever it was about to send.
|
|
||||||
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
|
|
||||||
if let Some(s) = self.ipc_mut(tid) {
|
|
||||||
s.outgoing = None;
|
|
||||||
}
|
|
||||||
self.wake(tid, SyscallResult::Error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_cap(
|
fn check_cap(
|
||||||
&self,
|
&self,
|
||||||
owner: ProcessId,
|
owner: ProcessId,
|
||||||
|
|||||||
@@ -1,521 +0,0 @@
|
|||||||
//! Address-space bookkeeping. The kernel decides every user mapping from
|
|
||||||
//! its own records (`FrameTable`, `RegionTable`, quotas) and only then tells
|
|
||||||
//! the platform to apply it; it never asks the hardware what is mapped.
|
|
||||||
|
|
||||||
use domain::{
|
|
||||||
error::KernelError,
|
|
||||||
memory::{
|
|
||||||
Region, RegionBacking, RegionMapping,
|
|
||||||
frame::{FrameTable, FrameUse, PAGE_SIZE},
|
|
||||||
quota::Quota,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use platform::{FrameSource, MapError, Platform};
|
|
||||||
use types::{
|
|
||||||
address::{PhysicalAddress, VirtualAddress},
|
|
||||||
bounded::PageCount,
|
|
||||||
id::{ProcessId, RegionId},
|
|
||||||
mapping::PagePerms,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Kernel;
|
|
||||||
|
|
||||||
/// First VA the kernel considers when picking a spot for `RequestPages`.
|
|
||||||
/// Below it sit the program image and boot-time device windows.
|
|
||||||
pub(crate) const USER_HEAP_START: usize = 0x40000;
|
|
||||||
|
|
||||||
/// Page-table frames for one address space, charged to its owner.
|
|
||||||
struct ChargedFrames<'a> {
|
|
||||||
frames: &'a mut FrameTable,
|
|
||||||
quota: &'a mut Quota,
|
|
||||||
owner: ProcessId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameSource for ChargedFrames<'_> {
|
|
||||||
fn alloc_table_frame(&mut self) -> Option<PhysicalAddress> {
|
|
||||||
if !self.quota.charge(1) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match self.frames.alloc(FrameUse::PageTable { owner: self.owner }) {
|
|
||||||
Some(pa) => Some(pa),
|
|
||||||
None => {
|
|
||||||
self.quota.uncharge(1);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_error(e: MapError) -> KernelError {
|
|
||||||
match e {
|
|
||||||
MapError::OutOfFrames => KernelError::OutOfMemory,
|
|
||||||
MapError::InvalidAddress => KernelError::InvalidAddress,
|
|
||||||
MapError::NoAddressSpace => KernelError::OutOfMemory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
|
||||||
/// Whether `[va, va + pages)` is page aligned and inside the user window.
|
|
||||||
pub(crate) fn in_user_window(va: VirtualAddress, pages: usize) -> bool {
|
|
||||||
let start = va.raw();
|
|
||||||
let Some(len) = pages.checked_mul(PAGE_SIZE) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let Some(end) = start.checked_add(len) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
pages > 0
|
|
||||||
&& start.is_multiple_of(PAGE_SIZE)
|
|
||||||
&& start >= P::USER_VA_START
|
|
||||||
&& end <= P::USER_VA_END
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `pid`'s user page at `va`: the platform's page table says where it
|
|
||||||
/// points, the frame table confirms the frame really is that page.
|
|
||||||
fn user_frame(
|
|
||||||
&self,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
) -> Option<(PhysicalAddress, PagePerms)> {
|
|
||||||
let pa = self.platform.translate(pid, va)?;
|
|
||||||
self.frames.user_at(pa, pid, va).map(|perms| (pa, perms))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_mapped(&self, pid: ProcessId, va: VirtualAddress) -> bool {
|
|
||||||
self.user_frame(pid, va).is_some() || self.regions.covering(pid, va).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn range_is_free(&self, pid: ProcessId, va: VirtualAddress, pages: usize) -> bool {
|
|
||||||
(0..pages).all(|i| !self.is_mapped(pid, page_at(va, i)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
|
||||||
self.quotas
|
|
||||||
.get_mut(pid.raw() as usize)
|
|
||||||
.ok_or(KernelError::InvalidProcess(pid))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
|
||||||
self.quotas.get(pid.raw() as usize).map_or(0, Quota::used)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_page_limit(&mut self, pid: ProcessId, limit: usize) -> Result<(), KernelError> {
|
|
||||||
self.processes.get(pid)?;
|
|
||||||
if self.quota_mut(pid)?.set_limit(limit) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(KernelError::OutOfMemory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free_page_count(&self) -> usize {
|
|
||||||
self.frames.free_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps `pa` at `va` in `pid`, building page tables on `pid`'s quota.
|
|
||||||
fn platform_map(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
pa: PhysicalAddress,
|
|
||||||
perms: PagePerms,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
let quota = self
|
|
||||||
.quotas
|
|
||||||
.get_mut(pid.raw() as usize)
|
|
||||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
|
||||||
let mut source = ChargedFrames {
|
|
||||||
frames: &mut self.frames,
|
|
||||||
quota,
|
|
||||||
owner: pid,
|
|
||||||
};
|
|
||||||
self.platform
|
|
||||||
.map_page(pid, va, pa, perms, &mut source)
|
|
||||||
.map_err(map_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
|
||||||
let quota = self
|
|
||||||
.quotas
|
|
||||||
.get_mut(pid.raw() as usize)
|
|
||||||
.ok_or(KernelError::InvalidProcess(pid))?;
|
|
||||||
*quota = Quota::default();
|
|
||||||
let mut source = ChargedFrames {
|
|
||||||
frames: &mut self.frames,
|
|
||||||
quota,
|
|
||||||
owner: pid,
|
|
||||||
};
|
|
||||||
let created = self
|
|
||||||
.platform
|
|
||||||
.create_address_space(pid, &mut source)
|
|
||||||
.map_err(map_error);
|
|
||||||
if created.is_err() {
|
|
||||||
self.frames.free_owned_by(pid);
|
|
||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
|
||||||
*q = Quota::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
created
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates, zeroes and maps one fresh page for `pid` at `va`.
|
|
||||||
fn map_fresh_page(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
perms: PagePerms,
|
|
||||||
) -> Result<PhysicalAddress, KernelError> {
|
|
||||||
let usage = FrameUse::user(pid, va, perms).ok_or(KernelError::InvalidAddress)?;
|
|
||||||
if !self.quota_mut(pid)?.charge(1) {
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
}
|
|
||||||
let Some(pa) = self.frames.alloc(usage) else {
|
|
||||||
self.quota_mut(pid)?.uncharge(1);
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
};
|
|
||||||
self.platform.zero_frame(pa);
|
|
||||||
if let Err(e) = self.platform_map(pid, va, pa, perms) {
|
|
||||||
self.frames.free(pa);
|
|
||||||
self.quota_mut(pid)?.uncharge(1);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
Ok(pa)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes one user page of `pid` and returns its frame to the pool.
|
|
||||||
fn unmap_and_free(&mut self, pid: ProcessId, va: VirtualAddress) {
|
|
||||||
if let Some((pa, _)) = self.user_frame(pid, va) {
|
|
||||||
self.platform.unmap_page(pid, va);
|
|
||||||
self.frames.free(pa);
|
|
||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
|
||||||
q.uncharge(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Frame backing the user page at `va` in `pid`. Lets boot code fill
|
|
||||||
/// pages it has just mapped for a process.
|
|
||||||
pub fn user_page_frame(&self, pid: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
|
||||||
self.user_frame(pid, va).map(|(pa, _)| pa)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps `pages` fresh zeroed pages at `va`, all or nothing.
|
|
||||||
pub fn map_fresh_range(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
pages: usize,
|
|
||||||
perms: PagePerms,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
if !Self::in_user_window(va, pages) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
if !self.range_is_free(pid, va, pages) {
|
|
||||||
return Err(KernelError::AddressInUse);
|
|
||||||
}
|
|
||||||
for i in 0..pages {
|
|
||||||
if let Err(e) = self.map_fresh_page(pid, page_at(va, i), perms) {
|
|
||||||
for j in 0..i {
|
|
||||||
self.unmap_and_free(pid, page_at(va, j));
|
|
||||||
}
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Next-fit search for `pages` free pages in the heap part of the
|
|
||||||
/// window: from just past `pid`'s previous request, then once more from
|
|
||||||
/// the bottom. Requests in a row cost O(pages) each instead of
|
|
||||||
/// rescanning everything allocated before.
|
|
||||||
pub(crate) fn find_free_heap_range(
|
|
||||||
&self,
|
|
||||||
pid: ProcessId,
|
|
||||||
pages: usize,
|
|
||||||
) -> Option<VirtualAddress> {
|
|
||||||
let bottom = USER_HEAP_START.max(P::USER_VA_START);
|
|
||||||
let cursor = self
|
|
||||||
.heap_cursors
|
|
||||||
.get(pid.raw() as usize)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(0)
|
|
||||||
.max(bottom);
|
|
||||||
self.first_fit_from(pid, cursor, pages)
|
|
||||||
.or_else(|| self.first_fit_from(pid, bottom, pages))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records where `pid`'s next heap search starts.
|
|
||||||
pub(crate) fn advance_heap_cursor(&mut self, pid: ProcessId, past: VirtualAddress) {
|
|
||||||
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
|
||||||
*c = past.raw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
|
||||||
let mut candidate = from;
|
|
||||||
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
|
||||||
let va = VirtualAddress::from_raw(candidate);
|
|
||||||
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
|
||||||
None => return Some(va),
|
|
||||||
Some(busy) => candidate += (busy + 1) * PAGE_SIZE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates that every page of the range is a user page `pid` owns,
|
|
||||||
/// then releases them all.
|
|
||||||
pub(crate) fn release_range(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
pages: usize,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
if !Self::in_user_window(va, pages) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
if !(0..pages).all(|i| self.user_frame(pid, page_at(va, i)).is_some()) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
for i in 0..pages {
|
|
||||||
self.unmap_and_free(pid, page_at(va, i));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Moves the page at `src` in `from` to `dst` in `to`. The frame and its
|
|
||||||
/// quota charge change hands; `from` loses access.
|
|
||||||
pub(crate) fn move_page(
|
|
||||||
&mut self,
|
|
||||||
from: ProcessId,
|
|
||||||
src: VirtualAddress,
|
|
||||||
to: ProcessId,
|
|
||||||
dst: VirtualAddress,
|
|
||||||
perms: PagePerms,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
if !Self::in_user_window(src, 1) || !Self::in_user_window(dst, 1) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
let (pa, _) = self
|
|
||||||
.user_frame(from, src)
|
|
||||||
.ok_or(KernelError::InvalidAddress)?;
|
|
||||||
self.processes.get(to)?;
|
|
||||||
if !self.range_is_free(to, dst, 1) {
|
|
||||||
return Err(KernelError::AddressInUse);
|
|
||||||
}
|
|
||||||
let usage = FrameUse::user(to, dst, perms).ok_or(KernelError::InvalidAddress)?;
|
|
||||||
if !self.quota_mut(to)?.charge(1) {
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
}
|
|
||||||
if let Err(e) = self.platform_map(to, dst, pa, perms) {
|
|
||||||
self.quota_mut(to)?.uncharge(1);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
self.platform.unmap_page(from, src);
|
|
||||||
self.frames.retag(pa, usage);
|
|
||||||
self.quota_mut(from)?.uncharge(1);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a physically contiguous, zeroed region charged to `owner`.
|
|
||||||
pub fn create_pool_region(
|
|
||||||
&mut self,
|
|
||||||
owner: ProcessId,
|
|
||||||
pages: PageCount,
|
|
||||||
) -> Result<RegionId, KernelError> {
|
|
||||||
self.processes.get(owner)?;
|
|
||||||
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
|
||||||
let count = pages.value() as usize;
|
|
||||||
if !self.quota_mut(owner)?.charge(count) {
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
}
|
|
||||||
let Some(base) = self
|
|
||||||
.frames
|
|
||||||
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
|
||||||
else {
|
|
||||||
self.quota_mut(owner)?.uncharge(count);
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
};
|
|
||||||
for i in 0..count {
|
|
||||||
self.platform.zero_frame(base.offset(i));
|
|
||||||
}
|
|
||||||
self.regions
|
|
||||||
.insert(Region::new(owner, RegionBacking::Pool, base, pages))
|
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes device memory at `base` so it can be mapped like a region.
|
|
||||||
pub fn create_device_region(
|
|
||||||
&mut self,
|
|
||||||
owner: ProcessId,
|
|
||||||
base: PhysicalAddress,
|
|
||||||
pages: PageCount,
|
|
||||||
) -> Result<RegionId, KernelError> {
|
|
||||||
self.processes.get(owner)?;
|
|
||||||
if !base.raw().is_multiple_of(PAGE_SIZE) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
// Device memory must never alias frames the kernel hands out.
|
|
||||||
let len = pages.value() as usize * PAGE_SIZE;
|
|
||||||
let start = base.raw();
|
|
||||||
let end = start.checked_add(len).ok_or(KernelError::InvalidAddress)?;
|
|
||||||
let pool_start = self.frames.base().raw();
|
|
||||||
let pool_end = pool_start + self.frames.capacity() * PAGE_SIZE;
|
|
||||||
if start < pool_end && end > pool_start {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
// Nor another device's window: each device has one region, so
|
|
||||||
// revoking or delegating it means the same thing everywhere.
|
|
||||||
let overlaps = self.regions.iter().any(|(_, r)| {
|
|
||||||
let r_start = r.base().raw();
|
|
||||||
let r_end = r_start.saturating_add(r.pages().value() as usize * PAGE_SIZE);
|
|
||||||
r.backing() == RegionBacking::Device && start < r_end && end > r_start
|
|
||||||
});
|
|
||||||
if overlaps {
|
|
||||||
return Err(KernelError::AddressInUse);
|
|
||||||
}
|
|
||||||
self.regions
|
|
||||||
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
|
||||||
self.regions.get(id).map(Region::base)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region_pages(&self, id: RegionId) -> Option<PageCount> {
|
|
||||||
self.regions.get(id).map(Region::pages)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps all of region `id` at `va` in `pid`, all or nothing.
|
|
||||||
/// Maps region `id` for boot code; the mapping is not tied to any
|
|
||||||
/// capability and lasts until the region or the process dies.
|
|
||||||
pub fn map_region(
|
|
||||||
&mut self,
|
|
||||||
id: RegionId,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
perms: PagePerms,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
self.map_region_via(id, pid, va, perms, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps all of region `id` at `va` in `pid`, all or nothing. A mapping
|
|
||||||
/// made through a capability (`via`) is removed when that capability
|
|
||||||
/// is deleted.
|
|
||||||
pub(crate) fn map_region_via(
|
|
||||||
&mut self,
|
|
||||||
id: RegionId,
|
|
||||||
pid: ProcessId,
|
|
||||||
va: VirtualAddress,
|
|
||||||
perms: PagePerms,
|
|
||||||
via: Option<domain::capability::CapRef>,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
self.processes.get(pid)?;
|
|
||||||
let region = self.regions.get(id).ok_or(KernelError::InvalidRegion)?;
|
|
||||||
let (base, pages) = (region.base(), region.pages().value() as usize);
|
|
||||||
if region.mappings().any(|m| m.process == pid) {
|
|
||||||
return Err(KernelError::AddressInUse);
|
|
||||||
}
|
|
||||||
// W^X holds across processes too: nobody may write what someone
|
|
||||||
// else can execute.
|
|
||||||
let clashes = |other: PagePerms| {
|
|
||||||
matches!(
|
|
||||||
(perms, other),
|
|
||||||
(PagePerms::ReadWrite, PagePerms::ReadExecute)
|
|
||||||
| (PagePerms::ReadExecute, PagePerms::ReadWrite)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if region.mappings().any(|m| clashes(m.perms)) {
|
|
||||||
return Err(KernelError::WritableAndExecutable);
|
|
||||||
}
|
|
||||||
if !Self::in_user_window(va, pages) {
|
|
||||||
return Err(KernelError::InvalidAddress);
|
|
||||||
}
|
|
||||||
if !self.range_is_free(pid, va, pages) {
|
|
||||||
return Err(KernelError::AddressInUse);
|
|
||||||
}
|
|
||||||
for i in 0..pages {
|
|
||||||
if let Err(e) = self.platform_map(pid, page_at(va, i), base.offset(i), perms) {
|
|
||||||
for j in 0..i {
|
|
||||||
self.platform.unmap_page(pid, page_at(va, j));
|
|
||||||
}
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let recorded = self.regions.get_mut(id).is_some_and(|r| {
|
|
||||||
r.add_mapping(RegionMapping {
|
|
||||||
process: pid,
|
|
||||||
va,
|
|
||||||
perms,
|
|
||||||
via,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if !recorded {
|
|
||||||
for i in 0..pages {
|
|
||||||
self.platform.unmap_page(pid, page_at(va, i));
|
|
||||||
}
|
|
||||||
return Err(KernelError::NoFreeSlot);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes `pid`'s mapping of region `id`, if it has one.
|
|
||||||
pub(crate) fn unmap_region_from(&mut self, id: RegionId, pid: ProcessId) {
|
|
||||||
let Some(region) = self.regions.get_mut(id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let pages = region.pages().value() as usize;
|
|
||||||
if let Some(m) = region.remove_mapping(pid) {
|
|
||||||
for i in 0..pages {
|
|
||||||
self.platform.unmap_page(pid, page_at(m.va, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Frees regions owned by `pid` (unmapping them from every other
|
|
||||||
/// process) and forgets `pid`'s mappings of other regions.
|
|
||||||
fn teardown_regions(&mut self, pid: ProcessId) {
|
|
||||||
let mut owned = [None; domain::memory::MAX_REGIONS];
|
|
||||||
for (slot, id) in owned.iter_mut().zip(self.regions.ids_owned_by(pid)) {
|
|
||||||
*slot = Some(id);
|
|
||||||
}
|
|
||||||
for id in owned.into_iter().flatten() {
|
|
||||||
let Some(region) = self.regions.remove(id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let pages = region.pages().value() as usize;
|
|
||||||
for m in region.mappings().filter(|m| m.process != pid) {
|
|
||||||
for i in 0..pages {
|
|
||||||
self.platform.unmap_page(m.process, page_at(m.va, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if region.backing() == RegionBacking::Pool {
|
|
||||||
for i in 0..pages {
|
|
||||||
self.frames.free(region.base().offset(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (_, region) in self.regions.iter_mut() {
|
|
||||||
region.remove_mapping(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Releases everything `pid` holds in memory: its address space, page
|
|
||||||
/// tables, user pages and owned regions.
|
|
||||||
pub(crate) fn teardown_memory(&mut self, pid: ProcessId) {
|
|
||||||
self.teardown_regions(pid);
|
|
||||||
self.platform.destroy_address_space(pid);
|
|
||||||
self.frames.free_owned_by(pid);
|
|
||||||
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
|
||||||
*q = Quota::default();
|
|
||||||
}
|
|
||||||
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
|
||||||
*c = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn page_at(base: VirtualAddress, i: usize) -> VirtualAddress {
|
|
||||||
VirtualAddress::from_raw(base.raw() + i * PAGE_SIZE)
|
|
||||||
}
|
|
||||||
214
crates/kernel/src/memory/mod.rs
Normal file
214
crates/kernel/src/memory/mod.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
//! Address-space bookkeeping. The kernel decides every user mapping from
|
||||||
|
//! its own records (`FrameTable`, `RegionTable`, quotas) and only then tells
|
||||||
|
//! the platform to apply it; it never asks the hardware what is mapped.
|
||||||
|
|
||||||
|
mod pages;
|
||||||
|
mod region;
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError,
|
||||||
|
memory::{
|
||||||
|
frame::{FrameTable, FrameUse, PAGE_SIZE},
|
||||||
|
quota::Quota,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use platform::{FrameSource, MapError, Platform};
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
id::ProcessId,
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// First VA the kernel considers when picking a spot for `RequestPages`.
|
||||||
|
/// Below it sit the program image and boot-time device windows.
|
||||||
|
pub(crate) const USER_HEAP_START: usize = 0x40000;
|
||||||
|
|
||||||
|
/// Page-table frames for one address space, charged to its owner.
|
||||||
|
struct ChargedFrames<'a> {
|
||||||
|
frames: &'a mut FrameTable,
|
||||||
|
quota: &'a mut Quota,
|
||||||
|
owner: ProcessId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameSource for ChargedFrames<'_> {
|
||||||
|
fn alloc_table_frame(&mut self) -> Option<PhysicalAddress> {
|
||||||
|
if !self.quota.charge(1) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match self.frames.alloc(FrameUse::PageTable { owner: self.owner }) {
|
||||||
|
Some(pa) => Some(pa),
|
||||||
|
None => {
|
||||||
|
self.quota.uncharge(1);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_error(e: MapError) -> KernelError {
|
||||||
|
match e {
|
||||||
|
MapError::OutOfFrames => KernelError::OutOfMemory,
|
||||||
|
MapError::InvalidAddress => KernelError::InvalidAddress,
|
||||||
|
MapError::NoAddressSpace => KernelError::OutOfMemory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Whether `[va, va + pages)` is page aligned and inside the user window.
|
||||||
|
pub(crate) fn in_user_window(va: VirtualAddress, pages: usize) -> bool {
|
||||||
|
let start = va.raw();
|
||||||
|
let Some(len) = pages.checked_mul(PAGE_SIZE) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(end) = start.checked_add(len) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
pages > 0
|
||||||
|
&& start.is_multiple_of(PAGE_SIZE)
|
||||||
|
&& start >= P::USER_VA_START
|
||||||
|
&& end <= P::USER_VA_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `pid`'s user page at `va`: the platform's page table says where it
|
||||||
|
/// points, the frame table confirms the frame really is that page.
|
||||||
|
fn user_frame(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
) -> Option<(PhysicalAddress, PagePerms)> {
|
||||||
|
let pa = self.platform.translate(pid, va)?;
|
||||||
|
self.frames.user_at(pa, pid, va).map(|perms| (pa, perms))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_mapped(&self, pid: ProcessId, va: VirtualAddress) -> bool {
|
||||||
|
self.user_frame(pid, va).is_some() || self.regions.covering(pid, va).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_is_free(&self, pid: ProcessId, va: VirtualAddress, pages: usize) -> bool {
|
||||||
|
(0..pages).all(|i| !self.is_mapped(pid, page_at(va, i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
||||||
|
self.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
||||||
|
self.quotas.get(pid.raw() as usize).map_or(0, Quota::used)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_page_limit(&mut self, pid: ProcessId, limit: usize) -> Result<(), KernelError> {
|
||||||
|
self.processes.get(pid)?;
|
||||||
|
if self.quota_mut(pid)?.set_limit(limit) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(KernelError::OutOfMemory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_page_count(&self) -> usize {
|
||||||
|
self.frames.free_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps `pa` at `va` in `pid`, building page tables on `pid`'s quota.
|
||||||
|
fn platform_map(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pa: PhysicalAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let quota = self
|
||||||
|
.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||||
|
let mut source = ChargedFrames {
|
||||||
|
frames: &mut self.frames,
|
||||||
|
quota,
|
||||||
|
owner: pid,
|
||||||
|
};
|
||||||
|
self.platform
|
||||||
|
.map_page(pid, va, pa, perms, &mut source)
|
||||||
|
.map_err(map_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
||||||
|
let quota = self
|
||||||
|
.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||||
|
*quota = Quota::default();
|
||||||
|
let mut source = ChargedFrames {
|
||||||
|
frames: &mut self.frames,
|
||||||
|
quota,
|
||||||
|
owner: pid,
|
||||||
|
};
|
||||||
|
let created = self
|
||||||
|
.platform
|
||||||
|
.create_address_space(pid, &mut source)
|
||||||
|
.map_err(map_error);
|
||||||
|
if created.is_err() {
|
||||||
|
self.frames.free_owned_by(pid);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
*q = Quota::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
created
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates, zeroes and maps one fresh page for `pid` at `va`.
|
||||||
|
fn map_fresh_page(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<PhysicalAddress, KernelError> {
|
||||||
|
let usage = FrameUse::user(pid, va, perms).ok_or(KernelError::InvalidAddress)?;
|
||||||
|
if !self.quota_mut(pid)?.charge(1) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
let Some(pa) = self.frames.alloc(usage) else {
|
||||||
|
self.quota_mut(pid)?.uncharge(1);
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
};
|
||||||
|
self.platform.zero_frame(pa);
|
||||||
|
if let Err(e) = self.platform_map(pid, va, pa, perms) {
|
||||||
|
self.frames.free(pa);
|
||||||
|
self.quota_mut(pid)?.uncharge(1);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes one user page of `pid` and returns its frame to the pool.
|
||||||
|
fn unmap_and_free(&mut self, pid: ProcessId, va: VirtualAddress) {
|
||||||
|
if let Some((pa, _)) = self.user_frame(pid, va) {
|
||||||
|
self.platform.unmap_page(pid, va);
|
||||||
|
self.frames.free(pa);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
q.uncharge(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases everything `pid` holds in memory: its address space, page
|
||||||
|
/// tables, user pages and owned regions.
|
||||||
|
pub(crate) fn teardown_memory(&mut self, pid: ProcessId) {
|
||||||
|
self.teardown_regions(pid);
|
||||||
|
self.platform.destroy_address_space(pid);
|
||||||
|
self.frames.free_owned_by(pid);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
*q = Quota::default();
|
||||||
|
}
|
||||||
|
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||||
|
*c = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_at(base: VirtualAddress, i: usize) -> VirtualAddress {
|
||||||
|
VirtualAddress::from_raw(base.raw() + i * PAGE_SIZE)
|
||||||
|
}
|
||||||
138
crates/kernel/src/memory/pages.rs
Normal file
138
crates/kernel/src/memory/pages.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
//! Page moves, release and heap placement.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError,
|
||||||
|
memory::frame::{FrameUse, PAGE_SIZE},
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
id::ProcessId,
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{USER_HEAP_START, page_at};
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Frame backing the user page at `va` in `pid`. Lets boot code fill
|
||||||
|
/// pages it has just mapped for a process.
|
||||||
|
pub fn user_page_frame(&self, pid: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
|
self.user_frame(pid, va).map(|(pa, _)| pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps `pages` fresh zeroed pages at `va`, all or nothing.
|
||||||
|
pub fn map_fresh_range(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pages: usize,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !self.range_is_free(pid, va, pages) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
if let Err(e) = self.map_fresh_page(pid, page_at(va, i), perms) {
|
||||||
|
for j in 0..i {
|
||||||
|
self.unmap_and_free(pid, page_at(va, j));
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Next-fit search for `pages` free heap pages from `pid`'s last request, then from the bottom.
|
||||||
|
pub(crate) fn find_free_heap_range(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
pages: usize,
|
||||||
|
) -> Option<VirtualAddress> {
|
||||||
|
let bottom = USER_HEAP_START.max(P::USER_VA_START);
|
||||||
|
let cursor = self
|
||||||
|
.heap_cursors
|
||||||
|
.get(pid.raw() as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.max(bottom);
|
||||||
|
self.first_fit_from(pid, cursor, pages)
|
||||||
|
.or_else(|| self.first_fit_from(pid, bottom, pages))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records where `pid`'s next heap search starts.
|
||||||
|
pub(crate) fn advance_heap_cursor(&mut self, pid: ProcessId, past: VirtualAddress) {
|
||||||
|
if let Some(c) = self.heap_cursors.get_mut(pid.raw() as usize) {
|
||||||
|
*c = past.raw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_fit_from(&self, pid: ProcessId, from: usize, pages: usize) -> Option<VirtualAddress> {
|
||||||
|
let mut candidate = from;
|
||||||
|
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
||||||
|
let va = VirtualAddress::from_raw(candidate);
|
||||||
|
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
||||||
|
None => return Some(va),
|
||||||
|
Some(busy) => candidate += (busy + 1) * PAGE_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that every page of the range is a user page `pid` owns,
|
||||||
|
/// then releases them all.
|
||||||
|
pub(crate) fn release_range(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pages: usize,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !(0..pages).all(|i| self.user_frame(pid, page_at(va, i)).is_some()) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
self.unmap_and_free(pid, page_at(va, i));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the page at `src` in `from` to `dst` in `to`. The frame and its
|
||||||
|
/// quota charge change hands; `from` loses access.
|
||||||
|
pub(crate) fn move_page(
|
||||||
|
&mut self,
|
||||||
|
from: ProcessId,
|
||||||
|
src: VirtualAddress,
|
||||||
|
to: ProcessId,
|
||||||
|
dst: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(src, 1) || !Self::in_user_window(dst, 1) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
let (pa, _) = self
|
||||||
|
.user_frame(from, src)
|
||||||
|
.ok_or(KernelError::InvalidAddress)?;
|
||||||
|
self.processes.get(to)?;
|
||||||
|
if !self.range_is_free(to, dst, 1) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
let usage = FrameUse::user(to, dst, perms).ok_or(KernelError::InvalidAddress)?;
|
||||||
|
if !self.quota_mut(to)?.charge(1) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
if let Err(e) = self.platform_map(to, dst, pa, perms) {
|
||||||
|
self.quota_mut(to)?.uncharge(1);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
self.platform.unmap_page(from, src);
|
||||||
|
self.frames.retag(pa, usage);
|
||||||
|
self.quota_mut(from)?.uncharge(1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
201
crates/kernel/src/memory/region.rs
Normal file
201
crates/kernel/src/memory/region.rs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
//! Shared and device regions and their mappings.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError,
|
||||||
|
memory::{
|
||||||
|
Region, RegionBacking, RegionMapping,
|
||||||
|
frame::{FrameUse, PAGE_SIZE},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
bounded::PageCount,
|
||||||
|
id::{ProcessId, RegionId},
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::page_at;
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Allocates a physically contiguous, zeroed region charged to `owner`.
|
||||||
|
pub fn create_pool_region(
|
||||||
|
&mut self,
|
||||||
|
owner: ProcessId,
|
||||||
|
pages: PageCount,
|
||||||
|
) -> Result<RegionId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
let count = pages.value() as usize;
|
||||||
|
if !self.quota_mut(owner)?.charge(count) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
let Some(base) = self
|
||||||
|
.frames
|
||||||
|
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
||||||
|
else {
|
||||||
|
self.quota_mut(owner)?.uncharge(count);
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
};
|
||||||
|
for i in 0..count {
|
||||||
|
self.platform.zero_frame(base.offset(i));
|
||||||
|
}
|
||||||
|
self.regions
|
||||||
|
.insert(Region::new(owner, RegionBacking::Pool, base, pages))
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes device memory at `base` so it can be mapped like a region.
|
||||||
|
pub fn create_device_region(
|
||||||
|
&mut self,
|
||||||
|
owner: ProcessId,
|
||||||
|
base: PhysicalAddress,
|
||||||
|
pages: PageCount,
|
||||||
|
) -> Result<RegionId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
if !base.raw().is_multiple_of(PAGE_SIZE) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
// Device memory must never alias frames the kernel hands out.
|
||||||
|
let len = pages.value() as usize * PAGE_SIZE;
|
||||||
|
let start = base.raw();
|
||||||
|
let end = start.checked_add(len).ok_or(KernelError::InvalidAddress)?;
|
||||||
|
let pool_start = self.frames.base().raw();
|
||||||
|
let pool_end = pool_start + self.frames.capacity() * PAGE_SIZE;
|
||||||
|
if start < pool_end && end > pool_start {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
// Nor another device's window: each device has one region, so
|
||||||
|
// revoking or delegating it means the same thing everywhere.
|
||||||
|
let overlaps = self.regions.iter().any(|(_, r)| {
|
||||||
|
let r_start = r.base().raw();
|
||||||
|
let r_end = r_start.saturating_add(r.pages().value() as usize * PAGE_SIZE);
|
||||||
|
r.backing() == RegionBacking::Device && start < r_end && end > r_start
|
||||||
|
});
|
||||||
|
if overlaps {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
self.regions
|
||||||
|
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
||||||
|
self.regions.get(id).map(Region::base)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn region_pages(&self, id: RegionId) -> Option<PageCount> {
|
||||||
|
self.regions.get(id).map(Region::pages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps region `id` for boot code, all or nothing; not tied to any capability.
|
||||||
|
pub fn map_region(
|
||||||
|
&mut self,
|
||||||
|
id: RegionId,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
self.map_region_via(id, pid, va, perms, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps region `id`, all or nothing; a mapping made `via` a capability dies with it.
|
||||||
|
pub(crate) fn map_region_via(
|
||||||
|
&mut self,
|
||||||
|
id: RegionId,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
via: Option<domain::capability::CapRef>,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
self.processes.get(pid)?;
|
||||||
|
let region = self.regions.get(id).ok_or(KernelError::InvalidRegion)?;
|
||||||
|
let (base, pages) = (region.base(), region.pages().value() as usize);
|
||||||
|
if region.mappings().any(|m| m.process == pid) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
// W^X holds across processes too: nobody may write what someone
|
||||||
|
// else can execute.
|
||||||
|
let clashes = |other: PagePerms| {
|
||||||
|
matches!(
|
||||||
|
(perms, other),
|
||||||
|
(PagePerms::ReadWrite, PagePerms::ReadExecute)
|
||||||
|
| (PagePerms::ReadExecute, PagePerms::ReadWrite)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if region.mappings().any(|m| clashes(m.perms)) {
|
||||||
|
return Err(KernelError::WritableAndExecutable);
|
||||||
|
}
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !self.range_is_free(pid, va, pages) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
if let Err(e) = self.platform_map(pid, page_at(va, i), base.offset(i), perms) {
|
||||||
|
for j in 0..i {
|
||||||
|
self.platform.unmap_page(pid, page_at(va, j));
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recorded = self.regions.get_mut(id).is_some_and(|r| {
|
||||||
|
r.add_mapping(RegionMapping {
|
||||||
|
process: pid,
|
||||||
|
va,
|
||||||
|
perms,
|
||||||
|
via,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if !recorded {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(pid, page_at(va, i));
|
||||||
|
}
|
||||||
|
return Err(KernelError::NoFreeSlot);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes `pid`'s mapping of region `id`, if it has one.
|
||||||
|
pub(crate) fn unmap_region_from(&mut self, id: RegionId, pid: ProcessId) {
|
||||||
|
let Some(region) = self.regions.get_mut(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let pages = region.pages().value() as usize;
|
||||||
|
if let Some(m) = region.remove_mapping(pid) {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(pid, page_at(m.va, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees regions owned by `pid` (unmapping them from every other
|
||||||
|
/// process) and forgets `pid`'s mappings of other regions.
|
||||||
|
pub(super) fn teardown_regions(&mut self, pid: ProcessId) {
|
||||||
|
let mut owned = [None; domain::memory::MAX_REGIONS];
|
||||||
|
for (slot, id) in owned.iter_mut().zip(self.regions.ids_owned_by(pid)) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(region) = self.regions.remove(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let pages = region.pages().value() as usize;
|
||||||
|
for m in region.mappings().filter(|m| m.process != pid) {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(m.process, page_at(m.va, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if region.backing() == RegionBacking::Pool {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.frames.free(region.base().offset(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (_, region) in self.regions.iter_mut() {
|
||||||
|
region.remove_mapping(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
//! Mint creates no authority. The real kernel, with tables shrunk under
|
//! Mint creates no authority: after `mint` with arbitrary arguments, no capability to a
|
||||||
//! `cfg(kani)`, runs `mint` with arbitrary arguments in a small state
|
//! pre-existing object carries rights beyond some earlier one. Other operations exceeded
|
||||||
//! whose capabilities carry arbitrary rights. Afterwards every capability
|
//! 12 GB in CBMC; tests/storm.rs and tests/captree_model.rs cover them.
|
||||||
//! to an object that already existed carries no more rights than some
|
|
||||||
//! capability to that object carried before.
|
|
||||||
//!
|
|
||||||
//! Only this harness is kept. The same check over delegate, delete,
|
|
||||||
//! revoke, create and kill either exceeded 12 GB in CBMC or tripped
|
|
||||||
//! modelling artifacts ("unreachable code" on safe `Option` matches that
|
|
||||||
//! an exhaustive host replay never reaches). Those operations are covered
|
|
||||||
//! by the syscall storm's global invariants (tests/storm.rs) and the
|
|
||||||
//! capability-tree model test (tests/captree_model.rs) instead.
|
|
||||||
|
|
||||||
use super::platform::KaniPlatform;
|
use super::platform::KaniPlatform;
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
//! Cores. Every thread has a home core, chosen when it is created, and
|
//! Cores: each thread has a home core and never migrates; waking or removing a thread on
|
||||||
//! only ever runs there: it waits in that core's run queue and is never
|
//! another core sends that core an IPI when it must re-check its queue.
|
||||||
//! migrated. Making a thread ready from another core sends that core an
|
|
||||||
//! IPI when it has to look at its queue again (it is idle, or the thread
|
|
||||||
//! outranks what it is running), and so does taking away the thread a
|
|
||||||
//! core is running.
|
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
scheduler::{Scheduler, SchedulerDecision},
|
scheduler::{Scheduler, SchedulerDecision},
|
||||||
@@ -88,10 +84,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.threads.get(tid).ok().map(|t| t.priority())
|
self.threads.get(tid).ok().map(|t| t.priority())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What `core` should do after an IPI: keep running its thread, or
|
/// What `core` does after an IPI: keep its thread or switch to a higher-priority one;
|
||||||
/// switch to a higher-priority one now waiting. When it has no thread
|
/// with no thread the caller picks.
|
||||||
/// (it was idle, or its thread was destroyed) the caller picks the
|
|
||||||
/// next one itself.
|
|
||||||
pub fn reschedule(&mut self, core: CoreId) -> SchedulerDecision {
|
pub fn reschedule(&mut self, core: CoreId) -> SchedulerDecision {
|
||||||
let Some(current) = self.scheduler.current(core) else {
|
let Some(current) = self.scheduler.current(core) else {
|
||||||
return SchedulerDecision::Continue;
|
return SchedulerDecision::Continue;
|
||||||
|
|||||||
@@ -1,121 +1,22 @@
|
|||||||
//! Endpoint IPC, notifications and interrupt delivery. docs/ipc.md is the
|
//! Endpoint send/receive, call/reply, capability transfer and minting.
|
||||||
//! specification; the comments below only point at the non-obvious rules.
|
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
capability::Capability,
|
capability::Capability, error::KernelError, message::Message, resource::Resource,
|
||||||
error::KernelError,
|
thread::BlockReason,
|
||||||
message::Message,
|
|
||||||
notification::SignalEffect,
|
|
||||||
resource::Resource,
|
|
||||||
scheduler::Scheduler,
|
|
||||||
thread::{BlockReason, ThreadState},
|
|
||||||
};
|
};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::{
|
use types::{
|
||||||
badge::Badge,
|
badge::Badge,
|
||||||
bounded::CapSlot,
|
bounded::CapSlot,
|
||||||
generation::Generation,
|
id::{EndpointId, ProcessId, ThreadId},
|
||||||
id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId},
|
|
||||||
permission::{Permission, PermissionSet},
|
permission::{Permission, PermissionSet},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
use super::super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
||||||
|
use super::Outgoing;
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
/// A thread named by id and generation, so a stale reference can be told
|
|
||||||
/// apart from a new thread that reused the id.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct ThreadRef {
|
|
||||||
pub tid: ThreadId,
|
|
||||||
pub generation: Generation,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A message a blocked sender is waiting to hand over.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub(crate) struct Outgoing {
|
|
||||||
message: Message,
|
|
||||||
badge: Badge,
|
|
||||||
/// The sender's endpoint cap had `Delegate`, so a capability may travel.
|
|
||||||
grant: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Per-thread IPC bookkeeping.
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
|
||||||
pub struct IpcState {
|
|
||||||
pub(crate) outgoing: Option<Outgoing>,
|
|
||||||
/// The caller this thread owes a reply to.
|
|
||||||
pub(crate) reply_to: Option<ThreadRef>,
|
|
||||||
pub(crate) bound: Option<NotificationId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rw_delegate() -> PermissionSet {
|
|
||||||
PermissionSet::empty()
|
|
||||||
.with(Permission::Read)
|
|
||||||
.with(Permission::Write)
|
|
||||||
.with(Permission::Delegate)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub(crate) fn ipc(&self, tid: ThreadId) -> Option<&IpcState> {
|
|
||||||
self.ipc.get(tid.raw() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn ipc_mut(&mut self, tid: ThreadId) -> Option<&mut IpcState> {
|
|
||||||
self.ipc.get_mut(tid.raw() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_pending_result(&mut self, tid: ThreadId, result: Option<SyscallResult>) {
|
|
||||||
if let Some(r) = self.pending_results.get_mut(tid.raw() as usize) {
|
|
||||||
*r = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn thread_ref(&self, tid: ThreadId) -> Option<ThreadRef> {
|
|
||||||
Some(ThreadRef {
|
|
||||||
tid,
|
|
||||||
generation: self.threads.generation(tid)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_live(&self, r: ThreadRef) -> bool {
|
|
||||||
self.threads.generation(r.tid) == Some(r.generation)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_blocked_as(&self, tid: ThreadId, f: impl Fn(&BlockReason) -> bool) -> bool {
|
|
||||||
matches!(self.threads.get(tid).map(|t| *t.state()), Ok(ThreadState::Blocked(r)) if f(&r))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blocks the running caller.
|
|
||||||
pub(crate) fn block_caller(&mut self, tid: ThreadId, reason: BlockReason) -> SyscallOutcome {
|
|
||||||
if let Ok(t) = self.threads.get_mut(tid) {
|
|
||||||
let _ = t.block(reason);
|
|
||||||
}
|
|
||||||
self.scheduler.remove(tid);
|
|
||||||
SyscallOutcome::BlockCaller(reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a blocked thread runnable with `result` as its syscall result.
|
|
||||||
pub(crate) fn wake(&mut self, tid: ThreadId, result: SyscallResult) {
|
|
||||||
let Ok(thread) = self.threads.get_mut(tid) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let priority = thread.priority();
|
|
||||||
if thread.unblock().is_ok() {
|
|
||||||
self.set_pending_result(tid, Some(result));
|
|
||||||
self.make_ready(tid, priority);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Releases a caller waiting on `reply` with `NoReply`, if it is still
|
|
||||||
/// that caller and still waiting.
|
|
||||||
pub(crate) fn release_caller(&mut self, reply: ThreadRef) {
|
|
||||||
if self.is_live(reply)
|
|
||||||
&& self.is_blocked_as(reply.tid, |r| *r == BlockReason::AwaitingReply)
|
|
||||||
{
|
|
||||||
self.wake(reply.tid, SyscallResult::Error(KernelError::NoReply));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn endpoint_cap(
|
fn endpoint_cap(
|
||||||
&self,
|
&self,
|
||||||
pid: ProcessId,
|
pid: ProcessId,
|
||||||
@@ -133,22 +34,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notification_cap(
|
|
||||||
&self,
|
|
||||||
pid: ProcessId,
|
|
||||||
slot: CapSlot,
|
|
||||||
needed: Permission,
|
|
||||||
) -> Result<(NotificationId, Badge, Generation), KernelError> {
|
|
||||||
let cap = self.check_cap(pid, slot, needed)?;
|
|
||||||
match cap.resource() {
|
|
||||||
Resource::Notification(id) => Ok((*id, cap.badge(), *cap.generation())),
|
|
||||||
_ => Err(KernelError::WrongObject),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies the capability in `slot` of `from` into `to`'s table, if the
|
|
||||||
/// transfer is allowed and still valid. Returns where it landed.
|
/// transfer is allowed and still valid. Returns where it landed.
|
||||||
fn transfer_cap(
|
pub(super) fn transfer_cap(
|
||||||
&mut self,
|
&mut self,
|
||||||
from: ProcessId,
|
from: ProcessId,
|
||||||
slot: Option<CapSlot>,
|
slot: Option<CapSlot>,
|
||||||
@@ -169,7 +56,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
|
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
|
||||||
/// reply slot now names the sender.
|
/// reply slot now names the sender.
|
||||||
fn deliver(
|
pub(super) fn deliver(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender: ThreadId,
|
sender: ThreadId,
|
||||||
out: Outgoing,
|
out: Outgoing,
|
||||||
@@ -413,157 +300,4 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- notifications ----
|
|
||||||
|
|
||||||
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
|
||||||
self.processes.get(owner)?;
|
|
||||||
self.notifications
|
|
||||||
.create(owner)
|
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create_notification_cap(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
) -> Result<CapSlot, KernelError> {
|
|
||||||
let id = self.create_notification(pid)?;
|
|
||||||
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
|
||||||
.inspect_err(|_| {
|
|
||||||
self.notifications.destroy(id);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signals `id` with `badge`; shared by `Signal` and interrupts.
|
|
||||||
fn signal_notification(&mut self, id: NotificationId, badge: Badge) {
|
|
||||||
let bound_receiving = self
|
|
||||||
.notifications
|
|
||||||
.get(id)
|
|
||||||
.and_then(|n| n.bound())
|
|
||||||
.is_some_and(|t| self.is_blocked_as(t, |r| matches!(r, BlockReason::Receiving(_))));
|
|
||||||
let Some(ntfn) = self.notifications.get_mut(id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
match ntfn.signal(badge, bound_receiving) {
|
|
||||||
SignalEffect::WakeWaiter(t, bits) => {
|
|
||||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
|
||||||
}
|
|
||||||
SignalEffect::WakeBound(t, bits) => {
|
|
||||||
self.leave_queue(t);
|
|
||||||
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
|
||||||
}
|
|
||||||
SignalEffect::Stored => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn signal(&mut self, pid: ProcessId, slot: CapSlot) -> Result<(), KernelError> {
|
|
||||||
let (id, badge, _) = self.notification_cap(pid, slot, Permission::Write)?;
|
|
||||||
self.signal_notification(id, badge);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn wait(&mut self, tid: ThreadId, pid: ProcessId, slot: CapSlot) -> SyscallOutcome {
|
|
||||||
let id = match self.notification_cap(pid, slot, Permission::Read) {
|
|
||||||
Ok((id, _, _)) => id,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let Some(ntfn) = self.notifications.get_mut(id) else {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::NoSuchObject));
|
|
||||||
};
|
|
||||||
if ntfn.has_bits() {
|
|
||||||
let bits = ntfn.take_bits();
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
|
||||||
}
|
|
||||||
if !ntfn.add_waiter(tid) {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
|
||||||
}
|
|
||||||
self.block_caller(tid, BlockReason::WaitingNotification(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn poll(&mut self, pid: ProcessId, slot: CapSlot) -> Result<usize, KernelError> {
|
|
||||||
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
|
||||||
self.notifications
|
|
||||||
.get_mut(id)
|
|
||||||
.map(|n| n.take_bits())
|
|
||||||
.ok_or(KernelError::NoSuchObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn bind_notification(
|
|
||||||
&mut self,
|
|
||||||
tid: ThreadId,
|
|
||||||
pid: ProcessId,
|
|
||||||
slot: CapSlot,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
|
||||||
if self.ipc(tid).is_none_or(|s| s.bound.is_some()) {
|
|
||||||
return Err(KernelError::InvalidArgument);
|
|
||||||
}
|
|
||||||
let ntfn = self
|
|
||||||
.notifications
|
|
||||||
.get_mut(id)
|
|
||||||
.ok_or(KernelError::NoSuchObject)?;
|
|
||||||
if !ntfn.bind(tid) {
|
|
||||||
return Err(KernelError::InvalidArgument);
|
|
||||||
}
|
|
||||||
if let Some(s) = self.ipc_mut(tid) {
|
|
||||||
s.bound = Some(id);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- interrupts ----
|
|
||||||
|
|
||||||
fn interrupt_cap(&self, pid: ProcessId, slot: CapSlot) -> Result<IrqId, KernelError> {
|
|
||||||
match self.check_cap(pid, slot, Permission::Execute)?.resource() {
|
|
||||||
Resource::Interrupt(irq) => Ok(*irq),
|
|
||||||
_ => Err(KernelError::WrongObject),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_irq_notification(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
irq_slot: CapSlot,
|
|
||||||
ntfn_slot: CapSlot,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
let irq = self.interrupt_cap(pid, irq_slot)?;
|
|
||||||
let (notification, badge, generation) =
|
|
||||||
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
|
||||||
let route = domain::irq::IrqRoute {
|
|
||||||
notification,
|
|
||||||
generation,
|
|
||||||
badge,
|
|
||||||
};
|
|
||||||
if self.irqs.set(irq, route) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(KernelError::InvalidIrq(irq))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn ack_interrupt(
|
|
||||||
&mut self,
|
|
||||||
pid: ProcessId,
|
|
||||||
slot: CapSlot,
|
|
||||||
) -> Result<(), KernelError> {
|
|
||||||
let irq = self.interrupt_cap(pid, slot)?;
|
|
||||||
self.platform.unmask_irq(irq);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An interrupt fired: mask the line and signal its notification. The
|
|
||||||
/// line stays masked until the handler acknowledges it.
|
|
||||||
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
|
||||||
self.platform.mask_irq(irq);
|
|
||||||
let Some(route) = self.irqs.route(irq) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// A route set through a capability that has since been revoked, or
|
|
||||||
// to a notification whose slot was reused, is dead.
|
|
||||||
if self.notifications.generation(route.notification) != Some(route.generation) {
|
|
||||||
self.irqs.clear(irq);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.signal_notification(route.notification, route.badge);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
67
crates/kernel/src/syscall/ipc/irq.rs
Normal file
67
crates/kernel/src/syscall/ipc/irq.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//! Interrupt routing to notifications.
|
||||||
|
|
||||||
|
use domain::{error::KernelError, resource::Resource};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
bounded::CapSlot,
|
||||||
|
id::{IrqId, ProcessId},
|
||||||
|
permission::Permission,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
fn interrupt_cap(&self, pid: ProcessId, slot: CapSlot) -> Result<IrqId, KernelError> {
|
||||||
|
match self.check_cap(pid, slot, Permission::Execute)?.resource() {
|
||||||
|
Resource::Interrupt(irq) => Ok(*irq),
|
||||||
|
_ => Err(KernelError::WrongObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_irq_notification(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
irq_slot: CapSlot,
|
||||||
|
ntfn_slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||||
|
let (notification, badge, generation) =
|
||||||
|
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||||
|
let route = domain::irq::IrqRoute {
|
||||||
|
notification,
|
||||||
|
generation,
|
||||||
|
badge,
|
||||||
|
};
|
||||||
|
if self.irqs.set(irq, route) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(KernelError::InvalidIrq(irq))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ack_interrupt(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let irq = self.interrupt_cap(pid, slot)?;
|
||||||
|
self.platform.unmask_irq(irq);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An interrupt fired: mask the line and signal its notification. The
|
||||||
|
/// line stays masked until the handler acknowledges it.
|
||||||
|
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||||
|
self.platform.mask_irq(irq);
|
||||||
|
let Some(route) = self.irqs.route(irq) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// A route set through a capability that has since been revoked, or
|
||||||
|
// to a notification whose slot was reused, is dead.
|
||||||
|
if self.notifications.generation(route.notification) != Some(route.generation) {
|
||||||
|
self.irqs.clear(irq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.signal_notification(route.notification, route.badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
crates/kernel/src/syscall/ipc/mod.rs
Normal file
109
crates/kernel/src/syscall/ipc/mod.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//! Endpoint IPC, notifications and interrupt delivery (see docs/ipc.md).
|
||||||
|
|
||||||
|
mod endpoint;
|
||||||
|
mod irq;
|
||||||
|
mod notification;
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError,
|
||||||
|
message::Message,
|
||||||
|
scheduler::Scheduler,
|
||||||
|
thread::{BlockReason, ThreadState},
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
badge::Badge,
|
||||||
|
generation::Generation,
|
||||||
|
id::{NotificationId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{SyscallOutcome, SyscallResult};
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// A thread named by id and generation, so a stale reference can be told
|
||||||
|
/// apart from a new thread that reused the id.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ThreadRef {
|
||||||
|
pub tid: ThreadId,
|
||||||
|
pub generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A message a blocked sender is waiting to hand over.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct Outgoing {
|
||||||
|
message: Message,
|
||||||
|
badge: Badge,
|
||||||
|
/// The sender's endpoint cap had `Delegate`, so a capability may travel.
|
||||||
|
grant: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-thread IPC bookkeeping.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct IpcState {
|
||||||
|
pub(crate) outgoing: Option<Outgoing>,
|
||||||
|
/// The caller this thread owes a reply to.
|
||||||
|
pub(crate) reply_to: Option<ThreadRef>,
|
||||||
|
pub(crate) bound: Option<NotificationId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
pub(crate) fn ipc(&self, tid: ThreadId) -> Option<&IpcState> {
|
||||||
|
self.ipc.get(tid.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ipc_mut(&mut self, tid: ThreadId) -> Option<&mut IpcState> {
|
||||||
|
self.ipc.get_mut(tid.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_pending_result(&mut self, tid: ThreadId, result: Option<SyscallResult>) {
|
||||||
|
if let Some(r) = self.pending_results.get_mut(tid.raw() as usize) {
|
||||||
|
*r = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_ref(&self, tid: ThreadId) -> Option<ThreadRef> {
|
||||||
|
Some(ThreadRef {
|
||||||
|
tid,
|
||||||
|
generation: self.threads.generation(tid)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_live(&self, r: ThreadRef) -> bool {
|
||||||
|
self.threads.generation(r.tid) == Some(r.generation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_blocked_as(&self, tid: ThreadId, f: impl Fn(&BlockReason) -> bool) -> bool {
|
||||||
|
matches!(self.threads.get(tid).map(|t| *t.state()), Ok(ThreadState::Blocked(r)) if f(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks the running caller.
|
||||||
|
pub(crate) fn block_caller(&mut self, tid: ThreadId, reason: BlockReason) -> SyscallOutcome {
|
||||||
|
if let Ok(t) = self.threads.get_mut(tid) {
|
||||||
|
let _ = t.block(reason);
|
||||||
|
}
|
||||||
|
self.scheduler.remove(tid);
|
||||||
|
SyscallOutcome::BlockCaller(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a blocked thread runnable with `result` as its syscall result.
|
||||||
|
pub(crate) fn wake(&mut self, tid: ThreadId, result: SyscallResult) {
|
||||||
|
let Ok(thread) = self.threads.get_mut(tid) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let priority = thread.priority();
|
||||||
|
if thread.unblock().is_ok() {
|
||||||
|
self.set_pending_result(tid, Some(result));
|
||||||
|
self.make_ready(tid, priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases a caller waiting on `reply` with `NoReply`, if it is still
|
||||||
|
/// that caller and still waiting.
|
||||||
|
pub(crate) fn release_caller(&mut self, reply: ThreadRef) {
|
||||||
|
if self.is_live(reply)
|
||||||
|
&& self.is_blocked_as(reply.tid, |r| *r == BlockReason::AwaitingReply)
|
||||||
|
{
|
||||||
|
self.wake(reply.tid, SyscallResult::Error(KernelError::NoReply));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
crates/kernel/src/syscall/ipc/notification.rs
Normal file
133
crates/kernel/src/syscall/ipc/notification.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//! Notification objects: signal, wait, poll, bind.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError, notification::SignalEffect, resource::Resource, thread::BlockReason,
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
badge::Badge,
|
||||||
|
bounded::CapSlot,
|
||||||
|
generation::Generation,
|
||||||
|
id::{NotificationId, ProcessId, ThreadId},
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::{Delivery, SyscallOutcome, SyscallResult};
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
fn rw_delegate() -> PermissionSet {
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Read)
|
||||||
|
.with(Permission::Write)
|
||||||
|
.with(Permission::Delegate)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
pub(super) fn notification_cap(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
needed: Permission,
|
||||||
|
) -> Result<(NotificationId, Badge, Generation), KernelError> {
|
||||||
|
let cap = self.check_cap(pid, slot, needed)?;
|
||||||
|
match cap.resource() {
|
||||||
|
Resource::Notification(id) => Ok((*id, cap.badge(), *cap.generation())),
|
||||||
|
_ => Err(KernelError::WrongObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
self.notifications
|
||||||
|
.create(owner)
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_notification_cap(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
) -> Result<CapSlot, KernelError> {
|
||||||
|
let id = self.create_notification(pid)?;
|
||||||
|
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||||
|
.inspect_err(|_| {
|
||||||
|
self.notifications.destroy(id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals `id` with `badge`; shared by `Signal` and interrupts.
|
||||||
|
pub(super) fn signal_notification(&mut self, id: NotificationId, badge: Badge) {
|
||||||
|
let bound_receiving = self
|
||||||
|
.notifications
|
||||||
|
.get(id)
|
||||||
|
.and_then(|n| n.bound())
|
||||||
|
.is_some_and(|t| self.is_blocked_as(t, |r| matches!(r, BlockReason::Receiving(_))));
|
||||||
|
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match ntfn.signal(badge, bound_receiving) {
|
||||||
|
SignalEffect::WakeWaiter(t, bits) => {
|
||||||
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
SignalEffect::WakeBound(t, bits) => {
|
||||||
|
self.leave_queue(t);
|
||||||
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
SignalEffect::Stored => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn signal(&mut self, pid: ProcessId, slot: CapSlot) -> Result<(), KernelError> {
|
||||||
|
let (id, badge, _) = self.notification_cap(pid, slot, Permission::Write)?;
|
||||||
|
self.signal_notification(id, badge);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wait(&mut self, tid: ThreadId, pid: ProcessId, slot: CapSlot) -> SyscallOutcome {
|
||||||
|
let id = match self.notification_cap(pid, slot, Permission::Read) {
|
||||||
|
Ok((id, _, _)) => id,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::NoSuchObject));
|
||||||
|
};
|
||||||
|
if ntfn.has_bits() {
|
||||||
|
let bits = ntfn.take_bits();
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
if !ntfn.add_waiter(tid) {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||||
|
}
|
||||||
|
self.block_caller(tid, BlockReason::WaitingNotification(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll(&mut self, pid: ProcessId, slot: CapSlot) -> Result<usize, KernelError> {
|
||||||
|
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||||
|
self.notifications
|
||||||
|
.get_mut(id)
|
||||||
|
.map(|n| n.take_bits())
|
||||||
|
.ok_or(KernelError::NoSuchObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_notification(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||||
|
if self.ipc(tid).is_none_or(|s| s.bound.is_some()) {
|
||||||
|
return Err(KernelError::InvalidArgument);
|
||||||
|
}
|
||||||
|
let ntfn = self
|
||||||
|
.notifications
|
||||||
|
.get_mut(id)
|
||||||
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
|
if !ntfn.bind(tid) {
|
||||||
|
return Err(KernelError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if let Some(s) = self.ipc_mut(tid) {
|
||||||
|
s.bound = Some(id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,9 +51,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves one of the caller's pages into the process named by
|
/// Moves one of the caller's pages into `process_cap`'s process (exec pages RX, others RW).
|
||||||
/// `process_cap`. Executable pages are mapped read-execute, others
|
|
||||||
/// read-write.
|
|
||||||
pub(crate) fn handle_map_page(
|
pub(crate) fn handle_map_page(
|
||||||
&mut self,
|
&mut self,
|
||||||
caller_pid: ProcessId,
|
caller_pid: ProcessId,
|
||||||
@@ -99,9 +97,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.grant_cap(pid, Resource::Memory(id), all)
|
self.grant_cap(pid, Resource::Memory(id), all)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps the region behind `slot` into the caller. The capability must
|
/// Maps the region behind `slot` into the caller; the cap must allow `perms`, mapping lasts as long as the cap.
|
||||||
/// allow every access `perms` grants; the mapping lasts as long as
|
|
||||||
/// the capability.
|
|
||||||
pub(crate) fn map_region_cap(
|
pub(crate) fn map_region_cap(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: ProcessId,
|
pid: ProcessId,
|
||||||
@@ -125,9 +121,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.map_region_via(id, pid, va, perms, via)
|
self.map_region_via(id, pid, va, perms, via)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The physical base of the region behind `slot`. Needs Read on the
|
/// Physical base of the region behind `slot` (needs Read); pool regions are contiguous.
|
||||||
/// capability; pool regions are physically contiguous, so the base is
|
|
||||||
/// enough to program a device.
|
|
||||||
pub(crate) fn region_address_cap(
|
pub(crate) fn region_address_cap(
|
||||||
&self,
|
&self,
|
||||||
pid: ProcessId,
|
pid: ProcessId,
|
||||||
|
|||||||
126
crates/kernel/src/teardown.rs
Normal file
126
crates/kernel/src/teardown.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
//! Process, thread, endpoint and notification teardown.
|
||||||
|
|
||||||
|
use domain::{error::KernelError, notification::MAX_NOTIFICATIONS, thread::ThreadState};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::id::{ProcessId, ThreadId};
|
||||||
|
|
||||||
|
use crate::{Kernel, SyscallResult};
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
pub(crate) fn destroy_process(&mut self, pid: ProcessId) {
|
||||||
|
use domain::process::MAX_THREADS_PER_PROCESS;
|
||||||
|
|
||||||
|
let thread_ids: [Option<ThreadId>; MAX_THREADS_PER_PROCESS] = match self.processes.get(pid)
|
||||||
|
{
|
||||||
|
Ok(p) => {
|
||||||
|
let mut ids = [None; MAX_THREADS_PER_PROCESS];
|
||||||
|
for (slot, tid) in ids.iter_mut().zip(p.thread_ids()) {
|
||||||
|
*slot = Some(tid);
|
||||||
|
}
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cleanup_threads(&thread_ids);
|
||||||
|
self.delete_all_caps(pid);
|
||||||
|
self.destroy_endpoints_owned_by(pid);
|
||||||
|
self.destroy_notifications_owned_by(pid);
|
||||||
|
self.teardown_memory(pid);
|
||||||
|
|
||||||
|
let _ = self.processes.destroy(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
||||||
|
use domain::scheduler::Scheduler;
|
||||||
|
for tid in thread_ids.iter().flatten() {
|
||||||
|
self.leave_queue(*tid);
|
||||||
|
if let Some(n) = self
|
||||||
|
.ipc(*tid)
|
||||||
|
.and_then(|s| s.bound)
|
||||||
|
.and_then(|n| self.notifications.get_mut(n))
|
||||||
|
{
|
||||||
|
n.remove_thread(*tid);
|
||||||
|
}
|
||||||
|
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
||||||
|
if let Some(caller) = ipc.reply_to {
|
||||||
|
self.release_caller(caller);
|
||||||
|
}
|
||||||
|
self.evict(*tid);
|
||||||
|
self.scheduler.remove(*tid);
|
||||||
|
self.set_pending_result(*tid, None);
|
||||||
|
let _ = self.threads.destroy(*tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a blocked thread off the one queue its block reason names, so
|
||||||
|
/// nothing needs to scan every endpoint and notification.
|
||||||
|
pub(crate) fn leave_queue(&mut self, tid: ThreadId) {
|
||||||
|
use domain::thread::BlockReason::*;
|
||||||
|
let Ok(ThreadState::Blocked(reason)) = self.threads.get(tid).map(|t| *t.state()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match reason {
|
||||||
|
Sending(ep) | Calling(ep) => {
|
||||||
|
self.endpoints.get_mut(ep).map(|e| e.remove_sender(tid));
|
||||||
|
}
|
||||||
|
Receiving(ep) => {
|
||||||
|
self.endpoints.get_mut(ep).map(|e| e.remove_receiver(tid));
|
||||||
|
}
|
||||||
|
WaitingNotification(n) => {
|
||||||
|
if let Some(n) = self.notifications.get_mut(n) {
|
||||||
|
n.remove_waiter(tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AwaitingReply => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys every endpoint `owner` created, waking anyone blocked on
|
||||||
|
/// them with an error instead of leaving them blocked forever.
|
||||||
|
pub(crate) fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
|
||||||
|
let mut owned = [None; domain::endpoint::MAX_ENDPOINTS];
|
||||||
|
for (slot, id) in owned.iter_mut().zip(self.endpoints.ids_owned_by(owner)) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(mut ep) = self.endpoints.destroy(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
while let Some(tid) = ep.dequeue_sender().or_else(|| ep.dequeue_receiver()) {
|
||||||
|
self.wake_with_error(tid, KernelError::InvalidEndpoint(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys every notification `owner` created, waking its waiters
|
||||||
|
/// with an error and unbinding its bound thread.
|
||||||
|
pub(crate) fn destroy_notifications_owned_by(&mut self, owner: ProcessId) {
|
||||||
|
let mut owned = [None; MAX_NOTIFICATIONS];
|
||||||
|
for (slot, id) in owned.iter_mut().zip(self.notifications.ids_owned_by(owner)) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(mut n) = self.notifications.destroy(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(bound) = n.bound()
|
||||||
|
&& let Some(s) = self.ipc_mut(bound)
|
||||||
|
{
|
||||||
|
s.bound = None;
|
||||||
|
}
|
||||||
|
while let Some(tid) = n.pop_waiter() {
|
||||||
|
self.wake_with_error(tid, KernelError::NoSuchObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a blocked thread runnable with `error` as its syscall result,
|
||||||
|
/// dropping whatever it was about to send.
|
||||||
|
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
|
||||||
|
if let Some(s) = self.ipc_mut(tid) {
|
||||||
|
s.outgoing = None;
|
||||||
|
}
|
||||||
|
self.wake(tid, SyscallResult::Error(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
//! Worst-case timings of kernel paths that scan tables. Not run by default:
|
//! Worst-case timings of table-scanning paths; ignored by default:
|
||||||
//! `cargo test -p kernel --lib bench -- --ignored --nocapture --test-threads 1`.
|
//! `cargo test -p kernel --lib bench -- --ignored --nocapture --test-threads 1`.
|
||||||
//! Numbers are host nanoseconds at the test profile's opt-level, good for
|
|
||||||
//! comparing before and after a change, not for absolute claims.
|
|
||||||
|
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
//! Randomized check of the derivation tree against an independent model.
|
//! Randomized check of the derivation tree against a model of the rules in docs/ipc.md.
|
||||||
//! The model applies the rules of docs/ipc.md (derive records a parent,
|
|
||||||
//! delete re-parents, revoke removes the subtree, death deletes everything
|
|
||||||
//! a process holds) to its own data; after every step the kernel must hold
|
|
||||||
//! exactly the capabilities the model says are alive.
|
|
||||||
|
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
//! Randomized check of the IPC state machine. Six threads in three
|
//! Randomized IPC state machine check: queues, block reasons, reply slots and run queue
|
||||||
//! processes send, call, receive, reply and signal at random over two
|
//! agree after every step, and no message is duplicated.
|
||||||
//! endpoints and a notification; one process is periodically killed and
|
|
||||||
//! re-created. After every step the kernel's queues, block reasons, reply
|
|
||||||
//! slots and run queue must agree, and no message may be duplicated.
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::syscall::{Delivery, DeliveryKind};
|
use crate::syscall::{Delivery, DeliveryKind};
|
||||||
|
|||||||
@@ -303,9 +303,8 @@ impl Rng {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up a fresh round: a hub process owning the shared endpoints,
|
/// Sets up a round: a hub owning shared endpoints, notification and region; two children
|
||||||
/// notification and region; two children with two threads each; and caps
|
/// with two threads each; caps to the shared objects for init and the children.
|
||||||
/// to all shared objects for init and the children.
|
|
||||||
fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadId) {
|
fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadId) {
|
||||||
use domain::resource::Resource;
|
use domain::resource::Resource;
|
||||||
let all = types::permission::PermissionSet::empty()
|
let all = types::permission::PermissionSet::empty()
|
||||||
@@ -416,10 +415,8 @@ fn the_kernel_survives_a_syscall_storm() {
|
|||||||
storm(1);
|
storm(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same storm with threads homed on four cores. Each step runs on one
|
/// The same storm with threads homed on four cores; each step runs as that core's thread
|
||||||
/// core, as the thread that core is running; the kernel is still entered
|
/// and IPIs arrive late.
|
||||||
/// one call at a time (stage 1 takes a single lock), but threads on other
|
|
||||||
/// cores are live meanwhile and IPIs arrive late.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_kernel_survives_a_syscall_storm_on_four_cores() {
|
fn the_kernel_survives_a_syscall_storm_on_four_cores() {
|
||||||
storm(4);
|
storm(4);
|
||||||
@@ -502,10 +499,7 @@ fn storm(cores: u8) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
let mut args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
||||||
// Random arguments almost never name a region cap, a free
|
// Half the time pick one of the caller's region caps so regions really get shared.
|
||||||
// address and valid perms at once; half the time, pick one of
|
|
||||||
// the caller's region caps (often the hub's shared region) so
|
|
||||||
// regions really are mapped into several processes.
|
|
||||||
if nr == SYS_MAP_REGION && rng.below(2) == 0 {
|
if nr == SYS_MAP_REGION && rng.below(2) == 0 {
|
||||||
let regions = region_slots(&k, k.thread_process(tid));
|
let regions = region_slots(&k, k.thread_process(tid));
|
||||||
if !regions.is_empty() {
|
if !regions.is_empty() {
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ pub enum MapError {
|
|||||||
/// The kernel's contract with an architecture. The kernel decides what is
|
/// The kernel's contract with an architecture. The kernel decides what is
|
||||||
/// mapped where; the platform only applies those decisions to hardware.
|
/// mapped where; the platform only applies those decisions to hardware.
|
||||||
pub trait Platform {
|
pub trait Platform {
|
||||||
/// User mappings live in `[USER_VA_START, USER_VA_END)`. The platform
|
/// User mappings live in `[USER_VA_START, USER_VA_END)`; nothing else is mapped there in any space.
|
||||||
/// guarantees nothing else is mapped in that window in any address
|
|
||||||
/// space, so page tables covering it are private to each process.
|
|
||||||
const USER_VA_START: usize;
|
const USER_VA_START: usize;
|
||||||
const USER_VA_END: usize;
|
const USER_VA_END: usize;
|
||||||
|
|
||||||
@@ -50,9 +48,7 @@ pub trait Platform {
|
|||||||
) -> Result<(), MapError>;
|
) -> Result<(), MapError>;
|
||||||
/// Removes the mapping at `va` and flushes it from the TLB.
|
/// Removes the mapping at `va` and flushes it from the TLB.
|
||||||
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress);
|
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress);
|
||||||
/// The frame `va` is mapped to in `process`, if any. Only a lookup:
|
/// The frame `va` maps to in `process`, if any. Advisory: the kernel confirms against its frame table.
|
||||||
/// the kernel confirms every answer against its own frame table, so a
|
|
||||||
/// wrong answer can make the kernel refuse but never trust a frame.
|
|
||||||
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress>;
|
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress>;
|
||||||
/// Fills a frame with zeros before it is handed to a process.
|
/// Fills a frame with zeros before it is handed to a process.
|
||||||
fn zero_frame(&mut self, pa: PhysicalAddress);
|
fn zero_frame(&mut self, pa: PhysicalAddress);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/// A word stamped on an endpoint or notification capability by `Mint`.
|
/// A word stamped on an endpoint or notification cap by `Mint`; zero means unbadged.
|
||||||
/// Receivers see the badge of the capability a sender used, which is how a
|
|
||||||
/// server tells its clients apart. Zero means "unbadged".
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Badge(usize);
|
pub struct Badge(usize);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
//! What boot tells init: a read-only page at `BOOT_INFO_VA` saying which
|
//! What boot tells init: a read-only page at `BOOT_INFO_VA` mapping init's capability slots to board devices.
|
||||||
//! of init's capability slots hold which board device (seL4's BootInfo,
|
|
||||||
//! reduced to what this system needs). Everything else init learns by
|
|
||||||
//! asking the devices themselves.
|
|
||||||
|
|
||||||
/// Where the page is mapped in init.
|
/// Where the page is mapped in init.
|
||||||
pub const BOOT_INFO_VA: usize = 0x2F000;
|
pub const BOOT_INFO_VA: usize = 0x2F000;
|
||||||
@@ -11,9 +8,7 @@ pub const MAX_BOOT_DEVICES: usize = 16;
|
|||||||
/// A slot field with no capability in it.
|
/// A slot field with no capability in it.
|
||||||
pub const NO_SLOT: u64 = u64::MAX;
|
pub const NO_SLOT: u64 = u64::MAX;
|
||||||
|
|
||||||
/// What kind of device a board entry is. Board code knows; init asks the
|
/// What kind of device a board entry is; init asks the device itself to disambiguate.
|
||||||
/// device itself only where a kind has several possibilities (which
|
|
||||||
/// virtio device sits in a slot).
|
|
||||||
pub const KIND_VIRTIO: u64 = 1;
|
pub const KIND_VIRTIO: u64 = 1;
|
||||||
pub const KIND_UART_16550: u64 = 2;
|
pub const KIND_UART_16550: u64 = 2;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use crate::generation::Generation;
|
use crate::generation::Generation;
|
||||||
|
|
||||||
/// Generations only ever increase, so a capability minted for an earlier
|
/// Generations only increase, so a capability minted for an earlier generation never matches again.
|
||||||
/// generation can never match again. (The old wrapping counter returned to
|
|
||||||
/// earlier values after 2^32 bumps.)
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
fn generation_next_is_strictly_greater_or_exhausted() {
|
fn generation_next_is_strictly_greater_or_exhausted() {
|
||||||
let value: u32 = kani::any();
|
let value: u32 = kani::any();
|
||||||
|
|||||||
Reference in New Issue
Block a user