blob: 5e97d66d97e9fb9d1ce3e5d031ab4a56010281b6 [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.
extern crate bitflags;
extern crate crc32fast;
extern crate zerocopy;
use super::partition::{MetadataBytes, MetadataParseError, SlotBlock};
use super::{
BootTarget, BootToken, Bootability, Error, Manager, OneShot, RecoveryTarget, Slot,
SlotIterator, Suffix, UnbootableReason,
};
use bitflags::bitflags;
use core::iter::zip;
use core::mem::size_of;
use crc32fast::Hasher;
use zerocopy::byteorder::big_endian::U32 as BigEndianU32;
use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref};
const DEFAULT_PRIORITY: u8 = 15;
const DEFAULT_RETRIES: u8 = 7;
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
struct AbrSlotData {
priority: u8,
tries: u8,
successful: u8,
unbootable_reason: u8,
}
impl Default for AbrSlotData {
fn default() -> Self {
Self {
priority: DEFAULT_PRIORITY,
tries: DEFAULT_RETRIES,
successful: 0,
unbootable_reason: 0,
}
}
}
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
struct OneShotFlags(u8);
bitflags! {
impl OneShotFlags: u8 {
/// No oneshot specified
const NONE = 0;
/// Oneshot boot to recovery mode
const RECOVERY = 1 << 0;
/// Oneshot boot to fastboot
const BOOTLOADER = 1 << 1;
}
}
impl From<OneShotFlags> for Option<OneShot> {
fn from(flags: OneShotFlags) -> Self {
match flags {
OneShotFlags::RECOVERY => Some(OneShot::Continue(RecoveryTarget::Dedicated)),
OneShotFlags::BOOTLOADER => Some(OneShot::Bootloader),
_ => None,
}
}
}
impl From<Option<OneShot>> for OneShotFlags {
fn from(oneshot: Option<OneShot>) -> Self {
if let Some(target) = oneshot {
match target {
OneShot::Bootloader => Self::BOOTLOADER,
OneShot::Continue(RecoveryTarget::Dedicated) => Self::RECOVERY,
_ => Self::NONE,
}
} else {
Self::NONE
}
}
}
const ABR_MAGIC: &[u8; 4] = b"\0AB0";
const ABR_VERSION_MAJOR: u8 = 2;
const ABR_VERSION_MINOR: u8 = 3;
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)]
struct AbrData {
magic: [u8; 4],
version_major: u8,
version_minor: u8,
reserved: [u8; 2],
slot_data: [AbrSlotData; 2],
oneshot_flag: OneShotFlags,
reserved2: [u8; 11],
crc32: BigEndianU32,
}
impl AbrData {
fn calculate_crc32(&self) -> u32 {
let mut hasher = Hasher::new();
// Note: core::offset_of isn't stable yet,
// and size_of_val isn't permitted on unaligned structs.
hasher.update(&self.as_bytes()[..(size_of::<Self>() - size_of::<BigEndianU32>())]);
hasher.finalize()
}
}
impl MetadataBytes for AbrData {
fn validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, AbrData>, MetadataParseError> {
let abr_data =
Ref::<B, AbrData>::new_from_prefix(buffer).ok_or(MetadataParseError::BufferTooSmall)?.0;
if abr_data.magic != *ABR_MAGIC {
return Err(MetadataParseError::BadMagic);
}
if abr_data.version_major > ABR_VERSION_MAJOR {
return Err(MetadataParseError::BadVersion);
}
if abr_data.crc32.get() != abr_data.calculate_crc32() {
return Err(MetadataParseError::BadChecksum);
}
Ok(abr_data)
}
fn prepare_for_sync(&mut self) {
self.version_minor = ABR_VERSION_MINOR;
self.crc32 = self.calculate_crc32().into();
}
}
impl Default for AbrData {
fn default() -> Self {
let mut data = Self {
magic: *ABR_MAGIC,
version_major: ABR_VERSION_MAJOR,
version_minor: ABR_VERSION_MINOR,
reserved: Default::default(),
slot_data: Default::default(),
oneshot_flag: OneShotFlags::NONE,
reserved2: Default::default(),
crc32: BigEndianU32::ZERO,
};
data.crc32.set(data.calculate_crc32());
data
}
}
impl super::private::SlotGet for SlotBlock<'_, AbrData> {
fn get_slot_by_number(&self, number: usize) -> Result<Slot, Error> {
let lower_ascii_suffixes = ('a'..='z').map(Suffix);
let (suffix, &abr_slot) = zip(lower_ascii_suffixes, self.get_data().slot_data.iter())
.nth(number)
.ok_or_else(|| Suffix::try_from(number).map_or(Error::Other, Error::NoSuchSlot))?;
let bootability = match (abr_slot.successful, abr_slot.tries) {
(s, _) if s != 0 => Bootability::Successful,
(0, t) if t > 0 => Bootability::Retriable(t.into()),
(_, _) => Bootability::Unbootable(abr_slot.unbootable_reason.into()),
};
Ok(Slot { suffix, priority: abr_slot.priority.into(), bootability })
}
}
impl Manager for SlotBlock<'_, AbrData> {
fn get_boot_target(&self) -> BootTarget {
self.slots_iter()
.filter(Slot::is_bootable)
.max_by_key(|slot| (slot.priority, slot.suffix.rank()))
.map_or(BootTarget::Recovery(RecoveryTarget::Dedicated), BootTarget::NormalBoot)
}
fn slots_iter(&self) -> SlotIterator {
SlotIterator::new(self)
}
fn set_slot_unbootable(
&mut self,
slot_suffix: Suffix,
reason: UnbootableReason,
) -> Result<(), Error> {
let (idx, slot) = self.get_index_and_slot_with_suffix(slot_suffix)?;
if slot.bootability == Bootability::Unbootable(reason) {
return Ok(());
}
let abr_data = self.get_mut_data();
let slot_data = &mut abr_data.slot_data[idx];
slot_data.unbootable_reason = reason.into();
slot_data.tries = 0;
slot_data.successful = 0;
Ok(())
}
fn mark_boot_attempt(&mut self, boot_target: BootTarget) -> Result<BootToken, Error> {
let target_slot = match boot_target {
BootTarget::NormalBoot(slot) => slot,
BootTarget::Recovery(RecoveryTarget::Slotted(_)) => Err(Error::OperationProhibited)?,
BootTarget::Recovery(RecoveryTarget::Dedicated) => {
// Even though boot to recovery does not cause a metadata update,
// we still need to gate access to the boot token.
return self.take_boot_token().ok_or(Error::OperationProhibited);
}
};
let (idx, slot) = self.get_index_and_slot_with_suffix(target_slot.suffix)?;
match slot.bootability {
Bootability::Unbootable(_) => Err(Error::OperationProhibited),
Bootability::Retriable(_) => {
let abr_slot = &mut self.get_mut_data().slot_data[idx];
abr_slot.tries -= 1;
if abr_slot.tries == 0 {
abr_slot.unbootable_reason = UnbootableReason::NoMoreTries.into();
}
let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
Ok(token)
}
Bootability::Successful => {
let token = self.take_boot_token().ok_or(Error::OperationProhibited)?;
Ok(token)
}
}
}
fn set_active_slot(&mut self, slot_suffix: Suffix) -> Result<(), Error> {
let (idx, _) = self.get_index_and_slot_with_suffix(slot_suffix)?;
let abr_data = self.get_mut_data();
for (i, slot) in abr_data.slot_data.iter_mut().enumerate() {
if i == idx {
*slot = Default::default();
} else {
slot.priority = DEFAULT_PRIORITY - 1;
}
}
Ok(())
}
fn get_oneshot_status(&self) -> Option<OneShot> {
self.get_data().oneshot_flag.into()
}
fn set_oneshot_status(&mut self, oneshot: OneShot) -> Result<(), Error> {
if Some(oneshot) == self.get_oneshot_status() {
return Ok(());
}
let oneshot_flag = OneShotFlags::from(Some(oneshot));
if oneshot_flag == OneShotFlags::NONE {
Err(match oneshot {
OneShot::Continue(RecoveryTarget::Slotted(_)) => Error::OperationProhibited,
_ => Error::Other,
})
} else {
self.get_mut_data().oneshot_flag = oneshot_flag;
Ok(())
}
}
fn clear_oneshot_status(&mut self) {
if self.get_oneshot_status().is_some() {
self.get_mut_data().oneshot_flag = OneShotFlags::NONE;
}
}
fn write_back<B: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut B) {
self.sync_to_disk(block_dev);
}
}
impl<'a> SlotBlock<'a, AbrData> {
fn get_index_and_slot_with_suffix(&self, slot_suffix: Suffix) -> Result<(usize, Slot), Error> {
self.slots_iter()
.enumerate()
.find(|(_, s)| s.suffix == slot_suffix)
.ok_or(Error::NoSuchSlot(slot_suffix))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::slots::{partition::CacheStatus, Cursor};
use gbl_storage::AsBlockDevice;
use gbl_storage_testlib::TestBlockDevice;
#[test]
fn test_slot_block_defaults() {
let sb: SlotBlock<AbrData> = Default::default();
let expected: Vec<Slot> = vec![
Slot {
suffix: 'a'.into(),
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Retriable(sb.get_max_retries()),
},
Slot {
suffix: 'b'.into(),
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Retriable(sb.get_max_retries()),
},
];
let actual: Vec<Slot> = sb.slots_iter().collect();
assert_eq!(actual, expected);
assert_eq!(sb.get_oneshot_status(), None);
}
#[test]
fn test_suffix() {
let slot = Slot { suffix: 'a'.into(), ..Default::default() };
assert_eq!(BootTarget::Recovery(RecoveryTarget::Dedicated).suffix(), 'r'.into());
assert_eq!(BootTarget::Recovery(RecoveryTarget::Slotted(slot)).suffix(), slot.suffix);
assert_eq!(BootTarget::NormalBoot(slot).suffix(), slot.suffix);
}
#[test]
fn test_slot_block_parse() {
let abr: AbrData = Default::default();
assert_eq!(AbrData::validate(abr.as_bytes()), Ok(Ref::new(abr.as_bytes()).unwrap()));
}
#[test]
fn test_slot_block_parse_buffer_too_small() {
let buffer: [u8; 0] = Default::default();
assert_eq!(AbrData::validate(&buffer[..]), Err(MetadataParseError::BufferTooSmall),);
}
#[test]
fn test_slot_block_parse_bad_magic() {
let mut abr: AbrData = Default::default();
abr.magic[0] += 1;
assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadMagic));
}
#[test]
fn test_slot_block_parse_bad_version_major() {
let mut abr: AbrData = Default::default();
abr.version_major = 15;
assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadVersion));
}
#[test]
fn test_slot_block_parse_bad_crc() {
let mut abr: AbrData = Default::default();
let bad_crc = abr.crc32.get() ^ BigEndianU32::MAX_VALUE.get();
abr.crc32 = bad_crc.into();
assert_eq!(AbrData::validate(abr.as_bytes()), Err(MetadataParseError::BadChecksum));
}
#[test]
fn test_slot_mark_boot_attempt() {
let mut sb: SlotBlock<AbrData> = Default::default();
let slot = Slot { suffix: 'a'.into(), ..Default::default() };
assert_eq!(sb.mark_boot_attempt(BootTarget::NormalBoot(slot)), Ok(BootToken(())));
assert_eq!(
sb.slots_iter().next().unwrap(),
Slot {
suffix: slot.suffix,
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Retriable((DEFAULT_RETRIES - 1).into())
}
);
// Make sure we can call exactly once
assert_eq!(
sb.mark_boot_attempt(BootTarget::NormalBoot(slot)),
Err(Error::OperationProhibited)
);
}
#[test]
fn test_slot_mark_boot_attempt_no_more_tries() {
let mut sb: SlotBlock<AbrData> = Default::default();
sb.get_mut_data().slot_data[0].tries = 1;
let slot = Slot { suffix: 'a'.into(), ..Default::default() };
assert_eq!(sb.mark_boot_attempt(BootTarget::NormalBoot(slot)), Ok(BootToken(())));
assert_eq!(
sb.slots_iter().next().unwrap(),
Slot {
suffix: slot.suffix,
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Unbootable(UnbootableReason::NoMoreTries)
}
);
}
#[test]
fn test_slot_mark_boot_attempt_successful() {
let mut sb: SlotBlock<AbrData> = Default::default();
sb.get_mut_data().slot_data[0].successful = 1;
let target = BootTarget::NormalBoot(Slot {
suffix: 'a'.into(),
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Successful,
});
assert_eq!(sb.mark_boot_attempt(target), Ok(BootToken(())));
assert_eq!(sb.get_boot_target(), target);
}
#[test]
fn test_slot_mark_tried_no_such_slot() {
let mut sb: SlotBlock<AbrData> = Default::default();
let slot = Slot { suffix: '$'.into(), ..Default::default() };
assert_eq!(
sb.mark_boot_attempt(BootTarget::NormalBoot(slot)),
Err(Error::NoSuchSlot(slot.suffix))
);
}
#[test]
fn test_slot_mark_tried_recovery() {
let mut sb: SlotBlock<AbrData> = Default::default();
let recovery_tgt = BootTarget::Recovery(RecoveryTarget::Dedicated);
assert_eq!(sb.mark_boot_attempt(recovery_tgt), Ok(BootToken(())));
// Make sure a second attempt fails due to the moved boot token
assert_eq!(sb.mark_boot_attempt(recovery_tgt), Err(Error::OperationProhibited));
}
#[test]
fn test_mark_slot_tried_slotted_recovery() {
let mut sb: SlotBlock<AbrData> = Default::default();
let slot: Slot = Default::default();
assert_eq!(
sb.mark_boot_attempt(BootTarget::Recovery(RecoveryTarget::Slotted(slot))),
Err(Error::OperationProhibited)
);
}
#[test]
fn test_slot_mark_tried_unbootable() {
let mut sb: SlotBlock<AbrData> = Default::default();
let slot = Slot { suffix: 'b'.into(), ..Default::default() };
assert_eq!(sb.set_slot_unbootable(slot.suffix, UnbootableReason::UserRequested), Ok(()));
assert_eq!(
sb.mark_boot_attempt(BootTarget::NormalBoot(slot)),
Err(Error::OperationProhibited)
);
}
macro_rules! set_unbootable_tests {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let mut sb: SlotBlock<AbrData> = Default::default();
let suffix: Suffix = 'a'.into();
assert_eq!(sb.set_slot_unbootable(suffix, $value), Ok(()));
assert_eq!(sb.slots_iter()
.find(|s| s.suffix == suffix)
.unwrap()
.bootability,
Bootability::Unbootable($value)
);
}
)*
}
}
use UnbootableReason::*;
set_unbootable_tests! {
test_set_unbootable_no_more_tries: NoMoreTries,
test_set_unbootable_system_update: SystemUpdate,
test_set_unbootable_user_requested: UserRequested,
test_set_unbootable_verification_failure: VerificationFailure,
test_set_unbootable_unknown: Unknown,
}
#[test]
fn test_no_bootable_slots_boot_recovery() {
let mut sb: SlotBlock<AbrData> = Default::default();
let v: Vec<Slot> = sb.slots_iter().collect();
for slot in v {
assert_eq!(
sb.set_slot_unbootable(slot.suffix, UnbootableReason::UserRequested),
Ok(())
);
}
assert_eq!(sb.get_boot_target(), BootTarget::Recovery(RecoveryTarget::Dedicated));
}
#[test]
fn test_set_active_slot() {
let mut sb: SlotBlock<AbrData> = Default::default();
let v: Vec<Slot> = sb.slots_iter().collect();
assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(v[0]));
for slot in v.iter() {
assert_eq!(sb.set_active_slot(slot.suffix), Ok(()));
assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(*slot));
}
}
#[test]
fn test_set_active_slot_no_such_slot() {
let mut sb: SlotBlock<AbrData> = Default::default();
let bad_suffix: Suffix = '$'.into();
assert_eq!(sb.set_active_slot(bad_suffix), Err(Error::NoSuchSlot(bad_suffix)));
}
#[test]
fn test_get_slot_last_set_active() {
let mut sb: SlotBlock<AbrData> = Default::default();
let v: Vec<Slot> = sb.slots_iter().collect();
assert_eq!(sb.set_active_slot(v[0].suffix), Ok(()));
assert_eq!(sb.get_slot_last_set_active(), v[0]);
for slot in v.iter() {
assert_eq!(sb.set_slot_unbootable(slot.suffix, NoMoreTries), Ok(()));
}
assert_eq!(sb.get_slot_last_set_active(), sb.slots_iter().next().unwrap());
}
macro_rules! set_oneshot_tests {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name(){
let mut sb: SlotBlock<AbrData> = Default::default();
assert_eq!(sb.set_oneshot_status($value), Ok(()));
assert_eq!(sb.get_oneshot_status(), Some($value));
assert_eq!(sb.get_boot_target(),
BootTarget::NormalBoot(
Slot{
suffix: 'a'.into(),
priority: DEFAULT_PRIORITY.into(),
bootability: Bootability::Retriable(sb.get_max_retries()),
},
));
}
)*
}
}
set_oneshot_tests! {
test_set_oneshot_bootloader: OneShot::Bootloader,
test_set_oneshot_recovery: OneShot::Continue(RecoveryTarget::Dedicated),
}
#[test]
fn test_clear_oneshot_status() {
let mut sb: SlotBlock<AbrData> = Default::default();
assert_eq!(sb.set_oneshot_status(OneShot::Bootloader), Ok(()));
sb.clear_oneshot_status();
assert_eq!(sb.get_oneshot_status(), None);
}
#[test]
fn test_set_oneshot_mistaken_recovery_slotted() {
let mut sb: SlotBlock<AbrData> = Default::default();
let slot = sb.slots_iter().next().unwrap();
assert_eq!(
sb.set_oneshot_status(OneShot::Continue(RecoveryTarget::Slotted(slot))),
Err(Error::OperationProhibited)
);
}
#[test]
fn test_deserialize_default_to_dirty_cache() {
let mut abr_data: AbrData = Default::default();
// Changing the success both invalidates the crc
// and lets us verify that the deserialized slot block
// uses defaulted backing bytes instead of the provided bytes.
abr_data.slot_data[0].successful = 1;
let sb = SlotBlock::<AbrData>::deserialize(
abr_data.as_bytes(),
"partition_moniker",
0,
BootToken(()),
);
assert_eq!(sb.cache_status(), CacheStatus::Dirty);
assert_eq!(
sb.slots_iter().next().unwrap().bootability,
Bootability::Retriable(DEFAULT_RETRIES.into())
);
}
#[test]
fn test_deserialize_modified_to_clean_cache() {
let mut abr_data: AbrData = Default::default();
abr_data.slot_data[0].successful = 1;
// If we recalculate the crc,
// that just means we have a metadata block that stores
// relevant, non-default information.
abr_data.crc32.set(abr_data.calculate_crc32());
let sb = SlotBlock::<AbrData>::deserialize(
abr_data.as_bytes(),
"partition_moniker",
0,
BootToken(()),
);
assert_eq!(sb.cache_status(), CacheStatus::Clean);
assert_eq!(sb.slots_iter().next().unwrap().bootability, Bootability::Successful);
}
#[test]
fn test_writeback() {
const PARTITION: &str = "test_partition";
const OFFSET: u64 = 2112; // Deliberately wrong to test propagation of parameter.
let mut block_dev: TestBlockDevice =
include_bytes!("../../testdata/writeback_test_disk.bin").as_slice().into();
assert!(block_dev.sync_gpt().is_ok());
let mut sb: SlotBlock<AbrData> = Default::default();
sb.partition = PARTITION;
sb.partition_offset = OFFSET;
let mut read_buffer: [u8; size_of::<AbrData>()] = Default::default();
// Clean cache, write_back is a no-op
sb.write_back(&mut block_dev);
let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
assert!(res.is_ok());
assert_eq!(read_buffer, [0; std::mem::size_of::<AbrData>()]);
// Make a change, write_back writes back to the defined partition
// at the defined offset.
assert_eq!(sb.set_oneshot_status(OneShot::Bootloader), Ok(()));
assert_eq!(sb.cache_status(), CacheStatus::Dirty);
sb.write_back(&mut block_dev);
let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
assert!(res.is_ok());
assert_eq!(read_buffer, sb.get_data().as_bytes());
assert_eq!(sb.cache_status(), CacheStatus::Clean);
}
#[test]
fn test_writeback_with_cursor() {
const PARTITION: &str = "test_partition";
const OFFSET: u64 = 2112; // Deliberately wrong to test propagation of parameter.
let mut block_dev: TestBlockDevice =
include_bytes!("../../testdata/writeback_test_disk.bin").as_slice().into();
assert!(block_dev.sync_gpt().is_ok());
let mut read_buffer: [u8; size_of::<AbrData>()] = Default::default();
let mut abr_data;
let mut sb: SlotBlock<AbrData> = Default::default();
sb.partition = PARTITION;
sb.partition_offset = OFFSET;
// New block to trigger drop on the cursor.
{
let mut cursor = Cursor { ctx: sb, block_dev: &mut block_dev };
assert!(cursor.ctx.set_active_slot('b'.into()).is_ok());
abr_data = cursor.ctx.get_data().clone();
}
// Need to manually recalculate crc because the cursor updates that
// right before writing to disk.
abr_data.prepare_for_sync();
let res = block_dev.read_gpt_partition(PARTITION, OFFSET, &mut read_buffer);
assert!(res.is_ok());
assert_eq!(read_buffer, abr_data.as_bytes());
}
}