diff options
author | Yecheng Zhao <zyecheng@google.com> | 2024-04-23 19:08:02 +0000 |
---|---|---|
committer | Yecheng Zhao <zyecheng@google.com> | 2024-05-01 19:09:09 +0000 |
commit | 611dbc99bb47faafcd5d8356f455d122660e80d5 (patch) | |
tree | 1d31309953278665c00565cfae14d86c99bc14ba | |
parent | 3b27c6f519f4ca5a96466129046397d1f79574f8 (diff) | |
download | libbootloader-611dbc99bb47faafcd5d8356f455d122660e80d5.tar.gz |
Put A/B/R logic in a separate libabr
Adds a libabr library to host Fuchsia A/B/R logic without the gbl
dependencies. This will be the first candidate for code share with
Fuchsia and will be replacing the Fuchsia libabr C library. libgbl may
also use the library to ensure backward compatibility for existing
devices that use the original libabr.
The library is mostly a line to line translation, including code
comments, from the Fuchsia C libabr. This makes sure that we are keeping
all subtle considerations and bug fixes that we have accumulated over
the past.
The library provides a C static library build target that exports the
same C interfaces as C libabr. It currently passes all upstream tests
in "src/firmware/lib/abr/test/libabr_test.cc". The only exceptions are
the `read/write_abr_metadata_custom` usage which we'll deprecate since
atlas is no longer relevant.
Bug: 336318818
Change-Id: Ifa179ef6cd50a887dacefd8b92ac790918d34fe0
-rw-r--r-- | gbl/libabr/BUILD | 56 | ||||
-rw-r--r-- | gbl/libabr/build/fuchsia/BUILD.gn | 39 | ||||
-rw-r--r-- | gbl/libabr/src/c_staticlib.rs | 413 | ||||
-rw-r--r-- | gbl/libabr/src/lib.rs | 662 | ||||
-rw-r--r-- | gbl/libabr/src/utils.rs | 87 | ||||
-rw-r--r-- | gbl/tests/BUILD | 1 |
6 files changed, 1258 insertions, 0 deletions
diff --git a/gbl/libabr/BUILD b/gbl/libabr/BUILD new file mode 100644 index 0000000..aa1eef1 --- /dev/null +++ b/gbl/libabr/BUILD @@ -0,0 +1,56 @@ +# Copyright (C) 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. + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_static_library", "rust_test") + +package( + default_visibility = ["//visibility:public"], +) + +rust_library( + name = "libabr", + srcs = ["src/lib.rs"], + crate_name = "abr", + edition = "2021", +) + +rust_test( + name = "libabr_test", + crate = ":libabr", +) + +rust_static_library( + name = "libabr_c", + srcs = [ + "src/c_staticlib.rs", + "src/utils.rs", + ], + crate_name = "abr_c", + crate_root = "src/c_staticlib.rs", + edition = "2021", + deps = [":libabr"], +) + +rust_test( + name = "libabr_c_test", + crate = ":libabr_c", +) + +test_suite( + name = "libabr_tests", + tests = [ + ":libabr_c_test", + ":libabr_test", + ], +) diff --git a/gbl/libabr/build/fuchsia/BUILD.gn b/gbl/libabr/build/fuchsia/BUILD.gn new file mode 100644 index 0000000..78cbe61 --- /dev/null +++ b/gbl/libabr/build/fuchsia/BUILD.gn @@ -0,0 +1,39 @@ +# Copyright (C) 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. + +import("//build/rust/rustc_library.gni") +import("//build/rust/rustc_staticlib.gni") + +rustc_library("libabr") { + edition = "2021" + name = "abr" + source_root = "../../src/lib.rs" + sources = [ "../../src/lib.rs" ] +} + +config("panic_as_abort") { + rustflags = [ "-Cpanic=abort" ] +} + +rustc_staticlib("libabr_c") { + edition = "2021" + name = "abr" + source_root = "../../src/c_staticlib.rs" + sources = [ + "../../src/c_staticlib.rs", + "../../src/utils.rs", + ] + deps = [ ":libabr" ] + configs += [":panic_as_abort"] +} diff --git a/gbl/libabr/src/c_staticlib.rs b/gbl/libabr/src/c_staticlib.rs new file mode 100644 index 0000000..0110580 --- /dev/null +++ b/gbl/libabr/src/c_staticlib.rs @@ -0,0 +1,413 @@ +// 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. + +//! This file provides C interface wrappers of libabr APIs. + +#![cfg_attr(not(test), no_std)] + +use abr::{ + get_and_clear_one_shot_flag, get_boot_slot, get_slot_info, get_slot_last_marked_active, + mark_slot_active, mark_slot_successful, mark_slot_unbootable, set_one_shot_bootloader, + set_one_shot_recovery, AbrResult, Error, Ops, SlotIndex, SlotInfo as AbrSlotInfo, SlotState, +}; +use core::{ + ffi::{c_char, c_uint, c_void}, + fmt::Write, +}; + +pub mod utils; + +pub const ABR_RESULT_OK: c_uint = 0; +pub const ABR_RESULT_ERR_IO: c_uint = 1; +pub const ABR_RESULT_ERR_INVALID_DATA: c_uint = 2; +pub const ABR_RESULT_ERR_UNSUPPORTED_VERSION: c_uint = 3; + +// ABR system dependencies. +// +// These correspond to the definitions in Fuchsia upstream header +// "src/firmware/lib/abr/include/lib/abr/sysdeps.h", which will eventually migrate over. +extern "C" { + /// Prints out a NULL-terminated string. + pub fn AbrPrint(message: *const c_char); + + /// Aborts the program or reboots the device if |abort| is not implemented. + pub fn AbrAbort(); +} + +/// A helper to print an ASCII character via `AbrPrint()`. +fn abr_print_ascii_char(ch: u8) { + let s = [ch, 0]; + // SAFETY: + // * `s` is a valid buffer + // * `s` is for input only and will not be retained by the function. + unsafe { AbrPrint(s.as_ptr() as _) } +} + +/// A helper structure that implements formatted write using `AbrPrint()`. +struct AbrPrintSysdeps {} + +impl Write for AbrPrintSysdeps { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + if s.is_ascii() { + s.as_bytes().iter().for_each(|v| abr_print_ascii_char(*v)); + } + Ok(()) + } +} + +/// A panic handler is needed when building as a static library. We simply call into +/// the AbrAbort() system dependency. +#[cfg(not(test))] +#[panic_handler] +fn panic(panic: &core::panic::PanicInfo<'_>) -> ! { + write!(AbrPrintSysdeps {}, "libabr panics! {}", panic).unwrap(); + // SAFETY: Call to external C function. The function simply aborts/reboots the system. + unsafe { AbrAbort() }; + unreachable!() +} + +/// This corresponds to the `AbrOps` C definition in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/ops.h", which will eventually migrate over. +/// +/// typedef struct AbrOps { +/// void* context; +/// bool (*read_abr_metadata)(void* context, size_t size, uint8_t* buffer); +/// bool (*write_abr_metadata)(void* context, const uint8_t* buffer, size_t size); +/// } AbrOps; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct AbrOps { + pub context: *mut c_void, + pub read_abr_metadata: + Option<unsafe extern "C" fn(context: *mut c_void, size: usize, buffer: *mut u8) -> bool>, + pub write_abr_metadata: + Option<unsafe extern "C" fn(context: *mut c_void, buffer: *const u8, size: usize) -> bool>, +} + +/// `AbrOpsSafe` wraps a reference to `AbrOps` and is created by an unsafe constructor that +/// establishes necessary safety invariants on `AbrOps`. +struct AbrOpsSafe<'a> { + ops: &'a AbrOps, + log: AbrPrintSysdeps, +} + +impl<'a> AbrOpsSafe<'a> { + /// Creates a new instance from a reference to `AbrOps`. + /// + /// # Safety + /// + /// * Caller must make sure that `ops.context` is either not used, or points to a valid and + /// correct type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. + unsafe fn new(ops: &'a AbrOps) -> Self { + Self { ops, log: AbrPrintSysdeps {} } + } +} + +type AbrSlotIndex = c_uint; + +impl Ops for AbrOpsSafe<'_> { + fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<(), Option<&'static str>> { + let read_abr_metadata = + self.ops.read_abr_metadata.ok_or(Some("Missing read_abr_metadata() method"))?; + // SAFETY: + // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, or + // a valid pointer to a correct type of object used by `self.ops.read_abr_metadata`. + // * `out` is a valid buffer + // * `out` is for reading data only and will not be retained by the function. + match unsafe { read_abr_metadata(self.ops.context, out.len(), out.as_mut_ptr() as _) } { + false => Err(Some("read_abr_metadata() failed")), + _ => Ok(()), + } + } + + fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<(), Option<&'static str>> { + let write_abr_metadata = + self.ops.write_abr_metadata.ok_or(Some("Missing write_abr_metadata() method"))?; + // SAFETY: + // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, or + // a valid pointer to a correct type of object used by `self.ops.write_abr_metadata`. + // * `data` is a valid buffer. + // * `data` is for input only and will not be retained by the function. + match unsafe { write_abr_metadata(self.ops.context, data.as_ptr() as _, data.len()) } { + false => Err(Some("write_abr_metadata() failed")), + _ => Ok(()), + } + } + + fn console(&mut self) -> Option<&mut dyn core::fmt::Write> { + Some(&mut self.log) + } +} + +/// A helper that extracts the return value and maps the result to an integer A/B/R result code. +fn unpack_result<T: Into<O>, O>(res: AbrResult<T>, val: &mut O) -> c_uint { + match res { + Err(e) => match e { + Error::BadMagic | Error::BadChecksum | Error::InvalidData => { + ABR_RESULT_ERR_INVALID_DATA + } + Error::UnsupportedVersion => ABR_RESULT_ERR_UNSUPPORTED_VERSION, + Error::OpsError(_) => ABR_RESULT_ERR_IO, + }, + Ok(v) => { + *val = v.into(); + ABR_RESULT_OK + } + } +} + +/// C interface wrapper of `abr::get_boot_slot()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +/// * Caller must make sure to pass either a NULL or valid pointer for `is_slot_marked_successful`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrGetBootSlot( + abr_ops: *const AbrOps, + update_metadata: bool, + is_slot_marked_successful: *mut bool, +) -> AbrSlotIndex { + let mut abr_ops = AbrOpsSafe::new(abr_ops.as_ref().unwrap()); + let (slot_index, successful) = get_boot_slot(&mut abr_ops, update_metadata); + match is_slot_marked_successful.as_mut() { + Some(v) => *v = successful, + _ => {} + }; + slot_index.into() +} + +// NULL terminated strings for slot suffixes. +const SLOT_A_SUFFIX: &[u8] = b"_a\0"; +const SLOT_B_SUFFIX: &[u8] = b"_b\0"; +const SLOT_R_SUFFIX: &[u8] = b"_r\0"; +const SLOT_SUFFIX_INVALID: &[u8] = b"\0"; + +/// C interface for getting slot suffix. +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn AbrGetSlotSuffix(slot_index: AbrSlotIndex) -> *const c_char { + match slot_index.try_into() { + Ok(SlotIndex::A) => &SLOT_A_SUFFIX, + Ok(SlotIndex::B) => &SLOT_B_SUFFIX, + Ok(SlotIndex::R) => &SLOT_R_SUFFIX, + Err(_) => &SLOT_SUFFIX_INVALID, + } + .as_ptr() as _ +} + +/// C interface wrapper of `abr::mark_slot_active()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrMarkSlotActive( + abr_ops: *const AbrOps, + slot_index: AbrSlotIndex, +) -> c_uint { + let slot_index = match slot_index.try_into() { + Ok(v) => v, + Err(_) => return ABR_RESULT_ERR_INVALID_DATA, + }; + unpack_result( + mark_slot_active(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index), + &mut (), + ) +} + +/// C interface wrapper of `abr::get_slot_last_marked_active()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops` and `out_slot`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrGetSlotLastMarkedActive( + abr_ops: *const AbrOps, + out_slot: *mut AbrSlotIndex, +) -> c_uint { + unpack_result( + get_slot_last_marked_active(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap())), + out_slot.as_mut().unwrap(), + ) +} + +/// C interface wrapper of `abr::mark_slot_unbootable()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrMarkSlotUnbootable( + abr_ops: *const AbrOps, + slot_index: AbrSlotIndex, +) -> c_uint { + let slot_index = match slot_index.try_into() { + Ok(v) => v, + Err(_) => return ABR_RESULT_ERR_INVALID_DATA, + }; + unpack_result( + mark_slot_unbootable(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index), + &mut (), + ) +} + +/// C interface wrapper of `abr::mark_slot_successful()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrMarkSlotSuccessful( + abr_ops: *const AbrOps, + slot_index: AbrSlotIndex, +) -> c_uint { + let slot_index = match slot_index.try_into() { + Ok(v) => v, + Err(_) => return ABR_RESULT_ERR_INVALID_DATA, + }; + unpack_result( + mark_slot_successful(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index), + &mut (), + ) +} + +/// `SlotInfo` contains the current state of a A/B/R slot. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SlotInfo { + /// Whether the slot is expected to be bootable. + pub is_bootable: bool, + /// Whether the slot is the highest priority A/B slot. + pub is_active: bool, + /// Whether the slot is currently marked successful. + pub is_marked_successful: bool, + /// If not marked successful, this represents the number of attempts left for booting this slot. + pub num_tries_remaining: u8, +} + +impl From<AbrSlotInfo> for SlotInfo { + fn from(val: abr::SlotInfo) -> Self { + let is_marked_successful = matches!(val.state, SlotState::Successful); + let num_tries_remaining = match val.state { + SlotState::Bootable(v) => v, + _ => 0, + }; + Self { + is_bootable: is_marked_successful || num_tries_remaining > 0, + is_active: val.is_active, + is_marked_successful, + num_tries_remaining, + } + } +} + +/// C interface wrapper of `abr::get_slot_info()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops` and 'info'. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrGetSlotInfo( + abr_ops: *const AbrOps, + slot_index: AbrSlotIndex, + info: *mut SlotInfo, +) -> c_uint { + let slot_index = match slot_index.try_into() { + Ok(v) => v, + Err(_) => return ABR_RESULT_ERR_INVALID_DATA, + }; + unpack_result( + get_slot_info(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), slot_index) + .map(|v| SlotInfo::from(v)), + info.as_mut().unwrap(), + ) +} + +/// C interface wrapper of `abr::set_one_shot_recovery()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrSetOneShotRecovery(abr_ops: *const AbrOps, enable: bool) -> c_uint { + unpack_result( + set_one_shot_recovery(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), enable), + &mut (), + ) +} + +/// C interface wrapper of `abr::set_one_shot_bootloader()` +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrSetOneShotBootloader(abr_ops: *const AbrOps, enable: bool) -> c_uint { + unpack_result( + set_one_shot_bootloader(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap()), enable), + &mut (), + ) +} + +/// Gets and clears the one shot flag. +/// +/// # Safety +/// +/// * Caller must make sure to pass a valid pointer for `abr_ops` and `flags`. +/// * Caller must make sure that `ops.context` is either not used, or points to a valid and correct +/// type of value needed by `ops.read_abr_metadata` and `ops.write_abr_metadata`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrGetAndClearOneShotFlags( + abr_ops: *const AbrOps, + flags: *mut c_uint, +) -> c_uint { + unpack_result( + get_and_clear_one_shot_flag(&mut AbrOpsSafe::new(abr_ops.as_ref().unwrap())), + flags.as_mut().unwrap(), + ) +} + +// Needed because of no-std environment in static lib build. +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn rust_eh_personality() {} diff --git a/gbl/libabr/src/lib.rs b/gbl/libabr/src/lib.rs new file mode 100644 index 0000000..dd0075c --- /dev/null +++ b/gbl/libabr/src/lib.rs @@ -0,0 +1,662 @@ +// 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. + +#![cfg_attr(not(test), no_std)] + +use core::{cmp::min, ffi::c_uint, fmt::Write, mem::size_of}; + +const ABR_MAGIC: &[u8; 4] = b"\0AB0"; +const ABR_MAJOR_VERSION: u8 = 2; +const ABR_MINOR_VERSION: u8 = 2; + +// The following flags are harcoded as u8 instead of using the bitflag crate to avoid additional +// crate dependency and improve portability. + +/// One-shot recovery boot bit for the flag returned by `get_and_clear_one_shot_flag()`. +pub const ONE_SHOT_RECOVERY: u8 = 1 << 0; +/// One-shot bootloader boot bit for the flag returned by `get_and_clear_one_shot_flag()`. +pub const ONE_SHOT_BOOTLOADER: u8 = 1 << 1; + +const ABR_MAX_PRIORITY: u8 = 15; +const ABR_MAX_TRIES_REMAINING: u8 = 7; + +/// Error type for this library. +#[derive(Debug)] +pub enum Error { + BadMagic, + UnsupportedVersion, + BadChecksum, + InvalidData, + OpsError(Option<&'static str>), +} + +impl From<Option<&'static str>> for Error { + fn from(val: Option<&'static str>) -> Self { + Error::OpsError(val) + } +} + +/// `Ops` provides the backend interfaces needed by A/B/R APIs. +pub trait Ops { + /// Reads exactly `out.len()` bytes into `out` from the persistent storage hosting the A/B/R + /// metadata. + fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<(), Option<&'static str>>; + + /// Writes exactly `data.len()` bytes from `data` to the persistent storage hosting the A/B/R + /// metadata. + fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<(), Option<&'static str>>; + + /// Returns an optional console writer for logging error messages. + fn console(&mut self) -> Option<&mut dyn Write>; +} + +/// Helper macro for printing ABR log messages. +macro_rules! avb_print { + ( $abr_ops:expr, $( $x:expr ),* $(,)? ) => { + match $abr_ops.console() { + Some(f) => write!(f, $($x,)*).unwrap(), + _ => {} + } + }; +} + +/// `SlotIndex` represents the A/B/R slot index. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum SlotIndex { + A, + B, + R, +} + +impl SlotIndex { + // Get the other counterpart of a A/B slot. + fn other(&self) -> Self { + match self { + SlotIndex::A => SlotIndex::B, + SlotIndex::B => SlotIndex::A, + _ => panic!("Invalid slot index for `fn other()`"), + } + } +} + +// Implement conversion to c_uint for C interfaces +impl From<SlotIndex> for c_uint { + fn from(_val: SlotIndex) -> Self { + match _val { + SlotIndex::A => 0, + SlotIndex::B => 1, + SlotIndex::R => 2, + } + } +} + +// Implement conversion from c_uint for C interfaces. +impl TryFrom<c_uint> for SlotIndex { + type Error = Error; + + fn try_from(val: c_uint) -> core::result::Result<SlotIndex, Self::Error> { + match val { + v if v == (SlotIndex::A).into() => Ok(SlotIndex::A), + v if v == (SlotIndex::B).into() => Ok(SlotIndex::B), + v if v == (SlotIndex::R).into() => Ok(SlotIndex::R), + _ => Err(Error::InvalidData), + } + } +} + +/// `SlotInfo` represents the current state of a A/B/R slot. +pub enum SlotState { + Successful, + Bootable(u8), // u8 = tries remaining + Unbootable, +} + +/// `SlotInfo` contains the current state and active status of a A/B/R slot. +pub struct SlotInfo { + pub state: SlotState, + pub is_active: bool, +} + +/// Error type for this library. +pub type AbrResult<T> = Result<T, Error>; + +/// `AbrSlotData` is the wire format metadata for A/B slot. +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct AbrSlotData { + pub priority: u8, + pub tries_remaining: u8, + pub successful_boot: u8, + pub reserved: u8, +} + +const ABR_SLOT_DATA_SIZE: usize = size_of::<AbrSlotData>(); + +impl AbrSlotData { + /// Parses from bytes. + pub fn deserialize(bytes: &[u8; ABR_SLOT_DATA_SIZE]) -> Self { + Self { + priority: bytes[0], + tries_remaining: bytes[1], + successful_boot: bytes[2], + reserved: bytes[3], + } + } + + /// Serializes to bytes. + pub fn serialize(&self) -> [u8; ABR_SLOT_DATA_SIZE] { + [self.priority, self.tries_remaining, self.successful_boot, self.reserved] + } + + /// Returns if slot is bootable + fn is_slot_bootable(&self) -> bool { + self.priority > 0 && (self.successful_boot == 1 || self.tries_remaining > 0) + } + + fn set_slot_unbootable(&mut self) { + self.tries_remaining = 0; + self.successful_boot = 0; + } + + /// Gets normalized priority. + fn get_normalized_priority(&self) -> u8 { + match self.is_slot_bootable() { + true => self.priority, + _ => 0, + } + } + + /// Ensures all unbootable or invalid states are marked as the canonical `unbootable` state. + /// That is priority=0, tries_remaining=0, and successful_boot=0. + fn slot_normalize(&mut self) { + if self.priority > 0 { + if self.tries_remaining == 0 && self.successful_boot == 0 { + // All tries exhausted + self.set_slot_unbootable(); + } + if self.tries_remaining > 0 && self.successful_boot == 1 { + // Illegal state. Reset to not successful state + self.tries_remaining = ABR_MAX_TRIES_REMAINING; + self.successful_boot = 0; + } + self.priority = min(self.priority, ABR_MAX_PRIORITY); + self.tries_remaining = min(self.tries_remaining, ABR_MAX_TRIES_REMAINING); + } else { + self.set_slot_unbootable(); + } + } +} + +/// `AbrData` is the wire format of A/B/R metadata. +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct AbrData { + pub magic: [u8; 4], + pub version_major: u8, + pub version_minor: u8, + pub reserved: [u8; 2], + pub slot_data: [AbrSlotData; 2], + pub one_shot_flags: u8, + pub reserved2: [u8; 11], + pub crc32: u32, +} + +const ABR_DATA_SIZE: usize = size_of::<AbrData>(); + +impl AbrData { + /// Returns the numeric index value for a `SlotIndex`. This is for indexing into + /// `Self::slot_data`. + fn slot_num_index(slot_index: SlotIndex) -> usize { + match slot_index { + SlotIndex::A => 0, + SlotIndex::B => 1, + _ => panic!("Invalid slot index"), + } + } + + /// Returns a const reference to `Self::slot_data['slot_index']` + fn slot_data(&self, slot_index: SlotIndex) -> &AbrSlotData { + &self.slot_data[Self::slot_num_index(slot_index)] + } + + /// Returns a mutable reference to `Self::slot_data[`slot_index`]` + fn slot_data_mut(&mut self, slot_index: SlotIndex) -> &mut AbrSlotData { + &mut self.slot_data[Self::slot_num_index(slot_index)] + } + + /// Reads, parses and checks metadata from persistent storage. + fn deserialize(abr_ops: &mut dyn Ops) -> AbrResult<Self> { + let mut bytes = [0u8; ABR_DATA_SIZE]; + abr_ops.read_abr_metadata(&mut bytes[..])?; + // Usually, the parsing below should be done using the zerocopy crate. However, the Fuchsia + // source tree uses the unreleased alpha/beta version of zerocopy which can have + // drastically different usage and bound requirements. In order to minimize maintenance + // burden for Android and Fuchsia build, we manually copy and parse from the bytes directly + // to avoid zerocopy crate dependency. + let res = Self { + magic: bytes[..4].try_into().unwrap(), + version_major: bytes[4], + version_minor: bytes[5], + reserved: bytes[6..8].try_into().unwrap(), + slot_data: [ + AbrSlotData::deserialize(&bytes[8..12].try_into().unwrap()), + AbrSlotData::deserialize(&bytes[12..16].try_into().unwrap()), + ], + one_shot_flags: bytes[16], + reserved2: bytes[17..28].try_into().unwrap(), + crc32: u32::from_be_bytes(bytes[28..ABR_DATA_SIZE].try_into().unwrap()), + }; + + if res.magic != *ABR_MAGIC { + avb_print!(abr_ops, "Magic is incorrect.\n"); + return Err(Error::BadMagic); + } + if res.crc32 != crc32(&bytes[..28]) { + avb_print!(abr_ops, "CRC32 does not match.\n"); + return Err(Error::BadChecksum); + } + if res.version_major > ABR_MAJOR_VERSION { + avb_print!(abr_ops, "No support for given major version.\n"); + return Err(Error::UnsupportedVersion); + } + + Ok(res) + } + + /// Updates CRC32 and writes metadata to persistent storage. + fn serialize(&mut self) -> [u8; ABR_DATA_SIZE] { + let mut res = [0u8; ABR_DATA_SIZE]; + res[..4].clone_from_slice(&self.magic); + res[4] = self.version_major; + res[5] = self.version_minor; + res[6..8].clone_from_slice(&self.reserved); + res[8..12].clone_from_slice(&self.slot_data(SlotIndex::A).serialize()); + res[12..16].clone_from_slice(&self.slot_data(SlotIndex::B).serialize()); + res[16] = self.one_shot_flags; + res[17..28].clone_from_slice(&self.reserved2[..]); + self.crc32 = crc32(&res[..28]); + res[28..ABR_DATA_SIZE].clone_from_slice(&self.crc32.to_be_bytes()); + res + } + + /// Returns an invalid instance. + fn null() -> Self { + Self { magic: [0u8; 4], ..Default::default() } + } + + /// Gets the active slot + fn get_active_slot(&self) -> SlotIndex { + let priority_a = self.slot_data(SlotIndex::A).get_normalized_priority(); + let priority_b = self.slot_data(SlotIndex::B).get_normalized_priority(); + if priority_b > priority_a { + return SlotIndex::B; + } else if priority_a > 0 { + return SlotIndex::A; + } + return SlotIndex::R; + } + + /// Is the given slot active. + fn is_slot_active(&self, slot_index: SlotIndex) -> bool { + self.get_active_slot() == slot_index + } + + /// Returns if one-shot recovery is set. + fn is_one_shot_recovery(&self) -> bool { + (self.one_shot_flags & ONE_SHOT_RECOVERY) != 0 + } + + /// Sets one-shot recovery. + pub fn set_one_shot_recovery(&mut self, enable: bool) { + match enable { + true => self.one_shot_flags |= ONE_SHOT_RECOVERY, + _ => self.one_shot_flags &= !ONE_SHOT_RECOVERY, + } + } + + /// Sets one-shot bootloader + pub fn set_one_shot_bootloader(&mut self, enable: bool) { + match enable { + true => self.one_shot_flags |= ONE_SHOT_BOOTLOADER, + _ => self.one_shot_flags &= !ONE_SHOT_BOOTLOADER, + } + } +} + +impl Default for AbrData { + fn default() -> Self { + Self { + magic: *ABR_MAGIC, + version_major: ABR_MAJOR_VERSION, + version_minor: ABR_MINOR_VERSION, + reserved: Default::default(), + slot_data: [ + AbrSlotData { + priority: ABR_MAX_PRIORITY, + tries_remaining: ABR_MAX_TRIES_REMAINING, + successful_boot: 0, + reserved: 0, + }, + AbrSlotData { + priority: ABR_MAX_PRIORITY - 1, + tries_remaining: ABR_MAX_TRIES_REMAINING, + successful_boot: 0, + reserved: 0, + }, + ], + one_shot_flags: 0, + reserved2: Default::default(), + crc32: 0, + } + } +} + +/// Loads |abr_data| from persistent storage and normalizes it, initializing new data if necessary. +/// Changes as a result of normalization are not written back to persistent storage but a copy of +/// the exact original data from persistent storage is provided in |abr_data_orig| for future use +/// with save_metadata_if_changed(). +/// +/// On success returns Ok((abr_data, abr_data_orig)). On failure an Error is returned. +fn load_metadata(abr_ops: &mut dyn Ops) -> AbrResult<(AbrData, AbrData)> { + let mut abr_data_orig = AbrData::null(); + let mut abr_data = match AbrData::deserialize(abr_ops) { + Ok(v) => { + abr_data_orig = v; + v + } + Err(Error::OpsError(e)) => { + avb_print!(abr_ops, "read_abr_metadata error: {:?}\n", e); + return Err(e.into()); + } + Err(Error::UnsupportedVersion) => { + // We don't want to clobber valid data in persistent storage, but we can't use this + // data, so bail out. + return Err(Error::UnsupportedVersion); + } + _ => Default::default(), + }; + abr_data.slot_data_mut(SlotIndex::A).slot_normalize(); + abr_data.slot_data_mut(SlotIndex::B).slot_normalize(); + + Ok((abr_data, abr_data_orig)) +} + +/// Serializes and saves metadata to persistent storage. +fn save_metadata(abr_ops: &mut dyn Ops, abr_data: &mut AbrData) -> AbrResult<()> { + let mut bytes = abr_data.serialize(); + abr_ops.write_abr_metadata(&mut bytes)?; + Ok(()) +} + +/// Writes metadata to disk only if it has changed. `abr_data_orig` should be from load_metadata(). +fn save_metadata_if_changed( + abr_ops: &mut dyn Ops, + abr_data: &mut AbrData, + abr_data_orig: &AbrData, +) -> AbrResult<()> { + match abr_data == abr_data_orig { + true => Ok(()), + _ => save_metadata(abr_ops, abr_data), + } +} + +/// Equivalent to C API `AbrGetBootSlot()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn get_boot_slot(abr_ops: &mut dyn Ops, update_metadata: bool) -> (SlotIndex, bool) { + let mut is_slot_marked_successful = false; + let (mut abr_data, abr_data_orig) = match load_metadata(abr_ops) { + Ok(v) => v, + Err(e) => { + avb_print!( + abr_ops, + "Failed to load metadata {:?}, falling back to recovery mode.\n", + e + ); + return (SlotIndex::R, is_slot_marked_successful); + } + }; + + if abr_data.is_one_shot_recovery() && update_metadata { + abr_data.set_one_shot_recovery(false); + match save_metadata(abr_ops, &mut abr_data) { + Ok(()) => return (SlotIndex::R, is_slot_marked_successful), + Err(e) => { + avb_print!( + abr_ops, + "Failed to update one-shot state {:?}. Ignoring one-shot request.\n", + e + ); + abr_data.set_one_shot_recovery(true); + } + } + } + + // Chooses the highest priority and bootable slot. Otherwise R slot. + let slot_to_boot = abr_data.get_active_slot(); + match slot_to_boot { + SlotIndex::R => {} + v => { + is_slot_marked_successful = abr_data.slot_data(v).successful_boot == 1; + } + }; + + if update_metadata { + // In addition to any changes that resulted from normalization, there are a couple changes + // to be made here. First is to decrement the tries remaining for a slot not yet marked as + // successful. + if slot_to_boot != SlotIndex::R && !is_slot_marked_successful { + let slot_data = abr_data.slot_data_mut(slot_to_boot); + slot_data.tries_remaining = slot_data.tries_remaining.checked_sub(1).unwrap(); + } + // Second is to clear the successful_boot bit from any successfully-marked slots that + // aren't the slot we're booting. It's possible that booting from one slot will render the + // other slot unbootable (say, by migrating a config file format in a shared partiton). + // Clearing these bits minimizes the risk we'll have an unhealthy slot marked + // "successful_boot", which would prevent the system from automatically booting into + // recovery. + for slot in [SlotIndex::A, SlotIndex::B] { + if slot != slot_to_boot && abr_data.slot_data(slot).successful_boot == 1 { + abr_data.slot_data_mut(slot).tries_remaining = ABR_MAX_TRIES_REMAINING; + abr_data.slot_data_mut(slot).successful_boot = 0; + } + } + if let Err(e) = save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) { + // We have no choice but to proceed without updating metadata. + avb_print!(abr_ops, "Failed to update metadata {:?}, proceeding anyways.\n", e); + } + } + (slot_to_boot, is_slot_marked_successful) +} + +/// Equivalent to C API `AbrMarkSlotActive()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn mark_slot_active(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> AbrResult<()> { + if slot_index == SlotIndex::R { + avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as active.\n"); + return Err(Error::InvalidData); + } + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + // Make requested slot top priority, unsuccessful, and with max tries. + abr_data.slot_data_mut(slot_index).priority = ABR_MAX_PRIORITY; + abr_data.slot_data_mut(slot_index).tries_remaining = ABR_MAX_TRIES_REMAINING; + abr_data.slot_data_mut(slot_index).successful_boot = 0; + + // Ensure other slot doesn't have as high a priority + let other = slot_index.other(); + abr_data.slot_data_mut(other).priority = + min(abr_data.slot_data_mut(other).priority, ABR_MAX_PRIORITY - 1); + + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) +} + +/// Equivalent to C API `AbrGetSlotLastMarkedActive()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn get_slot_last_marked_active(abr_ops: &mut dyn Ops) -> AbrResult<SlotIndex> { + let (abr_data, _) = load_metadata(abr_ops)?; + Ok( + match abr_data.slot_data(SlotIndex::B).priority > abr_data.slot_data(SlotIndex::A).priority + { + true => SlotIndex::B, + false => SlotIndex::A, + }, + ) +} + +/// Equivalent to C API `AbrMarkSlotUnbootable()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn mark_slot_unbootable(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> AbrResult<()> { + if slot_index == SlotIndex::R { + avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as unbootable.\n"); + return Err(Error::InvalidData); + } + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + abr_data.slot_data_mut(slot_index).set_slot_unbootable(); + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) +} + +/// Equivalent to C API `AbrMarkSlotSuccessful()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn mark_slot_successful(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> AbrResult<()> { + if slot_index == SlotIndex::R { + avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as successful.\n"); + return Err(Error::InvalidData); + } + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + + if !abr_data.slot_data(slot_index).is_slot_bootable() { + avb_print!(abr_ops, "Invalid argument: Cannot mark unbootable slot as successful.\n"); + return Err(Error::InvalidData); + } + + abr_data.slot_data_mut(slot_index).tries_remaining = 0; + abr_data.slot_data_mut(slot_index).successful_boot = 1; + + // Proactively remove any success mark on the other slot + // + // This can theoretically be removed since get_boot_slot() clear successful bit on non-boot + // slots. However, legacy devices might still be using old versions of ABR implementation that + // don't clear it. Therefore, we keep this logic to be safe. + // + // Context: https://fxbug.dev/42142842, https://crbug.com/fuchsia/64057. + let other = slot_index.other(); + if abr_data.slot_data(other).is_slot_bootable() { + abr_data.slot_data_mut(other).tries_remaining = ABR_MAX_TRIES_REMAINING; + abr_data.slot_data_mut(other).successful_boot = 0; + } + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) +} + +/// Equivalent to C API `AbrGetSlotInfo()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn get_slot_info(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> AbrResult<SlotInfo> { + let (abr_data, _) = load_metadata(abr_ops)?; + Ok(match slot_index { + // Assume that R slot is always OK. + SlotIndex::R => SlotInfo { + state: SlotState::Successful, + is_active: abr_data.is_slot_active(SlotIndex::R), + }, + _ => { + let slot_data = abr_data.slot_data(slot_index); + let state = match slot_data.successful_boot == 1 { + true => SlotState::Successful, + _ if slot_data.is_slot_bootable() => SlotState::Bootable(slot_data.tries_remaining), + _ => SlotState::Unbootable, + }; + SlotInfo { state, is_active: abr_data.is_slot_active(slot_index) } + } + }) +} + +/// Equivalent to C API `AbrSetOneShotRecovery()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn set_one_shot_recovery(abr_ops: &mut dyn Ops, enable: bool) -> AbrResult<()> { + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + abr_data.set_one_shot_recovery(enable); + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) +} + +/// Equivalent to C API `AbrSetOneShotBootloader()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn set_one_shot_bootloader(abr_ops: &mut dyn Ops, enable: bool) -> AbrResult<()> { + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + abr_data.set_one_shot_bootloader(enable); + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) +} + +/// Equivalent to C API `AbrGetAndClearOneShotFlags()`. +/// +/// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header +/// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo. +pub fn get_and_clear_one_shot_flag(abr_ops: &mut dyn Ops) -> AbrResult<u8> { + let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?; + let res = abr_data.one_shot_flags; + abr_data.one_shot_flags = 0; + save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?; + Ok(res) +} + +/// Reverses the bit of a byte. +fn reverse_byte(b: u8) -> u8 { + const LOOKUP_TABLE_4BIT_REVERSE: &[u8] = + &[0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF]; + LOOKUP_TABLE_4BIT_REVERSE[(b >> 4) as usize] + | (LOOKUP_TABLE_4BIT_REVERSE[(b & 0xf) as usize] << 4) +} + +// Reverses the bits of a u32; +fn reverse_u32(val: u32) -> u32 { + let mut bytes = val.to_le_bytes(); + bytes.iter_mut().for_each(|v| *v = reverse_byte(*v)); + u32::from_be_bytes(bytes) +} + +// Calculates the crc32 of the given bytes. +fn crc32(data: &[u8]) -> u32 { + let mut res: u32 = 0xffffffff; + for b in data { + res ^= (reverse_byte(*b) as u32) << 24; + for _ in 0..8 { + if (res & 0x80000000) != 0 { + res = (res << 1) ^ 0x04C11DB7; + } else { + res <<= 1; + } + } + } + reverse_u32(!res) +} + +#[cfg(test)] +mod test { + // Testing is currently done against the C interface tests in upstream Fuchsia: + // https://fuchsia.googlesource.com/fuchsia/+/96f7268b497f998ffcbeef73425b031bd7f4db65/src/firmware/lib/abr/test/libabr_test.cc + // These tests will be ported to here as rust tests in the future. +} diff --git a/gbl/libabr/src/utils.rs b/gbl/libabr/src/utils.rs new file mode 100644 index 0000000..0c446d3 --- /dev/null +++ b/gbl/libabr/src/utils.rs @@ -0,0 +1,87 @@ +// 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. + +use abr::{AbrData, ONE_SHOT_BOOTLOADER, ONE_SHOT_RECOVERY}; + +/// Converts big endian order to host order. +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn AbrBigEndianToHost(val: u32) -> u32 { + u32::from_be(val) +} + +/// Converts host order to big endian. +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn AbrHostToBigEndian(val: u32) -> u32 { + val.to_be() +} + +/// Checks if one-shot recovery boot is set in the given one-shot flags +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn AbrIsOneShotRecoveryBootSet(flags: u8) -> bool { + (flags & ONE_SHOT_RECOVERY) != 0 +} + +/// Checks if one-shot recovery boot is set in the given AbrData +/// +/// # Safety +/// +/// Caller must make sure to pass a valid pointer for `abr_data`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrIsOneShotRecoveryBoot(abr_data: *const AbrData) -> bool { + AbrIsOneShotRecoveryBootSet(abr_data.as_ref().unwrap().one_shot_flags) +} + +/// Checks if one-shot bootloader boot is set in the given one-shot flags +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn AbrIsOneShotBootloaderBootSet(flags: u8) -> bool { + (flags & ONE_SHOT_BOOTLOADER) != 0 +} + +/// Checks if one-shot bootloader boot is set in the given AbrData +/// +/// # Safety +/// +/// Caller must make sure to pass a valid pointer for `abr_data`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrIsOneShotBootloaderBoot(abr_data: *const AbrData) -> bool { + AbrIsOneShotBootloaderBootSet(abr_data.as_ref().unwrap().one_shot_flags) +} + +/// Sets the one-shot recovery flag in the given AbrData. +/// +/// # Safety +/// +/// Caller must make sure to pass a valid pointer for `abr_data`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrSetOneShotRecoveryBoot(abr_data: *mut AbrData, enable: bool) { + abr_data.as_mut().unwrap().set_one_shot_recovery(enable); +} + +/// Sets the one-shot bootloader flag in the given AbrData. +/// +/// # Safety +/// +/// Caller must make sure to pass a valid pointer for `abr_data`. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn AbrSetOneShotBootloaderBoot(abr_data: *mut AbrData, enable: bool) { + abr_data.as_mut().unwrap().set_one_shot_bootloader(enable); +} diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD index 27b6952..833d7a6 100644 --- a/gbl/tests/BUILD +++ b/gbl/tests/BUILD @@ -15,6 +15,7 @@ test_suite( name = "tests", tests = [ + "@gbl//libabr:libabr_tests", "@gbl//libbootconfig:libbootconfig_test", "@gbl//libbootimg:libbootimg_test", "@gbl//libefi:libefi_test", |