Skip to main content

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_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(expect_redundant_test_prefix)]
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    // Retire the early allocator.
201    let early_allocator = EARLY_ALLOCATOR.lock().take().unwrap();
202    let early_allocated_ranges = early_allocator.allocated_regions();
203
204    #[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
205    let unaccepted_range = crate::if_tdx_enabled!({
206        super::unaccepted::init_unaccepted_memory_state(&early_allocated_ranges)
207    } else {
208        0..0
209    });
210    #[cfg(not(all(target_arch = "x86_64", feature = "cvm_guest")))]
211    let unaccepted_range: Range<Paddr> = 0..0;
212
213    let regions = &crate::boot::EARLY_INFO.get().unwrap().memory_regions;
214    let [range_1, range_2] = &early_allocated_ranges;
215
216    for region in regions
217        .iter()
218        .filter(|region| region.typ() == MemoryRegionType::Usable)
219    {
220        debug_assert!(region.base().is_multiple_of(PAGE_SIZE));
221        debug_assert!(region.len().is_multiple_of(PAGE_SIZE));
222
223        let region_range = region.base()..region.end();
224        for free_range in range_difference(&region_range, range_1)
225            .flat_map(|range| range_difference(&range, range_2))
226            .flat_map(|range| range_difference(&range, &unaccepted_range))
227        {
228            crate::info!("Adding free frames to the allocator: {:x?}", free_range);
229            get_global_frame_allocator().add_free_memory(free_range.start, free_range.len());
230        }
231    }
232}
233
234/// An allocator in the early boot phase when frame metadata is not available.
235pub(super) struct EarlyFrameAllocator {
236    // We need to allocate from under 4G first since the linear mapping for
237    // the higher region is not constructed yet.
238    under_4g_range: Range<Paddr>,
239    under_4g_end: Paddr,
240
241    // And also sometimes 4G is not enough for early phase. This, if not `0..0`,
242    // is the largest region above 4G.
243    max_range: Range<Paddr>,
244    max_end: Paddr,
245}
246
247/// The global frame allocator in the early boot phase.
248///
249/// It is used to allocate frames before the frame metadata is initialized.
250/// The allocated frames are not tracked by the frame metadata. After the
251/// metadata is initialized with [`super::meta::init`], the frames are tracked
252/// with metadata and the early allocator is no longer used.
253///
254/// This is protected by the [`spin::Mutex`] rather than [`crate::sync::SpinLock`]
255/// since the latter uses CPU-local storage, which isn't available in the early
256/// boot phase. So we must make sure that no interrupts are enabled when using
257/// this allocator.
258pub(super) static EARLY_ALLOCATOR: spin::Mutex<Option<EarlyFrameAllocator>> =
259    spin::Mutex::new(None);
260
261impl EarlyFrameAllocator {
262    /// Creates a new early frame allocator.
263    ///
264    /// It uses at most 2 regions, the first is the maximum usable region below
265    /// 4 GiB. The other is the maximum usable region above 4 GiB and is only
266    /// usable when linear mapping is constructed.
267    pub(crate) fn new() -> Self {
268        let regions = &crate::boot::EARLY_INFO.get().unwrap().memory_regions;
269
270        let mut under_4g_range = 0..0;
271        let mut max_range = 0..0;
272        for region in regions.iter() {
273            if region.typ() != MemoryRegionType::Usable {
274                continue;
275            }
276            const PADDR4G: Paddr = 0x1_0000_0000;
277            if region.base() < PADDR4G {
278                let range = region.base()..region.end().min(PADDR4G);
279                if range.len() > under_4g_range.len() {
280                    under_4g_range = range;
281                }
282            }
283            if region.end() >= PADDR4G {
284                let range = region.base().max(PADDR4G)..region.end();
285                if range.len() > max_range.len() {
286                    max_range = range;
287                }
288            }
289        }
290
291        crate::debug!(
292            "Early frame allocator (below 4G) at: {:#x?}",
293            under_4g_range
294        );
295        if !max_range.is_empty() {
296            crate::debug!("Early frame allocator (above 4G) at: {:#x?}", max_range);
297        }
298
299        Self {
300            under_4g_range: under_4g_range.clone(),
301            under_4g_end: under_4g_range.start,
302            max_range: max_range.clone(),
303            max_end: max_range.start,
304        }
305    }
306
307    /// Allocates a contiguous range of frames.
308    pub(super) fn alloc(&mut self, layout: Layout) -> Option<Paddr> {
309        let size = layout.size().align_up(PAGE_SIZE);
310        let align = layout.align().max(PAGE_SIZE);
311
312        for (tail, end) in [
313            (&mut self.under_4g_end, self.under_4g_range.end),
314            (&mut self.max_end, self.max_range.end),
315        ] {
316            let allocated = tail.align_up(align);
317            if let Some(allocated_end) = allocated.checked_add(size)
318                && allocated_end <= end
319            {
320                *tail = allocated_end;
321                return Some(allocated);
322            }
323        }
324
325        None
326    }
327
328    pub(super) fn allocated_regions(&self) -> [Range<Paddr>; 2] {
329        [
330            self.under_4g_range.start..self.under_4g_end,
331            self.max_range.start..self.max_end,
332        ]
333    }
334}
335
336/// Metadata for frames allocated in the early boot phase.
337///
338/// Frames allocated with [`early_alloc`] are not immediately tracked with
339/// frame metadata. But [`super::meta::init`] will track them later.
340#[derive(Debug)]
341pub(crate) struct EarlyAllocatedFrameMeta;
342
343impl_frame_meta_for!(EarlyAllocatedFrameMeta);
344
345/// Allocates a contiguous range of frames in the early boot phase.
346///
347/// The early allocated frames will not be reclaimable, until the metadata is
348/// initialized by [`super::meta::init`]. Then we can use [`Frame::from_raw`]
349/// to free the frames.
350///
351/// # Panics
352///
353/// This function panics if:
354///  - it is called before [`init_early_allocator`],
355///  - or if is called after [`init`].
356pub(crate) fn early_alloc(layout: Layout) -> Option<Paddr> {
357    let mut early_allocator = EARLY_ALLOCATOR.lock();
358    let paddr = early_allocator.as_mut().unwrap().alloc(layout)?;
359
360    #[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
361    crate::if_tdx_enabled!({
362        // SAFETY: The early allocator only exists in the boot context of the BSP before the APs
363        // boot up. Therefore, there are no other concurrent operations that accept pages.
364        unsafe { super::unaccepted::accept_early_allocated_range(paddr, layout.size()) };
365    });
366
367    Some(paddr)
368}
369
370/// Initializes the early frame allocator.
371///
372/// [`early_alloc`] should be used after this initialization. After [`init`], the
373/// early allocator.
374///
375/// # Safety
376///
377/// This function should be called only once after the memory regions are ready.
378pub(crate) unsafe fn init_early_allocator() {
379    let mut early_allocator = EARLY_ALLOCATOR.lock();
380    *early_allocator = Some(EarlyFrameAllocator::new());
381}