[RFC] Load U-Boot without LK on DragonBoard 410c (+ DB820c?)

Stephan Gerhold
2021-07-01T09:08:07Z

Hi!

at the moment the U-Boot ports for both DragonBoard 410c and 820c are
designed to be loaded as an Android boot image after Qualcomm's LK
bootloader. This is simple to set up but LK is redundant in this case,
since everything done by LK can be also done directly by U-Boot.

Dropping LK entirely would have at least the following advantages:
- Easier installation/board code (no need for Android boot images)
- (Slightly) faster boot
- Boot directly in 64-bit without a round trip to 32-bit for LK

This was not possible so far because of some unsolved problems.
For clarity I try to describe them together with some background here,
but I want to apologize for the long text. It's all quite complicated. :)

1. "Signing" 64-bit U-Boot
==========================

Ramon already tried to eliminate LK for DB410c 3 years ago [1].
One of the open problems back then was to have a proper "signing"
tool with 64-bit support. The firmware expects an ELF image with a few
Qualcomm-specific ELF headers. Normally this is used for secure boot
setups. This is not used on DragonBoards, but the firmware still insists
on having a dummy (self-signed) certificate chain in the ELF images.

Linaro uses signlk [2] to sign their builds of LK. It looks like Nicolas
extended it with ELF64 support after Ramon's mail [3]. However, for some
reason signlk literally works only for LK for me. I tried to "sign"
U-Boot and some other firmware, but everything except LK is always
rejected with the following message on boot:

B - 1031113 - Error code 302e at boot_config.c Line 296

I tried to track down the issue in the source code for quite some time
but did not manage to find the problem. Perhaps it's some subtle mistake
with some of the ELF modifications, I'm not sure. (For some reason,
signlk makes subtle changes to all of the existing ELF headers...)

After reading about the image format myself I decided to try to make my
own "signing" tool, qtestsign: https://github.com/msm8916-mainline/qtestsign
It's based on a mixture of the specification [4] and some missing bits
taken from signlk, put in a simple and clean Python tool. I still don't
know what exactly qtestsign does different, but unlike signlk it can
successfully "sign" U-Boot and all other firmware from DragonBoard 410c.

[1]: https://lore.kernel.org/u-boot/CA+Kvs9kS=DbJKNAixk_3tz+3iWnRaSP0gJdZ8eKrzasKOr6wcw@mail.gmail.com/
[2]: https://git.linaro.org/landing-teams/working/qualcomm/signlk.git/
[3]: https://git.linaro.org/landing-teams/working/qualcomm/signlk.git/commit/?id=1f61c03322c3728f35b3f0cd4ff04f73522f1e67
[4]: https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview-v1-0.pdf

My solution
-----------

Now we have all we need to install U-Boot without LK. For DragonBoard 410c
the following steps end up in the U-Boot prompt without going through LK:

1. Change dragonboard410c_defconfig as follows:

-CONFIG_SYS_TEXT_BASE=0x80080000
+CONFIG_SYS_TEXT_BASE=0x8F600000
+CONFIG_OF_EMBED=y (I discuss this at the end of the mail)

2. $ make
3. Sign the ELF image: $ qtestsign.py aboot <out>/u-boot [5]
4. Flash "<out>/u-boot-test-signed.mbn" to the "aboot" partition

[5]: https://github.com/msm8916-mainline/qtestsign

2. Linux gets stuck when loaded by 64-bit U-Boot without LK
===========================================================

This should work well enough to get the U-Boot prompt on serial.
However, once you load Linux you will likely notice a problem:

