|
1 | 1 | //! UEFI services available during boot.
|
2 | 2 |
|
3 |
| -use super::Revision; |
| 3 | +use super::{system_table_boot, Revision}; |
4 | 4 | use crate::data_types::{Align, PhysicalAddress};
|
5 | 5 | use crate::proto::device_path::DevicePath;
|
6 | 6 | use crate::proto::loaded_image::LoadedImage;
|
@@ -1617,6 +1617,125 @@ impl Align for MemoryDescriptor {
|
1617 | 1617 | #[repr(C)]
|
1618 | 1618 | pub struct MemoryMapKey(usize);
|
1619 | 1619 |
|
| 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 | + |
1620 | 1739 | /// A structure containing the meta attributes associated with a call to
|
1621 | 1740 | /// `GetMemoryMap` of UEFI. Note that all values refer to the time this was
|
1622 | 1741 | /// called. All following invocations (hidden, subtle, and asynchronous ones)
|
|
0 commit comments