Implement Virtual A/B

To implement virtual A/B on a new device, or to retrofit a launched device, you must make changes to device-specific code.

Build flags

Devices that use virtual A/B must be configured as an A/B device and must launch with dynamic partitions.

For devices launching with virtual A/B, set them to inherit the virtual A/B device base configuration:

$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota.mk)

Devices launching with virtual A/B need only half as much board size for BOARD_SUPER_PARTITION_SIZE because B slots are no longer in super. That is, BOARD_SUPER_PARTITION_SIZE must be greater than or equal to sum(size of update groups) + overhead, which, in turn, must be greater than or equal to sum(size of partitions) + overhead.

For Android 13 and higher, to enable compressed snapshots with Virtual A/B, inherit the following base configuration:

$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota/android_t_baseline.mk)

This enables userspace snapshots with Virtual A/B while using a no-op compression method. You can then configure the compression method to one of the supported methods, gz, zstd and lz4.

PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := lz4

For Android 12, to enable compressed snapshots with Virtual A/B, inherit the following base configuration:

$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)

XOR compression

For devices upgrading to Android 13 and higher, the XOR compression feature isn't enabled by default. To enable XOR compression, add the following to the device's .mk file.

PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true

XOR compression is enabled by default for devices that inherit from android_t_baseline.mk.

Userspace merge

For devices upgrading to Android 13 and higher, the userspace merge process as described in Device-mapper layering isn't enabled by default. To enable userspace merge, add the following line to the device's .mk file:

PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true

Userspace merge is enabled by default on devices launching with 13 and higher.

Boot control HAL

The boot control HAL provides an interface for OTA clients to control boot slots. Virtual A/B requires a minor version upgrade of the boot control HAL because additional APIs are needed to ensure bootloader is protected during flashing/factory reset. See IBootControl.hal and types.hal for the latest version of the HAL definition.

// hardware/interfaces/boot/1.1/types.hal
enum MergeStatus : uint8_t {
    NONE, UNKNOWN, SNAPSHOTTED, MERGING, CANCELLED };

// hardware/interfaces/boot/1.1/IBootControl.hal
package android.hardware.boot@1.1;
interface IBootControl extends @1.0::IBootControl {
    setSnapshotMergeStatus(MergeStatus status)
        generates (bool success);
    getSnapshotMergeStatus()
        generates (MergeStatus status);
}
// Recommended implementation

Return<bool> BootControl::setSnapshotMergeStatus(MergeStatus v) {
    // Write value to persistent storage
    // e.g. misc partition (using libbootloader_message)
    // bootloader rejects wipe when status is SNAPSHOTTED
    // or MERGING
}

Fstab changes

The integrity of the metadata partition is essential to the boot process, especially right after an OTA update is applied. So, the metadata partition must be checked before first_stage_init mounts it. To ensure this happens, add the check fs_mgr flag to the entry for /metadata. The following provides an example:

/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,sync wait,formattable,first_stage_mount,check

Kernel requirements

To enable snapshotting, set CONFIG_DM_SNAPSHOT to true.

For devices using F2FS, include the f2fs: export FS_NOCOW_FL flag to user kernel patch to fix file pinning. Include the f2fs: support aligned pinned file kernel patch as well.

Virtual A/B relies on features added in kernel version 4.3: the overflow status bit in the snapshot and snapshot-merge targets. All devices launching with Android 9 and later should already have kernel version 4.4 or later.

To enable compressed snapshots, the minimum supported kernel version is 4.19. Set CONFIG_DM_USER=m or CONFIG_DM_USER=y. If using the former (a module), the module must be loaded in the first-stage ramdisk. This can be achieved by adding the following line to the device Makefile:

BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko

Retrofit on devices upgrading to Android 11

When upgrading to Android 11, devices that launched with dynamic partitions can optionally retrofit virtual A/B. The update process is mostly the same as for devices launching with virtual A/B, with some minor differences:

  • Location of COW files — For launch devices, the OTA client uses all available empty space in the super partition before using space in /data. For retrofit devices, there's always enough space in the super partition so that the COW file is never created on /data.

  • Build-time feature flags — For devices retrofitting virtual A/B, both PRODUCT_VIRTUAL_AB_OTA and PRODUCT_VIRTUAL_AB_OTA_RETROFIT are set to true, as shown below:

    (call inherit-product, \
        (SRC_TARGET_DIR)/product/virtual_ab_ota_retrofit.mk)
    
  • Super partition size — Devices launching with virtual A/B can cut BOARD_SUPER_PARTITION_SIZE in half because B slots aren't in the super partition. Devices retrofitting virtual A/B keep the old super partition size, so BOARD_SUPER_PARTITION_SIZE is greater than or equal to 2 * sum(size of update groups) + overhead, which in turn is greater than or equal to 2 * sum(size of partitions) + overhead.

Bootloader changes

