Good to see you again. Last lesson established where U-Boot itself runs: early code works in constrained memory, DDR is initialized, then U-Boot relocates into DRAM and reaches its command shell or automatic boot policy. We now use that DRAM-backed U-Boot environment to load Linux boot artifacts safely.
The objective is precise: configure a U-Boot environment and a bootcmd that boot an ARM64 kernel, a DTB, and an external initramfs from one explicitly selected storage device and partition—while demonstrating that their RAM ranges do not collide with one another, U-Boot, or known reserved memory.
This is the operational bridge between bootloader bring-up and the kernel/device-tree work that follows. For an automotive gateway, an address plan must be treated as an interface contract: an apparently harmless boot-script edit can overwrite an initramfs, a DTB, a remote-processor carveout, or U-Boot’s relocated state.
A boot command is a policy, not merely a command line
U-Boot’s environment is a key-value store in RAM. It supplies values such as filenames, memory addresses, boot arguments, and scripts. An environment can be built into U-Boot, loaded from persistent storage, or modified interactively.
The distinction between a test and a persistent configuration matters:
env setchanges the in-memory environment only.env savewrites the environment to its configured nonvolatile location.bootcmdis the script U-Boot executes after the autoboot delay, unless you interrupt it.bootargsare passed to Linux; they are not commands executed by U-Boot.
Before making anything persistent, capture the existing policy from the serial console:
=> env print bootcmd bootargs
=> env print boot_targets boot_prefixes boot_scripts
=> env print kernel_addr_r fdt_addr_r ramdisk_addr_r
=> env print fdtcontroladdr
Treat the current bootcmd as a known-good recovery reference. In a production system, a saved environment may control slot selection, boot counters, verified-boot behavior, or update recovery. Do not casually erase or reset it.
Environment Variables — Das U-Boot unknown version documentation
Read the official U-Boot environment guide to establish the difference between temporary environment changes, persistent changes, boot policy, and conventional image-address variables.
Start in “Environment Variables” and read environment persistence. Then, in the variable reference, find bootcmd and bootargs; read their two roles. Finally, read the complete “Image locations” section, especially the address constraints. Focus on why conventional variable names are useful but are not universally hard-coded rules.
The following boot-flow diagram shows the stage boundary we are now working at. ROM, SPL, and U-Boot have already made DRAM usable; U-Boot Proper is responsible for placing the Linux artifacts in that DRAM before transferring control.

