free embedded systems course
free embedded linux course
U-Boot
QEMU ARM
Compiling a kernel image and its device tree blob is only half the job — the other half of any free embedded linux course is getting that image to actually run. Booting is one of the most device-dependent parts of embedded Linux work: the exact steps differ from board to board, bootloader to bootloader. This lecture builds general intuition using two very common paths — booting real hardware through U-Boot, and booting entirely in emulation with QEMU — so you can adapt the pattern to whatever board you are working with.
What You Will Learn
- What a bootloader actually hands off to the kernel, and why that handoff matters
- How to load a kernel image and device tree from storage using U-Boot on real hardware
- Why the kernel command line’s
console=parameter is critical for visibility - How to boot the exact same kind of kernel entirely inside QEMU, with no hardware at all
- How to exit a QEMU session cleanly
Prerequisites
- A built
zImageorImagekernel and a matching device tree blob, from earlier lectures in this course - Access to a board running U-Boot, or
qemu-system-arminstalled for the emulation path - Basic familiarity with kernel command-line concepts is helpful but not required
What A Bootloader Hands To The Kernel
Before the kernel can run, something has to get it into memory, tell it which hardware it is running on, and hand it a set of startup parameters. That is the bootloader’s job. On most modern embedded ARM boards this is U-Boot, and the handoff consists of three pieces: the kernel image itself, a device tree blob describing the hardware, and a command line string of key-value parameters the kernel reads during early boot.
│
▼
ROM / SPL bootloader stage
│
▼
U-Boot loads: zImage + device-tree.dtb
│
▼
U-Boot passes: bootargs (command line)
│
▼
bootz <kernel-addr> – <dtb-addr>
│
▼
Linux kernel starts executing
Booting Real Hardware Through U-Boot
On a typical board, the kernel image and device tree blob live on an SD card, eMMC partition, or are pushed over the network via TFTP. U-Boot’s job is to load both into RAM at fixed addresses and then jump into the kernel with bootz (for a zImage) or booti (for an Image). A representative session, loading from an SD card’s first partition, looks like this:
=> fatload mmc 0:1 0x80200000 zImage
reading zImage
4823104 bytes read in 261 ms (17.6 MiB/s)
=> fatload mmc 0:1 0x80f00000 board.dtb
reading board.dtb
31022 bytes read in 11 ms (2.7 MiB/s)
=> setenv bootargs console=ttyS2,115200 root=/dev/mmcblk0p2 rootwait
=> bootz 0x80200000 - 0x80f00000
Kernel image @ 0x80200000 [ 0x000000 - 0x497800 ]
## Flattened Device Tree blob at 80f00000
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
Two settings here deserve attention. First, the two load addresses (0x80200000 and 0x80f00000) are board-specific — they come from your board’s memory map and must not overlap the kernel’s own working area. Second, and more important for debugging, is the console= parameter inside bootargs. This tells the kernel which serial device to use for its own log output. On this example board that is ttyS2 at 115200 baud; on other boards it might be ttyO0, ttyAMA0, or something else entirely, depending on which UART is wired to the debug header. Without a correct console= setting, the kernel may boot perfectly well and you would never see a single line of it — the screen would simply go dark after “Starting kernel…”, leaving you unable to tell success from failure.
Booting The Same Kernel Under QEMU
You do not need physical hardware to test a kernel build. QEMU can emulate an entire ARM system, which is invaluable for quickly verifying that a kernel boots at all before you ever touch real hardware. Assuming qemu-system-arm is installed, a minimal invocation for the ARM Versatile Express reference platform looks like this:
$ QEMU_AUDIO_DRV=none qemu-system-arm \
-m 512M \
-nographic \
-M vexpress-a9 \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-append "console=ttyAMA0"
Each flag maps directly onto a concept from the real-hardware boot: -kernel and -dtb are QEMU’s equivalent of U-Boot’s fatload commands, -M selects the emulated machine (which determines the default device tree layout QEMU expects), and -append supplies the same kind of bootargs string, again with a machine-appropriate console= device — ttyAMA0 for this emulated PL011 UART. Setting QEMU_AUDIO_DRV=none simply silences irrelevant warnings about audio backends that this headless boot does not use.
Running that command drops you straight into the kernel’s boot log in your terminal, exactly as if you were watching a serial console on real hardware:
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 6.9.0 (build@ep) ...
[ 0.041233] CPU: ARMv7 Processor [410fc090] revision 0
...
To leave the emulated session, use the QEMU monitor escape sequence: press Ctrl-A then, as a separate keystroke, x.
Real Hardware Versus QEMU: When To Use Each
| Aspect | Real Hardware (U-Boot) | QEMU Emulation |
|---|---|---|
| Setup effort | Requires board, serial cable, storage media | Just a host package install |
| Iteration speed | Slower — flashing takes real time | Very fast — seconds per boot |
| Peripheral accuracy | Exact hardware behavior | Only what the emulated machine models |
| Best used for | Final validation, driver bring-up on real silicon | Early smoke-testing of kernel builds and configs |
Common Mistakes And Troubleshooting
- Black screen after “Starting kernel…”: almost always a wrong or missing
console=parameter for that specific board’s UART. - Load addresses that overlap kernel memory: always use the addresses documented for your board, not values copied from an unrelated board’s example.
- QEMU device tree mismatch: the
-dtbfile must match the-Mmachine type exactly, or the kernel will fail to find expected devices during early boot. - Forgetting
-nographic: without it, QEMU opens a graphical window instead of streaming the serial console to your terminal, which is confusing on a headless build server.
Best Practices
- Always smoke-test a new kernel build under QEMU before flashing real hardware — it catches gross configuration mistakes in seconds.
- Document your board’s exact load addresses and console device in your project notes; they rarely change and save real debugging time.
- Keep your U-Boot boot commands in a saved environment script (
boot.scr) rather than retyping them by hand each session.
Summary And Key Takeaways
- A bootloader’s core job is loading the kernel image and device tree into memory, then jumping into the kernel with a command-line handoff.
- The
console=kernel parameter is the single most important setting for actually seeing what the kernel is doing during boot. - QEMU can emulate the same handoff entirely in software, making it an excellent fast-iteration testbed before touching real hardware.
Conclusion
Booting is where all the earlier build work in this free linux kernel development course becomes a running system. Once you understand the handoff — image, device tree, command line — the same mental model applies whether you are working with U-Boot on physical silicon or QEMU on your laptop. In the next lecture we look at what happens when that handoff goes wrong: kernel panics, and how to read them.
Frequently Asked Questions
Why do I see no output at all after the kernel starts on real hardware?
This is almost always a missing or incorrect console= parameter in bootargs. Confirm which physical UART is wired to your debug header and match its device name and baud rate exactly.
Can QEMU boot an ARM64 kernel the same way?
Yes, using qemu-system-aarch64 with an appropriate -M machine type such as virt, following the same pattern of -kernel, -dtb (or built-in ACPI for virt), and -append.
Do I always need a separate device tree blob file?
Most ARM platforms require one, since the device tree describes hardware the kernel cannot otherwise discover. Some platforms with firmware-provided ACPI tables, common on server-class ARM64 or x86, do not need a separate DTB.
What is the difference between bootz and booti?
bootz boots a compressed zImage, the traditional 32-bit ARM kernel format. booti boots the newer Image format used on ARM64, which is not the same container format as zImage.
How do I exit QEMU without killing my terminal session?
Press Ctrl-A and then x as two separate keystrokes. This sends QEMU’s monitor escape and quit command rather than sending a signal to the guest kernel.
Is the load address for the kernel image always the same across boards?
No. Load addresses depend on each board’s RAM layout and are documented per board. Always use the address your board’s reference manual or vendor BSP specifies.
Keep Learning Embedded Linux Boot Flows
This lecture is part of EmbeddedPathashala’s free embedded systems course, built for students who want real depth, not shortcuts.
