diff mbox series

[5/6] bootstd: Add a bootmeth for Android

Message ID 20240606-bootmeth-android-v1-5-0c69d4457cc5@baylibre.com
State Superseded
Delegated to: Tom Rini
Headers show
Series bootstd: Add Android support | expand

Commit Message

Mattijs Korpershoek June 6, 2024, 12:23 p.m. UTC
Android boot flow is a bit different than a regular Linux distro.
Android relies on multiple partitions in order to boot.

A typical boot flow would be:
1. Parse the Bootloader Control Block (BCB, misc partition)
2. If BCB requested bootonce-bootloader, start fastboot and wait.
3. If BCB requested recovery or normal android, run the following:
3.a. Get slot (A/B) from BCB
3.b. Run AVB (Android Verified Boot) on boot partitions
3.c. Load boot and vendor_boot partitions
3.d. Load device-tree, ramdisk and boot

The AOSP documentation has more details at [1], [2], [3]

This has been implemented via complex boot scripts such as [4].
However, these boot script are neither very maintainable nor generic.
Moreover, DISTRO_DEFAULTS is being deprecated [5].

Add a generic Android bootflow implementation for bootstd.
For this initial version, only boot image v4 is supported.

[1] https://source.android.com/docs/core/architecture/bootloader
[2] https://source.android.com/docs/core/architecture/partitions
[3] https://source.android.com/docs/core/architecture/partitions/generic-boot
[4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
[5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/

Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
 MAINTAINERS             |   7 +
 boot/Kconfig            |  14 ++
 boot/Makefile           |   2 +
 boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
 boot/bootmeth_android.h |  27 +++
 doc/develop/bootstd.rst |   6 +
 6 files changed, 578 insertions(+)

Comments

Igor Opaniuk June 10, 2024, 3:15 p.m. UTC | #1
Hi Mattijs,

On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> Android boot flow is a bit different than a regular Linux distro.
> Android relies on multiple partitions in order to boot.
>
> A typical boot flow would be:
> 1. Parse the Bootloader Control Block (BCB, misc partition)
> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
> 3. If BCB requested recovery or normal android, run the following:
> 3.a. Get slot (A/B) from BCB
> 3.b. Run AVB (Android Verified Boot) on boot partitions
> 3.c. Load boot and vendor_boot partitions
> 3.d. Load device-tree, ramdisk and boot
>
> The AOSP documentation has more details at [1], [2], [3]
>
> This has been implemented via complex boot scripts such as [4].
> However, these boot script are neither very maintainable nor generic.
> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>
> Add a generic Android bootflow implementation for bootstd.
> For this initial version, only boot image v4 is supported.
>
> [1] https://source.android.com/docs/core/architecture/bootloader
> [2] https://source.android.com/docs/core/architecture/partitions
> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
>  MAINTAINERS             |   7 +
>  boot/Kconfig            |  14 ++
>  boot/Makefile           |   2 +
>  boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>  boot/bootmeth_android.h |  27 +++
>  doc/develop/bootstd.rst |   6 +
>  6 files changed, 578 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 66783d636e3d..6d2b87720565 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -939,6 +939,13 @@ F: include/bootstd.h
>  F:     net/eth_bootdevice.c
>  F:     test/boot/
>
> +BOOTMETH_ANDROID
> +M:     Mattijs Korpershoek <mkorpershoek@baylibre.com>
> +S:     Maintained
> +T:     git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
> +F:     boot/bootmeth_android.c
> +F:     boot/bootmeth_android.h
> +
>  BTRFS
>  M:     Marek Behún <kabel@kernel.org>
>  R:     Qu Wenruo <wqu@suse.com>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 6f3096c15a6f..5fa6f3b8315d 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>           EFI bootmgr, since they take full control over which bootdevs are
>           selected to boot.
>
> +config BOOTMETH_ANDROID
> +       bool "Bootdev support for Android"
> +       depends on X86 || ARM || SANDBOX
> +       select ANDROID_AB
> +       select ANDROID_BOOT_IMAGE
> +       select CMD_BCB
> +       select PARTITION_TYPE_GUID
> +       select PARTITION_UUIDS
> +       help
> +         Enables support for booting Android using bootdevs. Android requires
> +         multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
> +
> +         Note that only MMC bootdevs are supported at present.
> +
>  config BOOTMETH_CROS
>         bool "Bootdev support for Chromium OS"
>         depends on X86 || ARM || SANDBOX
> diff --git a/boot/Makefile b/boot/Makefile
> index 84ccfeaecec4..75d1cd46fabf 100644
> --- a/boot/Makefile
> +++ b/boot/Makefile
> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
> +
> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
> new file mode 100644
> index 000000000000..26d548d2fd6e
> --- /dev/null
> +++ b/boot/bootmeth_android.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Bootmethod 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 "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"
> +
> +/**
> + * 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
> + */
> +struct android_priv {
> +       int boot_mode;
> +       char slot[2];
> +       u32 header_version;
> +};
> +
> +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
> +        */
Please use Linux kernel coding style for long comments [1].
Same in all occurrences below

[1] https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting
> +       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;
> +
> +       sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
> +       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);
> +       }
> +
> +       priv->header_version = android_image_get_version(buf);
> +
> +       return 0;
Shouldn't we free(buf) also here?

> +}
> +
> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
> +{
> +       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;
> +
> +       sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
> +       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);
> +       }
> +
> +       return 0;
free(buf)?

> +}
> +
> +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;
> +
> +       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[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("slot", 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 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);
> +
> +       bflow->bootmeth_priv = priv;
Probably we should do that just before successfully returning from the function,
otherwise we will end up with a dangling pointer.

> +       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);
> +               }
> +
is free(bflow->bootmeth_priv) handled in functions that call
android_read_bootflow() ?

