| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Bootmeth for Android | |
| * | |
| * Copyright (C) 2024 BayLibre, SAS | |
| * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com> | |
| */ | |
| #define LOG_CATEGORY UCLASS_BOOTSTD | |
| #include <android_ab.h> | |
| #include <android_image.h> | |
| #if CONFIG_IS_ENABLED(AVB_VERIFY) | |
| #include <avb_verify.h> | |
| #endif | |
| #include <bcb.h> | |
| #include <blk.h> | |
| #include <bootflow.h> | |
| #include <bootm.h> | |
| #include <bootmeth.h> | |
| #include <dm.h> | |
| #include <env.h> | |
| #include <image.h> | |
| #include <malloc.h> | |
| #include <mapmem.h> | |
| #include <part.h> | |
| #include <version.h> | |
| #include "bootmeth_android.h" | |
| #define BCB_FIELD_COMMAND_SZ 32 | |
| #define BCB_PART_NAME "misc" | |
| #define BOOT_PART_NAME "boot" | |
| #define VENDOR_BOOT_PART_NAME "vendor_boot" | |
| #define SLOT_LEN 2 | |
| /** | |
| * struct android_priv - Private data | |
| * | |
| * This is read from the disk and recorded for use when the full Android | |
| * kernel must be loaded and booted | |
| * | |
| * @boot_mode: Requested boot mode (normal, recovery, bootloader) | |
| * @slot: Nul-terminated partition slot suffix read from BCB ("a\0" or "b\0") | |
| * @header_version: Android boot image header version | |
| */ | |
| struct android_priv { | |
| enum android_boot_mode boot_mode; | |
| char *slot; | |
| u32 header_version; | |
| u32 boot_img_size; | |
| u32 vendor_boot_img_size; | |
| }; | |
| static int android_check(struct udevice *dev, struct bootflow_iter *iter) | |
| { | |
| /* This only works on mmc devices */ | |
| if (bootflow_iter_check_mmc(iter)) | |
| return log_msg_ret("mmc", -ENOTSUPP); | |
| /* | |
| * This only works on whole devices, as multiple | |
| * partitions are needed to boot Android | |
| */ | |
| if (iter->part != 0) | |
| return log_msg_ret("mmc part", -ENOTSUPP); | |
| return 0; | |
| } | |
| static int scan_boot_part(struct udevice *blk, struct android_priv *priv) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(blk); | |
| struct disk_partition partition; | |
| char partname[PART_NAME_LEN]; | |
| ulong num_blks, bufsz; | |
| char *buf; | |
| int ret; | |
| if (priv->slot) | |
| sprintf(partname, BOOT_PART_NAME "_%s", priv->slot); | |
| else | |
| sprintf(partname, BOOT_PART_NAME); | |
| ret = part_get_info_by_name(desc, partname, &partition); | |
| if (ret < 0) | |
| return log_msg_ret("part info", ret); | |
| num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz); | |
| bufsz = num_blks * desc->blksz; | |
| buf = malloc(bufsz); | |
| if (!buf) | |
| return log_msg_ret("buf", -ENOMEM); | |
| ret = blk_read(blk, partition.start, num_blks, buf); | |
| if (ret != num_blks) { | |
| free(buf); | |
| return log_msg_ret("part read", -EIO); | |
| } | |
| if (!is_android_boot_image_header(buf)) { | |
| free(buf); | |
| return log_msg_ret("header", -ENOENT); | |
| } | |
| if (!android_image_get_bootimg_size(buf, &priv->boot_img_size)) { | |
| free(buf); | |
| return log_msg_ret("get bootimg size", -EINVAL); | |
| } | |
| priv->header_version = ((struct andr_boot_img_hdr_v0 *)buf)->header_version; | |
| free(buf); | |
| return 0; | |
| } | |
| static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(blk); | |
| struct disk_partition partition; | |
| char partname[PART_NAME_LEN]; | |
| ulong num_blks, bufsz; | |
| char *buf; | |
| int ret; | |
| if (priv->slot) | |
| sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", priv->slot); | |
| else | |
| sprintf(partname, VENDOR_BOOT_PART_NAME); | |
| ret = part_get_info_by_name(desc, partname, &partition); | |
| if (ret < 0) | |
| return log_msg_ret("part info", ret); | |
| num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz); | |
| bufsz = num_blks * desc->blksz; | |
| buf = malloc(bufsz); | |
| if (!buf) | |
| return log_msg_ret("buf", -ENOMEM); | |
| ret = blk_read(blk, partition.start, num_blks, buf); | |
| if (ret != num_blks) { | |
| free(buf); | |
| return log_msg_ret("part read", -EIO); | |
| } | |
| if (!is_android_vendor_boot_image_header(buf)) { | |
| free(buf); | |
| return log_msg_ret("header", -ENOENT); | |
| } | |
| if (!android_image_get_vendor_bootimg_size(buf, &priv->vendor_boot_img_size)) { | |
| free(buf); | |
| return log_msg_ret("get vendor bootimg size", -EINVAL); | |
| } | |
| free(buf); | |
| return 0; | |
| } | |
| static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
| struct android_priv *priv = bflow->bootmeth_priv; | |
| struct disk_partition misc; | |
| char slot_suffix[3]; | |
| int ret; | |
| if (!CONFIG_IS_ENABLED(ANDROID_AB)) { | |
| priv->slot = NULL; | |
| return 0; | |
| } | |
| ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc); | |
| if (ret < 0) | |
| return log_msg_ret("part", ret); | |
| ret = ab_select_slot(desc, &misc, decrement); | |
| if (ret < 0) | |
| return log_msg_ret("slot", ret); | |
| priv->slot = malloc(SLOT_LEN); | |
| priv->slot[0] = BOOT_SLOT_NAME(ret); | |
| priv->slot[1] = '\0'; | |
| sprintf(slot_suffix, "_%s", priv->slot); | |
| ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix", | |
| slot_suffix, false); | |
| if (ret < 0) | |
| return log_msg_ret("cmdl", ret); | |
| return 0; | |
| } | |
| static int configure_serialno(struct bootflow *bflow) | |
| { | |
| char *serialno = env_get("serial#"); | |
| if (!serialno) | |
| return log_msg_ret("serial", -ENOENT); | |
| return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false); | |
| } | |
| static int configure_bootloader_version(struct bootflow *bflow) | |
| { | |
| return bootflow_cmdline_set_arg(bflow, "androidboot.bootloader", | |
| PLAIN_VERSION, false); | |
| } | |
| static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
| struct disk_partition misc; | |
| struct android_priv *priv; | |
| char command[BCB_FIELD_COMMAND_SZ]; | |
| int ret; | |
| bflow->state = BOOTFLOWST_MEDIA; | |
| /* | |
| * bcb_find_partition_and_load() will print errors to stdout | |
| * if BCB_PART_NAME is not found. To avoid that, check if the | |
| * partition exists first. | |
| */ | |
| ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc); | |
| if (ret < 0) | |
| return log_msg_ret("part", ret); | |
| ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME); | |
| if (ret < 0) | |
| return log_msg_ret("bcb load", ret); | |
| ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command)); | |
| if (ret < 0) | |
| return log_msg_ret("bcb read", ret); | |
| priv = malloc(sizeof(struct android_priv)); | |
| if (!priv) | |
| return log_msg_ret("buf", -ENOMEM); | |
| if (!strcmp("bootonce-bootloader", command)) { | |
| priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER; | |
| bflow->os_name = strdup("Android (bootloader)"); | |
| } else if (!strcmp("boot-fastboot", command)) { | |
| priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY; | |
| bflow->os_name = strdup("Android (fastbootd)"); | |
| } else if (!strcmp("boot-recovery", command)) { | |
| priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY; | |
| bflow->os_name = strdup("Android (recovery)"); | |
| } else { | |
| priv->boot_mode = ANDROID_BOOT_MODE_NORMAL; | |
| bflow->os_name = strdup("Android"); | |
| } | |
| if (!bflow->os_name) { | |
| free(priv); | |
| return log_msg_ret("os", -ENOMEM); | |
| } | |
| if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) { | |
| /* Clear BCB */ | |
| memset(command, 0, sizeof(command)); | |
| ret = bcb_set(BCB_FIELD_COMMAND, command); | |
| if (ret < 0) { | |
| free(priv); | |
| return log_msg_ret("bcb set", ret); | |
| } | |
| ret = bcb_store(); | |
| if (ret < 0) { | |
| free(priv); | |
| return log_msg_ret("bcb store", ret); | |
| } | |
| bflow->bootmeth_priv = priv; | |
| bflow->state = BOOTFLOWST_READY; | |
| return 0; | |
| } | |
| bflow->bootmeth_priv = priv; | |
| /* For recovery and normal boot, we need to scan the partitions */ | |
| ret = android_read_slot_from_bcb(bflow, false); | |
| if (ret < 0) { | |
| log_err("read slot: %d", ret); | |
| goto free_priv; | |
| } | |
| ret = scan_boot_part(bflow->blk, priv); | |
| if (ret < 0) { | |
| log_debug("scan boot failed: err=%d\n", ret); | |
| goto free_priv; | |
| } | |
| if (priv->header_version >= 3) { | |
| ret = scan_vendor_boot_part(bflow->blk, priv); | |
| if (ret < 0) { | |
| log_debug("scan vendor_boot failed: err=%d\n", ret); | |
| goto free_priv; | |
| } | |
| } | |
| /* | |
| * Ignoring return code for the following configurations: | |
| * these are not mandatory for booting. | |
| */ | |
| configure_serialno(bflow); | |
| configure_bootloader_version(bflow); | |
| if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL && priv->slot) { | |
| ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", | |
| "1", false); | |
| if (ret < 0) { | |
| log_debug("normal_boot %d", ret); | |
| goto free_priv; | |
| } | |
| } | |
| bflow->state = BOOTFLOWST_READY; | |
| return 0; | |
| free_priv: | |
| free(priv); | |
| bflow->bootmeth_priv = NULL; | |
| return ret; | |
| } | |
| static int android_read_file(struct udevice *dev, struct bootflow *bflow, | |
| const char *file_path, ulong addr, | |
| enum bootflow_img_t type, ulong *sizep) | |
| { | |
| /* | |
| * Reading individual files is not supported since we only | |
| * operate on whole mmc devices (because we require multiple partitions) | |
| */ | |
| return log_msg_ret("Unsupported", -ENOSYS); | |
| } | |
| /** | |
| * read_slotted_partition() - Read a partition by appending a slot suffix | |
| * | |
| * Most modern Android devices use Seamless Updates, where each partition | |
| * is duplicated. For example, the boot partition has boot_a and boot_b. | |
| * For more information, see: | |
| * https://source.android.com/docs/core/ota/ab | |
| * https://source.android.com/docs/core/ota/ab/ab_implement | |
| * | |
| * @blk: Block device to read | |
| * @name: Partition name to read | |
| * @slot: Nul-terminated slot suffixed to partition name ("a\0" or "b\0") | |
| * @image_size: Image size in bytes used when reading the partition | |
| * @addr: Address where the partition content is loaded into | |
| * Return: 0 if OK, negative errno on failure. | |
| */ | |
| static int read_slotted_partition(struct blk_desc *desc, const char *const name, | |
| const char slot[2], ulong image_size, ulong addr) | |
| { | |
| struct disk_partition partition; | |
| char partname[PART_NAME_LEN]; | |
| size_t partname_len; | |
| ulong num_blks = DIV_ROUND_UP(image_size, desc->blksz); | |
| int ret; | |
| u32 n; | |
| /* | |
| * Ensure name fits in partname. | |
| * For A/B, it should be <name>_<slot>\0 | |
| * For non A/B, it should be <name>\0 | |
| */ | |
| if (CONFIG_IS_ENABLED(ANDROID_AB)) | |
| partname_len = PART_NAME_LEN - 2 - 1; | |
| else | |
| partname_len = PART_NAME_LEN - 1; | |
| if (strlen(name) > partname_len) | |
| return log_msg_ret("name too long", -EINVAL); | |
| if (slot) | |
| sprintf(partname, "%s_%s", name, slot); | |
| else | |
| sprintf(partname, "%s", name); | |
| ret = part_get_info_by_name(desc, partname, &partition); | |
| if (ret < 0) | |
| return log_msg_ret("part", ret); | |
| n = blk_dread(desc, partition.start, num_blks, map_sysmem(addr, 0)); | |
| if (n < num_blks) | |
| return log_msg_ret("part read", -EIO); | |
| return 0; | |
| } | |
| #if CONFIG_IS_ENABLED(AVB_VERIFY) | |
| static int avb_append_commandline_arg(struct bootflow *bflow, char *arg) | |
| { | |
| char *key = strsep(&arg, "="); | |
| char *value = arg; | |
| int ret; | |
| ret = bootflow_cmdline_set_arg(bflow, key, value, false); | |
| if (ret < 0) | |
| return log_msg_ret("avb cmdline", ret); | |
| return 0; | |
| } | |
| static int avb_append_commandline(struct bootflow *bflow, char *cmdline) | |
| { | |
| char *arg = strsep(&cmdline, " "); | |
| int ret; | |
| while (arg) { | |
| ret = avb_append_commandline_arg(bflow, arg); | |
| if (ret < 0) | |
| return ret; | |
| arg = strsep(&cmdline, " "); | |
| } | |
| return 0; | |
| } | |
| static int run_avb_verification(struct bootflow *bflow) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
| struct android_priv *priv = bflow->bootmeth_priv; | |
| const char * const requested_partitions[] = {"boot", "vendor_boot", NULL}; | |
| struct AvbOps *avb_ops; | |
| AvbSlotVerifyResult result; | |
| AvbSlotVerifyData *out_data; | |
| enum avb_boot_state boot_state; | |
| char *extra_args; | |
| char slot_suffix[3] = ""; | |
| bool unlocked = false; | |
| int ret; | |
| avb_ops = avb_ops_alloc(desc->devnum); | |
| if (!avb_ops) | |
| return log_msg_ret("avb ops", -ENOMEM); | |
| if (priv->slot) | |
| sprintf(slot_suffix, "_%s", priv->slot); | |
| ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked); | |
| if (ret != AVB_IO_RESULT_OK) | |
| return log_msg_ret("avb lock", -EIO); | |
| result = avb_slot_verify(avb_ops, | |
| requested_partitions, | |
| slot_suffix, | |
| unlocked, | |
| AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, | |
| &out_data); | |
| if (!unlocked) { | |
| /* When device is locked, we only accept AVB_SLOT_VERIFY_RESULT_OK */ | |
| if (result != AVB_SLOT_VERIFY_RESULT_OK) { | |
| printf("Verification failed, reason: %s\n", | |
| str_avb_slot_error(result)); | |
| if (out_data) | |
| avb_slot_verify_data_free(out_data); | |
| return log_msg_ret("avb verify", -EIO); | |
| } | |
| boot_state = AVB_GREEN; | |
| } else { | |
| /* When device is unlocked, we also accept verification errors */ | |
| if (result != AVB_SLOT_VERIFY_RESULT_OK && | |
| result != AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION) { | |
| printf("Unlocked verification failed, reason: %s\n", | |
| str_avb_slot_error(result)); | |
| if (out_data) | |
| avb_slot_verify_data_free(out_data); | |
| return log_msg_ret("avb verify unlocked", -EIO); | |
| } | |
| boot_state = AVB_ORANGE; | |
| } | |
| 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; | |
| } | |
| if (result == AVB_SLOT_VERIFY_RESULT_OK) { | |
| 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 append_bootargs_to_cmdline(struct bootflow *bflow) | |
| { | |
| char *bootargs; | |
| int len = 0; | |
| /* | |
| * Check any additionnal bootargs coming from U-Boot env. If any, | |
| * merge them with the current cmdline | |
| */ | |
| bootargs = env_get("bootargs"); | |
| if (bootargs) { | |
| len += strlen(bootargs) + 1; /* Extra space character needed */ | |
| len += strlen(bflow->cmdline); | |
| char *newcmdline = malloc(len + 1); /* +1 for the '\0' */ | |
| if (!newcmdline) | |
| return log_msg_ret("newcmdline malloc", -ENOMEM); | |
| strcpy(newcmdline, bootargs); | |
| strcat(newcmdline, " "); | |
| strcat(newcmdline, bflow->cmdline); | |
| /* Free the previous cmdline and replace it */ | |
| free(bflow->cmdline); | |
| bflow->cmdline = newcmdline; | |
| } | |
| return 0; | |
| } | |
| static int boot_android_normal(struct bootflow *bflow) | |
| { | |
| struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
| struct android_priv *priv = bflow->bootmeth_priv; | |
| int ret; | |
| ulong loadaddr = env_get_hex("loadaddr", 0); | |
| ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0); | |
| ret = run_avb_verification(bflow); | |
| if (ret < 0) | |
| return log_msg_ret("avb", ret); | |
| /* Read slot once more to decrement counter from BCB */ | |
| ret = android_read_slot_from_bcb(bflow, true); | |
| if (ret < 0) | |
| return log_msg_ret("read slot", ret); | |
| ret = read_slotted_partition(desc, "boot", priv->slot, priv->boot_img_size, | |
| loadaddr); | |
| if (ret < 0) | |
| return log_msg_ret("read boot", ret); | |
| if (priv->header_version >= 3) { | |
| ret = read_slotted_partition(desc, "vendor_boot", priv->slot, | |
| priv->vendor_boot_img_size, vloadaddr); | |
| if (ret < 0) | |
| return log_msg_ret("read vendor_boot", ret); | |
| set_avendor_bootimg_addr(vloadaddr); | |
| } | |
| set_abootimg_addr(loadaddr); | |
| if (priv->slot) | |
| free(priv->slot); | |
| ret = append_bootargs_to_cmdline(bflow); | |
| if (ret < 0) | |
| return log_msg_ret("bootargs append", ret); | |
| 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, | |
| }; |