blob: f50bfac3613cfb9f273b12b2318a816919668743 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This module provides [`ScopedPageAllocation`], which uses the UEFI
//! boot services page allocation function to allocate memory in
//! multiples of the 4KiB page size.
//!
//! uefi-rs has an `alloc` feature that allows allocating through the
//! standard Rust global allocator, but that interface does not allow
//! controlling details of the allocation type. In particular, the
//! memory used to allocate the kernel should be of
//! [`MemoryType::LOADER_CODE`] rather than [`MemoryType::LOADER_DATA`],
//! since it is executable.
use core::fmt::{self, Display, Formatter};
use core::ops::{Deref, DerefMut};
use core::slice;
use log::{error, info};
use uefi::table::boot::{AllocateType, MemoryType, PAGE_SIZE};
use uefi::table::{Boot, SystemTable};
use uefi::Status;
pub enum PageAllocationError {
/// Allocation request is not an even multiple of the page size.
InvalidSize(usize),
/// UEFI page allocator failed.
AllocationFailed(usize, Status),
}
impl Display for PageAllocationError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::InvalidSize(num_bytes) => {
write!(
f,
"{num_bytes} is not an even multiple of page size ({PAGE_SIZE})"
)
}
Self::AllocationFailed(num_pages, status) => {
write!(f, "failed to allocate {num_pages} pages: {status}")
}
}
}
}
/// Page-aligned memory allocation that will be freed on drop. This
/// implements [`Deref`] and [`DerefMut`] to provide access to the
/// allocation.
pub struct ScopedPageAllocation<'a> {
allocation: &'a mut [u8],
num_pages: usize,
system_table: SystemTable<Boot>,
}
impl<'a> ScopedPageAllocation<'a> {
/// Allocate `num_bytes` of page-aligned memory.
pub fn new(
system_table: SystemTable<Boot>,
allocate_type: AllocateType,
memory_type: MemoryType,
num_bytes: usize,
) -> Result<Self, PageAllocationError> {
// Reject the allocation if it's not a multiple of the page size.
if num_bytes % PAGE_SIZE != 0 {
return Err(PageAllocationError::InvalidSize(num_bytes));
}
let num_pages = num_bytes / PAGE_SIZE;
info!("allocating {num_pages} pages ({allocate_type:?}, {memory_type:?})");
let addr = system_table
.boot_services()
.allocate_pages(allocate_type, memory_type, num_pages)
.map_err(|err| PageAllocationError::AllocationFailed(num_pages, err.status()))?;
info!("allocation address: {addr:#x}");
// Convert the physical address to a pointer.
let ptr = addr as *mut u8;
// Zero-initialize the allocation and convert to a slice.
//
// Safety:
//
// We zero-initialize the whole allocation using `write_bytes`,
// so no invalid reference is created. Then it is safe to
// convert the allocation to a slice.
let allocation: &mut [u8] = unsafe {
ptr.write_bytes(0, num_bytes);
slice::from_raw_parts_mut(ptr, num_bytes)
};
Ok(Self {
allocation,
num_pages,
system_table,
})
}
}
impl<'a> Drop for ScopedPageAllocation<'a> {
fn drop(&mut self) {
let addr = self.allocation.as_mut_ptr() as u64;
info!("freeing {} pages at {addr:#x}", self.num_pages);
// Can't propagate an error from here, so just log it.
//
// Safety:
//
// By the time we call `drop` no other references to the
// allocation can exist, so it is safe to de-allocate the
// pages.
if let Err(err) = unsafe {
self.system_table
.boot_services()
.free_pages(addr, self.num_pages)
} {
error!("free_pages failed: {:?}", err.status());
}
}
}
impl<'a> Deref for ScopedPageAllocation<'a> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.allocation
}
}
impl<'a> DerefMut for ScopedPageAllocation<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.allocation
}
}