> +               bflow->state = BOOTFLOWST_READY;
> +               return 0;
> +       }
> +
> +       /* For recovery and normal boot, we need to scan the partitions */
> +       ret = android_read_slot_from_bcb(bflow, false);
> +       if (ret < 0) {
> +               free(priv);
> +               return log_msg_ret("read slot", ret);
> +       }
> +
> +       ret = scan_boot_part(bflow->blk, priv);
> +       if (ret < 0) {
> +               printf("- scan boot failed: err=%d\n", ret);
> +               free(priv);
> +               return log_msg_ret("scan boot", ret);
> +       }
> +
> +       if (priv->header_version != 4) {
> +               printf("- Only boot.img v4 is supported\n");
> +               free(priv);
> +               return log_msg_ret("version", -EINVAL);
> +       }
> +
> +       ret = scan_vendor_boot_part(bflow->blk, priv->slot);
> +       if (ret < 0) {
> +               printf("- scan vendor_boot failed: err=%d\n", ret);
> +               free(priv);
> +               return log_msg_ret("scan vendor_boot", ret);
> +       }
> +
> +       /* Ignoring return code: setting serial number is not mandatory for booting */
> +       configure_serialno(bflow);
> +
> +       if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
> +               ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
> +               if (ret < 0) {
> +                       free(priv);
> +                       return log_msg_ret("normal_boot", ret);
> +               }
> +       }
> +
> +       bflow->state = BOOTFLOWST_READY;
> +
> +       return 0;
> +}
> +
> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
> +                            const char *file_path, ulong addr, 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);
> +}
> +
> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
> +                                 const char slot[2], ulong addr)
> +{
> +       struct disk_partition partition;
> +       char partname[PART_NAME_LEN];
> +       int ret;
> +       u32 n;
> +
> +       /* Ensure name fits in partname it should be: <name>_<slot>\0 */
> +       if (strlen(name) > (PART_NAME_LEN - 2 - 1))
> +               return log_msg_ret("name too long", -EINVAL);
> +
> +       sprintf(partname, "%s_%s", name, slot);
> +       ret = part_get_info_by_name(desc, partname, &partition);
> +       if (ret < 0)
> +               return log_msg_ret("part", ret);
> +
> +       n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
> +       if (n < partition.size)
> +               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);.
> +
> +       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_ops_free(avb_ops) ?
> +               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) {
> +               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, loadaddr);
> +       if (ret < 0)
> +               return log_msg_ret("read boot", ret);
> +
> +       ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
> +       if (ret < 0)
> +               return log_msg_ret("read vendor_boot", ret);
> +
> +       set_abootimg_addr(loadaddr);
> +       set_avendor_bootimg_addr(vloadaddr);
> +
> +       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);
select CMD_FASTBOOT in Kconfig (config BOOTMETH_ANDROID)?
> +       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,
> +};
> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
> new file mode 100644
> index 000000000000..411c2f2d15e7
> --- /dev/null
> +++ b/boot/bootmeth_android.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Bootmethod for Android
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +
> +enum android_boot_mode {
> +       ANDROID_BOOT_MODE_NORMAL = 0,
> +
> +       /* Android "recovery" is a special boot mode that uses another ramdisk.
> +        * It can be used to "factory reset" a board or to flash logical partitions
> +        * It operates in 2 modes: adb or fastbootd
> +        * To enter recovery from Android, we can do:
> +        * $ adb reboot recovery
> +        * $ adb reboot fastboot
> +        */
> +       ANDROID_BOOT_MODE_RECOVERY,
> +
> +       /* Android "bootloader" is for accessing/reflashing physical partitions
> +        * Typically, this will launch a fastboot process in U-Boot.
> +        * To enter "bootloader" from Android, we can do:
> +        * $ adb reboot bootloader
> +        */
> +       ANDROID_BOOT_MODE_BOOTLOADER,
> +};
> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
> index a07a72581e7a..709fa9e64ff3 100644
> --- a/doc/develop/bootstd.rst
> +++ b/doc/develop/bootstd.rst
> @@ -95,6 +95,7 @@ bootflows.
>
>  Note: it is possible to have a bootmeth that uses a partition or a whole device
>  directly, but it is more common to use a filesystem.
> +For example, the Android bootmeth uses a whole device.
>
>  Note that some bootmeths are 'global', meaning that they select the bootdev
>  themselves. Examples include VBE and EFI boot manager. In this case, they
> @@ -277,6 +278,9 @@ script_offset_f
>  script_size_f
>      Size of the script to load, e.g. 0x2000
>
> +vendor_boot_comp_addr_r
> +    Address to which to load the vendor_boot Android image, e.g. 0xe0000000
> +
>  Some variables are set by script bootmeth:
>
>  devtype
> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>     - EFI boot using bootefi from disk
>     - VBE
>     - EFI boot using boot manager
> +   - Android bootflow (boot image v4)
>
>
>  Command interface
> @@ -786,6 +791,7 @@ To do
>  Some things that need to be done to completely replace the distro-boot scripts:
>
>  - implement extensions (devicetree overlays with add-on boards)
> +- implement legacy (boot image v2) android boot flow
>
>  Other ideas:
>
>
> --
> 2.45.0
>

Some comments after a quick "scan". Will take a more detailed look a
bit later today/tomorrow.
Mattijs Korpershoek June 11, 2024, 9:32 a.m. UTC | #2
Hi Igor,

Thank you for your quick review.

On lun., juin 10, 2024 at 17:15, Igor Opaniuk <igor.opaniuk@gmail.com> wrote:

