In my previous post “Android Bootloader Analysis – ABL(1)”, I analyzed the overall boot process of ABL on contemporary Qualcomm platforms, and in “Android Bootloader Analysis – ABL(2)”, I explains in detail how to boot into fastboot mode. In this post, we will analyze the code in ABL to boot into Linux kernel.
Conditions for booting the Linux kernel
If you are not booting into fastboot mode and do not press a key combination during boot, ABL loads and verifies the kernel with LoadImageAndAuth (&Info)
(if it is not unlocked) and calls BootLinux (&Info)
to boot the loaded kernel, and fall-through to fastboot mode if it fails. to fastboot mode, where Info is a BootInfo
type defined as follows:
1 | typedef struct BootInfo { |
The ImageData
structure is the loaded boot image, defined as follows:
1 | typedef struct { |
During the loading and verification of the Linux kernel, the image is loaded here first. Later, during the booting, one of the images here will be used.
Linux kernel verification and loading
The implementation to verify and load, the function LoadImageAndAuth (BootInfo *Info)
is located in QcomModulePkg/Library/avb/VerifiedBoot.c
. The full name of avb
here is “Android Verified Boot”.
This function first tries to load the image from the recovery
partition, checking to see if it was loaded successfully, and if there is a legitimate boot image version (version 3 or higher, for system-as-root) and kernel size:
1 | /* check early if recovery exists and has a kernel size */ |
If the recovery
partition does not have a legal kernel, set the RecoveryHasNoKernel
global identifier with SetRecoveryHasNoKernel ()
for later use.
There are two cases then, which are used to handle the presence of an A/B partition and the presence of only a single partition.
In the case of a single partition, it is also possible to have a system-as-root, where recovery mode shares the kernel with normal booting, but mounts a different partition as the sysroot, so the following code sets the name of the partition used for booting to either recovery
or boot
:
1 | if (Info->BootIntoRecovery && |
The case of A/B partitions is a bit more complicated. First ABL looks for a bootable slot (i.e. a set of partitions) and stores it in a CurrentSlot
structure, defined as follows:
1 | typedef struct { |
This structure defines the suffix of the partition. In fact, multiple slots are implemented by adding a suffix to the partition name, e.g. boot_a
and boot_b
are the boot partitions of two slots. This suffix is obtained by FindBootableSlot
. The next step is similar to that for a single partition.
Once the bootable slots have been obtained, the verification of the bootable slots’ images begins. The validation of the image is platform dependent, the version is obtained by calling GetAVBVersion ()
, currently there are NO_AVB
, AVB_1
, AVB_2
and AVB_LE
, which use the corresponding function calls to load the image and validate it respectively.
Taking no AVB authentication as an example, it directly loads the image using LoadImageNoAuth
, where LoadImageHeader (Info->Pname, &ImageHdrBuffer, &ImageHdrSize)
is called to load the image into the buffer. During this time, the corresponding device tree and command line parameters are loaded and set.
Finally, the verification status is displayed on the screen DisplayVerifiedBootScreen (Info)
and the image verification status is returned.
Booting the Linux kernel
First, it loads the boot image:
1 | Status = GetImage (Info, |
It updates the command line parameters for booting the kernel, gets the base address for loading, and loads the memory disk.
After that, shut down the UEFI boot service to prepare the Linux kernel for booting, and uninitialize some devices in PreparePlatformHardware
, such as disabling interrupts, disabling caching, disabling MMUs, disabling branch prediction, and so on:
1 | ArmDisableBranchPrediction (); |
Finally, it loads and calls the Linux kernel:
1 | LinuxKernel = (LINUX_KERNEL) (UINT64)BootParamlistPtr.KernelLoadAddr; |
This is for the 32 bit kernel:
1 | LinuxKernel32 = (LINUX_KERNEL32) (UINT64)BootParamlistPtr.KernelLoadAddr; |
However, it needs to switch to 32 bit boot mode before the 32 bit kernel boots:
1 | Status = SwitchTo32bitModeBooting ( |
This is accomplished by writing 0 to the X4 register in the EL1 environment:
1 | HlosBootArgs.el1_x2 = DeviceTreeLoadAddr; |
If startup fails, go to CpuDeadLoop()
.
Summary
This post analyzes and summarizes the code and flow of ABL when booting Linux normally.