Android bootflow: experiments with U-Boot and GBL

Booting Android is fairly different from traditional Linux distributions. There are multiple partitions involved, the bootloader needs to pick a boot slot and decide which boot mode will be used. Because of this, Android has specific bootloader requirements. The AOSP documentation gives an overview of these.

In this blog post, we will explore how to boot Android on the BeaglePlay board using U-Boot’s standard boot and the Generic Bootloader Library (GBL).

Getting started

Let’s prepare our board with pre-built Android images to avoid rebuilding it ourselves. Texas Instruments provides Android releases for the BeaglePlay.

  1. Download the TI AM62X SDK 10.0 release image.
  2. Follow the BeaglePlay instructions to flash Android on the BeaglePlay’s eMMC.

Android bootmeth via standard boot (U-Boot)

U-Boot has many ways of loading various OS such as boot scripts, distro bootcmd and more. The most recent way of booting is named standard boot.

Standard boot has the concept of bootmeth, which describes how U-Boot should boot a particular OS.

Since U-Boot v2024.10, the Android bootmeth provides support for booting Android using standard boot.

Given that the TI AM62X SDK 10.0 shipped with U-Boot v2024.04, standard boot support is not included. Let’s see how we can enable it on our BeaglePlay.

Building U-Boot

The U-Boot BeaglePlay documentation explains how to fetch and build everything we need.

Let’s give it a try:

$ mkdir ~/src/beagleplay-bootloaders && cd $_
$ git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git/
$ git clone https://github.com/OP-TEE/optee_os.git
$ git clone https://git.ti.com/git/processor-firmware/ti-linux-firmware.git -b ti-linux-firmware
$ git clone https://source.denx.de/u-boot/u-boot.git

At the time of writing, we are experimenting using U-Boot v2025.01-rc4. Let’s make sure we use the same here:

$ cd u-boot
$ git fetch origin --tags && git checkout v2025.01-rc4

Follow the build procedure. Make sure to use the Android defconfig fragment as documented:

$ export UBOOT_CFG_CORTEXR="${UBOOT_CFG_CORTEXA} am62x_a53_android.config"

Flashing the new U-Boot on the BeaglePlay

After we are done building, we can reboot our BeaglePlay in bootloader mode and reflash the new images.

# Prepare tispl.bin and u-boot.img
$ dd if=/dev/zero of=bootloader.img bs=1048576 count=8
$ mkfs.vfat bootloader.img
$ mcopy -i bootloader.img u-boot/tispl.bin ::tispl.bin
$ mcopy -i bootloader.img u-boot/u-boot.img ::u-boot.img

# Flash everything to eMMC
$ adb reboot bootloader
$ fastboot flash tiboot3 u-boot/tiboot3.bin
$ fastboot flash bootloader bootloader.img

Then, we can reboot into our new bootloaders:

$ fastboot reboot

Make sure to halt in the U-Boot console:

U-Boot 2025.01-rc4 (Dec 16 2024 - 14:28:36 +0100)

SoC:   AM62X SR1.0 GP
Model: BeagleBoard.org BeaglePlay
DRAM:  2 GiB
Core:  109 devices, 31 uclasses, devicetree: separate
MMC:   mmc@fa10000: 0, mmc@fa00000: 1, mmc@fa20000: 2
Loading Environment from nowhere... OK
In:    serial@2800000
Out:   serial@2800000
Err:   serial@2800000
Net:   eth0: ethernet@8000000port@1
Press SPACE to abort autoboot in 2 seconds
=>

Run

To interact with standard boot, we can use the bootflow command.

=> bootflow scan -l

Will show us that there is an Android bootmeth available

Scanning for bootflows in all bootdevs
Seq  Method       State   Uclass    Part  Name                      Filename
---  -----------  ------  --------  ----  ------------------------  ----------------
Scanning bootdev 'mmc@fa10000.bootdev':
  0  android      ready   mmc          0  mmc@fa10000.bootdev.whole
No more bootdevs
---  -----------  ------  --------  ----  ------------------------  ----------------
(1 bootflow, 1 valid)

Let’s select it and print some information:

=> bootflow select 0
=> bootflow info
Name:      mmc@fa10000.bootdev.whole
Device:    mmc@fa10000.bootdev
Block dev: mmc@fa10000.blk
Method:    android
State:     ready
Partition: 0
Subdir:    (none)
Filename:  <NULL>
Buffer:    (not loaded)
Size:      0 (0 bytes)
OS:        Android
Cmdline:   androidboot.slot_suffix=_a androidboot.bootloader=2025.01-rc4 androidboot.force_normal_boot=1
Logo:      (none)
FDT:       <NULL>
Error:     0

Finally, we can start Android with:

=> bootflow boot

While developping bootmeth_android, we also discovered that Google was working on a new initiative: the Generic Bootloader Library (GBL).

Generic Bootloader Library (EFI application)

At Linux Plumbers 2024, the Android system developers presented The Generic Bootloader Library (GBL). This efforts aims to help SoC vendors to keep up with Android’s evolving bootloader requirements.

GBL is implemented as an EFI application, written in rust. The official documentation is available here

