Skip to content

Commit 1e041d6

Browse files
committed
uefi: introduce MemoryMapBackingMemory helper type
1 parent 3957106 commit 1e041d6

File tree

1 file changed

+120
-1
lines changed

1 file changed

+120
-1
lines changed

uefi/src/table/boot.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! UEFI services available during boot.
22
3-
use super::Revision;
3+
use super::{system_table_boot, Revision};
44
use crate::data_types::{Align, PhysicalAddress};
55
use crate::proto::device_path::DevicePath;
66
use crate::proto::loaded_image::LoadedImage;
@@ -1617,6 +1617,125 @@ impl Align for MemoryDescriptor {
16171617
#[repr(C)]
16181618
pub struct MemoryMapKey(usize);
16191619

1620+
/// The backing memory for the UEFI memory app on the UEFI heap, allocated using
1621+
/// the UEFI boot services allocator. This occupied memory will also be
1622+
/// reflected in the memory map itself.
1623+
///
1624+
/// Although untyped, it is similar to the `Box` type in terms of heap
1625+
/// allocation and deallocation, as well as ownership of the corresponding
1626+
/// memory. Apart from that, this type only has the semantics of a buffer.
1627+
///
1628+
/// The memory is untyped, which is necessary due to the nature of the UEFI
1629+
/// spec. It still ensures a correct alignment to hold [`MemoryDescriptor`]. The
1630+
/// size of the buffer is sufficient to hold the memory map at the point in time
1631+
/// where this is created. Note that due to (not obvious or asynchronous)
1632+
/// allocations/allocations in your environment, this might be outdated at the
1633+
/// time you store the memory map in it.
1634+
///
1635+
/// Note that due to the nature of the UEFI memory app, this buffer might
1636+
/// hold (a few) bytes more than necessary. The `map_size` reported by
1637+
/// `get_memory_map` tells the actual size.
1638+
///
1639+
/// When this type is dropped and boot services are not exited yet, the memory
1640+
/// is freed.
1641+
///
1642+
/// # Usage
1643+
/// The type is intended to be used like this:
1644+
/// 1. create it using [`MemoryMapBackingMemory::new`]
1645+
/// 2. pass it to [`BootServices::get_memory_map`]
1646+
/// 3. construct a [`MemoryMap`] from it
1647+
#[derive(Debug)]
1648+
#[allow(clippy::len_without_is_empty)] // this type is never empty
1649+
pub(crate) struct MemoryMapBackingMemory(NonNull<[u8]>);
1650+
1651+
impl MemoryMapBackingMemory {
1652+
/// Constructs a new [`MemoryMapBackingMemory`].
1653+
///
1654+
/// # Parameters
1655+
/// - `memory_type`: The memory type for the memory map allocation.
1656+
/// Typically, [`MemoryType::LOADER_DATA`] for regular UEFI applications.
1657+
pub(crate) fn new(memory_type: MemoryType) -> Result<Self> {
1658+
let st = system_table_boot().expect("Should have boot services activated");
1659+
let bs = st.boot_services();
1660+
1661+
let memory_map_meta = bs.memory_map_size();
1662+
let len = Self::allocation_size_hint(memory_map_meta);
1663+
let ptr = bs.allocate_pool(memory_type, len)?;
1664+
1665+
// Should be fine as UEFI always has allocations with a guaranteed
1666+
// alignment of 8 bytes.
1667+
assert_eq!(ptr.align_offset(mem::align_of::<MemoryDescriptor>()), 0);
1668+
1669+
// If this panics, the UEFI implementation is broken.
1670+
assert_eq!(memory_map_meta.map_size % memory_map_meta.desc_size, 0);
1671+
1672+
Ok(Self::from_raw(ptr, len))
1673+
}
1674+
1675+
fn from_raw(ptr: *mut u8, len: usize) -> Self {
1676+
assert_eq!(ptr.align_offset(mem::align_of::<MemoryDescriptor>()), 0);
1677+
1678+
let ptr = NonNull::new(ptr).expect("UEFI should never return a null ptr. An error should have been reflected via an Err earlier.");
1679+
let slice = NonNull::slice_from_raw_parts(ptr, alloc_size);
1680+
1681+
Ok(Self(slice))
1682+
}
1683+
1684+
/// Returns a best-effort size hint of the memory map size. This is
1685+
/// especially created with exiting boot services in mind.
1686+
pub fn allocation_size_hint(mms: GetMemoryMapMeta) -> usize {
1687+
let GetMemoryMapMeta {
1688+
desc_size,
1689+
map_size,
1690+
..
1691+
} = mms;
1692+
1693+
// Allocate space for extra entries beyond the current size of the
1694+
// memory map. The value of 8 matches the value in the Linux kernel:
1695+
// https://github.com/torvalds/linux/blob/e544a07438/drivers/firmware/efi/libstub/efistub.h#L173
1696+
let extra_entries = 8;
1697+
1698+
assert!(desc_size > 0);
1699+
// Although very unlikely, this might fail if the memory descriptor is
1700+
// extended by a future UEFI revision by a significant amount, we
1701+
// update the struct, but an old UEFI implementation reports a small
1702+
// size.
1703+
assert!(desc_size >= mem::size_of::<MemoryDescriptor>());
1704+
assert!(map_size > 0);
1705+
1706+
// Ensure the mmap size is (somehow) sane.
1707+
const ONE_GB: usize = 1024 * 1024 * 1024;
1708+
assert!(map_size <= ONE_GB);
1709+
1710+
let extra_size = desc_size * extra_entries;
1711+
map_size + extra_size
1712+
}
1713+
1714+
/// Returns the raw pointer to the beginning of the allocation.
1715+
pub fn as_ptr_mut(&mut self) -> *mut u8 {
1716+
self.0.as_ptr().cast()
1717+
}
1718+
1719+
/// Returns a mutable slice to the underlying memory.
1720+
#[must_use]
1721+
pub fn as_mut_slice(&mut self) -> &mut [u8] {
1722+
unsafe { self.0.as_mut() }
1723+
}
1724+
}
1725+
1726+
impl Drop for MemoryMapBackingMemory {
1727+
fn drop(&mut self) {
1728+
if let Some(bs) = system_table_boot() {
1729+
let res = unsafe { bs.boot_services().free_pool(self.0.as_ptr().cast()) };
1730+
if let Err(e) = res {
1731+
log::error!("Failed to deallocate memory map: {e:?}");
1732+
}
1733+
} else {
1734+
log::debug!("Boot services are excited. Memory map won't be freed using the UEFI boot services allocator.");
1735+
}
1736+
}
1737+
}
1738+
16201739
/// A structure containing the meta attributes associated with a call to
16211740
/// `GetMemoryMap` of UEFI. Note that all values refer to the time this was
16221741
/// called. All following invocations (hidden, subtle, and asynchronous ones)

0 commit comments

Comments
 (0)