android_ab: Try backup booloader_message

Some devices keep 2 copies of the bootloader_message in the misc
partition and write each in sequence when updating. This ensures that
there is always one valid copy of the bootloader_message. Teach u-boot
to optionally try a backup bootloader_message from a specified offset if
the primary one fails its CRC check.

Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
This commit is contained in:
Joshua Watt 2023-07-03 10:07:13 -05:00 committed by Tom Rini
parent 55a4244372
commit 3430f24bc6
3 changed files with 79 additions and 11 deletions

View file

@ -85,11 +85,13 @@ static int ab_control_default(struct bootloader_control *abc)
*/
static int ab_control_create_from_disk(struct blk_desc *dev_desc,
const struct disk_partition *part_info,
struct bootloader_control **abc)
struct bootloader_control **abc,
ulong offset)
{
ulong abc_offset, abc_blocks, ret;
abc_offset = offsetof(struct bootloader_message_ab, slot_suffix);
abc_offset = offset +
offsetof(struct bootloader_message_ab, slot_suffix);
if (abc_offset % part_info->blksz) {
log_err("ANDROID: Boot control block not block aligned.\n");
return -EINVAL;
@ -135,11 +137,12 @@ static int ab_control_create_from_disk(struct blk_desc *dev_desc,
*/
static int ab_control_store(struct blk_desc *dev_desc,
const struct disk_partition *part_info,
struct bootloader_control *abc)
struct bootloader_control *abc, ulong offset)
{
ulong abc_offset, abc_blocks, ret;
abc_offset = offsetof(struct bootloader_message_ab, slot_suffix) /
abc_offset = offset +
offsetof(struct bootloader_message_ab, slot_suffix) /
part_info->blksz;
abc_blocks = DIV_ROUND_UP(sizeof(struct bootloader_control),
part_info->blksz);
@ -189,8 +192,11 @@ int ab_select_slot(struct blk_desc *dev_desc, struct disk_partition *part_info,
int slot, i, ret;
bool store_needed = false;
char slot_suffix[4];
#if ANDROID_AB_BACKUP_OFFSET
struct bootloader_control *backup_abc = NULL;
#endif
ret = ab_control_create_from_disk(dev_desc, part_info, &abc);
ret = ab_control_create_from_disk(dev_desc, part_info, &abc, 0);
if (ret < 0) {
/*
* This condition represents an actual problem with the code or
@ -200,22 +206,53 @@ int ab_select_slot(struct blk_desc *dev_desc, struct disk_partition *part_info,
return ret;
}
#if ANDROID_AB_BACKUP_OFFSET
ret = ab_control_create_from_disk(dev_desc, part_info, &backup_abc,
ANDROID_AB_BACKUP_OFFSET);
if (ret < 0) {
free(abc);
return ret;
}
#endif
crc32_le = ab_control_compute_crc(abc);
if (abc->crc32_le != crc32_le) {
log_err("ANDROID: Invalid CRC-32 (expected %.8x, found %.8x),",
crc32_le, abc->crc32_le);
log_err("re-initializing A/B metadata.\n");
#if ANDROID_AB_BACKUP_OFFSET
crc32_le = ab_control_compute_crc(backup_abc);
if (backup_abc->crc32_le != crc32_le) {
log_err("ANDROID: Invalid backup CRC-32 ")
log_err("expected %.8x, found %.8x),",
crc32_le, backup_abc->crc32_le);
#endif
ret = ab_control_default(abc);
if (ret < 0) {
free(abc);
return -ENODATA;
log_err("re-initializing A/B metadata.\n");
ret = ab_control_default(abc);
if (ret < 0) {
#if ANDROID_AB_BACKUP_OFFSET
free(backup_abc);
#endif
free(abc);
return -ENODATA;
}
#if ANDROID_AB_BACKUP_OFFSET
} else {
/*
* Backup is valid. Copy it to the primary
*/
memcpy(abc, backup_abc, sizeof(*abc));
}
#endif
store_needed = true;
}
if (abc->magic != BOOT_CTRL_MAGIC) {
log_err("ANDROID: Unknown A/B metadata: %.8x\n", abc->magic);
#if ANDROID_AB_BACKUP_OFFSET
free(backup_abc);
#endif
free(abc);
return -ENODATA;
}
@ -223,6 +260,9 @@ int ab_select_slot(struct blk_desc *dev_desc, struct disk_partition *part_info,
if (abc->version > BOOT_CTRL_VERSION) {
log_err("ANDROID: Unsupported A/B metadata version: %.8x\n",
abc->version);
#if ANDROID_AB_BACKUP_OFFSET
free(backup_abc);
#endif
free(abc);
return -ENODATA;
}
@ -297,8 +337,21 @@ int ab_select_slot(struct blk_desc *dev_desc, struct disk_partition *part_info,
if (store_needed) {
abc->crc32_le = ab_control_compute_crc(abc);
ab_control_store(dev_desc, part_info, abc);
ab_control_store(dev_desc, part_info, abc, 0);
}
#if ANDROID_AB_BACKUP_OFFSET
/*
* If the backup doesn't match the primary, write the primary
* to the backup offset
*/
if (memcmp(backup_abc, abc, sizeof(*abc)) != 0) {
ab_control_store(dev_desc, part_info, abc,
ANDROID_AB_BACKUP_OFFSET);
}
free(backup_abc);
#endif
free(abc);
if (slot < 0)

View file

@ -936,6 +936,15 @@ config ANDROID_AB
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_AB_BACKUP_OFFSET
hex "Offset of backup bootloader control"
depends on ANDROID_AB
default 0x0
help
If non-zero, a backup bootloader message starting at this offset in
the partition will tried in the event that the primary one (starting
at offset 0) fails its checksum.
endmenu
menu "Blob list"

View file

@ -31,6 +31,12 @@ boot script. This command analyzes and processes A/B metadata stored on a
special partition (e.g. ``misc``) and determines which slot should be used for
booting up.
If the A/B metadata partition has a backup bootloader_message block that is used
to ensure one is always valid even in the event of interruption when writing, it
can be enabled in your board configuration file::
CONFIG_ANDROID_AB_BACKUP_OFFSET=0x1000
Command usage
-------------