tree: 881466596023c15782280df1c11b2de27aefbebd [path history] [tgz]
  1. include/
  2. test/
  3. BUILD.gn
  4. flow.c
  5. README.md
  6. util.c
src/firmware/lib/abr/README.md

libabr

Introduction

This library implements the bootloader A/B/R slot logic expected by Fuchsia. This logic is not required to boot Zircon or run a basic Fuchsia system, but it is required to enable Fuchsia's over-the-air (OTA) update features.

The term ‘slot’ is used to refer to a set of partitions that update together. If an update fails, the partitions fall back together. This works because there are two copies of each partition on disk. By convention, the slots used for normal updates are denoted A and B, and the corresponding partitions are labeled with a suffix of “_a” or “_b”. The slot used for recovery is denoted R; this is chosed when neither A nor B is bootable. This library also uses slot index values which correspond with these slots:

  • Slot A: 0
  • Slot B: 1
  • Slot R: 2

Choose Early

It is best to use an A/B update scheme for both firmware and OS images. Otherwise a system can get into a mismatched state where newer firmware interacts with an older OS or vice versa. Updating everything together keeps deployment and compatibility simple. The A/B decision can be made as soon as metadata storage can be read and to make this easier this library can be used in read-only mode so an A/B decision can be made even if metadata is read-only. This library is also designed with minimal dependencies, small code size, and low memory usage in order to ease integration into early boot environments.

Usage Example

The following code is an example of how to use libabr to make an A/B decision during boot when metadata is read-only.

#include "lib/abr/abr.h"
...
AbrOps ops = {
  .context = NULL,
  .read_abr_metadata = MyReadAbrMetadata,
  .write_abr_metadata = NULL
};
AbrSlotIndex slot_index = AbrGetBootSlot(
    &ops,
    false, /* update_metadata */
    NULL   /* is_slot_marked_successful */);
...

Note: abr.h is the only header you need to include.

Metadata

Libabr uses 32 bytes of persistent metadata to maintain state information. The related structures are defined in data.h. This data is mutable and may be modified by the bootloader or the OS during normal operation. The metadata is the only input to the A/B/R boot decision; there are no other arguments, configuration, or environment data that affect the A/B/R decision. In other words, given the 32 bytes of metadata, the A/B/R decision is deterministic. When multiple libabr instances are used during boot as part of different bootloader or firmware images, they will all compute the same A/B/R decision given the same metadata. As a result, only one bootloader should be responsible for updating the metadata during boot. Usually this is the main bootloader which is the last to run before handing off to an operating system.

Metadata tracks, for slots A and B:

  • Whether a slot is bootable, that is, whether it is expected to boot successfully.
  • Whether a slot has booted successfully since the last update.
  • A relative priority used to choose between multiple bootable slots. When a slot is marked as active the metadata is set to reflect the slot is bootable and has the highest priority.
  • An attempt counter which is only relevant when a slot is marked as bootable but not successful.

Updating Metadata

The most difficult and nuanced part of an A/B update is deciding when to mark a boot as successful. This should not be done by the bootloader except in response to an operator command. Similarly, the bootloader should not normally set a slot as active except in response to an operator command. These kinds of metadata changes are typically carried out by a high level update system.

A bootloader may mark a slot as unbootable in the event of an unrecoverable boot error, but libabr will never mark this unless AbrMarkSlotUnbootable() is called explicitly.

Libabr will update metadata if AbrGetBootSlot() is called with update_metadata set to true. However, only the boot attempt counter will be updated and only when the slot has not been marked successful.

Recovery

Note that AbrGetBootSlot() never fails. If something goes wrong while attempting to read metadata, the logic will choose slot R. If something goes wrong while attempting to update metadata, the error is ignored.

The difficult part is responding to an unexpected slot R decision. The main bootloader which will boot the OS is expected to invoke Fuchsia recovery. Earlier in boot, however, the ideal response is not so clear. The following responses are acceptable:

  • Enter a recovery mode specific to the hardware or firmware.
  • Pick a default, say slot A, and attempt to boot. This is only acceptable if failure to boot the default slot will result in another acceptable response such as attempting the other slot, or entering a hardware-specific recovery mode.

The most important thing is that the recovery response should not result in unrecoverable hardware. This may seem obvious, but it can happen. For example, it is usually not desirable if the response results in a boot loop, or in a system halt.

Porting and Integration

The library code itself is designed to be portable with minimal dependencies. It should work with any modern C toolchain. There are a few dependencies in sysdeps.h that need to be implemented, but these should be fairly straightforward in most environments and a libc-dependent implementation is provided.

Implementing Ops

There are I/O operations that need to be implemented for managing metadata. How and where metadata is stored is implementation-specific. If metadata is read-only in the context of an implementation, write_abr_metadata can be set to NULL. See ops.h for details.

Testing

The unit tests provided here are designed to run on the host, which is unlikely to match the architecture and constraints of the target implementation. It is recommended to also run the unit tests in the target environment if possible. To run the host tests in the fuchsia dev environment, do something like:

$ fx set core.x64 --with //src/firmware:host_tests
$ fx run-host-tests libabr_unittests