mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-26 07:17:10 +00:00

Simon Glass <sjg@chromium.org> says: This series provides a way to keep track of the images used in bootstd, including the type of each image. At present this is sort-of handled by struct bootflow but in quite an ad-hoc way. The structure has become quite large and is hard to query. Future work will be able to reduce its size. Ultimately the 'bootflow info' command may change to also show images as a list, but that is left for later, as this series is already fairly long. So for now, just introduce the concept and adjust bootstd to use it, with a simple command to list the images. This series includes various alist enhancements, to make use of this new data structure a little easier. [trini: Drop patch 18 and 19 for now due to size considerations] Link: https://lore.kernel.org/r/20241115231926.211999-1-sjg@chromium.org
613 lines
15 KiB
C
613 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Bootmeth for Android
|
|
*
|
|
* Copyright (C) 2024 BayLibre, SAS
|
|
* Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
|
|
*/
|
|
#define LOG_CATEGORY UCLASS_BOOTSTD
|
|
|
|
#include <android_ab.h>
|
|
#include <android_image.h>
|
|
#if CONFIG_IS_ENABLED(AVB_VERIFY)
|
|
#include <avb_verify.h>
|
|
#endif
|
|
#include <bcb.h>
|
|
#include <blk.h>
|
|
#include <bootflow.h>
|
|
#include <bootm.h>
|
|
#include <bootmeth.h>
|
|
#include <dm.h>
|
|
#include <image.h>
|
|
#include <malloc.h>
|
|
#include <mapmem.h>
|
|
#include <part.h>
|
|
#include <version.h>
|
|
#include "bootmeth_android.h"
|
|
|
|
#define BCB_FIELD_COMMAND_SZ 32
|
|
#define BCB_PART_NAME "misc"
|
|
#define BOOT_PART_NAME "boot"
|
|
#define VENDOR_BOOT_PART_NAME "vendor_boot"
|
|
#define SLOT_LEN 2
|
|
|
|
/**
|
|
* struct android_priv - Private data
|
|
*
|
|
* This is read from the disk and recorded for use when the full Android
|
|
* kernel must be loaded and booted
|
|
*
|
|
* @boot_mode: Requested boot mode (normal, recovery, bootloader)
|
|
* @slot: Nul-terminated partition slot suffix read from BCB ("a\0" or "b\0")
|
|
* @header_version: Android boot image header version
|
|
*/
|
|
struct android_priv {
|
|
enum android_boot_mode boot_mode;
|
|
char *slot;
|
|
u32 header_version;
|
|
u32 boot_img_size;
|
|
u32 vendor_boot_img_size;
|
|
};
|
|
|
|
static int android_check(struct udevice *dev, struct bootflow_iter *iter)
|
|
{
|
|
/* This only works on mmc devices */
|
|
if (bootflow_iter_check_mmc(iter))
|
|
return log_msg_ret("mmc", -ENOTSUPP);
|
|
|
|
/*
|
|
* This only works on whole devices, as multiple
|
|
* partitions are needed to boot Android
|
|
*/
|
|
if (iter->part != 0)
|
|
return log_msg_ret("mmc part", -ENOTSUPP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(blk);
|
|
struct disk_partition partition;
|
|
char partname[PART_NAME_LEN];
|
|
ulong num_blks, bufsz;
|
|
char *buf;
|
|
int ret;
|
|
|
|
if (priv->slot)
|
|
sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
|
|
else
|
|
sprintf(partname, BOOT_PART_NAME);
|
|
|
|
ret = part_get_info_by_name(desc, partname, &partition);
|
|
if (ret < 0)
|
|
return log_msg_ret("part info", ret);
|
|
|
|
num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
|
|
bufsz = num_blks * desc->blksz;
|
|
buf = malloc(bufsz);
|
|
if (!buf)
|
|
return log_msg_ret("buf", -ENOMEM);
|
|
|
|
ret = blk_read(blk, partition.start, num_blks, buf);
|
|
if (ret != num_blks) {
|
|
free(buf);
|
|
return log_msg_ret("part read", -EIO);
|
|
}
|
|
|
|
if (!is_android_boot_image_header(buf)) {
|
|
free(buf);
|
|
return log_msg_ret("header", -ENOENT);
|
|
}
|
|
|
|
if (!android_image_get_bootimg_size(buf, &priv->boot_img_size)) {
|
|
free(buf);
|
|
return log_msg_ret("get bootimg size", -EINVAL);
|
|
}
|
|
|
|
priv->header_version = ((struct andr_boot_img_hdr_v0 *)buf)->header_version;
|
|
|
|
free(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(blk);
|
|
struct disk_partition partition;
|
|
char partname[PART_NAME_LEN];
|
|
ulong num_blks, bufsz;
|
|
char *buf;
|
|
int ret;
|
|
|
|
if (priv->slot)
|
|
sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", priv->slot);
|
|
else
|
|
sprintf(partname, VENDOR_BOOT_PART_NAME);
|
|
|
|
ret = part_get_info_by_name(desc, partname, &partition);
|
|
if (ret < 0)
|
|
return log_msg_ret("part info", ret);
|
|
|
|
num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
|
|
bufsz = num_blks * desc->blksz;
|
|
buf = malloc(bufsz);
|
|
if (!buf)
|
|
return log_msg_ret("buf", -ENOMEM);
|
|
|
|
ret = blk_read(blk, partition.start, num_blks, buf);
|
|
if (ret != num_blks) {
|
|
free(buf);
|
|
return log_msg_ret("part read", -EIO);
|
|
}
|
|
|
|
if (!is_android_vendor_boot_image_header(buf)) {
|
|
free(buf);
|
|
return log_msg_ret("header", -ENOENT);
|
|
}
|
|
|
|
if (!android_image_get_vendor_bootimg_size(buf, &priv->vendor_boot_img_size)) {
|
|
free(buf);
|
|
return log_msg_ret("get vendor bootimg size", -EINVAL);
|
|
}
|
|
|
|
free(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
|
|
struct android_priv *priv = bflow->bootmeth_priv;
|
|
struct disk_partition misc;
|
|
char slot_suffix[3];
|
|
int ret;
|
|
|
|
if (!CONFIG_IS_ENABLED(ANDROID_AB)) {
|
|
priv->slot = NULL;
|
|
return 0;
|
|
}
|
|
|
|
ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
|
|
if (ret < 0)
|
|
return log_msg_ret("part", ret);
|
|
|
|
ret = ab_select_slot(desc, &misc, decrement);
|
|
if (ret < 0)
|
|
return log_msg_ret("slot", ret);
|
|
|
|
priv->slot = malloc(SLOT_LEN);
|
|
priv->slot[0] = BOOT_SLOT_NAME(ret);
|
|
priv->slot[1] = '\0';
|
|
|
|
sprintf(slot_suffix, "_%s", priv->slot);
|
|
ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
|
|
slot_suffix, false);
|
|
if (ret < 0)
|
|
return log_msg_ret("cmdl", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int configure_serialno(struct bootflow *bflow)
|
|
{
|
|
char *serialno = env_get("serial#");
|
|
|
|
if (!serialno)
|
|
return log_msg_ret("serial", -ENOENT);
|
|
|
|
return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
|
|
}
|
|
|
|
static int configure_bootloader_version(struct bootflow *bflow)
|
|
{
|
|
return bootflow_cmdline_set_arg(bflow, "androidboot.bootloader",
|
|
PLAIN_VERSION, false);
|
|
}
|
|
|
|
static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
|
|
struct disk_partition misc;
|
|
struct android_priv *priv;
|
|
char command[BCB_FIELD_COMMAND_SZ];
|
|
int ret;
|
|
|
|
bflow->state = BOOTFLOWST_MEDIA;
|
|
|
|
/*
|
|
* bcb_find_partition_and_load() will print errors to stdout
|
|
* if BCB_PART_NAME is not found. To avoid that, check if the
|
|
* partition exists first.
|
|
*/
|
|
ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
|
|
if (ret < 0)
|
|
return log_msg_ret("part", ret);
|
|
|
|
ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
|
|
if (ret < 0)
|
|
return log_msg_ret("bcb load", ret);
|
|
|
|
ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
|
|
if (ret < 0)
|
|
return log_msg_ret("bcb read", ret);
|
|
|
|
priv = malloc(sizeof(struct android_priv));
|
|
if (!priv)
|
|
return log_msg_ret("buf", -ENOMEM);
|
|
|
|
if (!strcmp("bootonce-bootloader", command)) {
|
|
priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
|
|
bflow->os_name = strdup("Android (bootloader)");
|
|
} else if (!strcmp("boot-fastboot", command)) {
|
|
priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
|
|
bflow->os_name = strdup("Android (fastbootd)");
|
|
} else if (!strcmp("boot-recovery", command)) {
|
|
priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
|
|
bflow->os_name = strdup("Android (recovery)");
|
|
} else {
|
|
priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
|
|
bflow->os_name = strdup("Android");
|
|
}
|
|
if (!bflow->os_name)
|
|
return log_msg_ret("os", -ENOMEM);
|
|
|
|
if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
|
|
/* Clear BCB */
|
|
memset(command, 0, sizeof(command));
|
|
ret = bcb_set(BCB_FIELD_COMMAND, command);
|
|
if (ret < 0) {
|
|
free(priv);
|
|
return log_msg_ret("bcb set", ret);
|
|
}
|
|
ret = bcb_store();
|
|
if (ret < 0) {
|
|
free(priv);
|
|
return log_msg_ret("bcb store", ret);
|
|
}
|
|
|
|
bflow->bootmeth_priv = priv;
|
|
bflow->state = BOOTFLOWST_READY;
|
|
return 0;
|
|
}
|
|
|
|
bflow->bootmeth_priv = priv;
|
|
|
|
/* For recovery and normal boot, we need to scan the partitions */
|
|
ret = android_read_slot_from_bcb(bflow, false);
|
|
if (ret < 0) {
|
|
log_err("read slot: %d", ret);
|
|
goto free_priv;
|
|
}
|
|
|
|
ret = scan_boot_part(bflow->blk, priv);
|
|
if (ret < 0) {
|
|
log_debug("scan boot failed: err=%d\n", ret);
|
|
goto free_priv;
|
|
}
|
|
|
|
if (priv->header_version >= 3) {
|
|
ret = scan_vendor_boot_part(bflow->blk, priv);
|
|
if (ret < 0) {
|
|
log_debug("scan vendor_boot failed: err=%d\n", ret);
|
|
goto free_priv;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ignoring return code for the following configurations:
|
|
* these are not mandatory for booting.
|
|
*/
|
|
configure_serialno(bflow);
|
|
configure_bootloader_version(bflow);
|
|
|
|
if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL && priv->slot) {
|
|
ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot",
|
|
"1", false);
|
|
if (ret < 0) {
|
|
log_debug("normal_boot %d", ret);
|
|
goto free_priv;
|
|
}
|
|
}
|
|
|
|
bflow->state = BOOTFLOWST_READY;
|
|
|
|
return 0;
|
|
|
|
free_priv:
|
|
free(priv);
|
|
bflow->bootmeth_priv = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static int android_read_file(struct udevice *dev, struct bootflow *bflow,
|
|
const char *file_path, ulong addr,
|
|
enum bootflow_img_t type, ulong *sizep)
|
|
{
|
|
/*
|
|
* Reading individual files is not supported since we only
|
|
* operate on whole mmc devices (because we require multiple partitions)
|
|
*/
|
|
return log_msg_ret("Unsupported", -ENOSYS);
|
|
}
|
|
|
|
/**
|
|
* read_slotted_partition() - Read a partition by appending a slot suffix
|
|
*
|
|
* Most modern Android devices use Seamless Updates, where each partition
|
|
* is duplicated. For example, the boot partition has boot_a and boot_b.
|
|
* For more information, see:
|
|
* https://source.android.com/docs/core/ota/ab
|
|
* https://source.android.com/docs/core/ota/ab/ab_implement
|
|
*
|
|
* @blk: Block device to read
|
|
* @name: Partition name to read
|
|
* @slot: Nul-terminated slot suffixed to partition name ("a\0" or "b\0")
|
|
* @image_size: Image size in bytes used when reading the partition
|
|
* @addr: Address where the partition content is loaded into
|
|
* Return: 0 if OK, negative errno on failure.
|
|
*/
|
|
static int read_slotted_partition(struct blk_desc *desc, const char *const name,
|
|
const char slot[2], ulong image_size, ulong addr)
|
|
{
|
|
struct disk_partition partition;
|
|
char partname[PART_NAME_LEN];
|
|
size_t partname_len;
|
|
ulong num_blks = DIV_ROUND_UP(image_size, desc->blksz);
|
|
int ret;
|
|
u32 n;
|
|
|
|
/*
|
|
* Ensure name fits in partname.
|
|
* For A/B, it should be <name>_<slot>\0
|
|
* For non A/B, it should be <name>\0
|
|
*/
|
|
if (CONFIG_IS_ENABLED(ANDROID_AB))
|
|
partname_len = PART_NAME_LEN - 2 - 1;
|
|
else
|
|
partname_len = PART_NAME_LEN - 1;
|
|
|
|
if (strlen(name) > partname_len)
|
|
return log_msg_ret("name too long", -EINVAL);
|
|
|
|
if (slot)
|
|
sprintf(partname, "%s_%s", name, slot);
|
|
else
|
|
sprintf(partname, "%s", name);
|
|
|
|
ret = part_get_info_by_name(desc, partname, &partition);
|
|
if (ret < 0)
|
|
return log_msg_ret("part", ret);
|
|
|
|
n = blk_dread(desc, partition.start, num_blks, map_sysmem(addr, 0));
|
|
if (n < num_blks)
|
|
return log_msg_ret("part read", -EIO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_IS_ENABLED(AVB_VERIFY)
|
|
static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
|
|
{
|
|
char *key = strsep(&arg, "=");
|
|
char *value = arg;
|
|
int ret;
|
|
|
|
ret = bootflow_cmdline_set_arg(bflow, key, value, false);
|
|
if (ret < 0)
|
|
return log_msg_ret("avb cmdline", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
|
|
{
|
|
char *arg = strsep(&cmdline, " ");
|
|
int ret;
|
|
|
|
while (arg) {
|
|
ret = avb_append_commandline_arg(bflow, arg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
arg = strsep(&cmdline, " ");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int run_avb_verification(struct bootflow *bflow)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
|
|
struct android_priv *priv = bflow->bootmeth_priv;
|
|
const char * const requested_partitions[] = {"boot", "vendor_boot"};
|
|
struct AvbOps *avb_ops;
|
|
AvbSlotVerifyResult result;
|
|
AvbSlotVerifyData *out_data;
|
|
enum avb_boot_state boot_state;
|
|
char *extra_args;
|
|
char slot_suffix[3] = "";
|
|
bool unlocked = false;
|
|
int ret;
|
|
|
|
avb_ops = avb_ops_alloc(desc->devnum);
|
|
if (!avb_ops)
|
|
return log_msg_ret("avb ops", -ENOMEM);
|
|
|
|
if (priv->slot)
|
|
sprintf(slot_suffix, "_%s", priv->slot);
|
|
|
|
ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
|
|
if (ret != AVB_IO_RESULT_OK)
|
|
return log_msg_ret("avb lock", -EIO);
|
|
|
|
result = avb_slot_verify(avb_ops,
|
|
requested_partitions,
|
|
slot_suffix,
|
|
unlocked,
|
|
AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
|
|
&out_data);
|
|
|
|
if (result != AVB_SLOT_VERIFY_RESULT_OK) {
|
|
printf("Verification failed, reason: %s\n",
|
|
str_avb_slot_error(result));
|
|
avb_slot_verify_data_free(out_data);
|
|
return log_msg_ret("avb verify", -EIO);
|
|
}
|
|
|
|
if (unlocked)
|
|
boot_state = AVB_ORANGE;
|
|
else
|
|
boot_state = AVB_GREEN;
|
|
|
|
extra_args = avb_set_state(avb_ops, boot_state);
|
|
if (extra_args) {
|
|
/* extra_args will be modified after this. This is fine */
|
|
ret = avb_append_commandline_arg(bflow, extra_args);
|
|
if (ret < 0)
|
|
goto free_out_data;
|
|
}
|
|
|
|
ret = avb_append_commandline(bflow, out_data->cmdline);
|
|
if (ret < 0)
|
|
goto free_out_data;
|
|
|
|
return 0;
|
|
|
|
free_out_data:
|
|
if (out_data)
|
|
avb_slot_verify_data_free(out_data);
|
|
|
|
return log_msg_ret("avb cmdline", ret);
|
|
}
|
|
#else
|
|
static int run_avb_verification(struct bootflow *bflow)
|
|
{
|
|
int ret;
|
|
|
|
/* When AVB is unsupported, pass ORANGE state */
|
|
ret = bootflow_cmdline_set_arg(bflow,
|
|
"androidboot.verifiedbootstate",
|
|
"orange", false);
|
|
if (ret < 0)
|
|
return log_msg_ret("avb cmdline", ret);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* AVB_VERIFY */
|
|
|
|
static int boot_android_normal(struct bootflow *bflow)
|
|
{
|
|
struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
|
|
struct android_priv *priv = bflow->bootmeth_priv;
|
|
int ret;
|
|
ulong loadaddr = env_get_hex("loadaddr", 0);
|
|
ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
|
|
|
|
ret = run_avb_verification(bflow);
|
|
if (ret < 0)
|
|
return log_msg_ret("avb", ret);
|
|
|
|
/* Read slot once more to decrement counter from BCB */
|
|
ret = android_read_slot_from_bcb(bflow, true);
|
|
if (ret < 0)
|
|
return log_msg_ret("read slot", ret);
|
|
|
|
ret = read_slotted_partition(desc, "boot", priv->slot, priv->boot_img_size,
|
|
loadaddr);
|
|
if (ret < 0)
|
|
return log_msg_ret("read boot", ret);
|
|
|
|
if (priv->header_version >= 3) {
|
|
ret = read_slotted_partition(desc, "vendor_boot", priv->slot,
|
|
priv->vendor_boot_img_size, vloadaddr);
|
|
if (ret < 0)
|
|
return log_msg_ret("read vendor_boot", ret);
|
|
set_avendor_bootimg_addr(vloadaddr);
|
|
}
|
|
set_abootimg_addr(loadaddr);
|
|
|
|
if (priv->slot)
|
|
free(priv->slot);
|
|
|
|
ret = bootm_boot_start(loadaddr, bflow->cmdline);
|
|
|
|
return log_msg_ret("boot", ret);
|
|
}
|
|
|
|
static int boot_android_recovery(struct bootflow *bflow)
|
|
{
|
|
int ret;
|
|
|
|
ret = boot_android_normal(bflow);
|
|
|
|
return log_msg_ret("boot", ret);
|
|
}
|
|
|
|
static int boot_android_bootloader(struct bootflow *bflow)
|
|
{
|
|
int ret;
|
|
|
|
ret = run_command("fastboot usb 0", 0);
|
|
do_reset(NULL, 0, 0, NULL);
|
|
|
|
return log_msg_ret("boot", ret);
|
|
}
|
|
|
|
static int android_boot(struct udevice *dev, struct bootflow *bflow)
|
|
{
|
|
struct android_priv *priv = bflow->bootmeth_priv;
|
|
int ret;
|
|
|
|
switch (priv->boot_mode) {
|
|
case ANDROID_BOOT_MODE_NORMAL:
|
|
ret = boot_android_normal(bflow);
|
|
break;
|
|
case ANDROID_BOOT_MODE_RECOVERY:
|
|
ret = boot_android_recovery(bflow);
|
|
break;
|
|
case ANDROID_BOOT_MODE_BOOTLOADER:
|
|
ret = boot_android_bootloader(bflow);
|
|
break;
|
|
default:
|
|
printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
|
|
priv->boot_mode);
|
|
boot_android_bootloader(bflow);
|
|
/* Tell we failed to boot since boot mode is unknown */
|
|
ret = -EFAULT;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int android_bootmeth_bind(struct udevice *dev)
|
|
{
|
|
struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
|
|
|
|
plat->desc = "Android boot";
|
|
plat->flags = BOOTMETHF_ANY_PART;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct bootmeth_ops android_bootmeth_ops = {
|
|
.check = android_check,
|
|
.read_bootflow = android_read_bootflow,
|
|
.read_file = android_read_file,
|
|
.boot = android_boot,
|
|
};
|
|
|
|
static const struct udevice_id android_bootmeth_ids[] = {
|
|
{ .compatible = "u-boot,android" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(bootmeth_android) = {
|
|
.name = "bootmeth_android",
|
|
.id = UCLASS_BOOTMETH,
|
|
.of_match = android_bootmeth_ids,
|
|
.ops = &android_bootmeth_ops,
|
|
.bind = android_bootmeth_bind,
|
|
};
|