diff options
author | Dov Shlachter <dovs@google.com> | 2024-01-30 23:17:16 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-01-30 23:17:16 +0000 |
commit | 496dc41ad6b6a0a57710c7051c89a629ecf552ba (patch) | |
tree | 570ca40be9ec068b7d54ee948ad027073e013a13 | |
parent | 638afc1a9e05d9e2cf0be5500e35f48e8b7e42d6 (diff) | |
parent | f7db52939848fbaf0edeacfc1c60fcc7dba233ad (diff) | |
download | libbootloader-496dc41ad6b6a0a57710c7051c89a629ecf552ba.tar.gz |
Merge "Integrate GBL A/B boot slot API with rest of GBL" into main
-rw-r--r-- | gbl/libgbl/Android.bp | 68 | ||||
-rw-r--r-- | gbl/libgbl/src/error.rs | 3 | ||||
-rw-r--r-- | gbl/libgbl/src/lib.rs | 170 | ||||
-rw-r--r-- | gbl/libgbl/src/ops.rs | 12 | ||||
-rw-r--r-- | gbl/libgbl/src/slots.rs | 13 | ||||
-rw-r--r-- | gbl/libgbl/src/slots/fuchsia.rs (renamed from gbl/libgbl/src/fuchsia.rs) | 166 | ||||
-rw-r--r-- | gbl/libgbl/tests/nostd.rs | 10 |
7 files changed, 259 insertions, 183 deletions
diff --git a/gbl/libgbl/Android.bp b/gbl/libgbl/Android.bp index f0bb406..1f1491f 100644 --- a/gbl/libgbl/Android.bp +++ b/gbl/libgbl/Android.bp @@ -20,28 +20,40 @@ rust_defaults { name: "libgbl_defaults", srcs: ["src/lib.rs"], rustlibs: [ + "libavb_rs", "libbitflags", + "libcrc32fast", + "liblazy_static", + "libring", + "libspin", "libstatic_assertions", + "libzbi", + "libzerocopy", // "sw_digest", ], } +rust_defaults { + name: "libgbl_defaults_nostd", + srcs: ["src/lib.rs"], + rlibs: [ + "libavb_rs_nostd", + "libcrc32fast", + "liblazy_static_nostd", + "libring_nostd", + "libspin_nostd", + "libzbi_nostd", + "libzerocopy_nostd", + ], + no_stdlibs: true, +} + // GBL Rust library. rust_library { name: "libgbl", host_supported: true, crate_name: "gbl", defaults: ["libgbl_defaults"], - rustlibs: [ - "libzbi", - "libavb_rs", - "libring", - "libspin", - "libzbi", - "libzerocopy", - "libcrc32fast", - "liblazy_static", - ], } // Defining rlib here because we want to build with [no_std], but @@ -51,18 +63,7 @@ rust_library { rust_library_rlib { name: "libgbl_nostd", crate_name: "gbl", - defaults: ["libgbl_defaults"], - rlibs: [ - "libzbi_nostd", - "libavb_rs_nostd", - "libring_nostd", - "libspin_nostd", - "libzbi_nostd", - "libzerocopy_nostd", - "libcrc32fast", - "liblazy_static_nostd", - ], - no_stdlibs: true, + defaults: ["libgbl_defaults_nostd"], prefer_rlib: true, } @@ -71,14 +72,18 @@ rust_defaults { name: "libgbl_test_defaults", defaults: ["libgbl_defaults"], rustlibs: [ - "libzbi", - "libavb_rs", + "libhex", "libitertools", + "libavb_rs", + "libbitflags", + "libcrc32fast", + "liblazy_static", "libring", - "libhex", "libspin", "libstatic_assertions", - "liblazy_static", + "libzbi", + "libzerocopy", + ], features: ["alloc"], } @@ -90,13 +95,8 @@ rust_test { } rust_test_host { - name: "libgbl_slot_test", - srcs: ["src/fuchsia.rs"], - rustlibs: [ - "libzbi", - "libcrc32fast", - "libzerocopy", - ], + name: "libgbl_unit_tests", + defaults: ["libgbl_test_defaults"], } // Test building with libgbl in a [no_std] environment. @@ -108,8 +108,8 @@ rust_binary { name: "libgbl_test_nostd", srcs: ["tests/nostd.rs"], rustlibs: [ - "libgbl_nostd", "libbuddy_system_allocator", + "libgbl_nostd", "liblazy_static_nostd", ], // panic=abort to satisfy the eh_personality compile requirement. diff --git a/gbl/libgbl/src/error.rs b/gbl/libgbl/src/error.rs index ef023d3..227bf0a 100644 --- a/gbl/libgbl/src/error.rs +++ b/gbl/libgbl/src/error.rs @@ -28,6 +28,8 @@ pub enum Error { MissingImage, /// Functionality is not implemented NotImplemented, + /// Some combination of parameters and global state prohibits the operation + OperationProhibited, } // Unfortunately thiserror is not available in `no_std` world. @@ -38,6 +40,7 @@ impl Display for Error { Error::Error => "Generic error", Error::MissingImage => "Missing image required to boot system", Error::NotImplemented => "Functionality is not implemented", + Error::OperationProhibited => "Operation is prohibited", }; write!(f, "{str}") } diff --git a/gbl/libgbl/src/lib.rs b/gbl/libgbl/src/lib.rs index 774bb6f..5d0d15b 100644 --- a/gbl/libgbl/src/lib.rs +++ b/gbl/libgbl/src/lib.rs @@ -33,9 +33,9 @@ extern crate lazy_static; extern crate spin; extern crate zbi; -use core::fmt::{Debug, Display, Formatter}; +use core::fmt::Debug; use lazy_static::lazy_static; -use spin::Mutex; +use spin::{Mutex, MutexGuard}; pub mod boot_mode; pub mod boot_reason; @@ -47,6 +47,8 @@ pub mod ops; /// querying and modifying slotted boot behavior. pub mod slots; +use slots::{BootTarget, BootToken, Cursor, OneShot}; + #[cfg(feature = "sw_digest")] pub mod sw_digest; @@ -164,27 +166,6 @@ pub fn get_images<'a: 'b, 'b: 'c, 'c>( (boot_image, init_boot_image, vendor_boot_image, partitions_ram_map) } -// TODO: b/312607649 - Boot slot placeholder -#[derive(Debug, PartialEq)] -#[repr(u32)] -/// Boot slots used by ABR model -pub enum BootSlot { - /// '_a' - A = 0, - /// '_b' - B = 1, -} - -impl Display for BootSlot { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let str = match self { - BootSlot::A => "a", - BootSlot::B => "b", - }; - write!(f, "{str}") - } -} - // TODO: b/312608785 - helper function that would track slices don't overlap // [core::slice::from_raw_parts_mut] is not stable for `const` so we have to use `lazy_static` for // initialization of constants. @@ -225,12 +206,13 @@ lazy_static! { }; } +static BOOT_TOKEN: Mutex<Option<BootToken>> = Mutex::new(Some(BootToken(()))); + #[derive(Debug)] /// GBL object that provides implementation of helpers for boot process. pub struct Gbl<'a, D, C> { ops: &'a mut dyn GblOps<D, C>, image_verification: bool, - boot_token: Option<slots::BootToken>, } impl<'a, D, C> Gbl<'a, D, C> @@ -242,14 +224,14 @@ where where 'b: 'a, { - Gbl { ops, image_verification: true, boot_token: Some(slots::BootToken(())) } + Gbl { ops, image_verification: true } } fn new_no_verification<'b>(ops: &'b mut impl GblOps<D, C>) -> Gbl<'a, D, C> where 'b: 'a, { - Gbl { ops, image_verification: false, boot_token: Some(slots::BootToken(())) } + Gbl { ops, image_verification: false } } } @@ -258,36 +240,6 @@ where D: Digest, C: Context<D>, { - /// Get Boot Mode - /// - /// # Returns - /// - /// * `Ok(())` - on success - /// * `Err(Error)` - on failure - pub fn get_boot_mode(&self) -> Result<BootMode> { - unimplemented!(); - } - - /// Reset Boot Mode (strictly reset generic android struct - not vendor specific) - /// - /// # Returns - /// - /// * `Ok(())` - on success - /// * `Err(Error)` - on failure - pub fn reset_boot_mode(&mut self) -> Result<()> { - unimplemented!(); - } - - /// Get Boot Slot - /// - /// # Returns - /// - /// * `Ok(&str)` - Slot Suffix as [BootSolt] enum - /// * `Err(Error)` - on failure - pub fn get_boot_slot(&self) -> Result<BootSlot> { - unimplemented!(); - } - /// Verify + Load Image Into memory /// /// Load from disk, validate with AVB @@ -295,7 +247,7 @@ where /// # Arguments /// * `partitions_ram_map` - Partitions to verify with optional address to load image to. /// * `avb_verification_flags` - AVB verification flags/options - /// * `boot_slot` - Optional Boot Slot + /// * `boot_target` - [Optional] Boot Target /// /// # Returns /// @@ -306,7 +258,7 @@ where &self, partitions_ram_map: &mut [PartitionRamMap], avb_verification_flags: AvbVerificationFlags, - boot_slot: Option<&BootSlot>, + boot_target: Option<BootTarget>, ) -> Result<&'b mut [AvbDescriptor]> where 'a: 'b, @@ -319,6 +271,20 @@ where unimplemented!("partition loading and verification"); } + /// Load Slot Manager Interface + /// + /// The default implementation loads from the `durable_boot` partition + /// and writes changes back on the destruction of the cursor. + /// + /// # Returns + /// + /// * `Ok(Cursor)` - Cursor object that manages a Manager + /// * `Err(Error)` - on failure + pub fn load_slot_interface(&mut self) -> Result<Cursor> { + let boot_token = BOOT_TOKEN.lock().take().ok_or(Error::OperationProhibited)?; + self.ops.load_slot_interface(boot_token).map_err(|_| Error::OperationProhibited) + } + /// Info Load /// /// Unpack boot image in RAM @@ -326,7 +292,7 @@ where /// # Arguments /// * `boot_image_buffer` - Buffer that contains (Optionally Verified) Boot Image /// * `boot_mode` - Boot Mode - /// * `boot_slot` - [Optional] Boot Slot + /// * `boot_target` - [Optional] Boot Target /// /// # Returns /// @@ -336,8 +302,7 @@ where pub fn unpack_boot_image( &self, boot_image_buffer: &BootImage, - boot_mode: &BootMode, - boot_slot: Option<&BootSlot>, + boot_target: Option<BootTarget>, ) -> Result<InfoStruct> { unimplemented!(); } @@ -388,7 +353,7 @@ where info: &InfoStruct, vendor_boot_image: &VendorBootImage, init_boot_image: &InitBootImage, - ramdisk_load_buffer: &mut [u8], + ramdisk: &mut Ramdisk, bootconfig_load_buffer: &mut [u8], ) -> Result<&'static str> { unimplemented!(); @@ -422,6 +387,7 @@ where /// * `ramdisk_bootconfi_load_buffer` - Concatenated Ramdisk, (Bootconfig if present) Load /// buffer /// * `dtb_load_buffer` - DTB Load buffer + /// * `boot_token` - Consumable boot token /// /// # Returns /// @@ -433,6 +399,7 @@ where kernel_load_buffer: KernelImage, ramdisk_load_buffer: Ramdisk, dtb_load_buffer: Dtb, + boot_token: BootToken, ) -> Result<()> { unimplemented!(); } @@ -445,6 +412,7 @@ where /// # Arguments /// * `partitions_ram_map` - Partitions to verify and optional address for them to be loaded. /// * `avb_verification_flags` - AVB verification flags/options + /// * `slot_cursor` - Cursor object that manages interactions with boot slot management /// /// # Returns /// @@ -455,30 +423,60 @@ where &mut self, partitions_ram_map: &'d mut [PartitionRamMap<'b, 'c>], avb_verification_flags: AvbVerificationFlags, + slot_cursor: Cursor, ) -> Result<()> { - let mut boot_mode = self.get_boot_mode()?; - self.reset_boot_mode()?; - - // Handle fastboot mode - if boot_mode == BootMode::Bootloader { - let res = self.ops.do_fastboot(); - match res { - // After `fastboot continue` we need to requery mode and continue usual process. - Ok(_) => { - boot_mode = self.get_boot_mode()?; - } - // Fastboot is not available so we try continue + // TODO(dovs): + // * Change the receiver of ops.load_slot_interface to be &mut self + // * Add partition write capabilites to slot manager + let mut dtb_load_buffer = DTB.lock(); + let dtb = Dtb(&mut dtb_load_buffer); + let mut ramdisk_load_buffer = RAMDISK.lock(); + let mut kernel_load_buffer = KERNEL_IMAGE.lock(); + let mut ramdisk = Ramdisk(&mut ramdisk_load_buffer); + + // Call the inner method which consumes the cursor + // in order to properly manager cursor lifetime + // and cleanup. + let (kernel_image, token) = self.lvb_inner( + &mut ramdisk, + &mut kernel_load_buffer, + partitions_ram_map, + avb_verification_flags, + slot_cursor, + )?; + + self.kernel_jump(kernel_image, ramdisk, dtb, token) + } + + fn lvb_inner<'b: 'c, 'c, 'd: 'b>( + &self, + ramdisk: &mut Ramdisk, + kernel_load_buffer: &mut MutexGuard<&'static mut [u8]>, + partitions_ram_map: &'d mut [PartitionRamMap<'b, 'c>], + avb_verification_flags: AvbVerificationFlags, + slot_cursor: Cursor, + ) -> Result<(KernelImage, BootToken)> { + let mut oneshot_status = slot_cursor.ctx.get_oneshot_status(); + slot_cursor.ctx.clear_oneshot_status(); + + if oneshot_status == Some(OneShot::Bootloader) { + match self.ops.do_fastboot(&slot_cursor) { + Ok(_) => oneshot_status = slot_cursor.ctx.get_oneshot_status(), Err(Error::NotImplemented) => (), - // Fail on any other error Err(e) => return Err(e), } } - let boot_slot = self.get_boot_slot()?; + let boot_target = match oneshot_status { + None | Some(OneShot::Bootloader) => slot_cursor.ctx.get_boot_target(), + Some(OneShot::Continue(target)) => target, + }; + + // TODO b/312608785: handle the failure by marking a failed boot attempt let avb_descriptors = self.load_and_verify_image( partitions_ram_map, AvbVerificationFlags(0), - Some(&boot_slot), + Some(boot_target), )?; let (boot_image, init_boot_image, vendor_boot_image, _) = @@ -487,27 +485,27 @@ where let vendor_boot_image = vendor_boot_image.ok_or(Error::MissingImage)?; let init_boot_image = init_boot_image.ok_or(Error::MissingImage)?; - let info_struct = self.unpack_boot_image(&boot_image, &boot_mode, Some(&boot_slot))?; + let info_struct = self.unpack_boot_image(&boot_image, Some(boot_target))?; - let mut kernel_load_buffer = KERNEL_IMAGE.lock(); - let kernel_image = self.kernel_load(&info_struct, boot_image, &mut kernel_load_buffer)?; + let kernel_image = self.kernel_load(&info_struct, boot_image, kernel_load_buffer)?; - let mut ramdisk_load_buffer = RAMDISK.lock(); let mut bootconfig_load_buffer = BOOTCONFIG.lock(); let cmd_line = self.ramdisk_bootconfig_load( &info_struct, &vendor_boot_image, &init_boot_image, - &mut ramdisk_load_buffer, + ramdisk, &mut bootconfig_load_buffer, )?; self.dtb_update_and_load(&info_struct, vendor_boot_image)?; - let mut dtb_load_buffer = DTB.lock(); - let dtb = Dtb(&mut dtb_load_buffer); - let ramdisk = Ramdisk(&mut ramdisk_load_buffer); - self.kernel_jump(kernel_image, ramdisk, dtb) + let token = slot_cursor + .ctx + .mark_boot_attempt(boot_target) + .map_err(|_| Error::OperationProhibited)?; + + Ok((kernel_image, token)) } } diff --git a/gbl/libgbl/src/ops.rs b/gbl/libgbl/src/ops.rs index d7c847c..0dd924b 100644 --- a/gbl/libgbl/src/ops.rs +++ b/gbl/libgbl/src/ops.rs @@ -26,6 +26,8 @@ use alloc::ffi::CString; use avb::{IoError, Ops, PublicKeyForPartitionInfo}; use core::{ffi::CStr, fmt::Debug, ptr::NonNull}; +use super::slots; + /// TODO: b/312607649 - Placeholder. Use result type from `avb` when it is stable and public pub type AvbResult<T> = core::result::Result<T, IoError>; @@ -55,15 +57,19 @@ pub trait GblOps<D: Digest, C: Context<D>>: Ops + Debug { 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(&self) -> Result<()> { + fn do_fastboot(&self, cursor: &slots::Cursor) -> Result<()> { Err(Error::NotImplemented) } /// TODO: b/312607649 - placeholder interface for Gbl specific callbacks that uses alloc. #[cfg(feature = "alloc")] fn gbl_alloc_extra_action(&mut self, s: &str) -> Result<()>; + + /// Load and initialize a slot manager and return a cursor over the manager on success. + fn load_slot_interface(&mut self, boot_token: slots::BootToken) -> Result<slots::Cursor>; } /// Default [GblOps] implementation that returns errors and does nothing. @@ -112,6 +118,10 @@ where D: Digest, C: Context<D>, { + fn load_slot_interface(&mut self, boot_token: slots::BootToken) -> Result<slots::Cursor> { + Err(Error::OperationProhibited) + } + #[cfg(feature = "alloc")] fn gbl_alloc_extra_action(&mut self, s: &str) -> Result<()> { let _c_string = CString::new(s); diff --git a/gbl/libgbl/src/slots.rs b/gbl/libgbl/src/slots.rs index cdf40ae..2456b38 100644 --- a/gbl/libgbl/src/slots.rs +++ b/gbl/libgbl/src/slots.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// Export the default implementation +pub mod fuchsia; + /// A type safe container for describing the number of retries a slot has left /// before it becomes unbootable. /// Slot tries can only be compared to, assigned to, or assigned from other @@ -246,7 +249,7 @@ impl<'a> Iterator for SlotIterator<'a> { /// /// if let Some(Continue(oneshot_target)) = manager.get_oneshot_status() { /// let regular_target = manager.get_boot_target(); -/// if regular_target == oneshot_target() { +/// if regular_target == oneshot_target { /// println!("The oneshot and regular boot targets are equal"); /// } else { /// println!("The oneshot and regular boot targets are NOT equal, which is VALID"); @@ -285,7 +288,7 @@ pub trait Manager: private::SlotGet { /// the slot last set active cannot be Recovery. fn get_slot_last_set_active(&self) -> Slot; - /// Given a slot suffix, updates internal metadata (usually the retry count) + /// Given a boot target, updates internal metadata (usually the retry count) /// indicating that the system will have tried to boot the slot. /// Returns Ok(BootToken) on success to verify that boot attempt metadata has been updated. /// The token must be consumed by `kernel_jump`. @@ -297,7 +300,11 @@ pub trait Manager: private::SlotGet { /// Note: mark_boot_attempt is NOT idempotent. /// It is intended to be called EXACTLY once, /// right before jumping into the kernel. - fn mark_boot_attempt(&mut self, slot_suffix: Suffix) -> Result<BootToken, Error>; + /// + /// Note: mark_boot_attempt takes a BootTarget to facilitate generating + /// the boot token when booting to recovery. If the boot target is recovery, + /// then implementations SHOULD NOT update internal metadata. + fn mark_boot_attempt(&mut self, boot_target: BootTarget) -> Result<BootToken, Error>; /// Attempts to set the active slot. /// diff --git a/gbl/libgbl/src/fuchsia.rs b/gbl/libgbl/src/slots/fuchsia.rs index 8a046ee..0ada37e 100644 --- a/gbl/libgbl/src/fuchsia.rs +++ b/gbl/libgbl/src/slots/fuchsia.rs @@ -12,47 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(not(test), no_std)] -pub mod slots; extern crate bitflags; extern crate crc32fast; extern crate zerocopy; -use bitflags::bitflags; -use core::mem::size_of; -use crc32fast::Hasher; -use slots::{ +use super::{ BootTarget, BootToken, Bootability, Error, Manager, OneShot, Slot, SlotIterator, Suffix, UnbootableReason, }; +use bitflags::bitflags; +use core::mem::size_of; +use crc32fast::Hasher; use zerocopy::byteorder::big_endian::U32 as BigEndianU32; use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref}; /// Custom error type #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AbrSlotError { + /// The magic number field was corrupted BadMagic, + /// The major version of the structure is unsupported BadVersion, + /// The struct crc check failed BadCrc, + /// The deserialization buffer is too small BufferTooSmall, } #[derive(Debug, PartialEq, Eq)] -pub enum CacheStatus { +enum CacheStatus { Clean, Dirty, } -pub const DEFAULT_PRIORITY: u8 = 15; -pub const DEFAULT_RETRIES: u8 = 7; +const DEFAULT_PRIORITY: u8 = 15; +const DEFAULT_RETRIES: u8 = 7; #[repr(C, packed)] #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)] -pub(crate) struct AbrSlotData { - pub priority: u8, - pub tries: u8, - pub successful: u8, - pub unbootable_reason: u8, +struct AbrSlotData { + priority: u8, + tries: u8, + successful: u8, + unbootable_reason: u8, } impl Default for AbrSlotData { @@ -68,17 +70,23 @@ impl Default for AbrSlotData { #[repr(C, packed)] #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)] -pub struct OneShotFlags(pub u8); +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; + /// Slot mask const SLOT = 1 << 2; + /// Oneshot boot to slot A const SLOT_A = (1 << 4) | Self::SLOT.bits(); + /// Oneshot boot to slot B const SLOT_B = (1 << 5) | Self::SLOT.bits(); } } @@ -129,15 +137,15 @@ const ABR_VERSION_MINOR: u8 = 3; #[repr(C, packed)] #[derive(Copy, Clone, Debug, PartialEq, Eq, AsBytes, FromBytes, FromZeroes)] -pub(crate) struct AbrData { - pub magic: [u8; 4], - pub version_major: u8, - pub version_minor: u8, - pub reserved: [u8; 2], - pub slot_data: [AbrSlotData; 2], - pub oneshot_flag: OneShotFlags, - pub reserved2: [u8; 11], - pub crc32: BigEndianU32, +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 { @@ -168,22 +176,32 @@ impl Default for AbrData { } #[derive(Debug, PartialEq, Eq)] -pub(crate) struct SlotBlock { +/// Default implementation for Manager. +/// Represents a partition-backed slot control block with two slots, A and B, +/// a special recovery partition, R, and support for oneshot boot. +/// Includes an Option<BootToken> to support `mark_boot_attempt` +/// and a cache status to support lazy write-back on destruction. +pub struct SlotBlock { cache_status: CacheStatus, boot_token: Option<BootToken>, abr_data: AbrData, } impl SlotBlock { - pub fn get_mut_data(&mut self) -> &mut AbrData { + fn get_mut_data(&mut self) -> &mut AbrData { self.cache_status = CacheStatus::Dirty; &mut self.abr_data } - pub fn get_data(&self) -> &AbrData { + fn get_data(&self) -> &AbrData { &self.abr_data } + /// Attempt to deserialize a slot control block + /// + /// # Returns + /// * `Ok(SlotBlock)` - on success returns a SlotBlock that wraps a copy of the serialized data + /// * `Err(AbrSlotError)` - on failure pub fn deserialize<B: ByteSlice>( buffer: B, boot_token: BootToken, @@ -209,7 +227,7 @@ impl SlotBlock { }) } - pub fn new(boot_token: BootToken) -> Self { + pub(crate) fn new(boot_token: BootToken) -> Self { Self { boot_token: Some(boot_token), ..Default::default() } } } @@ -226,7 +244,7 @@ impl Default for SlotBlock { } } -impl slots::private::SlotGet for SlotBlock { +impl super::private::SlotGet for SlotBlock { fn get_slot_by_number(&self, number: usize) -> Result<Slot, Error> { let abr_slot = self.abr_data.slot_data.get(number).ok_or(Error::Other)?; @@ -293,25 +311,34 @@ impl Manager for SlotBlock { Ok(()) } - fn mark_boot_attempt(&mut self, slot_suffix: Suffix) -> Result<BootToken, Error> { - let (idx, slot) = self.get_index_and_slot_with_suffix(slot_suffix)?; - let token = self.boot_token.take().ok_or(Error::OperationProhibited)?; - - match slot.bootability { - Bootability::Unbootable(_) => { - self.boot_token = Some(token); - Err(Error::OperationProhibited) + fn mark_boot_attempt(&mut self, boot_target: BootTarget) -> Result<BootToken, Error> { + let target_slot = match boot_target { + BootTarget::NormalBoot(slot) => slot, + BootTarget::Recovery(Some(_)) => Err(Error::OperationProhibited)?, + BootTarget::Recovery(None) => { + // Even though boot to recovery does not cause a metadata update, + // we still need to gate access to the boot token. + return self.boot_token.take().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.boot_token.take().ok_or(Error::OperationProhibited)?; + Ok(token) + } + Bootability::Successful => { + let token = self.boot_token.take().ok_or(Error::OperationProhibited)?; Ok(token) } - Bootability::Successful => Ok(token), } } @@ -367,6 +394,8 @@ impl Manager for SlotBlock { let data = self.get_mut_data(); data.version_minor = ABR_VERSION_MINOR; data.crc32 = data.calculate_crc32().into(); + + // TODO(dovs): write data back to partition } } @@ -467,31 +496,34 @@ mod test { #[test] fn test_slot_mark_boot_attempt() { let mut sb: SlotBlock = Default::default(); - let suffix: Suffix = 'a'.into(); - assert_eq!(sb.mark_boot_attempt(suffix), Ok(BootToken(()))); + 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, + 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(suffix), Err(Error::OperationProhibited)); + 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 = Default::default(); sb.get_mut_data().slot_data[0].tries = 1; - let suffix: Suffix = 'a'.into(); - assert_eq!(sb.mark_boot_attempt(suffix), Ok(BootToken(()))); + 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, + suffix: slot.suffix, priority: DEFAULT_PRIORITY.into(), bootability: Bootability::Unbootable(UnbootableReason::NoMoreTries) } @@ -502,28 +534,54 @@ mod test { fn test_slot_mark_boot_attempt_successful() { let mut sb: SlotBlock = Default::default(); sb.get_mut_data().slot_data[0].successful = 1; - let slot = Slot { + let target = BootTarget::NormalBoot(Slot { suffix: 'a'.into(), priority: DEFAULT_PRIORITY.into(), bootability: Bootability::Successful, - }; - assert_eq!(sb.mark_boot_attempt(slot.suffix), Ok(BootToken(()))); - assert_eq!(sb.get_boot_target(), BootTarget::NormalBoot(slot)); + }); + 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 = Default::default(); - let suffix: Suffix = '$'.into(); - assert_eq!(sb.mark_boot_attempt(suffix), Err(Error::NoSuchSlot(suffix))); + 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 = Default::default(); + let recovery_tgt = BootTarget::Recovery(None); + 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 = Default::default(); + let slot: Slot = Default::default(); + assert_eq!( + sb.mark_boot_attempt(BootTarget::Recovery(Some(slot))), + Err(Error::OperationProhibited) + ); } #[test] fn test_slot_mark_tried_unbootable() { let mut sb: SlotBlock = Default::default(); - let suffix: Suffix = 'b'.into(); - assert_eq!(sb.set_slot_unbootable(suffix, UnbootableReason::UserRequested), Ok(())); - assert_eq!(sb.mark_boot_attempt(suffix), Err(Error::OperationProhibited)); + 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 { diff --git a/gbl/libgbl/tests/nostd.rs b/gbl/libgbl/tests/nostd.rs index fe3a99f..157799b 100644 --- a/gbl/libgbl/tests/nostd.rs +++ b/gbl/libgbl/tests/nostd.rs @@ -21,7 +21,7 @@ #![no_main] #![no_std] -use core::panic::PanicInfo; +// use core::panic::PanicInfo; use gbl as _; @@ -33,10 +33,10 @@ static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new(); static mut HEAP: [u8; 65536] = [0; 65536]; -#[panic_handler] -fn panic(_: &PanicInfo) -> ! { - loop {} -} +// #[panic_handler] +// fn panic(_: &PanicInfo) -> ! { +// loop {} +// } /// main() entry point replacement required by [no_std]. #[no_mangle] |