Define the payload contract first
For this lesson, use this deliberately explicit artifact set:
| Role | Example file | Consumer | U-Boot variable |
|---|---|---|---|
| ARM64 kernel | /boot/Image | booti and then the CPU | kernel_addr_r |
| Hardware description | /boot/<board>.dtb | Linux kernel | fdt_addr_r |
| External initramfs | /boot/initramfs.cpio.gz | Linux kernel | ramdisk_addr_r |
An initramfs is normally a CPIO archive, often compressed. It is passed to Linux using what U-Boot calls an initrd argument. This terminology difference is normal: the third argument to booti identifies an external initial RAM filesystem payload.
For a raw, uncompressed AArch64 Image, the usual boot command is:
booti <kernel address> <initrd address>:<initrd size> <fdt address>
For example:
booti ${kernel_addr_r} ${ramdisk_addr_r}:${initrd_size} ${fdt_addr_r}
Do not substitute bootm or bootz merely because they are familiar:
| Payload format | Usual command | Notes |
|---|---|---|
Raw AArch64 Image | booti | The case used in this lesson |
ARM zImage | bootz | Typically used on 32-bit ARM |
Legacy uImage | bootm | U-Boot legacy image container |
FIT image (.itb) | bootm | Can package kernel, DTBs, ramdisks, hashes, and signatures |
A later secure-boot design should normally move toward authenticated FIT images or the platform’s required authenticated containers. For now, separate artifacts make memory placement and failure analysis visible.
Tutorial: Introduction to the Embedded Boot Loader U-boot - Behan Webster, Converse in Code
In “Tutorial: Introduction to the Embedded Boot Loader U-boot,” Behan Webster of Converse in Code gives a practical visual introduction to U-Boot environments, storage loading, and the kernel-image commands.
Watch environment scripting to see temporary versus persistent variables, bootcmd, bootargs, loadaddr, and the filesize variable that U-Boot updates after a load. Then watch loading from media for the relationship among MMC devices, partitions, filesystems, and RAM destinations. Finish with image boot commands, focusing on why raw ARM64 images use booti and why the device-tree address is supplied separately.
Start with discovered RAM, not assumed RAM
Do not copy address values from an unrelated board or a vendor forum post. The RAM base, RAM size, U-Boot relocation address, stack placement, secure firmware regions, and remote-processor carveouts are platform-specific.
At the U-Boot prompt, begin with:
=> bdinfo
=> env print fdtcontroladdr
=> env print bootm_low bootm_size bootm_mapsize
The key fields in bdinfo are usually:
bdinfo field | Why it matters |
|---|---|
DRAM bank start | Physical base of usable DRAM as U-Boot sees it |
DRAM bank size | Upper bound of the DRAM range |
relocaddr | Where U-Boot Proper relocated itself |
sp start | Top of U-Boot’s current stack; the stack grows downward |
TLB addr or architecture-specific entries | Additional U-Boot runtime allocations that should not be overwritten |
On many TI ARM systems, U-Boot relocates near the high end of RAM. That does not mean every low address is automatically safe. You must also account for:
- the memory range the kernel will occupy once started, including its BSS and early allocations;
- U-Boot’s control DTB at
fdtcontroladdr, if present; - secure-firmware and reserved-memory regions;
- remote-processor shared memory and firmware regions on multicore SoCs;
- a margin between independently loaded artifacts.
3.1. U-Boot — Processor SDK Linux Documentation
This TI SDK documentation is for an older TI platform, but its use of bdinfo remains a useful general method: establish U-Boot’s relocated and stack-reserved region before choosing download addresses.
In subsection “Available RAM for image download,” read the bdinfo example. The numerical addresses are not AM62x defaults and must not be copied. Focus instead on the reasoning: U-Boot relocation and a downward-growing stack make the upper part of DRAM unavailable for arbitrary image loading.
A conservative illustrative address plan
Assume, only for this example, that your target reports DRAM beginning at 0x80000000 and has at least 512 MiB available. The address plan below keeps all transient Linux boot artifacts in the first 256 MiB of DRAM, far away from a typical high-DRAM U-Boot relocation region.
| Region | Start | End, exclusive | Capacity | Purpose |
|---|---|---|---|---|
| Kernel runtime envelope | 0x80000000 | 0x88000000 | 128 MiB | A protected low-memory allowance for kernel placement, BSS, and early use |
| Loaded kernel file | 0x82000000 | 0x86000000 | 64 MiB | Raw ARM64 Image buffer |
| Guard gap | 0x86000000 | 0x89000000 | 48 MiB | Separation between kernel runtime envelope and DTB |
| DTB buffer | 0x89000000 | 0x89100000 | 1 MiB | One DTB, with a deliberately generous cap |
| Guard gap | 0x89100000 | 0x8a000000 | 15 MiB | Space for FDT growth or relocation |
| Initramfs buffer | 0x8a000000 | 0x8e000000 | 64 MiB | External initramfs archive |
| Remaining boot workspace | 0x8e000000 | 0x90000000 | 32 MiB | Margin within the first 256 MiB |
The crucial principle is interval analysis. If an artifact begins at and is bytes long, its occupied range is:
Two loaded artifacts are safely separate when the end of one range is no greater than the start of the next.
For the example kernel buffer, the 64 MiB maximum is:
It therefore remains below the DTB buffer at 0x89000000. Likewise, a 1 MiB DTB loaded at 0x89000000 ends at 0x89100000, safely below the initramfs at 0x8a000000.
Important limitation: this table proves that the load buffers do not overlap. It also reserves 128 MiB for the kernel’s eventual early runtime footprint. Still, validate the actual image sizes and the board’s Linux boot requirements before adopting the values. A debug kernel, a large initramfs, a large overlay set, or a custom reserved-memory map can invalidate an otherwise sensible plan.
The DTB address is 8-byte aligned, as required for a flattened device tree. In practice, page-aligned addresses such as these are easier to inspect and less error-prone.
Bind the boot policy to one storage device
“Boot from MMC” is not precise enough. A board may expose:
- an SD card as one MMC index;
- user-area eMMC as another;
- eMMC boot partitions separately;
- a partition table with a FAT boot partition and an ext4 root filesystem.
For this template, suppose inspection shows that the boot partition is a FAT filesystem on MMC device 1, partition 1. First verify this rather than assuming it:
=> mmc list
=> mmc dev 1
=> mmc rescan
=> mmc part
=> fatls mmc 1:1 /boot
If the actual boot filesystem is ext4, use ext4ls and ext4load consistently instead of fatls and fatload. Do not mix filesystem commands because a failed load can leave stale data at a valid-looking RAM address.
Now define the data-only environment variables. They make review easier than burying literal filenames and addresses in one long command:
=> env set bootdev 1
=> env set bootpart 1
=> env set bootdir /boot
=> env set kernel_image Image
=> env set fdtfile k3-am62x-your-board.dtb
=> env set initramfs initramfs.cpio.gz
=> env set kernel_addr_r 0x82000000
=> env set fdt_addr_r 0x89000000
=> env set ramdisk_addr_r 0x8a000000
Replace k3-am62x-your-board.dtb with the DTB that matches the actual board and its installed hardware. A boot that reaches Linux with the wrong DTB is not a successful boot; it is a hardware-description defect that may appear later as missing storage, console, Ethernet, CAN, regulators, or interrupts.
For an initramfs-based test boot, use boot arguments appropriate to your configured serial console and userspace. A representative form is:
=> env set bootargs 'console=ttyS2,115200n8 rdinit=/sbin/init'
ttyS2 is only an example. Preserve the console identifier known to work on the board’s vendor reference image unless you have verified a different one.
Build load scripts that retain the actual file sizes
U-Boot updates filesize after a successful load. That variable is overwritten by the next load, so capture each value immediately.
Enter the following as environment scripts:
=> env set load_kernel 'fatload mmc ${bootdev}:${bootpart} ${kernel_addr_r} ${bootdir}/${kernel_image} && env set kernel_size ${filesize}'
=> env set load_fdt 'fatload mmc ${bootdev}:${bootpart} ${fdt_addr_r} ${bootdir}/${fdtfile} && env set fdt_size ${filesize}'
=> env set load_initramfs 'fatload mmc ${bootdev}:${bootpart} ${ramdisk_addr_r} ${bootdir}/${initramfs} && env set initrd_size ${filesize}'
Then inspect what was stored:
=> env print load_kernel load_fdt load_initramfs
The output should retain expressions such as ${kernel_addr_r} and ${filesize} for evaluation when the script runs. If your U-Boot shell expands variables while defining the script, enter the dollar signs escaped, for example ${kernel_addr_r}, then inspect the result again. This is a frequent scripting failure: a script seems correctly defined but contains old literal values, empty values, or a size captured from an earlier command.
Load and inspect each payload before trying to boot:
=> mmc dev ${bootdev}
=> mmc rescan
=> run load_kernel
=> env print kernel_addr_r kernel_size
=> run load_fdt
=> env print fdt_addr_r fdt_size
=> fdt addr ${fdt_addr_r}
=> fdt header
=> run load_initramfs
=> env print ramdisk_addr_r initrd_size
A successful fatload reports the number of bytes read and updates filesize. The captured kernel_size, fdt_size, and initrd_size provide evidence for the address review.
For each loaded object, calculate its end address using its actual size. setexpr can assist with this directly in U-Boot:
=> setexpr kernel_end ${kernel_addr_r} + ${kernel_size}
=> setexpr fdt_end ${fdt_addr_r} + ${fdt_size}
=> setexpr initrd_end ${ramdisk_addr_r} + ${initrd_size}
=> env print kernel_end fdt_end initrd_end
Compare the outputs to these limits from the illustrative plan:
| Artifact | Required check |
|---|---|
| Kernel file | kernel_end must be no greater than 0x86000000 |
| DTB | fdt_end must be no greater than 0x89100000 |
| Initramfs | initrd_end must be no greater than 0x8e000000 |
| Entire working set | All addresses must be inside actual usable DRAM and outside known reserved regions |
If any size exceeds its allocated cap, do not move the next artifact upward blindly. Rebuild the memory map, preserving a kernel runtime envelope and avoiding the relocated U-Boot region indicated by bdinfo.
Compose, test, then persist bootcmd
A safe script should stop if a storage selection, rescan, or load operation fails. In U-Boot’s shell, && means the next command is performed only when the previous one succeeds.
=> env set boot_gateway 'mmc dev ${bootdev} && mmc rescan && run load_kernel && run load_fdt && run load_initramfs && booti ${kernel_addr_r} ${ramdisk_addr_r}:${initrd_size} ${fdt_addr_r}'
Test the policy explicitly before assigning it to autoboot:
=> run boot_gateway
The expected high-level sequence is:
- U-Boot selects the declared MMC device and rescans it.
- The kernel loads to
kernel_addr_r. - The DTB loads to
fdt_addr_r. - The initramfs loads to
ramdisk_addr_r, and its exact size is captured. bootivalidates and prepares the ARM64 kernel handoff using the supplied initramfs and DTB.
If booti starts Linux, the serial log should eventually show the kernel command line and the model taken from the supplied DTB. If it fails before the kernel starts, preserve the complete U-Boot output. The final successful line identifies whether the failure belongs to storage access, a missing file, an image format mismatch, a DTB issue, an address collision, or the boot command itself.
Only after a successful manual test should you make the automatic policy persistent:
=> env set bootcmd 'run boot_gateway'
=> env print bootcmd boot_gateway
=> env save
Before using env save, confirm where the environment is stored for the exact board configuration. It may reside in a raw eMMC offset, SPI-NOR, a filesystem file, or redundant environment sectors. On secure or production-oriented configurations, saving may be deliberately disabled or constrained.
Practical failure interpretation
| Symptom | First investigation |
|---|---|
mmc dev fails | Wrong MMC index, driver issue, boot source not exposed as expected |
fatload cannot find a file | Wrong partition, filesystem type, path, or filename |
fdt header fails | Wrong file, corrupted load, wrong address, or DTB overwritten |
booti rejects the kernel | Not a raw AArch64 Image, corrupted artifact, unsupported compression, or wrong command |
| Kernel starts but no early console | Incorrect console= parameter, incorrect DTB UART node, clock/pinmux issue |
| Kernel cannot start init | Bad or missing initramfs, invalid rdinit, kernel lacks initramfs support, or the initramfs was corrupted |
| Boot is intermittent | Address overlap, insufficient margins, RAM instability, stale load data after an ignored command failure |
For the private implementation repository, retain:
- the
bdinfooutput; - the selected storage-device and partition evidence;
- a table of artifact paths, addresses, actual sizes, and calculated end addresses;
- the final environment variables;
- one complete successful serial log.
For the public portfolio, publish a sanitized memory-layout diagram and a generic boot-script excerpt. Omit production partition offsets, security policy, keys, credentials, network addresses, and any board-specific recovery details that would aid an attacker.
Key takeaways
A reliable U-Boot boot policy is built from verified facts, not conventional addresses copied from another board:
- Use
bdinfoto identify actual DRAM, U-Boot relocation, and stack context before allocating load buffers. - Bind the script to a specific device, partition, filesystem, and artifact path.
- Keep the kernel, DTB, and initramfs in distinct, bounded RAM intervals with guard space.
- Capture
filesizeimmediately after every successful load, particularly for the initramfs passed tobooti. - Use
bootifor a raw ARM64Imageand pass the external initramfs asaddress:size. - Test with
run boot_gatewaybefore assigning and savingbootcmd. - A non-overlapping download plan is necessary but must also respect the kernel’s runtime memory envelope and the board’s reserved-memory regions.
Next, you will examine the DTB that this boot command passes to Linux: its nodes, properties, phandles, aliases, /chosen settings, and pin control configuration.
Can't find a good explanation? Sign up and we'll make it for you
Sign up