summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYecheng Zhao <zyecheng@google.com>2023-10-26 19:26:31 +0000
committerYecheng Zhao <zyecheng@google.com>2023-11-13 21:34:44 +0000
commit9df292c862b964a001c6d1218f7d06cd2b414933 (patch)
tree2b2829fb5947a8c1476487d5d2e2ba257082b34e
parent9b0ae388b77040d365fbf0f97625889c32e02f6c (diff)
downloadlibbootloader-9df292c862b964a001c6d1218f7d06cd2b414933.tar.gz
Update demo to boot Android from disk
Update the UEFI application into a simple demo for booting Android on disk. Bug: 305093905 Change-Id: I2cfcc964adad070c808eee5eb4bf98232ef44601
-rw-r--r--gbl/efi/BUILD2
-rw-r--r--gbl/efi/src/android_load.rs237
-rw-r--r--gbl/efi/src/main.rs170
-rw-r--r--gbl/efi/src/utils.rs126
4 files changed, 445 insertions, 90 deletions
diff --git a/gbl/efi/BUILD b/gbl/efi/BUILD
index 7224db6..17da7cf 100644
--- a/gbl/efi/BUILD
+++ b/gbl/efi/BUILD
@@ -23,6 +23,7 @@ package(
rust_binary(
name = "main",
srcs = [
+ "src/android_load.rs",
"src/main.rs",
"src/riscv64.rs",
"src/utils.rs",
@@ -37,6 +38,7 @@ rust_binary(
],
deps = [
"@gbl//libboot",
+ "@gbl//libbootconfig",
"@gbl//libbootimg",
"@gbl//libefi",
"@gbl//libfdt",
diff --git a/gbl/efi/src/android_load.rs b/gbl/efi/src/android_load.rs
new file mode 100644
index 0000000..c842837
--- /dev/null
+++ b/gbl/efi/src/android_load.rs
@@ -0,0 +1,237 @@
+// 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 core::ffi::CStr;
+use core::fmt::Write;
+use core::str::from_utf8;
+
+use bootconfig::{BootConfigBuilder, BootConfigError};
+use bootimg::{BootImage, VendorImageHeader};
+use efi::EfiEntry;
+use fdt::Fdt;
+
+use crate::utils::{
+ aligned_subslice, cstr_bytes_to_str, find_gpt_devices, get_efi_fdt, usize_add, usize_roundup,
+ EfiAppError, GblEfiError, Result,
+};
+
+// Linux kernel requires 2MB alignment.
+const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024;
+
+/// Loads Android images from disk and fixes up bootconfig, commandline, and FDT.
+///
+/// A number of simplifications are made:
+///
+/// * No A/B slot switching is performed. It always boot from *_a slot.
+/// * No AVB is performed. Thus boot mode is recovery only.
+/// * No dynamic partitions.
+/// * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
+///
+/// # Returns
+///
+/// Returns a tuple of 4 slices corresponding to:
+/// (kernel load buffer, ramdisk load buffer, FDT load buffer, unused buffer).
+pub fn load_android_simple<'a>(
+ efi_entry: &EfiEntry,
+ load: &'a mut [u8],
+) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> {
+ let mut gpt_devices = find_gpt_devices(efi_entry)?;
+
+ const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
+
+ // Parse boot header.
+ let (boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
+ gpt_devices.read_gpt_partition("boot_a", 0, boot_header_buffer)?;
+ let boot_header = BootImage::parse(boot_header_buffer)?;
+ let (kernel_size, cmdline, kernel_hdr_size) = match boot_header {
+ BootImage::V3(ref hdr) => (hdr.kernel_size as usize, &hdr.cmdline[..], PAGE_SIZE),
+ BootImage::V4(ref hdr) => {
+ (hdr._base.kernel_size as usize, &hdr._base.cmdline[..], PAGE_SIZE)
+ }
+ _ => {
+ efi_print!(efi_entry, "V0/V1/V2 images are not supported\n");
+ return Err(GblEfiError::EfiAppError(EfiAppError::Unsupported));
+ }
+ };
+ efi_print!(efi_entry, "boot image size: {}\n", kernel_size);
+ efi_print!(efi_entry, "boot image cmdline: \"{}\"\n", from_utf8(cmdline).unwrap());
+
+ // Parse vendor boot header.
+ let (vendor_boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
+ gpt_devices.read_gpt_partition("vendor_boot_a", 0, vendor_boot_header_buffer)?;
+ let vendor_boot_header = VendorImageHeader::parse(vendor_boot_header_buffer)?;
+ let (vendor_ramdisk_size, vendor_hdr_size, vendor_cmdline) = match vendor_boot_header {
+ VendorImageHeader::V3(ref hdr) => (
+ hdr.vendor_ramdisk_size as usize,
+ usize_roundup(hdr.bytes().len(), hdr.page_size)?,
+ &hdr.cmdline[..],
+ ),
+ VendorImageHeader::V4(ref hdr) => (
+ hdr._base.vendor_ramdisk_size as usize,
+ usize_roundup(hdr.bytes().len(), hdr._base.page_size)?,
+ &hdr._base.cmdline[..],
+ ),
+ };
+ efi_print!(efi_entry, "vendor ramdisk size: {}\n", vendor_ramdisk_size);
+ efi_print!(efi_entry, "vendor cmdline: \"{}\"\n", from_utf8(vendor_cmdline).unwrap());
+
+ // Parse init_boot header
+ let init_boot_header_buffer = &mut load[..PAGE_SIZE];
+ gpt_devices.read_gpt_partition("init_boot_a", 0, init_boot_header_buffer)?;
+ let init_boot_header = BootImage::parse(init_boot_header_buffer)?;
+ let (generic_ramdisk_size, init_boot_hdr_size) = match init_boot_header {
+ BootImage::V3(ref hdr) => (hdr.ramdisk_size as usize, PAGE_SIZE),
+ BootImage::V4(ref hdr) => (hdr._base.ramdisk_size as usize, PAGE_SIZE),
+ _ => {
+ efi_print!(efi_entry, "V0/V1/V2 images are not supported\n");
+ return Err(GblEfiError::EfiAppError(EfiAppError::Unsupported));
+ }
+ };
+ efi_print!(efi_entry, "init_boot image size: {}\n", generic_ramdisk_size);
+
+ // Load and prepare various images.
+
+ // Load kernel
+ let load = aligned_subslice(load, KERNEL_ALIGNMENT)?;
+ let (kernel_load_buffer, load) = load.split_at_mut(kernel_size);
+ gpt_devices.read_gpt_partition("boot_a", kernel_hdr_size as u64, kernel_load_buffer)?;
+
+ // Load vendor ramdisk
+ let load = aligned_subslice(load, KERNEL_ALIGNMENT)?;
+ let mut ramdisk_load_curr = 0;
+ gpt_devices.read_gpt_partition(
+ "vendor_boot_a",
+ vendor_hdr_size as u64,
+ &mut load[ramdisk_load_curr..][..vendor_ramdisk_size],
+ )?;
+ ramdisk_load_curr = usize_add(ramdisk_load_curr, vendor_ramdisk_size)?;
+
+ // Load generic ramdisk
+ gpt_devices.read_gpt_partition(
+ "init_boot_a",
+ init_boot_hdr_size as u64,
+ &mut load[ramdisk_load_curr..][..generic_ramdisk_size],
+ )?;
+ ramdisk_load_curr = usize_add(ramdisk_load_curr, generic_ramdisk_size)?;
+
+ // Use the remaining buffer for bootconfig
+ let mut bootconfig_builder = BootConfigBuilder::new(&mut load[ramdisk_load_curr..])?;
+ // Add slot index
+ bootconfig_builder.add("androidboot.slot_suffix=_a\n")?;
+ // By default, boot mode is recovery. The following needs to be added for normal boot.
+ // However, normal boot requires additional bootconfig generated during AVB, which is under
+ // development.
+ //
+ // bootconfig_builder.add("androidboot.force_normal_boot=1\n")?;
+
+ // V4 image has vendor bootconfig.
+ if let VendorImageHeader::V4(ref hdr) = vendor_boot_header {
+ let mut bootconfig_offset: usize = vendor_hdr_size;
+ for image_size in
+ [hdr._base.vendor_ramdisk_size, hdr._base.dtb_size, hdr.vendor_ramdisk_table_size]
+ {
+ bootconfig_offset =
+ usize_add(bootconfig_offset, usize_roundup(image_size, hdr._base.page_size)?)?;
+ }
+ bootconfig_builder.add_with(|out| {
+ gpt_devices
+ .read_gpt_partition(
+ "vendor_boot_a",
+ bootconfig_offset as u64,
+ &mut out[..hdr.bootconfig_size as usize],
+ )
+ .map_err(|_| BootConfigError::GenericReaderError(-1))?;
+ Ok(hdr.bootconfig_size as usize)
+ })?;
+ }
+ // Check if there is a device specific bootconfig partition.
+ match gpt_devices.partition_size("bootconfig") {
+ Ok(sz) => {
+ bootconfig_builder.add_with(|out| {
+ // For proof-of-concept only, we just load as much as possible and figure out the
+ // actual bootconfig string length after. This however, can introduce large amount
+ // of unnecessary disk access. In real implementation, we might want to either read
+ // page by page or find way to know the actual length first.
+ let max_size = core::cmp::min(sz, out.len());
+ gpt_devices
+ .read_gpt_partition("bootconfig", 0, &mut out[..max_size])
+ .map_err(|_| BootConfigError::GenericReaderError(-1))?;
+ // Compute the actual config string size. The config is a null-terminated string.
+ Ok(CStr::from_bytes_until_nul(&out[..])
+ .map_err(|_| BootConfigError::GenericReaderError(-1))?
+ .to_bytes()
+ .len())
+ })?;
+ }
+ _ => {}
+ }
+ efi_print!(efi_entry, "final bootconfig: \"{}\"\n", bootconfig_builder);
+ ramdisk_load_curr = usize_add(ramdisk_load_curr, bootconfig_builder.config_bytes().len())?;
+
+ // Prepare FDT.
+
+ // For cuttlefish, FDT comes from EFI vendor configuration table installed by u-boot. In real
+ // product, it may come from vendor boot image.
+ let (_, fdt_bytes) = get_efi_fdt(&efi_entry).ok_or_else(|| EfiAppError::NoFdt)?;
+ let fdt_origin = Fdt::new(fdt_bytes)?;
+
+ // Use the remaining load buffer for updating FDT.
+ let (ramdisk_load_buffer, load) = load.split_at_mut(ramdisk_load_curr);
+ // libfdt requires FDT buffer to be 8-byte aligned.
+ let load = aligned_subslice(load, 8)?;
+ let mut fdt = Fdt::new_from_init(&mut load[..], fdt_bytes)?;
+
+ // Add ramdisk range to FDT
+ let ramdisk_addr = ramdisk_load_buffer.as_ptr() as u64;
+ let ramdisk_end = ramdisk_addr + (ramdisk_load_buffer.len() as u64);
+ fdt.set_property(
+ "chosen",
+ CStr::from_bytes_with_nul(b"linux,initrd-start\0").unwrap(),
+ &ramdisk_addr.to_be_bytes(),
+ )?;
+ fdt.set_property(
+ "chosen",
+ CStr::from_bytes_with_nul(b"linux,initrd-end\0").unwrap(),
+ &ramdisk_end.to_be_bytes(),
+ )?;
+ efi_print!(&efi_entry, "linux,initrd-start: {:#x}\n", ramdisk_addr);
+ efi_print!(&efi_entry, "linux,initrd-end: {:#x}\n", ramdisk_end);
+
+ // Concatenate kernel commandline and add it to FDT.
+ let bootargs_prop = CStr::from_bytes_with_nul(b"bootargs\0").unwrap();
+ let all_cmdline = [
+ cstr_bytes_to_str(fdt_origin.get_property("chosen", bootargs_prop).unwrap_or(&[0]))?,
+ " ",
+ cstr_bytes_to_str(cmdline)?,
+ " ",
+ cstr_bytes_to_str(vendor_cmdline)?,
+ "\0",
+ ];
+ let mut all_cmdline_len = 0;
+ all_cmdline.iter().for_each(|v| all_cmdline_len += v.len());
+ let cmdline_payload = fdt.set_property_placeholder("chosen", bootargs_prop, all_cmdline_len)?;
+ let mut cmdline_payload_off: usize = 0;
+ for ele in all_cmdline {
+ cmdline_payload[cmdline_payload_off..][..ele.len()].clone_from_slice(ele.as_bytes());
+ cmdline_payload_off += ele.len();
+ }
+ efi_print!(&efi_entry, "final cmdline: \"{}\"\n", from_utf8(cmdline_payload).unwrap());
+
+ // Finalize FDT to actual used size.
+ fdt.shrink_to_fit()?;
+
+ let fdt_len = fdt.header_ref()?.actual_size();
+ let (fdt_load_buffer, remains) = load.split_at_mut(fdt_len);
+ Ok((kernel_load_buffer, ramdisk_load_buffer, fdt_load_buffer, remains))
+}
diff --git a/gbl/efi/src/main.rs b/gbl/efi/src/main.rs
index fd269c2..0df6e7b 100644
--- a/gbl/efi/src/main.rs
+++ b/gbl/efi/src/main.rs
@@ -12,108 +12,114 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// This EFI application implements a demo for booting Android from disk. It can be run from
+// Cuttlefish by adding `--android_efi_loader=<path of this EFI binary>` to the command line.
+//
+// A number of simplifications are made (see `android_load::load_android_simple()`):
+//
+// * No A/B slot switching is performed. It always boot from *_a slot.
+// * No AVB is performed. Thus boot mode is recovery only.
+// * No dynamic partitions.
+// * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
+//
+// The missing pieces above are currently under development as part of the full end-to-end boot
+// flow in libgbl, which will eventually replace this demo. The demo is currently used as an
+// end-to-end test for libraries developed so far.
+
#![no_std]
#![no_main]
-#[cfg(target_arch = "riscv64")]
-mod riscv64;
-
-use alloc::vec::Vec;
-use core::ffi::CStr;
-use core::fmt::Write;
-use core::str::from_utf8;
-use gbl_storage::BlockDevice;
-
-use efi::defs::EfiSystemTable;
-use efi::{initialize, BlockIoProtocol, LoadedImageProtocol};
-use fdt::Fdt;
-
// For the `vec!` macro
#[macro_use]
extern crate alloc;
+use core::fmt::Write;
+
+use efi::defs::EfiSystemTable;
+use efi::{exit_boot_services, initialize};
#[macro_use]
mod utils;
-use utils::{get_device_path, get_efi_fdt, EfiGptDevice, MultiGptDevices, Result};
+use utils::{loaded_image_path, Result};
+
+#[cfg(target_arch = "riscv64")]
+mod riscv64;
+
+mod android_load;
+use android_load::load_android_simple;
fn main(image_handle: *mut core::ffi::c_void, systab_ptr: *mut EfiSystemTable) -> Result<()> {
// SAFETY: Called only once here upon EFI app entry.
let entry = unsafe { initialize(image_handle, systab_ptr)? };
efi_print!(entry, "\n\n****Rust EFI Application****\n\n");
+ efi_print!(entry, "Image path: {}\n", loaded_image_path(&entry)?);
- let bs = entry.system_table().boot_services();
- let loaded_image = bs.open_protocol::<LoadedImageProtocol>(entry.image_handle())?;
- efi_print!(entry, "Image path: {}\n", get_device_path(&entry, loaded_image.device_handle()?)?);
-
- efi_print!(entry, "Scanning block devices...\n");
- let block_dev_handles = bs.locate_handle_buffer_by_protocol::<BlockIoProtocol>()?;
- let mut gpt_devices = Vec::<EfiGptDevice>::new();
- for (i, handle) in block_dev_handles.handles().iter().enumerate() {
- efi_print!(entry, "-------------------\n");
- efi_print!(entry, "Block device #{}: {}\n", i, get_device_path(&entry, *handle)?);
- let mut gpt_dev = EfiGptDevice::new(bs.open_protocol::<BlockIoProtocol>(*handle)?)?;
- efi_print!(
- entry,
- "block size: {}, blocks: {}, alignment: {}\n",
- gpt_dev.block_device().block_size(),
- gpt_dev.block_device().num_blocks(),
- gpt_dev.block_device().alignment()
- );
- match gpt_dev.sync_gpt() {
- Ok(()) => {
- efi_print!(entry, "GPT found!\n");
- for e in gpt_dev.gpt()?.entries()? {
- efi_print!(entry, "{}\n", e);
- }
- gpt_devices.push(gpt_dev);
- }
- Err(err) => {
- efi_print!(entry, "No GPT on device. {:?}\n", err);
- }
- };
+ // Allocate buffer for load.
+ let mut load_buffer = vec![0u8; 128 * 1024 * 1024]; // 128MB
+
+ let (kernel, ramdisk, fdt, remains) = load_android_simple(&entry, &mut load_buffer[..])?;
+
+ efi_print!(
+ &entry,
+ "\nBooting kernel @ {:#x}, ramdisk @ {:#x}, fdt @ {:#x}\n\n",
+ kernel.as_ptr() as usize,
+ ramdisk.as_ptr() as usize,
+ fdt.as_ptr() as usize
+ );
+
+ #[cfg(target_arch = "aarch64")]
+ {
+ let _ = exit_boot_services(entry, remains)?;
+ // SAFETY: We currently targets at Cuttlefish emulator where images are provided valid.
+ unsafe { boot::aarch64::jump_linux_el2_or_lower(kernel, fdt) };
}
- // Following is example code for testing library linkage. They'll be removed and replaced with
- // full GBL boot flow as development goes.
-
- let mut multi_gpt_dev = MultiGptDevices::new(gpt_devices);
- match multi_gpt_dev.partition_size("bootconfig") {
- Ok(sz) => {
- efi_print!(entry, "Has device-specific bootconfig: {} bytes\n", sz);
- let mut bootconfig = vec![0u8; sz];
- multi_gpt_dev.read_gpt_partition("bootconfig", 0, &mut bootconfig[..])?;
- efi_print!(
- entry,
- "{}\n",
- CStr::from_bytes_until_nul(&mut bootconfig[..]).unwrap().to_str().unwrap()
- );
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ {
+ let fdt = fdt::Fdt::new(&fdt[..])?;
+ let efi_mmap = exit_boot_services(entry, remains)?;
+ // SAFETY: We currently targets at Cuttlefish emulator where images are provided valid.
+ unsafe {
+ boot::x86::boot_linux_bzimage(
+ kernel,
+ ramdisk,
+ fdt.get_property(
+ "chosen",
+ core::ffi::CStr::from_bytes_with_nul(b"bootargs\0").unwrap(),
+ )
+ .unwrap(),
+ |e820_entries| {
+ // Convert EFI memorty type to e820 memory type.
+ if efi_mmap.len() > e820_entries.len() {
+ return Err(boot::BootError::E820MemoryMapCallbackError(-1));
+ }
+ for (idx, mem) in efi_mmap.into_iter().enumerate() {
+ e820_entries[idx] = boot::x86::e820entry {
+ addr: mem.physical_start,
+ size: mem.number_of_pages * 4096,
+ type_: utils::efi_to_e820_mem_type(mem.memory_type),
+ };
+ }
+ Ok(efi_mmap.len().try_into().unwrap())
+ },
+ 0x9_0000,
+ )?;
}
- _ => {}
- };
-
- // Check if we have a device tree.
- match get_efi_fdt(&entry) {
- Some((_, bytes)) => {
- let fdt = Fdt::new(bytes)?;
- efi_print!(entry, "Device tree found\n");
- let print_property = |node: &str, name: &CStr| -> Result<()> {
- efi_print!(
- entry,
- "{}:{} = {}\n",
- node,
- name.to_str().unwrap(),
- from_utf8(fdt.get_property(node, name).unwrap_or("NA".as_bytes())).unwrap()
- );
- Ok(())
- };
- print_property("/", CStr::from_bytes_with_nul(b"compatible\0").unwrap())?;
- print_property("/chosen", CStr::from_bytes_with_nul(b"u-boot,version\0").unwrap())?;
- print_property("/chosen", CStr::from_bytes_with_nul(b"bootargs\0").unwrap())?;
- }
- _ => {}
+ unreachable!();
+ }
+
+ #[cfg(target_arch = "riscv64")]
+ {
+ let boot_hart_id = entry
+ .system_table()
+ .boot_services()
+ .find_first_and_open::<efi::RiscvBootProtocol>()?
+ .get_boot_hartid()?;
+ efi_print!(entry, "riscv boot_hart_id: {}\n", boot_hart_id);
+ let _ = exit_boot_services(entry, remains)?;
+ // SAFETY: We currently targets at Cuttlefish emulator where images are provided valid.
+ unsafe { boot::riscv64::jump_linux(kernel, boot_hart_id, fdt) };
}
- Ok(())
}
#[no_mangle]
diff --git a/gbl/efi/src/utils.rs b/gbl/efi/src/utils.rs
index c470ded..326d8a2 100644
--- a/gbl/efi/src/utils.rs
+++ b/gbl/efi/src/utils.rs
@@ -13,10 +13,15 @@
// limitations under the License.
use alloc::vec::Vec;
+use core::ffi::CStr;
+
+use boot::BootError;
+use bootconfig::BootConfigError;
+use bootimg::ImageError;
use efi::defs::EfiGuid;
use efi::{
BlockIoProtocol, DeviceHandle, DevicePathProtocol, DevicePathText, DevicePathToTextProtocol,
- EfiEntry, EfiError, Protocol,
+ EfiEntry, EfiError, LoadedImageProtocol, Protocol,
};
use fdt::{FdtError, FdtHeader};
use gbl_storage::{required_scratch_size, BlockDevice, Gpt, GptEntry, StorageError};
@@ -40,21 +45,33 @@ pub type Result<T> = core::result::Result<T, GblEfiError>;
#[derive(Debug)]
pub enum EfiAppError {
ArithmeticOverflow,
+ InvalidString,
+ NoFdt,
NotFound,
+ Unsupported,
}
/// A top level error type that consolidates errors from different libraries.
#[derive(Debug)]
pub enum GblEfiError {
- StorageError(gbl_storage::StorageError),
+ BootConfigError(BootConfigError),
+ BootError(BootError),
EfiAppError(EfiAppError),
- EfiError(efi::EfiError),
- FdtError(fdt::FdtError),
+ EfiError(EfiError),
+ FdtError(FdtError),
+ ImageError(ImageError),
+ StorageError(StorageError),
}
-impl From<StorageError> for GblEfiError {
- fn from(error: StorageError) -> GblEfiError {
- GblEfiError::StorageError(error)
+impl From<BootConfigError> for GblEfiError {
+ fn from(error: BootConfigError) -> GblEfiError {
+ GblEfiError::BootConfigError(error)
+ }
+}
+
+impl From<BootError> for GblEfiError {
+ fn from(error: BootError) -> GblEfiError {
+ GblEfiError::BootError(error)
}
}
@@ -76,14 +93,48 @@ impl From<FdtError> for GblEfiError {
}
}
+impl From<ImageError> for GblEfiError {
+ fn from(error: ImageError) -> GblEfiError {
+ GblEfiError::ImageError(error)
+ }
+}
+
+impl From<StorageError> for GblEfiError {
+ fn from(error: StorageError) -> GblEfiError {
+ GblEfiError::StorageError(error)
+ }
+}
+
+/// Checks and converts an integer into usize
fn to_usize<T: TryInto<usize>>(val: T) -> Result<usize> {
Ok(val.try_into().map_err(|_| EfiAppError::ArithmeticOverflow)?)
}
-fn usize_mul<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
+/// Rounds up a usize convertible number.
+pub fn usize_roundup<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
+ // (lhs + rhs - 1) / rhs * rhs
+ let lhs = to_usize(lhs)?;
+ let rhs = to_usize(rhs)?;
+ let compute = || lhs.checked_add(rhs.checked_sub(1)?)?.checked_div(rhs)?.checked_mul(rhs);
+ Ok(compute().ok_or_else(|| EfiAppError::ArithmeticOverflow)?)
+}
+
+/// Adds two usize convertible numbers and checks overflow.
+pub fn usize_add<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
+ Ok(to_usize(lhs)?.checked_add(to_usize(rhs)?).ok_or_else(|| EfiAppError::ArithmeticOverflow)?)
+}
+
+/// Multiply two usize convertible numbers and checks overflow.
+pub fn usize_mul<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
Ok(to_usize(lhs)?.checked_mul(to_usize(rhs)?).ok_or_else(|| EfiAppError::ArithmeticOverflow)?)
}
+/// Gets a subslice of the given slice with aligned address according to `alignment`
+pub fn aligned_subslice(bytes: &mut [u8], alignment: usize) -> Result<&mut [u8]> {
+ let addr = bytes.as_ptr() as usize;
+ Ok(&mut bytes[usize_roundup(addr, alignment)? - addr..])
+}
+
// Implement a block device on top of BlockIoProtocol
pub struct EfiBlockDevice<'a>(pub Protocol<'a, BlockIoProtocol>);
@@ -231,6 +282,18 @@ pub fn get_device_path<'a>(
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() {
@@ -243,3 +306,50 @@ pub fn get_efi_fdt<'a>(entry: &'a EfiEntry) -> Option<(&FdtHeader, &[u8])> {
}
None
}
+
+/// Find all block devices that have a valid GPT and returns them as a `MultiGptDevices`.
+pub fn find_gpt_devices(efi_entry: &EfiEntry) -> Result<MultiGptDevices> {
+ let bs = efi_entry.system_table().boot_services();
+ let block_dev_handles = bs.locate_handle_buffer_by_protocol::<BlockIoProtocol>()?;
+ let mut gpt_devices = Vec::<EfiGptDevice>::new();
+ for handle in block_dev_handles.handles() {
+ let mut gpt_dev = EfiGptDevice::new(bs.open_protocol::<BlockIoProtocol>(*handle)?)?;
+ match gpt_dev.sync_gpt() {
+ Ok(()) => {
+ gpt_devices.push(gpt_dev);
+ }
+ _ => {}
+ };
+ }
+ Ok(MultiGptDevices::new(gpt_devices))
+}
+
+#[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::defs::EFI_MEMORY_TYPE_LOADER_CODE
+ | efi::defs::EFI_MEMORY_TYPE_LOADER_DATA
+ | efi::defs::EFI_MEMORY_TYPE_BOOT_SERVICES_CODE
+ | efi::defs::EFI_MEMORY_TYPE_BOOT_SERVICES_DATA
+ | efi::defs::EFI_MEMORY_TYPE_CONVENTIONAL_MEMORY => boot::x86::E820_ADDRESS_TYPE_RAM,
+ efi::defs::EFI_MEMORY_TYPE_RUNTIME_SERVICES_CODE
+ | efi::defs::EFI_MEMORY_TYPE_RUNTIME_SERVICES_DATA
+ | efi::defs::EFI_MEMORY_TYPE_MEMORY_MAPPED_IO
+ | efi::defs::EFI_MEMORY_TYPE_MEMORY_MAPPED_IOPORT_SPACE
+ | efi::defs::EFI_MEMORY_TYPE_PAL_CODE
+ | efi::defs::EFI_MEMORY_TYPE_RESERVED_MEMORY_TYPE => boot::x86::E820_ADDRESS_TYPE_RESERVED,
+ efi::defs::EFI_MEMORY_TYPE_UNUSABLE_MEMORY => boot::x86::E820_ADDRESS_TYPE_UNUSABLE,
+ efi::defs::EFI_MEMORY_TYPE_ACPIRECLAIM_MEMORY => boot::x86::E820_ADDRESS_TYPE_ACPI,
+ efi::defs::EFI_MEMORY_TYPE_ACPIMEMORY_NVS => boot::x86::E820_ADDRESS_TYPE_NVS,
+ efi::defs::EFI_MEMORY_TYPE_PERSISTENT_MEMORY => boot::x86::E820_ADDRESS_TYPE_PMEM,
+ v => panic!("Unmapped EFI memory type {v}"),
+ }
+}
+
+/// A helper to convert a bytes slice containing a null-terminated string to `str`
+pub fn cstr_bytes_to_str(data: &[u8]) -> Result<&str> {
+ Ok(CStr::from_bytes_until_nul(data)
+ .map_err(|_| EfiAppError::InvalidString)?
+ .to_str()
+ .map_err(|_| EfiAppError::InvalidString)?)
+}