> Hi Mattijs,
>
> On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> Android boot flow is a bit different than a regular Linux distro.
>> Android relies on multiple partitions in order to boot.
>>
>> A typical boot flow would be:
>> 1. Parse the Bootloader Control Block (BCB, misc partition)
>> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
>> 3. If BCB requested recovery or normal android, run the following:
>> 3.a. Get slot (A/B) from BCB
>> 3.b. Run AVB (Android Verified Boot) on boot partitions
>> 3.c. Load boot and vendor_boot partitions
>> 3.d. Load device-tree, ramdisk and boot
>>
>> The AOSP documentation has more details at [1], [2], [3]
>>
>> This has been implemented via complex boot scripts such as [4].
>> However, these boot script are neither very maintainable nor generic.
>> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>>
>> Add a generic Android bootflow implementation for bootstd.
>> For this initial version, only boot image v4 is supported.
>>
>> [1] https://source.android.com/docs/core/architecture/bootloader
>> [2] https://source.android.com/docs/core/architecture/partitions
>> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
>> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
>> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>>  MAINTAINERS             |   7 +
>>  boot/Kconfig            |  14 ++
>>  boot/Makefile           |   2 +
>>  boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>>  boot/bootmeth_android.h |  27 +++
>>  doc/develop/bootstd.rst |   6 +
>>  6 files changed, 578 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 66783d636e3d..6d2b87720565 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -939,6 +939,13 @@ F: include/bootstd.h
>>  F:     net/eth_bootdevice.c
>>  F:     test/boot/
>>
>> +BOOTMETH_ANDROID
>> +M:     Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> +S:     Maintained
>> +T:     git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
>> +F:     boot/bootmeth_android.c
>> +F:     boot/bootmeth_android.h
>> +
>>  BTRFS
>>  M:     Marek Behún <kabel@kernel.org>
>>  R:     Qu Wenruo <wqu@suse.com>
>> diff --git a/boot/Kconfig b/boot/Kconfig
>> index 6f3096c15a6f..5fa6f3b8315d 100644
>> --- a/boot/Kconfig
>> +++ b/boot/Kconfig
>> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>>           EFI bootmgr, since they take full control over which bootdevs are
>>           selected to boot.
>>
>> +config BOOTMETH_ANDROID
>> +       bool "Bootdev support for Android"
>> +       depends on X86 || ARM || SANDBOX
>> +       select ANDROID_AB
>> +       select ANDROID_BOOT_IMAGE
>> +       select CMD_BCB
>> +       select PARTITION_TYPE_GUID
>> +       select PARTITION_UUIDS
>> +       help
>> +         Enables support for booting Android using bootdevs. Android requires
>> +         multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
>> +
>> +         Note that only MMC bootdevs are supported at present.
>> +
>>  config BOOTMETH_CROS
>>         bool "Bootdev support for Chromium OS"
>>         depends on X86 || ARM || SANDBOX
>> diff --git a/boot/Makefile b/boot/Makefile
>> index 84ccfeaecec4..75d1cd46fabf 100644
>> --- a/boot/Makefile
>> +++ b/boot/Makefile
>> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
>> +
>> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
>> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
>> new file mode 100644
>> index 000000000000..26d548d2fd6e
>> --- /dev/null
>> +++ b/boot/bootmeth_android.c
>> @@ -0,0 +1,522 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Bootmethod 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 "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"
>> +
>> +/**
>> + * 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
>> + */
>> +struct android_priv {
>> +       int boot_mode;
>> +       char slot[2];
>> +       u32 header_version;
>> +};
>> +
>> +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
>> +        */
> Please use Linux kernel coding style for long comments [1].
> Same in all occurrences below
>
> [1] https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting

Sorry about that that. Will do.
Odd that running checkpatch with --u-boot did not caught this.

$ ./scripts/checkpatch.pl --strict --u-boot --git HEAD^^..HEAD


>> +       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;
>> +
>> +       sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
>> +       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);
>> +       }
>> +
>> +       priv->header_version = android_image_get_version(buf);
>> +
>> +       return 0;
> Shouldn't we free(buf) also here?

Yes we should. Will do for v2. Thanks for catching this!

>
>> +}
>> +
>> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
>> +{
>> +       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;
>> +
>> +       sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
>> +       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);
>> +       }
>> +
>> +       return 0;
> free(buf)?

Yes we should. Will do for v2. Thanks for catching this!

>
>> +}
>> +
>> +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;
>> +
>> +       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[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("slot", 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 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);
>> +
>> +       bflow->bootmeth_priv = priv;
> Probably we should do that just before successfully returning from the function,
> otherwise we will end up with a dangling pointer.

Right, thanks for the suggestion. Will do for v2.

>
>> +       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);
>> +               }
>> +
> is free(bflow->bootmeth_priv) handled in functions that call
> android_read_bootflow() ?

It's not. Future functions (called later) like boot_android() need to
access this structure.

I've modeled this after bootmeth_cros/cros_boot() but I don't
see a reason for not freeing this just before calling bootm().
On the other hand, we will be booting an OS (and this quitting U-Boot)
so it's not entirely needed.

I don't have a strong opinion on this so I can add the free() in
boot_android_normal() and boot_android_bootloader().

>
>> +               bflow->state = BOOTFLOWST_READY;
>> +               return 0;
>> +       }
>> +
>> +       /* For recovery and normal boot, we need to scan the partitions */
>> +       ret = android_read_slot_from_bcb(bflow, false);
>> +       if (ret < 0) {
>> +               free(priv);
>> +               return log_msg_ret("read slot", ret);
>> +       }
>> +
>> +       ret = scan_boot_part(bflow->blk, priv);
>> +       if (ret < 0) {
>> +               printf("- scan boot failed: err=%d\n", ret);
>> +               free(priv);
>> +               return log_msg_ret("scan boot", ret);
>> +       }
>> +
>> +       if (priv->header_version != 4) {
>> +               printf("- Only boot.img v4 is supported\n");
>> +               free(priv);
>> +               return log_msg_ret("version", -EINVAL);
>> +       }
>> +
>> +       ret = scan_vendor_boot_part(bflow->blk, priv->slot);
>> +       if (ret < 0) {
>> +               printf("- scan vendor_boot failed: err=%d\n", ret);
>> +               free(priv);
>> +               return log_msg_ret("scan vendor_boot", ret);
>> +       }
>> +
>> +       /* Ignoring return code: setting serial number is not mandatory for booting */
>> +       configure_serialno(bflow);
>> +
>> +       if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
>> +               ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
>> +               if (ret < 0) {
>> +                       free(priv);
>> +                       return log_msg_ret("normal_boot", ret);
>> +               }
>> +       }
>> +
>> +       bflow->state = BOOTFLOWST_READY;
>> +
>> +       return 0;
>> +}
>> +
>> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
>> +                            const char *file_path, ulong addr, 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);
>> +}
>> +
>> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
>> +                                 const char slot[2], ulong addr)
>> +{
>> +       struct disk_partition partition;
>> +       char partname[PART_NAME_LEN];
>> +       int ret;
>> +       u32 n;
>> +
>> +       /* Ensure name fits in partname it should be: <name>_<slot>\0 */
>> +       if (strlen(name) > (PART_NAME_LEN - 2 - 1))
>> +               return log_msg_ret("name too long", -EINVAL);
>> +
>> +       sprintf(partname, "%s_%s", name, slot);
>> +       ret = part_get_info_by_name(desc, partname, &partition);
>> +       if (ret < 0)
>> +               return log_msg_ret("part", ret);
>> +
>> +       n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
>> +       if (n < partition.size)
>> +               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);.
>> +
>> +       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_ops_free(avb_ops) ?

Yes we should. Will do for v2. Thanks for catching this!

>> +               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) {
>> +               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, loadaddr);
>> +       if (ret < 0)
>> +               return log_msg_ret("read boot", ret);
>> +
>> +       ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
>> +       if (ret < 0)
>> +               return log_msg_ret("read vendor_boot", ret);
>> +
>> +       set_abootimg_addr(loadaddr);
>> +       set_avendor_bootimg_addr(vloadaddr);
>> +
>> +       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);
> select CMD_FASTBOOT in Kconfig (config BOOTMETH_ANDROID)?

Yes, I will add this for v2. Thank you for the suggestion.

