blob: b89547d064dc6b6aac79b8921661dd1be5d90d60 [file] [log] [blame]
// 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;
#[cfg(test)]
extern crate static_assertions;
use crate::digest::{Algorithm, Context};
use crate::error::{Error, Result as GblResult};
#[cfg(feature = "sw_digest")]
use crate::sw_digest::SwContext;
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
use core::{
fmt::{Debug, Write},
ptr::NonNull,
result::Result,
};
use gbl_storage::{
required_scratch_size, AsBlockDevice, AsMultiBlockDevices, BlockDevice, BlockIo,
};
use safemath::SafeNum;
use super::slots;
/// `AndroidBootImages` contains references to loaded images for booting Android.
pub struct AndroidBootImages<'a> {
pub kernel: &'a mut [u8],
pub ramdisk: &'a mut [u8],
pub fdt: &'a mut [u8],
}
/// `FuchsiaBootImages` contains references to loaded images for booting Zircon.
pub struct FuchsiaBootImages<'a> {
pub zbi_kernel: &'a mut [u8],
pub zbi_items: &'a mut [u8],
}
/// `BootImages` contains images for booting Android/Zircon kernel.
pub enum BootImages<'a> {
Android(AndroidBootImages<'a>),
Fuchsia(FuchsiaBootImages<'a>),
}
/// `GblOpsError` is the error type returned by required methods in `GblOps`.
#[derive(Default, Debug)]
pub struct GblOpsError(Option<&'static str>);
// 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 {
/// Digest context type
type Context: Context;
/// Iterates block devices on the platform.
///
/// For each block device, implementation should call `f` with its 1) `BlockIo` trait
/// implementation, 2) a unique u64 ID and 3) maximum number of gpt entries. If the maximum
/// entries is 0, it is considered that the block should not use GPT.
///
/// The list of block devices and visit order should remain the same for the life time of the
/// object that implements this trait. If this can not be met due to media change, error should
/// be returned. Dynamic media change is not supported for now.
fn visit_block_devices(
&mut self,
f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64),
) -> Result<(), GblOpsError>;
/// Prints a ASCII character to the platform console.
fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError>;
/// 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, GblOpsError>;
/// Platform specific kernel boot implementation.
///
/// Implementation is not expected to return on success.
fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>;
/// Create digest object to use for hash computations.
///
/// Context interface allows to update value adding more data to process.
/// # Arguments
///
/// * algorithm - algorithm to use for hash computation.
fn new_digest(&self, algorithm: Algorithm) -> Self::Context {
Context::new(algorithm)
}
/// Calculate digest of provided data with requested algorithm. Single use unlike [new_digest]
/// flow.
fn digest(&self, algorithm: Algorithm, data: &[u8]) -> <Self::Context as Context>::Digest {
let mut ctx = self.new_digest(algorithm);
ctx.update(data);
ctx.finish()
}
/// 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, impl slots::Manager>,
) -> GblResult<()> {
Err(Error::NotImplemented.into())
}
/// 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<()> {
let _c_string = CString::new(s);
Err(Error::NotImplemented.into())
}
/// Load and initialize a slot manager and return a cursor over the manager on success.
fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice, M: slots::Manager>(
&mut self,
block_device: &'b mut B,
boot_token: slots::BootToken,
) -> GblResult<slots::Cursor<'b, B, M>> {
Err(Error::OperationProhibited.into())
}
/// Computes the sum of required scratch size for all block devices.
fn required_scratch_size(&mut self) -> GblResult<usize> {
let mut total = SafeNum::ZERO;
let mut res = Ok(());
self.visit_block_devices(&mut |io, id, max_gpt_entries| {
res = (|| {
total += required_scratch_size(io, max_gpt_entries).unwrap();
Ok(())
})();
})?;
let total = usize::try_from(total).map_err(|e| e.into());
res.and(total)
}
}
/// `GblUtils` takes a reference to `GblOps` and implements various traits.
pub(crate) struct GblUtils<'a, 'b, T: GblOps> {
ops: &'a mut T,
scratch: &'b mut [u8],
}
impl<'a, 'b, T: GblOps> GblUtils<'a, 'b, T> {
/// Create a new instance with user provided scratch buffer.
///
/// # Args
///
/// * `ops`: A reference to a `GblOps`,
/// * `scratch`: A scratch buffer.
///
/// # Returns
///
/// Returns a new instance and the trailing unused part of the input scratch buffer.
pub fn new(ops: &'a mut T, scratch: &'b mut [u8]) -> GblResult<(Self, &'b mut [u8])> {
let total_scratch_size = ops.required_scratch_size()?;
let (scratch, remaining) = scratch.split_at_mut(total_scratch_size);
Ok((Self { ops: ops, scratch: scratch }, remaining))
}
}
impl<T: GblOps> AsMultiBlockDevices for GblUtils<'_, '_, T> {
fn for_each(
&mut self,
f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
) -> core::result::Result<(), Option<&'static str>> {
let mut scratch_offset = SafeNum::ZERO;
self.ops
.visit_block_devices(&mut |io, id, max_gpt_entries| {
// Not expected to fail as `Self::new()` should have checked any overflow.
let scratch_size: usize = required_scratch_size(io, max_gpt_entries).unwrap();
let scratch =
&mut self.scratch[scratch_offset.try_into().unwrap()..][..scratch_size];
scratch_offset += scratch_size;
f(&mut BlockDevice::new(io, scratch, max_gpt_entries), id);
})
.map_err(|v| v.0)
}
}
impl<T: GblOps> Write for GblUtils<'_, '_, T> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for ch in s.as_bytes() {
self.ops.console_put_char(*ch).map_err(|_| core::fmt::Error {})?;
}
Ok(())
}
}
/// Default [GblOps] implementation that returns errors and does nothing.
#[derive(Debug)]
pub struct DefaultGblOps {}
#[cfg(feature = "sw_digest")]
impl GblOps for DefaultGblOps {
type Context = SwContext;
}
#[cfg(test)]
static_assertions::const_assert_eq!(core::mem::size_of::<DefaultGblOps>(), 0);
impl DefaultGblOps {
/// Create new DefaultGblOps object
pub fn new() -> &'static mut Self {
let mut ptr: NonNull<Self> = NonNull::dangling();
// SAFETY: Self is a ZST, asserted above, and ptr is appropriately aligned and nonzero by
// NonNull::dangling()
unsafe { ptr.as_mut() }
}
}