android: Implement A/B slot select.

The android_bootloader_control struct defined in bootloader_message.h
stored the A/B metadata used to decide which slot should we use to boot
the device. This patch implements the bootloader side of the slot
selection in a new "android_ab_select" command which decides the
current slot and updates the metadata as needed.

Bug: 32707546
Test: Booted a rpi3, updated to the other slot.
Change-Id: I9344ff5b76194160d2b466a50e84f4f423b1a98a
This commit is contained in:
Alex Deymo 2017-03-24 23:05:29 -07:00 committed by Kever Yang
parent df7cce4361
commit 180cc7c601
10 changed files with 487 additions and 62 deletions

View File

@ -762,6 +762,17 @@ config CMD_BOOT_ANDROID
and booting it. The boot mode is determined by the contents of the and booting it. The boot mode is determined by the contents of the
Android Bootloader Message. Android Bootloader Message.
config CMD_ANDROID_AB_SELECT
bool "android_ab_select"
default n
depends on ANDROID_AB
help
On Android devices with more than one boot slot (multiple copies of
the kernel and system images) this provides a command to select which
slot should be used to boot from and register the boot attempt. This
is used by the new A/B update model where one slot is updated in the
background while running from the other slot.
config CMD_MMC config CMD_MMC
bool "mmc" bool "mmc"
help help

View File

@ -14,6 +14,8 @@ obj-y += version.o
# command # command
obj-$(CONFIG_CMD_AES) += aes.o obj-$(CONFIG_CMD_AES) += aes.o
obj-$(CONFIG_CMD_AMBAPP) += ambapp.o
obj-$(CONFIG_CMD_ANDROID_AB_SELECT) += android_ab_select.o android_cmds.o
obj-$(CONFIG_CMD_ARMFLASH) += armflash.o obj-$(CONFIG_CMD_ARMFLASH) += armflash.o
obj-y += blk_common.o obj-y += blk_common.o
obj-$(CONFIG_SOURCE) += source.o obj-$(CONFIG_SOURCE) += source.o
@ -77,7 +79,7 @@ obj-$(CONFIG_LED_STATUS_CMD) += legacy_led.o
obj-$(CONFIG_CMD_LED) += led.o obj-$(CONFIG_CMD_LED) += led.o
obj-$(CONFIG_CMD_LICENSE) += license.o obj-$(CONFIG_CMD_LICENSE) += license.o
obj-y += load.o obj-y += load.o
obj-$(CONFIG_CMD_LOAD_ANDROID) += load_android.o obj-$(CONFIG_CMD_LOAD_ANDROID) += load_android.o android_cmds.o
obj-$(CONFIG_LOGBUFFER) += log.o obj-$(CONFIG_LOGBUFFER) += log.o
obj-$(CONFIG_ID_EEPROM) += mac.o obj-$(CONFIG_ID_EEPROM) += mac.o
obj-$(CONFIG_CMD_MD5SUM) += md5sum.o obj-$(CONFIG_CMD_MD5SUM) += md5sum.o

56
cmd/android_ab_select.c Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <android_cmds.h>
#include <android_ab.h>
#include <common.h>
#include <command.h>
static int do_android_ab_select(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
int ret;
struct blk_desc *dev_desc;
disk_partition_t part_info;
char slot[2];
if (argc != 4)
return CMD_RET_USAGE;
/* Lookup the "misc" partition from argv[2] and argv[3] */
if (part_get_info_by_dev_and_name_or_num(argv[2], argv[3],
&dev_desc, &part_info) < 0) {
return CMD_RET_FAILURE;
}
ret = android_ab_select(dev_desc, &part_info);
if (ret < 0) {
printf("Android boot failed, error %d.\n", ret);
return CMD_RET_FAILURE;
}
/* Android standard slot names are 'a', 'b', ... */
slot[0] = ANDROID_BOOT_SLOT_NAME(ret);
slot[1] = '\0';
setenv(argv[1], slot);
printf("ANDROID: Booting slot: %s\n", slot);
return CMD_RET_SUCCESS;
}
U_BOOT_CMD(
android_ab_select, 4, 0, do_android_ab_select,
"Select the slot used to boot from and register the boot attempt.",
"<slot_var_name> <interface> <dev[:part|;part_name]>\n"
" - Load the slot metadata from the partition 'part' on\n"
" device type 'interface' instance 'dev' and store the active\n"
" slot in the 'slot_var_name' variable. This also updates the\n"
" Android slot metadata with a boot attempt, which can cause\n"
" successive calls to this function to return a different result\n"
" if the returned slot runs out of boot attempts.\n"
" - If 'part_name' is passed, preceded with a ; instead of :, the\n"
" partition name whose label is 'part_name' will be looked up in\n"
" the partition table. This is commonly the \"misc\" partition.\n"
);

