machine number
MACHINE_START
free linux device drivers course
free embedded systems course
Most modern ARM boards are ported using a device tree, as we covered in the previous lecture.
But plenty of legacy boards, and some vendor BSPs still shipping today, use an older mechanism
entirely: the ARM machine number. This lecture, part of our free linux device
drivers course, explains how board identification worked before device trees existed, why
you still need to understand it, and walks through the full legacy porting workflow — Kconfig
entry, board file, machine number registration, and the MACHINE_START structure that ties
it all together.
What You Will Learn
- Why the ARM machine number mechanism exists, and why device trees eventually replaced it
- How to register a new board configuration in Kconfig and wire its board file into the build
- How the machine number is registered and how it reaches the kernel via the bootloader
- What the
MACHINE_START/MACHINE_ENDstructure actually does at boot time - When you’ll still realistically encounter this legacy mechanism today
Prerequisites
- The previous lecture on device-tree-based board porting (this lecture covers the older
alternative to that same problem) - A working ARM cross-compilation setup for your kernel source tree
- Basic familiarity with Kconfig and Makefile syntax in the kernel build system
Why This Mechanism Still Matters
Before device trees became standard on ARM (this landed broadly in the kernel around the 3.x
series), every board needed a unique numeric identifier called a machine number. The
bootloader passed this number to the kernel in a CPU register, and the kernel used it to pick
which board-specific C code should run. Device trees replaced this for new designs because they
describe hardware in data instead of code, avoiding a kernel rebuild for every hardware tweak. But
you’ll still meet the machine number mechanism when working with genuinely old hardware, some
long-lived vendor BSPs that never migrated, or when reading historical kernel commit history to
understand how a mature board’s support evolved.
The Legacy Porting Workflow, Step By Step
arch/arm/mach-<family>/Kconfig
-> new CONFIG_MACH_EP_LEGACY option2. Add board-*.c source file
arch/arm/mach-<family>/board-ep-legacy.c
-> board init, static peripheral setup3. Wire it into the Makefile
arch/arm/mach-<family>/Makefile
-> obj-$(CONFIG_MACH_EP_LEGACY) += board-ep-legacy.o4. Register a machine number
arch/arm/tools/mach-types
-> unique numeric ID for this board5. Bootloader passes machine number
in register r1 at kernel entry
6. Kernel matches it against
MACHINE_START(…) / MACHINE_END
-> selects the correct board init code
Step 1 — Add a Kconfig Entry
Every board needs a Kconfig option that a developer can enable in menuconfig, and that
declares its dependency on the SoC it’s actually built on. Add this to your SoC family’s Kconfig
file:
# arch/arm/mach-example/Kconfig
config MACH_EP_LEGACY
bool "EP Legacy board"
depends on SOC_EXAMPLE_SOC
default n
Step 2 — Write the Board File
Each legacy board has its own small source file containing board-specific setup — which
peripherals are wired up, what clock configuration to apply, any GPIO muxing that isn’t handled
elsewhere. Keep it minimal; this is glue code, not driver code:
/* arch/arm/mach-example/board-ep-legacy.c */
#include <linux/init.h>
#include <asm/mach/arch.h>
static void __init ep_legacy_init(void)
{
/* Register this board's platform devices and
* any static peripheral configuration here. */
}
MACHINE_START(EP_LEGACY, "ep_legacy")
.atag_offset = 0x100,
.init_machine = ep_legacy_init,
MACHINE_END
The string passed to MACHINE_START is a human-readable board name, and the first argument
(EP_LEGACY) is a symbolic constant the build system turns into the numeric machine ID behind
the scenes.
Step 3 — Wire the Board File Into the Build
Add a conditional build rule so your new board file only compiles in when its Kconfig option is
enabled:
# arch/arm/mach-example/Makefile
obj-$(CONFIG_MACH_EP_LEGACY) += board-ep-legacy.o
Step 4 — Register a Machine Number
Since there’s no device tree to identify the board, the kernel needs a unique number, assigned
by the machine’s bootloader and matched against a kernel-side table. Real machine numbers are
requested from the community’s official ARM machine number registry so no two boards worldwide
collide. For local development and testing, engineers commonly reserve a number from the
unallocated range and add it to the local machine-types table:
# arch/arm/tools/mach-types
#
# machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
ep_legacy MACH_EP_LEGACY EP_LEGACY 9001
Building the kernel regenerates the machine-types header from this table:
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- include/generated/mach-types.h
Step 5 — Tell the Bootloader Which Number to Pass
On a U-Boot-based board, the machine number is typically fixed in the board’s U-Boot
configuration, not something you set at the shell prompt. Conceptually, U-Boot loads this
constant into register r1 immediately before jumping into the kernel — the kernel’s ARM
entry code reads that register value before anything else happens, and uses it to pick the
matching MACHINE_START block.
Step 6 — Build and Boot Test
$ make ARCH=arm ep_legacy_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
A successful match shows up clearly in the early boot log:
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 6.9.0 (builder@ep) #1 SMP PREEMPT
[ 0.000000] Machine: ep_legacy
...
If the bootloader passes a machine number the kernel doesn’t recognise, you’ll see an explicit
error at the very top of the boot log instead, before anything else initialises — which is
actually a useful, fast failure signal compared to a device tree mismatch that might boot far
enough to look almost right before something silently breaks.
Error: unrecognized/unsupported machine ID (r1 = 0x0000232a).
Available machine support:
...
Device Tree vs Machine Number: Quick Comparison
| Aspect | Device Tree | Legacy Machine Number |
|---|---|---|
| Hardware description | Data file (.dtb), separate from kernel binary |
C code (board-*.c), compiled directly into the kernel |
| Changing a pin or peripheral | Edit and rebuild only the .dtb |
Edit C code and rebuild the kernel image |
| Board identification | String-based compatible property |
Single global numeric ID, centrally allocated |
| One kernel image, many boards | Straightforward — swap the .dtb |
Requires every board’s code built into one image, selected at runtime by number |
| Current status | Standard approach for new ARM boards | Legacy; still found in old trees and some long-lived vendor BSPs |
Real-World Use Cases
- Maintaining a legacy product line — a device shipped a decade ago on an old kernel
that predates device tree support for its SoC, still receiving security patches today - Reading kernel history — understanding old commits and old vendor BSP source trees when
diagnosing why a modern port behaves differently from the original board support - Academic and reference purposes — understanding this mechanism gives real insight into
why device trees were adopted, and what specific pain they were designed to remove
Common Mistakes and Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| “unrecognized/unsupported machine ID” at boot | Bootloader’s configured machine number doesn’t match any MACHINE_START block built into this kernel image |
Confirm the number in your bootloader config matches exactly what’s registered in arch/arm/tools/mach-types, and that your board’s Kconfig option was actually enabled |
Board file compiles but nothing in init_machine ever runs |
Kconfig option not selected, so board-*.o was never linked into the final image |
Check .config for CONFIG_MACH_EP_LEGACY=y and confirm the Makefile’s obj-$(...) line is correct |
| Two developers on the same team pick the same “spare” machine number | Using an unofficial, locally-improvised number without registering it centrally | Only use officially-requested machine numbers for anything beyond local, single-developer testing |
Best Practices
- Prefer a device tree port for any new board design — the legacy mechanism exists here purely
for understanding and maintaining older hardware - Keep board files minimal — push real driver logic into proper subsystem drivers, not into
init_machine - Never reuse an unofficial machine number in anything that leaves your own development bench
- When maintaining a legacy board, document clearly why it hasn’t migrated to device tree, so
the next engineer doesn’t waste time assuming it’s a device-tree board by mistake
Summary and Key Takeaways
- The legacy ARM machine number mechanism identifies a board with a single global numeric ID,
passed from the bootloader to the kernel in a CPU register - A legacy port needs a Kconfig entry, a board file, a Makefile rule, and a registered machine
number — four pieces, all compiled directly into the kernel image MACHINE_START/MACHINE_ENDis the structure that ties a machine number to its
board-specific init function- Device trees replaced this mechanism for new boards because they move hardware description
out of compiled code and into a swappable data file
Conclusion
The legacy machine number mechanism isn’t something you’ll reach for on a new design, but
understanding it gives real perspective on why device trees exist and what specific engineering
pain they solved — every hardware tweak used to mean a kernel rebuild. If you maintain older
hardware, or you’re reading through a decade of ARM kernel history, this workflow is exactly what
you’ll encounter. That wraps up board porting in this free embedded systems course —
next we move on to kernel configuration tuning for production images.
Frequently Asked Questions
Is the machine number mechanism still supported in modern kernels?
Yes, the infrastructure is still present and boards using it still boot, but it’s considered
legacy. New ARM board support submitted to the kernel today is expected to use device trees, not
new machine numbers.
Can a board support both a device tree and a machine number at the same time?
Some transitional board support historically did this to ease migration, but it adds real
complexity for no lasting benefit on new work. For any board you’re actively developing, pick one
mechanism — device tree, for anything new — and commit to it.
Where do I get an officially assigned machine number?
Historically, machine numbers were requested through the community-maintained ARM Linux machine
registry. Since device trees are now the standard approach, new machine number requests are
extremely rare in current kernel development, and are essentially only relevant for legacy
maintenance work.
What exactly does MACHINE_START do?
It’s a macro that builds a data structure associating a symbolic machine ID with board-specific
callbacks — most importantly init_machine, the function that runs board setup code early in
boot once the kernel has matched the bootloader-supplied machine number against this entry.
Why does the “unrecognized machine ID” error show a hex register value?
That value is exactly what the bootloader placed in register r1 before jumping to the kernel —
showing it raw makes it straightforward to check it against your bootloader configuration and the
kernel’s mach-types table to find the mismatch.
