blob: 3ddb0e992bd2dc003bdc0f3d5e282e37fc90b5dc [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.
//! GblOps trait that defines GBL callbacks.
//!
#[cfg(feature = "alloc")]
extern crate alloc;
pub use crate::image_buffer::ImageBuffer;
use crate::{
error::Result as GblResult,
partition::{
check_part_unique, read_unique_partition, write_unique_partition, PartitionBlockDevice,
},
};
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
use core::{
fmt::{Debug, Write},
num::NonZeroUsize,
result::Result,
};
use gbl_async::block_on;
use gbl_storage::{BlockIoAsync, BlockIoNull};
// Re-exports of types from other dependencies that appear in the APIs of this library.
pub use avb::{
CertPermanentAttributes, IoError as AvbIoError, IoResult as AvbIoResult, SHA256_DIGEST_SIZE,
};
use liberror::Error;
pub use zbi::ZbiContainer;
use super::slots;
/// `AndroidBootImages` contains references to loaded images for booting Android.
pub struct AndroidBootImages<'a> {
/// Kernel image.
pub kernel: &'a mut [u8],
/// Ramdisk to pass to the kernel.
pub ramdisk: &'a mut [u8],
/// FDT To pass to the kernel.
pub fdt: &'a mut [u8],
}
/// `FuchsiaBootImages` contains references to loaded images for booting Zircon.
pub struct FuchsiaBootImages<'a> {
/// Kernel image.
pub zbi_kernel: &'a mut [u8],
/// ZBI container with items to pass to the kernel.
pub zbi_items: &'a mut [u8],
}
/// Images required to boot the supported kernels.
pub enum BootImages<'a> {
/// Android boot images.
Android(AndroidBootImages<'a>),
/// Fuchsia boot images.
Fuchsia(FuchsiaBootImages<'a>),
}
// https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// should we use traits for this? or optional/box FnMut?
//
/* TODO: b/312612203 - needed callbacks:
missing:
- validate_public_key_for_partition: None,
- key management => atx extension in callback => atx_ops: ptr::null_mut(), // support optional ATX.
*/
/// Trait that defines callbacks that can be provided to Gbl.
pub trait GblOps<'a>
where
Self: 'a,
{
/// Type that implements `BlockIoAsync` for the array of `PartitionBlockDevice` returned by]
/// `partitions()`.
type PartitionBlockIo: BlockIoAsync = BlockIoNull;
/// Gets a console for logging messages.
fn console_out(&mut self) -> Option<&mut dyn Write>;
/// This method can be used to implement platform specific mechanism for deciding whether boot
/// should abort and enter Fastboot mode.
fn should_stop_in_fastboot(&mut self) -> Result<bool, Error>;
/// Platform specific processing of boot images before booting.
fn preboot(&mut self, boot_images: BootImages) -> Result<(), Error>;
/// Returns the list of partition block devices.
///
/// Notes that the return slice doesn't capture the life time of `&self`, meaning that the slice
/// reference must be producible without borrowing the `GblOps`. This is intended and necessary
/// in order to parallelize fastboot flash, download and other commands. For implementation,
/// this typically means that the `GblOps` object should hold a reference of the array instead
/// of owning it.
fn partitions(&self) -> Result<&'a [PartitionBlockDevice<'a, Self::PartitionBlockIo>], Error>;
/// Reads data from a partition.
async fn read_from_partition(
&mut self,
part: &str,
off: u64,
out: &mut [u8],
) -> Result<(), Error> {
read_unique_partition(self.partitions()?, part, off, out).await
}
/// Reads data from a partition synchronously.
fn read_from_partition_sync(
&mut self,
part: &str,
off: u64,
out: &mut [u8],
) -> Result<(), Error> {
block_on(self.read_from_partition(part, off, out))
}
/// Writes data to a partition.
async fn write_to_partition(
&mut self,
part: &str,
off: u64,
data: &mut [u8],
) -> Result<(), Error> {
write_unique_partition(self.partitions()?, part, off, data).await
}
/// Writes data to a partition synchronously.
fn write_to_partition_sync(
&mut self,
part: &str,
off: u64,
data: &mut [u8],
) -> Result<(), Error> {
block_on(self.write_to_partition(part, off, data))
}
/// Returns the size of a partiiton. Returns Ok(None) if partition doesn't exist.
fn partition_size(&mut self, part: &str) -> Result<Option<u64>, Error> {
match check_part_unique(self.partitions()?, part) {
Ok((_, p)) => Ok(Some(p.size()?)),
Err(Error::NotFound) => Ok(None),
Err(e) => Err(e),
}
}
/// Adds device specific ZBI items to the given `container`
fn zircon_add_device_zbi_items(
&mut self,
container: &mut ZbiContainer<&mut [u8]>,
) -> Result<(), Error>;
// TODO(b/334962570): figure out how to plumb ops-provided hash implementations into
// libavb. The tricky part is that libavb hashing APIs are global with no way to directly
// correlate the implementation to a particular [GblOps] object, so we'll probably have to
// create a [Context] ahead of time and store it globally for the hashing APIs to access.
// However this would mean that [Context] must be a standalone object and cannot hold a
// reference to [GblOps], which may restrict implementations.
// fn new_digest(&self) -> Option<Self::Context>;
/// Callback for when fastboot mode is requested.
// Nevertype could be used here when it is stable https://github.com/serde-rs/serde/issues/812
fn do_fastboot<B: gbl_storage::AsBlockDevice>(
&self,
cursor: &mut slots::Cursor<B>,
) -> GblResult<()>;
/// TODO: b/312607649 - placeholder interface for Gbl specific callbacks that uses alloc.
#[cfg(feature = "alloc")]
fn gbl_alloc_extra_action(&mut self, s: &str) -> GblResult<()> {
unimplemented!();
}
/// Load and initialize a slot manager and return a cursor over the manager on success.
fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice>(
&'b mut self,
block_device: &'b mut B,
boot_token: slots::BootToken,
) -> GblResult<slots::Cursor<'b, B>>;
// The following is a selective subset of the interfaces in `avb::Ops` and `avb::CertOps` needed
// by GBL's usage of AVB. The rest of the APIs are either not relevant to or are implemented and
// managed by GBL APIs.
/// Returns if device is in an unlocked state.
///
/// The interface has the same requirement as `avb::Ops::read_is_device_unlocked`.
fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool>;
/// Reads the AVB rollback index at the given location
///
/// The interface has the same requirement as `avb::Ops::read_rollback_index`.
fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64>;
/// Writes the AVB rollback index at the given location.
///
/// The interface has the same requirement as `avb::Ops::write_rollback_index`.
fn avb_write_rollback_index(
&mut self,
_rollback_index_location: usize,
_index: u64,
) -> AvbIoResult<()>;
/// Reads AVB certificate extension permanent attributes.
///
/// The interface has the same requirement as `avb::CertOps::read_permanent_attributes`.
fn avb_cert_read_permanent_attributes(
&mut self,
attributes: &mut CertPermanentAttributes,
) -> AvbIoResult<()>;
/// Reads AVB certificate extension permanent attributes hash.
///
/// The interface has the same requirement as `avb::CertOps::read_permanent_attributes_hash`.
fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]>;
/// Get buffer for specific image of requested size.
fn get_image_buffer<'c>(
&mut self,
image_name: &str,
size: NonZeroUsize,
) -> GblResult<ImageBuffer<'c>>;
}
/// Default [GblOps] implementation that returns errors and does nothing.
#[derive(Debug)]
pub struct DefaultGblOps {}
impl<'a> GblOps<'a> for DefaultGblOps
where
Self: 'a,
{
fn console_out(&mut self) -> Option<&mut dyn Write> {
unimplemented!();
}
fn should_stop_in_fastboot(&mut self) -> Result<bool, Error> {
unimplemented!();
}
fn preboot(&mut self, boot_images: BootImages) -> Result<(), Error> {
unimplemented!();
}
fn partitions(&self) -> Result<&'a [PartitionBlockDevice<'a, Self::PartitionBlockIo>], Error> {
unimplemented!();
}
fn zircon_add_device_zbi_items(
&mut self,
container: &mut ZbiContainer<&mut [u8]>,
) -> Result<(), Error> {
unimplemented!();
}
fn do_fastboot<B: gbl_storage::AsBlockDevice>(
&self,
cursor: &mut slots::Cursor<B>,
) -> GblResult<()> {
unimplemented!();
}
fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice>(
&'b mut self,
block_device: &'b mut B,
boot_token: slots::BootToken,
) -> GblResult<slots::Cursor<'b, B>> {
unimplemented!();
}
fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> {
unimplemented!();
}
fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64> {
unimplemented!();
}
fn avb_write_rollback_index(
&mut self,
_rollback_index_location: usize,
_index: u64,
) -> AvbIoResult<()> {
unimplemented!();
}
fn avb_cert_read_permanent_attributes(
&mut self,
_attributes: &mut CertPermanentAttributes,
) -> AvbIoResult<()> {
unimplemented!();
}
fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> {
unimplemented!();
}
fn get_image_buffer<'c>(
&mut self,
image_name: &str,
size: NonZeroUsize,
) -> GblResult<ImageBuffer<'c>> {
Err(Error::Unsupported.into())
}
}
/// Prints with `GblOps::console_out()`.
#[macro_export]
macro_rules! gbl_print {
( $ops:expr, $( $x:expr ),* $(,)? ) => {
{
match $ops.console_out() {
Some(v) => write!(v, $($x,)*).unwrap(),
_ => {}
}
}
};
}