Support fuchsia fastboot set_active, reboot mode
Supports "fastboot set_active", "fastboot reboot-bootloader",
"fastboot reboot-recovery" using libabr when operating in standard
Fuchsia A/B/R mode.
Bug: 374776896
Change-Id: I2fab6147ed3508aa3e812b7a1d0a0c8e066104ab
diff --git a/gbl/efi/src/android_boot.rs b/gbl/efi/src/android_boot.rs
index 3eedc71..8c48ac6 100644
--- a/gbl/efi/src/android_boot.rs
+++ b/gbl/efi/src/android_boot.rs
@@ -14,7 +14,7 @@
use crate::{efi_blocks::find_block_devices, fastboot::fastboot, ops::Ops};
use efi::{exit_boot_services, EfiEntry};
-use libgbl::{android_boot::load_android_simple, gbl_print, gbl_println, GblOps, Result};
+use libgbl::{android_boot::load_android_simple, gbl_print, gbl_println, GblOps, Os, Result};
// The following implements a demo for booting Android from disk. It can be run from
// Cuttlefish by adding `--android_efi_loader=<path of this EFI binary>` to the command line.
@@ -31,7 +31,7 @@
// end-to-end test for libraries developed so far.
pub fn android_boot_demo(entry: EfiEntry) -> Result<()> {
let blks = find_block_devices(&entry)?;
- let mut ops = Ops::new(&entry, &blks[..]);
+ let mut ops = Ops::new(&entry, &blks[..], Some(Os::Android));
match ops.should_stop_in_fastboot() {
Ok(true) => {
diff --git a/gbl/efi/src/fuchsia_boot.rs b/gbl/efi/src/fuchsia_boot.rs
index 9ab05ef..aac8819 100644
--- a/gbl/efi/src/fuchsia_boot.rs
+++ b/gbl/efi/src/fuchsia_boot.rs
@@ -14,8 +14,9 @@
use crate::utils::efi_to_zbi_mem_range_type;
#[allow(unused_imports)]
-use crate::utils::get_efi_mem_attr;
-use crate::{efi_blocks::find_block_devices, fastboot::fastboot, ops::Ops};
+use crate::{
+ efi_blocks::find_block_devices, fastboot::fastboot, ops::Ops, utils::get_efi_mem_attr,
+};
use core::fmt::Write;
use efi::{efi_print, efi_println, EfiEntry, EfiMemoryAttributesTable, EfiMemoryMap};
use efi_types::{
@@ -27,7 +28,7 @@
fuchsia_boot::{zircon_check_enter_fastboot, zircon_load_verify_abr, zircon_part_name},
partition::check_part_unique,
IntegrationError::UnificationError,
- Result,
+ Os, Result,
};
use safemath::SafeNum;
use zbi::{zbi_format::zbi_mem_range_t, ZbiContainer, ZbiFlags, ZbiType};
@@ -65,7 +66,7 @@
let mut load_buffer = vec![0u8; 128 * 1024 * 1024]; // 128MB
let (_zbi_items, _kernel, slot) = {
let blks = find_block_devices(&efi_entry)?;
- let mut ops = Ops::new(&efi_entry, &blks[..]);
+ let mut ops = Ops::new(&efi_entry, &blks[..], Some(Os::Fuchsia));
// Checks whether to enter fastboot mode.
if zircon_check_enter_fastboot(&mut ops) {
fastboot(&mut ops)?;
diff --git a/gbl/efi/src/ops.rs b/gbl/efi/src/ops.rs
index 419f56d..b34df2c 100644
--- a/gbl/efi/src/ops.rs
+++ b/gbl/efi/src/ops.rs
@@ -50,7 +50,7 @@
ops::{AvbIoError, AvbIoResult, CertPermanentAttributes, ImageBuffer, SHA256_DIGEST_SIZE},
partition::GblDisk,
slots::{BootToken, Cursor},
- GblOps, Result as GblResult,
+ GblOps, Os, Result as GblResult,
};
use safemath::SafeNum;
use zbi::ZbiContainer;
@@ -84,12 +84,13 @@
pub efi_entry: &'a EfiEntry,
pub disks: &'b [EfiGblDisk<'a>],
pub zbi_bootloader_files_buffer: Vec<u8>,
+ pub os: Option<Os>,
}
impl<'a, 'b> Ops<'a, 'b> {
/// Creates a new instance of [Ops]
- pub fn new(efi_entry: &'a EfiEntry, disks: &'b [EfiGblDisk<'a>]) -> Self {
- Self { efi_entry, disks, zbi_bootloader_files_buffer: Default::default() }
+ pub fn new(efi_entry: &'a EfiEntry, disks: &'b [EfiGblDisk<'a>], os: Option<Os>) -> Self {
+ Self { efi_entry, disks, zbi_bootloader_files_buffer: Default::default(), os }
}
/// Gets the property of an FDT node from EFI FDT.
@@ -255,6 +256,10 @@
self.disks
}
+ fn expected_os(&mut self) -> Result<Option<Os>> {
+ Ok(self.os)
+ }
+
fn zircon_add_device_zbi_items(
&mut self,
container: &mut ZbiContainer<&mut [u8]>,
@@ -450,7 +455,7 @@
mock_efi.con_out.expect_write_str().with(eq("foo bar")).return_const(Ok(()));
let installed = mock_efi.install();
- let mut ops = Ops::new(installed.entry(), &[]);
+ let mut ops = Ops::new(installed.entry(), &[], None);
assert!(write!(&mut ops, "{} {}", "foo", "bar").is_ok());
}
diff --git a/gbl/libefi/src/ab_slots.rs b/gbl/libefi/src/ab_slots.rs
index 5a0e16b..773190c 100644
--- a/gbl/libefi/src/ab_slots.rs
+++ b/gbl/libefi/src/ab_slots.rs
@@ -176,7 +176,7 @@
ops::{AvbIoResult, CertPermanentAttributes, SHA256_DIGEST_SIZE},
partition::GblDisk,
slots::{Bootability, Cursor, RecoveryTarget, UnbootableReason},
- Gbl, GblOps, Result as GblResult,
+ Gbl, GblOps, Os, Result as GblResult,
};
use gbl_storage::{BlockIo, BlockIoNull, Disk, Gpt};
use libgbl::{device_tree::DeviceTreeComponentsRegistry, ops::ImageBuffer};
@@ -267,6 +267,10 @@
&[] as &[GblDisk<Disk<BlockIoNull, &mut [u8]>, Gpt<&mut [u8]>>]
}
+ fn expected_os(&mut self) -> Result<Option<Os>> {
+ Ok(None)
+ }
+
fn zircon_add_device_zbi_items(&mut self, _: &mut ZbiContainer<&mut [u8]>) -> Result<()> {
unimplemented!();
}
diff --git a/gbl/libfastboot/src/lib.rs b/gbl/libfastboot/src/lib.rs
index 8fac123..42cc2a5 100644
--- a/gbl/libfastboot/src/lib.rs
+++ b/gbl/libfastboot/src/lib.rs
@@ -375,6 +375,9 @@
/// * `responder`: An instance of `InfoSender`.
async fn r#continue(&mut self, responder: impl InfoSender) -> CommandResult<()>;
+ /// Backend for `fastboot set_active`.
+ async fn set_active(&mut self, slot: &str, responder: impl InfoSender) -> CommandResult<()>;
+
/// Backend for `fastboot oem ...`.
///
/// # Args
@@ -821,6 +824,23 @@
}
}
+// Handles `fastboot set_active`
+async fn set_active(
+ mut args: Split<'_, char>,
+ transport: &mut impl Transport,
+ fb_impl: &mut impl FastbootImplementation,
+) -> Result<()> {
+ let mut resp = Responder::new(transport);
+ let res = async {
+ let slot = next_arg(&mut args).ok_or("Missing slot")?;
+ fb_impl.set_active(slot, &mut resp).await
+ };
+ match res.await {
+ Ok(_) => reply_okay!(resp, ""),
+ Err(e) => reply_fail!(resp, "{}", e.to_str()),
+ }
+}
+
/// Helper for handling "fastboot oem ...".
async fn oem(
cmd: &str,
@@ -873,6 +893,7 @@
"reboot-bootloader" => reboot(RebootMode::Bootloader, transport, fb_impl).await,
"reboot-fastboot" => reboot(RebootMode::Fastboot, transport, fb_impl).await,
"reboot-recovery" => reboot(RebootMode::Recovery, transport, fb_impl).await,
+ "set_active" => set_active(args, transport, fb_impl).await,
"continue" => {
r#continue(transport, fb_impl).await?;
return Ok(true);
@@ -1014,6 +1035,7 @@
download_buffer: Vec<u8>,
downloaded_size: usize,
reboot_mode: Option<RebootMode>,
+ active_slot: Option<String>,
}
impl FastbootImplementation for FastbootTest {
@@ -1094,6 +1116,11 @@
responder.send_info("Continuing to boot...").await
}
+ async fn set_active(&mut self, slot: &str, _: impl InfoSender) -> CommandResult<()> {
+ self.active_slot = Some(slot.into());
+ Ok(())
+ }
+
async fn oem<'b>(
&mut self,
cmd: &str,
@@ -1625,4 +1652,25 @@
tcp_stream.add_length_prefixed_input(b"continue");
block_on(run_tcp_session(&mut tcp_stream, &mut fastboot_impl)).unwrap();
}
+
+ #[test]
+ fn test_set_active() {
+ let mut fastboot_impl: FastbootTest = Default::default();
+ fastboot_impl.download_buffer = vec![0u8; 1024];
+ let mut transport = TestTransport::new();
+ transport.add_input(b"set_active:a");
+ block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
+ assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"OKAY".into()]));
+ assert_eq!(fastboot_impl.active_slot, Some("a".into()));
+ }
+
+ #[test]
+ fn test_set_active_missing_slot() {
+ let mut fastboot_impl: FastbootTest = Default::default();
+ fastboot_impl.download_buffer = vec![0u8; 1024];
+ let mut transport = TestTransport::new();
+ transport.add_input(b"set_active");
+ block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
+ assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"FAILMissing slot".into()]));
+ }
}
diff --git a/gbl/libgbl/src/fastboot/mod.rs b/gbl/libgbl/src/fastboot/mod.rs
index e9f2cca..ea95e14 100644
--- a/gbl/libgbl/src/fastboot/mod.rs
+++ b/gbl/libgbl/src/fastboot/mod.rs
@@ -15,10 +15,12 @@
//! Fastboot backend for libgbl.
use crate::{
+ fuchsia_boot::GblAbrOps,
gbl_print, gbl_println,
partition::{check_part_unique, GblDisk, PartitionIo},
GblOps,
};
+pub use abr::{mark_slot_active, set_one_shot_bootloader, set_one_shot_recovery, SlotIndex};
use core::{
array::from_fn,
cmp::min,
@@ -267,13 +269,21 @@
resp.send_info("Syncing storage...").await?;
self.sync_all_blocks().await?;
match mode {
- RebootMode::Normal => {
- resp.send_info("Rebooting...").await?;
- resp.send_okay("").await?;
- self.gbl_ops.reboot();
+ RebootMode::Normal => resp.send_info("Rebooting...").await?,
+ RebootMode::Bootloader => {
+ let f = self.gbl_ops.reboot_bootloader()?;
+ resp.send_info("Rebooting to bootloader...").await?;
+ f()
+ }
+ RebootMode::Recovery => {
+ let f = self.gbl_ops.reboot_recovery()?;
+ resp.send_info("Rebooting to recovery...").await?;
+ f()
}
_ => return Err("Unsupported".into()),
}
+ resp.send_okay("").await?;
+ self.gbl_ops.reboot();
Ok(())
}
@@ -439,6 +449,22 @@
Ok(self.sync_all_blocks().await?)
}
+ async fn set_active(&mut self, slot: &str, _: impl InfoSender) -> CommandResult<()> {
+ self.sync_all_blocks().await?;
+ match self.gbl_ops.expected_os_is_fuchsia()? {
+ // TODO(b/374776896): Prioritizes platform specific `set_active_slot` if available.
+ true => Ok(mark_slot_active(
+ &mut GblAbrOps(self.gbl_ops),
+ match slot {
+ "a" => SlotIndex::A,
+ "b" => SlotIndex::B,
+ _ => return Err("Invalid slot index for Fuchsia A/B/R".into()),
+ },
+ )?),
+ _ => Err("Not supported".into()),
+ }
+ }
+
async fn oem<'s>(
&mut self,
cmd: &str,
@@ -731,7 +757,13 @@
#[cfg(test)]
mod test {
use super::*;
- use crate::ops::test::{FakeGblOps, FakeGblOpsStorage};
+ use crate::{
+ ops::test::{FakeGblOps, FakeGblOpsStorage},
+ Os,
+ };
+ use abr::{
+ get_and_clear_one_shot_bootloader, get_boot_slot, mark_slot_unbootable, ABR_DATA_SIZE,
+ };
use core::{
mem::size_of,
pin::{pin, Pin},
@@ -2015,4 +2047,153 @@
listener.dump_usb_out_queue()
);
}
+
+ /// Helper for testing fastboot set_active in fuchsia A/B/R mode.
+ fn test_run_gbl_fastboot_set_active_fuchsia_abr(cmd: &str, slot: SlotIndex) {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let buffers = vec![vec![0u8; 128 * 1024]; 2];
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ let listener: SharedTestListener = Default::default();
+ let (usb, tcp) = (&listener, &listener);
+
+ mark_slot_unbootable(&mut GblAbrOps(&mut gbl_ops), SlotIndex::A).unwrap();
+ mark_slot_unbootable(&mut GblAbrOps(&mut gbl_ops), SlotIndex::B).unwrap();
+
+ // Flash some data to `durable_boot` after A/B/R metadata. This is for testing that sync
+ // storage is done first.
+ let data = vec![0x55u8; 4 * 1024 - ABR_DATA_SIZE];
+ listener.add_usb_input(b"oem gbl-enable-async-block-io");
+ listener.add_usb_input(format!("download:{:#x}", 4 * 1024 - ABR_DATA_SIZE).as_bytes());
+ listener.add_usb_input(&data);
+ listener.add_usb_input(format!("flash:durable_boot//{:#x}", ABR_DATA_SIZE).as_bytes());
+ // Issues set_active commands
+ listener.add_usb_input(cmd.as_bytes());
+ listener.add_usb_input(b"continue");
+ block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+
+ assert_eq!(
+ listener.usb_out_queue(),
+ make_expected_usb_out(&[
+ b"OKAY",
+ b"DATA00000fe0",
+ b"OKAY",
+ b"INFOAn IO task is launched. To sync manually, run \"oem gbl-sync-blocks\".",
+ b"OKAY",
+ b"OKAY",
+ b"INFOSyncing storage...",
+ b"OKAY",
+ ]),
+ "\nActual USB output:\n{}",
+ listener.dump_usb_out_queue()
+ );
+
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (slot, false));
+ // Verifies storage sync
+ assert_eq!(
+ storage[0].partition_io(None).unwrap().dev().io().storage[ABR_DATA_SIZE..],
+ data
+ );
+ }
+
+ #[test]
+ fn test_run_gbl_fastboot_set_active_fuchsia_abr_a() {
+ test_run_gbl_fastboot_set_active_fuchsia_abr("set_active:a", SlotIndex::A);
+ }
+
+ #[test]
+ fn test_run_gbl_fastboot_set_active_fuchsia_abr_b() {
+ test_run_gbl_fastboot_set_active_fuchsia_abr("set_active:b", SlotIndex::B);
+ }
+
+ #[test]
+ fn test_run_gbl_fastboot_set_active_fuchsia_abr_invalid_slot() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let buffers = vec![vec![0u8; 128 * 1024]; 2];
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ let listener: SharedTestListener = Default::default();
+ let (usb, tcp) = (&listener, &listener);
+
+ listener.add_usb_input(b"set_active:r");
+ listener.add_usb_input(b"continue");
+ block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+
+ assert_eq!(
+ listener.usb_out_queue(),
+ make_expected_usb_out(&[
+ b"FAILInvalid slot index for Fuchsia A/B/R",
+ b"INFOSyncing storage...",
+ b"OKAY",
+ ]),
+ "\nActual USB output:\n{}",
+ listener.dump_usb_out_queue()
+ );
+ }
+
+ #[test]
+ fn test_run_gbl_fastboot_fuchsia_reboot_bootloader_abr() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let buffers = vec![vec![0u8; 128 * 1024]; 2];
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ let listener: SharedTestListener = Default::default();
+ let (usb, tcp) = (&listener, &listener);
+
+ listener.add_usb_input(b"reboot-bootloader");
+ listener.add_usb_input(b"continue");
+ block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+
+ assert_eq!(
+ listener.usb_out_queue(),
+ make_expected_usb_out(&[
+ b"INFOSyncing storage...",
+ b"INFORebooting to bootloader...",
+ b"OKAY",
+ b"FAILUnknown",
+ b"INFOSyncing storage...",
+ b"OKAY",
+ ]),
+ "\nActual USB output:\n{}",
+ listener.dump_usb_out_queue()
+ );
+
+ assert_eq!(get_and_clear_one_shot_bootloader(&mut GblAbrOps(&mut gbl_ops)), Ok(true));
+ }
+
+ #[test]
+ fn test_run_gbl_fastboot_fuchsia_reboot_recovery_abr() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let buffers = vec![vec![0u8; 128 * 1024]; 2];
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ let listener: SharedTestListener = Default::default();
+ let (usb, tcp) = (&listener, &listener);
+
+ listener.add_usb_input(b"reboot-recovery");
+ listener.add_usb_input(b"continue");
+ block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+
+ assert_eq!(
+ listener.usb_out_queue(),
+ make_expected_usb_out(&[
+ b"INFOSyncing storage...",
+ b"INFORebooting to recovery...",
+ b"OKAY",
+ b"FAILUnknown",
+ b"INFOSyncing storage...",
+ b"OKAY",
+ ]),
+ "\nActual USB output:\n{}",
+ listener.dump_usb_out_queue()
+ );
+
+ // One shot recovery is set.
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::R, false));
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false));
+ }
}
diff --git a/gbl/libgbl/src/fuchsia_boot/mod.rs b/gbl/libgbl/src/fuchsia_boot/mod.rs
index 371d91d..ed21735 100644
--- a/gbl/libgbl/src/fuchsia_boot/mod.rs
+++ b/gbl/libgbl/src/fuchsia_boot/mod.rs
@@ -35,7 +35,10 @@
const ABR_PARTITION_ALIASES: &[&str] = &[DURABLE_BOOT_PARTITION, MISC_PARTITION];
/// Helper function to find partition given a list of possible aliases.
-fn find_part_aliases<'a, 'b>(ops: &mut impl GblOps<'a>, aliases: &'b [&str]) -> Result<&'b str> {
+fn find_part_aliases<'a, 'b>(
+ ops: &mut (impl GblOps<'a> + ?Sized),
+ aliases: &'b [&str],
+) -> Result<&'b str> {
Ok(*aliases
.iter()
.find(|v| matches!(ops.partition_size(v), Ok(Some(_))))
@@ -43,9 +46,9 @@
}
/// `GblAbrOps` wraps an object implementing `GblOps` and implements the `abr::Ops` trait.
-struct GblAbrOps<'a, T>(&'a mut T);
+pub(crate) struct GblAbrOps<'a, T: ?Sized>(pub &'a mut T);
-impl<'b, T: GblOps<'b>> AbrOps for GblAbrOps<'_, T> {
+impl<'b, T: GblOps<'b> + ?Sized> AbrOps for GblAbrOps<'_, T> {
fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> {
let part = find_part_aliases(self.0, &ABR_PARTITION_ALIASES)?;
self.0.read_from_partition_sync(part, 0, out)
diff --git a/gbl/libgbl/src/lib.rs b/gbl/libgbl/src/lib.rs
index 16a8a14..c580d90 100644
--- a/gbl/libgbl/src/lib.rs
+++ b/gbl/libgbl/src/lib.rs
@@ -63,7 +63,7 @@
pub use boot_reason::KnownBootReason;
pub use error::{IntegrationError, Result};
use liberror::Error;
-pub use ops::GblOps;
+pub use ops::{GblOps, Os};
use overlap::is_overlap;
diff --git a/gbl/libgbl/src/ops.rs b/gbl/libgbl/src/ops.rs
index 64e62ad..f9c3a66 100644
--- a/gbl/libgbl/src/ops.rs
+++ b/gbl/libgbl/src/ops.rs
@@ -17,8 +17,10 @@
pub use crate::image_buffer::ImageBuffer;
use crate::{
error::Result as GblResult,
+ fuchsia_boot::GblAbrOps,
partition::{check_part_unique, read_unique_partition, write_unique_partition, GblDisk},
};
+pub use abr::{set_one_shot_bootloader, set_one_shot_recovery, SlotIndex};
use core::{ffi::CStr, fmt::Write, num::NonZeroUsize, ops::DerefMut, result::Result};
use gbl_async::block_on;
use libutils::aligned_subslice;
@@ -34,6 +36,15 @@
use super::device_tree;
use super::slots;
+/// Target Type of OS to boot.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub enum Os {
+ /// Android
+ Android,
+ /// Fuchsia
+ Fuchsia,
+}
+
// https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// should we use traits for this? or optional/box FnMut?
//
@@ -67,6 +78,32 @@
/// way.
fn reboot(&mut self);
+ /// Reboots into recovery mode
+ ///
+ /// On success, returns a closure that performs the reboot.
+ fn reboot_recovery(&mut self) -> Result<impl FnOnce() + '_, Error> {
+ if self.expected_os_is_fuchsia()? {
+ // TODO(b/363075013): Checks and prioritizes platform specific
+ // `set_boot_reason()`.
+ set_one_shot_recovery(&mut GblAbrOps(self), true)?;
+ return Ok(|| self.reboot());
+ }
+ Err(Error::Unsupported)
+ }
+
+ /// Reboots into bootloader fastboot mode
+ ///
+ /// On success, returns a closure that performs the reboot.
+ fn reboot_bootloader(&mut self) -> Result<impl FnOnce() + '_, Error> {
+ if self.expected_os_is_fuchsia()? {
+ // TODO(b/363075013): Checks and prioritizes platform specific
+ // `set_boot_reason()`.
+ set_one_shot_bootloader(&mut GblAbrOps(self), true)?;
+ return Ok(|| self.reboot());
+ }
+ Err(Error::Unsupported)
+ }
+
/// Returns the list of disk devices on this platform.
///
/// Notes that the return slice doesn't capture the life time of `&self`, meaning that the slice
@@ -131,6 +168,15 @@
}
}
+ /// Returns which OS to load, or `None` to try to auto-detect based on disk layout & contents.
+ fn expected_os(&mut self) -> Result<Option<Os>, Error>;
+
+ /// Returns if the expected_os is fuchsia
+ fn expected_os_is_fuchsia(&mut self) -> Result<bool, Error> {
+ // TODO(b/374776896): Implement auto detection.
+ Ok(self.expected_os()?.map(|v| v == Os::Fuchsia).unwrap_or(false))
+ }
+
/// Adds device specific ZBI items to the given `container`
fn zircon_add_device_zbi_items(
&mut self,
@@ -289,6 +335,7 @@
pub(crate) mod test {
use super::*;
use crate::partition::GblDisk;
+ use abr::{get_and_clear_one_shot_bootloader, get_boot_slot};
use avb::{CertOps, Ops};
use avb_test::TestOps as AvbTestOps;
use core::ops::{Deref, DerefMut};
@@ -364,6 +411,12 @@
/// For returned by `fn get_zbi_bootloader_files_buffer()`
pub zbi_bootloader_files_buffer: Vec<u8>,
+
+ /// For checking that `Self::reboot` is called.
+ pub rebooted: bool,
+
+ /// For return by `Self::expected_os()`
+ pub os: Option<Os>,
}
/// Print `console_out` output, which can be useful for debugging.
@@ -421,7 +474,9 @@
self.stop_in_fastboot.unwrap_or(Ok(false))
}
- fn reboot(&mut self) {}
+ fn reboot(&mut self) {
+ self.rebooted = true;
+ }
fn disks(
&self,
@@ -432,6 +487,10 @@
self.partitions
}
+ fn expected_os(&mut self) -> Result<Option<Os>, Error> {
+ Ok(self.os)
+ }
+
fn zircon_add_device_zbi_items(
&mut self,
container: &mut ZbiContainer<&mut [u8]>,
@@ -523,4 +582,49 @@
unimplemented!();
}
}
+
+ #[test]
+ fn test_fuchsia_reboot_bootloader() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ (gbl_ops.reboot_bootloader().unwrap())();
+ assert!(gbl_ops.rebooted);
+ assert_eq!(get_and_clear_one_shot_bootloader(&mut GblAbrOps(&mut gbl_ops)), Ok(true));
+ }
+
+ #[test]
+ fn test_non_fuchsia_reboot_bootloader() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Android);
+ assert!(gbl_ops.reboot_bootloader().is_err_and(|e| e == Error::Unsupported));
+ assert_eq!(get_and_clear_one_shot_bootloader(&mut GblAbrOps(&mut gbl_ops)), Ok(false));
+ }
+
+ #[test]
+ fn test_fuchsia_reboot_recovery() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Fuchsia);
+ (gbl_ops.reboot_recovery().unwrap())();
+ assert!(gbl_ops.rebooted);
+ // One shot recovery is set.
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::R, false));
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false));
+ }
+
+ #[test]
+ fn test_non_fuchsia_reboot_recovery() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"durable_boot", [0x00u8; 4 * 1024]);
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.os = Some(Os::Android);
+ assert!(gbl_ops.reboot_recovery().is_err_and(|e| e == Error::Unsupported));
+ // One shot recovery is not set.
+ assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false));
+ }
}