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:
Tom Rini 2024-07-18 13:52:11 -06:00
commit 4595600007
16 changed files with 818 additions and 5 deletions

View File

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

View File

@ -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";
};

View File

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

View File

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

View File

@ -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
View 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
View 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,
};

View File

@ -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)
{

View File

@ -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))

View File

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

View File

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

View File

@ -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:

View File

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

View File

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

View File

@ -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);

View File

@ -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()