Skip to main content

faces_pfm/
mgr.rs

1//! Page frame manager implementation for `faces`.
2//!
3//! This module provides a concrete singleton manager (`PFM`) that implements
4//! `faces::AbsPageFrameManager`. It uses a static array of `spin::Mutex<PageFrame>`
5//! to store per‑frame metadata, initialised from the memory map provided by the
6//! Limine boot protocol.
7
8use crate::{PageFlags, PageFrame};
9use faces::{AbsPageFrameManager, Convertable as _, PhysicalAddress, to};
10use log::debug;
11
12/// The page frame manager singleton.
13///
14/// This type implements `AbsPageFrameManager` and is the central point for
15/// manipulating page frame flags and accessing per‑frame metadata.
16#[derive(Debug, Copy, Clone, Default)]
17pub struct PageFrameManager;
18
19/// Global page frame manager instance.
20///
21/// This is the singleton instance used throughout the system. It is safe to
22/// access because all methods are either read‑only or use internal locking.
23pub static PFM: PageFrameManager = PageFrameManager::new();
24
25type Fa = &'static mut [spin::Mutex<PageFrame>];
26
27/// Static slice of locked page frame metadata.
28///
29/// Each entry is a `spin::Mutex<PageFrame>` protecting the per‑frame data.
30/// The slice is initialised during `PageFrameManager::init()` and must be
31/// considered immutable afterwards.
32static mut FRAME_ARRAY: &mut [spin::Mutex<PageFrame>] = Fa::default();
33
34/// Helper macro to obtain a locked guard for a given physical frame number.
35///
36/// # Safety
37/// This macro dereferences `FRAME_ARRAY` mutably and must only be used after
38/// the array has been initialised by `PageFrameManager::init()`.
39macro_rules! frame { ($pfn:expr) => {unsafe{FRAME_ARRAY[to($pfn)].lock()}} }
40
41/// Limine memory map request structure.
42///
43/// This forces the bootloader to provide a complete memory map, which is used
44/// to determine usable RAM and to allocate the frame metadata array.
45#[used]
46#[unsafe(link_section = ".requests")]
47static MMAP: limine::request::MemmapRequest =  limine::request::MemmapRequest::new();
48
49impl PageFrameManager {
50    /// Creates a new uninitialised page frame manager.
51    ///
52    /// The manager is not usable until `init()` is called.
53    pub const fn new() -> Self { Self {} }
54
55    /// Detects the total amount of physical memory from the Limine memory map.
56    ///
57    /// Sums the lengths of all memory map entries. This is used to calculate
58    /// the total number of 4 KiB page frames.
59    fn detect_total_physical_memory() -> usize {
60        let memmap = MMAP.response().expect("Failed to obtain memory map").entries();
61        let mut total = 0;
62        for entry in memmap {
63            total += entry.length as usize;
64        }
65        total
66    }
67
68    /// Finds a suitable region of physical memory to place the frame metadata array.
69    ///
70    /// # Arguments
71    /// * `num_frames` - Number of `PageFrame` entries to allocate.
72    ///
73    /// # Returns
74    /// A tuple `(base_physical_address, size_in_bytes)` of the chosen region.
75    ///
76    /// # Panics
77    /// Panics if no usable memory region of sufficient size is found.
78    fn find_better_place(num_frames: usize) -> (PhysicalAddress, usize) {
79        let memmap = MMAP.response().expect("Failed to obtain memory map").entries();
80        let required_size = (num_frames * core::mem::size_of::<PageFrame>()).next_multiple_of(4096);
81        let mut best_start = 0;
82        let mut best_len = 0;
83
84        for entry in memmap {
85            if entry.type_ != limine::memmap::MEMMAP_USABLE {
86                continue;
87            }
88            let len = entry.length as usize;
89            if len >= required_size && len > best_len {
90                best_len = len;
91                best_start = entry.base;
92            }
93        }
94        if best_len == 0 {
95            panic!("No suitable memory region for PageFrameInfo array (need {} bytes)", required_size);
96        }
97        (to(best_start as usize), required_size)
98    }
99
100    /// Initialises the global page frame manager.
101    ///
102    /// This function:
103    /// 1. Detects total physical memory and computes the number of frames.
104    /// 2. Finds a physical memory region large enough to hold the `PageFrame` array.
105    /// 3. Maps that region into the kernel’s virtual address space.
106    /// 4. Initialises each `PageFrame` with default values.
107    /// 5. Sets the `RESERVED` flag for all frames, then clears it for usable RAM entries.
108    /// 6. Finally reserves the frames that contain the metadata array itself.
109    ///
110    /// # Panics
111    /// Panics if the memory map is unavailable or if no suitable region is found.
112    pub fn init() {
113        let this = Self{};
114        let total_memory = Self::detect_total_physical_memory();
115        let num_frames = total_memory >> 12;
116        let (phys_addr, size) = Self::find_better_place(num_frames);
117        let virt_addr = phys_addr.to() << 12;
118        let addr: &mut spin::Mutex<PageFrame>;
119        unsafe {
120            addr = faces::unsafe_to(to::<faces::VirtualAddress, _>(virt_addr));
121        }
122
123        debug!("Total mem: {:#x}", total_memory);
124        debug!("Total frames: {}", num_frames);
125        debug!("PFI array address: {:#x}", virt_addr);
126
127        unsafe {
128            let slice = core::slice::from_raw_parts_mut::<'static, spin::Mutex<PageFrame>>(addr, num_frames);
129            for frame in slice.iter_mut() {
130                *frame = spin::Mutex::<PageFrame>::new(PageFrame::default());
131            }
132            FRAME_ARRAY = slice;
133        }
134
135        let memmap = MMAP.response().expect("Failed to obtain memory map").entries();
136
137        for idx in 0..num_frames {
138            this.set_flags(to(idx), PageFlags::RESERVED);
139        }
140
141        for entry in memmap {
142            if entry.type_ == limine::memmap::MEMMAP_USABLE {
143                let start = (entry.base / 4096) as usize;
144                let end = ((entry.base + entry.length + 4095) / 4096) as usize;
145                for idx in start..end.min(num_frames) {
146                    this.clear_flags(to(idx), PageFlags::RESERVED);
147                }
148            }
149        }
150
151        let array_start = to(phys_addr) >> 12;
152        let array_end = (array_start + size + 4095) >> 12;
153        for idx in array_start..array_end.min(num_frames) {
154            this.set_flags(to(idx), PageFlags::RESERVED);
155        }
156    }
157}
158
159/// Type alias for a physical frame number (PFN) as defined by `faces`.
160type PFN = faces::PageFrameNumber;
161
162impl AbsPageFrameManager for PageFrameManager {
163    type Flags = PageFlags;
164    type Access = spin::MutexGuard<'static, PageFrame, spin::Spin>;
165
166    /// Checks whether a given flag is set on the specified page frame.
167    fn check_flags(&self, pfn: PFN, flag: PageFlags) -> bool {
168        frame![pfn].flags & flag != PageFlags::empty()
169    }
170
171    /// Clears the specified flags on the given page frame.
172    fn clear_flags(&self, pfn: PFN, flag: PageFlags) {
173        frame![pfn].flags &= !flag
174    }
175
176    /// Returns the maximum possible page frame number.
177    ///
178    /// # TODO
179    /// This currently returns `PFN::MAX`, which may be larger than the actual
180    /// number of frames. A proper implementation should return the highest
181    /// valid PFN based on the detected memory size.
182    fn max(&self) -> PFN {
183        to(usize::MAX)
184    }
185
186    /// Returns the minimum possible page frame number (always 0).
187    fn min(&self) -> PFN {
188        to(0)
189    }
190
191    /// Returns whether a page frame is physically present.
192    ///
193    /// # TODO
194    /// This always returns `true`. A real implementation should validate
195    /// the PFN against the number of available frames.
196    fn present(&self, _pfn: PFN) -> bool {
197        true
198    }
199
200    /// Sets the specified flags on the given page frame.
201    fn set_flags(&self, pfn: PFN, flag: PageFlags) {
202        frame!(pfn).flags |= flag
203    }
204
205    /// Returns a locked guard that provides mutable access to the page frame metadata.
206    fn get(&self, pfn: PFN) -> Self::Access {
207        frame!(pfn)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::PageFrame;
215    use spin::Mutex;
216
217    /// Create a mock frame array of given length and leak it to a 'static mut slice.
218    /// Then assign it to FRAME_ARRAY for testing.
219    unsafe fn setup_mock_frames(num_frames: usize) {
220        // Build a vector of Mutex<PageFrame> initialised with default frames.
221        let mut vec = Vec::with_capacity(num_frames);
222        for _ in 0..num_frames {
223            vec.push(Mutex::new(PageFrame::default()));
224        }
225        // Leak the vector to obtain a 'static mut slice.
226        let slice: &'static mut [Mutex<PageFrame>] = Box::leak(vec.into_boxed_slice());
227        unsafe {
228            FRAME_ARRAY = slice;
229        }
230    }
231
232    /// Reset FRAME_ARRAY to an empty slice after each test to avoid cross-test interference.
233    unsafe fn teardown_mock_frames() {
234        unsafe {
235            FRAME_ARRAY = Fa::default();
236        }
237    }
238
239    #[test]
240    fn test_flag_operations() {
241        unsafe {
242            setup_mock_frames(10);
243        }
244
245        let pfm = PageFrameManager::new();
246        let pfn = faces::to(5);
247
248        // Initially no flags should be set.
249        assert!(!pfm.check_flags(pfn, PageFlags::LOCKED));
250        assert!(!pfm.check_flags(pfn, PageFlags::DIRTY));
251
252        // Set LOCKED flag.
253        pfm.set_flags(pfn, PageFlags::LOCKED);
254        assert!(pfm.check_flags(pfn, PageFlags::LOCKED));
255        assert!(!pfm.check_flags(pfn, PageFlags::DIRTY));
256
257        // Set DIRTY flag (should keep LOCKED).
258        pfm.set_flags(pfn, PageFlags::DIRTY);
259        assert!(pfm.check_flags(pfn, PageFlags::LOCKED));
260        assert!(pfm.check_flags(pfn, PageFlags::DIRTY));
261
262        // Clear LOCKED flag.
263        pfm.clear_flags(pfn, PageFlags::LOCKED);
264        assert!(!pfm.check_flags(pfn, PageFlags::LOCKED));
265        assert!(pfm.check_flags(pfn, PageFlags::DIRTY));
266
267        // Clear DIRTY flag.
268        pfm.clear_flags(pfn, PageFlags::DIRTY);
269        assert!(!pfm.check_flags(pfn, PageFlags::LOCKED));
270        assert!(!pfm.check_flags(pfn, PageFlags::DIRTY));
271
272        unsafe {
273            teardown_mock_frames();
274        }
275    }
276
277    #[test]
278    fn test_get_frame_mut() {
279        unsafe {
280            setup_mock_frames(3);
281        }
282
283        let pfm = PageFrameManager::new();
284        let pfn = faces::to(1);
285
286        // Obtain a guard and modify the frame directly.
287        {
288            let mut guard = pfm.get(pfn);
289            guard.flags = PageFlags::SWAPCACHE;
290            guard.order = 42;
291            guard.rc = 100;
292        }
293
294        // Verify changes via flag checks.
295        assert!(pfm.check_flags(pfn, PageFlags::SWAPCACHE));
296        // Also verify using get and reading back.
297        let guard = pfm.get(pfn);
298        assert_eq!(guard.order, 42);
299        assert_eq!(guard.rc, 100);
300
301        unsafe {
302            teardown_mock_frames();
303        }
304    }
305
306    #[test]
307    fn test_min_max_present() {
308        let pfm = PageFrameManager::new();
309        // present currently always returns true
310        assert!(pfm.present(faces::to(0)));
311        assert!(pfm.present(faces::to(12345)));
312    }
313
314    #[test]
315    fn test_frame_macro() {
316        unsafe {
317            setup_mock_frames(5);
318        }
319
320        let pfn: faces::PageFrameNumber = faces::to(2);
321        // The macro frame! should give a locked guard.
322        {
323            let mut guard = frame!(pfn);
324            guard.flags = PageFlags::COMPOUND;
325        }
326        assert!(PFM.check_flags(pfn, PageFlags::COMPOUND));
327
328        unsafe {
329            teardown_mock_frames();
330        }
331    }
332}