>> +       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,
>> +};
>> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
>> new file mode 100644
>> index 000000000000..411c2f2d15e7
>> --- /dev/null
>> +++ b/boot/bootmeth_android.h
>> @@ -0,0 +1,27 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Bootmethod for Android
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +
>> +enum android_boot_mode {
>> +       ANDROID_BOOT_MODE_NORMAL = 0,
>> +
>> +       /* Android "recovery" is a special boot mode that uses another ramdisk.
>> +        * It can be used to "factory reset" a board or to flash logical partitions
>> +        * It operates in 2 modes: adb or fastbootd
>> +        * To enter recovery from Android, we can do:
>> +        * $ adb reboot recovery
>> +        * $ adb reboot fastboot
>> +        */
>> +       ANDROID_BOOT_MODE_RECOVERY,
>> +
>> +       /* Android "bootloader" is for accessing/reflashing physical partitions
>> +        * Typically, this will launch a fastboot process in U-Boot.
>> +        * To enter "bootloader" from Android, we can do:
>> +        * $ adb reboot bootloader
>> +        */
>> +       ANDROID_BOOT_MODE_BOOTLOADER,
>> +};
>> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
>> index a07a72581e7a..709fa9e64ff3 100644
>> --- a/doc/develop/bootstd.rst
>> +++ b/doc/develop/bootstd.rst
>> @@ -95,6 +95,7 @@ bootflows.
>>
>>  Note: it is possible to have a bootmeth that uses a partition or a whole device
>>  directly, but it is more common to use a filesystem.
>> +For example, the Android bootmeth uses a whole device.
>>
>>  Note that some bootmeths are 'global', meaning that they select the bootdev
>>  themselves. Examples include VBE and EFI boot manager. In this case, they
>> @@ -277,6 +278,9 @@ script_offset_f
>>  script_size_f
>>      Size of the script to load, e.g. 0x2000
>>
>> +vendor_boot_comp_addr_r
>> +    Address to which to load the vendor_boot Android image, e.g. 0xe0000000
>> +
>>  Some variables are set by script bootmeth:
>>
>>  devtype
>> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>>     - EFI boot using bootefi from disk
>>     - VBE
>>     - EFI boot using boot manager
>> +   - Android bootflow (boot image v4)
>>
>>
>>  Command interface
>> @@ -786,6 +791,7 @@ To do
>>  Some things that need to be done to completely replace the distro-boot scripts:
>>
>>  - implement extensions (devicetree overlays with add-on boards)
>> +- implement legacy (boot image v2) android boot flow
>>
>>  Other ideas:
>>
>>
>> --
>> 2.45.0
>>
>
> Some comments after a quick "scan". Will take a more detailed look a
> bit later today/tomorrow.

Thanks a bunch for your quick scan. You caught quite some issues
already.
I will implement these suggestions and will be awaiting the detailed
look before sending a v2.

> -- 
> Best regards - Atentamente - Meilleures salutations
>
> Igor Opaniuk
>
> mailto: igor.opaniuk@gmail.com
> skype: igor.opanyuk
> https://www.linkedin.com/in/iopaniuk
Simon Glass June 11, 2024, 6:52 p.m. UTC | #3
Hi Mattijs,

On Thu, 6 Jun 2024 at 06:24, Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> Android boot flow is a bit different than a regular Linux distro.
> Android relies on multiple partitions in order to boot.
>
> A typical boot flow would be:
> 1. Parse the Bootloader Control Block (BCB, misc partition)
> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
> 3. If BCB requested recovery or normal android, run the following:
> 3.a. Get slot (A/B) from BCB
> 3.b. Run AVB (Android Verified Boot) on boot partitions
> 3.c. Load boot and vendor_boot partitions
> 3.d. Load device-tree, ramdisk and boot
>
> The AOSP documentation has more details at [1], [2], [3]
>
> This has been implemented via complex boot scripts such as [4].
> However, these boot script are neither very maintainable nor generic.
> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>
> Add a generic Android bootflow implementation for bootstd.
> For this initial version, only boot image v4 is supported.
>
> [1] https://source.android.com/docs/core/architecture/bootloader
> [2] https://source.android.com/docs/core/architecture/partitions
> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
>  MAINTAINERS             |   7 +
>  boot/Kconfig            |  14 ++
>  boot/Makefile           |   2 +
>  boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>  boot/bootmeth_android.h |  27 +++
>  doc/develop/bootstd.rst |   6 +
>  6 files changed, 578 insertions(+)

Reviewed-by: Simon Glass <sjg@chromium.org>

nits below

>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 66783d636e3d..6d2b87720565 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -939,6 +939,13 @@ F: include/bootstd.h
>  F:     net/eth_bootdevice.c
>  F:     test/boot/
>
> +BOOTMETH_ANDROID
> +M:     Mattijs Korpershoek <mkorpershoek@baylibre.com>
> +S:     Maintained
> +T:     git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
> +F:     boot/bootmeth_android.c
> +F:     boot/bootmeth_android.h
> +
>  BTRFS
>  M:     Marek Behún <kabel@kernel.org>
>  R:     Qu Wenruo <wqu@suse.com>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 6f3096c15a6f..5fa6f3b8315d 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>           EFI bootmgr, since they take full control over which bootdevs are
>           selected to boot.
>
> +config BOOTMETH_ANDROID
> +       bool "Bootdev support for Android"
> +       depends on X86 || ARM || SANDBOX
> +       select ANDROID_AB
> +       select ANDROID_BOOT_IMAGE
> +       select CMD_BCB
> +       select PARTITION_TYPE_GUID
> +       select PARTITION_UUIDS
> +       help
> +         Enables support for booting Android using bootdevs. Android requires

using standard boot (or using bootstd).

> +         multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
> +
> +         Note that only MMC bootdevs are supported at present.

Why is that limitation present? Can you please mention what is needed
to remove it?

> +
>  config BOOTMETH_CROS
>         bool "Bootdev support for Chromium OS"
>         depends on X86 || ARM || SANDBOX
> diff --git a/boot/Makefile b/boot/Makefile
> index 84ccfeaecec4..75d1cd46fabf 100644
> --- a/boot/Makefile
> +++ b/boot/Makefile
> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
> +
> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
> new file mode 100644
> index 000000000000..26d548d2fd6e
> --- /dev/null
> +++ b/boot/bootmeth_android.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Bootmethod for Android

This is my fault, but I think we should settle on Bootmeth throughout.

> + *
> + * 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>

Can you include that header always?

