blob: d836ad903c1bc930b5f2862fded943f07b2c8528 [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.
//! Fuchsia A/B/R boot slot library.
#![cfg_attr(not(test), no_std)]
use core::{cmp::min, ffi::c_uint, ffi::CStr, fmt::Write, mem::size_of};
use liberror::{Error, Result};
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;
/// Maximum number of retries.
pub const ABR_MAX_TRIES_REMAINING: u8 = 7;
/// `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<()>;
/// 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<()>;
/// Returns an optional console writer for logging error messages.
fn console(&mut self) -> Option<&mut dyn Write>;
}
impl Ops for [u8; ABR_DATA_SIZE] {
fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> {
Ok(out
.clone_from_slice(self.get(..out.len()).ok_or(Error::BufferTooSmall(Some(out.len())))?))
}
fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()> {
Ok(self
.get_mut(..data.len())
.ok_or(Error::BufferTooSmall(Some(data.len())))?
.clone_from_slice(data))
}
fn console(&mut self) -> Option<&mut dyn Write> {
None
}
}
/// 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 slot; normal boot.
A,
/// B slot; normal boot.
B,
/// R slot; recovery boot. Doesn't have any associated metadata (e.g. cannot be active, no
/// retries), but is unconditionally used as a fallback if both A and B are unbootable.
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 to char
impl From<SlotIndex> for char {
fn from(val: SlotIndex) -> Self {
match val {
SlotIndex::A => 'a',
SlotIndex::B => 'b',
SlotIndex::R => 'r',
}
}
}
// Implement conversion to c string suffix.
impl From<SlotIndex> for &CStr {
fn from(s: SlotIndex) -> Self {
match s {
SlotIndex::A => c"_a",
SlotIndex::B => c"_b",
SlotIndex::R => c"_r",
}
}
}
// Implement conversion from char.
impl TryFrom<char> for SlotIndex {
type Error = Error;
fn try_from(val: char) -> Result<Self> {
match val {
'a' => Ok(SlotIndex::A),
'b' => Ok(SlotIndex::B),
'r' => Ok(SlotIndex::R),
_ => Err(Error::InvalidInput),
}
}
}
// Implement conversion from c_uint for C interfaces.
impl TryFrom<c_uint> for SlotIndex {
type Error = Error;
fn try_from(val: c_uint) -> Result<SlotIndex> {
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::InvalidInput),
}
}
}
/// `SlotInfo` represents the current state of a A/B/R slot.
pub enum SlotState {
/// Slot has successfully booted.
Successful,
/// Slot can be attempted but is not known to be successful. Contained value is the number
/// of boot attempts remaining before being marked as `Unbootable`.
Bootable(u8),
/// Slot is unbootable.
Unbootable,
}
/// `SlotInfo` contains the current state and active status of a A/B/R slot.
pub struct SlotInfo {
/// The [SlotState] describing the bootability.
pub state: SlotState,
/// Whether this is currently the active slot.
pub is_active: bool,
}
/// `AbrSlotData` is the wire format metadata for A/B slot.
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub struct AbrSlotData {
/// Slot priority. Unbootable slots should always have priority 0.
pub priority: u8,
/// Boot attempts remaining.
pub tries_remaining: u8,
/// Whether this slot is known successful.
pub successful_boot: u8,
/// Reserved for future use; must be set to 0.
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 {
/// Magic value; must be [ABR_MAGIC].
pub magic: [u8; 4],
/// Metadata major version, incremented when changes may break backwards compatibility.
pub version_major: u8,
/// Metadata minor version, incremented when changes do not break backwards compatibility.
pub version_minor: u8,
/// Reserved for future use; must be 0.
pub reserved: [u8; 2],
/// A/B slot data.
pub slot_data: [AbrSlotData; 2],
/// One-shot to bootloader/recovery.
pub one_shot_flags: u8,
/// Reserved for future use; must be 0.
pub reserved2: [u8; 11],
/// CRC32 checksum of this struct.
pub crc32: u32,
}
/// Size of `AbrData`
pub 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.
pub fn deserialize(abr_ops: &mut dyn Ops) -> Result<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.
pub 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) -> Result<(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::Other(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) -> Result<()> {
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,
) -> Result<()> {
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) -> Result<()> {
if slot_index == SlotIndex::R {
avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as active.\n");
return Err(Error::InvalidInput);
}
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) -> Result<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) -> Result<()> {
if slot_index == SlotIndex::R {
avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as unbootable.\n");
return Err(Error::InvalidInput);
}
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) -> Result<()> {
if slot_index == SlotIndex::R {
avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as successful.\n");
return Err(Error::InvalidInput);
}
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::InvalidInput);
}
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) -> Result<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) -> Result<()> {
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) -> Result<()> {
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) -> Result<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)
}
/// Gets and clears one shot bootloader flag only.
pub fn get_and_clear_one_shot_bootloader(abr_ops: &mut dyn Ops) -> Result<bool> {
let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
let res = abr_data.one_shot_flags;
abr_data.one_shot_flags &= !ONE_SHOT_BOOTLOADER;
save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?;
Ok((res & ONE_SHOT_BOOTLOADER) != 0)
}
/// 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 {
use super::*;
// 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.
#[test]
fn test_get_and_clear_one_shot_bootloader() {
let mut meta = [0u8; ABR_DATA_SIZE];
set_one_shot_bootloader(&mut meta, true).unwrap();
set_one_shot_recovery(&mut meta, true).unwrap();
assert!(get_and_clear_one_shot_bootloader(&mut meta).unwrap());
assert_eq!(get_and_clear_one_shot_flag(&mut meta).unwrap(), ONE_SHOT_RECOVERY);
}
}