74
cmd/android_cmds.c Normal file
View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <android_cmds.h>
#include <common.h>
#include <part.h>
/**
* part_get_info_by_dev_and_name - Parse a device number and partition name
* string in the form of "device_num;partition_name", for example "0;misc".
* If the partition is found, sets dev_desc and part_info accordingly with the
* information of the partition with the given partition_name.
*
* @dev_iface: Device interface.
* @dev_part_str: Input string argument, like "0;misc".
* @dev_desc: Place to store the device description pointer.
* @part_info: Place to store the partition information.
* @return 0 on success, or -1 on error
*/
static int part_get_info_by_dev_and_name(const char *dev_iface,
const char *dev_part_str,
struct blk_desc **dev_desc,
disk_partition_t *part_info)
{
char *ep;
const char *part_str;
int dev_num;
part_str = strchr(dev_part_str, ';');
if (!part_str || part_str == dev_part_str)
return -1;
dev_num = simple_strtoul(dev_part_str, &ep, 16);
if (ep != part_str) {
/* Not all the first part before the ; was parsed. */
return -1;
}
part_str++;
*dev_desc = blk_get_dev(dev_iface, dev_num);
if (!*dev_desc) {
printf("Could not find %s %d\n", dev_iface, dev_num);
return -1;
}
if (part_get_info_by_name(*dev_desc, part_str, part_info) < 0) {
printf("Could not find \"%s\" partition\n", part_str);
return -1;
}
return 0;
}
int part_get_info_by_dev_and_name_or_num(const char *dev_iface,
const char *dev_part_str,
struct blk_desc **dev_desc,
disk_partition_t *part_info) {
/* Split the part_name if passed as "$dev_num;part_name". */
if (!part_get_info_by_dev_and_name(dev_iface, dev_part_str,
dev_desc, part_info))
return 0;
/* Couldn't lookup by name, try looking up the partition description
* directly.
*/
if (blk_get_device_part_str(dev_iface, dev_part_str,
dev_desc, part_info, 1) < 0) {
printf("Couldn't find partition %s %s\n",
dev_iface, dev_part_str);
return -1;
}
return 0;
}

View File