> +#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 "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"
> +
> +/**
> + * 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

please add member comments as I don't know what these fields are

> + */
> +struct android_priv {
> +       int boot_mode;
> +       char slot[2];
> +       u32 header_version;
> +};
> +
> +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;
> +
> +       sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
> +       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);
> +       }
> +
> +       priv->header_version = android_image_get_version(buf);
> +
> +       return 0;
> +}
> +
> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
> +{
> +       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;
> +
> +       sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
> +       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);
> +       }
> +
> +       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;
> +
> +       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[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("slot", ret);

perhaps "cmdl" ?

> +
> +       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 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

I believe this first line should just be /*

> +        * 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);
> +
> +       bflow->bootmeth_priv = priv;

ideally this would happen at the end of the function

> +       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->state = BOOTFLOWST_READY;
> +               return 0;
> +       }
> +
> +       /* For recovery and normal boot, we need to scan the partitions */
> +       ret = android_read_slot_from_bcb(bflow, false);
> +       if (ret < 0) {
> +               free(priv);
> +               return log_msg_ret("read slot", ret);
> +       }
> +
> +       ret = scan_boot_part(bflow->blk, priv);
> +       if (ret < 0) {
> +               printf("- scan boot failed: err=%d\n", ret);
> +               free(priv);
> +               return log_msg_ret("scan boot", ret);
> +       }
> +
> +       if (priv->header_version != 4) {
> +               printf("- Only boot.img v4 is supported\n");

shouldn't this be log_debug() ?

> +               free(priv);
> +               return log_msg_ret("version", -EINVAL);
> +       }
> +
> +       ret = scan_vendor_boot_part(bflow->blk, priv->slot);
> +       if (ret < 0) {
> +               printf("- scan vendor_boot failed: err=%d\n", ret);
> +               free(priv);
> +               return log_msg_ret("scan vendor_boot", ret);
> +       }
> +
> +       /* Ignoring return code: setting serial number is not mandatory for booting */
> +       configure_serialno(bflow);
> +
> +       if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
> +               ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);

check line length

> +               if (ret < 0) {
> +                       free(priv);
> +                       return log_msg_ret("normal_boot", ret);
> +               }
> +       }
> +
> +       bflow->state = BOOTFLOWST_READY;
> +
> +       return 0;
> +}
> +
> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
> +                            const char *file_path, ulong addr, 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);
> +}
> +
> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
> +                                 const char slot[2], ulong addr)

What is a slotted partition? This function could use a comment

> +{
> +       struct disk_partition partition;
> +       char partname[PART_NAME_LEN];
> +       int ret;
> +       u32 n;
> +
> +       /* Ensure name fits in partname it should be: <name>_<slot>\0 */
> +       if (strlen(name) > (PART_NAME_LEN - 2 - 1))
> +               return log_msg_ret("name too long", -EINVAL);
> +
> +       sprintf(partname, "%s_%s", name, slot);
> +       ret = part_get_info_by_name(desc, partname, &partition);
> +       if (ret < 0)
> +               return log_msg_ret("part", ret);
> +
> +       n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
> +       if (n < partition.size)
> +               return log_msg_ret("part read", -EIO);
> +
> +       return 0;
> +}
> +
> +#if CONFIG_IS_ENABLED(AVB_VERIFY)

Can you use if() instead, and either rename the functions below or add
this if() to each function?

> +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);
> +
> +       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));
> +               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) {
> +               ret = avb_append_commandline_arg(bflow, extra_args);

This function will write to extra_args...is that OK?

> +               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;
> +

drop blank line

> +       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, loadaddr);
> +       if (ret < 0)
> +               return log_msg_ret("read boot", ret);
> +
> +       ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
> +       if (ret < 0)
> +               return log_msg_ret("read vendor_boot", ret);
> +
> +       set_abootimg_addr(loadaddr);
> +       set_avendor_bootimg_addr(vloadaddr);
> +
> +       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,
> +};
> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
> new file mode 100644
> index 000000000000..411c2f2d15e7
> --- /dev/null
> +++ b/boot/bootmeth_android.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Bootmethod for Android
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +
> +enum android_boot_mode {
> +       ANDROID_BOOT_MODE_NORMAL = 0,
> +
> +       /* Android "recovery" is a special boot mode that uses another ramdisk.
> +        * It can be used to "factory reset" a board or to flash logical partitions
> +        * It operates in 2 modes: adb or fastbootd
> +        * To enter recovery from Android, we can do:
> +        * $ adb reboot recovery
> +        * $ adb reboot fastboot
> +        */
> +       ANDROID_BOOT_MODE_RECOVERY,
> +
> +       /* Android "bootloader" is for accessing/reflashing physical partitions
> +        * Typically, this will launch a fastboot process in U-Boot.
> +        * To enter "bootloader" from Android, we can do:
> +        * $ adb reboot bootloader
> +        */
> +       ANDROID_BOOT_MODE_BOOTLOADER,
> +};
> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
> index a07a72581e7a..709fa9e64ff3 100644
> --- a/doc/develop/bootstd.rst
> +++ b/doc/develop/bootstd.rst
> @@ -95,6 +95,7 @@ bootflows.
>
>  Note: it is possible to have a bootmeth that uses a partition or a whole device
>  directly, but it is more common to use a filesystem.
> +For example, the Android bootmeth uses a whole device.
>
>  Note that some bootmeths are 'global', meaning that they select the bootdev
>  themselves. Examples include VBE and EFI boot manager. In this case, they
> @@ -277,6 +278,9 @@ script_offset_f
>  script_size_f
>      Size of the script to load, e.g. 0x2000
>
> +vendor_boot_comp_addr_r
> +    Address to which to load the vendor_boot Android image, e.g. 0xe0000000
> +
>  Some variables are set by script bootmeth:
>
>  devtype
> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>     - EFI boot using bootefi from disk
>     - VBE
>     - EFI boot using boot manager
> +   - Android bootflow (boot image v4)
>
>
>  Command interface
> @@ -786,6 +791,7 @@ To do
>  Some things that need to be done to completely replace the distro-boot scripts:
>
>  - implement extensions (devicetree overlays with add-on boards)
> +- implement legacy (boot image v2) android boot flow

Nice!

>
>  Other ideas:
>
>
> --
> 2.45.0
>

Regards,
Simon
Mattijs Korpershoek June 12, 2024, 10:18 a.m. UTC | #4
Hi Simon,

Thank you for your review.

On mar., juin 11, 2024 at 12:52, Simon Glass <sjg@chromium.org> wrote:

> Hi Mattijs,
>
> On Thu, 6 Jun 2024 at 06:24, Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> Android boot flow is a bit different than a regular Linux distro.
>> Android relies on multiple partitions in order to boot.
>>
>> A typical boot flow would be:
>> 1. Parse the Bootloader Control Block (BCB, misc partition)
>> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
>> 3. If BCB requested recovery or normal android, run the following:
>> 3.a. Get slot (A/B) from BCB
>> 3.b. Run AVB (Android Verified Boot) on boot partitions
>> 3.c. Load boot and vendor_boot partitions
>> 3.d. Load device-tree, ramdisk and boot
>>
>> The AOSP documentation has more details at [1], [2], [3]
>>
>> This has been implemented via complex boot scripts such as [4].
>> However, these boot script are neither very maintainable nor generic.
>> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>>
>> Add a generic Android bootflow implementation for bootstd.
>> For this initial version, only boot image v4 is supported.
>>
>> [1] https://source.android.com/docs/core/architecture/bootloader
>> [2] https://source.android.com/docs/core/architecture/partitions
>> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
>> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
>> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>>  MAINTAINERS             |   7 +
>>  boot/Kconfig            |  14 ++
>>  boot/Makefile           |   2 +
>>  boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>>  boot/bootmeth_android.h |  27 +++
>>  doc/develop/bootstd.rst |   6 +
>>  6 files changed, 578 insertions(+)
>
> Reviewed-by: Simon Glass <sjg@chromium.org>
>
> nits below
>
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 66783d636e3d..6d2b87720565 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -939,6 +939,13 @@ F: include/bootstd.h
>>  F:     net/eth_bootdevice.c
>>  F:     test/boot/
>>
>> +BOOTMETH_ANDROID
>> +M:     Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> +S:     Maintained
>> +T:     git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
>> +F:     boot/bootmeth_android.c
>> +F:     boot/bootmeth_android.h
>> +
>>  BTRFS
>>  M:     Marek Behún <kabel@kernel.org>
>>  R:     Qu Wenruo <wqu@suse.com>
>> diff --git a/boot/Kconfig b/boot/Kconfig
>> index 6f3096c15a6f..5fa6f3b8315d 100644
>> --- a/boot/Kconfig
>> +++ b/boot/Kconfig
>> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>>           EFI bootmgr, since they take full control over which bootdevs are
>>           selected to boot.
>>
>> +config BOOTMETH_ANDROID
>> +       bool "Bootdev support for Android"
>> +       depends on X86 || ARM || SANDBOX
>> +       select ANDROID_AB
>> +       select ANDROID_BOOT_IMAGE
>> +       select CMD_BCB
>> +       select PARTITION_TYPE_GUID
>> +       select PARTITION_UUIDS
>> +       help
>> +         Enables support for booting Android using bootdevs. Android requires
>
> using standard boot (or using bootstd).

Will do for v2.

>
>> +         multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
>> +
>> +         Note that only MMC bootdevs are supported at present.
>
> Why is that limitation present? Can you please mention what is needed
> to remove it?

Mainly because we use AVB and AVB is hard-coded for MMC. Alistair
submitted changes to convert to generic block devices here:

https://lore.kernel.org/all/20220926220211.868968-1-adelva@google.com/

There were some review comments but I did not see any v2 on the list.

I will add a comment in the KConfig description.

>
>> +
>>  config BOOTMETH_CROS
>>         bool "Bootdev support for Chromium OS"
>>         depends on X86 || ARM || SANDBOX
>> diff --git a/boot/Makefile b/boot/Makefile
>> index 84ccfeaecec4..75d1cd46fabf 100644
>> --- a/boot/Makefile
>> +++ b/boot/Makefile
>> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>>  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
>> +
>> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
>> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
>> new file mode 100644
>> index 000000000000..26d548d2fd6e
>> --- /dev/null
>> +++ b/boot/bootmeth_android.c
>> @@ -0,0 +1,522 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Bootmethod for Android
>
> This is my fault, but I think we should settle on Bootmeth throughout.

Yes, thank you for noticing. Will do for v2.

>
>> + *
>> + * 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>
>
> Can you include that header always?

I can't, it does not link if I do.

It would be possible if we implemented stub functions but that's not the
case today.

Since this is a nitpick, I will consider this for a future improvement.

>
>> +#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 "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"
>> +
>> +/**
>> + * 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
>
> please add member comments as I don't know what these fields are

Will do for v2.

>
>> + */
>> +struct android_priv {
>> +       int boot_mode;
>> +       char slot[2];
>> +       u32 header_version;
>> +};
>> +
>> +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;
>> +
>> +       sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
>> +       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);
>> +       }
>> +
>> +       priv->header_version = android_image_get_version(buf);
>> +
>> +       return 0;
>> +}
>> +
>> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
>> +{
>> +       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;
>> +
>> +       sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
>> +       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);
>> +       }
>> +
>> +       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;
>> +
>> +       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[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("slot", ret);
>
> perhaps "cmdl" ?

Yes, will do for v2.

>
>> +
>> +       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 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
>
> I believe this first line should just be /*

You are right. I've made this mistake in other parts of the file.
Will fix all occurences for v2.

>
>> +        * 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);
>> +
>> +       bflow->bootmeth_priv = priv;
>
> ideally this would happen at the end of the function

Yes, Igor pointed out as well. It's dangerous to have a dangling
pointer.
Will do for v2.

>
>> +       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->state = BOOTFLOWST_READY;
>> +               return 0;
>> +       }
>> +
>> +       /* For recovery and normal boot, we need to scan the partitions */
>> +       ret = android_read_slot_from_bcb(bflow, false);
>> +       if (ret < 0) {
>> +               free(priv);
>> +               return log_msg_ret("read slot", ret);
>> +       }
>> +
>> +       ret = scan_boot_part(bflow->blk, priv);
>> +       if (ret < 0) {
>> +               printf("- scan boot failed: err=%d\n", ret);
>> +               free(priv);
>> +               return log_msg_ret("scan boot", ret);
>> +       }
>> +
>> +       if (priv->header_version != 4) {
>> +               printf("- Only boot.img v4 is supported\n");
>
> shouldn't this be log_debug() ?

Hmm, I've modeled this to be similar to bootmeth_cros, but I can convert
to log_debug(). I don't have a strong opinion on this.

>
>> +               free(priv);
>> +               return log_msg_ret("version", -EINVAL);
>> +       }
>> +
>> +       ret = scan_vendor_boot_part(bflow->blk, priv->slot);
>> +       if (ret < 0) {
>> +               printf("- scan vendor_boot failed: err=%d\n", ret);
>> +               free(priv);
>> +               return log_msg_ret("scan vendor_boot", ret);
>> +       }
>> +
>> +       /* Ignoring return code: setting serial number is not mandatory for booting */
>> +       configure_serialno(bflow);
>> +
>> +       if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
>> +               ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
>
> check line length

Yes, somehow I missed this with checkpatch.
Will fix for v2.

>
>> +               if (ret < 0) {
>> +                       free(priv);
>> +                       return log_msg_ret("normal_boot", ret);
>> +               }
>> +       }
>> +
>> +       bflow->state = BOOTFLOWST_READY;
>> +
>> +       return 0;
>> +}
>> +
>> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
>> +                            const char *file_path, ulong addr, 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);
>> +}
>> +
>> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
>> +                                 const char slot[2], ulong addr)
>
> What is a slotted partition? This function could use a comment