During the merge step of an update, /data holds the only whole instance of the Android OS. Once the migration starts, the native system, vendor, and product partitions are incomplete until the copy finishes. If the device is factory-reset during this process, either by recovery or through the Systems settings dialog, then the device would be unbootable.

Before erasing /data, finish the merge in recovery or rollback depending on the device state:

  • If the new build booted successfully before, finish the migration.
  • Otherwise, rollback to the old slot:
    • For dynamic partitions, roll back to the previous state.
    • For static partitions, set the active slot to the old slot.

Both the bootloader and fastbootd can erase the /data partition if the device is unlocked. While fastbootd can force the migration to complete, the bootloader can’t. The bootloader doesn't know whether or not a merge is in progress, or what blocks in /data constitute the OS partitions. Devices must prevent the user from unknowingly making the device inoperable (bricking) by doing the following:

  1. Implement the boot control HAL so that the bootloader can read the value set by the setSnapshotMergeStatus() method.
  2. If the merge status is MERGING, or if the merge status is SNAPSHOTTED and the slot has changed to the newly updated slot, then requests to wipe userdata, metadata, or the partition storing the merge status must be rejected in the bootloader.
  3. Implement the fastboot snapshot-update cancel command so that users can signal to the bootloader that they want to bypass this protection mechanism.
  4. Modify custom flashing tools or scripts to issue fastboot snapshot-update cancel when flashing the entire device. This is safe to issue because flashing the entire device removes the OTA. Tooling can detect this command at runtime by implementing fastboot getvar snapshot-update-status. This command helps differentiate between error conditions.

Example

struct VirtualAbState {
    uint8_t StructVersion;
    uint8_t MergeStatus;
    uint8_t SourceSlot;
};

bool ShouldPreventUserdataWipe() {
    VirtualAbState state;
    if (!ReadVirtualAbState(&state)) ...
    return state.MergeStatus == MergeStatus::MERGING ||
           (state.MergeStatus == MergeStatus::SNAPSHOTTED &&
            state.SourceSlot != CurrentSlot()));
}

Fastboot tooling changes

Android 11 makes the following changes to the fastboot protocol:

  • getvar snapshot-update-status — Returns the value that the boot control HAL communicated to the bootloader:
    • If the state is MERGING, the bootloader must return merging.
    • If the state is SNAPSHOTTED, the bootloader must return snapshotted.
    • Otherwise, the bootloader must return none.
  • snapshot-update merge — Completes a merge operation, booting to recovery/fastbootd if necessary. This command is valid only if snapshot-update-status is merging, and is only supported in fastbootd.
  • snapshot-update cancel — Sets the boot control HAL's merge status to CANCELLED. This command is invalid when the device is locked.
  • erase or wipe — An erase or wipe of metadata, userdata, or a partition holding the merge status for the boot control HAL should check the snapshot merge status. If the status is MERGING or SNAPSHOTTED, the device should abort the operation.
  • set_active — A set_active command that changes the active slot should check the snapshot merge status. If the status is MERGING, the device should abort the operation. The slot can safely be changed in the SNAPSHOTTED state.

These changes are designed to prevent accidentally making a device unbootable, but they can be disruptive to automated tooling. When the commands are used as a component of flashing all partitions, such as running fastboot flashall, it's recommended to use the following flow:

  1. Query getvar snapshot-update-status.
  2. If merging or snapshotted, issue snapshot-update cancel.
  3. Proceed with flashing steps.

Reduce storage requirements

Devices that don't have full A/B storage allocated in super, and are expecting to use /data as necessary, are strongly recommended to use the block mapping tool. The block mapping tool keeps block allocation consistent between builds, reducing unnecessary writes to the snapshot. This is documented under Reducing OTA Size.

OTA compression methods

OTA packages can be tuned for different performance metrics. Android provides several supported compression methods (gz, lz4, zstd, and none) that have tradeoffs between install time, COW space usage, boot time, and snapshot merge time. The default option enabled for virtual ab with compression is the gz compression method. (Note: relative performance between compression methods varies depending on CPU speed and storage throughput which can change depending on device. All OTA packages generated below are with PostInstall disabled, which will slightly slow down boot time. The total dynamic partition size of a full ota without compression is 4.81 GB).

Incremental OTA on Pixel 6 Pro

Install time w/o postinstall phase COW space usage Post OTA boot time Snapshot merge time
gz 24 min 1.18 GB 40.2 sec 45.5 sec
lz4 13 min 1.49 GB 37.4 sec 37.1 sec
none 13 min 2.90 GB 37.6 sec 40.7 sec

Full OTA on Pixel 6 Pro

Install time w/o postinstall phase COW Space Usage Post OTA boot time Snapshot merge time
gz 23 min 2.79 GB 24.9 sec 41.7 sec
lz4 12 min 3.46 GB 20.0 sec 25.3 sec
none 10 min 4.85 GB 20.6 sec 29.8 sec