use crate::allocator::{DEFAULT_PAGE_ALLOCATION_GRANULARITY, RUNTIME_PAGE_ALLOCATION_GRANULARITY};
use patina::{
base::{UEFI_PAGE_SHIFT, align_up},
efi_types::{EFI_MAX_MEMORY_TYPE, INVALID_INFORMATION_INDEX},
pi::hob::{self, EFiMemoryTypeInformation, Hob, HobList, MEMORY_TYPE_INFO_HOB_GUID},
uefi_pages_to_size, uefi_size_to_pages,
};
use r_efi::efi;
use alloc::vec::Vec;
const MAX_MEMORY_TYPE_INFO_ENTRIES: usize = EFI_MAX_MEMORY_TYPE + 1;
const MAX_ALLOC_ADDRESS: efi::PhysicalAddress = u64::MAX >> 1;
const LOG_TARGET: &str = "memory_bin";
#[coverage(off)]
pub(crate) fn memory_type_name(memory_type: efi::MemoryType) -> &'static str {
match memory_type {
efi::RESERVED_MEMORY_TYPE => "ReservedMemoryType",
efi::LOADER_CODE => "LoaderCode",
efi::LOADER_DATA => "LoaderData",
efi::BOOT_SERVICES_CODE => "BootServicesCode",
efi::BOOT_SERVICES_DATA => "BootServicesData",
efi::RUNTIME_SERVICES_CODE => "RuntimeServicesCode",
efi::RUNTIME_SERVICES_DATA => "RuntimeServicesData",
efi::CONVENTIONAL_MEMORY => "ConventionalMemory",
efi::UNUSABLE_MEMORY => "UnusableMemory",
efi::ACPI_RECLAIM_MEMORY => "ACPIReclaimMemory",
efi::ACPI_MEMORY_NVS => "ACPIMemoryNVS",
efi::MEMORY_MAPPED_IO => "MemoryMappedIO",
efi::MEMORY_MAPPED_IO_PORT_SPACE => "MemoryMappedIOPortSpace",
efi::PAL_CODE => "PalCode",
efi::PERSISTENT_MEMORY => "PersistentMemory",
efi::UNACCEPTED_MEMORY_TYPE => "UnacceptedMemoryType",
_ => "Unknown",
}
}
const fn align_pages_to_granularity(pages: u64, granularity: usize) -> u64 {
let granularity_pages: u64 = (granularity >> UEFI_PAGE_SHIFT) as u64;
if granularity_pages <= 1 {
return pages;
}
pages.div_ceil(granularity_pages) * granularity_pages
}
#[derive(Debug, Clone, Copy)]
struct MemoryBinStatistics {
base_address: efi::PhysicalAddress,
maximum_address: efi::PhysicalAddress,
current_number_of_pages: u64,
number_of_pages: u64,
information_index: usize,
special: bool,
runtime: bool,
}
impl MemoryBinStatistics {
#[coverage(off)]
const fn new(special: bool, runtime: bool) -> Self {
Self {
base_address: 0,
maximum_address: MAX_ALLOC_ADDRESS,
current_number_of_pages: 0,
number_of_pages: 0,
information_index: INVALID_INFORMATION_INDEX,
special,
runtime,
}
}
}
const DEFAULT_STATISTICS: [MemoryBinStatistics; EFI_MAX_MEMORY_TYPE + 1] = [
MemoryBinStatistics::new(true, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(true, true), MemoryBinStatistics::new(true, true), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(true, false), MemoryBinStatistics::new(true, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(true, true), MemoryBinStatistics::new(false, false), MemoryBinStatistics::new(true, false), MemoryBinStatistics::new(false, false), ];
pub(crate) struct MemoryBinManager {
statistics: [MemoryBinStatistics; EFI_MAX_MEMORY_TYPE + 1],
memory_type_information: [EFiMemoryTypeInformation; MAX_MEMORY_TYPE_INFO_ENTRIES],
memory_type_information_count: usize,
initialized: bool,
}
impl MemoryBinManager {
pub(crate) const fn new() -> Self {
Self {
statistics: DEFAULT_STATISTICS,
memory_type_information: [EFiMemoryTypeInformation { memory_type: 0, number_of_pages: 0 };
MAX_MEMORY_TYPE_INFO_ENTRIES],
memory_type_information_count: 0,
initialized: false,
}
}
pub(crate) fn is_initialized(&self) -> bool {
self.initialized
}
pub const fn granularity_for_type(memory_type: efi::MemoryType) -> usize {
match memory_type {
efi::RESERVED_MEMORY_TYPE
| efi::ACPI_MEMORY_NVS
| efi::RUNTIME_SERVICES_CODE
| efi::RUNTIME_SERVICES_DATA => RUNTIME_PAGE_ALLOCATION_GRANULARITY,
_ => DEFAULT_PAGE_ALLOCATION_GRANULARITY,
}
}
fn calculate_total_bin_size(memory_type_info: &[EFiMemoryTypeInformation], bin_top: efi::PhysicalAddress) -> u64 {
let mut total_size: u64 = 0;
let mut current_top = bin_top;
for entry in memory_type_info {
if entry.memory_type as usize >= EFI_MAX_MEMORY_TYPE {
break;
}
let granularity = Self::granularity_for_type(entry.memory_type) as u64;
let entry_size = uefi_pages_to_size!(entry.number_of_pages as usize) as u64;
total_size += entry_size;
if current_top == 0 {
continue;
}
current_top -= entry_size;
let alignment_padding = current_top & (granularity - 1);
total_size += alignment_padding;
current_top &= !(granularity - 1);
}
total_size
}
pub(crate) fn contiguous_alloc_size(memory_type_info: &[EFiMemoryTypeInformation]) -> Option<usize> {
let mut raw_total: usize = 0;
let mut entry_count: usize = 0;
let mut max_granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY;
for entry in memory_type_info {
if entry.memory_type as usize >= EFI_MAX_MEMORY_TYPE {
break;
}
if entry.number_of_pages == 0 {
continue;
}
raw_total += uefi_pages_to_size!(entry.number_of_pages as usize);
entry_count += 1;
max_granularity = max_granularity.max(Self::granularity_for_type(entry.memory_type));
}
if raw_total == 0 {
return None;
}
let padded = raw_total + entry_count * max_granularity;
Some(align_up(padded, max_granularity).unwrap_or(padded))
}
pub(crate) fn max_granularity(memory_type_info: &[EFiMemoryTypeInformation]) -> usize {
memory_type_info
.iter()
.take_while(|e| (e.memory_type as usize) < EFI_MAX_MEMORY_TYPE)
.filter(|e| e.number_of_pages > 0)
.map(|e| Self::granularity_for_type(e.memory_type))
.max()
.unwrap_or(DEFAULT_PAGE_ALLOCATION_GRANULARITY)
}
pub(crate) fn initialize_from_range(
&mut self,
start: efi::PhysicalAddress,
length: u64,
memory_type_info: &[EFiMemoryTypeInformation],
) -> bool {
if self.initialized {
log::warn!("Memory bins already initialized, ignoring range.");
return false;
}
let end = match start.checked_add(length) {
Some(end) if end <= MAX_ALLOC_ADDRESS => end,
_ => {
log::warn!(
target: LOG_TARGET,
"Memory bin range invalid: start={:#X} length={:#X} (overflow or exceeds MAX_ALLOC_ADDRESS)",
start,
length
);
return false;
}
};
let total_needed = Self::calculate_total_bin_size(memory_type_info, end);
if total_needed > length {
log::warn!(
target: LOG_TARGET,
"Memory bin range too small: need {:#X} bytes but only {:#X} available.",
total_needed,
length
);
return false;
}
log::info!(
target: LOG_TARGET,
"Initializing memory bins from PEI range: base={:#X} length={:#X} total_needed={:#X}",
start,
length,
total_needed
);
let mut top = end;
for (index, entry) in memory_type_info.iter().enumerate() {
let mem_type = entry.memory_type;
if mem_type as usize >= EFI_MAX_MEMORY_TYPE {
break;
}
if entry.number_of_pages == 0 {
continue;
}
let entry_size = uefi_pages_to_size!(entry.number_of_pages as usize) as u64;
let stats =
self.statistics.get_mut(mem_type as usize).expect("Defined memory types should be in statistics");
stats.maximum_address = top - 1;
top -= entry_size;
let granularity = Self::granularity_for_type(mem_type) as u64;
top &= !(granularity - 1);
stats.base_address = top;
stats.number_of_pages = entry.number_of_pages as u64;
stats.information_index = index;
log::info!(
target: LOG_TARGET,
" Bin[{}] {}: base={:#X} max={:#X} pages={:#X} ({} pages)",
mem_type,
memory_type_name(mem_type),
stats.base_address,
stats.maximum_address,
stats.number_of_pages,
stats.number_of_pages
);
}
self.finalize_information_index(memory_type_info);
self.copy_memory_type_info(memory_type_info);
self.initialized = true;
log::info!(
target: LOG_TARGET,
"Memory bins initialized from pre-allocated range."
);
true
}
fn finalize_information_index(&mut self, memory_type_info: &[EFiMemoryTypeInformation]) {
for mem_type in 0..EFI_MAX_MEMORY_TYPE {
let stats = self.statistics.get_mut(mem_type).expect("All defined memory types should be in statistics");
for (index, entry) in memory_type_info.iter().enumerate() {
if mem_type == entry.memory_type as usize {
stats.information_index = index;
}
}
stats.current_number_of_pages = 0;
}
log::trace!(target: LOG_TARGET, "Bin stats: finalized information indices, reset current_number_of_pages to 0 for all types");
}
fn copy_memory_type_info(&mut self, memory_type_info: &[EFiMemoryTypeInformation]) {
let count = memory_type_info.len().min(self.memory_type_information.len());
let src = memory_type_info.get(..count).expect("Failed to get source slice");
let dest = self.memory_type_information.get_mut(..count).expect("Failed to get destination slice");
dest.copy_from_slice(src);
self.memory_type_information_count = count;
if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) {
log::trace!(
target: LOG_TARGET,
"Bin table: initialized with {} entries from HOB",
count
);
if let Some(entries) = self.memory_type_information.get(..count) {
for entry in entries {
log::trace!(
target: LOG_TARGET,
" Bin table: {} pages={}",
memory_type_name(entry.memory_type),
entry.number_of_pages
);
}
}
}
}
pub(crate) fn seed_statistics_from_hob(&mut self, memory_type: efi::MemoryType, pages: u64) {
if !self.initialized {
return;
}
let type_idx = memory_type as usize;
let Some(stats) = self.statistics.get_mut(type_idx).filter(|s| s.special) else {
return;
};
let aligned_pages = align_pages_to_granularity(pages, Self::granularity_for_type(memory_type));
stats.current_number_of_pages += aligned_pages;
log::debug!(
target: LOG_TARGET,
"PEI seed: {} +{} pages. total={}",
memory_type_name(memory_type),
pages,
stats.current_number_of_pages
);
}
#[cfg(test)]
pub(crate) fn current_pages_for_type(&self, memory_type: efi::MemoryType) -> u64 {
self.statistics.get(memory_type as usize).map_or(0, |s| s.current_number_of_pages)
}
pub(crate) fn active_bins(
&self,
) -> impl Iterator<Item = (efi::MemoryType, efi::PhysicalAddress, efi::PhysicalAddress, u64)> + '_ {
self.statistics.iter().enumerate().filter_map(|(idx, stats)| {
if stats.number_of_pages > 0 && idx < EFI_MAX_MEMORY_TYPE {
Some((idx as efi::MemoryType, stats.base_address, stats.maximum_address, stats.number_of_pages))
} else {
None
}
})
}
pub(crate) fn record_allocation(&mut self, memory_type: efi::MemoryType, pages: u64) {
if !self.initialized {
return;
}
let type_idx = memory_type as usize;
let Some(stats) = self.statistics.get_mut(type_idx).filter(|s| s.special) else {
return;
};
let aligned_pages = align_pages_to_granularity(pages, Self::granularity_for_type(memory_type));
let prev = stats.current_number_of_pages;
stats.current_number_of_pages += aligned_pages;
let current = stats.current_number_of_pages;
let info_idx = stats.information_index;
log::trace!(
target: LOG_TARGET,
"Bin stats: {} current_pages {} -> {} (alloc +{} aligned to {})",
memory_type_name(memory_type),
prev,
current,
pages,
aligned_pages
);
if let Some(mti_entry) = self.memory_type_information.get_mut(info_idx)
&& current > mti_entry.number_of_pages as u64
{
let prev_peak = mti_entry.number_of_pages;
mti_entry.number_of_pages = current as u32;
log::trace!(
target: LOG_TARGET,
"Bin table: {} pages {} -> {} (peak update)",
memory_type_name(memory_type),
prev_peak,
mti_entry.number_of_pages
);
}
}
pub(crate) fn record_free(&mut self, memory_type: efi::MemoryType, pages: u64) {
if !self.initialized {
return;
}
let type_idx = memory_type as usize;
let Some(stats) = self.statistics.get_mut(type_idx).filter(|s| s.special) else {
return;
};
let aligned_pages = align_pages_to_granularity(pages, Self::granularity_for_type(memory_type));
let prev = stats.current_number_of_pages;
stats.current_number_of_pages = stats.current_number_of_pages.saturating_sub(aligned_pages);
log::trace!(
target: LOG_TARGET,
"Bin stats: {} current_pages {} -> {} (free -{} aligned to {})",
memory_type_name(memory_type),
prev,
stats.current_number_of_pages,
pages,
aligned_pages
);
}
pub(crate) fn apply_bin_descriptors(&self, buffer: &mut [efi::MemoryDescriptor], count: usize) -> usize {
if !self.initialized {
return count;
}
let buffer_len = buffer.len();
let mut current_count = count;
for mem_type in 0..(EFI_MAX_MEMORY_TYPE as u32) {
let Some(stats) = self.statistics.get(mem_type as usize).filter(|s| s.special && s.number_of_pages > 0)
else {
continue;
};
let bin_start = stats.base_address;
let bin_end = stats.maximum_address;
log::debug!(
target: LOG_TARGET,
"GetMemoryMap: processing bin[{}] {} range=[{:#X}..{:#X}]",
mem_type,
memory_type_name(mem_type),
bin_start,
bin_end
);
loop {
current_count = Self::merge_descriptors(buffer, current_count);
let entry_count = current_count;
let mut did_modify = false;
for i in 0..entry_count {
let Some(entry) =
buffer.get_mut(i).filter(|e| e.r#type == efi::CONVENTIONAL_MEMORY && e.number_of_pages > 0)
else {
continue;
};
let entry_start = entry.physical_start;
let entry_end = entry_start + uefi_pages_to_size!(entry.number_of_pages as usize) as u64 - 1;
if entry_end < bin_start || entry_start > bin_end {
continue;
}
if entry_start >= bin_start && entry_end <= bin_end {
Self::set_descriptor_type(entry, mem_type, stats.runtime);
did_modify = true;
break;
}
if entry_start < bin_start {
let extra_needed = if entry_end > bin_end { 2 } else { 1 };
debug_assert!(
current_count + extra_needed <= buffer_len,
"apply_bin_descriptors: buffer is too small, caller must reserve max_additional_descriptors()"
);
if current_count + extra_needed > buffer_len {
log::error!(
target: LOG_TARGET,
"Buffer is too small for memory bin descriptor split, leaving entry as EfiConventionalMemory."
);
return current_count;
}
let pre_bin_pages = uefi_size_to_pages!((bin_start - entry_start) as usize);
entry.number_of_pages = pre_bin_pages as u64;
current_count = Self::insert_descriptor_after(buffer, current_count, i);
let new_idx = i + 1;
let new_entry = buffer.get_mut(new_idx).expect("Newly inserted entry not found");
new_entry.physical_start = bin_start;
new_entry.number_of_pages = uefi_size_to_pages!((entry_end - bin_start + 1) as usize) as u64;
Self::set_descriptor_type(new_entry, mem_type, stats.runtime);
if entry_end > bin_end {
new_entry.number_of_pages = uefi_size_to_pages!((bin_end - bin_start + 1) as usize) as u64;
current_count = Self::insert_descriptor_after(buffer, current_count, new_idx);
let post_idx = new_idx + 1;
let post_entry = buffer.get_mut(post_idx).expect("Failed to get post-bin entry");
post_entry.physical_start = bin_end + 1;
post_entry.number_of_pages = uefi_size_to_pages!((entry_end - bin_end) as usize) as u64;
Self::set_descriptor_type(post_entry, efi::CONVENTIONAL_MEMORY, false);
}
did_modify = true;
break;
}
if entry_end > bin_end {
debug_assert!(
current_count < buffer_len,
"apply_bin_descriptors: buffer is too small, caller must reserve max_additional_descriptors() slack"
);
if current_count + 1 > buffer_len {
log::error!(
target: LOG_TARGET,
"Buffer is too small for memory bin descriptor split, leaving entry as EfiConventionalMemory."
);
return current_count;
}
entry.number_of_pages = uefi_size_to_pages!((bin_end - entry_start + 1) as usize) as u64;
Self::set_descriptor_type(entry, mem_type, stats.runtime);
current_count = Self::insert_descriptor_after(buffer, current_count, i);
let post_idx = i + 1;
let post_entry = buffer.get_mut(post_idx).expect("Failed to get newly created post-bin entry");
post_entry.physical_start = bin_end + 1;
post_entry.number_of_pages = uefi_size_to_pages!((entry_end - bin_end) as usize) as u64;
Self::set_descriptor_type(post_entry, efi::CONVENTIONAL_MEMORY, false);
did_modify = true;
break;
}
debug_assert!(
false,
"apply_bin_descriptors: overlap case fell through; entry=[{:#X}..{:#X}] bin=[{:#X}..{:#X}]",
entry_start, entry_end, bin_start, bin_end
);
break;
}
if !did_modify {
break;
}
}
}
Self::merge_descriptors(buffer, current_count)
}
pub(crate) fn memory_type_information(&self) -> &[EFiMemoryTypeInformation] {
self.memory_type_information
.get(..self.memory_type_information_count)
.expect("Memory Type Info count should be correct")
}
pub(crate) fn max_additional_descriptors(&self) -> usize {
if !self.initialized {
return 0;
}
self.statistics.iter().filter(|s| s.number_of_pages > 0 && s.special).count() * 2
}
fn set_descriptor_type(descriptor: &mut efi::MemoryDescriptor, memory_type: efi::MemoryType, runtime: bool) {
descriptor.r#type = memory_type;
if runtime {
descriptor.attribute |= efi::MEMORY_RUNTIME;
} else {
descriptor.attribute &= !efi::MEMORY_RUNTIME;
}
}
fn insert_descriptor_after(buffer: &mut [efi::MemoryDescriptor], count: usize, after_idx: usize) -> usize {
buffer.copy_within((after_idx + 1)..count, after_idx + 2);
let source = buffer.get(after_idx).copied().expect("Failed to get source entry");
let dest = buffer.get_mut(after_idx + 1).expect("Failed to get destination entry");
*dest = source;
count + 1
}
fn merge_descriptors(buffer: &mut [efi::MemoryDescriptor], count: usize) -> usize {
if count <= 1 {
return count;
}
let mut write_idx = 0;
for read_idx in 1..count {
let write_entry = *buffer.get(write_idx).expect("count should be accurate");
let read_entry = *buffer.get(read_idx).expect("count should be correct");
let prev_end =
write_entry.physical_start + uefi_pages_to_size!(write_entry.number_of_pages as usize) as u64;
if read_entry.r#type == write_entry.r#type
&& read_entry.attribute == write_entry.attribute
&& read_entry.physical_start == prev_end
{
let write_entry = buffer.get_mut(write_idx).expect("count should be accurate");
write_entry.number_of_pages += read_entry.number_of_pages;
} else {
write_idx += 1;
if write_idx != read_idx {
let write_entry = buffer.get_mut(write_idx).expect("count should be accurate");
*write_entry = read_entry;
}
}
}
write_idx + 1
}
#[cfg(test)]
pub(crate) fn reset(&mut self) {
self.statistics = DEFAULT_STATISTICS;
self.memory_type_information =
[EFiMemoryTypeInformation { memory_type: 0, number_of_pages: 0 }; MAX_MEMORY_TYPE_INFO_ENTRIES];
self.memory_type_information_count = 0;
self.initialized = false;
}
}
pub(crate) fn find_memory_type_info_resource_hob(
hob_list: &HobList,
memory_type_info: &[EFiMemoryTypeInformation],
) -> Option<(efi::PhysicalAddress, u64)> {
let target_guid = MEMORY_TYPE_INFO_HOB_GUID;
let mut count = 0u32;
let mut result: Option<(efi::PhysicalAddress, u64)> = None;
for hob_entry in hob_list.iter() {
let res_desc = match hob_entry {
Hob::ResourceDescriptor(rd) => rd,
_ => continue,
};
if res_desc.owner != target_guid {
continue;
}
if res_desc.resource_type != hob::EFI_RESOURCE_SYSTEM_MEMORY {
continue;
}
if (res_desc.resource_attribute & hob::MEMORY_ATTRIBUTE_MASK) != hob::TESTED_MEMORY_ATTRIBUTES {
continue;
}
let end = match res_desc.physical_start.checked_add(res_desc.resource_length) {
Some(end) if end <= MAX_ALLOC_ADDRESS => end,
_ => {
log::warn!(
target: LOG_TARGET,
"Skipping MemoryTypeInformation Resource Descriptor HOB with invalid range: start={:#X} length={:#X}",
res_desc.physical_start,
res_desc.resource_length
);
continue;
}
};
count += 1;
let total_needed = MemoryBinManager::calculate_total_bin_size(memory_type_info, end);
if res_desc.resource_length >= total_needed {
result = Some((res_desc.physical_start, res_desc.resource_length));
}
}
if count > 1 {
log::warn!(
target: LOG_TARGET,
"Multiple MemoryTypeInformation Resource Descriptor HOBs found ({}), rejecting all.",
count
);
return None;
}
if let Some((start, length)) = result {
log::info!(
target: LOG_TARGET,
"Found MemoryTypeInformation Resource Descriptor HOB: base={:#X} length={:#X}",
start,
length
);
} else {
log::info!(
target: LOG_TARGET,
"No MemoryTypeInformation Resource Descriptor HOB found. DXE will allocate bins."
);
}
result
}
pub(crate) fn extract_memory_type_info_from_hob(hob_list: &HobList) -> Option<Vec<EFiMemoryTypeInformation>> {
hob_list.iter().find_map(|hob_entry| {
if let Hob::GuidHob(hob, data) = hob_entry {
if hob.name != MEMORY_TYPE_INFO_HOB_GUID.into_inner() {
return None;
}
let entry_size = core::mem::size_of::<EFiMemoryTypeInformation>();
if data.is_empty() || data.len() > (EFI_MAX_MEMORY_TYPE + 1) * entry_size {
log::error!(target: LOG_TARGET, "Invalid Memory Type Information HOB data size: {}", data.len());
return None;
}
log::info!(
target: LOG_TARGET,
"Found Memory Type Information HOB ({} bytes, {} entries)",
data.len(),
data.len() / entry_size
);
let ptr = data.as_ptr() as *const EFiMemoryTypeInformation;
let len = data.len() / entry_size;
let raw_entries = unsafe { core::slice::from_raw_parts(ptr, len) };
let mut entries: Vec<EFiMemoryTypeInformation> = Vec::with_capacity(len);
for entry in raw_entries {
if entry.memory_type as usize >= EFI_MAX_MEMORY_TYPE {
entries.push(EFiMemoryTypeInformation {
memory_type: entry.memory_type,
number_of_pages: entry.number_of_pages,
});
break;
}
let granularity = MemoryBinManager::granularity_for_type(entry.memory_type);
let unaligned_size = uefi_pages_to_size!(entry.number_of_pages as usize);
let aligned_size = align_up(unaligned_size, granularity).unwrap_or(unaligned_size);
let aligned_pages = uefi_size_to_pages!(aligned_size);
log::info!(
target: LOG_TARGET,
" MemTypeInfo: {} pages={} (GCD alloc will use {})",
memory_type_name(entry.memory_type),
entry.number_of_pages,
aligned_pages,
);
entries.push(*entry);
}
Some(entries)
} else {
None
}
})
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use super::*;
use patina::base::{SIZE_64KB, UEFI_PAGE_SIZE};
const RT_GRAN_PAGES: u64 =
(MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA) / UEFI_PAGE_SIZE) as u64;
fn preferred_range(
manager: &MemoryBinManager,
memory_type: efi::MemoryType,
) -> Option<(efi::PhysicalAddress, efi::PhysicalAddress)> {
manager.active_bins().find(|(mt, _, _, _)| *mt == memory_type).map(|(_, base, max, _)| (base, max))
}
fn rt_range_size(pages: u32) -> u64 {
let granularity = MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA);
(pages as u64) * UEFI_PAGE_SIZE as u64 + granularity as u64
}
#[coverage(off)]
fn init_bins(manager: &mut MemoryBinManager, base: u64, info: &[EFiMemoryTypeInformation]) {
crate::test_support::init_test_logger();
let range_size = MemoryBinManager::contiguous_alloc_size(info).unwrap() as u64;
assert!(manager.initialize_from_range(base, range_size, info), "init_bins failed");
}
#[test]
fn test_memory_bin_new_uninitialized() {
let manager = MemoryBinManager::new();
assert!(!manager.is_initialized());
assert_eq!(preferred_range(&manager, efi::RUNTIME_SERVICES_DATA), None);
assert_eq!(manager.max_additional_descriptors(), 0);
}
#[test]
fn test_memory_bin_calculate_total_size_no_alignment() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 10 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 20 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let size = MemoryBinManager::calculate_total_bin_size(&info, 0);
assert_eq!(size, (10 + 20) * UEFI_PAGE_SIZE as u64);
}
#[test]
fn test_memory_bin_initialize_from_range() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 8 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_size = rt_range_size(4) + rt_range_size(8);
let range_start = 0x1000_0000u64;
let result = manager.initialize_from_range(range_start, range_size, &info);
assert!(result);
assert!(manager.is_initialized());
let rt_code_range = preferred_range(&manager, efi::RUNTIME_SERVICES_CODE);
assert!(rt_code_range.is_some());
let (base, max) = rt_code_range.unwrap();
assert!(base >= range_start);
assert!(max < range_start + range_size);
let rt_data_range = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA);
assert!(rt_data_range.is_some());
assert_eq!(preferred_range(&manager, efi::BOOT_SERVICES_DATA), None);
}
#[test]
fn test_memory_bin_initialize_from_range_too_small() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 100 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let result = manager.initialize_from_range(0x1000_0000, UEFI_PAGE_SIZE as u64, &info);
assert!(!result);
assert!(!manager.is_initialized());
}
#[test]
fn test_memory_bin_record_allocation_in_bin() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 64 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(64);
manager.initialize_from_range(range_start, range_size, &info);
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, 4);
assert_eq!(
manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages,
align_pages_to_granularity(4, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA))
);
let prev = manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages;
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, 2);
assert_eq!(
manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages,
prev + align_pages_to_granularity(2, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA))
);
}
#[test]
fn test_memory_bin_record_free() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 64 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(64);
manager.initialize_from_range(range_start, range_size, &info);
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, RT_GRAN_PAGES);
assert_eq!(manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages, RT_GRAN_PAGES);
manager.record_free(efi::RUNTIME_SERVICES_DATA, RT_GRAN_PAGES);
assert_eq!(manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages, 0);
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, RT_GRAN_PAGES);
manager.record_free(efi::RUNTIME_SERVICES_DATA, 100);
assert_eq!(manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages, 0);
}
#[test]
fn test_memory_bin_peak_tracking() {
let bin_pages: u32 = 8;
let alloc_pages = (bin_pages as u64).max(RT_GRAN_PAGES) + RT_GRAN_PAGES;
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: bin_pages },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(bin_pages).max(rt_range_size(alloc_pages as u32));
manager.initialize_from_range(range_start, range_size, &info);
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, alloc_pages);
let expected =
align_pages_to_granularity(alloc_pages, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA));
assert_eq!(manager.memory_type_information()[0].number_of_pages, expected as u32);
}
#[test]
fn test_memory_bin_apply_descriptors_fully_within() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_pages = uefi_size_to_pages!((bin_max - bin_base + 1) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: bin_base,
virtual_start: 0,
number_of_pages: bin_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
assert_ne!(buffer[0].attribute & efi::MEMORY_RUNTIME, 0);
}
#[test]
fn test_memory_bin_apply_descriptors_starts_before() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_size = bin_max - bin_base + 1;
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: entry_start,
virtual_start: 0,
number_of_pages: entry_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert!(count >= 2);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].physical_start, bin_base);
}
#[test]
fn test_memory_bin_apply_descriptors_ends_after() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_size = bin_max - bin_base + 1;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: bin_base,
virtual_start: 0,
number_of_pages: entry_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[0].physical_start, bin_base);
assert_eq!(buffer[1].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[1].physical_start, bin_max + 1);
}
#[test]
fn test_memory_bin_apply_descriptors_spans_bin() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_size = bin_max - bin_base + 1;
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + 2 * UEFI_PAGE_SIZE as u64) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: entry_start,
virtual_start: 0,
number_of_pages: entry_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 3);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].physical_start, bin_base);
assert_eq!(buffer[2].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[2].physical_start, bin_max + 1);
}
#[test]
fn test_memory_bin_apply_descriptors_no_overlap() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x8000_0000,
virtual_start: 0,
number_of_pages: 4,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
}
#[test]
fn test_memory_bin_apply_descriptors_runtime_attribute() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_CODE).unwrap();
let bin_pages = uefi_size_to_pages!((bin_max - bin_base + 1) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: bin_base,
virtual_start: 0,
number_of_pages: bin_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_CODE);
assert_ne!(buffer[0].attribute & efi::MEMORY_RUNTIME, 0);
}
#[test]
fn test_memory_bin_max_additional_descriptors() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 8 },
EFiMemoryTypeInformation { memory_type: efi::ACPI_MEMORY_NVS, number_of_pages: 2 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
assert_eq!(manager.max_additional_descriptors(), 3 * 2);
}
#[test]
fn test_memory_bin_seed_statistics_from_hob() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 64 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(64);
manager.initialize_from_range(range_start, range_size, &info);
manager.seed_statistics_from_hob(efi::RUNTIME_SERVICES_DATA, 3);
assert_eq!(
manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages,
align_pages_to_granularity(3, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA))
);
}
#[test]
fn test_memory_bin_no_bins_when_not_initialized() {
let manager = MemoryBinManager::new();
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x1000_0000,
virtual_start: 0,
number_of_pages: 4,
attribute: 0,
}; 5];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1); assert_eq!(preferred_range(&manager, efi::RUNTIME_SERVICES_DATA), None);
assert_eq!(manager.max_additional_descriptors(), 0);
}
#[test]
fn test_memory_bin_double_initialization_rejected() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(4);
assert!(manager.initialize_from_range(range_start, range_size, &info));
assert!(!manager.initialize_from_range(range_start + 0x100_0000, range_size, &info));
}
#[test]
fn test_merge_descriptors() {
let mut buffer = [
efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x1000,
virtual_start: 0,
number_of_pages: 1,
attribute: efi::MEMORY_WB,
},
efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x2000,
virtual_start: 0,
number_of_pages: 1,
attribute: efi::MEMORY_WB,
},
efi::MemoryDescriptor {
r#type: efi::RUNTIME_SERVICES_DATA,
physical_start: 0x3000,
virtual_start: 0,
number_of_pages: 2,
attribute: efi::MEMORY_WB | efi::MEMORY_RUNTIME,
},
efi::MemoryDescriptor { r#type: 0, physical_start: 0, virtual_start: 0, number_of_pages: 0, attribute: 0 },
efi::MemoryDescriptor { r#type: 0, physical_start: 0, virtual_start: 0, number_of_pages: 0, attribute: 0 },
];
let count = MemoryBinManager::merge_descriptors(&mut buffer, 3);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].number_of_pages, 2);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
}
#[test]
fn test_merge_descriptors_single_entry() {
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x1000,
virtual_start: 0,
number_of_pages: 4,
attribute: efi::MEMORY_WB,
}];
let count = MemoryBinManager::merge_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
}
#[test]
fn test_merge_descriptors_gap_prevents_merge() {
let mut buffer = [
efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x1000,
virtual_start: 0,
number_of_pages: 1,
attribute: efi::MEMORY_WB,
},
efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: 0x4000,
virtual_start: 0,
number_of_pages: 1,
attribute: efi::MEMORY_WB,
},
];
let count = MemoryBinManager::merge_descriptors(&mut buffer, 2);
assert_eq!(count, 2);
}
#[test]
fn test_calculate_total_bin_size_with_alignment() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 1 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let entry_size = UEFI_PAGE_SIZE as u64;
let size_no_align = MemoryBinManager::calculate_total_bin_size(&info, 0);
assert_eq!(size_no_align, entry_size);
let granularity = MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA) as u64;
let aligned_top = 0x1000_0000u64;
assert_eq!(aligned_top % granularity, 0, "test precondition: top must be granularity-aligned");
let size_aligned = MemoryBinManager::calculate_total_bin_size(&info, aligned_top);
if entry_size.is_multiple_of(granularity) {
assert_eq!(size_aligned, entry_size);
} else {
assert!(size_aligned >= entry_size);
}
let size_unaligned = MemoryBinManager::calculate_total_bin_size(&info, 0x1000_0001);
assert!(size_unaligned > entry_size);
}
#[test]
fn test_active_bins_returns_only_configured_types() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 0 },
EFiMemoryTypeInformation { memory_type: efi::ACPI_MEMORY_NVS, number_of_pages: 2 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x3000_0000, &info);
let bins: Vec<_> = manager.active_bins().collect();
assert_eq!(bins.len(), 2);
assert_eq!(bins[0].0, efi::RUNTIME_SERVICES_CODE);
assert_eq!(bins[1].0, efi::ACPI_MEMORY_NVS);
}
#[test]
fn test_record_allocation_ignored_for_non_special_type() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::BOOT_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x1000_0000, &info);
manager.record_allocation(efi::BOOT_SERVICES_DATA, 2);
assert_eq!(manager.statistics[efi::BOOT_SERVICES_DATA as usize].current_number_of_pages, 0);
}
#[test]
fn test_record_allocation_counts_outside_bin_range() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x1000_0000, &info);
manager.record_allocation(efi::RUNTIME_SERVICES_DATA, 2);
assert_eq!(
manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages,
align_pages_to_granularity(2, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA))
);
}
#[test]
fn test_seed_statistics_always_counted_for_special_types() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 16 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
let range_start = 0x1000_0000u64;
let range_size = rt_range_size(16);
manager.initialize_from_range(range_start, range_size, &info);
manager.seed_statistics_from_hob(efi::RUNTIME_SERVICES_DATA, 5);
assert_eq!(
manager.statistics[efi::RUNTIME_SERVICES_DATA as usize].current_number_of_pages,
align_pages_to_granularity(5, MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA))
);
manager.seed_statistics_from_hob(efi::BOOT_SERVICES_DATA, 3);
assert_eq!(manager.statistics[efi::BOOT_SERVICES_DATA as usize].current_number_of_pages, 0);
}
#[test]
fn test_memory_type_information_returned_after_init() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 8 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x1000_0000, &info);
let mti = manager.memory_type_information();
assert_eq!(mti.len(), 3);
assert_eq!(mti[0].memory_type, efi::RUNTIME_SERVICES_CODE);
assert_eq!(mti[0].number_of_pages, 4);
assert_eq!(mti[1].memory_type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(mti[1].number_of_pages, 8);
}
#[test]
fn test_apply_descriptors_skips_non_conventional() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_pages = uefi_size_to_pages!((bin_max - bin_base + 1) as usize);
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::BOOT_SERVICES_CODE,
physical_start: bin_base,
virtual_start: 0,
number_of_pages: bin_pages as u64,
attribute: efi::MEMORY_WB,
}; 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::BOOT_SERVICES_CODE);
}
#[test]
fn test_memory_type_name_known_and_unknown() {
assert_eq!(memory_type_name(efi::RUNTIME_SERVICES_DATA), "RuntimeServicesData");
assert_eq!(memory_type_name(efi::BOOT_SERVICES_CODE), "BootServicesCode");
assert!(memory_type_name(0xFFFF).starts_with("Unknown"));
}
#[test]
fn test_align_pages_to_granularity_equal_to_page_size() {
assert_eq!(align_pages_to_granularity(0, UEFI_PAGE_SIZE), 0);
assert_eq!(align_pages_to_granularity(1, UEFI_PAGE_SIZE), 1);
assert_eq!(align_pages_to_granularity(7, UEFI_PAGE_SIZE), 7);
}
#[test]
fn test_align_pages_to_granularity_smaller_than_page_size() {
assert_eq!(align_pages_to_granularity(5, UEFI_PAGE_SIZE / 2), 5);
}
#[test]
fn test_align_pages_to_granularity_two_pages() {
assert_eq!(align_pages_to_granularity(0, 2 * UEFI_PAGE_SIZE), 0);
assert_eq!(align_pages_to_granularity(1, 2 * UEFI_PAGE_SIZE), 2);
assert_eq!(align_pages_to_granularity(2, 2 * UEFI_PAGE_SIZE), 2);
assert_eq!(align_pages_to_granularity(3, 2 * UEFI_PAGE_SIZE), 4);
assert_eq!(align_pages_to_granularity(4, 2 * UEFI_PAGE_SIZE), 4);
}
#[test]
fn test_align_pages_to_granularity_sixteen_pages() {
assert_eq!(align_pages_to_granularity(0, SIZE_64KB), 0);
assert_eq!(align_pages_to_granularity(1, SIZE_64KB), 16);
assert_eq!(align_pages_to_granularity(15, SIZE_64KB), 16);
assert_eq!(align_pages_to_granularity(16, SIZE_64KB), 16);
assert_eq!(align_pages_to_granularity(17, SIZE_64KB), 32);
}
#[test]
fn test_contiguous_alloc_size_single_entry() {
let rt_data_pages = 10;
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: rt_data_pages },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let size = MemoryBinManager::contiguous_alloc_size(&info).unwrap();
let raw = rt_data_pages as usize * UEFI_PAGE_SIZE;
let granularity = MemoryBinManager::granularity_for_type(efi::RUNTIME_SERVICES_DATA);
assert!(size >= raw + granularity);
assert_eq!(size % granularity, 0);
}
#[test]
fn test_contiguous_alloc_size_multiple_entries() {
let rt_code_pages = 4;
let rt_data_pages = 8;
let acpi_reclaim_pages = 2;
let total_pages: usize = (rt_code_pages + rt_data_pages + acpi_reclaim_pages) as usize;
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: rt_code_pages },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: rt_data_pages },
EFiMemoryTypeInformation { memory_type: efi::ACPI_RECLAIM_MEMORY, number_of_pages: acpi_reclaim_pages },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let size = MemoryBinManager::contiguous_alloc_size(&info).unwrap();
let raw = total_pages * UEFI_PAGE_SIZE;
assert!(size >= raw, "size {:#X} must be >= raw {:#X}", size, raw);
}
#[test]
fn test_contiguous_alloc_size_empty() {
let info = [EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 }];
assert_eq!(MemoryBinManager::contiguous_alloc_size(&info), None);
}
#[test]
fn test_contiguous_alloc_size_all_zero_pages() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: 0 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 0 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
assert_eq!(MemoryBinManager::contiguous_alloc_size(&info), None);
}
#[test]
fn test_contiguous_alloc_size_skips_zero_page_entries() {
let rt_data_pages = 4;
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 0 },
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: rt_data_pages },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let size = MemoryBinManager::contiguous_alloc_size(&info).unwrap();
let raw = rt_data_pages as usize * UEFI_PAGE_SIZE;
assert!(size >= raw);
}
fn single_bin_manager(pages: u32) -> (MemoryBinManager, efi::PhysicalAddress, efi::PhysicalAddress, u64) {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_DATA, number_of_pages: pages },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x2000_0000, &info);
let (bin_base, bin_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_DATA).unwrap();
let bin_size = bin_max - bin_base + 1;
(manager, bin_base, bin_max, bin_size)
}
fn conv_descriptor(physical_start: efi::PhysicalAddress, pages: u64) -> efi::MemoryDescriptor {
efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start,
virtual_start: 0,
number_of_pages: pages,
attribute: efi::MEMORY_WB,
}
}
#[test]
fn test_apply_descriptors_count_zero() {
let (manager, _, _, _) = single_bin_manager(4);
let mut buffer = [conv_descriptor(0x1000_0000, 4); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 0);
assert_eq!(count, 0);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
}
#[test]
fn test_apply_descriptors_entry_just_before_bin() {
let (manager, bin_base, _, _) = single_bin_manager(4);
let pages = 2u64;
let entry_start = bin_base - uefi_pages_to_size!(pages as usize) as u64;
let mut buffer = [conv_descriptor(entry_start, pages); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[0].number_of_pages, pages);
}
#[test]
fn test_apply_descriptors_entry_just_after_bin() {
let (manager, _, bin_max, _) = single_bin_manager(4);
let mut buffer = [conv_descriptor(bin_max + 1, 2); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, bin_max + 1);
}
#[test]
fn test_apply_descriptors_entry_equals_bin_exactly() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!(bin_size as usize) as u64;
let mut buffer = [conv_descriptor(bin_base, entry_pages); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[0].physical_start, bin_base);
let expected_end =
buffer[0].physical_start + uefi_pages_to_size!(buffer[0].number_of_pages as usize) as u64 - 1;
assert_eq!(expected_end, bin_max);
}
#[test]
fn test_apply_descriptors_starts_before_ends_at_bin_end() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [conv_descriptor(entry_start, entry_pages); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[0].number_of_pages, 1);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].physical_start, bin_base);
let post_end = buffer[1].physical_start + uefi_pages_to_size!(buffer[1].number_of_pages as usize) as u64 - 1;
assert_eq!(post_end, bin_max);
}
#[test]
fn test_apply_descriptors_starts_at_bin_start_ends_after_bin() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [conv_descriptor(bin_base, entry_pages); 10];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[0].physical_start, bin_base);
assert_eq!(buffer[1].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[1].physical_start, bin_max + 1);
assert_eq!(buffer[1].number_of_pages, 1);
}
#[test]
fn test_apply_descriptors_multiple_bins() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::RUNTIME_SERVICES_CODE, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: efi::ACPI_MEMORY_NVS, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x3000_0000, &info);
let (rt_base, rt_max) = preferred_range(&manager, efi::RUNTIME_SERVICES_CODE).unwrap();
let (acpi_base, acpi_max) = preferred_range(&manager, efi::ACPI_MEMORY_NVS).unwrap();
let rt_pages = uefi_size_to_pages!((rt_max - rt_base + 1) as usize) as u64;
let acpi_pages = uefi_size_to_pages!((acpi_max - acpi_base + 1) as usize) as u64;
let (lo_base, lo_pages, lo_type, hi_base, hi_pages, hi_type) = if rt_base < acpi_base {
(rt_base, rt_pages, efi::RUNTIME_SERVICES_CODE, acpi_base, acpi_pages, efi::ACPI_MEMORY_NVS)
} else {
(acpi_base, acpi_pages, efi::ACPI_MEMORY_NVS, rt_base, rt_pages, efi::RUNTIME_SERVICES_CODE)
};
let mut buffer = [conv_descriptor(0, 0); 10];
buffer[0] = conv_descriptor(lo_base, lo_pages);
buffer[1] = conv_descriptor(hi_base, hi_pages);
let count = manager.apply_bin_descriptors(&mut buffer, 2);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, lo_type);
assert_eq!(buffer[1].r#type, hi_type);
}
#[test]
fn test_apply_descriptors_multiple_entries_one_overlapping() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!(bin_size as usize) as u64;
let before_pages = 2u64;
let before_start = bin_base - 0x1_0000 - uefi_pages_to_size!(before_pages as usize) as u64;
let after_start = bin_max + 1 + 0x1_0000;
let mut buffer = [conv_descriptor(0, 0); 10];
buffer[0] = conv_descriptor(before_start, before_pages);
buffer[1] = conv_descriptor(bin_base, entry_pages);
buffer[2] = conv_descriptor(after_start, 2);
let count = manager.apply_bin_descriptors(&mut buffer, 3);
assert_eq!(count, 3);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, before_start);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].physical_start, bin_base);
assert_eq!(buffer[2].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[2].physical_start, after_start);
}
#[test]
fn test_apply_descriptors_exact_fit_buffer_case2_single() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [conv_descriptor(entry_start, entry_pages); 2];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
let post_end = buffer[1].physical_start + uefi_pages_to_size!(buffer[1].number_of_pages as usize) as u64 - 1;
assert_eq!(post_end, bin_max);
}
#[test]
fn test_apply_descriptors_exact_fit_buffer_case2_double() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + 2 * UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [conv_descriptor(entry_start, entry_pages); 3];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 3);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].physical_start, bin_base);
assert_eq!(buffer[2].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[2].physical_start, bin_max + 1);
}
#[test]
fn test_apply_descriptors_exact_fit_buffer_case3() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [conv_descriptor(bin_base, entry_pages); 2];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
assert_eq!(buffer[1].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[1].physical_start, bin_max + 1);
}
#[test]
fn test_apply_descriptors_buffer_too_small_case2_single_fallback() {
let (manager, bin_base, _, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let original = conv_descriptor(entry_start, entry_pages);
let mut buffer = [original; 1];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[0].number_of_pages, entry_pages);
}
#[test]
fn test_apply_descriptors_buffer_too_small_case2_double_fallback() {
let (manager, bin_base, _, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + 2 * UEFI_PAGE_SIZE as u64) as usize) as u64;
let original = conv_descriptor(entry_start, entry_pages);
let mut buffer = [original; 2];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, entry_start);
assert_eq!(buffer[0].number_of_pages, entry_pages);
}
#[test]
fn test_apply_descriptors_buffer_too_small_case3_fallback() {
let (manager, bin_base, _, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let original = conv_descriptor(bin_base, entry_pages);
let mut buffer = [original; 1];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, bin_base);
assert_eq!(buffer[0].number_of_pages, entry_pages);
}
#[test]
fn test_apply_descriptors_buffer_too_small_case1_unaffected() {
let (manager, bin_base, _, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!(bin_size as usize) as u64;
let mut buffer = [conv_descriptor(bin_base, entry_pages); 1];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::RUNTIME_SERVICES_DATA);
}
#[test]
fn test_apply_descriptors_initialized_no_special_bins() {
let info = [
EFiMemoryTypeInformation { memory_type: efi::BOOT_SERVICES_DATA, number_of_pages: 4 },
EFiMemoryTypeInformation { memory_type: EFI_MAX_MEMORY_TYPE as u32, number_of_pages: 0 },
];
let mut manager = MemoryBinManager::new();
init_bins(&mut manager, 0x4000_0000, &info);
assert_eq!(manager.max_additional_descriptors(), 0);
let mut buffer = [conv_descriptor(0x4000_0000, 4); 4];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
}
#[test]
fn test_apply_descriptors_preserves_attributes_on_pre_bin_split() {
let (manager, bin_base, bin_max, bin_size) = single_bin_manager(4);
let entry_start = bin_base - UEFI_PAGE_SIZE as u64;
let entry_pages = uefi_size_to_pages!((bin_size + UEFI_PAGE_SIZE as u64) as usize) as u64;
let mut buffer = [efi::MemoryDescriptor {
r#type: efi::CONVENTIONAL_MEMORY,
physical_start: entry_start,
virtual_start: 0,
number_of_pages: entry_pages,
attribute: efi::MEMORY_WB | efi::MEMORY_WT,
}; 4];
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 2);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].attribute & (efi::MEMORY_WB | efi::MEMORY_WT), efi::MEMORY_WB | efi::MEMORY_WT);
assert_eq!(buffer[0].attribute & efi::MEMORY_RUNTIME, 0);
assert_eq!(buffer[1].r#type, efi::RUNTIME_SERVICES_DATA);
assert_ne!(buffer[1].attribute & efi::MEMORY_RUNTIME, 0);
assert_eq!(buffer[1].attribute & (efi::MEMORY_WB | efi::MEMORY_WT), efi::MEMORY_WB | efi::MEMORY_WT);
let in_bin_end = buffer[1].physical_start + uefi_pages_to_size!(buffer[1].number_of_pages as usize) as u64 - 1;
assert_eq!(in_bin_end, bin_max);
}
#[test]
fn test_apply_descriptors_ignores_entries_beyond_count() {
let (manager, bin_base, _, bin_size) = single_bin_manager(4);
let entry_pages = uefi_size_to_pages!(bin_size as usize) as u64;
let mut buffer = [conv_descriptor(0, 0); 4];
buffer[0] = conv_descriptor(0x8000_0000, 2);
buffer[1] = conv_descriptor(bin_base, entry_pages);
let count = manager.apply_bin_descriptors(&mut buffer, 1);
assert_eq!(count, 1);
assert_eq!(buffer[0].r#type, efi::CONVENTIONAL_MEMORY);
assert_eq!(buffer[0].physical_start, 0x8000_0000);
assert_eq!(buffer[1].physical_start, bin_base);
assert_eq!(buffer[1].r#type, efi::CONVENTIONAL_MEMORY);
}
}