UKIBoot
============

This is an implementation of a UEFI based A/B boot protocol, that is
very similar to android boot.

If you install this very small boot loader as EFI/BOOT/BOOTX64.EFI,
then that will be loaded at boot by the UEFI firmware, and it will
then chain load another UEFI binary from either the `ukiboot_a`, or the
`ukiboot_b` partition, depending on what is the active boot slot.

The chain loaded binaries can be anything, but the primary idea is
that they would be Unified Kernel Images per:
 https://uapi-group.org/specifications/specs/unified_kernel_image/

Such images contain a kernel, an initrd, and a set of kernel
commandlines, and this allows booting a (optionally signed with
SecureBoot) complete linux kernel, which would then find the
remaining userspace.

The implementation used here is very much modeled on android verified
boot v2, in particular the way it is implemented in uboot:
 https://lore.kernel.org/all/CAModR+XkH1P6ZWcRyfDDua9_X4k6OQc5Wk-=PqzxLdOPdqqRGg@mail.gmail.com/T/#e896d728bde04a96c3ed35230f7d72048c7dcd9e5

Technical details
=================

The GPT has 3 partitions, `ukiboot_a`, `ukiboot_b` and `ukibootctl`. The
first two, with partition type `DF331E4D-BE00-463F-B4A7-8B43E18FB53A`,
contain raw (i.e. not on a filesystem) UKI UEFI binaries.

The `uki_bootctl` partition, type `FEFD9070-346F-4C9A-85E6-17F07F922773`,
is used to track which partitions are valid, active, and what the current
boot count is. The layout of this partition is this, with all field being
little endian:

```
typedef struct {
    UINT8 priority;
    UINT8 tries_remaining;
    UINT8 successful_boot;
    UINT8 reserved;
} UkiBootSlotInfo;

#define UKI_BOOT_CTRL_MAGIC 1420550408

typedef struct {
    UINT32 magic;
    UkiBootSlotInfo slots[2];
    UINT8 reserved[8];
    UINT32 CRC32;
} UkiBootCtrl;
```

Under the MBR scheme, the partitions are structured as logical
partitions within an extended partition. Each is assigned a unique
MBR identifier: `ukiboot_a` has the MBR id `0x46`, `ukiboot_b` has
the MBR id `0x47`, and `ukiboot_ctl` has the MBR id `0x48`.

A slot is valid either if `successful_boot` is set, or if
`tries_remaining` is larger than 0. The former means that this
partition was successfully booted before, and the later means
that we are testing boot of this partition.

When booting, the valid slot that has the highest priority is
chosen. And, if the chosen slot doesn't have `successful_boot` set,
then `tries_remaining` will be decreased by one each boot, until it
reaches zero, and the slot is considered invalid. When this happens
for a newly installed slot, what typically happens is that the
other slot has `successful_boot` set (being the previous working
version), but with a lower priority, so it will instead be booted.

If at boot, the `ukibootctl` partition is uninitilized (or
corrupted), then it will be initialized with a default setup that
marks both partitions as valid, with a boot count of 7, and with the
first slot having higher priority.

When the UKI is booted, the kernel commandline is extended with a
custom commandline option `androidboot.slot_suffix` with the value
`_a` or `_b`, depending on what the current active slot is. This is
the same key as android boot uses, which means most code that
integrates with it can work with ukiboot with few or no modifications.

The kernel commandline extension is implemented using two systemd
stub addons, one called `ukiboot_a.efi.extra.d/slot_a.addon.efi` and
one called `ukiboot_b.efi.extra.d/slot_b.addon.efi`. We then start
the UKI as either `ukiboot_a.efi` or `ukiboot_b.efi` which causes
the UKI stub to load the expected addon.

Implementing updates
====================

When userspace is implementing a version update, these are the steps
that are needed:

* Look at the kernel commandline to find the current active slot
* Rewrite the `ukibootctl partition with the following changes:
  * current slot: Set `successful_boot` to 1, and `tries_remaining` to
    0, keeping the current priority. This ensures the fallback is marked
    as working. Normally this isn't needed, as this should already be
    marked successfully booted.
  * next slot: Set `successful_boot`, `tries_remaining` and `priority` to
    zero. This marks the slot as invalid, so it will not be used until
    writing the partition is completed.
* Perform the update, doing whatever updates that are needed, as well
  as writing the new UKI to the next slot (`ukiboot_a` or `ukiboot_b`).
* Rewrite the `ukibootctl partition with the following changes:
  * current slot: Set `priority` to 14, keep the rest as is.
  * next slot: Set ``tries_remaining` to 7, and `priority` to 15. This
    will cause ukiboot to try to boot the new slot 7 times and then
    fall back to the old slot.
* Reboot, which should boot the new version
* If boot completes sucessfully, mark the boot successful by rewriting
  the `ukibootctl` partition, setting `successful_boot` to 1, and
  `tries_remaining` to 0 (for the booted partition, as per the kernel
  commandline option).

### Firmware Requirements

To support UKIBoot, the firmware must comply with the UEFI 2.x specification.
In particular, it must implement the following Protocols and Boot Services:

#### Required UEFI Protocols

* `EFI_BLOCK_IO_PROTOCOL`: Required on partition handles to read/write raw disk
                           sectors (for the `ukibootctl` data and UKI images).

* `EFI_PARTITION_INFO_PROTOCOL`: Required on partition handles. UKIBoot relies
                                 on the firmware to parse the GPT/MBR table and
				 provide this protocol to identify partitions by
				 Type GUID or ID.
* `EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL`: Required on the handle of the running
                                           image. UKIBoot uses this to determine
					   the device path of the boot media.

#### Required UEFI Boot Services

* `LoadImage`: The firmware must support loading a PE/COFF image directly from a
               source buffer (RAM) rather than just from a file path.
* `StartImage`: Required to transfer control to the chain-loaded UKI images.
* `OpenProtocol`: The firmware must support opening protocols in exclusive mode
                  prevent race conditions during partition access.

* `CalculateCrc32`: Required to validate the checksum of the `ukibootctl` metadata.
