Overview
Brillo では、a/b update という手法で、firmware updateをします。
A/Bとは、かつてのカセットテープのA面/B面から来ていると思われます。
つまり、表面と裏面を用意して、万一firmware update時に、問題が発生しても、元に戻れるというものです。
その仕組みについて迫ってみたいと思います。
build option
以前の記事 でも書きましたがおさらいしておきます。
# 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の一貫性を保つために必要なので、2つ共に必要です。
面白いのは、AOSP masterでは、
# 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
+ /*
+ * (*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面持つことも可能です。
ここ経由で何面あるのかを返せば良いことになっています。
+ /*
+ * (*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が何番目かを返せます。
+ /*
+ * (*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つ前の面で起動するようにする必要があるということです.
+ /*
+ * (*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())の値を設定されるということが、期待されますね。
+ /*
+ * (*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が帰ってきた時などに設定するのではないでしょうか。
+ /*
+ * (*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の値が、起動できたものなのかを事前に確認するためのもののようです。
+ /*
+ * (*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 編に続きます。