Skip to main content

kernel/mem/
pmm.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! Physical Memory Manager (PMM): tracks free and used 4 KiB physical frames with
3//! a bitmap and hands out or reclaims individual frames.
4//!
5//! Authors: MarioS271
6
7use crate::{kdebug, kemerg, kinfo};
8use crate::panic::kernel_panic;
9use crate::types::panic_codes::PanicCode;
10use x86_64::PhysAddr;
11use limine::memmap;
12use limine::memmap::MEMMAP_USABLE;
13
14pub static FRAME_SIZE: u64 = 4096;
15
16/// Bitmap-based tracker of free and used physical frames.
17pub struct Pmm {
18    bitmap_ptr: *mut u8,
19    total_frames: u64,
20    bitmap_start_frame: u64,
21    bitmap_end_frame: u64,
22}
23
24// TODO: write justification for this
25unsafe impl Send for Pmm {}
26unsafe impl Sync for Pmm {}
27
28impl Pmm {
29    /// Initialize the PMM from the Limine memory map.
30    ///
31    /// # Panics
32    /// Panics if no single usable memory region is large enough to hold the bitmap.
33    pub fn init(entries: &[&memmap::Entry], hhdm_offset: u64) -> Self {
34        let mut max_entry = 0;
35
36        for entry in entries {
37            if entry.type_ != MEMMAP_USABLE {
38                continue;
39            }
40
41            if entry.base + entry.length > max_entry {
42                max_entry = entry.base + entry.length;
43            }
44        }
45
46        let total_frames = max_entry / FRAME_SIZE;
47        let bitmap_bytes = total_frames.div_ceil(8);
48
49        kdebug!("[PMM] total_frames={total_frames}");
50        kdebug!("[PMM] bitmap_bytes={bitmap_bytes}");
51
52        let mut _bitmap_physical_base_addr: Option<u64> = None;
53        let mut _bitmap_base_addr: Option<*mut u8> = None;
54
55        for entry in entries {
56            if entry.type_ != MEMMAP_USABLE {
57                continue;
58            }
59
60            if entry.length >= bitmap_bytes {
61                _bitmap_physical_base_addr = Some(entry.base);
62                _bitmap_base_addr = Some((entry.base + hhdm_offset) as *mut u8);
63                break;
64            }
65        }
66
67        if _bitmap_physical_base_addr.is_none()
68            || _bitmap_base_addr.is_none()
69        {
70            kernel_panic(
71                PanicCode::NoValidMemMapEntry,
72                "Could not find a usable memmap entry to place PMM bitmap in",
73            );
74        }
75
76        let bitmap_physical_base_addr = _bitmap_physical_base_addr.unwrap();
77        let bitmap_base_addr = _bitmap_base_addr.unwrap();
78
79        kdebug!("[PMM] bitmap_physical_base_addr={bitmap_physical_base_addr}");
80        kdebug!("[PMM] bitmap_base_addr={bitmap_base_addr:p}");
81
82        // This is safe because we're writing over our bitmap which was determined from safe
83        // limine-provided values.
84        unsafe {
85            core::ptr::write_bytes(bitmap_base_addr, 0xFF, bitmap_bytes as usize);
86        }
87
88        let bitmap_start_frame = bitmap_physical_base_addr / FRAME_SIZE;
89
90        // Subtracting one so that the value is the last used frame, not the one after
91        let bitmap_end_frame = ((bitmap_physical_base_addr + bitmap_bytes).div_ceil(FRAME_SIZE)) - 1;
92
93        kdebug!("[PMM] bitmap_start_frame={bitmap_start_frame}");
94        kdebug!("[PMM] bitmap_end_frame={bitmap_end_frame}");
95
96        for entry in entries {
97            if entry.type_ != MEMMAP_USABLE {
98                continue;
99            }
100
101            let first_frame = entry.base / FRAME_SIZE;
102            let frame_count = entry.length / FRAME_SIZE;
103
104            for frame in first_frame..(first_frame + frame_count) {
105                let frame_byte_offset = frame as usize / 8;
106                let frame_bit_offset = frame % 8;
107
108                if frame == 0 || (frame >= bitmap_start_frame && frame <= bitmap_end_frame) {
109                    continue;
110                }
111
112                // Safe because we're iterating inside our bitmap with bound computed from the
113                // limine values
114                unsafe {
115                    *bitmap_base_addr.add(frame_byte_offset) &= !(1 << (frame_bit_offset));
116                }
117            }
118        }
119
120        kinfo!("Initialized PMM");
121
122        Pmm {
123            bitmap_ptr: bitmap_base_addr,
124            total_frames: total_frames,
125            bitmap_start_frame: bitmap_start_frame,
126            bitmap_end_frame: bitmap_end_frame,
127        }
128    }
129
130    /// Allocate one free physical frame and return its address, or `None` if out of memory.
131    pub fn alloc(&self) -> Option<PhysAddr> {
132        for byte_index in 0..self.total_frames.div_ceil(8) {
133            // Safe because we're operating inside the bitmap address range
134            unsafe {
135                let byte = *self.bitmap_ptr.add(byte_index as usize);
136
137                if byte == 0xFF {
138                    continue;
139                }
140
141                let bit_position = u8::trailing_ones(byte);
142                *self.bitmap_ptr.add(byte_index as usize) |= 1 << bit_position;
143
144                let frame_index = byte_index * 8 + bit_position as u64;
145                let frame_address = frame_index * FRAME_SIZE;
146
147                return Some(PhysAddr::new_truncate(frame_address));
148            }
149        }
150
151        kemerg!("[PMM] unable to alloc() a frame, out of memory");
152
153        None
154    }
155
156    /// Free a previously allocated physical frame.
157    ///
158    /// # Panics
159    /// Panics if `addr` is frame 0, lies within the bitmap's own frames, is beyond
160    /// tracked memory, or is already free (double-free).
161    pub fn free(&self, addr: PhysAddr) {
162        let frame_index = addr.as_u64() / FRAME_SIZE;
163
164        if frame_index == 0 {
165            kernel_panic(
166                PanicCode::IllegalFree,
167                "Attempting to Pmm::free() frame 0",
168            );
169        }
170
171        if frame_index >= self.bitmap_start_frame && frame_index <= self.bitmap_end_frame {
172            kernel_panic(
173                PanicCode::IllegalFree,
174                "Attempting to Pmm::free() in the range of the pmm bitmap",
175            );
176        }
177
178        if frame_index >= self.total_frames {
179            kernel_panic(
180                PanicCode::IllegalFree,
181                "Attempting to Pmm::free() outside of usable memory",
182            );
183        }
184
185        let byte_offset = frame_index / 8;
186        let bit_offset = frame_index % 8;
187
188        // Safe because we're operating inside the bitmap address range
189        unsafe {
190            let byte = *self.bitmap_ptr.add(byte_offset as usize);
191
192            if byte & (1 << bit_offset) == 0u8 {
193                kernel_panic(
194                    PanicCode::DoubleFree,
195                    "Attempting to Pmm::free() a frame which is already freed",
196                );
197            }
198
199            *self.bitmap_ptr.add(byte_offset as usize) &= !(1 << bit_offset);
200        }
201    }
202}