While GBL is still in early development phases, it would be nice to run it on the BeaglePlay as well. Let’s give it a try!

Building GBL

Fetch the code with:

$ mkdir ~/src/uefi-gbl-mainline && cd $_
$ repo init -u https://android.googlesource.com/kernel/manifest -b uefi-gbl-mainline
$ repo sync -j16

Build the EFI executable:

$ ./tools/bazel run //bootable/libbootloader:gbl_efi_dist --extra_toolchains=@gbl//toolchain:all

We now have an EFI executable here:

$ ls out/gbl_efi
gbl_aarch64.efi  gbl_riscv64.efi  gbl_x86_32.efi  gbl_x86_64.efi

In our case, we will use gbl_aarch64.efi.

Bundling GBL into an EFI partition

To make sure that U-Boot can start gbl_aarch64.efi, let’s put it in a dedicated partition.

We can create an efi.img file that we can then flash via fastboot on our BeaglePlay.

$ dd if=/dev/zero of=efi.img bs=1048576 count=2 status=none
$ mkfs.vfat efi.img

$ mcopy -i efi.img 
    ~/src/uefi-gbl-mainline/out/gbl_efi/gbl_aarch64.efi 
    ::gbl_aarch64.efi

Here we have a efi.img file that we can flash into a dedicated 2MiB efi partition.

Modify U-Boot

Let’s change U-Boot in order to:

  • Add our new efi partition
  • Configure a new boot option
  • Change BootOrder to boot GBL

BeaglePlay’s eMMC partitions are defined in the board’s environment file. With the following diff, we can add the efi partition:

diff --git a/board/beagle/beagleplay/beagleplay.env b/board/beagle/beagleplay/beagleplay.env
index fc29d49712db..c3dc6efe1b03 100644
--- a/board/beagle/beagleplay/beagleplay.env
+++ b/board/beagle/beagleplay/beagleplay.env
@@ -42,6 +42,7 @@ partitions+=name=vbmeta_vendor_dlkm_b,size=64K,uuid=${uuid_gpt_vbmeta_vendor_dlk
 partitions+=name=super,size=4608M,uuid=${uuid_gpt_super};
 partitions+=name=metadata,size=64M,uuid=${uuid_gpt_metadata};
 partitions+=name=persist,size=32M,uuid=${uuid_gpt_persist};
+partitions+=name=efi,size=2M,uuid=${uuid_gpt_efi};
 partitions+=name=userdata,size=-,uuid=${uuid_gpt_userdata}
 fastboot_raw_partition_tiboot3="0x0 0x800 mmcpart 1"

To configure the new boot option and so on, we can use the efidebug command from U-Boot.

With the following diff, we add a new boot entry named GBL which will use eMMC 0, partition 0x13 (efi) and load the gbl_aarch64.efi binary:

diff --git a/include/env/ti/android.env b/include/env/ti/android.env
index a058beb7fc42..8084d11a7cf6 100644
--- a/include/env/ti/android.env
+++ b/include/env/ti/android.env
@@ -26,6 +26,6 @@ fastboot.partition-type:metadata=f2fs

 boot_targets=mmc0
 mmcdev=0
-bootmeths=android
+bootmeths=efi_mgr
 vendor_boot_comp_addr_r=0xd0000000
-bootcmd=bootflow scan -lb
+bootcmd=efidebug boot add -b 0001 GBL mmc 0:13 gbl_aarch64.efi; efidebug boot order 0001; bootflow scan -lb

These changes are also available on our gitlab.

Modify Android

In order to boot GBL, we also need some small Android modifications. Mostly because GBL and U-Boot don’t use the same lz4 compression algorithm for the kernel.

To avoid any issues, we are going to disable kernel compression.

Download Android 15 source code following our documentation. After building everything, we can apply the following diff in device/ti/am62x:

diff --git a/device-common.mk b/device-common.mk
index 9d41f5dd38d4..e9e5475d08e8 100644
--- a/device-common.mk
+++ b/device-common.mk
@@ -21,7 +21,7 @@ TARGET_AVB_ENABLE := true
 endif
 
 # Kernel part
-LOCAL_KERNEL := device/ti/am62x-kernel/kernel/$(TARGET_KERNEL_USE)/Image.lz4
+LOCAL_KERNEL := device/ti/am62x-kernel/kernel/$(TARGET_KERNEL_USE)/Image
 LOCAL_DTB := device/ti/am62x-kernel/kernel/$(TARGET_KERNEL_USE)
 
 PRODUCT_COPY_FILES += 

Let’s reflash our Android 15 build by following the docs and then install our new bootloaders with EFI support and GBL.

Flashing the new bootloaders on the board

After flashing the modified Android 15 system, we can now flash our new bootloaders:

# Prepare tispl.bin and u-boot.img
$ dd if=/dev/zero of=bootloader.img bs=1048576 count=8
$ mkfs.vfat bootloader.img
$ mcopy -i bootloader.img u-boot/tispl.bin ::tispl.bin
$ mcopy -i bootloader.img u-boot/u-boot.img ::u-boot.img

# flash new bootloaders to eMMC
$ fastboot flash tiboot3 u-boot/tiboot3.bin
$ fastboot flash bootloader bootloader.img
$ fastboot reboot bootloader

After reboot, efi_mgr will fail to boot. Let’s enable fastboot manually via the commandline to continue:

=> fastboot usb 0

Then we can continue flashing:

$ fastboot oem format
$ fastboot continue
# Go back to U-Boot shell and re-enable fastboot again
$ fastboot flash efi efi.img

Because we re-partitioned, the userdata partition shrunk a bit. We also need to reflash userdata and metadata to start from a clean state.

$ fastboot flash userdata userdata.img
$ fastboot flash metadata metadata.img

Then reboot:

$ fastboot reboot

With that, we can see GBL running:

U-Boot 2025.01-rc4-00002-gf304c165fa06 (Dec 18 2024 - 14:45:27 +0100)

SoC:   AM62X SR1.0 GP
Model: BeagleBoard.org BeaglePlay
DRAM:  2 GiB
Core:  109 devices, 31 uclasses, devicetree: separate
MMC:   mmc@fa10000: 0, mmc@fa00000: 1, mmc@fa20000: 2
Loading Environment from nowhere... OK
In:    serial@2800000
Out:   serial@2800000
Err:   serial@2800000
Net:   eth0: ethernet@8000000port@1
Press SPACE to abort autoboot in 2 seconds
MMC: no card present
Card did not respond to voltage select! : -110
Cannot persist EFI variables without system partition
Scanning for bootflows in all bootdevs
Seq  Method       State   Uclass    Part  Name                      Filename
---  -----------  ------  --------  ----  ------------------------  ----------------
Scanning global bootmeth 'efi_mgr':
  0  efi_mgr      ready   (none)       0  <NULL>
** Booting bootflow '<NULL>' with efi_mgr
Booting: GBL
****Generic Bootloader Application****
Image path: /VenHw(e61d73b9-a384-4acc-aeab-82e828f3628b,0000000000000000)/VenHw(e61d73b9-a384-4acc-aeab-82e828f3628b,6d00000000000000)/eMMC(0)/eMMC(0)/HD(19,GPT,0de54c28-dd18-2843-b86b-de2ba80d8232,0x98f600,0x1000)
Block #0 GPT sync result: Found valid GPT.
Proceeding as Android
Block #0 GPT sync result: Found valid GPT.
Press Backspace to enter fastboot
Try booting as Android
boot mode from BCB: AndroidBootMode::Normal
boot image size: 36837888
boot image cmdline: ""
boot ramdisk size: 0
boot dtb size: 0
vendor ramdisk size: 18329592
vendor cmdline: "cma=512M console=ttyS2,115200 printk.devkmsg=on init=/init quiet firmware_class.path=/vendor/firmware mem_sleep_default=deep 8250.nr_uarts=10 bootconfig"
vendor dtb size: 248680
init_boot image size: 2121415
WARNING: UEFI GblEfiAvbProtocol.validate_vbmeta_public_key implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_rollback_index implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_rollback_index implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_rollback_index implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_rollback_index implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_is_device_unlocked implementation is missing. This will not be permitted in the future.
WARNING: UEFI GblEfiAvbProtocol.read_is_device_unlocked implementation is missing. This will not be permitted in the future.
final bootconfig: "androidboot.vbmeta.device=PARTUUID=00000000-0000-0000-0000-000000000000nandroidboot.vbmeta.avb_version=1.3nandroidboot.vbmeta.device_state=unlockednandroidboot.vbmeta.hash_alg=sha256nandroidboot.vbmeta.size=14400nandroidboot.vbmeta.digest=2a0d38d8b37524a1743372a3900a40f3fa6716a398259f087d59b718fa52c0a0nandroidboot.vbmeta.invalidate_on_error=yesnandroidboot.veritymode=enforcingnandroidboot.verifiedbootstate=orangenandroidboot.slot_suffix=_anandroidboot.force_normal_boot=1nandroidboot.hardware=am62xnandroidboot.load_modules_parallel=truenandroidboot.fstab_suffix=am62.mmc.avbnandroidboot.boot_devices=bus@f0000/fa10000.mmcn"
Applying 0 overlays
Overlays applied
linux,initrd-start: 0xe7400000
linux,initrd-end: 0xe8781146
final cmdline: "cma=512M console=ttyS2,115200 printk.devkmsg=on init=/init quiet firmware_class.path=/vendor/firmware mem_sleep_default=deep 8250.nr_uarts=10 bootconfig"

Booting kernel @ 0xe8800000, ramdisk @ 0xe7400000, fdt @ 0xe8781148

Note that GBL is in early development. While experimenting, we have noticed the following limitations:

  • No support for A/B slot (slot _a is hard-coded).
  • No fastboot over USB support.

To conclude

While standard boot (U-Boot) remains the most practical and well-supported for booting Android on the BeaglePlay, GBL is an exciting technology to watch. It shows potential for simplifying bootloader development in the long term but is not production-ready. Developers interested in exploring the bleeding edge of Android bootloader can experiment with GBL, but production devices should continue to rely on more mature solutions like standard boot (U-Boot).