mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-23 20:24:26 +08:00
Merge patch series "bootstd: Add Android support"
Mattijs Korpershoek <mkorpershoek@baylibre.com> says:
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:
a. Get slot (A/B) from BCB
b. Run AVB (Android Verified Boot) on boot partitions
c. Load boot and vendor_boot partitions
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.
This has been tested on sandbox using:
$ ./test/py/test.py --bd sandbox --build -k test_ut
This has also been tested on the AM62X SK EVM using TI's Android SDK[6]
To test on TI board, the following (WIP) patch is needed as well:
84cceb912b
[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/
[6] https://software-dl.ti.com/processor-sdk-android/esd/AM62X/09_02_00/docs/android/Overview.html
This commit is contained in:
commit
4595600007
@ -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>
|
||||
|
@ -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";
|
||||
};
|
||||
|
17
boot/Kconfig
17
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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
553
boot/bootmeth_android.c
Normal file
553
boot/bootmeth_android.c
Normal file
@ -0,0 +1,553 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Bootmeth for Android
|
||||
*
|
||||
* Copyright (C) 2024 BayLibre, SAS
|
||||
* Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
|
||||
*/
|
||||
#define LOG_CATEGORY UCLASS_BOOTSTD
|
||||
|
||||
#include <android_ab.h>
|
||||
#include <android_image.h>
|
||||
#if CONFIG_IS_ENABLED(AVB_VERIFY)
|
||||
#include <avb_verify.h>
|
||||
#endif
|
||||
#include <bcb.h>
|
||||
#include <blk.h>
|
||||
#include <bootflow.h>
|
||||
#include <bootm.h>
|
||||
#include <bootmeth.h>
|
||||
#include <dm.h>
|
||||
#include <image.h>
|
||||
#include <malloc.h>
|
||||
#include <mapmem.h>
|
||||
#include <part.h>
|
||||
#include "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: <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_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,
|
||||
};
|
29
boot/bootmeth_android.h
Normal file
29
boot/bootmeth_android.h
Normal file
@ -0,0 +1,29 @@
|
||||
/* 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,
|
||||
};
|
@ -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)
|
||||
{
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user