@ -5,53 +5,10 @@
*/ */
#include <android_bootloader.h> #include <android_bootloader.h>
#include <android_cmds.h>
#include <common.h> #include <common.h>
#include <command.h> #include <command.h>
/**
* part_get_info_by_dev_and_name - Parse a device number and partition name
* string in the form of "device_num;partition_name", for example "0;misc".
* If the partition is found, sets dev_desc and part_info accordingly with the
* information of the partition with the given partition_name.
*
* @dev_iface: Device interface.
* @dev_part_str: Input string argument, like "0;misc".
* @dev_desc: Place to put the device description pointer.
* @part_info: Place to put the partition information.
* @return 0 on success, or -1 on error
*/
static int part_get_info_by_dev_and_name(const char *dev_iface,
const char *dev_part_str,
struct blk_desc **dev_desc,
disk_partition_t *part_info)
{
char *ep;
const char *part_str;
int dev_num;
part_str = strchr(dev_part_str, ';');
if (!part_str)
return -1;
dev_num = simple_strtoul(dev_part_str, &ep, 16);
if (ep != part_str) {
/* Not all the first part before the ; was parsed. */
return -1;
}
part_str++;
*dev_desc = blk_get_dev(dev_iface, dev_num);
if (!*dev_desc) {
printf("Could not find %s %d\n", dev_iface, dev_num);
return -1;
}
if (part_get_info_by_name(*dev_desc, part_str, part_info) < 0) {
printf("Could not find \"%s\" partition\n", part_str);
return -1;
}
return 0;
}
static int do_boot_android(cmd_tbl_t *cmdtp, int flag, int argc, static int do_boot_android(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[]) char * const argv[])
{ {
@ -60,8 +17,6 @@ static int do_boot_android(cmd_tbl_t *cmdtp, int flag, int argc,
char *addr_arg_endp, *addr_str; char *addr_arg_endp, *addr_str;
struct blk_desc *dev_desc; struct blk_desc *dev_desc;
disk_partition_t part_info; disk_partition_t part_info;
const char *misc_part_iface;
const char *misc_part_desc;
if (argc < 4) if (argc < 4)
return CMD_RET_USAGE; return CMD_RET_USAGE;
@ -80,21 +35,9 @@ static int do_boot_android(cmd_tbl_t *cmdtp, int flag, int argc,
load_address = CONFIG_SYS_LOAD_ADDR; load_address = CONFIG_SYS_LOAD_ADDR;
} }
/* Lookup the "misc" partition from argv[1] and argv[2] */ if (part_get_info_by_dev_and_name_or_num(argv[1], argv[2],
misc_part_iface = argv[1]; &dev_desc, &part_info) < 0) {
misc_part_desc = argv[2]; return CMD_RET_FAILURE;
/* Split the part_name if passed as "$dev_num;part_name". */
if (part_get_info_by_dev_and_name(misc_part_iface, misc_part_desc,
&dev_desc, &part_info) < 0) {
/* Couldn't lookup by name from mmc, try looking up the
* partition description directly.
*/
if (blk_get_device_part_str(misc_part_iface, misc_part_desc,
&dev_desc, &part_info, 1) < 0) {
printf("Couldn't find partition %s %s\n",
misc_part_iface, misc_part_desc);
return CMD_RET_FAILURE;
}
} }
ret = android_bootloader_boot_flow(dev_desc, &part_info, argv[3], ret = android_bootloader_boot_flow(dev_desc, &part_info, argv[3],

View File

@ -482,6 +482,16 @@ config ANDROID_BOOTLOADER
recovery mode or bootloader mode) and, if enabled, the slot to boot recovery mode or bootloader mode) and, if enabled, the slot to boot
from in devices with multiple boot slots (A/B devices). from in devices with multiple boot slots (A/B devices).
config ANDROID_AB
bool "Support for Android A/B updates"
default n
help
If enabled, adds support for the new Android A/B update model. This
allows the bootloader to select which slot to boot from based on the
information provided by userspace via the Android boot_ctrl HAL. This
allows a bootloader to try a new version of the system but roll back
to previous version if the new one didn't boot all the way.
config ANDROID_BOOT_IMAGE config ANDROID_BOOT_IMAGE
bool "Enable support for Android Boot Images" bool "Enable support for Android Boot Images"
help help

View File

@ -100,6 +100,7 @@ obj-y += malloc_simple.o
endif endif
endif endif
obj-y += image.o obj-y += image.o
obj-$(CONFIG_ANDROID_AB) += android_ab.o
obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o

266
common/android_ab.c Normal file
View File

@ -0,0 +1,266 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <android_ab.h>
#include <android_bootloader_message.h>
#include <common.h>
#include <malloc.h>
#include <u-boot/crc.h>
/** android_boot_control_compute_crc - Compute the CRC-32 of the bootloader
* control struct. Only the bytes up to the crc32_le field are considered for
* the CRC-32 calculation.
*/
static uint32_t android_boot_control_compute_crc(
struct android_bootloader_control *abc)
{
return crc32(0, (void *)abc, offsetof(typeof(*abc), crc32_le));
}
/** android_boot_control_default - Initialize android_bootloader_control to the
* default value which allows to boot all slots in order from the first one.
* This value should be used when the bootloader message is corrupted, but not
* when a valid message indicates that all slots are unbootable.
*/
void android_boot_control_default(struct android_bootloader_control *abc)
{
int i;
const struct android_slot_metadata metadata = {
.priority = 15,
.tries_remaining = 7,
.successful_boot = 0,
.verity_corrupted = 0,
.reserved = 0
};
memcpy(abc->slot_suffix, "a\0\0\0", 4);
abc->magic = ANDROID_BOOT_CTRL_MAGIC;
abc->version = ANDROID_BOOT_CTRL_VERSION;
abc->nb_slot = ARRAY_SIZE(abc->slot_info);
memset(abc->reserved0, 0, sizeof(abc->reserved0));
for (i = 0; i < abc->nb_slot; ++i) {
abc->slot_info[i] = metadata;
}
memset(abc->reserved1, 0, sizeof(abc->reserved1));
abc->crc32_le = android_boot_control_compute_crc(abc);
}
/** android_boot_control_create_from_disk
* Load the boot_control struct from disk into newly allocated memory. This
* function allocates and returns an integer number of disk blocks, based on the
* block size of the passed device to help performing a read-modify-write
* operation on the boot_control struct. The boot_control struct offset (2 KiB)
* must be a multiple of the device block size, for simplicity.
* @dev_desc: device where to read the boot_control struct from.
* @part_info: partition in 'dev_desc' where to read from, normally the "misc"
* partition should be used.
*/
static void *android_boot_control_create_from_disk(
struct blk_desc *dev_desc,
const disk_partition_t *part_info)
{
ulong abc_offset, abc_blocks;
void *buf;
abc_offset = offsetof(struct android_bootloader_message_ab,
slot_suffix);
if (abc_offset % part_info->blksz) {
printf("ANDROID: Boot control block not block aligned.\n");
return NULL;
}
abc_offset /= part_info->blksz;
abc_blocks = DIV_ROUND_UP(sizeof(struct android_bootloader_control),
part_info->blksz);
if (abc_offset + abc_blocks > part_info->size) {
printf("ANDROID: boot control partition too small. Need at"
" least %lu blocks but have %lu blocks.\n",
abc_offset + abc_blocks, part_info->size);
return NULL;
}
buf = malloc(abc_blocks * part_info->blksz);
if (!buf)
return NULL;
if (blk_dread(dev_desc, part_info->start + abc_offset, abc_blocks,
buf) != abc_blocks) {
printf("ANDROID: Could not read from boot control partition\n");
free(buf);
return NULL;
}
debug("ANDROID: Loaded ABC, %lu blocks.\n", abc_blocks);
return buf;
}
/** android_boot_control_store
* Store the loaded boot_control block back to the same location it was read
* from with android_boot_control_create_from_misc().
*
* @abc_data_block: pointer to the boot_control struct and the extra bytes after
* it up to the nearest block boundary.
* @dev_desc: device where we should write the boot_control struct.
* @part_info: partition on the 'dev_desc' where to write.
* @return 0 on success and -1 on error.
*/
static int android_boot_control_store(void *abc_data_block,
struct blk_desc *dev_desc,
const disk_partition_t *part_info)
{
ulong abc_offset, abc_blocks;
abc_offset = offsetof(struct android_bootloader_message_ab,
slot_suffix) / part_info->blksz;
abc_blocks = DIV_ROUND_UP(sizeof(struct android_bootloader_control),
part_info->blksz);
if (blk_dwrite(dev_desc, part_info->start + abc_offset, abc_blocks,
abc_data_block) != abc_blocks) {
printf("ANDROID: Could not write back the misc partition\n");
return -1;
}
return 0;
}
/** android_boot_compare_slots - compares two slots returning which slot is
* should we boot from among the two.
* @a: The first bootable slot metadata
* @b: The second bootable slot metadata
* @return negative if the slot "a" is better, positive of the slot "b" is
* better or 0 if they are equally good.
*/
static int android_ab_compare_slots(const struct android_slot_metadata *a,
const struct android_slot_metadata *b)
{
/* Higher priority is better */
if (a->priority != b->priority)
return b->priority - a->priority;
/* Higher successful_boot value is better, in case of same priority. */
if (a->successful_boot != b->successful_boot)
return b->successful_boot - a->successful_boot;
/* Higher tries_remaining is better to ensure round-robin. */
if (a->tries_remaining != b->tries_remaining)
return b->tries_remaining - a->tries_remaining;
return 0;
}
int android_ab_select(struct blk_desc *dev_desc, disk_partition_t *part_info)
{
struct android_bootloader_control *abc;
u32 crc32_le;
int slot, i;
bool store_needed = false;
char slot_suffix[4];
abc = android_boot_control_create_from_disk(dev_desc, part_info);
if (!abc) {
/* This condition represents an actual problem with the code
* or the board setup, like an invalid partition information.
* Signal a repair mode and do not try to boot from either
* slot.
*/
return -1;
}
crc32_le = android_boot_control_compute_crc(abc);
if (abc->crc32_le != crc32_le) {
printf("ANDROID: Invalid CRC-32 (expected %.8x, found %.8x), "
"re-initializing A/B metadata.\n",
crc32_le, abc->crc32_le);
android_boot_control_default(abc);
store_needed = true;
}
if (abc->magic != ANDROID_BOOT_CTRL_MAGIC) {
printf("ANDROID: Unknown A/B metadata: %.8x\n", abc->magic);
free(abc);
return -1;
}
if (abc->version > ANDROID_BOOT_CTRL_VERSION) {
printf("ANDROID: Unsupported A/B metadata version: %.8x\n",
abc->version);
free(abc);
return -1;
}
/* At this point a valid boot control metadata is stored in abc,
* followed by other reserved data in the same block.
* We select a with the higher priority slot that
* - is not marked as corrupted and
* - either has tries_remaining > 0 or successful_boot is true.
* If the slot selected has a false successful_boot, we also decrement
* the tries_remaining until it eventually becomes unbootable because
* tries_remaining reaches 0. This mechanism produces a bootloader
* induced rollback, typically right after a failed update.
*/
/* Safety check: limit the number of slots. */
if (abc->nb_slot > ARRAY_SIZE(abc->slot_info)) {
abc->nb_slot = ARRAY_SIZE(abc->slot_info);
store_needed = true;
}
slot = -1;
for (i = 0; i < abc->nb_slot; ++i) {
if (abc->slot_info[i].verity_corrupted ||
!abc->slot_info[i].tries_remaining) {
debug("ANDROID: unbootable slot %d tries: %d, "
"corrupt: %d\n",
i,
abc->slot_info[i].tries_remaining,
abc->slot_info[i].verity_corrupted);
continue;
}
debug("ANDROID: bootable slot %d pri: %d, tries: %d, "
"corrupt: %d, successful: %d\n",
i,
abc->slot_info[i].priority,
abc->slot_info[i].tries_remaining,
abc->slot_info[i].verity_corrupted,
abc->slot_info[i].successful_boot);
if (slot < 0 ||
android_ab_compare_slots(&abc->slot_info[i],
&abc->slot_info[slot]) < 0) {
slot = i;
}
}
if (slot >= 0 && !abc->slot_info[slot].successful_boot) {
printf("ANDROID: Attempting slot %c, tries remaining %d\n",
ANDROID_BOOT_SLOT_NAME(slot),
abc->slot_info[slot].tries_remaining);
abc->slot_info[slot].tries_remaining--;
store_needed = true;
}
if (slot >= 0) {
/* Legacy user-space requires this field to be set in the BCB.
* Newer releases load this the slot suffix from the command
* line or the device tree.
*/
memset(slot_suffix, 0, sizeof(slot_suffix));
slot_suffix[0] = ANDROID_BOOT_SLOT_NAME(slot);
if (memcmp(abc->slot_suffix, slot_suffix,
sizeof(slot_suffix))) {
memcpy(abc->slot_suffix, slot_suffix,
sizeof(slot_suffix));
store_needed = true;
}
}
if (store_needed) {
abc->crc32_le = android_boot_control_compute_crc(abc);
android_boot_control_store(abc, dev_desc, part_info);
}
free(abc);
if (slot < 0)
return -1;
return slot;
}

30
include/android_ab.h Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef __ANDROID_AB_H
#define __ANDROID_AB_H
#include <common.h>
/* Android standard boot slot names are 'a', 'b', 'c', ... */
#define ANDROID_BOOT_SLOT_NAME(slot_num) ('a' + (slot_num))
/** android_ab_select - Select the slot where to boot from.
* On Android devices with more than one boot slot (multiple copies of the
* kernel and system images) selects which slot should be used to boot from and
* registers the boot attempt. This is used in by the new A/B update model where
* one slot is updated in the background while running from the other slot. If
* the selected slot did not successfully boot in the past, a boot attempt is
* registered before returning from this function so it isn't selected
* indefinitely.
*
* @dev_desc: Place to store the device description pointer.
* @part_info: Place to store the partition information.
* @return the slot number (0-based) on success, or -1 on error.
*/
int android_ab_select(struct blk_desc *dev_desc, disk_partition_t *part_info);
#endif

32
include/android_cmds.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef __ANDROID_CMDS_H
#define __ANDROID_CMDS_H
#include <common.h>
/**
* part_get_info_by_dev_and_name_or_num - Parse a device number and partition
* description (either name or number) in the form of device number plus
* partition name separated by a ";" (like "device_num;partition_name") or
* a device number plus a partition number separated by a ":". For example both
* "0;misc" and "0:1" can be valid partition descriptions for a given interface.
* If the partition is found, sets dev_desc and part_info accordingly with the
* information of the partition.
*
* @dev_iface: Device interface.
* @dev_part_str: Input partition description, like "0;misc" or "0:1".
* @dev_desc: Place to store the device description pointer.
* @part_info: Place to store the partition information.
* @return 0 on success, or -1 on error
*/
int part_get_info_by_dev_and_name_or_num(const char *dev_iface,
const char *dev_part_str,
struct blk_desc **dev_desc,
disk_partition_t *part_info);
#endif