blob: f4a7823d31e31a17d045291770e837596fb63edf [file] [view]
# 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.
```c
#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
```