blob: db110d8c42c401bfe2e6c95108ab750446126875 [file]
// Copyright 2024, 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.
//! Implements [Gbl::Ops] for the EFI environment.
use crate::{
efi,
efi_blocks::EfiBlockDeviceIo,
utils::{get_efi_fdt, wait_key_stroke},
};
use alloc::{
alloc::{alloc, handle_alloc_error, Layout},
vec::Vec,
};
use arrayvec::ArrayVec;
use core::{ffi::CStr, fmt::Write, mem::MaybeUninit, num::NonZeroUsize, slice::from_raw_parts_mut};
use efi::{
efi_print, efi_println, protocol::dt_fixup::DtFixupProtocol,
protocol::gbl_efi_image_loading::GblImageLoadingProtocol,
protocol::gbl_efi_os_configuration::GblOsConfigurationProtocol, EfiEntry,
};
use efi_types::{
GblEfiDeviceTreeMetadata, GblEfiImageInfo, GblEfiVerifiedDeviceTree,
GBL_EFI_DEVICE_TREE_SOURCE_BOOT, GBL_EFI_DEVICE_TREE_SOURCE_DTB,
GBL_EFI_DEVICE_TREE_SOURCE_DTBO, GBL_EFI_DEVICE_TREE_SOURCE_VENDOR_BOOT,
PARTITION_NAME_LEN_U16,
};
use fdt::Fdt;
use liberror::{Error, Result};
use libgbl::{
device_tree::{
DeviceTreeComponent, DeviceTreeComponentSource, DeviceTreeComponentsRegistry,
MAXIMUM_DEVICE_TREE_COMPONENTS,
},
ops::{AvbIoError, AvbIoResult, CertPermanentAttributes, ImageBuffer, SHA256_DIGEST_SIZE},
partition::PartitionBlockDevice,
slots::{BootToken, Cursor},
BootImages, GblOps, Result as GblResult,
};
use safemath::SafeNum;
use zbi::ZbiContainer;
use zerocopy::AsBytes;
fn dt_component_to_efi_dt(component: &DeviceTreeComponent) -> GblEfiVerifiedDeviceTree {
let metadata = match component.source {
DeviceTreeComponentSource::Dtb(m) | DeviceTreeComponentSource::Dtbo(m) => m,
_ => Default::default(),
};
GblEfiVerifiedDeviceTree {
metadata: GblEfiDeviceTreeMetadata {
source: match component.source {
DeviceTreeComponentSource::Boot => GBL_EFI_DEVICE_TREE_SOURCE_BOOT,
DeviceTreeComponentSource::VendorBoot => GBL_EFI_DEVICE_TREE_SOURCE_VENDOR_BOOT,
DeviceTreeComponentSource::Dtb(_) => GBL_EFI_DEVICE_TREE_SOURCE_DTB,
DeviceTreeComponentSource::Dtbo(_) => GBL_EFI_DEVICE_TREE_SOURCE_DTBO,
},
id: metadata.id,
rev: metadata.rev,
custom: metadata.custom,
reserved: Default::default(),
},
device_tree: component.dt.as_ptr() as _,
selected: component.selected,
}
}
pub struct Ops<'a, 'b> {
pub efi_entry: &'a EfiEntry,
pub partitions: &'b [PartitionBlockDevice<'b, &'b mut EfiBlockDeviceIo<'a>>],
pub zbi_bootloader_files_buffer: Vec<u8>,
}
impl<'a, 'b> Ops<'a, 'b> {
/// Creates a new instance of [Ops]
pub fn new(
efi_entry: &'a EfiEntry,
partitions: &'b [PartitionBlockDevice<'b, &'b mut EfiBlockDeviceIo<'a>>],
) -> Self {
Self { efi_entry, partitions, zbi_bootloader_files_buffer: Default::default() }
}
/// Gets the property of an FDT node from EFI FDT.
///
/// Returns `None` if fail to get the node
fn get_efi_fdt_prop(&self, path: &str, prop: &CStr) -> Option<&'a [u8]> {
let (_, fdt_bytes) = get_efi_fdt(&self.efi_entry)?;
let fdt = Fdt::new(fdt_bytes).ok()?;
fdt.get_property(path, prop).ok()
}
/// Get buffer for partition loading and verification.
/// Uses GBL EFI ImageLoading protocol.
///
/// # Arguments
/// * `image_name` - image name to differentiate the buffer properties. Processing is limited
/// to first [PARTITION_NAME_LEN_U16] symbols, and the remaining will be ignored.
/// * `size` - requested buffer size
///
/// # Return
/// * Ok(ImageBuffer) - Return buffer for partition loading and verification.
/// * Err(_) - on error
fn get_buffer_image_loading(
&mut self,
image_name: &str,
size: NonZeroUsize,
) -> GblResult<ImageBuffer<'static>> {
let mut image_type = [0u16; PARTITION_NAME_LEN_U16];
image_type.iter_mut().zip(image_name.encode_utf16()).for_each(|(dst, src)| {
*dst = src;
});
let image_info = GblEfiImageInfo { ImageType: image_type, SizeBytes: size.get() };
let efi_image_buffer = self
.efi_entry
.system_table()
.boot_services()
.find_first_and_open::<GblImageLoadingProtocol>()?
.get_buffer(&image_info)?;
// EfiImageBuffer -> ImageBuffer
// Make sure not to drop efi_image_buffer since we transferred ownership to ImageBuffer
let buffer = efi_image_buffer.take();
Ok(ImageBuffer::new(buffer))
}
/// Get buffer for partition loading and verification.
/// Uses provided allocator.
///
/// # Arguments
/// * `efi_entry` - EFI entry
/// * `image_name` - image name to differentiate the buffer properties
/// * `size` - requested buffer size
///
/// # Return
/// * Ok(ImageBuffer) - Return buffer for partition loading and verification.
/// * Err(_) - on error
// SAFETY:
// Allocated buffer is leaked intentionally. ImageBuffer is assumed to reference static memory.
// ImageBuffer is not expected to be released, and is allocated to hold data necessary for next
// boot stage (kernel boot). All allocated buffers are expected to be used by kernel.
fn allocate_image_buffer(image_name: &str, size: NonZeroUsize) -> Result<ImageBuffer<'static>> {
const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024;
const FDT_ALIGNMENT: usize = 8;
const BOOTCMD_SIZE: usize = 16 * 1024;
let size = match image_name {
"boot" => size.get(),
"ramdisk" => (SafeNum::from(size.get()) + BOOTCMD_SIZE).try_into()?,
_ => size.get(),
};
// Check for `from_raw_parts_mut()` safety requirements.
assert!(size < isize::MAX.try_into().unwrap());
let align = match image_name {
"boot" => KERNEL_ALIGNMENT,
"fdt" => FDT_ALIGNMENT,
_ => 1,
};
if size == 0 {
return Err(Error::Other(Some("allocate_image_buffer() expects non zero size")));
}
let layout = Layout::from_size_align(size, align).or(Err(Error::InvalidAlignment))?;
// SAFETY:
// `layout.size()` is checked to be not zero.
let ptr = unsafe { alloc(layout) } as *mut MaybeUninit<u8>;
if ptr.is_null() {
handle_alloc_error(layout);
}
// SAFETY:
// `ptr` is checked to be not Null.
// `ptr` is a valid pointer to start of a single memory region of `size`-bytes because it
// was just returned by alloc. Buffer alignment requirement for u8 is 1-byte which is
// always the case.
// `alloc()` makes sure there is no other allocation of the same memory region until
// current one is released.
// `size` is a valid size of the memory region since `alloc()` succeeded.
//
// Total size of buffer is not greater than `isize::MAX` since it is checked at the
// beginning of the function.
//
// `ptr + size` doesn't wrap since it is returned from alloc and it didn't fail.
let buf = unsafe { from_raw_parts_mut(ptr, size) };
Ok(ImageBuffer::new(buf))
}
}
impl Write for Ops<'_, '_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
efi_print!(self.efi_entry, "{}", s);
Ok(())
}
}
impl<'a, 'b> GblOps<'b> for Ops<'a, 'b>
where
Self: 'b,
{
type PartitionBlockIo = &'b mut EfiBlockDeviceIo<'a>;
fn console_out(&mut self) -> Option<&mut dyn Write> {
Some(self)
}
/// UEFI console uses \r\n newline.
fn console_newline(&self) -> &'static str {
"\r\n"
}
fn should_stop_in_fastboot(&mut self) -> Result<bool> {
// TODO(b/349829690): also query GblSlotProtocol.get_boot_reason() for board-specific
// fastboot triggers.
// TODO(b/366520234): Switch to use GblSlotProtocol.should_stop_in_fastboot once available.
match self.get_efi_fdt_prop("gbl", c"stop-in-fastboot") {
Some(v) => return Ok(*v.get(0).unwrap_or(&0) == 1),
_ => {}
}
efi_println!(self.efi_entry, "Press Backspace to enter fastboot");
let found = wait_key_stroke(
self.efi_entry,
|key| key.unicode_char == 0x08 || (key.unicode_char == 0x0 && key.scan_code == 0x08),
2000,
);
if matches!(found, Ok(true)) {
efi_println!(self.efi_entry, "Backspace pressed, entering fastboot");
}
// TODO(b/358377120): pass the UEFI error when liberror::Error support lands.
found.or(Err(Error::Other(Some("wait for key stroke error"))))
}
fn preboot(&mut self, _: BootImages) -> Result<()> {
unimplemented!();
}
/// Reboots the system into the last set boot mode.
fn reboot(&mut self) {
self.efi_entry.system_table().runtime_services().cold_reset();
}
fn partitions(&self) -> Result<&'b [PartitionBlockDevice<'b, Self::PartitionBlockIo>]> {
Ok(self.partitions)
}
fn zircon_add_device_zbi_items(
&mut self,
container: &mut ZbiContainer<&mut [u8]>,
) -> Result<()> {
// TODO(b/353272981): Switch to use OS configuration protocol once it is implemented on
// existing platforms such as VIM3.
Ok(match self.get_efi_fdt_prop("zircon", c"zbi-blob") {
Some(blob) => container.extend_unaligned(blob).map_err(|_| "Failed to append ZBI")?,
_ => efi_println!(self.efi_entry, "No device ZBI items.\r\n"),
})
}
fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> {
// Switches to use get_image_buffer once available.
const DEFAULT_SIZE: usize = 4096;
if self.zbi_bootloader_files_buffer.is_empty() {
self.zbi_bootloader_files_buffer.resize(DEFAULT_SIZE, 0);
}
Some(self.zbi_bootloader_files_buffer.as_mut_slice())
}
fn load_slot_interface<'c>(
&'c mut self,
_: &'c mut dyn FnMut(&mut [u8]) -> Result<()>,
_: BootToken,
) -> GblResult<Cursor<'c>> {
unimplemented!();
}
fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> {
// TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available.
Ok(true)
}
fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64> {
// TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available.
Ok(0)
}
fn avb_write_rollback_index(
&mut self,
_rollback_index_location: usize,
_index: u64,
) -> AvbIoResult<()> {
// TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available.
Ok(())
}
fn avb_cert_read_permanent_attributes(
&mut self,
attributes: &mut CertPermanentAttributes,
) -> AvbIoResult<()> {
// TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available.
let perm_attr = self
.get_efi_fdt_prop("gbl", c"avb-cert-permanent-attributes")
.ok_or(AvbIoError::NotImplemented)?;
attributes.as_bytes_mut().clone_from_slice(perm_attr);
Ok(())
}
fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> {
// TODO(b/337846185): Switch to use GBL Verified Boot EFI protocol when available.
let hash = self
.get_efi_fdt_prop("gbl", c"avb-cert-permanent-attributes-hash")
.ok_or(AvbIoError::NotImplemented)?;
Ok(hash.try_into().map_err(|_| AvbIoError::Io)?)
}
fn get_image_buffer<'c>(
&mut self,
image_name: &str,
size: NonZeroUsize,
) -> GblResult<ImageBuffer<'c>> {
self.get_buffer_image_loading(image_name, size)
.or(Self::allocate_image_buffer(image_name, size)
.map_err(|e| libgbl::IntegrationError::UnificationError(e)))
}
fn get_custom_device_tree(&mut self) -> Option<&'a [u8]> {
// On Cuttlefish, the device tree comes from the UEFI config tables.
// TODO(b/353272981): once we've settled on the device tree UEFI protocol, use that
// instead to provide a Cuttlefish-specific backend.
Some(get_efi_fdt(&self.efi_entry)?.1)
}
fn fixup_os_commandline<'c>(
&mut self,
commandline: &CStr,
fixup_buffer: &'c mut [u8],
) -> Result<Option<&'c str>> {
Ok(
match self
.efi_entry
.system_table()
.boot_services()
.find_first_and_open::<GblOsConfigurationProtocol>()
{
Ok(protocol) => {
protocol.fixup_kernel_commandline(commandline, fixup_buffer)?;
Some(CStr::from_bytes_until_nul(&fixup_buffer[..])?.to_str()?)
}
_ => None,
},
)
}
fn fixup_bootconfig<'c>(
&mut self,
bootconfig: &[u8],
fixup_buffer: &'c mut [u8],
) -> Result<Option<&'c [u8]>> {
Ok(
match self
.efi_entry
.system_table()
.boot_services()
.find_first_and_open::<GblOsConfigurationProtocol>()
{
Ok(protocol) => {
let fixup_size = protocol.fixup_bootconfig(bootconfig, fixup_buffer)?;
Some(&fixup_buffer[..fixup_size])
}
_ => None,
},
)
}
fn fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<()> {
if let Ok(protocol) =
self.efi_entry.system_table().boot_services().find_first_and_open::<DtFixupProtocol>()
{
protocol.fixup(device_tree)?;
}
Ok(())
}
fn select_device_trees(
&mut self,
components_registry: &mut DeviceTreeComponentsRegistry,
) -> Result<()> {
match self
.efi_entry
.system_table()
.boot_services()
.find_first_and_open::<GblOsConfigurationProtocol>()
{
Ok(protocol) => {
// Protocol detected, convert to UEFI types.
let mut uefi_components: ArrayVec<_, MAXIMUM_DEVICE_TREE_COMPONENTS> =
components_registry
.components()
.map(|component| dt_component_to_efi_dt(component))
.collect();
protocol.select_device_trees(&mut uefi_components[..])?;
// Propagate selections to the components_registry.
components_registry
.components_mut()
.zip(uefi_components.iter_mut())
.enumerate()
.for_each(|(index, (component, uefi_component))| {
if uefi_component.selected {
efi_println!(
self.efi_entry,
"Device tree component at index {} got selected by UEFI call. \
Source: {}",
index,
component.source
);
}
component.selected = uefi_component.selected;
});
Ok(())
}
_ => components_registry.autoselect(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use efi_mocks::MockEfi;
use mockall::predicate::eq;
#[test]
fn ops_write_trait() {
let mut mock_efi = MockEfi::new();
mock_efi.con_out.expect_write_str().with(eq("foo bar")).return_const(Ok(()));
let installed = mock_efi.install();
let mut ops = Ops::new(installed.entry(), &[]);
assert!(write!(&mut ops, "{} {}", "foo", "bar").is_ok());
}
}