December 21, 2025

RK3399 - U-boot "booti" command and EL1

The starting point for this is the "discovery" that it is U-boot that performs the EL2 to EL1 transition before booting linux.

I am using the U-boot "go" command to start my code, and clearly that does no more that what it says, namely it "jumps" to some address, remaining in EL2. So what is different about how linux gets started?

Well, linux doesn't get started by "go". Typically the "bootm" (actually the more up to date "booti") command gets used. I want to try this. I want to learn how to get U-boot to start my code using booti. I see mention of an "mkimage" utility. I also see a boot header compiled into the linux image by digging through the linux source code.
(I found this in linux-git/arch/arm64/kernel/head.S

Take a look at the boot files on my RK3399

The board I am using is booting from eMMC apparently. It certainly has no SD card in the slot, and my notes tell me that one of my two boards has 16G of eMMC.

I interrupt U-boot (which is easy), then type:

run distro_bootcmd
It finds a U-boot script /boot/boot.scr, reads the script (3180 bytes) then executes it at 0050_0000. The boot fails. I had hopes that I could get linux running and then examing boot.scr. I won't be able to do that without setting up a bootable SD card. I dig out my other RK3399, but there is no SD card in the slot.

Take a look at boot files on my RK3328

Here I definitely have an SD card, so I can put the card into an SD card reader if I need to. I fire it up, find it easy to interrupt U-boot, and discover that it uses "bootflow scan" to boot linux, with boot_targets = mmc1 mmc0 nvme scsi usb pxe dhcp spi.
Here the command to boot linux is:
run linux  (which does bootflow scan)
This darn board won't boot linux either, but I remember why. I only have U-boot on this SD card.

I find my card with Debian, and indeed it boots up. Along the way, I see:

Booting bootflow 'mmc@ff500000.bootdev.part_2' with script
This is booting Debian "forky" with root password "er3nae"

I look in /boot and find boot.cmd (and boot.scr)
It does this:

load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /boot/vmlinuz-6.17.8+deb14-arm64
booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdtcontroladdr}
I copy the vmlinuz file to my big desktop machine for study.
ls -l vmlinuz-6.17.8+deb14-arm64
-rw-r--r-- 1 tom tom 42233792 Dec 21 21:05 vmlinuz-6.17.8+deb14-arm64

file vmlinuz-6.17.8+deb14-arm64
vmlinuz-6.17.8+deb14-arm64: Linux kernel ARM64 boot executable Image, little-endian, 4K pages

00000000 4d5a 40fa 3bf0 8014 0000 0000 0000 0000   MZ@ ;
00000010 0000 9402 0000 0000 0a00 0000 0000 0000
00000020 0000 0000 0000 0000 0000 0000 0000 0000
00000030 0000 0000 0000 0000 4152 4d64 4000 0000           ARMd@
00000040 5045 0000 64aa 0200 0000 0000 0000 0000   PE  d
00000050 0000 0000 a000 0602 0b02 0214 0000 1302
00000060 0000 8000 0000 0000 9067 0e02 0000 0100            g
00000070 0000 0000 0000 0000 0000 0100 0002 0000
00000080 0000 0000 0300 0000 0000 0000 0000 0000
00000090 0000 9402 0000 0100 b9a6 8402 0a00 0001
000000a0 0000 0000 0000 0000 0000 0000 0000 0000
The fact that the filename is vmlinuz indicates that the kernel image is compressed. It is supposed to have the required decompression code at the head.

Once again, Mike has been there before me.

Try this on the RK3328

I add a header to my code. It can still boot using "go" since the first two instructions in the header just jump to my usual startup. This works.

Next I interrupt U-boot and type:

dhcp
booti 02000000
I get this:
> booti 02000000
Image lacks image_size field, assuming 16MiB
Moving Image from 0x2000000 to 0x280000, end=0x1280000
Working FDT set to 0
FDT and ATAGS support not compiled in
resetting ...

Look at "booti" in the U-boot sources

Searching for the string "booti" is all but useless, given how often the word "booting" is used in the sources. Searching for booti\" is more focused and helpful.
It yields two starting points:
cmd/booti.c
boot/bootm.c
Searching on the first message we see yields:
arch/arm/lib/image.c:		puts("Image lacks image_size field, assuming 16MiB\n");
Searching on the last message yields:
arch/arm/lib/bootm.c:		panic("FDT and ATAGS support not compiled in\n");
Note that this is a panic, hence the reset, which had nothing to do with our code running.

The "Moving Image" message is from

boot/bootm.c:			printf("Moving Image from 0x%lx to 0x%lx, end=0x%lx\n",

So, what are ATAGS

These are (in the context of U-boot) "ARM tags" and are a obsolete scheme (replaced by the device tree) for passing information to the Linux kernel. Of course I am neither desiring nor attempting to use these, but apparently what I am doing makes U-boot think I want to use ATAGS, and it has no support for them, so it panics.

And, what about booti versus bootm

I read that "bootm" is for legacy U-boot images (often named uImage or zImage). These were often called FIT images (flat image tree). These wre common for aarch32 platforms. These old images were built using mkimage. On the other hand, booti can deal with a flat image without a header, or a compressed image (Image.gz) and U-boot will do the decompression.

This document discusses booting Aarch64 Linux:

Give booti a valid size and a device tree

I modify my source so that the size field is correct.
I leave the flags zero for now.
I try just giving an address without a device tree present:
=> booti 0x02000000 - 03000000
Moving Image from 0x2000000 to 0x200000, end=0x201604
ERROR: Did not find a cmdline Flattened Device Tree
Could not find a valid device tree
For whatever reason, it does shift my image to a lower location in memory, which should work OK -- but it wants to find a valid device tree.

See the next page for what device trees are all about.
We generate a minimal dtb, then try this:

dhcp
tftpboot 03000000 bogus.dtb
booti 0x02000000 - 03000000
This works!
=> booti 0x02000000 - 03000000
Moving Image from 0x2000000 to 0x200000, end=0x201604
## Flattened Device Tree blob at 03000000
   Booting using the fdt blob at 0x3000000
Working FDT set to 3000000
   Loading Device Tree to 000000003cf05000, end 000000003cf08069 ... OK
Working FDT set to 3cf05000

Starting kernel ...

Int is 4 bytes on aarch64
Long is 8 bytes on aarch64
SPSR_el2 = AABBCCDDDEADBEEF
SPSR_el2 = 00000000DEADBEEF
SPSR_el2 = 0000DEADBEEF0000
Running at EL 2
MPidr_el1 = 80000000
Core 0
CurrentEL reg = 00000008
CurrentEL = 2
Midr_el1 = 410FD034
This is an A53 core (00000D03)
HCR_el2 = 00000022
HCR_el2 = 0000000000000022
SPSR_el2 = 1E252A06
SPSR_el2 = 000000001E252A06
VBAR_el2 = 3FF2B800
Blinking ...
It runs my code, and I see all the expected messages. It is blinking very very slowly (so the CPU clock is much less than when I run my demo using "go"). I now get a 45 second delay where I previously got about 1/2 second -- so the clock is reduced by about 90.

But this is the least of our problems. The big disappointment is that we are running at EL2. We had hoped that having "booti" launch our code would provoke U-boot into running us at EL1.


Have any comments? Questions? Drop me a line!

Tom's electronics pages / tom@mmto.org