ostd/mm/frame/allocator.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! The physical memory allocator.
4
5use core::{alloc::Layout, ops::Range};
6
7use align_ext::AlignExt;
8
9use super::{Frame, meta::AnyFrameMeta, segment::Segment};
10use crate::{
11 boot::memory_region::MemoryRegionType,
12 error::Error,
13 impl_frame_meta_for,
14 mm::{PAGE_SIZE, Paddr, paddr_to_vaddr},
15 prelude::*,
16 util::ops::range_difference,
17};
18
19/// Options for allocating physical memory frames.
20pub struct FrameAllocOptions {
21 zeroed: bool,
22}
23
24impl Default for FrameAllocOptions {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl FrameAllocOptions {
31 /// Creates new options for allocating the specified number of frames.
32 pub fn new() -> Self {
33 Self { zeroed: true }
34 }
35
36 /// Sets whether the allocated frames should be initialized with zeros.
37 ///
38 /// If `zeroed` is `true`, the allocated frames are filled with zeros.
39 /// If not, the allocated frames will contain sensitive data and the caller
40 /// should clear them before sharing them with other components.
41 ///
42 /// By default, the frames are zero-initialized.
43 pub fn zeroed(&mut self, zeroed: bool) -> &mut Self {
44 self.zeroed = zeroed;
45 self
46 }
47
48 /// Allocates a single untyped frame without metadata.
49 pub fn alloc_frame(&self) -> Result<Frame<()>> {
50 self.alloc_frame_with(())
51 }
52
53 /// Allocates a single frame with additional metadata.
54 pub fn alloc_frame_with<M: AnyFrameMeta>(&self, metadata: M) -> Result<Frame<M>> {
55 let single_layout = Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap();
56 let frame = get_global_frame_allocator()
57 .alloc(single_layout)
58 .map(|paddr| Frame::from_unused(paddr, metadata).unwrap())
59 .ok_or(Error::NoMemory)?;
60
61 if self.zeroed {
62 let addr = paddr_to_vaddr(frame.paddr()) as *mut u8;
63 // SAFETY: The newly allocated frame is guaranteed to be valid.
64 unsafe { core::ptr::write_bytes(addr, 0, PAGE_SIZE) }
65 }
66
67 Ok(frame)
68 }
69
70 /// Allocates a contiguous range of untyped frames without metadata.
71 pub fn alloc_segment(&self, nframes: usize) -> Result<Segment<()>> {
72 self.alloc_segment_with(nframes, |_| ())
73 }
74
75 /// Allocates a contiguous range of frames with additional metadata.
76 ///
77 /// The returned [`Segment`] contains at least one frame. The method returns
78 /// an error if the number of frames is zero.
79 pub fn alloc_segment_with<M: AnyFrameMeta, F>(
80 &self,
81 nframes: usize,
82 metadata_fn: F,
83 ) -> Result<Segment<M>>
84 where
85 F: FnMut(Paddr) -> M,
86 {
87 if nframes == 0 {
88 return Err(Error::InvalidArgs);
89 }
90 let layout = Layout::from_size_align(nframes * PAGE_SIZE, PAGE_SIZE).unwrap();
91 let segment = get_global_frame_allocator()
92 .alloc(layout)
93 .map(|start| {
94 Segment::from_unused(start..start + nframes * PAGE_SIZE, metadata_fn).unwrap()
95 })
96 .ok_or(Error::NoMemory)?;
97
98 if self.zeroed {
99 let addr = paddr_to_vaddr(segment.paddr()) as *mut u8;
100 // SAFETY: The newly allocated segment is guaranteed to be valid.
101 unsafe { core::ptr::write_bytes(addr, 0, nframes * PAGE_SIZE) }
102 }
103
104 Ok(segment)
105 }
106}
107
108#[cfg(ktest)]
109#[ktest]
110fn test_alloc_dealloc() {
111 // Here we allocate and deallocate frames in random orders to test the allocator.
112 // We expect the test to fail if the underlying implementation panics.
113 let single_options = FrameAllocOptions::new();
114 let mut contiguous_options = FrameAllocOptions::new();
115 contiguous_options.zeroed(false);
116 let mut remember_vec = Vec::new();
117 for _ in 0..10 {
118 for i in 0..10 {
119 let single_frame = single_options.alloc_frame().unwrap();
120 if i % 3 == 0 {
121 remember_vec.push(single_frame);
122 }
123 }
124 let contiguous_segment = contiguous_options.alloc_segment(10).unwrap();
125 drop(contiguous_segment);
126 remember_vec.pop();
127 }
128}
129
130/// The trait for the global frame allocator.
131///
132/// OSTD allows a customized frame allocator by the [`global_frame_allocator`]
133/// attribute, which marks a static variable of this type.
134///
135/// The API mimics the standard Rust allocator API ([`GlobalAlloc`] and
136/// [`global_allocator`]). However, this trait is much safer. Double free
137/// or freeing in-use memory through this trait only messes up the allocator's
138/// state rather than causing undefined behavior.
139///
140/// Whenever OSTD or other modules need to allocate or deallocate frames via
141/// [`FrameAllocOptions`], they are forwarded to the global frame allocator.
142/// It is not encouraged to call the global allocator directly.
143///
144/// [`global_frame_allocator`]: crate::global_frame_allocator
145/// [`GlobalAlloc`]: core::alloc::GlobalAlloc
146pub trait GlobalFrameAllocator: Sync {
147 /// Allocates a contiguous range of frames.
148 ///
149 /// The caller guarantees that `layout.size()` is aligned to [`PAGE_SIZE`].
150 ///
151 /// When any of the allocated memory is not in use, OSTD returns them by
152 /// calling [`GlobalFrameAllocator::dealloc`]. If multiple frames are
153 /// allocated, they may be returned in any order with any number of calls.
154 fn alloc(&self, layout: Layout) -> Option<Paddr>;
155
156 /// Deallocates a contiguous range of frames.
157 ///
158 /// The caller guarantees that `addr` and `size` are both aligned to
159 /// [`PAGE_SIZE`]. The deallocated memory should always be allocated by
160 /// [`GlobalFrameAllocator::alloc`]. However, if
161 /// [`GlobalFrameAllocator::alloc`] returns multiple frames, it is possible
162 /// that some of them are deallocated before others. The deallocated memory
163 /// must never overlap with any memory that is already deallocated or
164 /// added, without being allocated in between.
165 ///
166 /// The deallocated memory can be uninitialized.
167 fn dealloc(&self, addr: Paddr, size: usize);
168
169 /// Adds a contiguous range of frames to the allocator.
170 ///
171 /// The memory being added must never overlap with any memory that was
172 /// added before.
173 ///
174 /// The added memory can be uninitialized.
175 fn add_free_memory(&self, addr: Paddr, size: usize);
176}
177
178unsafe extern "Rust" {
179 /// The global frame allocator's reference exported by
180 /// [`crate::global_frame_allocator`].
181 static __GLOBAL_FRAME_ALLOCATOR_REF: &'static dyn GlobalFrameAllocator;
182}
183
184pub(super) fn get_global_frame_allocator() -> &'static dyn GlobalFrameAllocator {
185 // SAFETY: The global frame allocator is set up correctly with the
186 // `global_frame_allocator` attribute. If they use safe code only, the
187 // up-call is safe.
188 unsafe { __GLOBAL_FRAME_ALLOCATOR_REF }
189}
190
191/// Initializes the global frame allocator.
192///
193/// It just does adds the frames to the global frame allocator. Calling it
194/// multiple times would be not safe.
195///
196/// # Safety
197///
198/// This function should be called only once.
199pub(crate) unsafe fn init() {
200 let regions = &crate::boot::EARLY_INFO.get().unwrap().memory_regions;
201
202 // Retire the early allocator.
203 let early_allocator = EARLY_ALLOCATOR.lock().take().unwrap();
204 let (range_1, range_2) = early_allocator.allocated_regions();
205
206 for region in regions.iter() {
207 if region.typ() == MemoryRegionType::Usable {
208 debug_assert!(region.base().is_multiple_of(PAGE_SIZE));
209 debug_assert!(region.len().is_multiple_of(PAGE_SIZE));
210
211 // Add global free pages to the frame allocator.
212 // Truncate the early allocated frames if there is an overlap.
213 for r1 in range_difference(&(region.base()..region.end()), &range_1) {
214 for r2 in range_difference(&r1, &range_2) {
215 log::info!("Adding free frames to the allocator: {:x?}", r2);
216 get_global_frame_allocator().add_free_memory(r2.start, r2.len());
217 }
218 }
219 }
220 }
221}
222
223/// An allocator in the early boot phase when frame metadata is not available.
224pub(super) struct EarlyFrameAllocator {
225 // We need to allocate from under 4G first since the linear mapping for
226 // the higher region is not constructed yet.
227 under_4g_range: Range<Paddr>,
228 under_4g_end: Paddr,
229
230 // And also sometimes 4G is not enough for early phase. This, if not `0..0`,
231 // is the largest region above 4G.
232 max_range: Range<Paddr>,
233 max_end: Paddr,
234}
235
236/// The global frame allocator in the early boot phase.
237///
238/// It is used to allocate frames before the frame metadata is initialized.
239/// The allocated frames are not tracked by the frame metadata. After the
240/// metadata is initialized with [`super::meta::init`], the frames are tracked
241/// with metadata and the early allocator is no longer used.
242///
243/// This is protected by the [`spin::Mutex`] rather than [`crate::sync::SpinLock`]
244/// since the latter uses CPU-local storage, which isn't available in the early
245/// boot phase. So we must make sure that no interrupts are enabled when using
246/// this allocator.
247pub(super) static EARLY_ALLOCATOR: spin::Mutex<Option<EarlyFrameAllocator>> =
248 spin::Mutex::new(None);
249
250impl EarlyFrameAllocator {
251 /// Creates a new early frame allocator.
252 ///
253 /// It uses at most 2 regions, the first is the maximum usable region below
254 /// 4 GiB. The other is the maximum usable region above 4 GiB and is only
255 /// usable when linear mapping is constructed.
256 pub fn new() -> Self {
257 let regions = &crate::boot::EARLY_INFO.get().unwrap().memory_regions;
258
259 let mut under_4g_range = 0..0;
260 let mut max_range = 0..0;
261 for region in regions.iter() {
262 if region.typ() != MemoryRegionType::Usable {
263 continue;
264 }
265 const PADDR4G: Paddr = 0x1_0000_0000;
266 if region.base() < PADDR4G {
267 let range = region.base()..region.end().min(PADDR4G);
268 if range.len() > under_4g_range.len() {
269 under_4g_range = range;
270 }
271 }
272 if region.end() >= PADDR4G {
273 let range = region.base().max(PADDR4G)..region.end();
274 if range.len() > max_range.len() {
275 max_range = range;
276 }
277 }
278 }
279
280 log::debug!(
281 "Early frame allocator (below 4G) at: {:#x?}",
282 under_4g_range
283 );
284 if !max_range.is_empty() {
285 log::debug!("Early frame allocator (above 4G) at: {:#x?}", max_range);
286 }
287
288 Self {
289 under_4g_range: under_4g_range.clone(),
290 under_4g_end: under_4g_range.start,
291 max_range: max_range.clone(),
292 max_end: max_range.start,
293 }
294 }
295
296 /// Allocates a contiguous range of frames.
297 pub fn alloc(&mut self, layout: Layout) -> Option<Paddr> {
298 let size = layout.size().align_up(PAGE_SIZE);
299 let align = layout.align().max(PAGE_SIZE);
300
301 for (tail, end) in [
302 (&mut self.under_4g_end, self.under_4g_range.end),
303 (&mut self.max_end, self.max_range.end),
304 ] {
305 let allocated = tail.align_up(align);
306 if let Some(allocated_end) = allocated.checked_add(size)
307 && allocated_end <= end
308 {
309 *tail = allocated_end;
310 return Some(allocated);
311 }
312 }
313
314 None
315 }
316
317 pub(super) fn allocated_regions(&self) -> (Range<Paddr>, Range<Paddr>) {
318 (
319 self.under_4g_range.start..self.under_4g_end,
320 self.max_range.start..self.max_end,
321 )
322 }
323}
324
325/// Metadata for frames allocated in the early boot phase.
326///
327/// Frames allocated with [`early_alloc`] are not immediately tracked with
328/// frame metadata. But [`super::meta::init`] will track them later.
329#[derive(Debug)]
330pub(crate) struct EarlyAllocatedFrameMeta;
331
332impl_frame_meta_for!(EarlyAllocatedFrameMeta);
333
334/// Allocates a contiguous range of frames in the early boot phase.
335///
336/// The early allocated frames will not be reclaimable, until the metadata is
337/// initialized by [`super::meta::init`]. Then we can use [`Frame::from_raw`]
338/// to free the frames.
339///
340/// # Panics
341///
342/// This function panics if:
343/// - it is called before [`init_early_allocator`],
344/// - or if is called after [`init`].
345pub(crate) fn early_alloc(layout: Layout) -> Option<Paddr> {
346 let mut early_allocator = EARLY_ALLOCATOR.lock();
347 early_allocator.as_mut().unwrap().alloc(layout)
348}
349
350/// Initializes the early frame allocator.
351///
352/// [`early_alloc`] should be used after this initialization. After [`init`], the
353/// early allocator.
354///
355/// # Safety
356///
357/// This function should be called only once after the memory regions are ready.
358pub(crate) unsafe fn init_early_allocator() {
359 let mut early_allocator = EARLY_ALLOCATOR.lock();
360 *early_allocator = Some(EarlyFrameAllocator::new());
361}