use core::{ffi::c_void, mem::size_of, num::NonZeroUsize, ptr::NonNull, slice};
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
use patina::{
pi::{
self,
fw_fs::{ffs, fv, fvb},
hob,
},
uefi::device_path::{
fv_types::{FvMemMapDevicePath, FvPiWgDevicePath},
walker::concat_device_path_to_boxed_slice,
},
};
use patina::error::EfiError;
use patina::standard::efi::{self, MEMORY_MAPPED_IO};
use patina_ffs::{file::FileRef, section::SectionExtractor, volume::VolumeRef};
use crate::{
Core, PlatformInfo,
allocator::core_allocate_pool,
protocols::{PROTOCOL_DB, core_install_protocol_interface},
tpl_mutex,
};
#[allow(dead_code)]
enum Protocol {
Fv(&'static pi::protocol::firmware_volume::FirmwareVolumeProtocol),
Fvb(&'static pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol),
}
struct Metadata {
protocol: Protocol,
physical_address: u64,
}
impl Metadata {
fn new_fvb(
protocol: Box<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
physical_address: u64,
) -> Self {
Self { protocol: Protocol::Fvb(Box::leak(protocol)), physical_address }
}
fn new_fv(protocol: Box<pi::protocol::firmware_volume::FirmwareVolumeProtocol>, physical_address: u64) -> Self {
Self { protocol: Protocol::Fv(Box::leak(protocol)), physical_address }
}
}
pub(super) struct FvProtocolData<P: PlatformInfo> {
fv_metadata: BTreeMap<NonZeroUsize, Metadata>,
_platform_info: core::marker::PhantomData<P>,
}
impl<P: PlatformInfo> FvProtocolData<P> {
#[inline(always)]
fn get_fv_address(&self, protocol: NonNull<pi::protocol::firmware_volume::FirmwareVolumeProtocol>) -> Option<u64> {
if let Some(Metadata { protocol: Protocol::Fv(_), physical_address }) = self.fv_metadata.get(&protocol.addr()) {
Some(*physical_address)
} else {
None
}
}
#[inline(always)]
fn get_fvb_address(
&self,
protocol: NonNull<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
) -> Option<u64> {
if let Some(Metadata { protocol: Protocol::Fvb(_), physical_address }) = self.fv_metadata.get(&protocol.addr())
{
Some(*physical_address)
} else {
None
}
}
}
impl<P: PlatformInfo> FvProtocolData<P> {
pub const fn new() -> Self {
Self { fv_metadata: BTreeMap::new(), _platform_info: core::marker::PhantomData }
}
pub const fn new_locked() -> tpl_mutex::TplMutex<Self> {
tpl_mutex::TplMutex::new(efi::TPL_NOTIFY, Self::new(), "FvData")
}
fn instance<'a>() -> tpl_mutex::TplGuard<'a, Self> {
Core::<P>::instance().pi_dispatcher.fv_data.lock()
}
fn fvb_get_attributes(
&self,
protocol: NonNull<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
) -> Result<fvb::attributes::EfiFvbAttributes2, EfiError> {
let physical_address = self.get_fvb_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address)? };
Ok(fv.attributes())
}
fn fvb_get_physical_address(
&self,
protocol: NonNull<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
) -> Result<efi::PhysicalAddress, EfiError> {
let physical_address = self.get_fvb_address(protocol).ok_or(EfiError::NotFound)?;
Ok(physical_address as efi::PhysicalAddress)
}
fn fvb_get_block_size(
&self,
protocol: NonNull<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
lba: efi::Lba,
) -> Result<(usize, usize), EfiError> {
let physical_address = self.get_fvb_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address)? };
let lba: u32 = lba.try_into().map_err(|_| EfiError::InvalidParameter)?;
let (block_size, remaining_blocks, _) = fv.lba_info(lba)?;
Ok((block_size as usize, remaining_blocks as usize))
}
fn fvb_read(
&self,
protocol: NonNull<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>,
lba: efi::Lba,
offset: usize,
num_bytes: usize,
) -> Result<&'static [u8], EfiError> {
let physical_address = self.get_fvb_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address) }?;
let Ok(lba) = lba.try_into() else {
return Err(EfiError::InvalidParameter);
};
let (lba_base_addr, block_size) = fv.lba_info(lba).map(|(addr, size, _)| (addr as usize, size as usize))?;
let mut bytes_to_read = num_bytes;
if offset.saturating_add(bytes_to_read) > block_size {
debug_assert!(offset.saturating_add(bytes_to_read) <= block_size); bytes_to_read = block_size.saturating_sub(offset);
}
let lba_start = (physical_address as usize)
.checked_add(lba_base_addr)
.and_then(|addr| addr.checked_add(offset))
.ok_or(EfiError::InvalidParameter)? as *mut u8;
unsafe { Ok(slice::from_raw_parts(lba_start, bytes_to_read)) }
}
fn fv_get_volume_attributes(
&self,
protocol: NonNull<pi::protocol::firmware_volume::FirmwareVolumeProtocol>,
) -> Result<fv::attributes::EfiFvAttributes, EfiError> {
let physical_address = self.get_fv_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address)? };
Ok(fv::attributes::EfiFvAttributes::from(fv.attributes()))
}
fn fv_read_file(
&self,
protocol: NonNull<pi::protocol::firmware_volume::FirmwareVolumeProtocol>,
name: efi::Guid,
) -> Result<FileRef<'_>, EfiError> {
let physical_address = self.get_fv_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address) }?;
if (fv.attributes() & fvb::attributes::raw::fvb2::READ_STATUS) == 0 {
return Err(EfiError::AccessDenied);
}
let file = match fv.files().find(|f| f.as_ref().is_ok_and(|f| f.name() == name) || f.is_err()) {
Some(Ok(file)) => file,
Some(Err(err)) => return Err(err.into()),
_ => return Err(EfiError::NotFound),
};
Ok(file)
}
fn fv_read_section<E: SectionExtractor>(
&self,
protocol: NonNull<pi::protocol::firmware_volume::FirmwareVolumeProtocol>,
name: efi::Guid,
section_type: ffs::section::EfiSectionType,
section_instance: usize,
section_extractor: &E,
) -> Result<patina_ffs::section::Section, EfiError> {
let file = self.fv_read_file(protocol, name)?;
let sections = file.sections_with_extractor(section_extractor)?;
sections
.iter()
.filter(|s| s.section_type_raw() == section_type)
.nth(section_instance)
.cloned()
.ok_or(EfiError::NotFound)
}
fn fv_get_next_file(
&self,
protocol: NonNull<pi::protocol::firmware_volume::FirmwareVolumeProtocol>,
file_type: fv::EfiFvFileType,
key: usize,
) -> Result<(efi::Guid, fv::file::EfiFvFileAttributes, usize, fv::EfiFvFileType), EfiError> {
let physical_address = self.get_fv_address(protocol).ok_or(EfiError::NotFound)?;
let fv = unsafe { VolumeRef::new_from_address(physical_address) }?;
let fv_attributes = fv.attributes();
if (fv_attributes & fvb::attributes::raw::fvb2::READ_STATUS) == 0 {
return Err(EfiError::AccessDenied);
}
let file_candidate = fv
.files()
.filter(|f| {
f.is_err()
|| file_type == ffs::file::raw::r#type::ALL
|| f.as_ref().is_ok_and(|f| f.file_type_raw() == file_type)
})
.nth(key);
let file = match file_candidate {
Some(Err(err)) => return Err(err.into()),
Some(Ok(file)) => file,
_ => return Err(EfiError::NotFound),
};
let attributes = if (fv_attributes & fvb::attributes::raw::fvb2::MEMORY_MAPPED)
== fvb::attributes::raw::fvb2::MEMORY_MAPPED
{
file.fv_attributes() | fv::file::raw::attribute::MEMORY_MAPPED
} else {
file.fv_attributes()
};
Ok((file.name().into_inner(), attributes, file.data().len(), file.file_type_raw()))
}
fn new_fvb_protocol(
parent_handle: Option<efi::Handle>,
) -> Box<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol> {
Box::new(pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol {
get_attributes: Self::fvb_get_attributes_efiapi,
set_attributes: Self::fvb_set_attributes_efiapi,
get_physical_address: Self::fvb_get_physical_address_efiapi,
get_block_size: Self::fvb_get_block_size_efiapi,
read: Self::fvb_read_efiapi,
write: Self::fvb_write_efiapi,
erase_blocks: Self::fvb_erase_blocks_efiapi,
parent_handle: parent_handle.unwrap_or_default(),
})
}
fn new_fv_protocol(
parent_handle: Option<efi::Handle>,
) -> Box<pi::protocol::firmware_volume::FirmwareVolumeProtocol> {
Box::from(pi::protocol::firmware_volume::FirmwareVolumeProtocol {
get_volume_attributes: Self::fv_get_volume_attributes_efiapi,
set_volume_attributes: Self::fv_set_volume_attributes_efiapi,
read_file: Self::fv_read_file_efiapi,
read_section: Self::fv_read_section_efiapi,
write_file: Self::fv_write_file_efiapi,
get_next_file: Self::fv_get_next_file_efiapi,
key_size: size_of::<usize>() as u32,
parent_handle: parent_handle.unwrap_or_default(),
get_info: Self::fv_get_info_efiapi,
set_info: Self::fv_set_info_efiapi,
})
}
fn install_fvb_protocol(
&mut self,
handle: Option<efi::Handle>,
parent_handle: Option<efi::Handle>,
base_address: u64,
) -> Result<efi::Handle, EfiError> {
let protocol = Self::new_fvb_protocol(parent_handle);
let protocol_ptr = NonNull::from(&*protocol).cast::<c_void>();
let metadata = Metadata::new_fvb(protocol, base_address);
self.fv_metadata.insert(protocol_ptr.addr(), metadata);
core_install_protocol_interface(
handle,
pi::protocol::firmware_volume_block::PROTOCOL_GUID.into_inner(),
protocol_ptr.as_ptr(),
)
}
fn install_fv_protocol(
&mut self,
handle: Option<efi::Handle>,
parent_handle: Option<efi::Handle>,
base_address: u64,
) -> Result<efi::Handle, EfiError> {
let protocol = Self::new_fv_protocol(parent_handle);
let protocol_ptr = NonNull::from(&*protocol).cast::<c_void>();
let metadata = Metadata::new_fv(protocol, base_address);
self.fv_metadata.insert(protocol_ptr.addr(), metadata);
core_install_protocol_interface(
handle,
pi::protocol::firmware_volume::PROTOCOL_GUID.into_inner(),
protocol_ptr.as_ptr(),
)
}
pub unsafe fn install_firmware_volume(
&mut self,
base_address: u64,
parent_handle: Option<efi::Handle>,
) -> Result<efi::Handle, EfiError> {
let handle = unsafe { self.install_fv_device_path_protocol(None, base_address)? };
self.install_fvb_protocol(Some(handle), parent_handle, base_address)?;
self.install_fv_protocol(Some(handle), parent_handle, base_address)?;
Ok(handle)
}
pub(super) fn install_firmware_volumes_from_hoblist(&mut self, hob_list: &hob::HobList) -> Result<(), efi::Status> {
let fv_hobs =
hob_list.iter().filter_map(|h| if let hob::Hob::FirmwareVolume(fv) = h { Some(*fv) } else { None });
for fv in fv_hobs {
let fv_slice = unsafe { slice::from_raw_parts(fv.base_address as *const u8, fv.length as usize) };
VolumeRef::new(fv_slice)?;
unsafe { self.install_firmware_volume(fv.base_address, None) }?;
}
Ok(())
}
unsafe fn install_fv_device_path_protocol(
&self,
handle: Option<efi::Handle>,
base_address: u64,
) -> Result<efi::Handle, EfiError> {
let fv = unsafe { VolumeRef::new_from_address(base_address) }?;
let device_path_ptr = if let Some(fv_name) = fv.fv_name() {
let device_path = FvPiWgDevicePath::new_fv(fv_name.into_inner());
Box::into_raw(Box::new(device_path)) as *mut c_void
} else {
let device_path =
FvMemMapDevicePath::new(MEMORY_MAPPED_IO, base_address, base_address.saturating_add(fv.size()));
Box::into_raw(Box::new(device_path)) as *mut c_void
};
core_install_protocol_interface(handle, efi::protocols::device_path::PROTOCOL_GUID, device_path_ptr)
}
}
#[cfg_attr(coverage, coverage(off))]
impl<P: PlatformInfo> FvProtocolData<P> {
extern "efiapi" fn fvb_get_attributes_efiapi(
this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
attributes: *mut fvb::attributes::EfiFvbAttributes2,
) -> efi::Status {
if attributes.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this) else {
return efi::Status::INVALID_PARAMETER;
};
match Self::instance().fvb_get_attributes(protocol) {
Err(err) => return err.into(),
Ok(fvb_attributes) => unsafe { attributes.write_unaligned(fvb_attributes) },
}
efi::Status::SUCCESS
}
extern "efiapi" fn fvb_set_attributes_efiapi(
_this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
_attributes: *mut fvb::attributes::EfiFvbAttributes2,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fvb_get_physical_address_efiapi(
this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
address: *mut efi::PhysicalAddress,
) -> efi::Status {
if address.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this) else {
return efi::Status::INVALID_PARAMETER;
};
match Self::instance().fvb_get_physical_address(protocol) {
Err(err) => return err.into(),
Ok(physical_address) => unsafe { address.write_unaligned(physical_address) },
}
efi::Status::SUCCESS
}
extern "efiapi" fn fvb_get_block_size_efiapi(
this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
lba: efi::Lba,
block_size: *mut usize,
number_of_blocks: *mut usize,
) -> efi::Status {
if block_size.is_null() || number_of_blocks.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this) else {
return efi::Status::INVALID_PARAMETER;
};
let (size, remaining_blocks) = match Self::instance().fvb_get_block_size(protocol, lba) {
Err(err) => return err.into(),
Ok((size, remaining_blocks)) => (size, remaining_blocks),
};
unsafe {
block_size.write_unaligned(size);
number_of_blocks.write_unaligned(remaining_blocks);
}
efi::Status::SUCCESS
}
extern "efiapi" fn fvb_read_efiapi(
this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
lba: efi::Lba,
offset: usize,
num_bytes: *mut usize,
buffer: *mut core::ffi::c_void,
) -> efi::Status {
if num_bytes.is_null() || buffer.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this) else {
return efi::Status::INVALID_PARAMETER;
};
let bytes_to_read = unsafe { num_bytes.read_unaligned() };
let data = match Self::instance().fvb_read(protocol, lba, offset, bytes_to_read) {
Err(err) => return err.into(),
Ok(data) => data,
};
if data.len() > bytes_to_read {
unsafe { num_bytes.write_unaligned(data.len()) };
return efi::Status::BUFFER_TOO_SMALL;
}
unsafe {
let dest_buffer = slice::from_raw_parts_mut(buffer as *mut u8, data.len());
dest_buffer.copy_from_slice(data);
num_bytes.write_unaligned(data.len());
}
if data.len() == bytes_to_read { efi::Status::SUCCESS } else { efi::Status::BAD_BUFFER_SIZE }
}
extern "efiapi" fn fvb_write_efiapi(
_this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
_lba: efi::Lba,
_offset: usize,
_num_bytes: *mut usize,
_buffer: *mut core::ffi::c_void,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fvb_erase_blocks_efiapi(
_this: *mut pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fv_get_volume_attributes_efiapi(
this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
fv_attributes: *mut fv::attributes::EfiFvAttributes,
) -> efi::Status {
if fv_attributes.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this.cast_mut()) else {
return efi::Status::INVALID_PARAMETER;
};
let fv_attributes_data = match Self::instance().fv_get_volume_attributes(protocol) {
Err(err) => return err.into(),
Ok(attrs) => attrs,
};
unsafe { fv_attributes.write_unaligned(fv_attributes_data) };
efi::Status::SUCCESS
}
extern "efiapi" fn fv_set_volume_attributes_efiapi(
_this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
_fv_attributes: *mut fv::attributes::EfiFvAttributes,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fv_read_file_efiapi(
this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
name_guid: *const efi::Guid,
buffer: *mut *mut c_void,
buffer_size: *mut usize,
found_type: *mut fv::EfiFvFileType,
file_attributes: *mut fv::file::EfiFvFileAttributes,
authentication_status: *mut u32,
) -> efi::Status {
if name_guid.is_null()
|| buffer_size.is_null()
|| found_type.is_null()
|| file_attributes.is_null()
|| authentication_status.is_null()
{
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this.cast_mut()) else {
return efi::Status::INVALID_PARAMETER;
};
let local_buffer_size = unsafe { buffer_size.read_unaligned() };
let name = unsafe { name_guid.read_unaligned() };
let this = Self::instance();
let file = match this.fv_read_file(protocol, name) {
Err(err) => return err.into(),
Ok(file) => file,
};
unsafe {
found_type.write_unaligned(file.file_type_raw());
file_attributes.write_unaligned(file.fv_attributes());
buffer_size.write_unaligned(file.content().len());
}
if buffer.is_null() {
unsafe {
buffer_size.write_unaligned(file.content().len());
}
return efi::Status::SUCCESS;
}
let mut local_buffer_ptr = unsafe { buffer.read_unaligned() };
let (copy_size, status) = if local_buffer_ptr.is_null() {
match core_allocate_pool(efi::BOOT_SERVICES_DATA, file.content().len()) {
Err(err) => return err.into(),
Ok(allocation) => unsafe {
local_buffer_ptr = allocation;
buffer.write_unaligned(local_buffer_ptr);
},
}
(file.content().len(), efi::Status::SUCCESS)
} else if file.content().len() > local_buffer_size {
(local_buffer_size, efi::Status::WARN_BUFFER_TOO_SMALL)
} else {
(file.content().len(), efi::Status::SUCCESS)
};
unsafe {
buffer_size.write_unaligned(copy_size);
}
let out_buffer = unsafe { slice::from_raw_parts_mut(local_buffer_ptr as *mut u8, copy_size) };
out_buffer.copy_from_slice(file.content().get(..copy_size).expect("copy_size <= file.content().len()"));
status
}
extern "efiapi" fn fv_read_section_efiapi(
this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
name_guid: *const efi::Guid,
section_type: ffs::section::EfiSectionType,
section_instance: usize,
buffer: *mut *mut c_void,
buffer_size: *mut usize,
authentication_status: *mut u32,
) -> efi::Status {
if name_guid.is_null() || buffer.is_null() || buffer_size.is_null() || authentication_status.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this.cast_mut()) else {
return efi::Status::INVALID_PARAMETER;
};
let name = unsafe { name_guid.read_unaligned() };
let extractor = &Core::<P>::instance().pi_dispatcher.section_extractor;
let section = match Self::instance().fv_read_section(protocol, name, section_type, section_instance, extractor)
{
Err(err) => return err.into(),
Ok(section) => section,
};
let section_content = match section.try_content_as_slice() {
Ok(data) => data,
Err(err) => return err.into(),
};
let type_specific_header = section.header().type_specific_header_bytes();
let owned_section_data: Vec<u8>;
let section_data = if type_specific_header.is_empty() {
section_content
} else {
owned_section_data = type_specific_header.iter().chain(section_content.iter()).copied().collect();
owned_section_data.as_slice()
};
let mut local_buffer_size = unsafe { buffer_size.read_unaligned() };
let mut local_buffer_ptr = unsafe { buffer.read_unaligned() };
if local_buffer_ptr.is_null() {
match core_allocate_pool(efi::BOOT_SERVICES_DATA, section_data.len()) {
Err(err) => return err.into(),
Ok(allocation) => unsafe {
local_buffer_size = section_data.len();
local_buffer_ptr = allocation;
buffer_size.write_unaligned(local_buffer_size);
buffer.write_unaligned(local_buffer_ptr);
},
}
} else {
unsafe {
buffer_size.write_unaligned(section_data.len());
}
}
let copy_size = core::cmp::min(section_data.len(), local_buffer_size);
let dest_buffer = unsafe { slice::from_raw_parts_mut(local_buffer_ptr as *mut u8, copy_size) };
dest_buffer.copy_from_slice(
section_data.get(..dest_buffer.len()).expect("copy_size = min(section_data.len(), local_buffer_size)"),
);
if dest_buffer.len() < section_data.len() { efi::Status::WARN_BUFFER_TOO_SMALL } else { efi::Status::SUCCESS }
}
extern "efiapi" fn fv_write_file_efiapi(
_this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
_number_of_files: u32,
_write_policy: pi::protocol::firmware_volume::EfiFvWritePolicy,
_file_data: *mut pi::protocol::firmware_volume::EfiFvWriteFileData,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fv_get_next_file_efiapi(
this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
key: *mut c_void,
file_type: *mut fv::EfiFvFileType,
name_guid: *mut efi::Guid,
attributes: *mut fv::file::EfiFvFileAttributes,
size: *mut usize,
) -> efi::Status {
if key.is_null() || file_type.is_null() || name_guid.is_null() || attributes.is_null() || size.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let Some(protocol) = NonNull::new(this.cast_mut()) else {
return efi::Status::INVALID_PARAMETER;
};
let local_key = unsafe { (key as *mut usize).read_unaligned() };
let local_file_type = unsafe { file_type.read_unaligned() };
if local_file_type >= ffs::file::raw::r#type::FFS_MIN {
return efi::Status::NOT_FOUND;
}
let (file_name, fv_attributes, file_size, found_file_type) =
match Self::instance().fv_get_next_file(protocol, local_file_type, local_key) {
Err(err) => return err.into(),
Ok((name, attrs, size, file_type)) => (name, attrs, size, file_type),
};
unsafe {
(key as *mut usize).write_unaligned(local_key.saturating_add(1));
name_guid.write_unaligned(file_name);
if (fv_attributes & fvb::attributes::raw::fvb2::MEMORY_MAPPED) == fvb::attributes::raw::fvb2::MEMORY_MAPPED
{
attributes.write_unaligned(fv_attributes | fv::file::raw::attribute::MEMORY_MAPPED);
} else {
attributes.write_unaligned(fv_attributes);
}
size.write_unaligned(file_size);
file_type.write_unaligned(found_file_type);
}
efi::Status::SUCCESS
}
extern "efiapi" fn fv_get_info_efiapi(
_this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
_information_type: *const efi::Guid,
_buffer_size: *mut usize,
_buffer: *mut c_void,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
extern "efiapi" fn fv_set_info_efiapi(
_this: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol,
_information_type: *const efi::Guid,
_buffer_size: usize,
_buffer: *const c_void,
) -> efi::Status {
efi::Status::UNSUPPORTED
}
}
pub fn device_path_bytes_for_fv_file(fv_handle: efi::Handle, file_name: efi::Guid) -> Result<Box<[u8]>, efi::Status> {
let fv_device_path = PROTOCOL_DB.get_interface_for_handle(fv_handle, efi::protocols::device_path::PROTOCOL_GUID)?;
let file_node = &FvPiWgDevicePath::new_file(file_name);
concat_device_path_to_boxed_slice(
fv_device_path as *mut _ as *const efi::protocols::device_path::Protocol,
core::ptr::from_ref(file_node) as *const efi::protocols::device_path::Protocol,
)
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use crate::{MockComponentInfo, MockCpuInfo, MockMemoryInfo, test_support};
use patina::pi::{
BootMode,
fw_fs::{ffs::section::header::FreeformSubtypeGuid, fv::BlockMapEntry, fvb},
hob::{self, Hob, HobList},
};
use patina_ffs::{
file::File as FfsFile,
section::{Section, SectionHeader},
volume::Volume,
};
use patina_ffs_extractors::CompositeSectionExtractor;
extern crate alloc;
use crate::test_collateral;
use std::{
alloc::{Layout, alloc, dealloc},
ffi::c_void,
fs::File,
io::Read,
ptr,
};
const BUFFER_SIZE_EMPTY: usize = 0;
const LBA: u64 = 0;
const SECTION_TYPE: ffs::section::EfiSectionType = 0;
const SECTION_INSTANCE: usize = 0;
struct MockPlatformInfo;
impl PlatformInfo for MockPlatformInfo {
type MemoryInfo = MockMemoryInfo;
type CpuInfo = MockCpuInfo;
type ComponentInfo = MockComponentInfo;
type Extractor = CompositeSectionExtractor;
}
type MockCore = Core<MockPlatformInfo>;
type MockProtocolData = FvProtocolData<MockPlatformInfo>;
#[test]
fn test_fv_init_core() {
test_support::with_global_lock(|| {
fn gen_firmware_volume2() -> hob::FirmwareVolume2 {
let header =
hob::HobHeader { r#type: hob::FV, length: size_of::<hob::FirmwareVolume2>() as u16, reserved: 0 };
hob::FirmwareVolume2 {
header,
base_address: 0,
length: 0x8000,
fv_name: patina::BinaryGuid::from_fields(1, 2, 3, 4, 5, &[6, 7, 8, 9, 10, 11]),
file_name: patina::BinaryGuid::from_fields(1, 2, 3, 4, 5, &[6, 7, 8, 9, 10, 11]),
}
}
fn gen_firmware_volume() -> hob::FirmwareVolume {
let mut file = File::open(test_collateral!("DXEFV.Fv")).unwrap();
let mut fv: Vec<u8> = Vec::new();
file.read_to_end(&mut fv).expect("failed to read test file");
let len: u64 = fv.len() as u64;
let base: u64 = fv.as_ptr() as u64;
let header =
hob::HobHeader { r#type: hob::FV, length: size_of::<hob::FirmwareVolume>() as u16, reserved: 0 };
hob::FirmwareVolume { header, base_address: base, length: len }
}
fn gen_end_of_hoblist() -> hob::PhaseHandoffInformationTable {
let header = hob::HobHeader {
r#type: hob::END_OF_HOB_LIST,
length: size_of::<hob::PhaseHandoffInformationTable>() as u16,
reserved: 0,
};
hob::PhaseHandoffInformationTable {
header,
version: 0x00010000,
boot_mode: BootMode::BootWithFullConfiguration,
memory_top: 0xdeadbeef,
memory_bottom: 0xdeadc0de,
free_memory_top: 104,
free_memory_bottom: 255,
end_of_hob_list: 0xdeaddeadc0dec0de,
}
}
let firmware_volume2 = gen_firmware_volume2();
let _firmware_volume0 = gen_firmware_volume();
let end_of_hob_list = gen_end_of_hoblist();
let mut hoblist = HobList::new();
hoblist.push(Hob::FirmwareVolume2(&firmware_volume2));
hoblist.push(Hob::Handoff(&end_of_hob_list));
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
CORE.pi_dispatcher.install_firmware_volumes_from_hoblist(&hoblist).unwrap();
})
.expect("Unexpected Error Initalising hob fvs ");
}
#[test]
fn test_fv_functionality() {
test_support::with_global_lock(|| {
let mut fv_att: u64 = 0x1;
let fv_attributes: *mut fv::attributes::EfiFvAttributes = &raw mut fv_att;
let guid_invalid: efi::Guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0, 0, 0, 0, 0, 0]);
let guid_ref_invalid_ref: *const efi::Guid = &raw const guid_invalid;
let mut auth_valid_status: u32 = 1;
let auth_valid_p: *mut u32 = &raw mut auth_valid_status;
let mut guid_valid: efi::Guid =
efi::Guid::from_fields(0x1fa1f39e, 0xfeff, 0x4aae, 0xbd, 0x7b, &[0x38, 0xa0, 0x70, 0xa3, 0xb6, 0x09]);
let guid_valid_ref: *mut efi::Guid = &raw mut guid_valid;
let mut file_rd_attr: u32 = fvb::attributes::raw::fvb2::READ_STATUS;
let file_attributes: *mut fv::file::EfiFvFileAttributes = &raw mut file_rd_attr;
let mut file = File::open(test_collateral!("DXEFV.Fv")).unwrap();
let mut fv: Vec<u8> = Vec::new();
file.read_to_end(&mut fv).expect("failed to read test file");
let fv = fv.leak();
let base_address: u64 = fv.as_ptr() as u64;
let parent_handle: Option<efi::Handle> = None;
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
let _handle =
unsafe { CORE.pi_dispatcher.fv_data.lock().install_fv_device_path_protocol(None, base_address) };
CORE.pi_dispatcher.fv_data.lock().fv_metadata.clear();
assert!(CORE.pi_dispatcher.fv_data.lock().fv_metadata.is_empty());
let fv_interface = MockProtocolData::new_fv_protocol(parent_handle);
let fv_ptr = NonNull::from(&*fv_interface).cast::<c_void>();
let metadata = Metadata::new_fv(fv_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr.addr(), metadata);
let fv_ptr1 = fv_ptr.cast::<pi::protocol::firmware_volume::FirmwareVolumeProtocol>().as_ptr();
let fvb_interface = MockProtocolData::new_fvb_protocol(parent_handle);
let fvb_ptr = NonNull::from(&*fvb_interface).cast::<c_void>();
let fvb_ptr_mut_prot =
fvb_ptr.cast::<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>().as_ptr();
let metadata = Metadata::new_fvb(fvb_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fvb_ptr.addr(), metadata);
let fv_interface3 = MockProtocolData::new_fv_protocol(parent_handle);
let fv_ptr3 = NonNull::from(&*fv_interface3).cast::<c_void>();
let fv_ptr3_const = fv_ptr3.cast::<pi::protocol::firmware_volume::FirmwareVolumeProtocol>().as_ptr();
let bad_fv_buf = vec![0u8; size_of::<fv::Header>()].leak();
let base_no2: u64 = bad_fv_buf.as_ptr() as u64;
let metadata2 = Metadata::new_fv(fv_interface3, base_no2);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr3.addr(), metadata2);
let fv_interface_no_data = MockProtocolData::new_fv_protocol(None);
let fv_ptr_no_data = std::ptr::from_ref::<pi::protocol::firmware_volume::FirmwareVolumeProtocol>(
fv_interface_no_data.as_ref(),
);
let fvb_intf_invalid = MockProtocolData::new_fvb_protocol(parent_handle);
let fvb_intf_invalid_void = NonNull::from(&*fvb_intf_invalid).cast::<c_void>();
let fvb_intf_invalid_mutpro = fvb_intf_invalid_void
.cast::<pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol>()
.as_ptr();
let base_no: u64 = fv.as_ptr() as u64 + 0x1000;
let private_data4 = Metadata::new_fvb(fvb_intf_invalid, base_no);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fvb_intf_invalid_void.addr(), private_data4);
let mut fvb_intf_data_n = Box::from(pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol {
get_attributes: MockProtocolData::fvb_get_attributes_efiapi,
set_attributes: MockProtocolData::fvb_set_attributes_efiapi,
get_physical_address: MockProtocolData::fvb_get_physical_address_efiapi,
get_block_size: MockProtocolData::fvb_get_block_size_efiapi,
read: MockProtocolData::fvb_read_efiapi,
write: MockProtocolData::fvb_write_efiapi,
erase_blocks: MockProtocolData::fvb_erase_blocks_efiapi,
parent_handle: match parent_handle {
Some(handle) => handle,
None => core::ptr::null_mut(),
},
});
let fvb_intf_data_n_mut = std::ptr::from_mut::<
pi::protocol::firmware_volume_block::FirmwareVolumeBlockProtocol,
>(fvb_intf_data_n.as_mut());
unsafe {
let fv_test_set_info = || {
MockProtocolData::fv_set_info_efiapi(ptr::null(), ptr::null(), BUFFER_SIZE_EMPTY, ptr::null());
};
let fv_test_get_info = || {
MockProtocolData::fv_get_info_efiapi(ptr::null(), ptr::null(), ptr::null_mut(), ptr::null_mut());
};
let fv_test_set_volume_attributes = || {
MockProtocolData::fv_set_volume_attributes_efiapi(ptr::null(), fv_attributes);
};
let fv_test_get_volume_attributes = || {
MockProtocolData::fv_get_volume_attributes_efiapi(fv_ptr1, std::ptr::null_mut());
MockProtocolData::fv_get_volume_attributes_efiapi(fv_ptr_no_data, fv_attributes);
MockProtocolData::fv_get_volume_attributes_efiapi(fv_ptr3_const, fv_attributes);
MockProtocolData::fv_get_volume_attributes_efiapi(fv_ptr1, fv_attributes);
};
let fv_test_fvb_read = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let buffer_valid3 = alloc(layout3) as *mut c_void;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
MockProtocolData::fvb_read_efiapi(
fvb_ptr_mut_prot,
LBA,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
MockProtocolData::fvb_read_efiapi(fvb_ptr_mut_prot, LBA, 0, buffer_valid_size3, buffer_valid3);
MockProtocolData::fvb_read_efiapi(
fvb_ptr_mut_prot,
0xfffffffff,
0,
buffer_valid_size3,
buffer_valid3,
);
MockProtocolData::fvb_read_efiapi(
fvb_intf_invalid_mutpro,
LBA,
0,
buffer_valid_size3,
buffer_valid3,
);
MockProtocolData::fvb_read_efiapi(fvb_ptr_mut_prot, u64::MAX, 0, buffer_valid_size3, buffer_valid3);
MockProtocolData::fvb_read_efiapi(
fvb_ptr_mut_prot,
0x22299222,
0x999999,
buffer_valid_size3,
buffer_valid3,
);
MockProtocolData::fvb_read_efiapi(fvb_intf_data_n_mut, LBA, 0, buffer_valid_size3, buffer_valid3);
dealloc(buffer_valid3 as *mut u8, layout3);
};
let fv_test_get_block_size = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let buffer_valid3 = alloc(layout3) as *mut c_void;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
let mut buffer_size_random: usize = 99;
let buffer_size_random_ref: *mut usize = &raw mut buffer_size_random;
let mut num_buffer_empty: usize = 0;
let num_buffer_empty_ref: *mut usize = &raw mut num_buffer_empty;
MockProtocolData::fvb_get_block_size_efiapi(
fvb_ptr_mut_prot,
LBA,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
MockProtocolData::fvb_get_block_size_efiapi(
fvb_ptr_mut_prot,
LBA,
buffer_valid_size3,
buffer_valid_size3,
);
MockProtocolData::fvb_get_block_size_efiapi(
fvb_intf_invalid_mutpro,
LBA,
buffer_valid_size3,
buffer_valid_size3,
);
MockProtocolData::fvb_get_block_size_efiapi(
fvb_intf_data_n_mut,
LBA,
buffer_valid_size3,
buffer_valid_size3,
);
MockProtocolData::fvb_get_block_size_efiapi(
fvb_ptr_mut_prot,
u64::MAX,
buffer_valid_size3,
buffer_valid_size3,
);
MockProtocolData::fvb_get_block_size_efiapi(
fvb_ptr_mut_prot,
222222,
buffer_size_random_ref,
num_buffer_empty_ref,
);
dealloc(buffer_valid3 as *mut u8, layout3);
};
let fvb_test_erase_block = || {
MockProtocolData::fvb_erase_blocks_efiapi(fvb_ptr_mut_prot);
};
let fvb_test_get_physical_address = || {
let mut p_address: efi::PhysicalAddress = 0x12345;
MockProtocolData::fvb_get_physical_address_efiapi(fvb_intf_data_n_mut, &raw mut p_address);
MockProtocolData::fvb_get_physical_address_efiapi(fvb_intf_invalid_mutpro, &raw mut p_address);
MockProtocolData::fvb_get_physical_address_efiapi(fvb_ptr_mut_prot, &raw mut p_address);
MockProtocolData::fvb_get_physical_address_efiapi(fvb_ptr_mut_prot, std::ptr::null_mut());
};
let fvb_test_write_file = || {
let number_of_files: u32 = 0;
let write_policy: pi::protocol::firmware_volume::EfiFvWritePolicy = 0;
MockProtocolData::fv_write_file_efiapi(
fv_ptr1,
number_of_files,
write_policy,
std::ptr::null_mut(),
);
};
let fvb_test_set_attributes = || {
MockProtocolData::fvb_set_attributes_efiapi(fvb_ptr_mut_prot, std::ptr::null_mut());
};
let fvb_test_write = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let buffer_valid3 = alloc(layout3) as *mut c_void;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
MockProtocolData::fvb_write_efiapi(
fvb_ptr_mut_prot,
LBA,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
MockProtocolData::fvb_write_efiapi(fvb_ptr_mut_prot, LBA, 0, buffer_valid_size3, buffer_valid3);
MockProtocolData::fvb_write_efiapi(
fvb_intf_invalid_mutpro,
LBA,
0,
buffer_valid_size3,
buffer_valid3,
);
MockProtocolData::fvb_write_efiapi(fvb_intf_data_n_mut, LBA, 0, buffer_valid_size3, buffer_valid3);
dealloc(buffer_valid3 as *mut u8, layout3);
};
let fvb_test_get_attributes = || {
let mut fvb_attributes: fvb::attributes::EfiFvbAttributes2 = 0x123456;
let fvb_attributes_ref: *mut fvb::attributes::EfiFvbAttributes2 = &raw mut fvb_attributes;
MockProtocolData::fvb_get_attributes_efiapi(fvb_ptr_mut_prot, std::ptr::null_mut());
MockProtocolData::fvb_get_attributes_efiapi(fvb_ptr_mut_prot, fvb_attributes_ref);
MockProtocolData::fvb_get_attributes_efiapi(fvb_intf_invalid_mutpro, fvb_attributes_ref);
MockProtocolData::fvb_get_attributes_efiapi(fvb_intf_data_n_mut, fvb_attributes_ref);
};
let fvb_test_get_next_file = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let buffer_valid3 = alloc(layout3) as *mut c_void;
let mut file_type_read: fv::EfiFvFileType = 1;
let file_type_read_ref: *mut fv::EfiFvFileType = &raw mut file_type_read;
let mut n_guid_mut: efi::Guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &[0, 0, 0, 0, 0, 0]);
let n_guid_ref_mut: *mut efi::Guid = &raw mut n_guid_mut;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
MockProtocolData::fv_get_next_file_efiapi(
ptr::null(),
std::ptr::null_mut(),
file_type_read_ref,
std::ptr::null_mut(),
file_attributes,
buffer_valid_size3,
);
MockProtocolData::fv_get_next_file_efiapi(
ptr::null(),
buffer_valid3,
file_type_read_ref,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
MockProtocolData::fv_get_next_file_efiapi(
fv_ptr1,
buffer_valid3,
file_type_read_ref,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
MockProtocolData::fv_get_next_file_efiapi(
fv_ptr3_const,
buffer_valid3,
file_type_read_ref,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
MockProtocolData::fv_get_next_file_efiapi(
fv_ptr_no_data,
buffer_valid3,
file_type_read_ref,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
let mut file_type_read: fv::EfiFvFileType = ffs::file::raw::r#type::FFS_MIN;
let file_type_read_ref1: *mut fv::EfiFvFileType = &raw mut file_type_read;
MockProtocolData::fv_get_next_file_efiapi(
fv_ptr1,
buffer_valid3,
file_type_read_ref1,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
MockProtocolData::fv_get_next_file_efiapi(
fv_ptr1,
std::ptr::null_mut(),
file_type_read_ref,
n_guid_ref_mut,
file_attributes,
buffer_valid_size3,
);
dealloc(buffer_valid3 as *mut u8, layout3);
};
let fvb_test_read_section = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let mut buffer_valid3 = alloc(layout3) as *mut c_void;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
let mut gd2: efi::Guid = efi::Guid::from_fields(
0x434f695c,
0xef26,
0x4a12,
0x9e,
0xba,
&[0xdd, 0xef, 0x00, 0x97, 0x49, 0x7c],
);
let name_guid2: *mut efi::Guid = &raw mut gd2;
MockProtocolData::fv_read_section_efiapi(
ptr::null(),
ptr::null(),
SECTION_TYPE,
SECTION_INSTANCE,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
guid_ref_invalid_ref,
6,
10,
&raw mut buffer_valid3,
buffer_valid_size3,
auth_valid_p,
);
MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
guid_valid_ref,
6,
10,
&raw mut buffer_valid3,
buffer_valid_size3,
auth_valid_p,
);
MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
name_guid2,
6,
10,
&raw mut buffer_valid3,
buffer_valid_size3,
auth_valid_p,
);
MockProtocolData::fv_read_section_efiapi(
fv_ptr3_const,
guid_ref_invalid_ref,
1,
1,
&raw mut buffer_valid3,
buffer_valid_size3,
auth_valid_p,
);
MockProtocolData::fv_read_section_efiapi(
fv_ptr_no_data,
guid_ref_invalid_ref,
1,
1,
&raw mut buffer_valid3,
buffer_valid_size3,
auth_valid_p,
);
dealloc(buffer_valid3 as *mut u8, layout3);
};
let fvb_test_read_file = || {
let mut len3 = 1000;
let buffer_valid_size3: *mut usize = &raw mut len3;
let layout3 = Layout::from_size_align(1001, 8).unwrap();
let mut buffer_valid3 = alloc(layout3) as *mut c_void;
let mut found_type: u8 = ffs::file::raw::r#type::DRIVER;
let found_type_ref: *mut fv::EfiFvFileType = &raw mut found_type;
assert!(!buffer_valid3.is_null(), "Memory allocation failed!");
MockProtocolData::fv_read_file_efiapi(
ptr::null(),
ptr::null(),
&raw mut buffer_valid3,
std::ptr::null_mut(),
found_type_ref,
file_attributes,
std::ptr::null_mut(),
);
MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
guid_ref_invalid_ref,
&raw mut buffer_valid3,
buffer_valid_size3,
found_type_ref,
file_attributes,
auth_valid_p,
);
MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
guid_valid_ref,
&raw mut buffer_valid3,
buffer_valid_size3,
found_type_ref,
file_attributes,
auth_valid_p,
);
MockProtocolData::fv_read_file_efiapi(
fv_ptr3_const,
guid_valid_ref,
&raw mut buffer_valid3,
buffer_valid_size3,
found_type_ref,
file_attributes,
auth_valid_p,
);
MockProtocolData::fv_read_file_efiapi(
fv_ptr_no_data,
guid_valid_ref,
&raw mut buffer_valid3,
buffer_valid_size3,
found_type_ref,
file_attributes,
auth_valid_p,
);
MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
guid_valid_ref,
std::ptr::null_mut(),
buffer_valid_size3,
found_type_ref,
file_attributes,
auth_valid_p,
);
let mut buffer_size_zero = 0usize;
let buffer_size_zero_ptr: *mut usize = &raw mut buffer_size_zero;
let status = MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
guid_valid_ref,
&raw mut buffer_valid3,
buffer_size_zero_ptr,
found_type_ref,
file_attributes,
auth_valid_p,
);
assert_eq!(status, efi::Status::WARN_BUFFER_TOO_SMALL);
dealloc(buffer_valid3 as *mut u8, layout3);
};
fv_test_set_info();
fv_test_get_info();
fv_test_set_volume_attributes();
fv_test_get_volume_attributes();
fv_test_fvb_read();
fv_test_get_block_size();
fvb_test_erase_block();
fvb_test_get_physical_address();
fvb_test_set_attributes();
fvb_test_get_attributes();
fvb_test_write();
fvb_test_read_section();
fvb_test_get_next_file();
fvb_test_read_file();
fvb_test_write_file();
}
})
.unwrap();
}
#[test]
fn test_fv_special_section_read() {
test_support::with_global_lock(|| {
let mut file = File::open(test_collateral!("DXEFV.Fv")).unwrap();
let mut fv: Vec<u8> = Vec::new();
file.read_to_end(&mut fv).expect("failed to read test file");
let base_address: u64 = fv.as_ptr() as u64;
let parent_handle: Option<efi::Handle> = None;
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
let fv_interface = MockProtocolData::new_fv_protocol(parent_handle);
let fv_ptr = NonNull::from(&*fv_interface);
let private_data = Metadata::new_fv(fv_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr.addr(), private_data);
let fv_ptr1: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol = fv_ptr.as_ptr();
unsafe {
let layout = Layout::from_size_align(1000, 8).unwrap();
let mut buffer = alloc(layout) as *mut c_void;
assert!(!buffer.is_null(), "Memory allocation failed!");
let mut len = 1000;
let buffer_size: *mut usize = &raw mut len;
let mut authentication_status: u32 = 1;
let authentication_statusp: *mut u32 = &raw mut authentication_status;
let mut guid1: efi::Guid = efi::Guid::from_fields(
0x1fa1f39e,
0xfeff,
0x4aae,
0xbd,
0x7b,
&[0x38, 0xa0, 0x70, 0xa3, 0xb6, 0x09],
);
let name_guid3: *mut efi::Guid = &raw mut guid1;
MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
name_guid3,
6,
10,
&raw mut buffer,
buffer_size,
authentication_statusp,
);
dealloc(buffer as *mut u8, layout);
}
})
.expect("Failed to read Firmware Volume Section");
}
#[test]
fn test_fv_read_file_truncated_copy() {
test_support::with_global_lock(|| {
let mut file = File::open(test_collateral!("DXEFV.Fv")).unwrap();
let mut fv: Vec<u8> = Vec::new();
file.read_to_end(&mut fv).expect("failed to read test file");
let fv = fv.leak();
let base_address: u64 = fv.as_ptr() as u64;
let parent_handle: Option<efi::Handle> = None;
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
let fv_interface = MockProtocolData::new_fv_protocol(parent_handle);
let fv_ptr = NonNull::from(&*fv_interface);
let metadata = Metadata::new_fv(fv_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr.addr(), metadata);
let fv_ptr1: *const pi::protocol::firmware_volume::FirmwareVolumeProtocol = fv_ptr.as_ptr();
unsafe {
let mut guid: efi::Guid = efi::Guid::from_fields(
0x1fa1f39e,
0xfeff,
0x4aae,
0xbd,
0x7b,
&[0x38, 0xa0, 0x70, 0xa3, 0xb6, 0x09],
);
let name_guid: *mut efi::Guid = &raw mut guid;
let mut actual_file_size: usize = 0;
let mut found_type: u8 = 0;
let mut file_attributes: u32 = 0;
let mut auth_status: u32 = 0;
let status = MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
name_guid,
std::ptr::null_mut(),
&raw mut actual_file_size,
&raw mut found_type,
&raw mut file_attributes,
&raw mut auth_status,
);
assert_eq!(status, efi::Status::SUCCESS);
assert!(actual_file_size > 0, "File size should be greater than 0");
let truncated_size = actual_file_size / 2; let layout = Layout::from_size_align(truncated_size, 8).unwrap();
let mut buffer = alloc(layout) as *mut c_void;
assert!(!buffer.is_null(), "Memory allocation failed!");
let buffer_slice = slice::from_raw_parts_mut(buffer as *mut u8, truncated_size);
buffer_slice.fill(0xFE);
let mut buffer_size = truncated_size;
let status = MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
name_guid,
&raw mut buffer,
&raw mut buffer_size,
&raw mut found_type,
&raw mut file_attributes,
&raw mut auth_status,
);
assert_eq!(
status,
efi::Status::WARN_BUFFER_TOO_SMALL,
"Expected WARN_BUFFER_TOO_SMALL when buffer is too small"
);
assert_eq!(
buffer_size, truncated_size,
"buffer_size should be updated to truncated size (what was actually copied)"
);
let copied_data = slice::from_raw_parts(buffer as *const u8, truncated_size);
let all_ff = copied_data.iter().all(|&b| b == 0xFE);
assert!(!all_ff, "Data should have been copied to buffer (not all 0xFE)");
dealloc(buffer as *mut u8, layout);
let zero_size = 0;
let layout_zero = Layout::from_size_align(64, 8).unwrap();
let mut buffer_zero = alloc(layout_zero) as *mut c_void;
assert!(!buffer_zero.is_null(), "Memory allocation failed!");
let mut buffer_size_zero = zero_size;
let status_zero = MockProtocolData::fv_read_file_efiapi(
fv_ptr1,
name_guid,
&raw mut buffer_zero,
&raw mut buffer_size_zero,
&raw mut found_type,
&raw mut file_attributes,
&raw mut auth_status,
);
assert_eq!(
status_zero,
efi::Status::WARN_BUFFER_TOO_SMALL,
"Expected WARN_BUFFER_TOO_SMALL with a 0-byte buffer"
);
assert_eq!(buffer_size_zero, 0, "buffer_size should remain 0 when input is 0");
dealloc(buffer_zero as *mut u8, layout_zero);
}
})
.unwrap();
}
#[test]
fn test_fv_read_section_limits_read() {
let mut file = File::open(test_collateral!("DXEFV.Fv")).unwrap();
let mut fv: Vec<u8> = Vec::new();
file.read_to_end(&mut fv).expect("failed to read test file");
let fv = fv.leak();
let base_address: u64 = fv.as_ptr() as u64;
let parent_handle: Option<efi::Handle> = None;
test_support::with_global_lock(|| {
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
unsafe { test_support::init_test_gcd(None) };
let fv_interface = MockProtocolData::new_fv_protocol(parent_handle);
let fv_ptr = NonNull::from(&*fv_interface);
let metadata = Metadata::new_fv(fv_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr.addr(), metadata);
let fv_ptr1 = fv_ptr.as_ptr();
unsafe {
let mut guid: efi::Guid = efi::Guid::from_fields(
0x79E4A61C,
0xED73,
0x4312,
0x94,
0xFE,
&[0xE3, 0xE7, 0x56, 0x33, 0x62, 0xA9],
);
let name_guid: *mut efi::Guid = &raw mut guid;
let mut actual_section_size: usize = 0;
let mut auth_status: u32 = 0;
let status = MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
name_guid,
19,
0,
std::ptr::from_mut::<*mut c_void>(&mut std::ptr::null_mut()),
&raw mut actual_section_size,
&raw mut auth_status,
);
assert_eq!(status, efi::Status::SUCCESS);
assert!(actual_section_size > 0, "Section size should be greater than 0");
let larger_size = actual_section_size + 512;
let layout = Layout::from_size_align(larger_size, 8).unwrap();
let mut buffer = alloc(layout) as *mut c_void;
assert!(!buffer.is_null(), "Memory allocation failed!");
let buffer_slice = slice::from_raw_parts_mut(buffer as *mut u8, larger_size);
buffer_slice.fill(0xFE);
let mut buffer_size = larger_size;
let status = MockProtocolData::fv_read_section_efiapi(
fv_ptr1,
name_guid,
19,
0,
&raw mut buffer,
&raw mut buffer_size,
&raw mut auth_status,
);
assert_eq!(status, efi::Status::SUCCESS, "Expected SUCCESS when buffer is larger than file size");
assert_eq!(buffer_size, actual_section_size, "buffer_size should be updated to actual section size");
let copied_data = slice::from_raw_parts(buffer as *const u8, actual_section_size);
let all_ff = copied_data.iter().all(|&b| b == 0xFE);
assert!(!all_ff, "Section data should have been copied to buffer (not all 0xFE)");
let remaining_data = slice::from_raw_parts(
buffer.add(actual_section_size) as *const u8,
larger_size - actual_section_size,
);
let all_ff_remaining = remaining_data.iter().all(|&b| b == 0xFE);
assert!(all_ff_remaining, "Remaining buffer beyond section size should remain unchanged (all 0xFE)");
}
})
.unwrap();
}
#[test]
fn test_fv_read_section_freeform_subtype_returns_guid_plus_payload() {
fn enable_read_status(fv_bytes: &mut [u8]) {
let mut header = unsafe { ptr::read_unaligned(fv_bytes.as_ptr() as *const patina::pi::fw_fs::fv::Header) };
header.attributes |= fvb::attributes::raw::fvb2::READ_STATUS;
header.checksum = 0;
unsafe {
ptr::write_unaligned(fv_bytes.as_mut_ptr() as *mut patina::pi::fw_fs::fv::Header, header);
}
let header_len = header.header_length as usize;
let checksum = fv_bytes[..header_len]
.chunks_exact(2)
.fold(0u16, |sum, value| sum.wrapping_add(u16::from_le_bytes(value.try_into().unwrap())));
header.checksum = 0u16.wrapping_sub(checksum);
unsafe {
ptr::write_unaligned(fv_bytes.as_mut_ptr() as *mut patina::pi::fw_fs::fv::Header, header);
}
}
let file_guid = patina::BinaryGuid::from_fields(
0x12345678,
0x9abc,
0xdef0,
0x12,
0x34,
&[0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0],
);
let subtype_guid = patina::BinaryGuid::from_fields(
0x9bec7109,
0x6d7a,
0x413a,
0x8e,
0x4b,
&[0x01, 0x9c, 0xed, 0x05, 0x03, 0xe1],
);
let payload = vec![0xaa, 0xbb, 0xcc, 0xdd, 0xee];
let section = Section::new_from_header_with_data(
SectionHeader::FreeFormSubtypeGuid(
FreeformSubtypeGuid { sub_type_guid: subtype_guid },
payload.len() as u32,
),
payload.clone(),
)
.expect("freeform subtype section should be created");
let mut file = FfsFile::new(file_guid, ffs::file::raw::r#type::FREEFORM);
file.sections_mut().push(section);
let mut fv = Volume::new(vec![BlockMapEntry { num_blocks: 1, length: 4096 }]);
fv.files_mut().push(file);
let mut fv_bytes = fv.serialize().expect("synthetic FV should serialize");
enable_read_status(&mut fv_bytes);
let leaked_fv = fv_bytes.leak();
let base_address = leaked_fv.as_ptr() as u64;
test_support::with_global_lock(|| {
static CORE: MockCore = MockCore::new(CompositeSectionExtractor::new());
CORE.override_instance();
unsafe { test_support::init_test_gcd(None) };
let fv_interface = MockProtocolData::new_fv_protocol(None);
let fv_ptr = NonNull::from(&*fv_interface);
let metadata = Metadata::new_fv(fv_interface, base_address);
CORE.pi_dispatcher.fv_data.lock().fv_metadata.insert(fv_ptr.addr(), metadata);
let fv_ptr_raw = fv_ptr.as_ptr();
let name_guid = file_guid.into_inner();
let mut auth_status = 0u32;
let expected_size = core::mem::size_of::<FreeformSubtypeGuid>() + payload.len();
let mut returned = vec![0u8; expected_size + 8];
let mut returned_ptr = returned.as_mut_ptr() as *mut c_void;
let mut returned_size = returned.len();
let status = MockProtocolData::fv_read_section_efiapi(
fv_ptr_raw,
&raw const name_guid,
ffs::section::raw_type::FREEFORM_SUBTYPE_GUID,
0,
&raw mut returned_ptr,
&raw mut returned_size,
&raw mut auth_status,
);
assert_eq!(status, efi::Status::SUCCESS);
assert_eq!(returned_size, expected_size);
assert_eq!(&returned[..size_of::<efi::Guid>()], subtype_guid.into_inner().as_bytes());
assert_eq!(&returned[size_of::<efi::Guid>()..returned_size], payload.as_slice());
})
.unwrap();
}
}