Add slot support to android_main
Bug: 383620444
Change-Id: Ic16c840bde03c8a0838e79a9e5ec78f3fb5f6323
diff --git a/gbl/efi/src/ops.rs b/gbl/efi/src/ops.rs
index 2ce0432..7eda2fb 100644
--- a/gbl/efi/src/ops.rs
+++ b/gbl/efi/src/ops.rs
@@ -608,6 +608,39 @@
})
}
+ #[cfg(not(test))]
+ fn get_current_slot(&mut self) -> Result<Slot> {
+ // TODO(b/383620444): GBL EFI slot protocol is currently implemented on a few platforms such
+ // as Cuttlefish but is out of sync. Wait until protocol is more stable and all platforms
+ // pick up the latest before enabling.
+ Err(Error::Unsupported)
+ }
+
+ #[cfg(not(test))]
+ fn get_next_slot(&mut self, _: bool) -> Result<Slot> {
+ // TODO(b/383620444): See `get_current_slot()`.
+ Err(Error::Unsupported)
+ }
+
+ #[cfg(not(test))]
+ fn set_active_slot(&mut self, _: u8) -> Result<()> {
+ // TODO(b/383620444): See `get_current_slot()`.
+ Err(Error::Unsupported)
+ }
+
+ #[cfg(not(test))]
+ fn set_reboot_reason(&mut self, _: RebootReason) -> Result<()> {
+ // TODO(b/383620444): See `get_current_slot()`.
+ Err(Error::Unsupported)
+ }
+
+ #[cfg(not(test))]
+ fn get_reboot_reason(&mut self) -> Result<RebootReason> {
+ // TODO(b/383620444): See `get_current_slot()`.
+ Err(Error::Unsupported)
+ }
+
+ #[cfg(test)]
fn get_current_slot(&mut self) -> Result<Slot> {
// TODO(b/363075013): Refactors the opening of slot protocol into a common helper once
// `MockBootServices::find_first_and_open` is updated to return Protocol<'_, T>.
@@ -619,6 +652,7 @@
.try_into()
}
+ #[cfg(test)]
fn get_next_slot(&mut self, mark_boot_attempt: bool) -> Result<Slot> {
self.efi_entry
.system_table()
@@ -628,6 +662,7 @@
.try_into()
}
+ #[cfg(test)]
fn set_active_slot(&mut self, slot: u8) -> Result<()> {
self.efi_entry
.system_table()
@@ -636,6 +671,7 @@
.set_active_slot(slot)
}
+ #[cfg(test)]
fn set_reboot_reason(&mut self, reason: RebootReason) -> Result<()> {
self.efi_entry
.system_table()
@@ -644,6 +680,7 @@
.set_boot_reason(gbl_to_efi_boot_reason(reason), b"")
}
+ #[cfg(test)]
fn get_reboot_reason(&mut self) -> Result<RebootReason> {
let mut subreason = [0u8; 128];
self.efi_entry
@@ -656,6 +693,8 @@
}
/// Converts a [GblEfiBootReason] to [RebootReason].
+// TODO(b/383620444): Remove the attribute once all boards picks up the stable Slot protocol.
+#[allow(dead_code)]
fn efi_to_gbl_boot_reason(reason: GblEfiBootReason) -> RebootReason {
match reason {
GBL_EFI_BOOT_REASON_RECOVERY => RebootReason::Recovery,
@@ -666,6 +705,8 @@
}
/// Converts a [RebootReason] to [GblEfiBootReason].
+// TODO(b/383620444): Remove the attribute once all boards picks up the stable Slot protocol.
+#[allow(dead_code)]
fn gbl_to_efi_boot_reason(reason: RebootReason) -> GblEfiBootReason {
match reason {
RebootReason::Recovery => GBL_EFI_BOOT_REASON_RECOVERY,
diff --git a/gbl/libgbl/src/android_boot/mod.rs b/gbl/libgbl/src/android_boot/mod.rs
index 1b6b0db..690a6c7 100644
--- a/gbl/libgbl/src/android_boot/mod.rs
+++ b/gbl/libgbl/src/android_boot/mod.rs
@@ -17,7 +17,7 @@
use crate::{
constants::{FDT_ALIGNMENT, KERNEL_ALIGNMENT, PAGE_SIZE},
device_tree::{DeviceTreeComponentSource, DeviceTreeComponentsRegistry},
- gbl_print, gbl_println, GblOps, Result,
+ gbl_print, gbl_println, GblOps, Result, SuffixBytes,
};
use bootimg::{BootImage, VendorImageHeader};
use bootparams::{bootconfig::BootConfigBuilder, commandline::CommandlineBuilder};
@@ -622,9 +622,34 @@
.unwrap_or(AndroidBootMode::Normal);
gbl_println!(ops, "Boot mode from BCB: {}", boot_mode);
+ let slot_idx = match ops.get_boot_slot(true) {
+ Ok(slot) => {
+ // TODO(b/383620444): Checks whether fastboot has set a different active slot and resets
+ // if it does.
+
+ // Currently we assume slot suffix only takes value within 'a' to 'z'. Revisit if this
+ // is not the case.
+ //
+ // It's a little awkward to convert suffix char to integer which will then be converted
+ // back to char by the API. Consider passing in the char bytes directly.
+ let suffix_bytes = SuffixBytes::from(slot.suffix);
+ suffix_bytes[0] - b'a'
+ }
+ Err(Error::Unsupported) => {
+ // Default to slot A if slotting is not supported.
+ // Slotless partition name is currently not supported. Revisit if this causes problems.
+ gbl_println!(ops, "Slotting is not supported. Choose A slot by default");
+ 0
+ }
+ Err(e) => {
+ gbl_println!(ops, "Failed to get boot slot: {e}");
+ return Err(e.into());
+ }
+ };
+
// TODO(b/383620444): Add slot and fastboot support.
let is_recovery = matches!(boot_mode, AndroidBootMode::Recovery);
- android_load_verify_fixup(ops, 0, is_recovery, load)
+ android_load_verify_fixup(ops, slot_idx, is_recovery, load)
}
#[cfg(test)]
@@ -632,7 +657,7 @@
use super::*;
use crate::{
gbl_avb::state::{BootStateColor, KeyValidationStatus},
- ops::test::{FakeGblOps, FakeGblOpsStorage},
+ ops::test::{slot, FakeGblOps, FakeGblOpsStorage},
tests::AlignedBuffer,
};
use load::tests::{
@@ -1268,6 +1293,7 @@
ops.avb_ops.unlock_state = Ok(false);
ops.avb_ops.rollbacks = HashMap::from([(TEST_ROLLBACK_INDEX_LOCATION, Ok(0))]);
ops.avb_key_validation_status = Some(Ok(KeyValidationStatus::Valid));
+ ops.current_slot = Some(Ok(slot('a')));
ops
}
@@ -1313,4 +1339,95 @@
let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
checks_loaded_v2_slot_a_recovery_mode(ramdisk, kernel)
}
+
+ /// Helper for checking V2 image loaded from slot B and in normal mode.
+ fn checks_loaded_v2_slot_b_normal_mode(ramdisk: &[u8], kernel: &[u8]) {
+ let expected_bootconfig = AvbResultBootconfigBuilder::new()
+ .vbmeta_size(read_test_data("vbmeta_v2_b.img").len())
+ .digest(read_test_data_as_str("vbmeta_v2_b.digest.txt").strip_suffix("\n").unwrap())
+ .public_key_digest(TEST_PUBLIC_KEY_DIGEST)
+ .extra(FakeGblOps::GBL_TEST_BOOTCONFIG)
+ .extra("androidboot.force_normal_boot=1\n")
+ .extra(format!("androidboot.slot_suffix=_b\n"))
+ .build();
+ check_ramdisk(ramdisk, &read_test_data("generic_ramdisk_b.img"), &expected_bootconfig);
+ assert_eq!(kernel, read_test_data("kernel_b.img"));
+ }
+
+ #[test]
+ fn test_android_main_slotted_gbl_slot_a() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
+ storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
+ storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
+
+ let mut ops = default_test_gbl_ops(&storage);
+ let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
+ let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
+ assert_eq!(ops.mark_boot_attempt_called, 0);
+ checks_loaded_v2_slot_a_normal_mode(ramdisk, kernel)
+ }
+
+ #[test]
+ fn test_android_main_slotless_gbl_slot_a() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
+ storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
+ storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
+
+ let mut ops = default_test_gbl_ops(&storage);
+ ops.current_slot = Some(Err(Error::Unsupported));
+ ops.next_slot = Some(Ok(slot('a')));
+ let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
+ let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
+ assert_eq!(ops.mark_boot_attempt_called, 1);
+ checks_loaded_v2_slot_a_normal_mode(ramdisk, kernel)
+ }
+
+ #[test]
+ fn test_android_main_slotted_gbl_slot_b() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"boot_b", read_test_data("boot_v2_b.img"));
+ storage.add_raw_device(c"vbmeta_b", read_test_data("vbmeta_v2_b.img"));
+ storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
+
+ let mut ops = default_test_gbl_ops(&storage);
+ ops.current_slot = Some(Ok(slot('b')));
+
+ let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
+ let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
+ assert_eq!(ops.mark_boot_attempt_called, 0);
+ checks_loaded_v2_slot_b_normal_mode(ramdisk, kernel)
+ }
+
+ #[test]
+ fn test_android_main_slotless_gbl_slot_b() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"boot_b", read_test_data("boot_v2_b.img"));
+ storage.add_raw_device(c"vbmeta_b", read_test_data("vbmeta_v2_b.img"));
+ storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
+
+ let mut ops = default_test_gbl_ops(&storage);
+ ops.current_slot = Some(Err(Error::Unsupported));
+ ops.next_slot = Some(Ok(slot('b')));
+ let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
+ let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
+ assert_eq!(ops.mark_boot_attempt_called, 1);
+ checks_loaded_v2_slot_b_normal_mode(ramdisk, kernel);
+ }
+
+ #[test]
+ fn test_android_main_unsupported_slot_default_to_a() {
+ let mut storage = FakeGblOpsStorage::default();
+ storage.add_raw_device(c"boot_a", read_test_data("boot_v2_a.img"));
+ storage.add_raw_device(c"vbmeta_a", read_test_data("vbmeta_v2_a.img"));
+ storage.add_raw_device(c"misc", vec![0u8; 4 * 1024 * 1024]);
+
+ let mut ops = default_test_gbl_ops(&storage);
+ ops.current_slot = Some(Err(Error::Unsupported));
+ ops.next_slot = Some(Err(Error::Unsupported));
+ let mut load_buffer = AlignedBuffer::new(8 * 1024 * 1024, KERNEL_ALIGNMENT);
+ let (ramdisk, _, kernel, _) = android_main(&mut ops, &mut load_buffer).unwrap();
+ checks_loaded_v2_slot_a_normal_mode(ramdisk, kernel)
+ }
}
diff --git a/gbl/libgbl/src/ops.rs b/gbl/libgbl/src/ops.rs
index 1405f91..ec86b06 100644
--- a/gbl/libgbl/src/ops.rs
+++ b/gbl/libgbl/src/ops.rs
@@ -410,6 +410,20 @@
/// any state change.
fn get_next_slot(&mut self, _mark_boot_attempt: bool) -> Result<Slot, Error>;
+ /// Gets the target slot to boot.
+ ///
+ /// * If GBL is slotless (`Self::get_current_slot()` returns `Error::Unsupported`), the API
+ /// behaves the same as `Self::get_next_slot()`.
+ /// * If GBL is slotted, the API behaves the same as `Self::get_current_slot()` and
+ /// `mark_boot_attempt` is ignored.
+ fn get_boot_slot(&mut self, mark_boot_attempt: bool) -> Result<Slot, Error> {
+ match self.get_current_slot() {
+ // Slotless bootloader
+ Err(Error::Unsupported) => self.get_next_slot(mark_boot_attempt),
+ v => v,
+ }
+ }
+
/// Sets the active slot for the next A/B decision.
///
/// # Args
@@ -566,6 +580,19 @@
Option<&[u8]>,
) -> AvbIoResult<()>,
>,
+
+ /// For returned by `get_current_slot`
+ //
+ // We wrap it in an `Option` so that if a test exercises code paths that use it but did not
+ // set it, it can panic with "unwrap()" which will give a clearer error and location
+ // message than a vague error such as `Error::Unimplemented`.
+ pub current_slot: Option<Result<Slot, Error>>,
+
+ /// For returned by `get_next_slot`
+ pub next_slot: Option<Result<Slot, Error>>,
+
+ /// Number of times `get_next_slot()` is called with `mark_boot_attempt` set to true.
+ pub mark_boot_attempt_called: usize,
}
/// Print `console_out` output, which can be useful for debugging.
@@ -839,11 +866,12 @@
}
fn get_current_slot(&mut self) -> Result<Slot, Error> {
- unimplemented!()
+ self.current_slot.unwrap()
}
- fn get_next_slot(&mut self, _: bool) -> Result<Slot, Error> {
- unimplemented!()
+ fn get_next_slot(&mut self, mark_boot_attempt: bool) -> Result<Slot, Error> {
+ self.mark_boot_attempt_called += usize::from(mark_boot_attempt);
+ self.next_slot.unwrap()
}
fn set_active_slot(&mut self, _: u8) -> Result<(), Error> {
@@ -903,4 +931,29 @@
// One shot recovery is not set.
assert_eq!(get_boot_slot(&mut GblAbrOps(&mut gbl_ops), true), (SlotIndex::A, false));
}
+
+ /// Helper for creating a slot object.
+ pub(crate) fn slot(suffix: char) -> Slot {
+ Slot { suffix: suffix.into(), ..Default::default() }
+ }
+
+ #[test]
+ fn test_get_boot_slot_slotted_gbl() {
+ let storage = FakeGblOpsStorage::default();
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.current_slot = Some(Ok(slot('a')));
+ assert_eq!(gbl_ops.get_current_slot().unwrap().suffix, 'a'.into());
+ }
+
+ #[test]
+ fn test_get_boot_slot_slotless_gbl() {
+ let storage = FakeGblOpsStorage::default();
+ let mut gbl_ops = FakeGblOps::new(&storage);
+ gbl_ops.next_slot = Some(Ok(slot('a')));
+ gbl_ops.current_slot = Some(Err(Error::Unsupported));
+ assert_eq!(gbl_ops.get_next_slot(false).unwrap().suffix, 'a'.into());
+ assert_eq!(gbl_ops.mark_boot_attempt_called, 0);
+ assert_eq!(gbl_ops.get_next_slot(true).unwrap().suffix, 'a'.into());
+ assert_eq!(gbl_ops.mark_boot_attempt_called, 1);
+ }
}