blob: dfe1d75fdb3f37f181dac2b45a5d5828b22e892f [file]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::efi;
use ::efi::EfiMemoryAttributesTable;
use core::time::Duration;
use efi::{
protocol::{
device_path::{DevicePathProtocol, DevicePathText, DevicePathToTextProtocol},
loaded_image::LoadedImageProtocol,
simple_text_input::SimpleTextInputProtocol,
},
utils::Timeout,
DeviceHandle, EfiEntry,
};
use efi_types::{EfiGuid, EfiInputKey};
use fdt::FdtHeader;
use liberror::Error;
use libgbl::Result;
pub const EFI_DTB_TABLE_GUID: EfiGuid =
EfiGuid::new(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]);
/// Helper function to get the `DevicePathText` from a `DeviceHandle`.
pub fn get_device_path<'a>(
entry: &'a EfiEntry,
handle: DeviceHandle,
) -> Result<DevicePathText<'a>> {
let bs = entry.system_table().boot_services();
let path = bs.open_protocol::<DevicePathProtocol>(handle)?;
let path_to_text = bs.find_first_and_open::<DevicePathToTextProtocol>()?;
Ok(path_to_text.convert_device_path_to_text(&path, false, false)?)
}
/// Helper function to get the loaded image path.
pub fn loaded_image_path(entry: &EfiEntry) -> Result<DevicePathText> {
get_device_path(
entry,
entry
.system_table()
.boot_services()
.open_protocol::<LoadedImageProtocol>(entry.image_handle())?
.device_handle()?,
)
}
/// Find FDT from EFI configuration table.
pub fn get_efi_fdt<'a>(entry: &'a EfiEntry) -> Option<(&FdtHeader, &[u8])> {
if let Some(config_tables) = entry.system_table().configuration_table() {
for table in config_tables {
if table.vendor_guid == EFI_DTB_TABLE_GUID {
// SAFETY: Buffer provided by EFI configuration table.
return unsafe { FdtHeader::from_raw(table.vendor_table as *const _).ok() };
}
}
}
None
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
pub fn efi_to_e820_mem_type(efi_mem_type: u32) -> u32 {
match efi_mem_type {
efi_types::EFI_MEMORY_TYPE_LOADER_CODE
| efi_types::EFI_MEMORY_TYPE_LOADER_DATA
| efi_types::EFI_MEMORY_TYPE_BOOT_SERVICES_CODE
| efi_types::EFI_MEMORY_TYPE_BOOT_SERVICES_DATA
| efi_types::EFI_MEMORY_TYPE_CONVENTIONAL_MEMORY => boot::x86::E820_ADDRESS_TYPE_RAM,
efi_types::EFI_MEMORY_TYPE_RUNTIME_SERVICES_CODE
| efi_types::EFI_MEMORY_TYPE_RUNTIME_SERVICES_DATA
| efi_types::EFI_MEMORY_TYPE_MEMORY_MAPPED_IO
| efi_types::EFI_MEMORY_TYPE_MEMORY_MAPPED_IOPORT_SPACE
| efi_types::EFI_MEMORY_TYPE_PAL_CODE
| efi_types::EFI_MEMORY_TYPE_RESERVED_MEMORY_TYPE => boot::x86::E820_ADDRESS_TYPE_RESERVED,
efi_types::EFI_MEMORY_TYPE_UNUSABLE_MEMORY => boot::x86::E820_ADDRESS_TYPE_UNUSABLE,
efi_types::EFI_MEMORY_TYPE_ACPIRECLAIM_MEMORY => boot::x86::E820_ADDRESS_TYPE_ACPI,
efi_types::EFI_MEMORY_TYPE_ACPIMEMORY_NVS => boot::x86::E820_ADDRESS_TYPE_NVS,
efi_types::EFI_MEMORY_TYPE_PERSISTENT_MEMORY => boot::x86::E820_ADDRESS_TYPE_PMEM,
v => panic!("Unmapped EFI memory type {v}"),
}
}
/// Repetitively runs a closure until it signals completion or timeout.
///
/// * If `f` returns `Ok(R)`, an `Ok(Some(R))` is returned immediately.
/// * If `f` has been repetitively called and returning `Err(false)` for `timeout_duration`, an
/// `Ok(None)` is returned. This is the time out case.
/// * If `f` returns `Err(true)` the timeout is reset.
pub fn loop_with_timeout<F, R>(
efi_entry: &EfiEntry,
timeout_duration: Duration,
mut f: F,
) -> Result<Option<R>>
where
F: FnMut() -> core::result::Result<R, bool>,
{
let timeout = Timeout::new(efi_entry, timeout_duration)?;
while !timeout.check()? {
match f() {
Ok(v) => return Ok(Some(v)),
Err(true) => timeout.reset(timeout_duration)?,
_ => {}
}
}
Ok(None)
}
/// Waits for a key stroke value from simple text input.
///
/// Returns `Ok(true)` if the expected key stroke is read, `Ok(false)` if timeout, `Err` otherwise.
pub fn wait_key_stroke(
efi_entry: &EfiEntry,
pred: impl Fn(EfiInputKey) -> bool,
timeout: Duration,
) -> Result<bool> {
let input = efi_entry
.system_table()
.boot_services()
.find_first_and_open::<SimpleTextInputProtocol>()?;
loop_with_timeout(efi_entry, timeout, || -> core::result::Result<Result<bool>, bool> {
match input.read_key_stroke() {
Ok(Some(key)) if pred(key) => Ok(Ok(true)),
Err(e) => Ok(Err(e.into())),
_ => Err(false),
}
})?
.unwrap_or(Ok(false))
}
// Converts an EFI memory type to a zbi_mem_range_t type.
pub fn efi_to_zbi_mem_range_type(efi_mem_type: u32) -> u32 {
match efi_mem_type {
efi_types::EFI_MEMORY_TYPE_LOADER_CODE
| efi_types::EFI_MEMORY_TYPE_LOADER_DATA
| efi_types::EFI_MEMORY_TYPE_BOOT_SERVICES_CODE
| efi_types::EFI_MEMORY_TYPE_BOOT_SERVICES_DATA
| efi_types::EFI_MEMORY_TYPE_CONVENTIONAL_MEMORY => zbi::zbi_format::ZBI_MEM_TYPE_RAM,
_ => zbi::zbi_format::ZBI_MEM_TYPE_RESERVED,
}
}
/// Find Memory attributes from EFI configuration_table
#[allow(unused)]
pub fn get_efi_mem_attr<'a>(entry: &'a EfiEntry) -> Option<EfiMemoryAttributesTable<'static>> {
entry.system_table().configuration_table().and_then(|config_tables| {
config_tables
.iter()
.find_map(|&table| {
// SAFETY:
// `table` is valid EFI Configuration table provided by EFI
match unsafe { EfiMemoryAttributesTable::new(table) } {
Err(Error::NotFound) => None,
other => Some(other.ok()),
}
})
.flatten()
})
}