xref: /aosp_15_r20/bootable/libbootloader/gbl/libabr/src/lib.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Fuchsia A/B/R boot slot library.
16 
17 #![cfg_attr(not(test), no_std)]
18 
19 use core::{cmp::min, ffi::c_uint, fmt::Write, mem::size_of};
20 
21 use liberror::{Error, Result};
22 
23 const ABR_MAGIC: &[u8; 4] = b"\0AB0";
24 const ABR_MAJOR_VERSION: u8 = 2;
25 const ABR_MINOR_VERSION: u8 = 2;
26 
27 // The following flags are harcoded as u8 instead of using the bitflag crate to avoid additional
28 // crate dependency and improve portability.
29 
30 /// One-shot recovery boot bit for the flag returned by `get_and_clear_one_shot_flag()`.
31 pub const ONE_SHOT_RECOVERY: u8 = 1 << 0;
32 /// One-shot bootloader boot bit for the flag returned by `get_and_clear_one_shot_flag()`.
33 pub const ONE_SHOT_BOOTLOADER: u8 = 1 << 1;
34 
35 const ABR_MAX_PRIORITY: u8 = 15;
36 /// Maximum number of retries.
37 pub const ABR_MAX_TRIES_REMAINING: u8 = 7;
38 
39 /// `Ops` provides the backend interfaces needed by A/B/R APIs.
40 pub trait Ops {
41     /// Reads exactly `out.len()` bytes into `out` from the persistent storage hosting the A/B/R
42     /// metadata.
read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()>43     fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()>;
44 
45     /// Writes exactly `data.len()` bytes from `data` to the persistent storage hosting the A/B/R
46     /// metadata.
write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()>47     fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()>;
48 
49     /// Returns an optional console writer for logging error messages.
console(&mut self) -> Option<&mut dyn Write>50     fn console(&mut self) -> Option<&mut dyn Write>;
51 }
52 
53 impl Ops for [u8; ABR_DATA_SIZE] {
read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()>54     fn read_abr_metadata(&mut self, out: &mut [u8]) -> Result<()> {
55         Ok(out
56             .clone_from_slice(self.get(..out.len()).ok_or(Error::BufferTooSmall(Some(out.len())))?))
57     }
58 
write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()>59     fn write_abr_metadata(&mut self, data: &mut [u8]) -> Result<()> {
60         Ok(self
61             .get_mut(..data.len())
62             .ok_or(Error::BufferTooSmall(Some(data.len())))?
63             .clone_from_slice(data))
64     }
65 
console(&mut self) -> Option<&mut dyn Write>66     fn console(&mut self) -> Option<&mut dyn Write> {
67         None
68     }
69 }
70 
71 /// Helper macro for printing ABR log messages.
72 macro_rules! avb_print {
73     ( $abr_ops:expr, $( $x:expr ),* $(,)? ) => {
74             match $abr_ops.console() {
75                 Some(f) => write!(f, $($x,)*).unwrap(),
76                 _ => {}
77             }
78     };
79 }
80 
81 /// `SlotIndex` represents the A/B/R slot index.
82 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
83 pub enum SlotIndex {
84     /// A slot; normal boot.
85     A,
86     /// B slot; normal boot.
87     B,
88     /// R slot; recovery boot. Doesn't have any associated metadata (e.g. cannot be active, no
89     /// retries), but is unconditionally used as a fallback if both A and B are unbootable.
90     R,
91 }
92 
93 impl SlotIndex {
94     // Get the other counterpart of a A/B slot.
other(&self) -> Self95     fn other(&self) -> Self {
96         match self {
97             SlotIndex::A => SlotIndex::B,
98             SlotIndex::B => SlotIndex::A,
99             _ => panic!("Invalid slot index for `fn other()`"),
100         }
101     }
102 }
103 
104 // Implement conversion to c_uint for C interfaces
105 impl From<SlotIndex> for c_uint {
from(_val: SlotIndex) -> Self106     fn from(_val: SlotIndex) -> Self {
107         match _val {
108             SlotIndex::A => 0,
109             SlotIndex::B => 1,
110             SlotIndex::R => 2,
111         }
112     }
113 }
114 
115 // Implement conversion to char
116 impl From<SlotIndex> for char {
from(_val: SlotIndex) -> Self117     fn from(_val: SlotIndex) -> Self {
118         match _val {
119             SlotIndex::A => 'a',
120             SlotIndex::B => 'b',
121             SlotIndex::R => 'r',
122         }
123     }
124 }
125 
126 // Implement conversion from c_uint for C interfaces.
127 impl TryFrom<c_uint> for SlotIndex {
128     type Error = Error;
129 
try_from(val: c_uint) -> Result<SlotIndex>130     fn try_from(val: c_uint) -> Result<SlotIndex> {
131         match val {
132             v if v == (SlotIndex::A).into() => Ok(SlotIndex::A),
133             v if v == (SlotIndex::B).into() => Ok(SlotIndex::B),
134             v if v == (SlotIndex::R).into() => Ok(SlotIndex::R),
135             _ => Err(Error::InvalidInput),
136         }
137     }
138 }
139 
140 /// `SlotInfo` represents the current state of a A/B/R slot.
141 pub enum SlotState {
142     /// Slot has successfully booted.
143     Successful,
144     /// Slot can be attempted but is not known to be successful. Contained value is the number
145     /// of boot attempts remaining before being marked as `Unbootable`.
146     Bootable(u8),
147     /// Slot is unbootable.
148     Unbootable,
149 }
150 
151 /// `SlotInfo` contains the current state and active status of a A/B/R slot.
152 pub struct SlotInfo {
153     /// The [SlotState] describing the bootability.
154     pub state: SlotState,
155     /// Whether this is currently the active slot.
156     pub is_active: bool,
157 }
158 
159 /// `AbrSlotData` is the wire format metadata for A/B slot.
160 #[repr(C, packed)]
161 #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
162 pub struct AbrSlotData {
163     /// Slot priority. Unbootable slots should always have priority 0.
164     pub priority: u8,
165     /// Boot attempts remaining.
166     pub tries_remaining: u8,
167     /// Whether this slot is known successful.
168     pub successful_boot: u8,
169     /// Reserved for future use; must be set to 0.
170     pub reserved: u8,
171 }
172 
173 const ABR_SLOT_DATA_SIZE: usize = size_of::<AbrSlotData>();
174 
175 impl AbrSlotData {
176     /// Parses from bytes.
deserialize(bytes: &[u8; ABR_SLOT_DATA_SIZE]) -> Self177     pub fn deserialize(bytes: &[u8; ABR_SLOT_DATA_SIZE]) -> Self {
178         Self {
179             priority: bytes[0],
180             tries_remaining: bytes[1],
181             successful_boot: bytes[2],
182             reserved: bytes[3],
183         }
184     }
185 
186     /// Serializes to bytes.
serialize(&self) -> [u8; ABR_SLOT_DATA_SIZE]187     pub fn serialize(&self) -> [u8; ABR_SLOT_DATA_SIZE] {
188         [self.priority, self.tries_remaining, self.successful_boot, self.reserved]
189     }
190 
191     /// Returns if slot is bootable
is_slot_bootable(&self) -> bool192     fn is_slot_bootable(&self) -> bool {
193         self.priority > 0 && (self.successful_boot == 1 || self.tries_remaining > 0)
194     }
195 
set_slot_unbootable(&mut self)196     fn set_slot_unbootable(&mut self) {
197         self.tries_remaining = 0;
198         self.successful_boot = 0;
199     }
200 
201     /// Gets normalized priority.
get_normalized_priority(&self) -> u8202     fn get_normalized_priority(&self) -> u8 {
203         match self.is_slot_bootable() {
204             true => self.priority,
205             _ => 0,
206         }
207     }
208 
209     /// Ensures all unbootable or invalid states are marked as the canonical `unbootable` state.
210     /// That is priority=0, tries_remaining=0, and successful_boot=0.
slot_normalize(&mut self)211     fn slot_normalize(&mut self) {
212         if self.priority > 0 {
213             if self.tries_remaining == 0 && self.successful_boot == 0 {
214                 // All tries exhausted
215                 self.set_slot_unbootable();
216             }
217             if self.tries_remaining > 0 && self.successful_boot == 1 {
218                 // Illegal state. Reset to not successful state
219                 self.tries_remaining = ABR_MAX_TRIES_REMAINING;
220                 self.successful_boot = 0;
221             }
222             self.priority = min(self.priority, ABR_MAX_PRIORITY);
223             self.tries_remaining = min(self.tries_remaining, ABR_MAX_TRIES_REMAINING);
224         } else {
225             self.set_slot_unbootable();
226         }
227     }
228 }
229 
230 /// `AbrData` is the wire format of A/B/R metadata.
231 #[repr(C, packed)]
232 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
233 pub struct AbrData {
234     /// Magic value; must be [ABR_MAGIC].
235     pub magic: [u8; 4],
236     /// Metadata major version, incremented when changes may break backwards compatibility.
237     pub version_major: u8,
238     /// Metadata minor version, incremented when changes do not break backwards compatibility.
239     pub version_minor: u8,
240     /// Reserved for future use; must be 0.
241     pub reserved: [u8; 2],
242     /// A/B slot data.
243     pub slot_data: [AbrSlotData; 2],
244     /// One-shot to bootloader/recovery.
245     pub one_shot_flags: u8,
246     /// Reserved for future use; must be 0.
247     pub reserved2: [u8; 11],
248     /// CRC32 checksum of this struct.
249     pub crc32: u32,
250 }
251 
252 /// Size of `AbrData`
253 pub const ABR_DATA_SIZE: usize = size_of::<AbrData>();
254 
255 impl AbrData {
256     /// Returns the numeric index value for a `SlotIndex`. This is for indexing into
257     /// `Self::slot_data`.
slot_num_index(slot_index: SlotIndex) -> usize258     fn slot_num_index(slot_index: SlotIndex) -> usize {
259         match slot_index {
260             SlotIndex::A => 0,
261             SlotIndex::B => 1,
262             _ => panic!("Invalid slot index"),
263         }
264     }
265 
266     /// Returns a const reference to `Self::slot_data['slot_index']`
slot_data(&self, slot_index: SlotIndex) -> &AbrSlotData267     fn slot_data(&self, slot_index: SlotIndex) -> &AbrSlotData {
268         &self.slot_data[Self::slot_num_index(slot_index)]
269     }
270 
271     /// Returns a mutable reference to `Self::slot_data[`slot_index`]`
slot_data_mut(&mut self, slot_index: SlotIndex) -> &mut AbrSlotData272     fn slot_data_mut(&mut self, slot_index: SlotIndex) -> &mut AbrSlotData {
273         &mut self.slot_data[Self::slot_num_index(slot_index)]
274     }
275 
276     /// Reads, parses and checks metadata from persistent storage.
deserialize(abr_ops: &mut dyn Ops) -> Result<Self>277     pub fn deserialize(abr_ops: &mut dyn Ops) -> Result<Self> {
278         let mut bytes = [0u8; ABR_DATA_SIZE];
279         abr_ops.read_abr_metadata(&mut bytes[..])?;
280         // Usually, the parsing below should be done using the zerocopy crate. However, the Fuchsia
281         // source tree uses the unreleased alpha/beta version of zerocopy which can have
282         // drastically different usage and bound requirements. In order to minimize maintenance
283         // burden for Android and Fuchsia build, we manually copy and parse from the bytes directly
284         // to avoid zerocopy crate dependency.
285         let res = Self {
286             magic: bytes[..4].try_into().unwrap(),
287             version_major: bytes[4],
288             version_minor: bytes[5],
289             reserved: bytes[6..8].try_into().unwrap(),
290             slot_data: [
291                 AbrSlotData::deserialize(&bytes[8..12].try_into().unwrap()),
292                 AbrSlotData::deserialize(&bytes[12..16].try_into().unwrap()),
293             ],
294             one_shot_flags: bytes[16],
295             reserved2: bytes[17..28].try_into().unwrap(),
296             crc32: u32::from_be_bytes(bytes[28..ABR_DATA_SIZE].try_into().unwrap()),
297         };
298 
299         if res.magic != *ABR_MAGIC {
300             avb_print!(abr_ops, "Magic is incorrect.\n");
301             return Err(Error::BadMagic);
302         }
303         if res.crc32 != crc32(&bytes[..28]) {
304             avb_print!(abr_ops, "CRC32 does not match.\n");
305             return Err(Error::BadChecksum);
306         }
307         if res.version_major > ABR_MAJOR_VERSION {
308             avb_print!(abr_ops, "No support for given major version.\n");
309             return Err(Error::UnsupportedVersion);
310         }
311 
312         Ok(res)
313     }
314 
315     /// Updates CRC32 and writes metadata to persistent storage.
serialize(&mut self) -> [u8; ABR_DATA_SIZE]316     pub fn serialize(&mut self) -> [u8; ABR_DATA_SIZE] {
317         let mut res = [0u8; ABR_DATA_SIZE];
318         res[..4].clone_from_slice(&self.magic);
319         res[4] = self.version_major;
320         res[5] = self.version_minor;
321         res[6..8].clone_from_slice(&self.reserved);
322         res[8..12].clone_from_slice(&self.slot_data(SlotIndex::A).serialize());
323         res[12..16].clone_from_slice(&self.slot_data(SlotIndex::B).serialize());
324         res[16] = self.one_shot_flags;
325         res[17..28].clone_from_slice(&self.reserved2[..]);
326         self.crc32 = crc32(&res[..28]);
327         res[28..ABR_DATA_SIZE].clone_from_slice(&self.crc32.to_be_bytes());
328         res
329     }
330 
331     /// Returns an invalid instance.
null() -> Self332     fn null() -> Self {
333         Self { magic: [0u8; 4], ..Default::default() }
334     }
335 
336     /// Gets the active slot
get_active_slot(&self) -> SlotIndex337     fn get_active_slot(&self) -> SlotIndex {
338         let priority_a = self.slot_data(SlotIndex::A).get_normalized_priority();
339         let priority_b = self.slot_data(SlotIndex::B).get_normalized_priority();
340         if priority_b > priority_a {
341             return SlotIndex::B;
342         } else if priority_a > 0 {
343             return SlotIndex::A;
344         }
345         return SlotIndex::R;
346     }
347 
348     /// Is the given slot active.
is_slot_active(&self, slot_index: SlotIndex) -> bool349     fn is_slot_active(&self, slot_index: SlotIndex) -> bool {
350         self.get_active_slot() == slot_index
351     }
352 
353     /// Returns if one-shot recovery is set.
is_one_shot_recovery(&self) -> bool354     fn is_one_shot_recovery(&self) -> bool {
355         (self.one_shot_flags & ONE_SHOT_RECOVERY) != 0
356     }
357 
358     /// Sets one-shot recovery.
set_one_shot_recovery(&mut self, enable: bool)359     pub fn set_one_shot_recovery(&mut self, enable: bool) {
360         match enable {
361             true => self.one_shot_flags |= ONE_SHOT_RECOVERY,
362             _ => self.one_shot_flags &= !ONE_SHOT_RECOVERY,
363         }
364     }
365 
366     /// Sets one-shot bootloader
set_one_shot_bootloader(&mut self, enable: bool)367     pub fn set_one_shot_bootloader(&mut self, enable: bool) {
368         match enable {
369             true => self.one_shot_flags |= ONE_SHOT_BOOTLOADER,
370             _ => self.one_shot_flags &= !ONE_SHOT_BOOTLOADER,
371         }
372     }
373 }
374 
375 impl Default for AbrData {
default() -> Self376     fn default() -> Self {
377         Self {
378             magic: *ABR_MAGIC,
379             version_major: ABR_MAJOR_VERSION,
380             version_minor: ABR_MINOR_VERSION,
381             reserved: Default::default(),
382             slot_data: [
383                 AbrSlotData {
384                     priority: ABR_MAX_PRIORITY,
385                     tries_remaining: ABR_MAX_TRIES_REMAINING,
386                     successful_boot: 0,
387                     reserved: 0,
388                 },
389                 AbrSlotData {
390                     priority: ABR_MAX_PRIORITY - 1,
391                     tries_remaining: ABR_MAX_TRIES_REMAINING,
392                     successful_boot: 0,
393                     reserved: 0,
394                 },
395             ],
396             one_shot_flags: 0,
397             reserved2: Default::default(),
398             crc32: 0,
399         }
400     }
401 }
402 
403 /// Loads |abr_data| from persistent storage and normalizes it, initializing new data if necessary.
404 /// Changes as a result of normalization are not written back to persistent storage but a copy of
405 /// the exact original data from persistent storage is provided in |abr_data_orig| for future use
406 /// with save_metadata_if_changed().
407 ///
408 /// On success returns Ok((abr_data, abr_data_orig)). On failure an Error is returned.
load_metadata(abr_ops: &mut dyn Ops) -> Result<(AbrData, AbrData)>409 fn load_metadata(abr_ops: &mut dyn Ops) -> Result<(AbrData, AbrData)> {
410     let mut abr_data_orig = AbrData::null();
411     let mut abr_data = match AbrData::deserialize(abr_ops) {
412         Ok(v) => {
413             abr_data_orig = v;
414             v
415         }
416         Err(Error::Other(e)) => {
417             avb_print!(abr_ops, "read_abr_metadata error: {:?}\n", e);
418             return Err(e.into());
419         }
420         Err(Error::UnsupportedVersion) => {
421             // We don't want to clobber valid data in persistent storage, but we can't use this
422             // data, so bail out.
423             return Err(Error::UnsupportedVersion);
424         }
425         _ => Default::default(),
426     };
427     abr_data.slot_data_mut(SlotIndex::A).slot_normalize();
428     abr_data.slot_data_mut(SlotIndex::B).slot_normalize();
429 
430     Ok((abr_data, abr_data_orig))
431 }
432 
433 /// Serializes and saves metadata to persistent storage.
save_metadata(abr_ops: &mut dyn Ops, abr_data: &mut AbrData) -> Result<()>434 fn save_metadata(abr_ops: &mut dyn Ops, abr_data: &mut AbrData) -> Result<()> {
435     let mut bytes = abr_data.serialize();
436     abr_ops.write_abr_metadata(&mut bytes)?;
437     Ok(())
438 }
439 
440 /// Writes metadata to disk only if it has changed. `abr_data_orig` should be from load_metadata().
save_metadata_if_changed( abr_ops: &mut dyn Ops, abr_data: &mut AbrData, abr_data_orig: &AbrData, ) -> Result<()>441 fn save_metadata_if_changed(
442     abr_ops: &mut dyn Ops,
443     abr_data: &mut AbrData,
444     abr_data_orig: &AbrData,
445 ) -> Result<()> {
446     match abr_data == abr_data_orig {
447         true => Ok(()),
448         _ => save_metadata(abr_ops, abr_data),
449     }
450 }
451 
452 /// Equivalent to C API `AbrGetBootSlot()`.
453 ///
454 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
455 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
get_boot_slot(abr_ops: &mut dyn Ops, update_metadata: bool) -> (SlotIndex, bool)456 pub fn get_boot_slot(abr_ops: &mut dyn Ops, update_metadata: bool) -> (SlotIndex, bool) {
457     let mut is_slot_marked_successful = false;
458     let (mut abr_data, abr_data_orig) = match load_metadata(abr_ops) {
459         Ok(v) => v,
460         Err(e) => {
461             avb_print!(
462                 abr_ops,
463                 "Failed to load metadata {:?}, falling back to recovery mode.\n",
464                 e
465             );
466             return (SlotIndex::R, is_slot_marked_successful);
467         }
468     };
469 
470     if abr_data.is_one_shot_recovery() && update_metadata {
471         abr_data.set_one_shot_recovery(false);
472         match save_metadata(abr_ops, &mut abr_data) {
473             Ok(()) => return (SlotIndex::R, is_slot_marked_successful),
474             Err(e) => {
475                 avb_print!(
476                     abr_ops,
477                     "Failed to update one-shot state {:?}. Ignoring one-shot request.\n",
478                     e
479                 );
480                 abr_data.set_one_shot_recovery(true);
481             }
482         }
483     }
484 
485     // Chooses the highest priority and bootable slot. Otherwise R slot.
486     let slot_to_boot = abr_data.get_active_slot();
487     match slot_to_boot {
488         SlotIndex::R => {}
489         v => {
490             is_slot_marked_successful = abr_data.slot_data(v).successful_boot == 1;
491         }
492     };
493 
494     if update_metadata {
495         // In addition to any changes that resulted from normalization, there are a couple changes
496         // to be made here. First is to decrement the tries remaining for a slot not yet marked as
497         // successful.
498         if slot_to_boot != SlotIndex::R && !is_slot_marked_successful {
499             let slot_data = abr_data.slot_data_mut(slot_to_boot);
500             slot_data.tries_remaining = slot_data.tries_remaining.checked_sub(1).unwrap();
501         }
502         // Second is to clear the successful_boot bit from any successfully-marked slots that
503         // aren't the slot we're booting. It's possible that booting from one slot will render the
504         // other slot unbootable (say, by migrating a config file format in a shared partiton).
505         // Clearing these bits minimizes the risk we'll have an unhealthy slot marked
506         // "successful_boot", which would prevent the system from automatically booting into
507         // recovery.
508         for slot in [SlotIndex::A, SlotIndex::B] {
509             if slot != slot_to_boot && abr_data.slot_data(slot).successful_boot == 1 {
510                 abr_data.slot_data_mut(slot).tries_remaining = ABR_MAX_TRIES_REMAINING;
511                 abr_data.slot_data_mut(slot).successful_boot = 0;
512             }
513         }
514         if let Err(e) = save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig) {
515             // We have no choice but to proceed without updating metadata.
516             avb_print!(abr_ops, "Failed to update metadata {:?}, proceeding anyways.\n", e);
517         }
518     }
519     (slot_to_boot, is_slot_marked_successful)
520 }
521 
522 /// Equivalent to C API `AbrMarkSlotActive()`.
523 ///
524 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
525 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
mark_slot_active(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()>526 pub fn mark_slot_active(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> {
527     if slot_index == SlotIndex::R {
528         avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as active.\n");
529         return Err(Error::InvalidInput);
530     }
531     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
532     // Make requested slot top priority, unsuccessful, and with max tries.
533     abr_data.slot_data_mut(slot_index).priority = ABR_MAX_PRIORITY;
534     abr_data.slot_data_mut(slot_index).tries_remaining = ABR_MAX_TRIES_REMAINING;
535     abr_data.slot_data_mut(slot_index).successful_boot = 0;
536 
537     // Ensure other slot doesn't have as high a priority
538     let other = slot_index.other();
539     abr_data.slot_data_mut(other).priority =
540         min(abr_data.slot_data_mut(other).priority, ABR_MAX_PRIORITY - 1);
541 
542     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)
543 }
544 
545 /// Equivalent to C API `AbrGetSlotLastMarkedActive()`.
546 ///
547 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
548 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
get_slot_last_marked_active(abr_ops: &mut dyn Ops) -> Result<SlotIndex>549 pub fn get_slot_last_marked_active(abr_ops: &mut dyn Ops) -> Result<SlotIndex> {
550     let (abr_data, _) = load_metadata(abr_ops)?;
551     Ok(
552         match abr_data.slot_data(SlotIndex::B).priority > abr_data.slot_data(SlotIndex::A).priority
553         {
554             true => SlotIndex::B,
555             false => SlotIndex::A,
556         },
557     )
558 }
559 
560 /// Equivalent to C API `AbrMarkSlotUnbootable()`.
561 ///
562 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
563 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
mark_slot_unbootable(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()>564 pub fn mark_slot_unbootable(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> {
565     if slot_index == SlotIndex::R {
566         avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as unbootable.\n");
567         return Err(Error::InvalidInput);
568     }
569     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
570     abr_data.slot_data_mut(slot_index).set_slot_unbootable();
571     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)
572 }
573 
574 /// Equivalent to C API `AbrMarkSlotSuccessful()`.
575 ///
576 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
577 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
mark_slot_successful(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()>578 pub fn mark_slot_successful(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<()> {
579     if slot_index == SlotIndex::R {
580         avb_print!(abr_ops, "Invalid argument: Cannot mark slot R as successful.\n");
581         return Err(Error::InvalidInput);
582     }
583     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
584 
585     if !abr_data.slot_data(slot_index).is_slot_bootable() {
586         avb_print!(abr_ops, "Invalid argument: Cannot mark unbootable slot as successful.\n");
587         return Err(Error::InvalidInput);
588     }
589 
590     abr_data.slot_data_mut(slot_index).tries_remaining = 0;
591     abr_data.slot_data_mut(slot_index).successful_boot = 1;
592 
593     // Proactively remove any success mark on the other slot
594     //
595     // This can theoretically be removed since get_boot_slot() clear successful bit on non-boot
596     // slots. However, legacy devices might still be using old versions of ABR implementation that
597     // don't clear it. Therefore, we keep this logic to be safe.
598     //
599     // Context: https://fxbug.dev/42142842, https://crbug.com/fuchsia/64057.
600     let other = slot_index.other();
601     if abr_data.slot_data(other).is_slot_bootable() {
602         abr_data.slot_data_mut(other).tries_remaining = ABR_MAX_TRIES_REMAINING;
603         abr_data.slot_data_mut(other).successful_boot = 0;
604     }
605     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)
606 }
607 
608 /// Equivalent to C API `AbrGetSlotInfo()`.
609 ///
610 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
611 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
get_slot_info(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<SlotInfo>612 pub fn get_slot_info(abr_ops: &mut dyn Ops, slot_index: SlotIndex) -> Result<SlotInfo> {
613     let (abr_data, _) = load_metadata(abr_ops)?;
614     Ok(match slot_index {
615         // Assume that R slot is always OK.
616         SlotIndex::R => SlotInfo {
617             state: SlotState::Successful,
618             is_active: abr_data.is_slot_active(SlotIndex::R),
619         },
620         _ => {
621             let slot_data = abr_data.slot_data(slot_index);
622             let state = match slot_data.successful_boot == 1 {
623                 true => SlotState::Successful,
624                 _ if slot_data.is_slot_bootable() => SlotState::Bootable(slot_data.tries_remaining),
625                 _ => SlotState::Unbootable,
626             };
627             SlotInfo { state, is_active: abr_data.is_slot_active(slot_index) }
628         }
629     })
630 }
631 
632 /// Equivalent to C API `AbrSetOneShotRecovery()`.
633 ///
634 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
635 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
set_one_shot_recovery(abr_ops: &mut dyn Ops, enable: bool) -> Result<()>636 pub fn set_one_shot_recovery(abr_ops: &mut dyn Ops, enable: bool) -> Result<()> {
637     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
638     abr_data.set_one_shot_recovery(enable);
639     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)
640 }
641 
642 /// Equivalent to C API `AbrSetOneShotBootloader()`.
643 ///
644 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
645 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
set_one_shot_bootloader(abr_ops: &mut dyn Ops, enable: bool) -> Result<()>646 pub fn set_one_shot_bootloader(abr_ops: &mut dyn Ops, enable: bool) -> Result<()> {
647     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
648     abr_data.set_one_shot_bootloader(enable);
649     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)
650 }
651 
652 /// Equivalent to C API `AbrGetAndClearOneShotFlags()`.
653 ///
654 /// TODO(b/338243123): Detailed documentation is available in Fuchsia upstream header
655 /// "src/firmware/lib/abr/include/lib/abr/abr.h", which will migrate to the GBL repo.
get_and_clear_one_shot_flag(abr_ops: &mut dyn Ops) -> Result<u8>656 pub fn get_and_clear_one_shot_flag(abr_ops: &mut dyn Ops) -> Result<u8> {
657     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
658     let res = abr_data.one_shot_flags;
659     abr_data.one_shot_flags = 0;
660     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?;
661     Ok(res)
662 }
663 
664 /// Gets and clears one shot bootloader flag only.
get_and_clear_one_shot_bootloader(abr_ops: &mut dyn Ops) -> Result<bool>665 pub fn get_and_clear_one_shot_bootloader(abr_ops: &mut dyn Ops) -> Result<bool> {
666     let (mut abr_data, abr_data_orig) = load_metadata(abr_ops)?;
667     let res = abr_data.one_shot_flags;
668     abr_data.one_shot_flags &= !ONE_SHOT_BOOTLOADER;
669     save_metadata_if_changed(abr_ops, &mut abr_data, &abr_data_orig)?;
670     Ok((res & ONE_SHOT_BOOTLOADER) != 0)
671 }
672 
673 /// Reverses the bit of a byte.
reverse_byte(b: u8) -> u8674 fn reverse_byte(b: u8) -> u8 {
675     const LOOKUP_TABLE_4BIT_REVERSE: &[u8] =
676         &[0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF];
677     LOOKUP_TABLE_4BIT_REVERSE[(b >> 4) as usize]
678         | (LOOKUP_TABLE_4BIT_REVERSE[(b & 0xf) as usize] << 4)
679 }
680 
681 // Reverses the bits of a u32;
reverse_u32(val: u32) -> u32682 fn reverse_u32(val: u32) -> u32 {
683     let mut bytes = val.to_le_bytes();
684     bytes.iter_mut().for_each(|v| *v = reverse_byte(*v));
685     u32::from_be_bytes(bytes)
686 }
687 
688 // Calculates the crc32 of the given bytes.
crc32(data: &[u8]) -> u32689 fn crc32(data: &[u8]) -> u32 {
690     let mut res: u32 = 0xffffffff;
691     for b in data {
692         res ^= (reverse_byte(*b) as u32) << 24;
693         for _ in 0..8 {
694             if (res & 0x80000000) != 0 {
695                 res = (res << 1) ^ 0x04C11DB7;
696             } else {
697                 res <<= 1;
698             }
699         }
700     }
701     reverse_u32(!res)
702 }
703 
704 #[cfg(test)]
705 mod test {
706     use super::*;
707     // Testing is currently done against the C interface tests in upstream Fuchsia:
708     // https://fuchsia.googlesource.com/fuchsia/+/96f7268b497f998ffcbeef73425b031bd7f4db65/src/firmware/lib/abr/test/libabr_test.cc
709     // These tests will be ported to here as rust tests in the future.
710 
711     #[test]
test_get_and_clear_one_shot_bootloader()712     fn test_get_and_clear_one_shot_bootloader() {
713         let mut meta = [0u8; ABR_DATA_SIZE];
714         set_one_shot_bootloader(&mut meta, true).unwrap();
715         set_one_shot_recovery(&mut meta, true).unwrap();
716         assert!(get_and_clear_one_shot_bootloader(&mut meta).unwrap());
717         assert_eq!(get_and_clear_one_shot_flag(&mut meta).unwrap(), ONE_SHOT_RECOVERY);
718     }
719 }
720