Brillo A/B update (2面 firmware update) とは?

# Overview

Brillo では、a/b update という手法で、firmware updateをします。
A/Bとは、かつてのカセットテープのA面/B面から来ていると思われます。

つまり、表面と裏面を用意して、万一firmware update時に、問題が発生しても、元に戻れるというものです。

その仕組みについて迫ってみたいと思います。

# build option

[以前の記事](http://qiita.com/hidenorly/items/b6563174680bb4c6f536) でも書きましたがおさらいしておきます。

```device/generic/brillo/brillo_base.mk

# Brillo targets use the A/B updater.
AB_OTA_UPDATER := true

# Do not build Android OTA package.
TARGET_SKIP_OTA_PACKAGE := true

# This is the list of partitions the A/B updater will update. These need to have
# two partitions each in the partition table, with the right suffix used by the
# bootloader, for example "system_a" and "system_b".
AB_OTA_PARTITIONS := \
  boot \
  system
```

このように、2面でupdateするということは、boot_a, boot_b, system_a, system_b のように、partitionが複数あるということです。

* bootは、linux kernel image + root file system (ramfs)です。
* systemは、いわゆる/system以下のイメージで、binやlibなども入っています。

2つは、firmwareの一貫性を保つために必要なので、２つ共に必要です。


面白いのは、AOSP masterでは、

```build/core/Makefile
# Carry the public key for update_engine if it's a non-Brillo target that
# uses the AB updater. We use the same key as otacerts but in RSA public key
# format.
ifeq ($(AB_OTA_UPDATER),true)
ifeq ($(BRILLO),)
ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem
$(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))
	$(hide) rm -f $@
	$(hide) mkdir -p $(dir $@)
	$(hide) openssl x509 -pubkey -noout -in $< > $@
endif
endif
```

A/Bで、まぁ、当然ですが、鍵は同じもので署名します。

や、まぁ、当然なのですが、以前のbuild ota toolは使わなくなっています。

```
# Build OTA tools if not using the AB Updater.
ifneq ($(AB_OTA_UPDATER),true)
$(BUILT_TARGET_FILES_PACKAGE): $(built_ota_tools)
endif
```

# HAL

2面を制御するということは、boot loaderが、適切なboot, systemの組み合わせを選んで起動するということです。

というわけで、update後に、boot loaderへ、適切な面を選択するように、通知しなければいけませんが、それは、HALとして抽象化されています。

```
~/work/brillo/hardware/libhardware$ git show a13a426a7d44f5bae5398f2ffbaa53800511ae31
commit a13a426a7d44f5bae5398f2ffbaa53800511ae31
Author: Rom Lemarchand <romlem@android.com>
Date:   Fri Aug 14 14:58:21 2015 -0700

    boot: Add a boot_control HAL
    
    The purpose of the boot control HAL is to communicate with the
    bootloader and set various flags letting the bootloader know which
    partition to boot
    
    Change-Id: I15178abaaf9ca208b1e5300c9207cedbb7950a88
```

```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*getNumberSlots)() returns the number of available slots.
+     * For instance, a system with a single set of partitions would return
+     * 1, a system with A/B would return 2, A/B/C -> 3...
+     */
+    unsigned (*getNumberSlots)(struct boot_control_module *module);
```
A/Bではありますが、3面持つことも可能です。
ここ経由で何面あるのかを返せば良いことになっています。


```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*getCurrentSlot)() returns the value letting the system know
+     * whether the current slot is A or B. The meaning of A and B is
+     * left up to the implementer. It is assumed that if the current slot
+     * is A, then the block devices underlying B can be accessed directly
+     * without any risk of corruption.
+     * The returned value is always guaranteed to be strictly less than the
+     * value returned by getNumberSlots. Slots start at 0 and
+     * finish at getNumberSlots() - 1
+     */
+    unsigned (*getCurrentSlot)(struct boot_control_module *module);
```

現在選択されているslotが何番目かを返せます。

```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*markBootSuccessful)() marks the current slot
+     * as having booted successfully
+     *
+     * Returns 0 on success, -errno on error.
+     */
+    int (*markBootSuccessful)(struct boot_control_module *module);
```

FW update後に、ちゃんと起動できた場合に、systemから呼ばれるので、
bootが成功したので、この面を使うというflagをboot loaderの管理blockに設定することが必要です。

bootloaderは、以後、その確定した面で起動します。
これが呼ばれなかったら、boot loaderは1つ前の面で起動するようにする必要があるということです.


```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*setActiveBootSlot)() marks the slot passed in parameter as
+     * the active boot slot (see getCurrentSlot for an explanation
+     * of the "slot" parameter). This overrides any previous call to
+     * setSlotAsUnbootable.
+     * Returns 0 on success, -errno on error.
+     */
+    int (*setActiveBootSlot)(struct boot_control_module *module, unsigned slot);
```

起動に使うslotを上から設定されますので、boot loaderに設定する必要があります。
通常は、先ほどの (getCurrentSlot() + 1) % (getNumberSlots())の値を設定されるということが、期待されますね。

```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*setSlotAsUnbootable)() marks the slot passed in parameter as
+     * an unbootable. This can be used while updating the contents of the slot's
+     * partitions, so that the system will not attempt to boot a known bad set up.
+     * Returns 0 on success, -errno on error.
+     */
+    int (*setSlotAsUnbootable)(struct boot_control_module *module, unsigned slot);
```

起動失敗時、例えば、dm-verityのfailureが帰ってきた時などに設定するのではないでしょうか。


```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*isSlotBootable)() returns if the slot passed in parameter has
+     * booted successfully in the past.
+     * Returns 1 if the slot has booted successfully, 0 if it has not,
+     * and -errno on error.
+     */
+    int (*isSlotBootable)(struct boot_control_module *module, unsigned slot);
```

setActiveBootSlot()で設定するindexの値が、起動できたものなのかを事前に確認するためのもののようです。

```hardware/libhardware/include/hardware/boot_control.h
+    /*
+     * (*getSuffix)() returns the string suffix used by partitions that
+     * correspond to the slot number passed in parameter. The returned string
+     * is expected to be statically allocated and not need to be freed.
+     * Returns NULL if slot does not match an existing slot.
+     */
+    const char* (*getSuffix)(struct boot_control_module *module, unsigned slot);
```

partitionのA面/B面などを取得するためのもののようです。

このように、HALが用意されているので、HALの実装を用意する必要があります。

update_engine 編に続きます。