diff options
author | Yecheng Zhao <zyecheng@google.com> | 2023-10-26 19:26:31 +0000 |
---|---|---|
committer | Yecheng Zhao <zyecheng@google.com> | 2023-11-13 21:34:44 +0000 |
commit | 9df292c862b964a001c6d1218f7d06cd2b414933 (patch) | |
tree | 2b2829fb5947a8c1476487d5d2e2ba257082b34e | |
parent | 9b0ae388b77040d365fbf0f97625889c32e02f6c (diff) | |
download | libbootloader-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/BUILD | 2 | ||||
-rw-r--r-- | gbl/efi/src/android_load.rs | 237 | ||||
-rw-r--r-- | gbl/efi/src/main.rs | 170 | ||||
-rw-r--r-- | gbl/efi/src/utils.rs | 126 |
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)?) +} |