use core::{
mem::{ManuallyDrop, MaybeUninit},
ptr::{NonNull, with_exposed_provenance_mut},
};
use r_efi::efi;
use crate::{base::UEFI_PAGE_SIZE, efi_types::EfiMemoryType, error::EfiError};
#[cfg(any(test, feature = "alloc"))]
use core::alloc::Allocator;
#[cfg(any(test, feature = "alloc"))]
use alloc::boxed::Box;
#[cfg(any(test, feature = "mockall"))]
use mockall::automock;
#[cfg_attr(any(test, feature = "mockall"), automock)]
pub trait MemoryManager {
fn allocate_pages(&self, page_count: usize, options: AllocationOptions) -> Result<PageAllocation, MemoryError>;
fn allocate_zero_pages(
&self,
page_count: usize,
options: AllocationOptions,
) -> Result<PageAllocation, MemoryError> {
let allocation = self.allocate_pages(page_count, options)?;
allocation.zero_pages();
Ok(allocation)
}
unsafe fn free_pages(&self, address: usize, page_count: usize) -> Result<(), MemoryError>;
#[cfg(feature = "alloc")]
fn get_allocator(&self, memory_type: EfiMemoryType) -> Result<&'static dyn Allocator, MemoryError>;
unsafe fn set_page_attributes(
&self,
address: usize,
page_count: usize,
access: AccessType,
caching: Option<CachingType>,
) -> Result<(), MemoryError>;
fn get_page_attributes(&self, address: usize, page_count: usize) -> Result<(AccessType, CachingType), MemoryError>;
}
#[derive(Debug)]
pub struct AllocationOptions {
allocation_strategy: PageAllocationStrategy,
alignment: usize,
memory_type: EfiMemoryType,
}
impl Default for AllocationOptions {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl AllocationOptions {
#[inline(always)]
pub const fn new() -> Self {
Self {
allocation_strategy: PageAllocationStrategy::Any,
alignment: UEFI_PAGE_SIZE,
memory_type: EfiMemoryType::BootServicesData,
}
}
#[inline(always)]
pub const fn with_strategy(mut self, allocation_strategy: PageAllocationStrategy) -> Self {
self.allocation_strategy = allocation_strategy;
self
}
#[inline(always)]
pub const fn with_alignment(mut self, alignment: usize) -> Self {
self.alignment = alignment;
self
}
#[inline(always)]
pub const fn with_memory_type(mut self, memory_type: EfiMemoryType) -> Self {
self.memory_type = memory_type;
self
}
#[inline(always)]
pub fn strategy(&self) -> PageAllocationStrategy {
self.allocation_strategy
}
#[inline(always)]
pub fn alignment(&self) -> usize {
self.alignment
}
#[inline(always)]
pub fn memory_type(&self) -> EfiMemoryType {
self.memory_type
}
}
#[repr(align(4096))]
#[allow(dead_code)]
struct UefiPage([u8; UEFI_PAGE_SIZE]);
#[must_use]
pub struct PageAllocation {
blob: NonNull<u8>,
page_count: usize,
memory_manager: &'static dyn MemoryManager,
}
impl PageAllocation {
pub unsafe fn new(
addr: usize,
page_count: usize,
memory_manager: &'static dyn MemoryManager,
) -> Result<Self, MemoryError> {
let Some(blob) = NonNull::new(with_exposed_provenance_mut(addr)) else {
return Err(MemoryError::InvalidAddress);
};
if !blob.cast::<UefiPage>().is_aligned() {
return Err(MemoryError::UnalignedAddress);
}
if page_count == 0 {
return Err(MemoryError::InvalidPageCount);
}
Ok(Self { blob, page_count, memory_manager })
}
#[inline(always)]
pub fn page_count(&self) -> usize {
self.page_count
}
#[inline(always)]
pub fn byte_length(&self) -> usize {
uefi_pages_to_size!(self.page_count)
}
fn zero_pages(&self) {
unsafe { self.blob.write_bytes(0, self.byte_length()) };
}
#[inline(always)]
#[cfg(any(test, feature = "alloc"))]
fn get_page_free(&self) -> PageFree {
PageFree { blob: self.blob, page_count: self.page_count, memory_manager: self.memory_manager }
}
#[must_use]
pub fn into_raw_ptr<T>(mut self) -> Option<*mut T> {
if self.byte_length() < size_of::<T>() {
self.free_pages();
core::mem::forget(self);
return None;
}
Some(ManuallyDrop::new(self).blob.cast::<T>().as_ptr())
}
#[must_use]
pub fn into_raw_slice<T>(self) -> *mut [T] {
let count = self.byte_length() / size_of::<T>();
let ptr = ManuallyDrop::new(self).blob.cast::<T>().as_ptr();
core::ptr::slice_from_raw_parts_mut(ptr, count)
}
#[must_use]
#[cfg(any(test, feature = "alloc"))]
pub fn into_box<T>(self, value: T) -> Option<Box<T, PageFree>> {
let page_free = self.get_page_free();
let ptr: *mut T = self.into_raw_ptr()?;
unsafe {
ptr.write(value);
Some(Box::from_raw_in(ptr, page_free))
}
}
#[must_use]
#[cfg(any(test, feature = "alloc"))]
pub fn into_boxed_slice<T: Default>(self) -> Box<[T], PageFree> {
let page_free = self.get_page_free();
let slice = self.leak_as_slice::<T>();
unsafe { Box::from_raw_in(slice as *mut _, page_free) }
}
#[must_use]
pub fn leak_as<'a, T>(self, value: T) -> Option<&'a mut T> {
let ptr = self.into_raw_ptr::<T>()?;
unsafe {
ptr.write(value);
ptr.as_mut()
}
}
#[must_use]
pub fn leak_as_slice<'a, T: Default>(self) -> &'a mut [T] {
let slice = self.into_raw_slice::<MaybeUninit<T>>();
unsafe {
(*slice).fill_with(|| MaybeUninit::new(Default::default()));
(slice as *mut [T]).as_mut().expect("Slice Pointer just created and is not null")
}
}
fn free_pages(&mut self) {
let address = self.blob.addr().get();
unsafe {
if self.memory_manager.free_pages(address, self.page_count).is_err() {
log_debug_assert!("Failed to free page allocation at {address:x}!");
}
}
}
}
impl Drop for PageAllocation {
fn drop(&mut self) {
self.free_pages();
debug_assert!(false, "Page allocation was never used!");
}
}
impl core::fmt::Display for PageAllocation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PageAllocation").field("address", &self.blob).field("page_count", &self.page_count).finish()
}
}
#[cfg(any(test, feature = "alloc"))]
pub struct PageFree {
blob: NonNull<u8>,
page_count: usize,
memory_manager: &'static dyn MemoryManager,
}
#[cfg(any(test, feature = "alloc"))]
unsafe impl Allocator for PageFree {
fn allocate(&self, _layout: core::alloc::Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
Err(core::alloc::AllocError)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: core::alloc::Layout) {
if self.blob != ptr {
log_debug_assert!(
"PageFree was not used to free the correct memory! Leaking memory at {:?}!",
self.blob.as_ptr()
);
return;
}
let address = self.blob.addr().get();
if unsafe { self.memory_manager.free_pages(address, self.page_count).is_err() } {
log_debug_assert!("Failed to free page allocation at {address:x}!");
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AccessType {
NoAccess,
ReadOnly,
ReadWrite,
ReadExecute,
ReadWriteExecute,
}
impl AccessType {
pub fn from_efi_attributes(attributes: u64) -> AccessType {
if attributes & efi::MEMORY_RP != 0 {
AccessType::NoAccess
} else {
let readable_attr = attributes & (efi::MEMORY_RO | efi::MEMORY_XP);
if readable_attr == efi::MEMORY_RO {
AccessType::ReadExecute
} else if readable_attr == efi::MEMORY_XP {
AccessType::ReadWrite
} else if readable_attr == (efi::MEMORY_RO | efi::MEMORY_XP) {
AccessType::ReadOnly
} else {
AccessType::ReadWriteExecute
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CachingType {
Uncached,
WriteCombining,
WriteBack,
WriteThrough,
WriteProtect,
}
impl CachingType {
pub fn from_efi_attributes(attributes: u64) -> Option<CachingType> {
match attributes & efi::CACHE_ATTRIBUTE_MASK {
efi::MEMORY_WB => Some(CachingType::WriteBack),
efi::MEMORY_WC => Some(CachingType::WriteCombining),
efi::MEMORY_WT => Some(CachingType::WriteThrough),
efi::MEMORY_UC => Some(CachingType::Uncached),
efi::MEMORY_WP => Some(CachingType::WriteProtect),
_ => None,
}
}
}
#[derive(Debug)]
pub enum MemoryError {
InternalError,
NoAvailableMemory,
UnalignedAddress,
InvalidAlignment,
InvalidAddress,
InconsistentRangeAttributes,
InvalidPageCount,
UnsupportedMemoryType,
UnsupportedAttributes,
}
impl From<MemoryError> for EfiError {
fn from(value: MemoryError) -> Self {
match value {
MemoryError::NoAvailableMemory => EfiError::OutOfResources,
MemoryError::UnsupportedAttributes | MemoryError::UnsupportedMemoryType => EfiError::Unsupported,
_ => EfiError::InvalidParameter,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PageAllocationStrategy {
Any,
Address(usize),
MaxAddress(usize),
}
#[cfg(any(test, feature = "mockall"))]
pub use mock::StdMemoryManager;
#[cfg(any(test, feature = "mockall"))]
#[coverage(off)]
mod mock {
extern crate std;
use std::{
alloc::{Layout, alloc, dealloc},
collections::HashMap,
sync::Mutex,
};
use super::*;
#[derive(Default)]
pub struct StdMemoryManager {
memory_attributes: Mutex<HashMap<usize, (AccessType, CachingType)>>,
}
impl StdMemoryManager {
pub fn new() -> Self {
Self::default()
}
}
impl MemoryManager for StdMemoryManager {
fn allocate_pages(&self, page_count: usize, options: AllocationOptions) -> Result<PageAllocation, MemoryError> {
let Ok(layout) = Layout::from_size_align(page_count * UEFI_PAGE_SIZE, options.alignment()) else {
return Err(MemoryError::InvalidAlignment);
};
let blob = unsafe { NonNull::new(alloc(layout)).expect("Test has sufficient memory to allocate pages") };
unsafe {
PageAllocation::new(blob.as_ptr().expose_provenance(), page_count, Box::leak(Box::new(Self::new())))
}
}
unsafe fn free_pages(&self, address: usize, page_count: usize) -> Result<(), MemoryError> {
let ptr = address as *mut u8;
let layout = Layout::from_size_align(page_count * UEFI_PAGE_SIZE, UEFI_PAGE_SIZE).unwrap();
unsafe { dealloc(ptr, layout) };
Ok(())
}
unsafe fn set_page_attributes(
&self,
address: usize,
_page_count: usize,
access: AccessType,
caching: Option<CachingType>,
) -> Result<(), MemoryError> {
let caching = caching.unwrap_or(CachingType::WriteBack);
self.memory_attributes.lock().expect("This is not actually shared.").insert(address, (access, caching));
Ok(())
}
fn get_page_attributes(
&self,
address: usize,
_page_count: usize,
) -> Result<(AccessType, CachingType), MemoryError> {
if let Some((access, caching)) =
self.memory_attributes.lock().expect("This is not actually shared.").get(&address)
{
Ok((*access, *caching))
} else {
Err(MemoryError::InvalidAddress)
}
}
#[cfg(feature = "alloc")]
fn get_allocator(&self, _memory_type: EfiMemoryType) -> Result<&'static dyn Allocator, MemoryError> {
Ok(&std::alloc::System)
}
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use core::{
alloc::Layout,
sync::atomic::{AtomicBool, AtomicUsize},
};
use super::*;
use crate::component::service::Service;
#[test]
fn test_custom_mock_with_failing() {
let mut mock = MockMemoryManager::new();
mock.expect_allocate_pages().returning(|_, _| Err(MemoryError::NoAvailableMemory));
mock.expect_free_pages().returning(|_, _| Err(MemoryError::InvalidAddress));
mock.expect_set_page_attributes().returning(|_, _, _, _| Err(MemoryError::UnsupportedAttributes));
mock.expect_get_page_attributes().returning(|_, _| Err(MemoryError::InvalidAddress));
let service = Service::mock(Box::new(mock));
assert!(service.allocate_pages(5, AllocationOptions::new()).is_err());
assert!(unsafe { service.free_pages(0, 5).is_err() });
assert!(unsafe { service.set_page_attributes(0, 5, AccessType::ReadOnly, None).is_err() });
assert!(service.get_page_attributes(0, 5).is_err());
}
#[test]
fn test_error_to_efi_error_conversion_not_changed() {
let error = MemoryError::NoAvailableMemory;
assert_eq!(Into::<EfiError>::into(error), EfiError::OutOfResources);
let error = MemoryError::UnsupportedAttributes;
assert_eq!(Into::<EfiError>::into(error), EfiError::Unsupported);
let error = MemoryError::InvalidAddress;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
let error = MemoryError::InternalError;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
let error = MemoryError::InconsistentRangeAttributes;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
let error = MemoryError::InvalidPageCount;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
let error = MemoryError::InvalidAlignment;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
let error = MemoryError::UnsupportedMemoryType;
assert_eq!(Into::<EfiError>::into(error), EfiError::Unsupported);
let error = MemoryError::UnalignedAddress;
assert_eq!(Into::<EfiError>::into(error), EfiError::InvalidParameter);
}
#[test]
fn test_access_type_rp_always_no_access() {
let access = AccessType::from_efi_attributes(efi::MEMORY_RP | efi::MEMORY_RO);
assert_eq!(access, AccessType::NoAccess);
let access = AccessType::from_efi_attributes(efi::MEMORY_RP | efi::MEMORY_XP);
assert_eq!(access, AccessType::NoAccess);
let access = AccessType::from_efi_attributes(efi::MEMORY_RP | 0x50000);
assert_eq!(access, AccessType::NoAccess);
}
#[test]
fn test_access_type_logic_matches_expectations() {
let access = AccessType::from_efi_attributes(efi::MEMORY_RO);
assert_eq!(access, AccessType::ReadExecute);
let access = AccessType::from_efi_attributes(efi::MEMORY_XP);
assert_eq!(access, AccessType::ReadWrite);
let access = AccessType::from_efi_attributes(efi::MEMORY_RO | efi::MEMORY_XP);
assert_eq!(access, AccessType::ReadOnly);
let access = AccessType::from_efi_attributes(0);
assert_eq!(access, AccessType::ReadWriteExecute);
}
#[test]
fn test_conflicting_caching_types() {
let caching = CachingType::from_efi_attributes(efi::MEMORY_WB | efi::MEMORY_WC);
assert_eq!(caching, None);
let caching = CachingType::from_efi_attributes(efi::MEMORY_WT | efi::MEMORY_UC);
assert_eq!(caching, None);
let caching = CachingType::from_efi_attributes(0x50000);
assert_eq!(caching, None);
}
#[test]
fn test_caching_type_hardcoded_conversion_has_not_changed() {
let caching = CachingType::from_efi_attributes(efi::MEMORY_WB);
assert_eq!(caching, Some(CachingType::WriteBack));
let caching = CachingType::from_efi_attributes(efi::MEMORY_WC);
assert_eq!(caching, Some(CachingType::WriteCombining));
let caching = CachingType::from_efi_attributes(efi::MEMORY_WT);
assert_eq!(caching, Some(CachingType::WriteThrough));
let caching = CachingType::from_efi_attributes(efi::MEMORY_UC);
assert_eq!(caching, Some(CachingType::Uncached));
let caching = CachingType::from_efi_attributes(efi::MEMORY_WP);
assert_eq!(caching, Some(CachingType::WriteProtect));
let caching = CachingType::from_efi_attributes(0x50000);
assert_eq!(caching, None);
}
#[test]
fn test_page_free_allocate_errors() {
let pf = PageFree {
blob: NonNull::dangling(),
page_count: 1,
memory_manager: Box::leak(Box::new(StdMemoryManager::new())),
};
assert!(pf.allocate(Layout::new::<u8>()).is_err_and(|e| matches!(e, core::alloc::AllocError)));
}
#[test]
fn test_page_free_mismatched_address_should_assert() {
let mut value: u8 = 5;
let data = NonNull::new(&mut value).unwrap();
let pf = PageFree {
blob: unsafe { data.add(0x1000) },
page_count: 1,
memory_manager: Box::leak(Box::new(StdMemoryManager::new())),
};
unsafe { pf.deallocate(data, Layout::new::<u8>()) };
}
#[test]
fn test_page_free_should_bubble_update_page_dealloc_error() {
let mut value: u8 = 5;
let blob = NonNull::new(&mut value).unwrap();
let mut mock = MockMemoryManager::new();
mock.expect_free_pages().returning(|_, _| Err(MemoryError::InvalidAddress));
let pf = PageFree { blob, page_count: 1, memory_manager: Box::leak(Box::new(mock)) };
unsafe { pf.deallocate(blob, Layout::new::<u8>()) };
}
#[test]
fn test_bubble_up_free_pages_err() {
let mut pa = StdMemoryManager::new().allocate_pages(1, AllocationOptions::new()).unwrap();
let mut mock = MockMemoryManager::new();
mock.expect_free_pages().returning(|_, _| Err(MemoryError::InvalidAddress));
pa.memory_manager = Box::leak(Box::new(mock));
}
#[test]
fn test_page_allocation_display() {
let mm = StdMemoryManager::new();
let page = mm.allocate_pages(1, AllocationOptions::new()).unwrap();
let address = page.blob.as_ptr() as usize;
let display = format!("{page}");
let _ = page.into_raw_ptr::<u8>(); let expected = format!("PageAllocation {{ address: 0x{address:x}, page_count: 1 }}");
assert_eq!(display, expected);
}
#[test]
fn test_page_allocation() {
let mock = StdMemoryManager::new();
let service = Service::mock(Box::new(mock));
let page = service.allocate_pages(1, AllocationOptions::new()).unwrap();
assert_eq!(page.page_count(), 1);
assert_eq!(page.byte_length(), UEFI_PAGE_SIZE);
let my_thing = page.leak_as(42).unwrap();
assert_eq!(*my_thing, 42);
}
#[test]
fn test_failed_into_box_does_not_panic() {
let mm = Service::mock(Box::new(StdMemoryManager::new()));
let page = mm
.allocate_pages(1, AllocationOptions::default())
.unwrap_or_else(|e| panic!("Failed to allocate pages: {e:?}"));
assert!(
page.into_box([42_u8; UEFI_PAGE_SIZE + 1]).is_none(),
"Expected allocation to fail due to insufficient size for Box<[u8]>"
);
}
#[test]
fn test_failed_leak_as_does_not_panic() {
let mm = Service::mock(Box::new(StdMemoryManager::new()));
let page = mm
.allocate_pages(1, AllocationOptions::default())
.unwrap_or_else(|e| panic!("Failed to allocate pages: {e:?}"));
assert!(
page.leak_as([42_u8; UEFI_PAGE_SIZE + 1]).is_none(),
"Expected allocation to fail due to insufficient size for [u8]"
);
}
#[test]
fn test_into_boxed_slice_will_call_drop_properly() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
static COUNT: AtomicUsize = AtomicUsize::new(0);
struct MyStruct(usize);
impl MyStruct {
fn value(&self) -> usize {
self.0
}
}
impl Default for MyStruct {
fn default() -> Self {
MyStruct(COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
}
}
impl Drop for MyStruct {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(1, AllocationOptions::default()).expect("Should not fail for test.");
{
let boxed_slice = pa.into_boxed_slice::<MyStruct>();
assert_eq!(boxed_slice.len(), UEFI_PAGE_SIZE / size_of::<MyStruct>());
let mut i = 0;
boxed_slice.iter().for_each(|item| {
assert_eq!(item.value(), i, "Default value of MyStruct should be {i}");
i += 1;
});
}
assert_eq!(
DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst),
UEFI_PAGE_SIZE / size_of::<MyStruct>(),
"Drop should be called for each item in the boxed slice"
);
}
#[test]
fn test_allocation_options_config_sticks() {
let options = AllocationOptions::default()
.with_alignment(0x200)
.with_memory_type(EfiMemoryType::PalCode)
.with_strategy(PageAllocationStrategy::Address(0x1000_0000_0000_0004));
assert_eq!(options.alignment(), 0x200);
assert_eq!(options.memory_type(), EfiMemoryType::PalCode);
assert_eq!(options.strategy(), PageAllocationStrategy::Address(0x1000_0000_0000_0004));
}
#[test]
fn test_bad_page_allocation() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let address = UefiPage([0u8; UEFI_PAGE_SIZE]).0.as_mut_ptr() as usize;
assert!(
unsafe { PageAllocation::new(address + 1, 1, mm) }
.is_err_and(|e| matches!(e, MemoryError::UnalignedAddress))
);
assert!(
unsafe { PageAllocation::new(address - 1, 1, mm) }
.is_err_and(|e| matches!(e, MemoryError::UnalignedAddress))
);
assert!(
unsafe { PageAllocation::new(address, 0, mm) }.is_err_and(|e| matches!(e, MemoryError::InvalidPageCount))
);
}
#[test]
fn test_page_allocation_zeroing_all_pages_works() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(10, AllocationOptions::default()).expect("Should not fail for test.");
unsafe { pa.blob.cast::<UefiPage>().write(UefiPage([1u8; UEFI_PAGE_SIZE])) };
pa.zero_pages();
let a = pa.into_raw_ptr::<u8>().unwrap();
for i in 0..(UEFI_PAGE_SIZE * 10) {
assert_eq!(unsafe { *a.add(i) }, 0, "Byte at index {i} is not zeroed");
}
}
#[test]
fn test_into_raw_slice() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(10, AllocationOptions::default()).expect("Should not fail for test.");
let slice: *mut [u64] = pa.into_raw_slice();
assert_eq!(slice.len(), (UEFI_PAGE_SIZE * 10) / size_of::<u64>());
#[repr(C, packed(1))]
struct TestWeirdSized {
_a: u64,
_b: u32,
_c: u16,
}
assert_ne!(size_of::<TestWeirdSized>() % UEFI_PAGE_SIZE, 0);
let pa = mm.allocate_pages(10, AllocationOptions::default()).expect("Should not fail for test.");
let slice: *mut [TestWeirdSized] = pa.into_raw_slice();
assert_eq!(slice.len(), (UEFI_PAGE_SIZE * 10) / size_of::<TestWeirdSized>());
}
#[test]
fn test_allocate_zero_pages_bubbles_up_error() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let Ok(pa) = mm.allocate_zero_pages(10, AllocationOptions::default()) else {
panic!("Expected allocation to succeed, but it failed.");
};
let _ = pa.into_raw_ptr::<u8>();
let pages = 2usize.pow(63) / UEFI_PAGE_SIZE;
assert!(mm.allocate_pages(pages, AllocationOptions::default()).is_err());
}
#[test]
fn test_into_box_value_is_placed_properly() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
static DROPPED: AtomicBool = AtomicBool::new(false);
struct MyStruct(usize);
impl MyStruct {
fn new(value: usize) -> Self {
MyStruct(value)
}
fn value(&self) -> usize {
self.0
}
}
impl Drop for MyStruct {
fn drop(&mut self) {
DROPPED.store(true, core::sync::atomic::Ordering::SeqCst);
}
}
let pa = mm.allocate_pages(1, AllocationOptions::default()).expect("Should not fail for test.");
{
let boxed = pa.into_box(MyStruct::new(42)).expect("Should convert to Box<T> successfully");
assert_eq!(boxed.value(), 42);
}
assert!(DROPPED.load(core::sync::atomic::Ordering::SeqCst), "Drop was not called on MyStruct");
}
#[test]
fn test_into_boxed_slice_with_missing_size_returns_slice_of_size_zero() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(1, AllocationOptions::default()).expect("Should not fail for test.");
let slice = pa.into_raw_slice::<[u8; UEFI_PAGE_SIZE * 2]>();
assert_eq!(slice.len(), 0);
}
#[test]
fn test_leak_as_slice_does_not_drop_items() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
static COUNT: AtomicUsize = AtomicUsize::new(0);
struct MyStruct(usize);
impl MyStruct {
fn value(&self) -> usize {
self.0
}
}
impl Default for MyStruct {
fn default() -> Self {
MyStruct(COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
}
}
impl Drop for MyStruct {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(1, AllocationOptions::default()).expect("Should not fail for test.");
{
let boxed_slice = pa.leak_as_slice::<MyStruct>();
assert_eq!(boxed_slice.len(), UEFI_PAGE_SIZE / size_of::<MyStruct>());
let mut i = 0;
boxed_slice.iter().for_each(|item| {
assert_eq!(item.value(), i, "Default value of MyStruct should be {i}");
i += 1;
});
}
assert_eq!(
DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst),
0,
"Slice is static, so individual items should not be dropped unless replaced"
);
}
#[test]
fn test_uefi_page_size_is_4k() {
assert_eq!(UEFI_PAGE_SIZE, 4096, "UEFI page size should be 4k (4096 bytes)");
}
#[test]
fn test_ptr_too_large() {
let mm = Box::leak(Box::new(StdMemoryManager::new()));
let pa = mm.allocate_pages(1, AllocationOptions::default()).expect("Should not fail for test.");
let res = pa.into_raw_ptr::<[u8; UEFI_PAGE_SIZE + 1]>();
assert!(res.is_none(), "Expected allocation to fail due to insufficient size for [u8; UEFI_PAGE_SIZE + 1]");
}
}