Most modern Android devices use "Seamless updates", where each partition
is duplicated. For example, the boot partition has boot_a and boot_b.
This is what I called "slotted partition".

See:
https://source.android.com/docs/core/ota/ab
https://source.android.com/docs/core/ota/ab/ab_implement

I will add a comment pointing to the above documentation and some
additional explanation in v2.

>
>> +{
>> +       struct disk_partition partition;
>> +       char partname[PART_NAME_LEN];
>> +       int ret;
>> +       u32 n;
>> +
>> +       /* Ensure name fits in partname it should be: <name>_<slot>\0 */
>> +       if (strlen(name) > (PART_NAME_LEN - 2 - 1))
>> +               return log_msg_ret("name too long", -EINVAL);
>> +
>> +       sprintf(partname, "%s_%s", name, slot);
>> +       ret = part_get_info_by_name(desc, partname, &partition);
>> +       if (ret < 0)
>> +               return log_msg_ret("part", ret);
>> +
>> +       n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
>> +       if (n < partition.size)
>> +               return log_msg_ret("part read", -EIO);
>> +
>> +       return 0;
>> +}
>> +
>> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
>
> Can you use if() instead, and either rename the functions below or add
> this if() to each function?

I will try, but most avb_ops related functions are not stubbed so I'm
not sure we can do this easily.

Since this is a nitpick, I might consider this a future improvement (If
stubs are needed, for example).

>
>> +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);
>> +
>> +       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));
>> +               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) {
>> +               ret = avb_append_commandline_arg(bflow, extra_args);
>
> This function will write to extra_args...is that OK?

Yes, extra_args is no longer used after this.
I will add a comment to state that this is safe in v2.

>
>> +               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;
>> +
>
> drop blank line

Will do for v2.

>
>> +       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, loadaddr);
>> +       if (ret < 0)
>> +               return log_msg_ret("read boot", ret);
>> +
>> +       ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
>> +       if (ret < 0)
>> +               return log_msg_ret("read vendor_boot", ret);
>> +
>> +       set_abootimg_addr(loadaddr);
>> +       set_avendor_bootimg_addr(vloadaddr);
>> +
>> +       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,
>> +};
>> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
>> new file mode 100644
>> index 000000000000..411c2f2d15e7
>> --- /dev/null
>> +++ b/boot/bootmeth_android.h
>> @@ -0,0 +1,27 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Bootmethod for Android
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +
>> +enum android_boot_mode {
>> +       ANDROID_BOOT_MODE_NORMAL = 0,
>> +
>> +       /* Android "recovery" is a special boot mode that uses another ramdisk.
>> +        * It can be used to "factory reset" a board or to flash logical partitions
>> +        * It operates in 2 modes: adb or fastbootd
>> +        * To enter recovery from Android, we can do:
>> +        * $ adb reboot recovery
>> +        * $ adb reboot fastboot
>> +        */
>> +       ANDROID_BOOT_MODE_RECOVERY,
>> +
>> +       /* Android "bootloader" is for accessing/reflashing physical partitions
>> +        * Typically, this will launch a fastboot process in U-Boot.
>> +        * To enter "bootloader" from Android, we can do:
>> +        * $ adb reboot bootloader
>> +        */
>> +       ANDROID_BOOT_MODE_BOOTLOADER,
>> +};
>> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
>> index a07a72581e7a..709fa9e64ff3 100644
>> --- a/doc/develop/bootstd.rst
>> +++ b/doc/develop/bootstd.rst
>> @@ -95,6 +95,7 @@ bootflows.
>>
>>  Note: it is possible to have a bootmeth that uses a partition or a whole device
>>  directly, but it is more common to use a filesystem.
>> +For example, the Android bootmeth uses a whole device.
>>
>>  Note that some bootmeths are 'global', meaning that they select the bootdev
>>  themselves. Examples include VBE and EFI boot manager. In this case, they
>> @@ -277,6 +278,9 @@ script_offset_f
>>  script_size_f
>>      Size of the script to load, e.g. 0x2000
>>
>> +vendor_boot_comp_addr_r
>> +    Address to which to load the vendor_boot Android image, e.g. 0xe0000000
>> +
>>  Some variables are set by script bootmeth:
>>
>>  devtype
>> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>>     - EFI boot using bootefi from disk
>>     - VBE
>>     - EFI boot using boot manager
>> +   - Android bootflow (boot image v4)
>>
>>
>>  Command interface
>> @@ -786,6 +791,7 @@ To do
>>  Some things that need to be done to completely replace the distro-boot scripts:
>>
>>  - implement extensions (devicetree overlays with add-on boards)
>> +- implement legacy (boot image v2) android boot flow
>
> Nice!

Thank you! It was easier to implement that I imagined, mainly because
the bootflow framework is well written (imo) and the bootmeth_cros example
was very helpful as well.

>
>>
>>  Other ideas:
>>
>>
>> --
>> 2.45.0
>>
>
> Regards,
> Simon
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 66783d636e3d..6d2b87720565 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -939,6 +939,13 @@  F:	include/bootstd.h
 F:	net/eth_bootdevice.c
 F:	test/boot/
 
+BOOTMETH_ANDROID
+M:	Mattijs Korpershoek <mkorpershoek@baylibre.com>
+S:	Maintained
+T:	git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
+F:	boot/bootmeth_android.c
+F:	boot/bootmeth_android.h
+
 BTRFS
 M:	Marek Behún <kabel@kernel.org>
 R:	Qu Wenruo <wqu@suse.com>
diff --git a/boot/Kconfig b/boot/Kconfig
index 6f3096c15a6f..5fa6f3b8315d 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -494,6 +494,20 @@  config BOOTMETH_GLOBAL
 	  EFI bootmgr, since they take full control over which bootdevs are
 	  selected to boot.
 
