Skip to main content

strat9_kernel/memory/
mapping_index.rs

1//! Reverse mapping index for memory capabilities.
2
3use alloc::collections::BTreeMap;
4
5use smallvec::SmallVec;
6use x86_64::VirtAddr;
7
8use crate::{
9    capability::CapId, memory::address_space::VmaPageSize, process::task::Pid, sync::SpinLock,
10};
11
12/// Reference to a concrete mapping in an address space.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct MappingRef {
15    /// Process that owns the address space.
16    pub pid: Pid,
17    /// Virtual address of the mapping.
18    pub vaddr: VirtAddr,
19    /// Effective page size of the mapping.
20    pub page_size: VmaPageSize,
21}
22
23/// Reverse index from capability ID to live mappings.
24///
25/// The inline capacity of 8 covers the common case (a memory region mapped
26/// in the kernel + up to 7 user address spaces) without heap allocation.
27/// A `debug_assert` in `register` catches spills in test builds so we can
28/// detect if the inline capacity needs to grow further.
29///
30/// If a capability ever acquires more than 8 mappings, `SmallVec` spills to
31/// the heap while the `SpinLock` is held.  This is not an IRQ path and the
32/// heap lock order (mapping_index => heap) does not conflict with any other
33/// known lock order, so the spill is not a correctness issue : only a minor
34/// latency concern.
35pub struct MappingIndex {
36    index: SpinLock<BTreeMap<CapId, SmallVec<[MappingRef; 8]>>>,
37}
38
39impl MappingIndex {
40    /// Creates an empty reverse mapping index.
41    pub fn new() -> Self {
42        Self {
43            index: SpinLock::new(BTreeMap::new()),
44        }
45    }
46
47    /// Registers a mapping for the given capability.
48    pub fn register(&self, cap_id: CapId, mapping: MappingRef) {
49        let mut index = self.index.lock();
50        let mappings = index.entry(cap_id).or_default();
51        if !mappings.iter().any(|existing| *existing == mapping) {
52            let was_spilled = mappings.spilled();
53            mappings.push(mapping);
54            debug_assert!(
55                !mappings.spilled() || was_spilled,
56                "mapping_index: SmallVec spilled to heap for cap={:?} : consider growing inline capacity",
57                cap_id,
58            );
59        }
60    }
61
62    /// Removes a single mapping for the given capability.
63    pub fn unregister(&self, cap_id: CapId, pid: Pid, vaddr: VirtAddr) {
64        let mut index = self.index.lock();
65        let should_remove = if let Some(mappings) = index.get_mut(&cap_id) {
66            mappings.retain(|mapping| !(mapping.pid == pid && mapping.vaddr == vaddr));
67            mappings.is_empty()
68        } else {
69            false
70        };
71        if should_remove {
72            index.remove(&cap_id);
73        }
74    }
75
76    /// Returns a snapshot of the mappings for the given capability.
77    pub fn lookup(&self, cap_id: CapId) -> SmallVec<[MappingRef; 8]> {
78        self.index.lock().get(&cap_id).cloned().unwrap_or_default()
79    }
80
81    /// Removes and returns every mapping associated with the given capability.
82    pub fn remove_all(&self, cap_id: CapId) -> SmallVec<[MappingRef; 8]> {
83        self.index.lock().remove(&cap_id).unwrap_or_default()
84    }
85}
86
87impl Default for MappingIndex {
88    /// Creates an empty reverse mapping index.
89    fn default() -> Self {
90        Self::new()
91    }
92}