Sooner or later every embedded engineer inherits a board U-Boot has never heard of. This lecture
in our free embedded systems course walks through the U-Boot source tree the way
you actually need to read it when porting: which directories hold architecture code, which hold
board code, and where the two meet. We’ll ground every directory in a concrete example board so it
isn’t abstract.
u-boot source tree
free embedded systems course
arch and board directories
What You Will Learn
- Which U-Boot directories are architecture-wide versus board-specific
- How
arch,board,common,doc, and
includedivide responsibilities - How to identify the closest existing board to start a port from, instead of starting blank
- What “porting” actually means in U-Boot terms versus writing a driver from scratch
Prerequisites
- A cloned, buildable U-Boot source tree (see our earlier lecture on building U-Boot from source)
- Comfort navigating a large C codebase with
grep/ripgrep
Our Running Example: The EP-Falcon Board
To keep this concrete, imagine your hardware team hands you a new carrier board called
EP-Falcon, built around the same SoC family as an existing, already-supported
reference board. That’s the normal starting point for almost every real port: you are rarely writing
SoC-level bring-up from a blank page, you are adapting an existing, working reference design to your
board’s specific memory map, pinout, and peripherals.
The Directories That Matter For A Port
U-Boot’s top-level layout separates code along two axes: what silicon it runs on, and what board
it’s soldered onto. Understanding that split is the entire skill of porting.
├── arch/ SoC-family and CPU-variant code (shared across many boards)
│ └── arm/cpu/armv7/…
├── board/ Board-specific code, one directory per board or vendor
│ └── vendor/ep-falcon/
├── common/ Core shell + command implementations (cmd_*.c), architecture-neutral
├── doc/ READMEs describing subsystems and porting notes
├── include/
│ └── configs/ Per-board configuration headers (legacy path, still consulted)
└── configs/ Per-board defconfig files consumed by Kconfig
arch — Everything The CPU Family Shares
arch holds code specific to a CPU architecture and, within it, to a CPU variant —
for example arch/arm/cpu contains subdirectories per ARM core generation. This code is
shared by every board using that core: cache setup, exception vectors, low-level MMU init. You will
almost never touch arch when porting to a new board built on an already-supported SoC —
that work was done once, by whoever ported the SoC itself.
board — Everything Your Specific Board Owns
board is where a port actually lives. Each board — or each vendor’s family of boards
— gets its own subdirectory containing pin muxing, DRAM timing, and any board-specific init that
can’t be inferred from the SoC alone. Boards from the same vendor are commonly grouped, e.g. several
TI EVM variants under board/ti/. This is the directory you copy from a reference board
and then edit for EP-Falcon.
common — Shell And Commands, Not Board Code
common holds the U-Boot command shell itself and the implementation of every command
you type at the prompt, each typically in a file named cmd_<name>.c. It’s
architecture- and board-neutral — you’d only edit it if you were adding a brand-new U-Boot command,
not porting to new hardware.
doc — Where To Start When You’re Lost
doc contains README files describing U-Boot subsystems and, often, porting notes
written by whoever added that subsystem. When you’re not sure how a mechanism is supposed to be used,
this is the first place to check before reading source.
include/configs And configs — Two Eras Of The Same Job
Older U-Boot releases kept the bulk of board configuration in per-board headers under
include/configs. Modern U-Boot has migrated almost all of that into Kconfig, with the
per-board defaults living as defconfig files under the top-level configs
directory instead. You’ll still find a header under include/configs for most boards —
it now typically holds only the handful of settings that haven’t been converted to Kconfig options
yet, rather than the bulk of the configuration. We cover the Kconfig side properly in the next
lecture.
Finding The Closest Reference Board
Before writing anything, identify the board in board/ that is electrically and
architecturally closest to yours — same SoC, ideally the same reference design family. Search by SoC
name across existing board directories:
$ grep -rl "CONFIG_SOC_" board/ | xargs grep -l "CONFIG_SOC_MYFAMILY"
$ ls board/vendor/
For our EP-Falcon example, suppose the closest match is an existing reference board
board/vendor/ep-refdesign built on the same SoC. The standard first move is a straight
copy, which we’ll turn into a real, buildable EP-Falcon port in the next two lectures:
$ mkdir -p board/vendor/ep-falcon
$ cp -a board/vendor/ep-refdesign/. board/vendor/ep-falcon/
Common Mistakes And Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Board hangs before any console output | Copied SoC-level arch settings that don’t match your actual silicon revision |
Diff your SoC’s datasheet against the reference board’s arch-level config, not just board files |
| Build fails referencing an undefined board symbol | Board directory copied but not yet registered in Kconfig | Covered in the Kconfig lecture — the directory alone isn’t enough |
| Wrong reference board chosen | Matched on SoC name only, ignoring DRAM type/size differences | Confirm memory controller and boot-media configuration match too, not just the CPU |
Best Practices
- Never modify an existing, working board’s directory to “test” your port — always copy first.
A broken reference board helps nobody. - Keep a running diff against the reference board directory as you edit; it’s the fastest way to
see exactly what you’ve actually changed versus what you copied unnecessarily. - Read any README in the reference board’s directory before you start — vendors often leave
board-specific notes there that never made it into the main docs.
Summary And Key Takeaways
archis SoC-family code you rarely touch when porting to an already-supported SoC.boardis where your actual port lives — copy the closest reference board first.commonis shell/command code, not board code.- Configuration has moved mostly to Kconfig
defconfigfiles, with
include/configsheaders now holding only the leftover settings.
Conclusion
Porting U-Boot is far less intimidating once you see it as editing one board directory that
started life as a copy of a working one, not writing bootloader code from a blank page. With the
directory layout clear, the next lecture makes EP-Falcon’s copied board directory actually buildable
by wiring it into Kconfig.
Frequently Asked Questions
Do I ever need to edit files under arch/ for a normal board port?
Only if your board uses a genuinely new SoC variant not yet supported — porting to an
already-supported SoC almost never requires touching arch.
What if no existing board is close to mine?
Start from the closest SoC-family board you can find rather than a completely unrelated one —
even an imperfect match saves far more work than starting from nothing.
Is copying a board directory enough to build for the new board?
No — the directory has to be registered with Kconfig and given a defconfig before it’s
buildable, which is the subject of the next lecture.
Why do some vendors group multiple boards under one subdirectory?
Boards sharing a common vendor and similar hardware often share helper code, so grouping them
avoids duplicating that logic across separate top-level directories.
Are include/configs headers deprecated?
Not entirely — most configuration has moved to Kconfig, but a header under
include/configs is still used for the remaining settings on most boards.
Continue The Free Embedded Systems Course
Next: wiring EP-Falcon into U-Boot’s Kconfig system.
