From 58fed99f46115fe8547e7978fa361bbbbb3f3b50 Mon Sep 17 00:00:00 2001 From: Mattijs Korpershoek Date: Wed, 10 Jul 2024 10:40:02 +0200 Subject: [PATCH 1/5] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() When calling android_image_get_dtb_by_index() using boot image v3+, we should also pass the vendor_boot ramdisk address. Use get_avendor_bootimg_addr() to do so. Notes: on boot image v2, this is harmless since get_avendor_bootimg_addr() returns -1. for legacy implementations that don't have CMD_ABOOTIMG, add a weak implementation to avoid linking errors. Reviewed-by: Simon Glass Reviewed-by: Julien Masson Reviewed-by: Igor Opaniuk Reviewed-by: Guillaume La Roque Tested-by: Guillaume La Roque Signed-off-by: Mattijs Korpershoek --- boot/image-android.c | 5 +++++ boot/image-fdt.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/boot/image-android.c b/boot/image-android.c index ee626972c11..09c7a44e058 100644 --- a/boot/image-android.c +++ b/boot/image-android.c @@ -56,6 +56,11 @@ static ulong add_trailer(ulong bootconfig_start_addr, ulong bootconfig_size) return BOOTCONFIG_TRAILER_SIZE; } +__weak ulong get_avendor_bootimg_addr(void) +{ + return -1; +} + static void android_boot_image_v3_v4_parse_hdr(const struct andr_boot_img_hdr_v3 *hdr, struct andr_image_data *data) { diff --git a/boot/image-fdt.c b/boot/image-fdt.c index 56dd7687f51..8332792b8e8 100644 --- a/boot/image-fdt.c +++ b/boot/image-fdt.c @@ -502,7 +502,7 @@ int boot_get_fdt(void *buf, const char *select, uint arch, * Firstly check if this android boot image has dtb field. */ dtb_idx = (u32)env_get_ulong("adtb_idx", 10, 0); - if (android_image_get_dtb_by_index((ulong)hdr, 0, + if (android_image_get_dtb_by_index((ulong)hdr, get_avendor_bootimg_addr(), dtb_idx, &fdt_addr, &fdt_size)) { fdt_blob = (char *)map_sysmem(fdt_addr, 0); if (fdt_check_header(fdt_blob)) From be0b076c55a3de8a1a10053a8db9ff9ca8ffb8ac Mon Sep 17 00:00:00 2001 From: Mattijs Korpershoek Date: Wed, 10 Jul 2024 10:40:03 +0200 Subject: [PATCH 2/5] bootstd: Add bootflow_iter_check_mmc() helper Some bootflows might be able to only boot from MMC devices. Add a helper function these bootflows can use. Reviewed-by: Igor Opaniuk Reviewed-by: Julien Masson Reviewed-by: Guillaume La Roque Tested-by: Guillaume La Roque Signed-off-by: Mattijs Korpershoek Reviewed-by: Simon Glass --- boot/bootflow.c | 12 ++++++++++++ include/bootflow.h | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/boot/bootflow.c b/boot/bootflow.c index 9aa3179c388..59d77d2385f 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -575,6 +575,18 @@ int bootflow_iter_check_blk(const struct bootflow_iter *iter) return -ENOTSUPP; } +int bootflow_iter_check_mmc(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_MMC) + return 0; + + return -ENOTSUPP; +} + int bootflow_iter_check_sf(const struct bootflow_iter *iter) { const struct udevice *media = dev_get_parent(iter->dev); diff --git a/include/bootflow.h b/include/bootflow.h index 6affc5e1a4f..4d2fc7b69b5 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -408,6 +408,15 @@ void bootflow_remove(struct bootflow *bflow); */ int bootflow_iter_check_blk(const struct bootflow_iter *iter); +/** + * bootflow_iter_check_mmc() - Check that a bootflow uses a MMC device + * + * This checks the bootdev in the bootflow to make sure it uses a mmc device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. ethernet) + */ +int bootflow_iter_check_mmc(const struct bootflow_iter *iter); + /** * bootflow_iter_check_sf() - Check that a bootflow uses SPI FLASH * From a525656c5ba69d307864c4de14e911384cc7dc0c Mon Sep 17 00:00:00 2001 From: Mattijs Korpershoek Date: Wed, 10 Jul 2024 10:40:04 +0200 Subject: [PATCH 3/5] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr() The only way to configure the load addresses for both bootimg and vendor_bootimg is by using the "abootimg" command. If we want to use the C API, there is no equivalent. Add set_abootimg_addr() and set_avendor_bootimg_addr() so that we can specify the load address from C. This can be useful for implementing an Android bootmethod. Reviewed-by: Igor Opaniuk Reviewed-by: Julien Masson Reviewed-by: Simon Glass Reviewed-by: Guillaume La Roque Tested-by: Guillaume La Roque Signed-off-by: Mattijs Korpershoek --- cmd/abootimg.c | 10 ++++++++++ include/image.h | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/cmd/abootimg.c b/cmd/abootimg.c index 327712a536c..ae7a1a7c83b 100644 --- a/cmd/abootimg.c +++ b/cmd/abootimg.c @@ -22,6 +22,11 @@ ulong get_abootimg_addr(void) return (_abootimg_addr == -1 ? image_load_addr : _abootimg_addr); } +void set_abootimg_addr(ulong addr) +{ + _abootimg_addr = addr; +} + ulong get_ainit_bootimg_addr(void) { return _ainit_bootimg_addr; @@ -32,6 +37,11 @@ ulong get_avendor_bootimg_addr(void) return _avendor_bootimg_addr; } +void set_avendor_bootimg_addr(ulong addr) +{ + _avendor_bootimg_addr = addr; +} + static int abootimg_get_ver(int argc, char *const argv[]) { const struct andr_boot_img_hdr_v0 *hdr; diff --git a/include/image.h b/include/image.h index 9daaee15cdb..dd4042d1bd9 100644 --- a/include/image.h +++ b/include/image.h @@ -1971,6 +1971,13 @@ bool is_android_vendor_boot_image_header(const void *vendor_boot_img); */ ulong get_abootimg_addr(void); +/** + * set_abootimg_addr() - Set Android boot image address + * + * Return: no returned results + */ +void set_abootimg_addr(ulong addr); + /** * get_ainit_bootimg_addr() - Get Android init boot image address * @@ -1985,6 +1992,13 @@ ulong get_ainit_bootimg_addr(void); */ ulong get_avendor_bootimg_addr(void); +/** + * set_abootimg_addr() - Set Android vendor boot image address + * + * Return: no returned results + */ +void set_avendor_bootimg_addr(ulong addr); + /** * board_fit_config_name_match() - Check for a matching board name * From 125d9f3306ea4bc8888dc991ae3aa87327b0d522 Mon Sep 17 00:00:00 2001 From: Mattijs Korpershoek Date: Wed, 10 Jul 2024 10:40:05 +0200 Subject: [PATCH 4/5] bootstd: Add a bootmeth for Android 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/ Reviewed-by: Simon Glass Reviewed-by: Julien Masson Tested-by: Guillaume La Roque Signed-off-by: Mattijs Korpershoek --- MAINTAINERS | 7 + boot/Kconfig | 17 ++ boot/Makefile | 2 + boot/bootmeth_android.c | 553 ++++++++++++++++++++++++++++++++++++++++ boot/bootmeth_android.h | 29 +++ doc/develop/bootstd.rst | 6 + 6 files changed, 614 insertions(+) create mode 100644 boot/bootmeth_android.c create mode 100644 boot/bootmeth_android.h diff --git a/MAINTAINERS b/MAINTAINERS index 2c6de3a1d84..19ca57d200b 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 +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 R: Qu Wenruo diff --git a/boot/Kconfig b/boot/Kconfig index ffcae840a50..4a17deaa3e2 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -494,6 +494,23 @@ 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 + depends on CMDLINE + select ANDROID_AB + select ANDROID_BOOT_IMAGE + select CMD_BCB + select CMD_FASTBOOT + select PARTITION_TYPE_GUID + select PARTITION_UUIDS + help + Enables support for booting Android using bootstd. Android requires + multiple partitions (misc, boot, vbmeta, ...) in storage for booting. + + Note that only MMC bootdevs are supported at present. This is caused + by AVB being limited to MMC devices only. + config BOOTMETH_CROS bool "Bootdev support for Chromium OS" depends on X86 || ARM || SANDBOX diff --git a/boot/Makefile b/boot/Makefile index 84ccfeaecec..75d1cd46fab 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 00000000000..6e8d3e615db --- /dev/null +++ b/boot/bootmeth_android.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmeth for Android + * + * Copyright (C) 2024 BayLibre, SAS + * Written by Mattijs Korpershoek + */ +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#if CONFIG_IS_ENABLED(AVB_VERIFY) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * @boot_mode: Requested boot mode (normal, recovery, bootloader) + * @slot: Nul-terminated partition slot suffix read from BCB ("a\0" or "b\0") + * @header_version: Android boot image header version + */ +struct android_priv { + enum android_boot_mode boot_mode; + char slot[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 = ((struct andr_boot_img_hdr_v0 *)buf)->header_version; + free(buf); + + return 0; +} + +static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + struct disk_partition partition; + char partname[PART_NAME_LEN]; + ulong num_blks, bufsz; + char *buf; + int ret; + + sprintf(partname, VENDOR_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_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); + } + free(buf); + + return 0; +} + +static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement) +{ + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); + struct android_priv *priv = bflow->bootmeth_priv; + struct disk_partition misc; + char slot_suffix[3]; + int ret; + + 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("cmdl", ret); + + return 0; +} + +static int configure_serialno(struct bootflow *bflow) +{ + char *serialno = env_get("serial#"); + + if (!serialno) + return log_msg_ret("serial", -ENOENT); + + return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false); +} + +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); + struct disk_partition misc; + struct android_priv *priv; + char command[BCB_FIELD_COMMAND_SZ]; + int ret; + + bflow->state = BOOTFLOWST_MEDIA; + + /* + * bcb_find_partition_and_load() will print errors to stdout + * if BCB_PART_NAME is not found. To avoid that, check if the + * partition exists first. + */ + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc); + if (ret < 0) + return log_msg_ret("part", ret); + + ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME); + if (ret < 0) + return log_msg_ret("bcb load", ret); + + ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command)); + if (ret < 0) + return log_msg_ret("bcb read", ret); + + priv = malloc(sizeof(struct android_priv)); + if (!priv) + return log_msg_ret("buf", -ENOMEM); + + if (!strcmp("bootonce-bootloader", command)) { + priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER; + bflow->os_name = strdup("Android (bootloader)"); + } else if (!strcmp("boot-fastboot", command)) { + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY; + bflow->os_name = strdup("Android (fastbootd)"); + } else if (!strcmp("boot-recovery", command)) { + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY; + bflow->os_name = strdup("Android (recovery)"); + } else { + priv->boot_mode = ANDROID_BOOT_MODE_NORMAL; + bflow->os_name = strdup("Android"); + } + if (!bflow->os_name) + return log_msg_ret("os", -ENOMEM); + + if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) { + /* Clear BCB */ + memset(command, 0, sizeof(command)); + ret = bcb_set(BCB_FIELD_COMMAND, command); + if (ret < 0) { + free(priv); + return log_msg_ret("bcb set", ret); + } + ret = bcb_store(); + if (ret < 0) { + free(priv); + return log_msg_ret("bcb store", ret); + } + + bflow->bootmeth_priv = priv; + bflow->state = BOOTFLOWST_READY; + return 0; + } + + bflow->bootmeth_priv = priv; + + /* For recovery and normal boot, we need to scan the partitions */ + ret = android_read_slot_from_bcb(bflow, false); + if (ret < 0) { + log_err("read slot: %d", ret); + goto free_priv; + } + + ret = scan_boot_part(bflow->blk, priv); + if (ret < 0) { + log_debug("scan boot failed: err=%d\n", ret); + goto free_priv; + } + + if (priv->header_version != 4) { + log_debug("only boot.img v4 is supported %u\n", priv->header_version); + ret = -EINVAL; + goto free_priv; + } + + ret = scan_vendor_boot_part(bflow->blk, priv); + if (ret < 0) { + log_debug("scan vendor_boot failed: err=%d\n", ret); + goto free_priv; + } + + /* Ignoring return code: 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) { + log_debug("normal_boot %d", ret); + goto free_priv; + } + } + + bflow->state = BOOTFLOWST_READY; + + return 0; + + free_priv: + free(priv); + bflow->bootmeth_priv = NULL; + return ret; +} + +static int android_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + /* + * Reading individual files is not supported since we only + * operate on whole mmc devices (because we require multiple partitions) + */ + return log_msg_ret("Unsupported", -ENOSYS); +} + +/** + * read_slotted_partition() - Read a partition by appending a slot suffix + * + * Most modern Android devices use Seamless Updates, where each partition + * is duplicated. For example, the boot partition has boot_a and boot_b. + * For more information, see: + * https://source.android.com/docs/core/ota/ab + * https://source.android.com/docs/core/ota/ab/ab_implement + * + * @blk: Block device to read + * @name: Partition name to read + * @slot: Nul-terminated slot suffixed to partition name ("a\0" or "b\0") + * @addr: Address where the partition content is loaded into + * Return: 0 if OK, negative errno on failure. + */ +static int read_slotted_partition(struct blk_desc *desc, const char *const name, + const char slot[2], ulong addr) +{ + struct disk_partition partition; + char partname[PART_NAME_LEN]; + int ret; + u32 n; + + /* Ensure name fits in partname it should be: _\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_slot_verify_data_free(out_data); + return log_msg_ret("avb verify", -EIO); + } + + if (unlocked) + boot_state = AVB_ORANGE; + else + boot_state = AVB_GREEN; + + extra_args = avb_set_state(avb_ops, boot_state); + if (extra_args) { + /* extra_args will be modified after this. This is fine */ + ret = avb_append_commandline_arg(bflow, extra_args); + if (ret < 0) + goto free_out_data; + } + + ret = avb_append_commandline(bflow, out_data->cmdline); + if (ret < 0) + goto free_out_data; + + return 0; + + free_out_data: + if (out_data) + avb_slot_verify_data_free(out_data); + + return log_msg_ret("avb cmdline", ret); +} +#else +static int run_avb_verification(struct bootflow *bflow) +{ + int ret; + + /* When AVB is unsupported, pass ORANGE state */ + ret = bootflow_cmdline_set_arg(bflow, + "androidboot.verifiedbootstate", + "orange", false); + if (ret < 0) + return log_msg_ret("avb cmdline", ret); + + return 0; +} +#endif /* AVB_VERIFY */ + +static int boot_android_normal(struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); + struct android_priv *priv = bflow->bootmeth_priv; + int ret; + ulong loadaddr = env_get_hex("loadaddr", 0); + ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0); + + ret = run_avb_verification(bflow); + if (ret < 0) + return log_msg_ret("avb", ret); + + /* Read slot once more to decrement counter from BCB */ + ret = android_read_slot_from_bcb(bflow, true); + if (ret < 0) + return log_msg_ret("read slot", ret); + + ret = read_slotted_partition(desc, "boot", priv->slot, 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 00000000000..a57fad9f011 --- /dev/null +++ b/boot/bootmeth_android.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Bootmethod for Android + * + * Copyright (C) 2024 BayLibre, SAS + * Written by Mattijs Korpershoek + */ + +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 34631089ae0..bdda90fae3f 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: From c2a2a06e01651ed814f8fae0c21fc49dc72b5af1 Mon Sep 17 00:00:00 2001 From: Mattijs Korpershoek Date: Wed, 10 Jul 2024 10:40:06 +0200 Subject: [PATCH 5/5] bootstd: Add test for bootmeth_android Add a unit test for testing the Android bootmethod. This requires another mmc image (mmc7) to contain the following partitions: - misc: contains the Bootloader Control Block (BCB) - boot_a: contains a fake generic kernel image - vendor_boot_a: contains a fake vendor_boot image Also add BOOTMETH_ANDROID as a dependency on sandbox so that we can test this with: $ ./test/py/test.py --bd sandbox --build -k test_ut # build the mmc7.img $ ./test/py/test.py --bd sandbox --build -k bootflow_android Signed-off-by: Mattijs Korpershoek Reviewed-by: Julien Masson Reviewed-by: Guillaume La Roque Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 8 ++++ configs/sandbox_defconfig | 2 +- test/boot/bootflow.c | 68 +++++++++++++++++++++++++++++++-- test/py/tests/test_ut.py | 79 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 4 deletions(-) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index a012f5c4c9b..5fb5eac862e 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -43,6 +43,7 @@ mmc4 = "/mmc4"; mmc5 = "/mmc5"; mmc6 = "/mmc6"; + mmc7 = "/mmc7"; pci0 = &pci0; pci1 = &pci1; pci2 = &pci2; @@ -1129,6 +1130,13 @@ filename = "mmc6.img"; }; + /* This is used for Android tests */ + mmc7 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc7.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index da8c1976d7b..36e43e0e21a 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -15,6 +15,7 @@ CONFIG_FIT=y CONFIG_FIT_RSASSA_PSS=y CONFIG_FIT_CIPHER=y CONFIG_FIT_VERBOSE=y +CONFIG_BOOTMETH_ANDROID=y CONFIG_LEGACY_IMAGE_FORMAT=y CONFIG_MEASURED_BOOT=y CONFIG_BOOTSTAGE=y @@ -40,7 +41,6 @@ CONFIG_LOG_MAX_LEVEL=9 CONFIG_LOG_DEFAULT_LEVEL=6 CONFIG_DISPLAY_BOARDINFO_LATE=y CONFIG_STACKPROTECTOR=y -CONFIG_ANDROID_AB=y CONFIG_CMD_CPU=y CONFIG_CMD_LICENSE=y CONFIG_CMD_SMBIOS=y diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 4511cfa7f9b..8b46256fa48 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -27,6 +27,7 @@ DECLARE_GLOBAL_DATA_PTR; +extern U_BOOT_DRIVER(bootmeth_android); extern U_BOOT_DRIVER(bootmeth_cros); extern U_BOOT_DRIVER(bootmeth_2script); @@ -518,12 +519,12 @@ BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT); * @uts: Unit test state * @mmc_dev: MMC device to use, e.g. "mmc4". Note that this must remain valid * in the caller until - * @bind_cros: true to bind the ChromiumOS bootmeth + * @bind_cros: true to bind the ChromiumOS and Android bootmeths * @old_orderp: Returns the original bootdev order, which must be restored * Returns 0 on success, -ve on failure */ static int prep_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev, - bool bind_cros, const char ***old_orderp) + bool bind_cros_android, const char ***old_orderp) { static const char *order[] = {"mmc2", "mmc1", NULL, NULL}; struct udevice *dev, *bootstd; @@ -545,12 +546,19 @@ static int prep_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev, "bootmeth_script", 0, ofnode_null(), &dev)); /* Enable the cros bootmeth if needed */ - if (IS_ENABLED(CONFIG_BOOTMETH_CROS) && bind_cros) { + if (IS_ENABLED(CONFIG_BOOTMETH_CROS) && bind_cros_android) { ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_cros), "cros", 0, ofnode_null(), &dev)); } + /* Enable the android bootmeths if needed */ + if (IS_ENABLED(CONFIG_BOOTMETH_ANDROID) && bind_cros_android) { + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_android), + "android", 0, ofnode_null(), &dev)); + } + /* Change the order to include the device */ std = dev_get_priv(bootstd); old_order = std->bootdev_order; @@ -589,6 +597,37 @@ static int scan_mmc_bootdev(struct unit_test_state *uts, const char *mmc_dev, return 0; } +/** + * scan_mmc_android_bootdev() - Set up an mmc bootdev so we can access other + * distros. Android bootflow might print "ANDROID:*" while scanning + * + * @uts: Unit test state + * @mmc_dev: MMC device to use, e.g. "mmc4" + * Returns 0 on success, -ve on failure + */ +static int scan_mmc_android_bootdev(struct unit_test_state *uts, const char *mmc_dev) +{ + struct bootstd_priv *std; + struct udevice *bootstd; + const char **old_order; + + ut_assertok(prep_mmc_bootdev(uts, mmc_dev, true, &old_order)); + + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan", 0)); + /* Android bootflow might print one or two 'ANDROID:*' logs */ + ut_check_skipline(uts); + ut_check_skipline(uts); + ut_assert_console_end(); + + /* Restore the order used by the device tree */ + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + std = dev_get_priv(bootstd); + std->bootdev_order = old_order; + + return 0; +} + /** * scan_mmc4_bootdev() - Set up the mmc4 bootdev so we can access a fake Armbian * @@ -1160,3 +1199,26 @@ static int bootflow_cros(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootflow_cros, 0); + +/* Test Android bootmeth */ +static int bootflow_android(struct unit_test_state *uts) +{ + if (!IS_ENABLED(CONFIG_BOOTMETH_ANDROID)) + return -EAGAIN; + + ut_assertok(scan_mmc_android_bootdev(uts, "mmc7")); + ut_assertok(run_command("bootflow list", 0)); + + ut_assert_nextlinen("Showing all"); + ut_assert_nextlinen("Seq"); + ut_assert_nextlinen("---"); + ut_assert_nextlinen(" 0 extlinux"); + ut_assert_nextlinen(" 1 android ready mmc 0 mmc7.bootdev.whole "); + ut_assert_nextlinen("---"); + ut_assert_skip_to_line("(2 bootflows, 2 valid)"); + + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_android, 0); diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 58205066ec8..05e15830590 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -11,6 +11,7 @@ import pytest import u_boot_utils # pylint: disable=E0611 from tests import fs_helper +from test_android import test_abootimg def mkdir_cond(dirname): """Create a directory if it doesn't already exist @@ -423,6 +424,83 @@ def setup_cros_image(cons): return fname +def setup_android_image(cons): + """Create a 20MB disk image with Android partitions""" + Partition = collections.namedtuple('part', 'start,size,name') + parts = {} + disk_data = None + + def set_part_data(partnum, data): + """Set the contents of a disk partition + + This updates disk_data by putting data in the right place + + Args: + partnum (int): Partition number to set + data (bytes): Data for that partition + """ + nonlocal disk_data + + start = parts[partnum].start * sect_size + disk_data = disk_data[:start] + data + disk_data[start + len(data):] + + mmc_dev = 7 + fname = os.path.join(cons.config.source_dir, f'mmc{mmc_dev}.img') + u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname) + u_boot_utils.run_and_log(cons, f'cgpt create {fname}') + + ptr = 40 + + # Number of sectors in 1MB + sect_size = 512 + sect_1mb = (1 << 20) // sect_size + + required_parts = [ + {'num': 1, 'label':'misc', 'size': '1M'}, + {'num': 2, 'label':'boot_a', 'size': '4M'}, + {'num': 3, 'label':'boot_b', 'size': '4M'}, + {'num': 4, 'label':'vendor_boot_a', 'size': '4M'}, + {'num': 5, 'label':'vendor_boot_b', 'size': '4M'}, + ] + + for part in required_parts: + size_str = part['size'] + if 'M' in size_str: + size = int(size_str[:-1]) * sect_1mb + else: + size = int(size_str) + u_boot_utils.run_and_log( + cons, + f"cgpt add -i {part['num']} -b {ptr} -s {size} -l {part['label']} -t basicdata {fname}") + ptr += size + + u_boot_utils.run_and_log(cons, f'cgpt boot -p {fname}') + out = u_boot_utils.run_and_log(cons, f'cgpt show -q {fname}') + + # Create a dict (indexed by partition number) containing the above info + for line in out.splitlines(): + start, size, num, name = line.split(maxsplit=3) + parts[int(num)] = Partition(int(start), int(size), name) + + with open(fname, 'rb') as inf: + disk_data = inf.read() + + test_abootimg.AbootimgTestDiskImage(cons, 'bootv4.img', test_abootimg.boot_img_hex) + boot_img = os.path.join(cons.config.result_dir, 'bootv4.img') + with open(boot_img, 'rb') as inf: + set_part_data(2, inf.read()) + + test_abootimg.AbootimgTestDiskImage(cons, 'vendor_boot.img', test_abootimg.vboot_img_hex) + vendor_boot_img = os.path.join(cons.config.result_dir, 'vendor_boot.img') + with open(vendor_boot_img, 'rb') as inf: + set_part_data(4, inf.read()) + + with open(fname, 'wb') as outf: + outf.write(disk_data) + + print('wrote to {}'.format(fname)) + + return fname def setup_cedit_file(cons): infname = os.path.join(cons.config.source_dir, @@ -478,6 +556,7 @@ def test_ut_dm_init_bootstd(u_boot_console): setup_bootmenu_image(u_boot_console) setup_cedit_file(u_boot_console) setup_cros_image(u_boot_console) + setup_android_image(u_boot_console) # Restart so that the new mmc1.img is picked up u_boot_console.restart_uboot()