+config BOOTMETH_ANDROID
+	bool "Bootdev support for Android"
+	depends on X86 || ARM || SANDBOX
+	select ANDROID_AB
+	select ANDROID_BOOT_IMAGE
+	select CMD_BCB
+	select PARTITION_TYPE_GUID
+	select PARTITION_UUIDS
+	help
+	  Enables support for booting Android using bootdevs. Android requires
+	  multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
+
+	  Note that only MMC bootdevs are supported at present.
+
 config BOOTMETH_CROS
 	bool "Bootdev support for Chromium OS"
 	depends on X86 || ARM || SANDBOX
diff --git a/boot/Makefile b/boot/Makefile
index 84ccfeaecec4..75d1cd46fabf 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -66,3 +66,5 @@  obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
 obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
+
+obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
new file mode 100644
index 000000000000..26d548d2fd6e
--- /dev/null
+++ b/boot/bootmeth_android.c
@@ -0,0 +1,522 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod 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 "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"
+
+/**
+ * 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
+ */
+struct android_priv {
+	int boot_mode;
+	char slot[2];
+	u32 header_version;
+};
+
+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;
+
+	sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
+	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);
+	}
+
+	priv->header_version = android_image_get_version(buf);
+
+	return 0;
+}
+
+static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
+{
+	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;
+
+	sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
+	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);
+	}
+
+	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;
+
+	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[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("slot", 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 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);
+
+	bflow->bootmeth_priv = priv;
+	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->state = BOOTFLOWST_READY;
+		return 0;
+	}
+
+	/* For recovery and normal boot, we need to scan the partitions */
+	ret = android_read_slot_from_bcb(bflow, false);
+	if (ret < 0) {
+		free(priv);
+		return log_msg_ret("read slot", ret);
+	}
+
+	ret = scan_boot_part(bflow->blk, priv);
+	if (ret < 0) {
+		printf("- scan boot failed: err=%d\n", ret);
+		free(priv);
+		return log_msg_ret("scan boot", ret);
+	}
+
+	if (priv->header_version != 4) {
+		printf("- Only boot.img v4 is supported\n");
+		free(priv);
+		return log_msg_ret("version", -EINVAL);
+	}
+
+	ret = scan_vendor_boot_part(bflow->blk, priv->slot);
+	if (ret < 0) {
+		printf("- scan vendor_boot failed: err=%d\n", ret);
+		free(priv);
+		return log_msg_ret("scan vendor_boot", ret);
+	}
+
+	/* Ignoring return code: setting serial number is not mandatory for booting */
+	configure_serialno(bflow);
+
+	if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
+		ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
+		if (ret < 0) {
+			free(priv);
+			return log_msg_ret("normal_boot", ret);
+		}
+	}
+
+	bflow->state = BOOTFLOWST_READY;
+
+	return 0;
+}
+
+static int android_read_file(struct udevice *dev, struct bootflow *bflow,
+			     const char *file_path, ulong addr, 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);
+}
+
+static int read_slotted_partition(struct blk_desc *desc, const char *const name,
+				  const char slot[2], ulong addr)
+{
+	struct disk_partition partition;
+	char partname[PART_NAME_LEN];
+	int ret;
+	u32 n;
+
+	/* Ensure name fits in partname it should be: <name>_<slot>\0 */
+	if (strlen(name) > (PART_NAME_LEN - 2 - 1))
+		return log_msg_ret("name too long", -EINVAL);
+
+	sprintf(partname, "%s_%s", name, slot);
+	ret = part_get_info_by_name(desc, partname, &partition);
+	if (ret < 0)
+		return log_msg_ret("part", ret);
+
+	n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
+	if (n < partition.size)
+		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);
+
+	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));
+		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) {
+		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, loadaddr);
+	if (ret < 0)
+		return log_msg_ret("read boot", ret);
+
+	ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
+	if (ret < 0)
+		return log_msg_ret("read vendor_boot", ret);
+
+	set_abootimg_addr(loadaddr);
+	set_avendor_bootimg_addr(vloadaddr);
+
+	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,
+};
diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
new file mode 100644
index 000000000000..411c2f2d15e7
--- /dev/null
+++ b/boot/bootmeth_android.h
@@ -0,0 +1,27 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Bootmethod for Android
+ *
+ * Copyright (C) 2024 BayLibre, SAS
+ * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
+ */
+
+enum android_boot_mode {
+	ANDROID_BOOT_MODE_NORMAL = 0,
+
+	/* Android "recovery" is a special boot mode that uses another ramdisk.
+	 * It can be used to "factory reset" a board or to flash logical partitions
+	 * It operates in 2 modes: adb or fastbootd
+	 * To enter recovery from Android, we can do:
+	 * $ adb reboot recovery
+	 * $ adb reboot fastboot
+	 */
+	ANDROID_BOOT_MODE_RECOVERY,
+
+	/* Android "bootloader" is for accessing/reflashing physical partitions
+	 * Typically, this will launch a fastboot process in U-Boot.
+	 * To enter "bootloader" from Android, we can do:
+	 * $ adb reboot bootloader
+	 */
+	ANDROID_BOOT_MODE_BOOTLOADER,
+};
diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
index a07a72581e7a..709fa9e64ff3 100644
--- a/doc/develop/bootstd.rst
+++ b/doc/develop/bootstd.rst
@@ -95,6 +95,7 @@  bootflows.
 
 Note: it is possible to have a bootmeth that uses a partition or a whole device
 directly, but it is more common to use a filesystem.
+For example, the Android bootmeth uses a whole device.
 
 Note that some bootmeths are 'global', meaning that they select the bootdev
 themselves. Examples include VBE and EFI boot manager. In this case, they
@@ -277,6 +278,9 @@  script_offset_f
 script_size_f
     Size of the script to load, e.g. 0x2000
 
+vendor_boot_comp_addr_r
+    Address to which to load the vendor_boot Android image, e.g. 0xe0000000
+
 Some variables are set by script bootmeth:
 
 devtype
@@ -418,6 +422,7 @@  Bootmeth drivers are provided for:
    - EFI boot using bootefi from disk
    - VBE
    - EFI boot using boot manager
+   - Android bootflow (boot image v4)
 
 
 Command interface
@@ -786,6 +791,7 @@  To do
 Some things that need to be done to completely replace the distro-boot scripts:
 
 - implement extensions (devicetree overlays with add-on boards)
+- implement legacy (boot image v2) android boot flow
 
 Other ideas: