* [PATCH 0/6] bootstd: Add Android support
@ 2024-06-06 12:23 Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() Mattijs Korpershoek
` (5 more replies)
0 siblings, 6 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
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:
https://gitlab.baylibre.com/baylibre/ti/ti-u-boot/-/commit/84cceb912bccd7cdd7f9dd69bca0e5d987a1fd04
[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
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
Mattijs Korpershoek (6):
boot: android: Provide vendor_bootimg_addr in boot_get_fdt()
boot: android: Add image_android_get_version()
bootstd: Add bootflow_iter_check_mmc() helper
android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr()
bootstd: Add a bootmeth for Android
bootstd: Add test for bootmeth_android
MAINTAINERS | 7 +
arch/sandbox/dts/test.dts | 8 +
boot/Kconfig | 14 ++
boot/Makefile | 2 +
boot/bootflow.c | 12 ++
boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++
boot/bootmeth_android.h | 27 +++
boot/image-android.c | 7 +-
boot/image-fdt.c | 2 +-
cmd/abootimg.c | 10 +
configs/sandbox_defconfig | 2 +-
doc/develop/bootstd.rst | 6 +
include/bootflow.h | 9 +
include/image.h | 21 ++
test/boot/bootflow.c | 65 +++++-
test/py/tests/test_ut.py | 76 +++++++
16 files changed, 784 insertions(+), 6 deletions(-)
---
base-commit: 227be29df37545f74243a98c12a4a33c4160e3cd
change-id: 20240605-bootmeth-android-bfc8596e9367
Best regards,
--
Mattijs Korpershoek <mkorpershoek@baylibre.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt()
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-10 8:51 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 2/6] boot: android: Add image_android_get_version() Mattijs Korpershoek
` (4 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
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.
Note: on boot image v2, this is harmless since get_avendor_bootimg_addr()
returns -1.
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
boot/image-fdt.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
index 56dd7687f51c..8332792b8e80 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))
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 2/6] boot: android: Add image_android_get_version()
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-10 9:20 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper Mattijs Korpershoek
` (3 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
When reading a boot image header, we may need to retrieve the header
version.
Add a helper function for it.
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
boot/image-android.c | 7 ++++++-
include/image.h | 7 +++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/boot/image-android.c b/boot/image-android.c
index ddd8ffd5e540..4f8fb51585eb 100644
--- a/boot/image-android.c
+++ b/boot/image-android.c
@@ -185,7 +185,7 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
return false;
}
- if (((struct andr_boot_img_hdr_v0 *)boot_hdr)->header_version > 2) {
+ if (android_image_get_version(boot_hdr) > 2) {
if (!vendor_boot_hdr) {
printf("For boot header v3+ vendor boot image has to be provided\n");
return false;
@@ -203,6 +203,11 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
return true;
}
+u32 android_image_get_version(const void *hdr)
+{
+ return ((struct andr_boot_img_hdr_v0 *)hdr)->header_version;
+}
+
static ulong android_image_get_kernel_addr(struct andr_image_data *img_data)
{
/*
diff --git a/include/image.h b/include/image.h
index acffd17e0dfd..18e5ced5ab42 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1963,6 +1963,13 @@ bool is_android_boot_image_header(const void *hdr);
*/
bool is_android_vendor_boot_image_header(const void *vendor_boot_img);
+/**
+ * android_image_get_version() - Retrieve the boot.img version
+ *
+ * Return: Android boot image header version.
+ */
+u32 android_image_get_version(const void *hdr);
+
/**
* get_abootimg_addr() - Get Android boot image address
*
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 2/6] boot: android: Add image_android_get_version() Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-10 9:31 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr() Mattijs Korpershoek
` (2 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
Some bootflows might be able to only boot from MMC devices.
Add a helper function these bootflows can use.
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
boot/bootflow.c | 12 ++++++++++++
include/bootflow.h | 9 +++++++++
2 files changed, 21 insertions(+)
diff --git a/boot/bootflow.c b/boot/bootflow.c
index 9aa3179c3881..59d77d2385f4 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 080ee8501225..6058ddd89b16 100644
--- a/include/bootflow.h
+++ b/include/bootflow.h
@@ -407,6 +407,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
*
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr()
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
` (2 preceding siblings ...)
2024-06-06 12:23 ` [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-10 14:27 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 5/6] bootstd: Add a bootmeth for Android Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 6/6] bootstd: Add test for bootmeth_android Mattijs Korpershoek
5 siblings, 1 reply; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
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.
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
cmd/abootimg.c | 10 ++++++++++
include/image.h | 14 ++++++++++++++
2 files changed, 24 insertions(+)
diff --git a/cmd/abootimg.c b/cmd/abootimg.c
index 88c77d999290..33381e22dec2 100644
--- a/cmd/abootimg.c
+++ b/cmd/abootimg.c
@@ -21,11 +21,21 @@ 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_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 18e5ced5ab42..6deaf406605e 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1977,6 +1977,13 @@ u32 android_image_get_version(const void *hdr);
*/
ulong get_abootimg_addr(void);
+/**
+ * set_abootimg_addr() - Set Android boot image address
+ *
+ * Return: no returned results
+ */
+void set_abootimg_addr(ulong addr);
+
/**
* get_avendor_bootimg_addr() - Get Android vendor boot image address
*
@@ -1984,6 +1991,13 @@ ulong get_abootimg_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
*
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 5/6] bootstd: Add a bootmeth for Android
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
` (3 preceding siblings ...)
2024-06-06 12:23 ` [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr() Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-10 15:15 ` Igor Opaniuk
2024-06-11 18:52 ` Simon Glass
2024-06-06 12:23 ` [PATCH 6/6] bootstd: Add test for bootmeth_android Mattijs Korpershoek
5 siblings, 2 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
Android boot flow is a bit different than a regular Linux distro.
Android relies on multiple partitions in order to boot.
A typical boot flow would be:
1. Parse the Bootloader Control Block (BCB, misc partition)
2. If BCB requested bootonce-bootloader, start fastboot and wait.
3. If BCB requested recovery or normal android, run the following:
3.a. Get slot (A/B) from BCB
3.b. Run AVB (Android Verified Boot) on boot partitions
3.c. Load boot and vendor_boot partitions
3.d. Load device-tree, ramdisk and boot
The AOSP documentation has more details at [1], [2], [3]
This has been implemented via complex boot scripts such as [4].
However, these boot script are neither very maintainable nor generic.
Moreover, DISTRO_DEFAULTS is being deprecated [5].
Add a generic Android bootflow implementation for bootstd.
For this initial version, only boot image v4 is supported.
[1] https://source.android.com/docs/core/architecture/bootloader
[2] https://source.android.com/docs/core/architecture/partitions
[3] https://source.android.com/docs/core/architecture/partitions/generic-boot
[4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
[5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
MAINTAINERS | 7 +
boot/Kconfig | 14 ++
boot/Makefile | 2 +
boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
boot/bootmeth_android.h | 27 +++
doc/develop/bootstd.rst | 6 +
6 files changed, 578 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 66783d636e3d..6d2b87720565 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -939,6 +939,13 @@ F: include/bootstd.h
F: net/eth_bootdevice.c
F: test/boot/
+BOOTMETH_ANDROID
+M: Mattijs Korpershoek <mkorpershoek@baylibre.com>
+S: Maintained
+T: git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
+F: boot/bootmeth_android.c
+F: boot/bootmeth_android.h
+
BTRFS
M: Marek Behún <kabel@kernel.org>
R: Qu Wenruo <wqu@suse.com>
diff --git a/boot/Kconfig b/boot/Kconfig
index 6f3096c15a6f..5fa6f3b8315d 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
EFI bootmgr, since they take full control over which bootdevs are
selected to boot.
+config BOOTMETH_ANDROID
+ bool "Bootdev support for Android"
+ depends on X86 || ARM || SANDBOX
+ select ANDROID_AB
+ select ANDROID_BOOT_IMAGE
+ select CMD_BCB
+ select PARTITION_TYPE_GUID
+ select PARTITION_UUIDS
+ help
+ Enables support for booting Android using bootdevs. Android requires
+ multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
+
+ Note that only MMC bootdevs are supported at present.
+
config BOOTMETH_CROS
bool "Bootdev support for Chromium OS"
depends on X86 || ARM || SANDBOX
diff --git a/boot/Makefile b/boot/Makefile
index 84ccfeaecec4..75d1cd46fabf 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
+
+obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
new file mode 100644
index 000000000000..26d548d2fd6e
--- /dev/null
+++ b/boot/bootmeth_android.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for Android
+ *
+ * Copyright (C) 2024 BayLibre, SAS
+ * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
+ */
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <android_ab.h>
+#include <android_image.h>
+#if CONFIG_IS_ENABLED(AVB_VERIFY)
+#include <avb_verify.h>
+#endif
+#include <bcb.h>
+#include <blk.h>
+#include <bootflow.h>
+#include <bootm.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <image.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <part.h>
+#include "bootmeth_android.h"
+
+#define BCB_FIELD_COMMAND_SZ 32
+#define BCB_PART_NAME "misc"
+#define BOOT_PART_NAME "boot"
+#define VENDOR_BOOT_PART_NAME "vendor_boot"
+
+/**
+ * struct android_priv - Private data
+ *
+ * This is read from the disk and recorded for use when the full Android
+ * kernel must be loaded and booted
+ */
+struct android_priv {
+ int boot_mode;
+ char slot[2];
+ u32 header_version;
+};
+
+static int android_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /* This only works on mmc devices */
+ if (bootflow_iter_check_mmc(iter))
+ return log_msg_ret("mmc", -ENOTSUPP);
+
+ /* This only works on whole devices, as multiple
+ * partitions are needed to boot Android
+ */
+ if (iter->part != 0)
+ return log_msg_ret("mmc part", -ENOTSUPP);
+
+ return 0;
+}
+
+static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ ulong num_blks, bufsz;
+ char *buf;
+ int ret;
+
+ sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part info", ret);
+
+ num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
+ bufsz = num_blks * desc->blksz;
+ buf = malloc(bufsz);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ ret = blk_read(blk, partition.start, num_blks, buf);
+ if (ret != num_blks) {
+ free(buf);
+ return log_msg_ret("part read", -EIO);
+ }
+
+ if (!is_android_boot_image_header(buf)) {
+ free(buf);
+ return log_msg_ret("header", -ENOENT);
+ }
+
+ priv->header_version = android_image_get_version(buf);
+
+ return 0;
+}
+
+static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
+{
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ ulong num_blks, bufsz;
+ char *buf;
+ int ret;
+
+ sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part info", ret);
+
+ num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
+ bufsz = num_blks * desc->blksz;
+ buf = malloc(bufsz);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ ret = blk_read(blk, partition.start, num_blks, buf);
+ if (ret != num_blks) {
+ free(buf);
+ return log_msg_ret("part read", -EIO);
+ }
+
+ if (!is_android_vendor_boot_image_header(buf)) {
+ free(buf);
+ return log_msg_ret("header", -ENOENT);
+ }
+
+ return 0;
+}
+
+static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ struct disk_partition misc;
+ char slot_suffix[3];
+ int ret;
+
+ ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ ret = ab_select_slot(desc, &misc, decrement);
+ if (ret < 0)
+ return log_msg_ret("slot", ret);
+
+ priv->slot[0] = BOOT_SLOT_NAME(ret);
+ priv->slot[1] = '\0';
+
+ sprintf(slot_suffix, "_%s", priv->slot);
+ ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
+ slot_suffix, false);
+ if (ret < 0)
+ return log_msg_ret("slot", ret);
+
+ return 0;
+}
+
+static int configure_serialno(struct bootflow *bflow)
+{
+ char *serialno = env_get("serial#");
+
+ if (!serialno)
+ return log_msg_ret("serial", -ENOENT);
+
+ return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
+}
+
+static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct disk_partition misc;
+ struct android_priv *priv;
+ char command[BCB_FIELD_COMMAND_SZ];
+ int ret;
+
+ bflow->state = BOOTFLOWST_MEDIA;
+
+ /* bcb_find_partition_and_load() will print errors to stdout
+ * if BCB_PART_NAME is not found. To avoid that, check if the
+ * partition exists first.
+ */
+ ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
+ if (ret < 0)
+ return log_msg_ret("bcb load", ret);
+
+ ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
+ if (ret < 0)
+ return log_msg_ret("bcb read", ret);
+
+ priv = malloc(sizeof(struct android_priv));
+ if (!priv)
+ return log_msg_ret("buf", -ENOMEM);
+
+ bflow->bootmeth_priv = priv;
+ if (!strcmp("bootonce-bootloader", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
+ bflow->os_name = strdup("Android (bootloader)");
+ } else if (!strcmp("boot-fastboot", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
+ bflow->os_name = strdup("Android (fastbootd)");
+ } else if (!strcmp("boot-recovery", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
+ bflow->os_name = strdup("Android (recovery)");
+ } else {
+ priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
+ bflow->os_name = strdup("Android");
+ }
+ if (!bflow->os_name)
+ return log_msg_ret("os", -ENOMEM);
+
+ if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
+ /* Clear BCB */
+ memset(command, 0, sizeof(command));
+ ret = bcb_set(BCB_FIELD_COMMAND, command);
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("bcb set", ret);
+ }
+ ret = bcb_store();
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("bcb store", ret);
+ }
+
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+
+ /* For recovery and normal boot, we need to scan the partitions */
+ ret = android_read_slot_from_bcb(bflow, false);
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("read slot", ret);
+ }
+
+ ret = scan_boot_part(bflow->blk, priv);
+ if (ret < 0) {
+ printf("- scan boot failed: err=%d\n", ret);
+ free(priv);
+ return log_msg_ret("scan boot", ret);
+ }
+
+ if (priv->header_version != 4) {
+ printf("- Only boot.img v4 is supported\n");
+ free(priv);
+ return log_msg_ret("version", -EINVAL);
+ }
+
+ ret = scan_vendor_boot_part(bflow->blk, priv->slot);
+ if (ret < 0) {
+ printf("- scan vendor_boot failed: err=%d\n", ret);
+ free(priv);
+ return log_msg_ret("scan vendor_boot", ret);
+ }
+
+ /* Ignoring return code: setting serial number is not mandatory for booting */
+ configure_serialno(bflow);
+
+ if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
+ ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("normal_boot", ret);
+ }
+ }
+
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int android_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr, ulong *sizep)
+{
+ /* Reading individual files is not supported since we only
+ * operate on whole mmc devices (because we require multiple partitions)
+ */
+ return log_msg_ret("Unsupported", -ENOSYS);
+}
+
+static int read_slotted_partition(struct blk_desc *desc, const char *const name,
+ const char slot[2], ulong addr)
+{
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ int ret;
+ u32 n;
+
+ /* Ensure name fits in partname it should be: <name>_<slot>\0 */
+ if (strlen(name) > (PART_NAME_LEN - 2 - 1))
+ return log_msg_ret("name too long", -EINVAL);
+
+ sprintf(partname, "%s_%s", name, slot);
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
+ if (n < partition.size)
+ return log_msg_ret("part read", -EIO);
+
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(AVB_VERIFY)
+static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
+{
+ char *key = strsep(&arg, "=");
+ char *value = arg;
+ int ret;
+
+ ret = bootflow_cmdline_set_arg(bflow, key, value, false);
+ if (ret < 0)
+ return log_msg_ret("avb cmdline", ret);
+
+ return 0;
+}
+
+static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
+{
+ char *arg = strsep(&cmdline, " ");
+ int ret;
+
+ while (arg) {
+ ret = avb_append_commandline_arg(bflow, arg);
+ if (ret < 0)
+ return ret;
+
+ arg = strsep(&cmdline, " ");
+ }
+
+ return 0;
+}
+
+static int run_avb_verification(struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ const char * const requested_partitions[] = {"boot", "vendor_boot"};
+ struct AvbOps *avb_ops;
+ AvbSlotVerifyResult result;
+ AvbSlotVerifyData *out_data;
+ enum avb_boot_state boot_state;
+ char *extra_args;
+ char slot_suffix[3];
+ bool unlocked = false;
+ int ret;
+
+ avb_ops = avb_ops_alloc(desc->devnum);
+ if (!avb_ops)
+ return log_msg_ret("avb ops", -ENOMEM);
+
+ sprintf(slot_suffix, "_%s", priv->slot);
+
+ ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
+ if (ret != AVB_IO_RESULT_OK)
+ return log_msg_ret("avb lock", -EIO);
+
+ result = avb_slot_verify(avb_ops,
+ requested_partitions,
+ slot_suffix,
+ unlocked,
+ AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+ &out_data);
+
+ if (result != AVB_SLOT_VERIFY_RESULT_OK) {
+ printf("Verification failed, reason: %s\n",
+ str_avb_slot_error(result));
+ return log_msg_ret("avb verify", -EIO);
+ }
+
+ if (unlocked)
+ boot_state = AVB_ORANGE;
+ else
+ boot_state = AVB_GREEN;
+
+ extra_args = avb_set_state(avb_ops, boot_state);
+ if (extra_args) {
+ ret = avb_append_commandline_arg(bflow, extra_args);
+ if (ret < 0)
+ goto free_out_data;
+ }
+
+ ret = avb_append_commandline(bflow, out_data->cmdline);
+ if (ret < 0)
+ goto free_out_data;
+
+ return 0;
+
+ free_out_data:
+ if (out_data)
+ avb_slot_verify_data_free(out_data);
+
+ return log_msg_ret("avb cmdline", ret);
+}
+#else
+static int run_avb_verification(struct bootflow *bflow)
+{
+ int ret;
+
+ /* When AVB is unsupported, pass ORANGE state */
+ ret = bootflow_cmdline_set_arg(bflow,
+ "androidboot.verifiedbootstate",
+ "orange", false);
+ if (ret < 0)
+ return log_msg_ret("avb cmdline", ret);
+
+ return 0;
+}
+#endif /* AVB_VERIFY */
+
+static int boot_android_normal(struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ int ret;
+
+ ulong loadaddr = env_get_hex("loadaddr", 0);
+ ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
+
+ ret = run_avb_verification(bflow);
+ if (ret < 0)
+ return log_msg_ret("avb", ret);
+
+ /* Read slot once more to decrement counter from BCB */
+ ret = android_read_slot_from_bcb(bflow, true);
+ if (ret < 0)
+ return log_msg_ret("read slot", ret);
+
+ ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
+ if (ret < 0)
+ return log_msg_ret("read boot", ret);
+
+ ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
+ if (ret < 0)
+ return log_msg_ret("read vendor_boot", ret);
+
+ set_abootimg_addr(loadaddr);
+ set_avendor_bootimg_addr(vloadaddr);
+
+ ret = bootm_boot_start(loadaddr, bflow->cmdline);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int boot_android_recovery(struct bootflow *bflow)
+{
+ int ret;
+
+ ret = boot_android_normal(bflow);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int boot_android_bootloader(struct bootflow *bflow)
+{
+ int ret;
+
+ ret = run_command("fastboot usb 0", 0);
+ do_reset(NULL, 0, 0, NULL);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int android_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct android_priv *priv = bflow->bootmeth_priv;
+ int ret;
+
+ switch (priv->boot_mode) {
+ case ANDROID_BOOT_MODE_NORMAL:
+ ret = boot_android_normal(bflow);
+ break;
+ case ANDROID_BOOT_MODE_RECOVERY:
+ ret = boot_android_recovery(bflow);
+ break;
+ case ANDROID_BOOT_MODE_BOOTLOADER:
+ ret = boot_android_bootloader(bflow);
+ break;
+ default:
+ printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
+ priv->boot_mode);
+ boot_android_bootloader(bflow);
+ /* Tell we failed to boot since boot mode is unknown */
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
+static int android_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "Android boot";
+ plat->flags = BOOTMETHF_ANY_PART;
+
+ return 0;
+}
+
+static struct bootmeth_ops android_bootmeth_ops = {
+ .check = android_check,
+ .read_bootflow = android_read_bootflow,
+ .read_file = android_read_file,
+ .boot = android_boot,
+};
+
+static const struct udevice_id android_bootmeth_ids[] = {
+ { .compatible = "u-boot,android" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_android) = {
+ .name = "bootmeth_android",
+ .id = UCLASS_BOOTMETH,
+ .of_match = android_bootmeth_ids,
+ .ops = &android_bootmeth_ops,
+ .bind = android_bootmeth_bind,
+};
diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
new file mode 100644
index 000000000000..411c2f2d15e7
--- /dev/null
+++ b/boot/bootmeth_android.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Bootmethod for Android
+ *
+ * Copyright (C) 2024 BayLibre, SAS
+ * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
+ */
+
+enum android_boot_mode {
+ ANDROID_BOOT_MODE_NORMAL = 0,
+
+ /* Android "recovery" is a special boot mode that uses another ramdisk.
+ * It can be used to "factory reset" a board or to flash logical partitions
+ * It operates in 2 modes: adb or fastbootd
+ * To enter recovery from Android, we can do:
+ * $ adb reboot recovery
+ * $ adb reboot fastboot
+ */
+ ANDROID_BOOT_MODE_RECOVERY,
+
+ /* Android "bootloader" is for accessing/reflashing physical partitions
+ * Typically, this will launch a fastboot process in U-Boot.
+ * To enter "bootloader" from Android, we can do:
+ * $ adb reboot bootloader
+ */
+ ANDROID_BOOT_MODE_BOOTLOADER,
+};
diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
index a07a72581e7a..709fa9e64ff3 100644
--- a/doc/develop/bootstd.rst
+++ b/doc/develop/bootstd.rst
@@ -95,6 +95,7 @@ bootflows.
Note: it is possible to have a bootmeth that uses a partition or a whole device
directly, but it is more common to use a filesystem.
+For example, the Android bootmeth uses a whole device.
Note that some bootmeths are 'global', meaning that they select the bootdev
themselves. Examples include VBE and EFI boot manager. In this case, they
@@ -277,6 +278,9 @@ script_offset_f
script_size_f
Size of the script to load, e.g. 0x2000
+vendor_boot_comp_addr_r
+ Address to which to load the vendor_boot Android image, e.g. 0xe0000000
+
Some variables are set by script bootmeth:
devtype
@@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
- EFI boot using bootefi from disk
- VBE
- EFI boot using boot manager
+ - Android bootflow (boot image v4)
Command interface
@@ -786,6 +791,7 @@ To do
Some things that need to be done to completely replace the distro-boot scripts:
- implement extensions (devicetree overlays with add-on boards)
+- implement legacy (boot image v2) android boot flow
Other ideas:
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 6/6] bootstd: Add test for bootmeth_android
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
` (4 preceding siblings ...)
2024-06-06 12:23 ` [PATCH 5/6] bootstd: Add a bootmeth for Android Mattijs Korpershoek
@ 2024-06-06 12:23 ` Mattijs Korpershoek
2024-06-11 18:52 ` Simon Glass
5 siblings, 1 reply; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-06 12:23 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot, Mattijs Korpershoek
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 # to build the mmc7.img
$ ./test/py/test.py --bd sandbox --build -k bootflow_android
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
arch/sandbox/dts/test.dts | 8 +++++
configs/sandbox_defconfig | 2 +-
test/boot/bootflow.c | 65 ++++++++++++++++++++++++++++++++++++++--
test/py/tests/test_ut.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 147 insertions(+), 4 deletions(-)
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index a012f5c4c9ba..5fb5eac862ec 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 93b52f2de5cf..bc4398f101a7 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 4511cfa7f9bf..934c4dcbad2b 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,23 @@ 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)
+{
+ 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 c169c835e38a..39e1abe02a68 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -423,6 +423,81 @@ 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()
+
+ boot_img = os.path.join(cons.config.result_dir, 'bootv4.img')
+ with open(boot_img, 'rb') as inf:
+ set_part_data(2, inf.read())
+
+ 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,
@@ -477,6 +552,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()
--
2.45.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt()
2024-06-06 12:23 ` [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() Mattijs Korpershoek
@ 2024-06-10 8:51 ` Igor Opaniuk
0 siblings, 0 replies; 18+ messages in thread
From: Igor Opaniuk @ 2024-06-10 8:51 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> 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.
>
> Note: on boot image v2, this is harmless since get_avendor_bootimg_addr()
> returns -1.
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> boot/image-fdt.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/boot/image-fdt.c b/boot/image-fdt.c
> index 56dd7687f51c..8332792b8e80 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))
>
> --
> 2.45.0
>
Reviewed-by: Igor Opaniuk <igor.opaniuk@gmail.com>
--
Best regards - Atentamente - Meilleures salutations
Igor Opaniuk
mailto: igor.opaniuk@gmail.com
skype: igor.opanyuk
https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/6] boot: android: Add image_android_get_version()
2024-06-06 12:23 ` [PATCH 2/6] boot: android: Add image_android_get_version() Mattijs Korpershoek
@ 2024-06-10 9:20 ` Igor Opaniuk
2024-06-11 9:01 ` Mattijs Korpershoek
0 siblings, 1 reply; 18+ messages in thread
From: Igor Opaniuk @ 2024-06-10 9:20 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Mattijs,
On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> When reading a boot image header, we may need to retrieve the header
> version.
>
> Add a helper function for it.
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> boot/image-android.c | 7 ++++++-
> include/image.h | 7 +++++++
> 2 files changed, 13 insertions(+), 1 deletion(-)
>
> diff --git a/boot/image-android.c b/boot/image-android.c
> index ddd8ffd5e540..4f8fb51585eb 100644
> --- a/boot/image-android.c
> +++ b/boot/image-android.c
> @@ -185,7 +185,7 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
> return false;
> }
>
> - if (((struct andr_boot_img_hdr_v0 *)boot_hdr)->header_version > 2) {
> + if (android_image_get_version(boot_hdr) > 2) {
> if (!vendor_boot_hdr) {
> printf("For boot header v3+ vendor boot image has to be provided\n");
> return false;
> @@ -203,6 +203,11 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
> return true;
> }
>
> +u32 android_image_get_version(const void *hdr)
> +{
> + return ((struct andr_boot_img_hdr_v0 *)hdr)->header_version;
> +}
> +
> static ulong android_image_get_kernel_addr(struct andr_image_data *img_data)
> {
> /*
> diff --git a/include/image.h b/include/image.h
> index acffd17e0dfd..18e5ced5ab42 100644
> --- a/include/image.h
> +++ b/include/image.h
> @@ -1963,6 +1963,13 @@ bool is_android_boot_image_header(const void *hdr);
> */
> bool is_android_vendor_boot_image_header(const void *vendor_boot_img);
>
> +/**
> + * android_image_get_version() - Retrieve the boot.img version
> + *
> + * Return: Android boot image header version.
> + */
> +u32 android_image_get_version(const void *hdr);
> +
> /**
> * get_abootimg_addr() - Get Android boot image address
> *
>
> --
> 2.45.0
>
why introduce a helper function if there is only one user of it?
android_image_get_data() expects andr_boot_img_hdr_v0 anyway,
as it has an explicit check for it in the very beginning
(is_android_boot_image_header()).
Have you considered adjusting android_image_get_data() declaration, and just use
andr_boot_img_hdr_v0 *boot_hdr as first param instead (like it's done
for example in
android_boot_image_v0_v1_v2_parse_hdr()) and then rely on implicit
cast when this
function is used.
this is of course all a matter of preference, just thinking out loud
--
Best regards - Atentamente - Meilleures salutations
Igor Opaniuk
mailto: igor.opaniuk@gmail.com
skype: igor.opanyuk
https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper
2024-06-06 12:23 ` [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper Mattijs Korpershoek
@ 2024-06-10 9:31 ` Igor Opaniuk
2024-06-11 9:06 ` Mattijs Korpershoek
0 siblings, 1 reply; 18+ messages in thread
From: Igor Opaniuk @ 2024-06-10 9:31 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Mattijs,
On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> Some bootflows might be able to only boot from MMC devices.
>
> Add a helper function these bootflows can use.
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> boot/bootflow.c | 12 ++++++++++++
> include/bootflow.h | 9 +++++++++
> 2 files changed, 21 insertions(+)
>
> diff --git a/boot/bootflow.c b/boot/bootflow.c
> index 9aa3179c3881..59d77d2385f4 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 080ee8501225..6058ddd89b16 100644
> --- a/include/bootflow.h
> +++ b/include/bootflow.h
> @@ -407,6 +407,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
> *
>
> --
> 2.45.0
>
Reviewed-by: Igor Opaniuk <igor.opaniuk@gmail.com>
A bit offtopic (just an idea for future refactoring), but I think all these
bootflow_iter_check_* helpers should be replaced by just one
int bootflow_iter_check_id(const struct bootflow_iter *iter, enum uclass_id id)
to avoid code duplication or at least keep all these
bootmedia-specific functions as
wrappers with one-line call to bootflow_iter_check_id(iter,
UCLASS_SPI_*) inside.
--
Best regards - Atentamente - Meilleures salutations
Igor Opaniuk
mailto: igor.opaniuk@gmail.com
skype: igor.opanyuk
https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr()
2024-06-06 12:23 ` [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr() Mattijs Korpershoek
@ 2024-06-10 14:27 ` Igor Opaniuk
0 siblings, 0 replies; 18+ messages in thread
From: Igor Opaniuk @ 2024-06-10 14:27 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> 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.
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> cmd/abootimg.c | 10 ++++++++++
> include/image.h | 14 ++++++++++++++
> 2 files changed, 24 insertions(+)
>
> diff --git a/cmd/abootimg.c b/cmd/abootimg.c
> index 88c77d999290..33381e22dec2 100644
> --- a/cmd/abootimg.c
> +++ b/cmd/abootimg.c
> @@ -21,11 +21,21 @@ 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_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 18e5ced5ab42..6deaf406605e 100644
> --- a/include/image.h
> +++ b/include/image.h
> @@ -1977,6 +1977,13 @@ u32 android_image_get_version(const void *hdr);
> */
> ulong get_abootimg_addr(void);
>
> +/**
> + * set_abootimg_addr() - Set Android boot image address
> + *
> + * Return: no returned results
> + */
> +void set_abootimg_addr(ulong addr);
> +
> /**
> * get_avendor_bootimg_addr() - Get Android vendor boot image address
> *
> @@ -1984,6 +1991,13 @@ ulong get_abootimg_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
> *
>
> --
> 2.45.0
>
Reviewed-by: Igor Opaniuk <igor.opaniuk@gmail.com>
--
Best regards - Atentamente - Meilleures salutations
Igor Opaniuk
mailto: igor.opaniuk@gmail.com
skype: igor.opanyuk
https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/6] bootstd: Add a bootmeth for Android
2024-06-06 12:23 ` [PATCH 5/6] bootstd: Add a bootmeth for Android Mattijs Korpershoek
@ 2024-06-10 15:15 ` Igor Opaniuk
2024-06-11 9:32 ` Mattijs Korpershoek
2024-06-11 18:52 ` Simon Glass
1 sibling, 1 reply; 18+ messages in thread
From: Igor Opaniuk @ 2024-06-10 15:15 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Mattijs,
On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> Android boot flow is a bit different than a regular Linux distro.
> Android relies on multiple partitions in order to boot.
>
> A typical boot flow would be:
> 1. Parse the Bootloader Control Block (BCB, misc partition)
> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
> 3. If BCB requested recovery or normal android, run the following:
> 3.a. Get slot (A/B) from BCB
> 3.b. Run AVB (Android Verified Boot) on boot partitions
> 3.c. Load boot and vendor_boot partitions
> 3.d. Load device-tree, ramdisk and boot
>
> The AOSP documentation has more details at [1], [2], [3]
>
> This has been implemented via complex boot scripts such as [4].
> However, these boot script are neither very maintainable nor generic.
> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>
> Add a generic Android bootflow implementation for bootstd.
> For this initial version, only boot image v4 is supported.
>
> [1] https://source.android.com/docs/core/architecture/bootloader
> [2] https://source.android.com/docs/core/architecture/partitions
> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> MAINTAINERS | 7 +
> boot/Kconfig | 14 ++
> boot/Makefile | 2 +
> boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
> boot/bootmeth_android.h | 27 +++
> doc/develop/bootstd.rst | 6 +
> 6 files changed, 578 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 66783d636e3d..6d2b87720565 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -939,6 +939,13 @@ F: include/bootstd.h
> F: net/eth_bootdevice.c
> F: test/boot/
>
> +BOOTMETH_ANDROID
> +M: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> +S: Maintained
> +T: git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
> +F: boot/bootmeth_android.c
> +F: boot/bootmeth_android.h
> +
> BTRFS
> M: Marek Behún <kabel@kernel.org>
> R: Qu Wenruo <wqu@suse.com>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 6f3096c15a6f..5fa6f3b8315d 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
> EFI bootmgr, since they take full control over which bootdevs are
> selected to boot.
>
> +config BOOTMETH_ANDROID
> + bool "Bootdev support for Android"
> + depends on X86 || ARM || SANDBOX
> + select ANDROID_AB
> + select ANDROID_BOOT_IMAGE
> + select CMD_BCB
> + select PARTITION_TYPE_GUID
> + select PARTITION_UUIDS
> + help
> + Enables support for booting Android using bootdevs. Android requires
> + multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
> +
> + Note that only MMC bootdevs are supported at present.
> +
> config BOOTMETH_CROS
> bool "Bootdev support for Chromium OS"
> depends on X86 || ARM || SANDBOX
> diff --git a/boot/Makefile b/boot/Makefile
> index 84ccfeaecec4..75d1cd46fabf 100644
> --- a/boot/Makefile
> +++ b/boot/Makefile
> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
> +
> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
> new file mode 100644
> index 000000000000..26d548d2fd6e
> --- /dev/null
> +++ b/boot/bootmeth_android.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Bootmethod for Android
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +#define LOG_CATEGORY UCLASS_BOOTSTD
> +
> +#include <android_ab.h>
> +#include <android_image.h>
> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
> +#include <avb_verify.h>
> +#endif
> +#include <bcb.h>
> +#include <blk.h>
> +#include <bootflow.h>
> +#include <bootm.h>
> +#include <bootmeth.h>
> +#include <dm.h>
> +#include <image.h>
> +#include <malloc.h>
> +#include <mapmem.h>
> +#include <part.h>
> +#include "bootmeth_android.h"
> +
> +#define BCB_FIELD_COMMAND_SZ 32
> +#define BCB_PART_NAME "misc"
> +#define BOOT_PART_NAME "boot"
> +#define VENDOR_BOOT_PART_NAME "vendor_boot"
> +
> +/**
> + * struct android_priv - Private data
> + *
> + * This is read from the disk and recorded for use when the full Android
> + * kernel must be loaded and booted
> + */
> +struct android_priv {
> + int boot_mode;
> + char slot[2];
> + u32 header_version;
> +};
> +
> +static int android_check(struct udevice *dev, struct bootflow_iter *iter)
> +{
> + /* This only works on mmc devices */
> + if (bootflow_iter_check_mmc(iter))
> + return log_msg_ret("mmc", -ENOTSUPP);
> +
> + /* This only works on whole devices, as multiple
> + * partitions are needed to boot Android
> + */
Please use Linux kernel coding style for long comments [1].
Same in all occurrences below
[1] https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting
> + if (iter->part != 0)
> + return log_msg_ret("mmc part", -ENOTSUPP);
> +
> + return 0;
> +}
> +
> +static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(blk);
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + ulong num_blks, bufsz;
> + char *buf;
> + int ret;
> +
> + sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part info", ret);
> +
> + num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
> + bufsz = num_blks * desc->blksz;
> + buf = malloc(bufsz);
> + if (!buf)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + ret = blk_read(blk, partition.start, num_blks, buf);
> + if (ret != num_blks) {
> + free(buf);
> + return log_msg_ret("part read", -EIO);
> + }
> +
> + if (!is_android_boot_image_header(buf)) {
> + free(buf);
> + return log_msg_ret("header", -ENOENT);
> + }
> +
> + priv->header_version = android_image_get_version(buf);
> +
> + return 0;
Shouldn't we free(buf) also here?
> +}
> +
> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(blk);
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + ulong num_blks, bufsz;
> + char *buf;
> + int ret;
> +
> + sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part info", ret);
> +
> + num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
> + bufsz = num_blks * desc->blksz;
> + buf = malloc(bufsz);
> + if (!buf)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + ret = blk_read(blk, partition.start, num_blks, buf);
> + if (ret != num_blks) {
> + free(buf);
> + return log_msg_ret("part read", -EIO);
> + }
> +
> + if (!is_android_vendor_boot_image_header(buf)) {
> + free(buf);
> + return log_msg_ret("header", -ENOENT);
> + }
> +
> + return 0;
free(buf)?
> +}
> +
> +static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + struct disk_partition misc;
> + char slot_suffix[3];
> + int ret;
> +
> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + ret = ab_select_slot(desc, &misc, decrement);
> + if (ret < 0)
> + return log_msg_ret("slot", ret);
> +
> + priv->slot[0] = BOOT_SLOT_NAME(ret);
> + priv->slot[1] = '\0';
> +
> + sprintf(slot_suffix, "_%s", priv->slot);
> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
> + slot_suffix, false);
> + if (ret < 0)
> + return log_msg_ret("slot", ret);
> +
> + return 0;
> +}
> +
> +static int configure_serialno(struct bootflow *bflow)
> +{
> + char *serialno = env_get("serial#");
> +
> + if (!serialno)
> + return log_msg_ret("serial", -ENOENT);
> +
> + return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
> +}
> +
> +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct disk_partition misc;
> + struct android_priv *priv;
> + char command[BCB_FIELD_COMMAND_SZ];
> + int ret;
> +
> + bflow->state = BOOTFLOWST_MEDIA;
> +
> + /* bcb_find_partition_and_load() will print errors to stdout
> + * if BCB_PART_NAME is not found. To avoid that, check if the
> + * partition exists first.
> + */
> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
> + if (ret < 0)
> + return log_msg_ret("bcb load", ret);
> +
> + ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
> + if (ret < 0)
> + return log_msg_ret("bcb read", ret);
> +
> + priv = malloc(sizeof(struct android_priv));
> + if (!priv)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + bflow->bootmeth_priv = priv;
Probably we should do that just before successfully returning from the function,
otherwise we will end up with a dangling pointer.
> + if (!strcmp("bootonce-bootloader", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
> + bflow->os_name = strdup("Android (bootloader)");
> + } else if (!strcmp("boot-fastboot", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
> + bflow->os_name = strdup("Android (fastbootd)");
> + } else if (!strcmp("boot-recovery", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
> + bflow->os_name = strdup("Android (recovery)");
> + } else {
> + priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
> + bflow->os_name = strdup("Android");
> + }
> + if (!bflow->os_name)
> + return log_msg_ret("os", -ENOMEM);
> +
> + if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
> + /* Clear BCB */
> + memset(command, 0, sizeof(command));
> + ret = bcb_set(BCB_FIELD_COMMAND, command);
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("bcb set", ret);
> + }
> + ret = bcb_store();
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("bcb store", ret);
> + }
> +
is free(bflow->bootmeth_priv) handled in functions that call
android_read_bootflow() ?
> + bflow->state = BOOTFLOWST_READY;
> + return 0;
> + }
> +
> + /* For recovery and normal boot, we need to scan the partitions */
> + ret = android_read_slot_from_bcb(bflow, false);
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("read slot", ret);
> + }
> +
> + ret = scan_boot_part(bflow->blk, priv);
> + if (ret < 0) {
> + printf("- scan boot failed: err=%d\n", ret);
> + free(priv);
> + return log_msg_ret("scan boot", ret);
> + }
> +
> + if (priv->header_version != 4) {
> + printf("- Only boot.img v4 is supported\n");
> + free(priv);
> + return log_msg_ret("version", -EINVAL);
> + }
> +
> + ret = scan_vendor_boot_part(bflow->blk, priv->slot);
> + if (ret < 0) {
> + printf("- scan vendor_boot failed: err=%d\n", ret);
> + free(priv);
> + return log_msg_ret("scan vendor_boot", ret);
> + }
> +
> + /* Ignoring return code: setting serial number is not mandatory for booting */
> + configure_serialno(bflow);
> +
> + if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("normal_boot", ret);
> + }
> + }
> +
> + bflow->state = BOOTFLOWST_READY;
> +
> + return 0;
> +}
> +
> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
> + const char *file_path, ulong addr, ulong *sizep)
> +{
> + /* Reading individual files is not supported since we only
> + * operate on whole mmc devices (because we require multiple partitions)
> + */
> + return log_msg_ret("Unsupported", -ENOSYS);
> +}
> +
> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
> + const char slot[2], ulong addr)
> +{
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + int ret;
> + u32 n;
> +
> + /* Ensure name fits in partname it should be: <name>_<slot>\0 */
> + if (strlen(name) > (PART_NAME_LEN - 2 - 1))
> + return log_msg_ret("name too long", -EINVAL);
> +
> + sprintf(partname, "%s_%s", name, slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
> + if (n < partition.size)
> + return log_msg_ret("part read", -EIO);
> +
> + return 0;
> +}
> +
> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
> +static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
> +{
> + char *key = strsep(&arg, "=");
> + char *value = arg;
> + int ret;
> +
> + ret = bootflow_cmdline_set_arg(bflow, key, value, false);
> + if (ret < 0)
> + return log_msg_ret("avb cmdline", ret);
> +
> + return 0;
> +}
> +
> +static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
> +{
> + char *arg = strsep(&cmdline, " ");
> + int ret;
> +
> + while (arg) {
> + ret = avb_append_commandline_arg(bflow, arg);
> + if (ret < 0)
> + return ret;
> +
> + arg = strsep(&cmdline, " ");
> + }
> +
> + return 0;
> +}
> +
> +static int run_avb_verification(struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + const char * const requested_partitions[] = {"boot", "vendor_boot"};
> + struct AvbOps *avb_ops;
> + AvbSlotVerifyResult result;
> + AvbSlotVerifyData *out_data;
> + enum avb_boot_state boot_state;
> + char *extra_args;
> + char slot_suffix[3];
> + bool unlocked = false;
> + int ret;
> +
> + avb_ops = avb_ops_alloc(desc->devnum);
> + if (!avb_ops)
> + return log_msg_ret("avb ops", -ENOMEM);.
> +
> + sprintf(slot_suffix, "_%s", priv->slot);
> +
> + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
> + if (ret != AVB_IO_RESULT_OK)
> + return log_msg_ret("avb lock", -EIO);
> +
> + result = avb_slot_verify(avb_ops,
> + requested_partitions,
> + slot_suffix,
> + unlocked,
> + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
> + &out_data);
> +
> + if (result != AVB_SLOT_VERIFY_RESULT_OK) {
> + printf("Verification failed, reason: %s\n",
> + str_avb_slot_error(result));
avb_ops_free(avb_ops) ?
> + return log_msg_ret("avb verify", -EIO);
> + }
> +
> + if (unlocked)
> + boot_state = AVB_ORANGE;
> + else
> + boot_state = AVB_GREEN;
> +
> + extra_args = avb_set_state(avb_ops, boot_state);
> + if (extra_args) {
> + ret = avb_append_commandline_arg(bflow, extra_args);
> + if (ret < 0)
> + goto free_out_data;
> + }
> +
> + ret = avb_append_commandline(bflow, out_data->cmdline);
> + if (ret < 0)
> + goto free_out_data;
> +
> + return 0;
> +
> + free_out_data:
> + if (out_data)
> + avb_slot_verify_data_free(out_data);
> +
> + return log_msg_ret("avb cmdline", ret);
> +}
> +#else
> +static int run_avb_verification(struct bootflow *bflow)
> +{
> + int ret;
> +
> + /* When AVB is unsupported, pass ORANGE state */
> + ret = bootflow_cmdline_set_arg(bflow,
> + "androidboot.verifiedbootstate",
> + "orange", false);
> + if (ret < 0)
> + return log_msg_ret("avb cmdline", ret);
> +
> + return 0;
> +}
> +#endif /* AVB_VERIFY */
> +
> +static int boot_android_normal(struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + int ret;
> +
> + ulong loadaddr = env_get_hex("loadaddr", 0);
> + ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
> +
> + ret = run_avb_verification(bflow);
> + if (ret < 0)
> + return log_msg_ret("avb", ret);
> +
> + /* Read slot once more to decrement counter from BCB */
> + ret = android_read_slot_from_bcb(bflow, true);
> + if (ret < 0)
> + return log_msg_ret("read slot", ret);
> +
> + ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
> + if (ret < 0)
> + return log_msg_ret("read boot", ret);
> +
> + ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
> + if (ret < 0)
> + return log_msg_ret("read vendor_boot", ret);
> +
> + set_abootimg_addr(loadaddr);
> + set_avendor_bootimg_addr(vloadaddr);
> +
> + ret = bootm_boot_start(loadaddr, bflow->cmdline);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int boot_android_recovery(struct bootflow *bflow)
> +{
> + int ret;
> +
> + ret = boot_android_normal(bflow);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int boot_android_bootloader(struct bootflow *bflow)
> +{
> + int ret;
> +
> + ret = run_command("fastboot usb 0", 0);
select CMD_FASTBOOT in Kconfig (config BOOTMETH_ANDROID)?
> + do_reset(NULL, 0, 0, NULL);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int android_boot(struct udevice *dev, struct bootflow *bflow)
> +{
> + struct android_priv *priv = bflow->bootmeth_priv;
> + int ret;
> +
> + switch (priv->boot_mode) {
> + case ANDROID_BOOT_MODE_NORMAL:
> + ret = boot_android_normal(bflow);
> + break;
> + case ANDROID_BOOT_MODE_RECOVERY:
> + ret = boot_android_recovery(bflow);
> + break;
> + case ANDROID_BOOT_MODE_BOOTLOADER:
> + ret = boot_android_bootloader(bflow);
> + break;
> + default:
> + printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
> + priv->boot_mode);
> + boot_android_bootloader(bflow);
> + /* Tell we failed to boot since boot mode is unknown */
> + ret = -EFAULT;
> + }
> +
> + return ret;
> +}
> +
> +static int android_bootmeth_bind(struct udevice *dev)
> +{
> + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
> +
> + plat->desc = "Android boot";
> + plat->flags = BOOTMETHF_ANY_PART;
> +
> + return 0;
> +}
> +
> +static struct bootmeth_ops android_bootmeth_ops = {
> + .check = android_check,
> + .read_bootflow = android_read_bootflow,
> + .read_file = android_read_file,
> + .boot = android_boot,
> +};
> +
> +static const struct udevice_id android_bootmeth_ids[] = {
> + { .compatible = "u-boot,android" },
> + { }
> +};
> +
> +U_BOOT_DRIVER(bootmeth_android) = {
> + .name = "bootmeth_android",
> + .id = UCLASS_BOOTMETH,
> + .of_match = android_bootmeth_ids,
> + .ops = &android_bootmeth_ops,
> + .bind = android_bootmeth_bind,
> +};
> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
> new file mode 100644
> index 000000000000..411c2f2d15e7
> --- /dev/null
> +++ b/boot/bootmeth_android.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Bootmethod for Android
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +
> +enum android_boot_mode {
> + ANDROID_BOOT_MODE_NORMAL = 0,
> +
> + /* Android "recovery" is a special boot mode that uses another ramdisk.
> + * It can be used to "factory reset" a board or to flash logical partitions
> + * It operates in 2 modes: adb or fastbootd
> + * To enter recovery from Android, we can do:
> + * $ adb reboot recovery
> + * $ adb reboot fastboot
> + */
> + ANDROID_BOOT_MODE_RECOVERY,
> +
> + /* Android "bootloader" is for accessing/reflashing physical partitions
> + * Typically, this will launch a fastboot process in U-Boot.
> + * To enter "bootloader" from Android, we can do:
> + * $ adb reboot bootloader
> + */
> + ANDROID_BOOT_MODE_BOOTLOADER,
> +};
> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
> index a07a72581e7a..709fa9e64ff3 100644
> --- a/doc/develop/bootstd.rst
> +++ b/doc/develop/bootstd.rst
> @@ -95,6 +95,7 @@ bootflows.
>
> Note: it is possible to have a bootmeth that uses a partition or a whole device
> directly, but it is more common to use a filesystem.
> +For example, the Android bootmeth uses a whole device.
>
> Note that some bootmeths are 'global', meaning that they select the bootdev
> themselves. Examples include VBE and EFI boot manager. In this case, they
> @@ -277,6 +278,9 @@ script_offset_f
> script_size_f
> Size of the script to load, e.g. 0x2000
>
> +vendor_boot_comp_addr_r
> + Address to which to load the vendor_boot Android image, e.g. 0xe0000000
> +
> Some variables are set by script bootmeth:
>
> devtype
> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
> - EFI boot using bootefi from disk
> - VBE
> - EFI boot using boot manager
> + - Android bootflow (boot image v4)
>
>
> Command interface
> @@ -786,6 +791,7 @@ To do
> Some things that need to be done to completely replace the distro-boot scripts:
>
> - implement extensions (devicetree overlays with add-on boards)
> +- implement legacy (boot image v2) android boot flow
>
> Other ideas:
>
>
> --
> 2.45.0
>
Some comments after a quick "scan". Will take a more detailed look a
bit later today/tomorrow.
--
Best regards - Atentamente - Meilleures salutations
Igor Opaniuk
mailto: igor.opaniuk@gmail.com
skype: igor.opanyuk
https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/6] boot: android: Add image_android_get_version()
2024-06-10 9:20 ` Igor Opaniuk
@ 2024-06-11 9:01 ` Mattijs Korpershoek
0 siblings, 0 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-11 9:01 UTC (permalink / raw)
To: Igor Opaniuk
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Igor,
Thank you for the review.
On lun., juin 10, 2024 at 11:20, Igor Opaniuk <igor.opaniuk@gmail.com> wrote:
> Hi Mattijs,
>
> On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> When reading a boot image header, we may need to retrieve the header
>> version.
>>
>> Add a helper function for it.
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>> boot/image-android.c | 7 ++++++-
>> include/image.h | 7 +++++++
>> 2 files changed, 13 insertions(+), 1 deletion(-)
>>
>> diff --git a/boot/image-android.c b/boot/image-android.c
>> index ddd8ffd5e540..4f8fb51585eb 100644
>> --- a/boot/image-android.c
>> +++ b/boot/image-android.c
>> @@ -185,7 +185,7 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
>> return false;
>> }
>>
>> - if (((struct andr_boot_img_hdr_v0 *)boot_hdr)->header_version > 2) {
>> + if (android_image_get_version(boot_hdr) > 2) {
>> if (!vendor_boot_hdr) {
>> printf("For boot header v3+ vendor boot image has to be provided\n");
>> return false;
>> @@ -203,6 +203,11 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
>> return true;
>> }
>>
>> +u32 android_image_get_version(const void *hdr)
>> +{
>> + return ((struct andr_boot_img_hdr_v0 *)hdr)->header_version;
>> +}
>> +
>> static ulong android_image_get_kernel_addr(struct andr_image_data *img_data)
>> {
>> /*
>> diff --git a/include/image.h b/include/image.h
>> index acffd17e0dfd..18e5ced5ab42 100644
>> --- a/include/image.h
>> +++ b/include/image.h
>> @@ -1963,6 +1963,13 @@ bool is_android_boot_image_header(const void *hdr);
>> */
>> bool is_android_vendor_boot_image_header(const void *vendor_boot_img);
>>
>> +/**
>> + * android_image_get_version() - Retrieve the boot.img version
>> + *
>> + * Return: Android boot image header version.
>> + */
>> +u32 android_image_get_version(const void *hdr);
>> +
>> /**
>> * get_abootimg_addr() - Get Android boot image address
>> *
>>
>> --
>> 2.45.0
>>
> why introduce a helper function if there is only one user of it?
I added a second user of the helper function in patch 5/6.
>
> android_image_get_data() expects andr_boot_img_hdr_v0 anyway,
> as it has an explicit check for it in the very beginning
> (is_android_boot_image_header()).
Right.
>
> Have you considered adjusting android_image_get_data() declaration, and just use
> andr_boot_img_hdr_v0 *boot_hdr as first param instead (like it's done
> for example in
> android_boot_image_v0_v1_v2_parse_hdr()) and then rely on implicit
> cast when this
> function is used.
>
> this is of course all a matter of preference, just thinking out loud
I've given this some more thought, and since I'm already using
struct andr_boot_img_hdr_v0 in bootmeth_android/scan_boot_part(), I will
drop this patch for v2.
The helper seems indeed a bit useless given that we can use the struct's
member for this.
Thanks!
>
> --
> Best regards - Atentamente - Meilleures salutations
>
> Igor Opaniuk
>
> mailto: igor.opaniuk@gmail.com
> skype: igor.opanyuk
> https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper
2024-06-10 9:31 ` Igor Opaniuk
@ 2024-06-11 9:06 ` Mattijs Korpershoek
0 siblings, 0 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-11 9:06 UTC (permalink / raw)
To: Igor Opaniuk
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Igor,
Thank you for the review.
On lun., juin 10, 2024 at 11:31, Igor Opaniuk <igor.opaniuk@gmail.com> wrote:
> Hi Mattijs,
>
> On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> Some bootflows might be able to only boot from MMC devices.
>>
>> Add a helper function these bootflows can use.
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>> boot/bootflow.c | 12 ++++++++++++
>> include/bootflow.h | 9 +++++++++
>> 2 files changed, 21 insertions(+)
>>
>> diff --git a/boot/bootflow.c b/boot/bootflow.c
>> index 9aa3179c3881..59d77d2385f4 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 080ee8501225..6058ddd89b16 100644
>> --- a/include/bootflow.h
>> +++ b/include/bootflow.h
>> @@ -407,6 +407,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
>> *
>>
>> --
>> 2.45.0
>>
>
> Reviewed-by: Igor Opaniuk <igor.opaniuk@gmail.com>
>
> A bit offtopic (just an idea for future refactoring), but I think all these
> bootflow_iter_check_* helpers should be replaced by just one
> int bootflow_iter_check_id(const struct bootflow_iter *iter, enum uclass_id id)
> to avoid code duplication or at least keep all these
> bootmedia-specific functions as
> wrappers with one-line call to bootflow_iter_check_id(iter,
> UCLASS_SPI_*) inside.
I like this idea as well, I'll consider to implement this as a future refactoring.
>
> --
> Best regards - Atentamente - Meilleures salutations
>
> Igor Opaniuk
>
> mailto: igor.opaniuk@gmail.com
> skype: igor.opanyuk
> https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/6] bootstd: Add a bootmeth for Android
2024-06-10 15:15 ` Igor Opaniuk
@ 2024-06-11 9:32 ` Mattijs Korpershoek
0 siblings, 0 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-11 9:32 UTC (permalink / raw)
To: Igor Opaniuk
Cc: Simon Glass, Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, u-boot
Hi Igor,
Thank you for your quick review.
On lun., juin 10, 2024 at 17:15, Igor Opaniuk <igor.opaniuk@gmail.com> wrote:
> Hi Mattijs,
>
> On Thu, Jun 6, 2024 at 2:24 PM Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> Android boot flow is a bit different than a regular Linux distro.
>> Android relies on multiple partitions in order to boot.
>>
>> A typical boot flow would be:
>> 1. Parse the Bootloader Control Block (BCB, misc partition)
>> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
>> 3. If BCB requested recovery or normal android, run the following:
>> 3.a. Get slot (A/B) from BCB
>> 3.b. Run AVB (Android Verified Boot) on boot partitions
>> 3.c. Load boot and vendor_boot partitions
>> 3.d. Load device-tree, ramdisk and boot
>>
>> The AOSP documentation has more details at [1], [2], [3]
>>
>> This has been implemented via complex boot scripts such as [4].
>> However, these boot script are neither very maintainable nor generic.
>> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>>
>> Add a generic Android bootflow implementation for bootstd.
>> For this initial version, only boot image v4 is supported.
>>
>> [1] https://source.android.com/docs/core/architecture/bootloader
>> [2] https://source.android.com/docs/core/architecture/partitions
>> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
>> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
>> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>> MAINTAINERS | 7 +
>> boot/Kconfig | 14 ++
>> boot/Makefile | 2 +
>> boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>> boot/bootmeth_android.h | 27 +++
>> doc/develop/bootstd.rst | 6 +
>> 6 files changed, 578 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 66783d636e3d..6d2b87720565 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -939,6 +939,13 @@ F: include/bootstd.h
>> F: net/eth_bootdevice.c
>> F: test/boot/
>>
>> +BOOTMETH_ANDROID
>> +M: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> +S: Maintained
>> +T: git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
>> +F: boot/bootmeth_android.c
>> +F: boot/bootmeth_android.h
>> +
>> BTRFS
>> M: Marek Behún <kabel@kernel.org>
>> R: Qu Wenruo <wqu@suse.com>
>> diff --git a/boot/Kconfig b/boot/Kconfig
>> index 6f3096c15a6f..5fa6f3b8315d 100644
>> --- a/boot/Kconfig
>> +++ b/boot/Kconfig
>> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>> EFI bootmgr, since they take full control over which bootdevs are
>> selected to boot.
>>
>> +config BOOTMETH_ANDROID
>> + bool "Bootdev support for Android"
>> + depends on X86 || ARM || SANDBOX
>> + select ANDROID_AB
>> + select ANDROID_BOOT_IMAGE
>> + select CMD_BCB
>> + select PARTITION_TYPE_GUID
>> + select PARTITION_UUIDS
>> + help
>> + Enables support for booting Android using bootdevs. Android requires
>> + multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
>> +
>> + Note that only MMC bootdevs are supported at present.
>> +
>> config BOOTMETH_CROS
>> bool "Bootdev support for Chromium OS"
>> depends on X86 || ARM || SANDBOX
>> diff --git a/boot/Makefile b/boot/Makefile
>> index 84ccfeaecec4..75d1cd46fabf 100644
>> --- a/boot/Makefile
>> +++ b/boot/Makefile
>> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
>> +
>> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
>> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
>> new file mode 100644
>> index 000000000000..26d548d2fd6e
>> --- /dev/null
>> +++ b/boot/bootmeth_android.c
>> @@ -0,0 +1,522 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Bootmethod for Android
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +#define LOG_CATEGORY UCLASS_BOOTSTD
>> +
>> +#include <android_ab.h>
>> +#include <android_image.h>
>> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
>> +#include <avb_verify.h>
>> +#endif
>> +#include <bcb.h>
>> +#include <blk.h>
>> +#include <bootflow.h>
>> +#include <bootm.h>
>> +#include <bootmeth.h>
>> +#include <dm.h>
>> +#include <image.h>
>> +#include <malloc.h>
>> +#include <mapmem.h>
>> +#include <part.h>
>> +#include "bootmeth_android.h"
>> +
>> +#define BCB_FIELD_COMMAND_SZ 32
>> +#define BCB_PART_NAME "misc"
>> +#define BOOT_PART_NAME "boot"
>> +#define VENDOR_BOOT_PART_NAME "vendor_boot"
>> +
>> +/**
>> + * struct android_priv - Private data
>> + *
>> + * This is read from the disk and recorded for use when the full Android
>> + * kernel must be loaded and booted
>> + */
>> +struct android_priv {
>> + int boot_mode;
>> + char slot[2];
>> + u32 header_version;
>> +};
>> +
>> +static int android_check(struct udevice *dev, struct bootflow_iter *iter)
>> +{
>> + /* This only works on mmc devices */
>> + if (bootflow_iter_check_mmc(iter))
>> + return log_msg_ret("mmc", -ENOTSUPP);
>> +
>> + /* This only works on whole devices, as multiple
>> + * partitions are needed to boot Android
>> + */
> Please use Linux kernel coding style for long comments [1].
> Same in all occurrences below
>
> [1] https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting
Sorry about that that. Will do.
Odd that running checkpatch with --u-boot did not caught this.
$ ./scripts/checkpatch.pl --strict --u-boot --git HEAD^^..HEAD
>> + if (iter->part != 0)
>> + return log_msg_ret("mmc part", -ENOTSUPP);
>> +
>> + return 0;
>> +}
>> +
>> +static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(blk);
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + ulong num_blks, bufsz;
>> + char *buf;
>> + int ret;
>> +
>> + sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part info", ret);
>> +
>> + num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
>> + bufsz = num_blks * desc->blksz;
>> + buf = malloc(bufsz);
>> + if (!buf)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + ret = blk_read(blk, partition.start, num_blks, buf);
>> + if (ret != num_blks) {
>> + free(buf);
>> + return log_msg_ret("part read", -EIO);
>> + }
>> +
>> + if (!is_android_boot_image_header(buf)) {
>> + free(buf);
>> + return log_msg_ret("header", -ENOENT);
>> + }
>> +
>> + priv->header_version = android_image_get_version(buf);
>> +
>> + return 0;
> Shouldn't we free(buf) also here?
Yes we should. Will do for v2. Thanks for catching this!
>
>> +}
>> +
>> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(blk);
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + ulong num_blks, bufsz;
>> + char *buf;
>> + int ret;
>> +
>> + sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part info", ret);
>> +
>> + num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
>> + bufsz = num_blks * desc->blksz;
>> + buf = malloc(bufsz);
>> + if (!buf)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + ret = blk_read(blk, partition.start, num_blks, buf);
>> + if (ret != num_blks) {
>> + free(buf);
>> + return log_msg_ret("part read", -EIO);
>> + }
>> +
>> + if (!is_android_vendor_boot_image_header(buf)) {
>> + free(buf);
>> + return log_msg_ret("header", -ENOENT);
>> + }
>> +
>> + return 0;
> free(buf)?
Yes we should. Will do for v2. Thanks for catching this!
>
>> +}
>> +
>> +static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + struct disk_partition misc;
>> + char slot_suffix[3];
>> + int ret;
>> +
>> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + ret = ab_select_slot(desc, &misc, decrement);
>> + if (ret < 0)
>> + return log_msg_ret("slot", ret);
>> +
>> + priv->slot[0] = BOOT_SLOT_NAME(ret);
>> + priv->slot[1] = '\0';
>> +
>> + sprintf(slot_suffix, "_%s", priv->slot);
>> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
>> + slot_suffix, false);
>> + if (ret < 0)
>> + return log_msg_ret("slot", ret);
>> +
>> + return 0;
>> +}
>> +
>> +static int configure_serialno(struct bootflow *bflow)
>> +{
>> + char *serialno = env_get("serial#");
>> +
>> + if (!serialno)
>> + return log_msg_ret("serial", -ENOENT);
>> +
>> + return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
>> +}
>> +
>> +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct disk_partition misc;
>> + struct android_priv *priv;
>> + char command[BCB_FIELD_COMMAND_SZ];
>> + int ret;
>> +
>> + bflow->state = BOOTFLOWST_MEDIA;
>> +
>> + /* bcb_find_partition_and_load() will print errors to stdout
>> + * if BCB_PART_NAME is not found. To avoid that, check if the
>> + * partition exists first.
>> + */
>> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
>> + if (ret < 0)
>> + return log_msg_ret("bcb load", ret);
>> +
>> + ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
>> + if (ret < 0)
>> + return log_msg_ret("bcb read", ret);
>> +
>> + priv = malloc(sizeof(struct android_priv));
>> + if (!priv)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + bflow->bootmeth_priv = priv;
> Probably we should do that just before successfully returning from the function,
> otherwise we will end up with a dangling pointer.
Right, thanks for the suggestion. Will do for v2.
>
>> + if (!strcmp("bootonce-bootloader", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
>> + bflow->os_name = strdup("Android (bootloader)");
>> + } else if (!strcmp("boot-fastboot", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
>> + bflow->os_name = strdup("Android (fastbootd)");
>> + } else if (!strcmp("boot-recovery", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
>> + bflow->os_name = strdup("Android (recovery)");
>> + } else {
>> + priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
>> + bflow->os_name = strdup("Android");
>> + }
>> + if (!bflow->os_name)
>> + return log_msg_ret("os", -ENOMEM);
>> +
>> + if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
>> + /* Clear BCB */
>> + memset(command, 0, sizeof(command));
>> + ret = bcb_set(BCB_FIELD_COMMAND, command);
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("bcb set", ret);
>> + }
>> + ret = bcb_store();
>> + if (ret < 0) {
>> + free(priv);
>
>> + return log_msg_ret("bcb store", ret);
>> + }
>> +
> is free(bflow->bootmeth_priv) handled in functions that call
> android_read_bootflow() ?
It's not. Future functions (called later) like boot_android() need to
access this structure.
I've modeled this after bootmeth_cros/cros_boot() but I don't
see a reason for not freeing this just before calling bootm().
On the other hand, we will be booting an OS (and this quitting U-Boot)
so it's not entirely needed.
I don't have a strong opinion on this so I can add the free() in
boot_android_normal() and boot_android_bootloader().
>
>> + bflow->state = BOOTFLOWST_READY;
>> + return 0;
>> + }
>> +
>> + /* For recovery and normal boot, we need to scan the partitions */
>> + ret = android_read_slot_from_bcb(bflow, false);
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("read slot", ret);
>> + }
>> +
>> + ret = scan_boot_part(bflow->blk, priv);
>> + if (ret < 0) {
>> + printf("- scan boot failed: err=%d\n", ret);
>> + free(priv);
>> + return log_msg_ret("scan boot", ret);
>> + }
>> +
>> + if (priv->header_version != 4) {
>> + printf("- Only boot.img v4 is supported\n");
>> + free(priv);
>> + return log_msg_ret("version", -EINVAL);
>> + }
>> +
>> + ret = scan_vendor_boot_part(bflow->blk, priv->slot);
>> + if (ret < 0) {
>> + printf("- scan vendor_boot failed: err=%d\n", ret);
>> + free(priv);
>> + return log_msg_ret("scan vendor_boot", ret);
>> + }
>> +
>> + /* Ignoring return code: setting serial number is not mandatory for booting */
>> + configure_serialno(bflow);
>> +
>> + if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
>> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("normal_boot", ret);
>> + }
>> + }
>> +
>> + bflow->state = BOOTFLOWST_READY;
>> +
>> + return 0;
>> +}
>> +
>> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
>> + const char *file_path, ulong addr, ulong *sizep)
>> +{
>> + /* Reading individual files is not supported since we only
>> + * operate on whole mmc devices (because we require multiple partitions)
>> + */
>> + return log_msg_ret("Unsupported", -ENOSYS);
>> +}
>> +
>> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
>> + const char slot[2], ulong addr)
>> +{
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + int ret;
>> + u32 n;
>> +
>> + /* Ensure name fits in partname it should be: <name>_<slot>\0 */
>> + if (strlen(name) > (PART_NAME_LEN - 2 - 1))
>> + return log_msg_ret("name too long", -EINVAL);
>> +
>> + sprintf(partname, "%s_%s", name, slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
>> + if (n < partition.size)
>> + return log_msg_ret("part read", -EIO);
>> +
>> + return 0;
>> +}
>> +
>> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
>> +static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
>> +{
>> + char *key = strsep(&arg, "=");
>> + char *value = arg;
>> + int ret;
>> +
>> + ret = bootflow_cmdline_set_arg(bflow, key, value, false);
>> + if (ret < 0)
>> + return log_msg_ret("avb cmdline", ret);
>> +
>> + return 0;
>> +}
>> +
>> +static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
>> +{
>> + char *arg = strsep(&cmdline, " ");
>> + int ret;
>> +
>> + while (arg) {
>> + ret = avb_append_commandline_arg(bflow, arg);
>> + if (ret < 0)
>> + return ret;
>> +
>> + arg = strsep(&cmdline, " ");
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int run_avb_verification(struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + const char * const requested_partitions[] = {"boot", "vendor_boot"};
>> + struct AvbOps *avb_ops;
>> + AvbSlotVerifyResult result;
>> + AvbSlotVerifyData *out_data;
>> + enum avb_boot_state boot_state;
>> + char *extra_args;
>> + char slot_suffix[3];
>> + bool unlocked = false;
>> + int ret;
>> +
>> + avb_ops = avb_ops_alloc(desc->devnum);
>> + if (!avb_ops)
>> + return log_msg_ret("avb ops", -ENOMEM);.
>> +
>> + sprintf(slot_suffix, "_%s", priv->slot);
>> +
>> + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
>> + if (ret != AVB_IO_RESULT_OK)
>> + return log_msg_ret("avb lock", -EIO);
>> +
>> + result = avb_slot_verify(avb_ops,
>> + requested_partitions,
>> + slot_suffix,
>> + unlocked,
>> + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
>> + &out_data);
>> +
>> + if (result != AVB_SLOT_VERIFY_RESULT_OK) {
>> + printf("Verification failed, reason: %s\n",
>> + str_avb_slot_error(result));
> avb_ops_free(avb_ops) ?
Yes we should. Will do for v2. Thanks for catching this!
>> + return log_msg_ret("avb verify", -EIO);
>> + }
>> +
>> + if (unlocked)
>> + boot_state = AVB_ORANGE;
>> + else
>> + boot_state = AVB_GREEN;
>> +
>> + extra_args = avb_set_state(avb_ops, boot_state);
>> + if (extra_args) {
>> + ret = avb_append_commandline_arg(bflow, extra_args);
>> + if (ret < 0)
>> + goto free_out_data;
>> + }
>> +
>> + ret = avb_append_commandline(bflow, out_data->cmdline);
>> + if (ret < 0)
>> + goto free_out_data;
>> +
>> + return 0;
>> +
>> + free_out_data:
>> + if (out_data)
>> + avb_slot_verify_data_free(out_data);
>> +
>> + return log_msg_ret("avb cmdline", ret);
>> +}
>> +#else
>> +static int run_avb_verification(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + /* When AVB is unsupported, pass ORANGE state */
>> + ret = bootflow_cmdline_set_arg(bflow,
>> + "androidboot.verifiedbootstate",
>> + "orange", false);
>> + if (ret < 0)
>> + return log_msg_ret("avb cmdline", ret);
>> +
>> + return 0;
>> +}
>> +#endif /* AVB_VERIFY */
>> +
>> +static int boot_android_normal(struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + int ret;
>> +
>> + ulong loadaddr = env_get_hex("loadaddr", 0);
>> + ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
>> +
>> + ret = run_avb_verification(bflow);
>> + if (ret < 0)
>> + return log_msg_ret("avb", ret);
>> +
>> + /* Read slot once more to decrement counter from BCB */
>> + ret = android_read_slot_from_bcb(bflow, true);
>> + if (ret < 0)
>> + return log_msg_ret("read slot", ret);
>> +
>> + ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
>> + if (ret < 0)
>> + return log_msg_ret("read boot", ret);
>> +
>> + ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
>> + if (ret < 0)
>> + return log_msg_ret("read vendor_boot", ret);
>> +
>> + set_abootimg_addr(loadaddr);
>> + set_avendor_bootimg_addr(vloadaddr);
>> +
>> + ret = bootm_boot_start(loadaddr, bflow->cmdline);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int boot_android_recovery(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + ret = boot_android_normal(bflow);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int boot_android_bootloader(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + ret = run_command("fastboot usb 0", 0);
> select CMD_FASTBOOT in Kconfig (config BOOTMETH_ANDROID)?
Yes, I will add this for v2. Thank you for the suggestion.
>> + do_reset(NULL, 0, 0, NULL);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int android_boot(struct udevice *dev, struct bootflow *bflow)
>> +{
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + int ret;
>> +
>> + switch (priv->boot_mode) {
>> + case ANDROID_BOOT_MODE_NORMAL:
>> + ret = boot_android_normal(bflow);
>> + break;
>> + case ANDROID_BOOT_MODE_RECOVERY:
>> + ret = boot_android_recovery(bflow);
>> + break;
>> + case ANDROID_BOOT_MODE_BOOTLOADER:
>> + ret = boot_android_bootloader(bflow);
>> + break;
>> + default:
>> + printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
>> + priv->boot_mode);
>> + boot_android_bootloader(bflow);
>> + /* Tell we failed to boot since boot mode is unknown */
>> + ret = -EFAULT;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int android_bootmeth_bind(struct udevice *dev)
>> +{
>> + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
>> +
>> + plat->desc = "Android boot";
>> + plat->flags = BOOTMETHF_ANY_PART;
>> +
>> + return 0;
>> +}
>> +
>> +static struct bootmeth_ops android_bootmeth_ops = {
>> + .check = android_check,
>> + .read_bootflow = android_read_bootflow,
>> + .read_file = android_read_file,
>> + .boot = android_boot,
>> +};
>> +
>> +static const struct udevice_id android_bootmeth_ids[] = {
>> + { .compatible = "u-boot,android" },
>> + { }
>> +};
>> +
>> +U_BOOT_DRIVER(bootmeth_android) = {
>> + .name = "bootmeth_android",
>> + .id = UCLASS_BOOTMETH,
>> + .of_match = android_bootmeth_ids,
>> + .ops = &android_bootmeth_ops,
>> + .bind = android_bootmeth_bind,
>> +};
>> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
>> new file mode 100644
>> index 000000000000..411c2f2d15e7
>> --- /dev/null
>> +++ b/boot/bootmeth_android.h
>> @@ -0,0 +1,27 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Bootmethod for Android
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +
>> +enum android_boot_mode {
>> + ANDROID_BOOT_MODE_NORMAL = 0,
>> +
>> + /* Android "recovery" is a special boot mode that uses another ramdisk.
>> + * It can be used to "factory reset" a board or to flash logical partitions
>> + * It operates in 2 modes: adb or fastbootd
>> + * To enter recovery from Android, we can do:
>> + * $ adb reboot recovery
>> + * $ adb reboot fastboot
>> + */
>> + ANDROID_BOOT_MODE_RECOVERY,
>> +
>> + /* Android "bootloader" is for accessing/reflashing physical partitions
>> + * Typically, this will launch a fastboot process in U-Boot.
>> + * To enter "bootloader" from Android, we can do:
>> + * $ adb reboot bootloader
>> + */
>> + ANDROID_BOOT_MODE_BOOTLOADER,
>> +};
>> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
>> index a07a72581e7a..709fa9e64ff3 100644
>> --- a/doc/develop/bootstd.rst
>> +++ b/doc/develop/bootstd.rst
>> @@ -95,6 +95,7 @@ bootflows.
>>
>> Note: it is possible to have a bootmeth that uses a partition or a whole device
>> directly, but it is more common to use a filesystem.
>> +For example, the Android bootmeth uses a whole device.
>>
>> Note that some bootmeths are 'global', meaning that they select the bootdev
>> themselves. Examples include VBE and EFI boot manager. In this case, they
>> @@ -277,6 +278,9 @@ script_offset_f
>> script_size_f
>> Size of the script to load, e.g. 0x2000
>>
>> +vendor_boot_comp_addr_r
>> + Address to which to load the vendor_boot Android image, e.g. 0xe0000000
>> +
>> Some variables are set by script bootmeth:
>>
>> devtype
>> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>> - EFI boot using bootefi from disk
>> - VBE
>> - EFI boot using boot manager
>> + - Android bootflow (boot image v4)
>>
>>
>> Command interface
>> @@ -786,6 +791,7 @@ To do
>> Some things that need to be done to completely replace the distro-boot scripts:
>>
>> - implement extensions (devicetree overlays with add-on boards)
>> +- implement legacy (boot image v2) android boot flow
>>
>> Other ideas:
>>
>>
>> --
>> 2.45.0
>>
>
> Some comments after a quick "scan". Will take a more detailed look a
> bit later today/tomorrow.
Thanks a bunch for your quick scan. You caught quite some issues
already.
I will implement these suggestions and will be awaiting the detailed
look before sending a v2.
> --
> Best regards - Atentamente - Meilleures salutations
>
> Igor Opaniuk
>
> mailto: igor.opaniuk@gmail.com
> skype: igor.opanyuk
> https://www.linkedin.com/in/iopaniuk
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/6] bootstd: Add a bootmeth for Android
2024-06-06 12:23 ` [PATCH 5/6] bootstd: Add a bootmeth for Android Mattijs Korpershoek
2024-06-10 15:15 ` Igor Opaniuk
@ 2024-06-11 18:52 ` Simon Glass
2024-06-12 10:18 ` Mattijs Korpershoek
1 sibling, 1 reply; 18+ messages in thread
From: Simon Glass @ 2024-06-11 18:52 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot
Hi Mattijs,
On Thu, 6 Jun 2024 at 06:24, Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> Android boot flow is a bit different than a regular Linux distro.
> Android relies on multiple partitions in order to boot.
>
> A typical boot flow would be:
> 1. Parse the Bootloader Control Block (BCB, misc partition)
> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
> 3. If BCB requested recovery or normal android, run the following:
> 3.a. Get slot (A/B) from BCB
> 3.b. Run AVB (Android Verified Boot) on boot partitions
> 3.c. Load boot and vendor_boot partitions
> 3.d. Load device-tree, ramdisk and boot
>
> The AOSP documentation has more details at [1], [2], [3]
>
> This has been implemented via complex boot scripts such as [4].
> However, these boot script are neither very maintainable nor generic.
> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>
> Add a generic Android bootflow implementation for bootstd.
> For this initial version, only boot image v4 is supported.
>
> [1] https://source.android.com/docs/core/architecture/bootloader
> [2] https://source.android.com/docs/core/architecture/partitions
> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> MAINTAINERS | 7 +
> boot/Kconfig | 14 ++
> boot/Makefile | 2 +
> boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
> boot/bootmeth_android.h | 27 +++
> doc/develop/bootstd.rst | 6 +
> 6 files changed, 578 insertions(+)
Reviewed-by: Simon Glass <sjg@chromium.org>
nits below
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 66783d636e3d..6d2b87720565 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -939,6 +939,13 @@ F: include/bootstd.h
> F: net/eth_bootdevice.c
> F: test/boot/
>
> +BOOTMETH_ANDROID
> +M: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> +S: Maintained
> +T: git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
> +F: boot/bootmeth_android.c
> +F: boot/bootmeth_android.h
> +
> BTRFS
> M: Marek Behún <kabel@kernel.org>
> R: Qu Wenruo <wqu@suse.com>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 6f3096c15a6f..5fa6f3b8315d 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
> EFI bootmgr, since they take full control over which bootdevs are
> selected to boot.
>
> +config BOOTMETH_ANDROID
> + bool "Bootdev support for Android"
> + depends on X86 || ARM || SANDBOX
> + select ANDROID_AB
> + select ANDROID_BOOT_IMAGE
> + select CMD_BCB
> + select PARTITION_TYPE_GUID
> + select PARTITION_UUIDS
> + help
> + Enables support for booting Android using bootdevs. Android requires
using standard boot (or using bootstd).
> + multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
> +
> + Note that only MMC bootdevs are supported at present.
Why is that limitation present? Can you please mention what is needed
to remove it?
> +
> config BOOTMETH_CROS
> bool "Bootdev support for Chromium OS"
> depends on X86 || ARM || SANDBOX
> diff --git a/boot/Makefile b/boot/Makefile
> index 84ccfeaecec4..75d1cd46fabf 100644
> --- a/boot/Makefile
> +++ b/boot/Makefile
> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
> +
> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
> new file mode 100644
> index 000000000000..26d548d2fd6e
> --- /dev/null
> +++ b/boot/bootmeth_android.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Bootmethod for Android
This is my fault, but I think we should settle on Bootmeth throughout.
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +#define LOG_CATEGORY UCLASS_BOOTSTD
> +
> +#include <android_ab.h>
> +#include <android_image.h>
> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
> +#include <avb_verify.h>
Can you include that header always?
> +#endif
> +#include <bcb.h>
> +#include <blk.h>
> +#include <bootflow.h>
> +#include <bootm.h>
> +#include <bootmeth.h>
> +#include <dm.h>
> +#include <image.h>
> +#include <malloc.h>
> +#include <mapmem.h>
> +#include <part.h>
> +#include "bootmeth_android.h"
> +
> +#define BCB_FIELD_COMMAND_SZ 32
> +#define BCB_PART_NAME "misc"
> +#define BOOT_PART_NAME "boot"
> +#define VENDOR_BOOT_PART_NAME "vendor_boot"
> +
> +/**
> + * struct android_priv - Private data
> + *
> + * This is read from the disk and recorded for use when the full Android
> + * kernel must be loaded and booted
please add member comments as I don't know what these fields are
> + */
> +struct android_priv {
> + int boot_mode;
> + char slot[2];
> + u32 header_version;
> +};
> +
> +static int android_check(struct udevice *dev, struct bootflow_iter *iter)
> +{
> + /* This only works on mmc devices */
> + if (bootflow_iter_check_mmc(iter))
> + return log_msg_ret("mmc", -ENOTSUPP);
> +
> + /* This only works on whole devices, as multiple
> + * partitions are needed to boot Android
> + */
> + if (iter->part != 0)
> + return log_msg_ret("mmc part", -ENOTSUPP);
> +
> + return 0;
> +}
> +
> +static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(blk);
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + ulong num_blks, bufsz;
> + char *buf;
> + int ret;
> +
> + sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part info", ret);
> +
> + num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
> + bufsz = num_blks * desc->blksz;
> + buf = malloc(bufsz);
> + if (!buf)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + ret = blk_read(blk, partition.start, num_blks, buf);
> + if (ret != num_blks) {
> + free(buf);
> + return log_msg_ret("part read", -EIO);
> + }
> +
> + if (!is_android_boot_image_header(buf)) {
> + free(buf);
> + return log_msg_ret("header", -ENOENT);
> + }
> +
> + priv->header_version = android_image_get_version(buf);
> +
> + return 0;
> +}
> +
> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(blk);
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + ulong num_blks, bufsz;
> + char *buf;
> + int ret;
> +
> + sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part info", ret);
> +
> + num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
> + bufsz = num_blks * desc->blksz;
> + buf = malloc(bufsz);
> + if (!buf)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + ret = blk_read(blk, partition.start, num_blks, buf);
> + if (ret != num_blks) {
> + free(buf);
> + return log_msg_ret("part read", -EIO);
> + }
> +
> + if (!is_android_vendor_boot_image_header(buf)) {
> + free(buf);
> + return log_msg_ret("header", -ENOENT);
> + }
> +
> + return 0;
> +}
> +
> +static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + struct disk_partition misc;
> + char slot_suffix[3];
> + int ret;
> +
> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + ret = ab_select_slot(desc, &misc, decrement);
> + if (ret < 0)
> + return log_msg_ret("slot", ret);
> +
> + priv->slot[0] = BOOT_SLOT_NAME(ret);
> + priv->slot[1] = '\0';
> +
> + sprintf(slot_suffix, "_%s", priv->slot);
> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
> + slot_suffix, false);
> + if (ret < 0)
> + return log_msg_ret("slot", ret);
perhaps "cmdl" ?
> +
> + return 0;
> +}
> +
> +static int configure_serialno(struct bootflow *bflow)
> +{
> + char *serialno = env_get("serial#");
> +
> + if (!serialno)
> + return log_msg_ret("serial", -ENOENT);
> +
> + return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
> +}
> +
> +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct disk_partition misc;
> + struct android_priv *priv;
> + char command[BCB_FIELD_COMMAND_SZ];
> + int ret;
> +
> + bflow->state = BOOTFLOWST_MEDIA;
> +
> + /* bcb_find_partition_and_load() will print errors to stdout
I believe this first line should just be /*
> + * if BCB_PART_NAME is not found. To avoid that, check if the
> + * partition exists first.
> + */
> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
> + if (ret < 0)
> + return log_msg_ret("bcb load", ret);
> +
> + ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
> + if (ret < 0)
> + return log_msg_ret("bcb read", ret);
> +
> + priv = malloc(sizeof(struct android_priv));
> + if (!priv)
> + return log_msg_ret("buf", -ENOMEM);
> +
> + bflow->bootmeth_priv = priv;
ideally this would happen at the end of the function
> + if (!strcmp("bootonce-bootloader", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
> + bflow->os_name = strdup("Android (bootloader)");
> + } else if (!strcmp("boot-fastboot", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
> + bflow->os_name = strdup("Android (fastbootd)");
> + } else if (!strcmp("boot-recovery", command)) {
> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
> + bflow->os_name = strdup("Android (recovery)");
> + } else {
> + priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
> + bflow->os_name = strdup("Android");
> + }
> + if (!bflow->os_name)
> + return log_msg_ret("os", -ENOMEM);
> +
> + if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
> + /* Clear BCB */
> + memset(command, 0, sizeof(command));
> + ret = bcb_set(BCB_FIELD_COMMAND, command);
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("bcb set", ret);
> + }
> + ret = bcb_store();
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("bcb store", ret);
> + }
> +
> + bflow->state = BOOTFLOWST_READY;
> + return 0;
> + }
> +
> + /* For recovery and normal boot, we need to scan the partitions */
> + ret = android_read_slot_from_bcb(bflow, false);
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("read slot", ret);
> + }
> +
> + ret = scan_boot_part(bflow->blk, priv);
> + if (ret < 0) {
> + printf("- scan boot failed: err=%d\n", ret);
> + free(priv);
> + return log_msg_ret("scan boot", ret);
> + }
> +
> + if (priv->header_version != 4) {
> + printf("- Only boot.img v4 is supported\n");
shouldn't this be log_debug() ?
> + free(priv);
> + return log_msg_ret("version", -EINVAL);
> + }
> +
> + ret = scan_vendor_boot_part(bflow->blk, priv->slot);
> + if (ret < 0) {
> + printf("- scan vendor_boot failed: err=%d\n", ret);
> + free(priv);
> + return log_msg_ret("scan vendor_boot", ret);
> + }
> +
> + /* Ignoring return code: setting serial number is not mandatory for booting */
> + configure_serialno(bflow);
> +
> + if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
check line length
> + if (ret < 0) {
> + free(priv);
> + return log_msg_ret("normal_boot", ret);
> + }
> + }
> +
> + bflow->state = BOOTFLOWST_READY;
> +
> + return 0;
> +}
> +
> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
> + const char *file_path, ulong addr, ulong *sizep)
> +{
> + /* Reading individual files is not supported since we only
> + * operate on whole mmc devices (because we require multiple partitions)
> + */
> + return log_msg_ret("Unsupported", -ENOSYS);
> +}
> +
> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
> + const char slot[2], ulong addr)
What is a slotted partition? This function could use a comment
> +{
> + struct disk_partition partition;
> + char partname[PART_NAME_LEN];
> + int ret;
> + u32 n;
> +
> + /* Ensure name fits in partname it should be: <name>_<slot>\0 */
> + if (strlen(name) > (PART_NAME_LEN - 2 - 1))
> + return log_msg_ret("name too long", -EINVAL);
> +
> + sprintf(partname, "%s_%s", name, slot);
> + ret = part_get_info_by_name(desc, partname, &partition);
> + if (ret < 0)
> + return log_msg_ret("part", ret);
> +
> + n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
> + if (n < partition.size)
> + return log_msg_ret("part read", -EIO);
> +
> + return 0;
> +}
> +
> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
Can you use if() instead, and either rename the functions below or add
this if() to each function?
> +static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
> +{
> + char *key = strsep(&arg, "=");
> + char *value = arg;
> + int ret;
> +
> + ret = bootflow_cmdline_set_arg(bflow, key, value, false);
> + if (ret < 0)
> + return log_msg_ret("avb cmdline", ret);
> +
> + return 0;
> +}
> +
> +static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
> +{
> + char *arg = strsep(&cmdline, " ");
> + int ret;
> +
> + while (arg) {
> + ret = avb_append_commandline_arg(bflow, arg);
> + if (ret < 0)
> + return ret;
> +
> + arg = strsep(&cmdline, " ");
> + }
> +
> + return 0;
> +}
> +
> +static int run_avb_verification(struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + const char * const requested_partitions[] = {"boot", "vendor_boot"};
> + struct AvbOps *avb_ops;
> + AvbSlotVerifyResult result;
> + AvbSlotVerifyData *out_data;
> + enum avb_boot_state boot_state;
> + char *extra_args;
> + char slot_suffix[3];
> + bool unlocked = false;
> + int ret;
> +
> + avb_ops = avb_ops_alloc(desc->devnum);
> + if (!avb_ops)
> + return log_msg_ret("avb ops", -ENOMEM);
> +
> + sprintf(slot_suffix, "_%s", priv->slot);
> +
> + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
> + if (ret != AVB_IO_RESULT_OK)
> + return log_msg_ret("avb lock", -EIO);
> +
> + result = avb_slot_verify(avb_ops,
> + requested_partitions,
> + slot_suffix,
> + unlocked,
> + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
> + &out_data);
> +
> + if (result != AVB_SLOT_VERIFY_RESULT_OK) {
> + printf("Verification failed, reason: %s\n",
> + str_avb_slot_error(result));
> + return log_msg_ret("avb verify", -EIO);
> + }
> +
> + if (unlocked)
> + boot_state = AVB_ORANGE;
> + else
> + boot_state = AVB_GREEN;
> +
> + extra_args = avb_set_state(avb_ops, boot_state);
> + if (extra_args) {
> + ret = avb_append_commandline_arg(bflow, extra_args);
This function will write to extra_args...is that OK?
> + if (ret < 0)
> + goto free_out_data;
> + }
> +
> + ret = avb_append_commandline(bflow, out_data->cmdline);
> + if (ret < 0)
> + goto free_out_data;
> +
> + return 0;
> +
> + free_out_data:
> + if (out_data)
> + avb_slot_verify_data_free(out_data);
> +
> + return log_msg_ret("avb cmdline", ret);
> +}
> +#else
> +static int run_avb_verification(struct bootflow *bflow)
> +{
> + int ret;
> +
> + /* When AVB is unsupported, pass ORANGE state */
> + ret = bootflow_cmdline_set_arg(bflow,
> + "androidboot.verifiedbootstate",
> + "orange", false);
> + if (ret < 0)
> + return log_msg_ret("avb cmdline", ret);
> +
> + return 0;
> +}
> +#endif /* AVB_VERIFY */
> +
> +static int boot_android_normal(struct bootflow *bflow)
> +{
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct android_priv *priv = bflow->bootmeth_priv;
> + int ret;
> +
drop blank line
> + ulong loadaddr = env_get_hex("loadaddr", 0);
> + ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
> +
> + ret = run_avb_verification(bflow);
> + if (ret < 0)
> + return log_msg_ret("avb", ret);
> +
> + /* Read slot once more to decrement counter from BCB */
> + ret = android_read_slot_from_bcb(bflow, true);
> + if (ret < 0)
> + return log_msg_ret("read slot", ret);
> +
> + ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
> + if (ret < 0)
> + return log_msg_ret("read boot", ret);
> +
> + ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
> + if (ret < 0)
> + return log_msg_ret("read vendor_boot", ret);
> +
> + set_abootimg_addr(loadaddr);
> + set_avendor_bootimg_addr(vloadaddr);
> +
> + ret = bootm_boot_start(loadaddr, bflow->cmdline);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int boot_android_recovery(struct bootflow *bflow)
> +{
> + int ret;
> +
> + ret = boot_android_normal(bflow);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int boot_android_bootloader(struct bootflow *bflow)
> +{
> + int ret;
> +
> + ret = run_command("fastboot usb 0", 0);
> + do_reset(NULL, 0, 0, NULL);
> +
> + return log_msg_ret("boot", ret);
> +}
> +
> +static int android_boot(struct udevice *dev, struct bootflow *bflow)
> +{
> + struct android_priv *priv = bflow->bootmeth_priv;
> + int ret;
> +
> + switch (priv->boot_mode) {
> + case ANDROID_BOOT_MODE_NORMAL:
> + ret = boot_android_normal(bflow);
> + break;
> + case ANDROID_BOOT_MODE_RECOVERY:
> + ret = boot_android_recovery(bflow);
> + break;
> + case ANDROID_BOOT_MODE_BOOTLOADER:
> + ret = boot_android_bootloader(bflow);
> + break;
> + default:
> + printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
> + priv->boot_mode);
> + boot_android_bootloader(bflow);
> + /* Tell we failed to boot since boot mode is unknown */
> + ret = -EFAULT;
> + }
> +
> + return ret;
> +}
> +
> +static int android_bootmeth_bind(struct udevice *dev)
> +{
> + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
> +
> + plat->desc = "Android boot";
> + plat->flags = BOOTMETHF_ANY_PART;
> +
> + return 0;
> +}
> +
> +static struct bootmeth_ops android_bootmeth_ops = {
> + .check = android_check,
> + .read_bootflow = android_read_bootflow,
> + .read_file = android_read_file,
> + .boot = android_boot,
> +};
> +
> +static const struct udevice_id android_bootmeth_ids[] = {
> + { .compatible = "u-boot,android" },
> + { }
> +};
> +
> +U_BOOT_DRIVER(bootmeth_android) = {
> + .name = "bootmeth_android",
> + .id = UCLASS_BOOTMETH,
> + .of_match = android_bootmeth_ids,
> + .ops = &android_bootmeth_ops,
> + .bind = android_bootmeth_bind,
> +};
> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
> new file mode 100644
> index 000000000000..411c2f2d15e7
> --- /dev/null
> +++ b/boot/bootmeth_android.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Bootmethod for Android
> + *
> + * Copyright (C) 2024 BayLibre, SAS
> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
> + */
> +
> +enum android_boot_mode {
> + ANDROID_BOOT_MODE_NORMAL = 0,
> +
> + /* Android "recovery" is a special boot mode that uses another ramdisk.
> + * It can be used to "factory reset" a board or to flash logical partitions
> + * It operates in 2 modes: adb or fastbootd
> + * To enter recovery from Android, we can do:
> + * $ adb reboot recovery
> + * $ adb reboot fastboot
> + */
> + ANDROID_BOOT_MODE_RECOVERY,
> +
> + /* Android "bootloader" is for accessing/reflashing physical partitions
> + * Typically, this will launch a fastboot process in U-Boot.
> + * To enter "bootloader" from Android, we can do:
> + * $ adb reboot bootloader
> + */
> + ANDROID_BOOT_MODE_BOOTLOADER,
> +};
> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
> index a07a72581e7a..709fa9e64ff3 100644
> --- a/doc/develop/bootstd.rst
> +++ b/doc/develop/bootstd.rst
> @@ -95,6 +95,7 @@ bootflows.
>
> Note: it is possible to have a bootmeth that uses a partition or a whole device
> directly, but it is more common to use a filesystem.
> +For example, the Android bootmeth uses a whole device.
>
> Note that some bootmeths are 'global', meaning that they select the bootdev
> themselves. Examples include VBE and EFI boot manager. In this case, they
> @@ -277,6 +278,9 @@ script_offset_f
> script_size_f
> Size of the script to load, e.g. 0x2000
>
> +vendor_boot_comp_addr_r
> + Address to which to load the vendor_boot Android image, e.g. 0xe0000000
> +
> Some variables are set by script bootmeth:
>
> devtype
> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
> - EFI boot using bootefi from disk
> - VBE
> - EFI boot using boot manager
> + - Android bootflow (boot image v4)
>
>
> Command interface
> @@ -786,6 +791,7 @@ To do
> Some things that need to be done to completely replace the distro-boot scripts:
>
> - implement extensions (devicetree overlays with add-on boards)
> +- implement legacy (boot image v2) android boot flow
Nice!
>
> Other ideas:
>
>
> --
> 2.45.0
>
Regards,
Simon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 6/6] bootstd: Add test for bootmeth_android
2024-06-06 12:23 ` [PATCH 6/6] bootstd: Add test for bootmeth_android Mattijs Korpershoek
@ 2024-06-11 18:52 ` Simon Glass
0 siblings, 0 replies; 18+ messages in thread
From: Simon Glass @ 2024-06-11 18:52 UTC (permalink / raw)
To: Mattijs Korpershoek
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot
On Thu, 6 Jun 2024 at 06:24, Mattijs Korpershoek
<mkorpershoek@baylibre.com> wrote:
>
> 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 # to build the mmc7.img
> $ ./test/py/test.py --bd sandbox --build -k bootflow_android
>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> ---
> arch/sandbox/dts/test.dts | 8 +++++
> configs/sandbox_defconfig | 2 +-
> test/boot/bootflow.c | 65 ++++++++++++++++++++++++++++++++++++++--
> test/py/tests/test_ut.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 147 insertions(+), 4 deletions(-)
>
Reviewed-by: Simon Glass <sjg@chromium.org>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/6] bootstd: Add a bootmeth for Android
2024-06-11 18:52 ` Simon Glass
@ 2024-06-12 10:18 ` Mattijs Korpershoek
0 siblings, 0 replies; 18+ messages in thread
From: Mattijs Korpershoek @ 2024-06-12 10:18 UTC (permalink / raw)
To: Simon Glass
Cc: Julien Masson, Guillaume La Roque, Dmitrii Merkurev,
Roman Stratiienko, Igor Opaniuk, u-boot
Hi Simon,
Thank you for your review.
On mar., juin 11, 2024 at 12:52, Simon Glass <sjg@chromium.org> wrote:
> Hi Mattijs,
>
> On Thu, 6 Jun 2024 at 06:24, Mattijs Korpershoek
> <mkorpershoek@baylibre.com> wrote:
>>
>> Android boot flow is a bit different than a regular Linux distro.
>> Android relies on multiple partitions in order to boot.
>>
>> A typical boot flow would be:
>> 1. Parse the Bootloader Control Block (BCB, misc partition)
>> 2. If BCB requested bootonce-bootloader, start fastboot and wait.
>> 3. If BCB requested recovery or normal android, run the following:
>> 3.a. Get slot (A/B) from BCB
>> 3.b. Run AVB (Android Verified Boot) on boot partitions
>> 3.c. Load boot and vendor_boot partitions
>> 3.d. Load device-tree, ramdisk and boot
>>
>> The AOSP documentation has more details at [1], [2], [3]
>>
>> This has been implemented via complex boot scripts such as [4].
>> However, these boot script are neither very maintainable nor generic.
>> Moreover, DISTRO_DEFAULTS is being deprecated [5].
>>
>> Add a generic Android bootflow implementation for bootstd.
>> For this initial version, only boot image v4 is supported.
>>
>> [1] https://source.android.com/docs/core/architecture/bootloader
>> [2] https://source.android.com/docs/core/architecture/partitions
>> [3] https://source.android.com/docs/core/architecture/partitions/generic-boot
>> [4] https://source.denx.de/u-boot/u-boot/-/blob/master/include/configs/meson64_android.h
>> [5] https://lore.kernel.org/r/all/20230914165615.1058529-17-sjg@chromium.org/
>>
>> Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> ---
>> MAINTAINERS | 7 +
>> boot/Kconfig | 14 ++
>> boot/Makefile | 2 +
>> boot/bootmeth_android.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++
>> boot/bootmeth_android.h | 27 +++
>> doc/develop/bootstd.rst | 6 +
>> 6 files changed, 578 insertions(+)
>
> Reviewed-by: Simon Glass <sjg@chromium.org>
>
> nits below
>
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 66783d636e3d..6d2b87720565 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -939,6 +939,13 @@ F: include/bootstd.h
>> F: net/eth_bootdevice.c
>> F: test/boot/
>>
>> +BOOTMETH_ANDROID
>> +M: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> +S: Maintained
>> +T: git https://source.denx.de/u-boot/custodians/u-boot-dfu.git
>> +F: boot/bootmeth_android.c
>> +F: boot/bootmeth_android.h
>> +
>> BTRFS
>> M: Marek Behún <kabel@kernel.org>
>> R: Qu Wenruo <wqu@suse.com>
>> diff --git a/boot/Kconfig b/boot/Kconfig
>> index 6f3096c15a6f..5fa6f3b8315d 100644
>> --- a/boot/Kconfig
>> +++ b/boot/Kconfig
>> @@ -494,6 +494,20 @@ config BOOTMETH_GLOBAL
>> EFI bootmgr, since they take full control over which bootdevs are
>> selected to boot.
>>
>> +config BOOTMETH_ANDROID
>> + bool "Bootdev support for Android"
>> + depends on X86 || ARM || SANDBOX
>> + select ANDROID_AB
>> + select ANDROID_BOOT_IMAGE
>> + select CMD_BCB
>> + select PARTITION_TYPE_GUID
>> + select PARTITION_UUIDS
>> + help
>> + Enables support for booting Android using bootdevs. Android requires
>
> using standard boot (or using bootstd).
Will do for v2.
>
>> + multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
>> +
>> + Note that only MMC bootdevs are supported at present.
>
> Why is that limitation present? Can you please mention what is needed
> to remove it?
Mainly because we use AVB and AVB is hard-coded for MMC. Alistair
submitted changes to convert to generic block devices here:
https://lore.kernel.org/all/20220926220211.868968-1-adelva@google.com/
There were some review comments but I did not see any v2 on the list.
I will add a comment in the KConfig description.
>
>> +
>> config BOOTMETH_CROS
>> bool "Bootdev support for Chromium OS"
>> depends on X86 || ARM || SANDBOX
>> diff --git a/boot/Makefile b/boot/Makefile
>> index 84ccfeaecec4..75d1cd46fabf 100644
>> --- a/boot/Makefile
>> +++ b/boot/Makefile
>> @@ -66,3 +66,5 @@ obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
>> obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
>> +
>> +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_ANDROID) += bootmeth_android.o
>> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
>> new file mode 100644
>> index 000000000000..26d548d2fd6e
>> --- /dev/null
>> +++ b/boot/bootmeth_android.c
>> @@ -0,0 +1,522 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Bootmethod for Android
>
> This is my fault, but I think we should settle on Bootmeth throughout.
Yes, thank you for noticing. Will do for v2.
>
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +#define LOG_CATEGORY UCLASS_BOOTSTD
>> +
>> +#include <android_ab.h>
>> +#include <android_image.h>
>> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
>> +#include <avb_verify.h>
>
> Can you include that header always?
I can't, it does not link if I do.
It would be possible if we implemented stub functions but that's not the
case today.
Since this is a nitpick, I will consider this for a future improvement.
>
>> +#endif
>> +#include <bcb.h>
>> +#include <blk.h>
>> +#include <bootflow.h>
>> +#include <bootm.h>
>> +#include <bootmeth.h>
>> +#include <dm.h>
>> +#include <image.h>
>> +#include <malloc.h>
>> +#include <mapmem.h>
>> +#include <part.h>
>> +#include "bootmeth_android.h"
>> +
>> +#define BCB_FIELD_COMMAND_SZ 32
>> +#define BCB_PART_NAME "misc"
>> +#define BOOT_PART_NAME "boot"
>> +#define VENDOR_BOOT_PART_NAME "vendor_boot"
>> +
>> +/**
>> + * struct android_priv - Private data
>> + *
>> + * This is read from the disk and recorded for use when the full Android
>> + * kernel must be loaded and booted
>
> please add member comments as I don't know what these fields are
Will do for v2.
>
>> + */
>> +struct android_priv {
>> + int boot_mode;
>> + char slot[2];
>> + u32 header_version;
>> +};
>> +
>> +static int android_check(struct udevice *dev, struct bootflow_iter *iter)
>> +{
>> + /* This only works on mmc devices */
>> + if (bootflow_iter_check_mmc(iter))
>> + return log_msg_ret("mmc", -ENOTSUPP);
>> +
>> + /* This only works on whole devices, as multiple
>> + * partitions are needed to boot Android
>> + */
>> + if (iter->part != 0)
>> + return log_msg_ret("mmc part", -ENOTSUPP);
>> +
>> + return 0;
>> +}
>> +
>> +static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(blk);
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + ulong num_blks, bufsz;
>> + char *buf;
>> + int ret;
>> +
>> + sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part info", ret);
>> +
>> + num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
>> + bufsz = num_blks * desc->blksz;
>> + buf = malloc(bufsz);
>> + if (!buf)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + ret = blk_read(blk, partition.start, num_blks, buf);
>> + if (ret != num_blks) {
>> + free(buf);
>> + return log_msg_ret("part read", -EIO);
>> + }
>> +
>> + if (!is_android_boot_image_header(buf)) {
>> + free(buf);
>> + return log_msg_ret("header", -ENOENT);
>> + }
>> +
>> + priv->header_version = android_image_get_version(buf);
>> +
>> + return 0;
>> +}
>> +
>> +static int scan_vendor_boot_part(struct udevice *blk, const char slot[2])
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(blk);
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + ulong num_blks, bufsz;
>> + char *buf;
>> + int ret;
>> +
>> + sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part info", ret);
>> +
>> + num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
>> + bufsz = num_blks * desc->blksz;
>> + buf = malloc(bufsz);
>> + if (!buf)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + ret = blk_read(blk, partition.start, num_blks, buf);
>> + if (ret != num_blks) {
>> + free(buf);
>> + return log_msg_ret("part read", -EIO);
>> + }
>> +
>> + if (!is_android_vendor_boot_image_header(buf)) {
>> + free(buf);
>> + return log_msg_ret("header", -ENOENT);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + struct disk_partition misc;
>> + char slot_suffix[3];
>> + int ret;
>> +
>> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + ret = ab_select_slot(desc, &misc, decrement);
>> + if (ret < 0)
>> + return log_msg_ret("slot", ret);
>> +
>> + priv->slot[0] = BOOT_SLOT_NAME(ret);
>> + priv->slot[1] = '\0';
>> +
>> + sprintf(slot_suffix, "_%s", priv->slot);
>> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
>> + slot_suffix, false);
>> + if (ret < 0)
>> + return log_msg_ret("slot", ret);
>
> perhaps "cmdl" ?
Yes, will do for v2.
>
>> +
>> + return 0;
>> +}
>> +
>> +static int configure_serialno(struct bootflow *bflow)
>> +{
>> + char *serialno = env_get("serial#");
>> +
>> + if (!serialno)
>> + return log_msg_ret("serial", -ENOENT);
>> +
>> + return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
>> +}
>> +
>> +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct disk_partition misc;
>> + struct android_priv *priv;
>> + char command[BCB_FIELD_COMMAND_SZ];
>> + int ret;
>> +
>> + bflow->state = BOOTFLOWST_MEDIA;
>> +
>> + /* bcb_find_partition_and_load() will print errors to stdout
>
> I believe this first line should just be /*
You are right. I've made this mistake in other parts of the file.
Will fix all occurences for v2.
>
>> + * if BCB_PART_NAME is not found. To avoid that, check if the
>> + * partition exists first.
>> + */
>> + ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
>> + if (ret < 0)
>> + return log_msg_ret("bcb load", ret);
>> +
>> + ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
>> + if (ret < 0)
>> + return log_msg_ret("bcb read", ret);
>> +
>> + priv = malloc(sizeof(struct android_priv));
>> + if (!priv)
>> + return log_msg_ret("buf", -ENOMEM);
>> +
>> + bflow->bootmeth_priv = priv;
>
> ideally this would happen at the end of the function
Yes, Igor pointed out as well. It's dangerous to have a dangling
pointer.
Will do for v2.
>
>> + if (!strcmp("bootonce-bootloader", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
>> + bflow->os_name = strdup("Android (bootloader)");
>> + } else if (!strcmp("boot-fastboot", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
>> + bflow->os_name = strdup("Android (fastbootd)");
>> + } else if (!strcmp("boot-recovery", command)) {
>> + priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
>> + bflow->os_name = strdup("Android (recovery)");
>> + } else {
>> + priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
>> + bflow->os_name = strdup("Android");
>> + }
>> + if (!bflow->os_name)
>> + return log_msg_ret("os", -ENOMEM);
>> +
>> + if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
>> + /* Clear BCB */
>> + memset(command, 0, sizeof(command));
>> + ret = bcb_set(BCB_FIELD_COMMAND, command);
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("bcb set", ret);
>> + }
>> + ret = bcb_store();
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("bcb store", ret);
>> + }
>> +
>> + bflow->state = BOOTFLOWST_READY;
>> + return 0;
>> + }
>> +
>> + /* For recovery and normal boot, we need to scan the partitions */
>> + ret = android_read_slot_from_bcb(bflow, false);
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("read slot", ret);
>> + }
>> +
>> + ret = scan_boot_part(bflow->blk, priv);
>> + if (ret < 0) {
>> + printf("- scan boot failed: err=%d\n", ret);
>> + free(priv);
>> + return log_msg_ret("scan boot", ret);
>> + }
>> +
>> + if (priv->header_version != 4) {
>> + printf("- Only boot.img v4 is supported\n");
>
> shouldn't this be log_debug() ?
Hmm, I've modeled this to be similar to bootmeth_cros, but I can convert
to log_debug(). I don't have a strong opinion on this.
>
>> + free(priv);
>> + return log_msg_ret("version", -EINVAL);
>> + }
>> +
>> + ret = scan_vendor_boot_part(bflow->blk, priv->slot);
>> + if (ret < 0) {
>> + printf("- scan vendor_boot failed: err=%d\n", ret);
>> + free(priv);
>> + return log_msg_ret("scan vendor_boot", ret);
>> + }
>> +
>> + /* Ignoring return code: setting serial number is not mandatory for booting */
>> + configure_serialno(bflow);
>> +
>> + if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
>> + ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot", "1", false);
>
> check line length
Yes, somehow I missed this with checkpatch.
Will fix for v2.
>
>> + if (ret < 0) {
>> + free(priv);
>> + return log_msg_ret("normal_boot", ret);
>> + }
>> + }
>> +
>> + bflow->state = BOOTFLOWST_READY;
>> +
>> + return 0;
>> +}
>> +
>> +static int android_read_file(struct udevice *dev, struct bootflow *bflow,
>> + const char *file_path, ulong addr, ulong *sizep)
>> +{
>> + /* Reading individual files is not supported since we only
>> + * operate on whole mmc devices (because we require multiple partitions)
>> + */
>> + return log_msg_ret("Unsupported", -ENOSYS);
>> +}
>> +
>> +static int read_slotted_partition(struct blk_desc *desc, const char *const name,
>> + const char slot[2], ulong addr)
>
> What is a slotted partition? This function could use a comment
Most modern Android devices use "Seamless updates", where each partition
is duplicated. For example, the boot partition has boot_a and boot_b.
This is what I called "slotted partition".
See:
https://source.android.com/docs/core/ota/ab
https://source.android.com/docs/core/ota/ab/ab_implement
I will add a comment pointing to the above documentation and some
additional explanation in v2.
>
>> +{
>> + struct disk_partition partition;
>> + char partname[PART_NAME_LEN];
>> + int ret;
>> + u32 n;
>> +
>> + /* Ensure name fits in partname it should be: <name>_<slot>\0 */
>> + if (strlen(name) > (PART_NAME_LEN - 2 - 1))
>> + return log_msg_ret("name too long", -EINVAL);
>> +
>> + sprintf(partname, "%s_%s", name, slot);
>> + ret = part_get_info_by_name(desc, partname, &partition);
>> + if (ret < 0)
>> + return log_msg_ret("part", ret);
>> +
>> + n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
>> + if (n < partition.size)
>> + return log_msg_ret("part read", -EIO);
>> +
>> + return 0;
>> +}
>> +
>> +#if CONFIG_IS_ENABLED(AVB_VERIFY)
>
> Can you use if() instead, and either rename the functions below or add
> this if() to each function?
I will try, but most avb_ops related functions are not stubbed so I'm
not sure we can do this easily.
Since this is a nitpick, I might consider this a future improvement (If
stubs are needed, for example).
>
>> +static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
>> +{
>> + char *key = strsep(&arg, "=");
>> + char *value = arg;
>> + int ret;
>> +
>> + ret = bootflow_cmdline_set_arg(bflow, key, value, false);
>> + if (ret < 0)
>> + return log_msg_ret("avb cmdline", ret);
>> +
>> + return 0;
>> +}
>> +
>> +static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
>> +{
>> + char *arg = strsep(&cmdline, " ");
>> + int ret;
>> +
>> + while (arg) {
>> + ret = avb_append_commandline_arg(bflow, arg);
>> + if (ret < 0)
>> + return ret;
>> +
>> + arg = strsep(&cmdline, " ");
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int run_avb_verification(struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + const char * const requested_partitions[] = {"boot", "vendor_boot"};
>> + struct AvbOps *avb_ops;
>> + AvbSlotVerifyResult result;
>> + AvbSlotVerifyData *out_data;
>> + enum avb_boot_state boot_state;
>> + char *extra_args;
>> + char slot_suffix[3];
>> + bool unlocked = false;
>> + int ret;
>> +
>> + avb_ops = avb_ops_alloc(desc->devnum);
>> + if (!avb_ops)
>> + return log_msg_ret("avb ops", -ENOMEM);
>> +
>> + sprintf(slot_suffix, "_%s", priv->slot);
>> +
>> + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
>> + if (ret != AVB_IO_RESULT_OK)
>> + return log_msg_ret("avb lock", -EIO);
>> +
>> + result = avb_slot_verify(avb_ops,
>> + requested_partitions,
>> + slot_suffix,
>> + unlocked,
>> + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
>> + &out_data);
>> +
>> + if (result != AVB_SLOT_VERIFY_RESULT_OK) {
>> + printf("Verification failed, reason: %s\n",
>> + str_avb_slot_error(result));
>> + return log_msg_ret("avb verify", -EIO);
>> + }
>> +
>> + if (unlocked)
>> + boot_state = AVB_ORANGE;
>> + else
>> + boot_state = AVB_GREEN;
>> +
>> + extra_args = avb_set_state(avb_ops, boot_state);
>> + if (extra_args) {
>> + ret = avb_append_commandline_arg(bflow, extra_args);
>
> This function will write to extra_args...is that OK?
Yes, extra_args is no longer used after this.
I will add a comment to state that this is safe in v2.
>
>> + if (ret < 0)
>> + goto free_out_data;
>> + }
>> +
>> + ret = avb_append_commandline(bflow, out_data->cmdline);
>> + if (ret < 0)
>> + goto free_out_data;
>> +
>> + return 0;
>> +
>> + free_out_data:
>> + if (out_data)
>> + avb_slot_verify_data_free(out_data);
>> +
>> + return log_msg_ret("avb cmdline", ret);
>> +}
>> +#else
>> +static int run_avb_verification(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + /* When AVB is unsupported, pass ORANGE state */
>> + ret = bootflow_cmdline_set_arg(bflow,
>> + "androidboot.verifiedbootstate",
>> + "orange", false);
>> + if (ret < 0)
>> + return log_msg_ret("avb cmdline", ret);
>> +
>> + return 0;
>> +}
>> +#endif /* AVB_VERIFY */
>> +
>> +static int boot_android_normal(struct bootflow *bflow)
>> +{
>> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + int ret;
>> +
>
> drop blank line
Will do for v2.
>
>> + ulong loadaddr = env_get_hex("loadaddr", 0);
>> + ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
>> +
>> + ret = run_avb_verification(bflow);
>> + if (ret < 0)
>> + return log_msg_ret("avb", ret);
>> +
>> + /* Read slot once more to decrement counter from BCB */
>> + ret = android_read_slot_from_bcb(bflow, true);
>> + if (ret < 0)
>> + return log_msg_ret("read slot", ret);
>> +
>> + ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
>> + if (ret < 0)
>> + return log_msg_ret("read boot", ret);
>> +
>> + ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
>> + if (ret < 0)
>> + return log_msg_ret("read vendor_boot", ret);
>> +
>> + set_abootimg_addr(loadaddr);
>> + set_avendor_bootimg_addr(vloadaddr);
>> +
>> + ret = bootm_boot_start(loadaddr, bflow->cmdline);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int boot_android_recovery(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + ret = boot_android_normal(bflow);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int boot_android_bootloader(struct bootflow *bflow)
>> +{
>> + int ret;
>> +
>> + ret = run_command("fastboot usb 0", 0);
>> + do_reset(NULL, 0, 0, NULL);
>> +
>> + return log_msg_ret("boot", ret);
>> +}
>> +
>> +static int android_boot(struct udevice *dev, struct bootflow *bflow)
>> +{
>> + struct android_priv *priv = bflow->bootmeth_priv;
>> + int ret;
>> +
>> + switch (priv->boot_mode) {
>> + case ANDROID_BOOT_MODE_NORMAL:
>> + ret = boot_android_normal(bflow);
>> + break;
>> + case ANDROID_BOOT_MODE_RECOVERY:
>> + ret = boot_android_recovery(bflow);
>> + break;
>> + case ANDROID_BOOT_MODE_BOOTLOADER:
>> + ret = boot_android_bootloader(bflow);
>> + break;
>> + default:
>> + printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
>> + priv->boot_mode);
>> + boot_android_bootloader(bflow);
>> + /* Tell we failed to boot since boot mode is unknown */
>> + ret = -EFAULT;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int android_bootmeth_bind(struct udevice *dev)
>> +{
>> + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
>> +
>> + plat->desc = "Android boot";
>> + plat->flags = BOOTMETHF_ANY_PART;
>> +
>> + return 0;
>> +}
>> +
>> +static struct bootmeth_ops android_bootmeth_ops = {
>> + .check = android_check,
>> + .read_bootflow = android_read_bootflow,
>> + .read_file = android_read_file,
>> + .boot = android_boot,
>> +};
>> +
>> +static const struct udevice_id android_bootmeth_ids[] = {
>> + { .compatible = "u-boot,android" },
>> + { }
>> +};
>> +
>> +U_BOOT_DRIVER(bootmeth_android) = {
>> + .name = "bootmeth_android",
>> + .id = UCLASS_BOOTMETH,
>> + .of_match = android_bootmeth_ids,
>> + .ops = &android_bootmeth_ops,
>> + .bind = android_bootmeth_bind,
>> +};
>> diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
>> new file mode 100644
>> index 000000000000..411c2f2d15e7
>> --- /dev/null
>> +++ b/boot/bootmeth_android.h
>> @@ -0,0 +1,27 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Bootmethod for Android
>> + *
>> + * Copyright (C) 2024 BayLibre, SAS
>> + * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
>> + */
>> +
>> +enum android_boot_mode {
>> + ANDROID_BOOT_MODE_NORMAL = 0,
>> +
>> + /* Android "recovery" is a special boot mode that uses another ramdisk.
>> + * It can be used to "factory reset" a board or to flash logical partitions
>> + * It operates in 2 modes: adb or fastbootd
>> + * To enter recovery from Android, we can do:
>> + * $ adb reboot recovery
>> + * $ adb reboot fastboot
>> + */
>> + ANDROID_BOOT_MODE_RECOVERY,
>> +
>> + /* Android "bootloader" is for accessing/reflashing physical partitions
>> + * Typically, this will launch a fastboot process in U-Boot.
>> + * To enter "bootloader" from Android, we can do:
>> + * $ adb reboot bootloader
>> + */
>> + ANDROID_BOOT_MODE_BOOTLOADER,
>> +};
>> diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
>> index a07a72581e7a..709fa9e64ff3 100644
>> --- a/doc/develop/bootstd.rst
>> +++ b/doc/develop/bootstd.rst
>> @@ -95,6 +95,7 @@ bootflows.
>>
>> Note: it is possible to have a bootmeth that uses a partition or a whole device
>> directly, but it is more common to use a filesystem.
>> +For example, the Android bootmeth uses a whole device.
>>
>> Note that some bootmeths are 'global', meaning that they select the bootdev
>> themselves. Examples include VBE and EFI boot manager. In this case, they
>> @@ -277,6 +278,9 @@ script_offset_f
>> script_size_f
>> Size of the script to load, e.g. 0x2000
>>
>> +vendor_boot_comp_addr_r
>> + Address to which to load the vendor_boot Android image, e.g. 0xe0000000
>> +
>> Some variables are set by script bootmeth:
>>
>> devtype
>> @@ -418,6 +422,7 @@ Bootmeth drivers are provided for:
>> - EFI boot using bootefi from disk
>> - VBE
>> - EFI boot using boot manager
>> + - Android bootflow (boot image v4)
>>
>>
>> Command interface
>> @@ -786,6 +791,7 @@ To do
>> Some things that need to be done to completely replace the distro-boot scripts:
>>
>> - implement extensions (devicetree overlays with add-on boards)
>> +- implement legacy (boot image v2) android boot flow
>
> Nice!
Thank you! It was easier to implement that I imagined, mainly because
the bootflow framework is well written (imo) and the bootmeth_cros example
was very helpful as well.
>
>>
>> Other ideas:
>>
>>
>> --
>> 2.45.0
>>
>
> Regards,
> Simon
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2024-06-12 10:18 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-06 12:23 [PATCH 0/6] bootstd: Add Android support Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 1/6] boot: android: Provide vendor_bootimg_addr in boot_get_fdt() Mattijs Korpershoek
2024-06-10 8:51 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 2/6] boot: android: Add image_android_get_version() Mattijs Korpershoek
2024-06-10 9:20 ` Igor Opaniuk
2024-06-11 9:01 ` Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 3/6] bootstd: Add bootflow_iter_check_mmc() helper Mattijs Korpershoek
2024-06-10 9:31 ` Igor Opaniuk
2024-06-11 9:06 ` Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 4/6] android: boot: Add set_abootimg_addr() and set_avendor_bootimg_addr() Mattijs Korpershoek
2024-06-10 14:27 ` Igor Opaniuk
2024-06-06 12:23 ` [PATCH 5/6] bootstd: Add a bootmeth for Android Mattijs Korpershoek
2024-06-10 15:15 ` Igor Opaniuk
2024-06-11 9:32 ` Mattijs Korpershoek
2024-06-11 18:52 ` Simon Glass
2024-06-12 10:18 ` Mattijs Korpershoek
2024-06-06 12:23 ` [PATCH 6/6] bootstd: Add test for bootmeth_android Mattijs Korpershoek
2024-06-11 18:52 ` Simon Glass
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox