Device Tree Overlay Not Applying: Debug Order
If a Device Tree overlay is listed in config.txt but the device never appears, suspect the driver last. Narrow it in document order: compile symbols → loader path → live tree. This post has no board war stories or prices. It follows the Linux Devicetree Dynamic Resolver Notes and Raspberry Pi overlays README plus config.txt dtoverlay.
What happens if you omit dtc -@?
One-line answer: Without -@, the .dtbo often lacks __symbols__ / __fixups__ metadata needed to resolve label/phandle references later. Overlay target = <&label> breaks. The in-kernel resolver expects a tree built with the proper dtc option and a /plugin/ tag so __fixups__ and __local_fixups__ exist.
Typical overlay header:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&i2c1>;
__overlay__ {
/* status, compatible, reg … */
};
};
};
Labels such as &i2c1 are not fixed numeric phandles at compile time. dtc -@ emits symbol and fixup nodes so the merger can patch target against the base DTB’s __symbols__. Overlay docs (U-Boot/kernel family) likewise say to compile both base and overlays with -@.
Recommended compile:
dtc -@ -I dts -O dtb -o myoverlay.dtbo myoverlay.dts
Checks:
fdtdump myoverlay.dtboordtc -I dtb -O dts myoverlay.dtbo— look for__fixups__/__symbols__/__local_fixups__.- Confirm
/plugin/;is present in the source. - If you need
#include, use a cpp-wrapping helper such as Raspberry Pikdtc. Plaindtcalone fails when headers are missing.
Omitting -@ often looks like “file exists, but no node after apply / apply fails.” Verify fixups in the blob first.
How do you verify the config.txt / dtoverlay path?
One-line answer: Firmware reads dtoverlay=name as a name without extension and loads …/overlays/name.dtbo. Wrong line syntax, wrong boot mount path, or wrong filename fails at the loader before the kernel driver stage.
From the Raspberry Pi overlays README:
dtoverlay=i2c-rtc,ds1307→ loads/boot/overlays/i2c-rtc.dtbo(as documented).- Parameters are comma-separated;
dtparam=can enable base interfaces. - Bare
dtoverlay=ends a parameter list; used first, it can suppress HAT overlay loading (official config docs).
Checklist:
| Check | Why |
|---|---|
dtoverlay=foo vs foo.dtbo | Config wants the name only; including .dtbo often misses the file |
| Overlay directory | Older layouts: /boot/overlays/; Bookworm-era: often /boot/firmware/overlays/ (boot partition mounted at /boot/firmware) |
Which config.txt | Edit the one on that boot partition (e.g. /boot/firmware/config.txt) |
| Line length / commas | Overlay params are comma-separated; spaces can break parsing (docs also note line-length limits) |
Empty device_tree= | Disables DT entirely — not an overlay-only issue |
# Bookworm-style example — confirm your mount first
ls -l /boot/firmware/overlays/myoverlay.dtbo
grep -nE '^(dtoverlay|dtparam|device_tree)' /boot/firmware/config.txt
Matching name + directory to the README rule clears many “not applying” cases. Runtime dtoverlay loads differ from firmware merge at boot (logs differ). For boot merge failures, check serial/firmware logs and OF-related dmesg lines.
What should you look at under /proc/device-tree?
One-line answer: The merged live tree the kernel holds. If the node/status/compatible/reg your overlay should add is missing, DT failed before the driver.
Useful checks:
# Did the expected path appear?
ls /proc/device-tree/soc/*/i2c@*/ 2>/dev/null
find /proc/device-tree -iname '*rtc*' 2>/dev/null | head
# status / compatible (binary props → strings)
cat /proc/device-tree/.../status; echo
strings /proc/device-tree/.../compatible
# Full dump (large)
dtc -I fs -O dts /proc/device-tree | less
How to read it:
- No node → revisit compile/path/
targetlabel / fixups. - Node exists but
status = "disabled"→ overlay/dtparamnever enabled it, or another fragment overwrote it. - Node okay, no device → then check
compatible↔ driver, module blacklist, power/clocks (out of scope here, but a clean branch). - Runtime-only overlays may show in
dtoverlay -l; boot-merged overlays may not. Final judgment is/proc/device-tree.
Pi dtoverlay/dtmerge docs also mention reading compatible from the live tree. Unless you change the order, symbols → config path → /proc/device-tree separates most documented “overlay not applying” failures.
FAQ
Q. Is /plugin/ enough without -@?
Resolver notes assume proper dtc options plus /plugin/ so __fixups__ / __local_fixups__ exist. Label target without -@ can fail merge or leave bad phandles.
Q. Missing from dtoverlay -l means failure?
Not always. Firmware-merged boot overlays may not appear in the runtime list. Use /proc/device-tree and the expected sysfs device.
Q. Pricing / board picks?
Out of scope. Use official overlay lists and bindings only.
Takeaways
dtc -@+/plugin/→ confirm__fixups__/__symbols__.- Confirm
dtoverlay=name and realoverlays/*.dtbopath (Bookworm: often/boot/firmware). - Confirm node / status / compatible under
/proc/device-tree, then debug the driver.
Sources: Dynamic Resolver Notes, rpi overlays README, config.txt dtoverlay.