[ 0.059043] smp: Bringing up secondary CPUs ...
[ 5.120691] CPU1: failed to come online
[ 10.246760] CPU2: failed to come online
[ 15.372848] CPU3: failed to come online
[ 15.406275] CPU: All CPU(s) started at EL1
...
[ 16.185527] genirq: irq_chip msmgpio did not update eff. affinity mask of irq 79
Board freezes forever. :(

My investigations have shown this is a bug in the PSCI implementation on
DB410c (part of the TrustZone/"tz" firmware). Shortly said, since we
have never done the 32-bit -> 64-bit switch in LK, the PSCI implementation
seems to believe we are still running in 32-bit mode and starts all
further CPUs in 32-bit mode. The other CPU cores crash immediately when
coming up and CPU 0 hangs once CPU idle suspends it for the first time.

I have described this problem together with a workaround in detail here:
https://github.com/msm8916-mainline/qhypstub#boot-flow

The idea is to execute the TZ syscall to switch from 32-bit -> 64-bit
even though we are already running in 64-bit mode. This will make the
PSCI implementation aware that we want all further CPU cores booted in
64-bit mode as well.

My solution
-----------

The workaround is applied automatically when using my open-source "hyp"
firmware replacement qhypstub: https://github.com/msm8916-mainline/qhypstub
As a bonus, both U-Boot and Linux start in EL2, making it possible to
use virtualization (e.g. KVM in Linux).

$ git clone https://github.com/msm8916-mainline/qhypstub.git
$ cd qhypstub
$ make CROSS_COMPILE=aarch64-linux-gnu-
$ qtestsign.py hyp qhypstub.elf
# Flash "qhypstub-test-signed.mbn" to "hyp" partition and reboot.

Now it works:

[ 0.063411] CPU1: Booted secondary processor 0x0000000001 [0x410fd030]
[ 0.064184] CPU2: Booted secondary processor 0x0000000002 [0x410fd030]
[ 0.064906] CPU3: Booted secondary processor 0x0000000003 [0x410fd030]
[ 0.123032] CPU: All CPU(s) started at EL2
[ 0.448743] kvm [1]: Hyp mode initialized successfully
...

And with that U-Boot is fully working as far as I can tell.
(I have only tested serial, SD card and USB so far. If something is
broken, it's likely some missing register initialization that should
be ported from LK/Linux...)

3. Remaining open questions
===========================

I still see 3 questions that we need to discuss:

1. This is a quite fundamental change.
Can we just make it to dragonboard410c_defconfig?
Does it make sense to keep the old setup with LK?
When would it be used?

2. Workaround for PSCI bug: I'm not sure if we want to make qhypstub [6]
a requirement for U-Boot. On the one hand it's open-source, solves
the problem nicely without changes in U-Boot and provides EL2
additionally. I'm also not aware of any problem/disadvantage when
using it (if you find a problem, please let me know!).

But I realize it's unofficial. If we want to support using Qualcomm's
"hyp" firmware as well I could try porting the PSCI workaround
from qhypstub to U-Boot. It should be ~10 lines of ARM64 assembly [7]
placed e.g. in board/qualcomm/dragonboard410c/head.S.

However, I will need to make sure to detect if U-Boot was started
in EL2 by qhypstub because otherwise doing the workaround twice
will conflict and U-Boot might demote itself back to EL1.

3. CONFIG_OF_EMBED: There is a big warning about this in the build log:
"This option should only be used for debugging purposes. Please use
CONFIG_OF_SEPARATE for boards in mainline."

The important part here is that we need an ELF image with both
U-Boot and the DTB. CONFIG_OF_EMBED is convenient for that because
we can just use the ELF image built by the linker and it already
contains the DTB.

If CONFIG_OF_EMBED is really so bad it might be possible to build
a new boot image based on "u-boot-dtb.bin" (which is U-Boot with
DTB appended). I'm not sure if this is really much better though.

Bonus question: Could something similar also work for DB820c? I don't
have one myself but I think a similar setup short also work on it.
If someone is interested in testing this I would be happy to help. :)

Thanks for reading!
Stephan

[6]: https://github.com/msm8916-mainline/qhypstub
[7]: https://github.com/msm8916-mainline/qhypstub/blob/c9c3fd0f66ea60032812b06b51da39f25e678638/qhypstub.s#L197-L204