| // 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. |
| |
| //! This file provides C interface wrappers of libabr APIs. |
| |
| #![cfg_attr(not(test), no_std)] |
| |
| use abr::{ |
| get_and_clear_one_shot_flag, get_boot_slot, get_slot_info, get_slot_last_marked_active, |
| mark_slot_active, mark_slot_successful, mark_slot_unbootable, set_one_shot_bootloader, |
| set_one_shot_recovery, AbrData, AbrSlotData, Ops, SlotIndex, SlotInfo as AbrSlotInfo, |
| SlotState, ABR_DATA_SIZE, |
| }; |
| use core::{ |
| ffi::{c_char, c_uint, c_void}, |
| fmt::Write, |
| }; |
| use liberror::{Error, Result}; |
| |
| pub mod utils; |
| |
| pub const ABR_RESULT_OK: c_uint = 0; |
| pub const ABR_RESULT_ERR_IO: c_uint = 1; |
| pub const ABR_RESULT_ERR_INVALID_DATA: c_uint = 2; |
| pub const ABR_RESULT_ERR_UNSUPPORTED_VERSION: c_uint = 3; |
| |
| // ABR system dependencies. |
| // |
| // These correspond to the definitions in Fuchsia upstream header |
| // "src/firmware/lib/abr/include/lib/abr/sysdeps.h", which will eventually migrate over. |
| extern "C" { |
| /// Prints out a NULL-terminated string. |
| pub fn AbrPrint(message: *const c_char); |
| |
| /// Aborts the program or reboots the device if |abort| is not implemented. |
| pub fn AbrAbort(); |
| } |
| |
| /// A helper to print an ASCII character via `AbrPrint()`. |
| fn abr_print_ascii_char(ch: u8) { |
| let s = [ch, 0]; |
| // SAFETY: |
| // * `s` is a valid buffer |
| // * `s` is for input only and will not be retained by the function. |
| unsafe { AbrPrint(s.as_ptr() as _) } |
| } |
| |
| /// A helper structure that implements formatted write using `AbrPrint()`. |
| struct AbrPrintSysdeps {} |
| |
| impl Write for AbrPrintSysdeps { |
| fn write_str(&mut self, s: &str) -> core::fmt::Result { |
| if s.is_ascii() { |
| s.as_bytes().iter().for_each(|v| abr_print_ascii_char(*v)); |
| } |
| Ok(()) |
| } |
| } |
| |
| /// A panic handler is needed when building as a static library. We simply call into |
| /// the AbrAbort() system dependency. |
| #[cfg(not(test))] |
| #[panic_handler] |
| fn panic(panic: &core::panic::PanicInfo<'_>) -> ! { |
| write!(AbrPrintSysdeps {}, "libabr panics! {}", panic).unwrap(); |
| // SAFETY: Call to external C function. The function simply aborts/reboots the system. |
| unsafe { AbrAbort() }; |
| unreachable!() |
| } |
| |
| /// This corresponds to the `AbrOps` C definition in Fuchsia upstream header |
| /// "src/firmware/lib/abr/include/lib/abr/ops.h", which will eventually migrate over. |
| /// |
| /// typedef struct AbrOps { |
| /// void* context; |
| /// bool (*read_abr_metadata)(void* context, size_t size, uint8_t* buffer); |
| /// bool (*write_abr_metadata)(void* context, const uint8_t* buffer, size_t size); |
| /// bool (*read_abr_metadata_custom)(void* context, AbrSlotData* a_slot_data, |
| /// AbrSlotData* b_slot_data, uint8_t* one_shot_flags); |
| /// bool (*write_abr_metadata_custom)(void* context, const AbrSlotData* a_slot_data, |
| /// const AbrSlotData* b_slot_data, uint8_t one_shot_flags); |
| /// } AbrOps; |
| #[repr(C)] |
| #[derive(Debug, Copy, Clone)] |
| pub struct AbrOps { |
| pub context: *mut c_void, |
| pub read_abr_metadata: |
| Option<unsafe extern "C" fn(context: *mut c_void, size: usize, buffer: *mut u8) -> bool>, |
| pub write_abr_metadata: |
| Option<unsafe extern "C" fn(context: *mut c_void, buffer: *const u8, size: usize) -> bool>, |
| pub read_abr_metadata_custom: Option< |
| unsafe extern "C" fn( |
| context: *mut c_void, |
| a_slot_data: *mut AbrSlotData, |
| b_slot_data: *mut AbrSlotData, |
| one_shot_flags: *mut u8, |
| ) -> bool, |
| >, |
| pub write_abr_metadata_custom: Option< |
| unsafe extern "C" fn( |
| context: *mut c_void, |
| a_slot_data: *const AbrSlotData, |
| b_slot_data: *const AbrSlotData, |
| one_shot_flags: u8, |
| ) -> bool, |
| >, |
| } |
| |
| /// `AbrOpsSafe` wraps a reference to `AbrOps` and is created by an unsafe constructor that |
| /// establishes necessary safety invariants on `AbrOps`. |
| struct AbrOpsSafe<'a> { |
| ops: &'a AbrOps, |
| log: AbrPrintSysdeps, |
| } |
| |
| impl<'a> AbrOpsSafe<'a> { |
| /// Creates a new instance from a reference to `AbrOps`. |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure that `ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `ops.read_abr_metadata`, `ops.write_abr_metadata`, |
| /// `ops.read_abr_metadata_custom` and `ops.write_abr_metadata_custom`. |
| unsafe fn new(ops: &'a AbrOps) -> Self { |
| Self { ops, log: AbrPrintSysdeps {} } |
| } |
| } |
| |
| type AbrSlotIndex = c_uint; |
| |
| impl Ops for AbrOpsSafe<'_> { |
| fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> { |
| if let Some(f) = self.ops.read_abr_metadata.as_ref() { |
| // SAFETY: |
| // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, |
| // or a valid pointer to a correct type of object used by |
| // `self.ops.write_abr_metadata`. |
| // * `out` is a valid buffer |
| // * `out` is for reading data only and will not be retained by the function. |
| match unsafe { f(self.ops.context, out.len(), out.as_mut_ptr() as _) } { |
| false => Err(Error::Other(Some("read_abr_metadata() failed"))), |
| _ => Ok(()), |
| } |
| } else if let Some(f) = self.ops.read_abr_metadata_custom.as_ref() { |
| let mut data: AbrData = Default::default(); |
| // SAFETY: |
| // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, |
| // or a valid pointer to a correct type of object used by |
| // `self.ops.read_abr_metadata_custom`. |
| // * Pointers to `slot_a`, `slot_b` and `one_shot_flags` are a valid memory locations. |
| // * `slot_a`, `slot_b` and `one_shot_flags` are for output and will not be retained by |
| // the function. |
| match unsafe { |
| f( |
| self.ops.context, |
| &mut data.slot_data[SlotIndex::A as usize], |
| &mut data.slot_data[SlotIndex::B as usize], |
| &mut data.one_shot_flags, |
| ) |
| } { |
| false => Err(Error::Other(Some("read_abr_metadata_custom() failed"))), |
| _ => Ok(out[..ABR_DATA_SIZE].clone_from_slice(&data.serialize())), |
| } |
| } else { |
| Err(Error::NotImplemented) |
| } |
| } |
| |
| fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()> { |
| if let Some(f) = self.ops.write_abr_metadata.as_ref() { |
| // SAFETY: |
| // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, |
| // or a valid pointer to a correct type of object used by |
| // `self.ops.write_abr_metadata`. |
| // * `data` is a valid buffer. |
| // * `data` is for input only and will not be retained by the function. |
| match unsafe { f(self.ops.context, data.as_ptr() as _, data.len()) } { |
| false => Err(Error::Other(Some("write_abr_metadata() failed"))), |
| _ => Ok(()), |
| } |
| } else if let Some(f) = self.ops.write_abr_metadata_custom.as_ref() { |
| let mut abr_data = [0u8; ABR_DATA_SIZE]; |
| abr_data.clone_from_slice( |
| data.get(..ABR_DATA_SIZE).ok_or(Error::BufferTooSmall(Some(ABR_DATA_SIZE)))?, |
| ); |
| let abr_data = AbrData::deserialize(&mut abr_data).unwrap(); |
| // SAFETY: |
| // * By safety requirement of `AbrOpsSafe::new()`, `self.ops.context` is either unused, |
| // or a valid pointer to a correct type of object used by |
| // `self.ops.write_abr_metadata_custom`. |
| // * Pointers to `slot_a` and `slot_b` are a valid memory locations. |
| // * `slot_a` and `slot_b` are for input and will not be retained by the function. |
| match unsafe { |
| f( |
| self.ops.context, |
| &abr_data.slot_data[SlotIndex::A as usize], |
| &abr_data.slot_data[SlotIndex::B as usize], |
| abr_data.one_shot_flags, |
| ) |
| } { |
| false => Err(Error::Other(Some("read_abr_metadata_custom() failed"))), |
| _ => Ok(()), |
| } |
| } else { |
| Err(Error::NotImplemented) |
| } |
| } |
| |
| fn console(&mut self) -> Option<&mut dyn core::fmt::Write> { |
| Some(&mut self.log) |
| } |
| } |
| |
| /// A helper that extracts the return value and maps the result to an integer A/B/R result code. |
| fn unpack_result<T: Into<O>, O>(res: Result<T>, val: &mut O) -> c_uint { |
| match res { |
| Err(e) => match e { |
| Error::BadMagic | Error::BadChecksum | Error::InvalidInput => { |
| ABR_RESULT_ERR_INVALID_DATA |
| } |
| Error::UnsupportedVersion => ABR_RESULT_ERR_UNSUPPORTED_VERSION, |
| _ => ABR_RESULT_ERR_IO, |
| }, |
| Ok(v) => { |
| *val = v.into(); |
| ABR_RESULT_OK |
| } |
| } |
| } |
| |
| /// C interface wrapper of `abr::get_boot_slot()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| /// * Caller must make sure to pass either a NULL or valid pointer for `is_slot_marked_successful`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrGetBootSlot( |
| abr_ops: *const AbrOps, |
| update_metadata: bool, |
| is_slot_marked_successful: *mut bool, |
| ) -> AbrSlotIndex { |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| let (slot_index, successful) = get_boot_slot(&mut abr_ops, update_metadata); |
| // SAFETY: function safety requires `out_slot` to be a valid pointer or `NULL`. |
| match unsafe { is_slot_marked_successful.as_mut() } { |
| Some(v) => *v = successful, |
| _ => {} |
| }; |
| slot_index.into() |
| } |
| |
| // NULL terminated strings for slot suffixes. |
| const SLOT_A_SUFFIX: &[u8] = b"_a\0"; |
| const SLOT_B_SUFFIX: &[u8] = b"_b\0"; |
| const SLOT_R_SUFFIX: &[u8] = b"_r\0"; |
| const SLOT_SUFFIX_INVALID: &[u8] = b"\0"; |
| |
| /// C interface for getting slot suffix. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub extern "C" fn AbrGetSlotSuffix(slot_index: AbrSlotIndex) -> *const c_char { |
| match slot_index.try_into() { |
| Ok(SlotIndex::A) => &SLOT_A_SUFFIX, |
| Ok(SlotIndex::B) => &SLOT_B_SUFFIX, |
| Ok(SlotIndex::R) => &SLOT_R_SUFFIX, |
| Err(_) => &SLOT_SUFFIX_INVALID, |
| } |
| .as_ptr() as _ |
| } |
| |
| /// C interface wrapper of `abr::mark_slot_active()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrMarkSlotActive( |
| abr_ops: *const AbrOps, |
| slot_index: AbrSlotIndex, |
| ) -> c_uint { |
| let slot_index = match slot_index.try_into() { |
| Ok(v) => v, |
| Err(_) => return ABR_RESULT_ERR_INVALID_DATA, |
| }; |
| |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| unpack_result(mark_slot_active(&mut abr_ops, slot_index), &mut ()) |
| } |
| |
| /// C interface wrapper of `abr::get_slot_last_marked_active()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops` and `out_slot`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrGetSlotLastMarkedActive( |
| abr_ops: *const AbrOps, |
| out_slot: *mut AbrSlotIndex, |
| ) -> c_uint { |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| // SAFETY: function safety requires `out_slot` to be a valid pointer. |
| let out_slot = unsafe { out_slot.as_mut() }.unwrap(); |
| |
| unpack_result(get_slot_last_marked_active(&mut abr_ops), out_slot) |
| } |
| |
| /// C interface wrapper of `abr::mark_slot_unbootable()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrMarkSlotUnbootable( |
| abr_ops: *const AbrOps, |
| slot_index: AbrSlotIndex, |
| ) -> c_uint { |
| let slot_index = match slot_index.try_into() { |
| Ok(v) => v, |
| Err(_) => return ABR_RESULT_ERR_INVALID_DATA, |
| }; |
| |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| unpack_result(mark_slot_unbootable(&mut abr_ops, slot_index), &mut ()) |
| } |
| |
| /// C interface wrapper of `abr::mark_slot_successful()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrMarkSlotSuccessful( |
| abr_ops: *const AbrOps, |
| slot_index: AbrSlotIndex, |
| ) -> c_uint { |
| let slot_index = match slot_index.try_into() { |
| Ok(v) => v, |
| Err(_) => return ABR_RESULT_ERR_INVALID_DATA, |
| }; |
| |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| unpack_result(mark_slot_successful(&mut abr_ops, slot_index), &mut ()) |
| } |
| |
| /// `SlotInfo` contains the current state of a A/B/R slot. |
| /// |
| /// 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. |
| #[repr(C)] |
| #[derive(Debug, Copy, Clone)] |
| pub struct SlotInfo { |
| /// Whether the slot is expected to be bootable. |
| pub is_bootable: bool, |
| /// Whether the slot is the highest priority A/B slot. |
| pub is_active: bool, |
| /// Whether the slot is currently marked successful. |
| pub is_marked_successful: bool, |
| /// If not marked successful, this represents the number of attempts left for booting this slot. |
| pub num_tries_remaining: u8, |
| } |
| |
| impl From<AbrSlotInfo> for SlotInfo { |
| fn from(val: abr::SlotInfo) -> Self { |
| let is_marked_successful = matches!(val.state, SlotState::Successful); |
| let num_tries_remaining = match val.state { |
| SlotState::Bootable(v) => v, |
| _ => 0, |
| }; |
| Self { |
| is_bootable: is_marked_successful || num_tries_remaining > 0, |
| is_active: val.is_active, |
| is_marked_successful, |
| num_tries_remaining, |
| } |
| } |
| } |
| |
| /// C interface wrapper of `abr::get_slot_info()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops` and 'info'. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrGetSlotInfo( |
| abr_ops: *const AbrOps, |
| slot_index: AbrSlotIndex, |
| info: *mut SlotInfo, |
| ) -> c_uint { |
| let slot_index = match slot_index.try_into() { |
| Ok(v) => v, |
| Err(_) => return ABR_RESULT_ERR_INVALID_DATA, |
| }; |
| |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| // SAFETY: function safety requires `info` to be a valid pointer. |
| let info = unsafe { info.as_mut() }.unwrap(); |
| |
| unpack_result(get_slot_info(&mut abr_ops, slot_index).map(|v| SlotInfo::from(v)), info) |
| } |
| |
| /// C interface wrapper of `abr::set_one_shot_recovery()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrSetOneShotRecovery(abr_ops: *const AbrOps, enable: bool) -> c_uint { |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| unpack_result(set_one_shot_recovery(&mut abr_ops, enable), &mut ()) |
| } |
| |
| /// C interface wrapper of `abr::set_one_shot_bootloader()` |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrSetOneShotBootloader(abr_ops: *const AbrOps, enable: bool) -> c_uint { |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| |
| unpack_result(set_one_shot_bootloader(&mut abr_ops, enable), &mut ()) |
| } |
| |
| /// Gets and clears the one shot flag. |
| /// |
| /// # Safety |
| /// |
| /// * Caller must make sure to pass a valid pointer for `abr_ops` and `flags`. |
| /// * Caller must make sure that `abr_ops.context` is either not used, or points to a valid and |
| /// correct type of value needed by `abr_ops.read_abr_metadata` and `abr_ops.write_abr_metadata`. |
| #[no_mangle] |
| #[allow(non_snake_case)] |
| pub unsafe extern "C" fn AbrGetAndClearOneShotFlags( |
| abr_ops: *const AbrOps, |
| flags: *mut c_uint, |
| ) -> c_uint { |
| // SAFETY: function safety requires `abr_ops` to be a valid pointer. |
| let abr_ops = unsafe { abr_ops.as_ref() }.unwrap(); |
| // SAFETY: function safety requires `abr_ops.context` to be valid for `AbrOpsSafe::new`. |
| let mut abr_ops = unsafe { AbrOpsSafe::new(abr_ops) }; |
| // SAFETY: function safety requires `flags` to be a valid pointer. |
| let flags = unsafe { flags.as_mut() }.unwrap(); |
| |
| unpack_result(get_and_clear_one_shot_flag(&mut abr_ops), flags) |
| } |
| |
| // Needed because of no-std environment in static lib build. |
| #[cfg(not(test))] |
| #[no_mangle] |
| pub extern "C" fn rust_eh_personality() {} |