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