Skip to content

Commit 98215a2

Browse files
committed
uefi: introduce MemoryMapBackingMemory helper type
1 parent 82178a2 commit 98215a2

File tree

1 file changed

+128
-4
lines changed

1 file changed

+128
-4
lines changed

uefi/src/table/boot.rs

Lines changed: 128 additions & 4 deletions
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;
@@ -186,7 +186,7 @@ impl BootServices {
186186
/// for the memory map, as the memory map itself also needs heap memory,
187187
/// and other allocations might occur before that call.
188188
#[must_use]
189-
pub fn memory_map_size(&self) -> MemoryMapMeta {
189+
fn memory_map_size(&self) -> MemoryMapMeta {
190190
let mut map_size = 0;
191191
let mut map_key = MemoryMapKey(0);
192192
let mut desc_size = 0;
@@ -209,12 +209,16 @@ impl BootServices {
209209
"Memory map must be a multiple of the reported descriptor size."
210210
);
211211

212-
MemoryMapMeta {
212+
let mmm = MemoryMapMeta {
213213
desc_size,
214214
map_size,
215215
map_key,
216216
desc_version,
217-
}
217+
};
218+
219+
mmm.assert_sanity_checks();
220+
221+
mmm
218222
}
219223

220224
/// Stores the current UEFI memory map in the provided buffer.
@@ -1621,6 +1625,111 @@ impl Align for MemoryDescriptor {
16211625
#[repr(C)]
16221626
pub struct MemoryMapKey(usize);
16231627

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

16501774
/// An accessory to the memory map that can be either iterated or

0 commit comments

Comments
 (0)