What is Porting Linux Without Device Tree-Free Embedded Linux Training

Porting Linux Without Device Tree
Understanding the legacy ARM machine number mechanism
Free Linux Device Drivers Course
Chapter 4 · Porting & Configuring the Kernel
Legacy board bring-up, explained clearly
porting linux without device tree
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_END structure 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

Legacy (Non-Device-Tree) Board Port — Full Flow
1. Add Kconfig entry
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_END is 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.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *