Files
k-os/crates/domain/src/memory/mod.rs
Gabriel Kaszewski 154599e3af feat(kernel): derivation-tree revocation, region caps
- caps record parent (mint/delegate/ipc), CapRef w/ pid+slot gens
- Revoke = delete subtree, keep self; drop/death re-parents children
- CreateRegion/MapRegion; mapping lives only as long as its cap
- drop Permission::Revoke + object-wide revoke
- mint any cap (badge only ep/ntfn), process caps delegable
- kani: descendants == subtree; model-based fuzz vs spec
2026-10-26 20:41:03 +01:00

208 lines
5.8 KiB
Rust

use crate::{capability::CapRef, generations::SlotGenerations};
use types::{
address::{PhysicalAddress, VirtualAddress},
bounded::PageCount,
generation::Generation,
id::{ProcessId, RegionId},
mapping::PagePerms,
};
pub mod frame;
pub mod quota;
#[cfg(test)]
#[path = "quota_tests.rs"]
mod quota_tests;
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
pub const MAX_REGIONS: usize = 64;
pub const MAX_REGION_MAPPINGS: usize = 4;
/// Where a region's frames come from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegionBacking {
/// Frames from the user pool, freed with the region.
Pool,
/// Device MMIO; never freed.
Device,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegionMapping {
pub process: ProcessId,
pub va: VirtualAddress,
pub perms: PagePerms,
/// The capability the mapping was made through. Deleting that
/// capability removes the mapping; `None` for kernel-made mappings.
pub via: Option<CapRef>,
}
/// Physically contiguous memory that may be mapped into several address
/// spaces. `owner` is charged for pool-backed frames.
pub struct Region {
owner: ProcessId,
backing: RegionBacking,
base: PhysicalAddress,
pages: PageCount,
mappings: [Option<RegionMapping>; MAX_REGION_MAPPINGS],
}
impl Region {
pub fn new(
owner: ProcessId,
backing: RegionBacking,
base: PhysicalAddress,
pages: PageCount,
) -> Self {
Self {
owner,
backing,
base,
pages,
mappings: [None; MAX_REGION_MAPPINGS],
}
}
pub fn owner(&self) -> ProcessId {
self.owner
}
pub fn backing(&self) -> RegionBacking {
self.backing
}
pub fn base(&self) -> PhysicalAddress {
self.base
}
pub fn pages(&self) -> PageCount {
self.pages
}
pub fn mappings(&self) -> impl Iterator<Item = &RegionMapping> {
self.mappings.iter().flatten()
}
/// Records that `mapping.process` maps this region. Each process maps a
/// region at most once.
pub fn add_mapping(&mut self, mapping: RegionMapping) -> bool {
if self.mappings().any(|m| m.process == mapping.process) {
return false;
}
match self.mappings.iter_mut().find(|m| m.is_none()) {
Some(slot) => {
*slot = Some(mapping);
true
}
None => false,
}
}
pub fn remove_mapping(&mut self, process: ProcessId) -> Option<RegionMapping> {
self.mappings
.iter_mut()
.find(|m| m.is_some_and(|m| m.process == process))?
.take()
}
/// Whether `va` falls inside this region's mapping in `process`.
pub fn covers(&self, process: ProcessId, va: VirtualAddress) -> bool {
let len = self.pages.value() as usize * frame::PAGE_SIZE;
self.mappings()
.any(|m| m.process == process && va.raw() >= m.va.raw() && va.raw() - m.va.raw() < len)
}
}
pub struct RegionTable {
regions: [Option<Region>; MAX_REGIONS],
generations: SlotGenerations<MAX_REGIONS>,
}
impl RegionTable {
pub fn new() -> Self {
Self {
regions: [const { None }; MAX_REGIONS],
generations: SlotGenerations::new(),
}
}
/// Generation capabilities to a live region must carry.
pub fn generation(&self, id: RegionId) -> Option<Generation> {
self.get(id)?;
self.generations.current(id.raw() as usize)
}
fn free_slot(&self) -> Option<usize> {
(0..MAX_REGIONS).find(|&i| self.regions[i].is_none() && self.generations.is_usable(i))
}
/// The id the next `insert` will return.
pub fn next_free_id(&self) -> Option<RegionId> {
let idx = self.free_slot()?;
Some(RegionId::from_raw(idx as u16))
}
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
let idx = self.free_slot()?;
self.regions[idx] = Some(region);
Some(RegionId::from_raw(idx as u16))
}
pub fn get(&self, id: RegionId) -> Option<&Region> {
self.regions.get(id.raw() as usize)?.as_ref()
}
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut Region> {
self.regions.get_mut(id.raw() as usize)?.as_mut()
}
/// Removes the region; capabilities to it become invalid even if the
/// slot is later reused.
pub fn remove(&mut self, id: RegionId) -> Option<Region> {
let region = self.regions.get_mut(id.raw() as usize)?.take()?;
self.generations.advance(id.raw() as usize);
Some(region)
}
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = RegionId> + '_ {
self.regions.iter().enumerate().filter_map(move |(i, r)| {
r.as_ref()
.filter(|r| r.owner == owner)
.map(|_| RegionId::from_raw(i as u16))
})
}
pub fn iter(&self) -> impl Iterator<Item = (RegionId, &Region)> {
self.regions
.iter()
.enumerate()
.filter_map(|(i, r)| r.as_ref().map(|r| (RegionId::from_raw(i as u16), r)))
}
/// Every region, by id, so callers can drop a process's mappings.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut Region)> {
self.regions
.iter_mut()
.enumerate()
.filter_map(|(i, r)| r.as_mut().map(|r| (RegionId::from_raw(i as u16), r)))
}
/// The region whose mapping in `process` covers `va`.
pub fn covering(&self, process: ProcessId, va: VirtualAddress) -> Option<RegionId> {
self.regions.iter().enumerate().find_map(|(i, r)| {
r.as_ref()
.filter(|r| r.covers(process, va))
.map(|_| RegionId::from_raw(i as u16))
})
}
}
impl Default for RegionTable {
fn default() -> Self {
Self::new()
}
}