Skip to main content

kernel/arch/x86_64/mm/
pmm.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! Physical Memory Manager (PMM): buddy allocator that tracks and hands out physical frames.
3//!
4//! Authors: MarioS271
5
6#[cfg(feature = "debug-logging")]
7use crate::kdebug;
8
9// TODO: correct safety comments and declarations
10
11use crate::kinfo;
12use crate::lib::addr::PhysAddr;
13use crate::state::kstate::KSTATE;
14use limine::memmap;
15
16pub const FRAME_SIZE: u64 = 4096;
17const MAX_ORDER: usize = 10;
18const NUM_ORDERS: usize = MAX_ORDER + 1;
19
20/// Buddy-Allocator based tracker of free and head physical frames.
21pub struct Pmm {
22    head: [Option<PhysAddr>; 11],
23    total_mem: u64
24}
25
26impl Pmm {
27    /// Initialize the PMM from the Limine memory map, coalescing usable frames into the buddy free lists.
28    pub fn init(entries: &[&memmap::Entry]) -> Self {
29        let mut pmm = Pmm {
30            head: [None; NUM_ORDERS],
31            total_mem: 0
32        };
33
34        #[cfg(feature = "debug-logging")]
35        {
36            let mut total_usable = 0u64;
37            let mut total_reclaimable = 0u64;
38            let mut count_usable = 0u64;
39            let mut count_reclaimable = 0u64;
40            let mut count_reserved = 0u64;
41            let mut count_acpi = 0u64;
42
43            for &entry in entries {
44                match entry.type_ {
45                    memmap::MEMMAP_USABLE => { count_usable += 1; total_usable += entry.length; }
46                    memmap::MEMMAP_BOOTLOADER_RECLAIMABLE => { count_reclaimable += 1; total_reclaimable += entry.length; }
47                    memmap::MEMMAP_RESERVED => { count_reserved += 1; }
48                    memmap::MEMMAP_ACPI_RECLAIMABLE => { count_acpi += 1; }
49                    _ => {}
50                }
51            }
52
53            kdebug!("[PMM] total entries: {}", entries.len());
54            kdebug!("[PMM] usable: {} regions, {} MiB", count_usable, total_usable / 1024 / 1024);
55            kdebug!("[PMM] reclaimable: {} regions, {} MiB", count_reclaimable, total_reclaimable / 1024 / 1024);
56            kdebug!("[PMM] reserved: {} regions", count_reserved);
57            kdebug!("[PMM] acpi: {} regions", count_acpi);
58        }
59
60        let mut total_mem = 0u64;
61
62        for &entry in entries {
63            if entry.type_ == memmap::MEMMAP_USABLE || entry.type_ == memmap::MEMMAP_BOOTLOADER_RECLAIMABLE {
64                total_mem = total_mem.max(entry.base + entry.length);
65            }
66
67            if entry.type_ != memmap::MEMMAP_USABLE { continue; }
68
69            let mut addr = entry.base;
70            let mut remaining = entry.length;
71
72            if addr == 0 {
73                addr += FRAME_SIZE;
74                remaining -= FRAME_SIZE;
75            }
76
77            while remaining >= FRAME_SIZE {
78                let mut order = MAX_ORDER;
79                while order > 0 {
80                    let block_size = FRAME_SIZE << order;
81                    if remaining >= block_size && addr % (FRAME_SIZE << order) == 0 { break; }
82                    order -= 1;
83                }
84
85                pmm.free(PhysAddr::new(addr), order);
86
87                let block_size = FRAME_SIZE << order;
88                addr += block_size;
89                remaining -= block_size;
90            }
91        }
92
93        kinfo!("Initialized PMM");
94
95        pmm.total_mem = total_mem;
96        pmm
97    }
98
99    /// Getter for the length of total memory, returns the highest physical address given by the memmap
100    pub fn get_total_mem(&self) -> u64 {
101        self.total_mem
102    }
103
104    /// Allocate a block of `FRAME_SIZE << order` bytes
105    pub fn alloc(&mut self, order: usize) -> Option<PhysAddr> {
106        if order > 10 { return None; }
107
108        if self.head[order] != None {
109            return self.pop(order);
110        }
111
112        let mut current: usize = MAX_ORDER;
113        for o in order..=MAX_ORDER {
114            if self.head[o] != None {
115                current = o;
116                break;
117            }
118            if o == MAX_ORDER {
119                return None;
120            }
121        }
122
123        let addr = self.pop(current)?;
124        while current > order {
125            current -= 1;
126            let buddy = PhysAddr::new(addr.as_u64() + (FRAME_SIZE << current));
127            self.push(current, buddy);
128        }
129
130        Some(addr)
131    }
132
133    /// Wrapper for [`alloc(0)`](Self::alloc), allocates exactly one frame (order 0)
134    pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
135        self.alloc(0)
136    }
137
138    /// Wrapper for [`alloc_frame`] which zeroes the frame before returning it
139    pub fn alloc_frame_zeroed(&mut self) -> Option<PhysAddr> {
140        let frame = self.alloc_frame()?;
141        unsafe { core::ptr::write_bytes(frame.as_mut_hhdm_ptr::<u8>(), 0x00, FRAME_SIZE as usize); }
142        Some(frame)
143    }
144
145    /// Return a block of `FRAME_SIZE << order` bytes to the free lists, merging with available buddies
146    pub fn free(&mut self, mut addr: PhysAddr, mut order: usize) {
147        'outer: while order < MAX_ORDER {
148            if self.head[order].is_none() {
149                break;
150            }
151
152            let buddy = PhysAddr::new(addr.as_u64() ^ (FRAME_SIZE << order));
153            let mut current_node_addr = self.head[order].unwrap();
154            let mut prev_node_addr = PhysAddr::null();
155
156            loop {
157                let next_node_addr: u64 = unsafe { core::ptr::read(current_node_addr.as_hhdm_ptr()) };
158
159                if current_node_addr == buddy {
160                    if prev_node_addr.as_u64() == 0 {
161                        self.head[order] = Some(PhysAddr::new(next_node_addr));
162                    }
163                    else {
164                        unsafe { core::ptr::write(prev_node_addr.as_mut_hhdm_ptr(), next_node_addr) };
165                    }
166                    break;
167                }
168
169                if next_node_addr == 0 {
170                    break 'outer;
171                }
172                prev_node_addr = current_node_addr;
173                current_node_addr = PhysAddr::new(next_node_addr);
174            }
175
176            order += 1;
177            addr = PhysAddr::new(core::cmp::min(addr.as_u64(), buddy.as_u64()));
178        }
179
180        self.push(order, addr);
181    }
182
183    /// Wrapper for [`free(addr, 0)`](Self::free), frees exactly one frame (order 0)
184    pub fn free_frame(&mut self, addr: PhysAddr) {
185        self.free(addr, 0)
186    }
187
188
189    /// Remove and return the head block from the order-`order` free list, or `None` if empty.
190    fn pop(&mut self, order: usize) -> Option<PhysAddr> {
191        let hhdm_offset = &KSTATE.mm.hhdm_offset();
192        let head = self.head[order]?;
193        let result;
194
195        unsafe {
196            // Safe: head was placed here by push() with a valid usable physical frame
197            // HHDM maps all usable memory, so head + hhdm_offset is a valid mapped address
198            result = core::ptr::read(
199                (head.as_u64() + hhdm_offset) as *const u64
200            );
201        }
202        if result == 0 {
203            self.head[order] = None;
204        } else {
205            self.head[order] = Some(PhysAddr::new(result));
206        }
207
208        Some(head)
209    }
210
211    /// Prepend `addr` onto the order-`order` free list.
212    fn push(&mut self, order: usize, addr: PhysAddr) {
213        let hhdm_offset = &KSTATE.mm.hhdm_offset();
214
215        // Safe: addr is a valid usable physical frame sourced from the Limine memmap
216        // HHDM maps all usable memory, so addr + hhdm_offset is a valid mapped address
217        unsafe {
218            core::ptr::write(
219                (addr.as_u64() + hhdm_offset) as *mut u64,
220                 self.head[order].map_or(0, |addr| addr.as_u64())
221            );
222        }
223
224        self.head[order] = Some(addr);
225    }
226}