Device Tree in der Praxis: Hardware beschreiben, statt Treiber zu raten
Wie der Device Tree in Embedded-Linux-Projekten wirklich funktioniert: compatible-Matching, pinctrl und Clocks, Overlays für Gerätevarianten und die Debug-Rezepte, mit denen sich Bring-up-Probleme eingrenzen lassen.
Kurz gesagt: Der Device Tree beschreibt die Hardware, die sich nicht selbst anmelden kann – also fast alles, was auf einem typischen ARM-SoC hängt. Er ist keine Konfigurationsdatei, in der man so lange Werte ändert, bis etwas funktioniert, sondern ein Vertrag zwischen Board und Treiber. Wer diesen Vertrag liest, statt ihn zu erraten, verkürzt ein Board-Bring-up erheblich: Ein großer Teil der Probleme, die zunächst wie Treiberfehler aussehen, sind falsch beschriebene Ressourcen.
Warum es den Device Tree überhaupt gibt
Auf einem PC meldet sich Hardware selbst: PCIe und USB liefern Kennungen, aus denen der Kernel den passenden Treiber ableitet. Ein I2C-Sensor an einem SoC tut das nicht. Er hängt an einer Adresse an einem Bus, wird von einem bestimmten Regulator versorgt, hängt an einem Interrupt-Pin – und nichts davon lässt sich durch Nachfragen herausfinden.
Früher stand diese Beschreibung als C-Code im Kernel, ein „board file“ je Platine. Das skalierte nicht: Jede Hardwarevariante bedeutete Kernel-Code, der gepflegt und mitgeführt werden musste. Der Device Tree trennt beides. Der Kernel liefert die Treiber, der Device Tree beschreibt, was auf dieser Platine tatsächlich verbaut ist und wie es verdrahtet wurde. Dieselbe Kernel-Binärdatei bedient damit viele Boards – ein Grundpfeiler jeder wartbaren Embedded-Linux-Plattform.
Die Bausteine
Die Quellen sind Textdateien: .dts für ein konkretes Board und .dtsi für alles, was mehrere Boards teilen – üblicherweise die Beschreibung des SoC selbst, die der Hersteller mitliefert. Der Compiler dtc übersetzt sie in ein .dtb, ein kompaktes Binärformat. Der Bootloader lädt dieses Blob in den Speicher und übergibt es dem Kernel, der daraus beim Start seinen Gerätebaum aufbaut.
Innerhalb des Baums tragen wenige Eigenschaften die Hauptlast:
compatibleist der Schlüssel. Der String – etwa"ti,tmp102"– wird gegen dieof_match_tableder Treiber gehalten. Passt nichts, passiert nichts: kein Fehler, nur ein Gerät, das niemand bedient.reggibt die Adresse an: eine Speicheradresse samt Länge bei speichergemappten Blöcken, eine Bus-Adresse bei I2C oder SPI.interruptsverweist auf den Interrupt-Controller samt Nummer und Auslöseart.clocksund Regulator-Verweise beschreiben Takt und Versorgung – fehlen sie, bricht die Initialisierung des Treibers ab.pinctrllegt fest, welche Funktion ein physischer Pin bekommt. Ein SoC-Pin kann GPIO, I2C oder PWM sein; ohne passende Zuordnung liegt ein korrekt beschriebenes Gerät an einem Pin, der etwas anderes tut.statusschaltet Knoten scharf. SoC-dtsi-Dateien definieren Schnittstellen üblicherweise mitdisabled; das Board-dts aktiviert das, was tatsächlich bestückt ist.
Ein konkreter Blick: einen Sensor ergänzen
Angenommen, auf der Platine sitzt ein Temperatursensor an I2C-Bus 1, Adresse 0x48, mit einem Alarm-Pin an einem GPIO. Im Board-dts sieht das so aus:
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
clock-frequency = <400000>;
status = "okay";
temp_sensor: temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio2>;
interrupts = <13 IRQ_TYPE_LEVEL_LOW>;
#thermal-sensor-cells = <1>;
};
};
Drei Details verdienen Aufmerksamkeit. Der Bus wird mit status = "okay" überhaupt erst aktiviert – der Sensorknoten allein genügt nicht. Die Zahl hinter dem @ im Knotennamen muss mit dem ersten Wert von reg übereinstimmen; weicht sie ab, warnt dtc, und der Baum wird schwer lesbar. Und der pinctrl-0-Verweis sorgt dafür, dass die beiden SoC-Pins tatsächlich als I2C arbeiten statt als GPIO.
Bindings: das übersehene Vertragsdokument
Zu jedem compatible-String gehört eine Binding-Beschreibung im Kernel-Baum unter Documentation/devicetree/bindings/. Sie legt fest, welche Eigenschaften Pflicht sind, welche optional und welche Werte zulässig sind. Neuere Bindings liegen als YAML-Schema vor, was maschinelle Prüfung erlaubt:
make dt_binding_check # prüft die Bindings selbst
make dtbs_check # prüft die Device Trees gegen die Bindings
Diese beiden Kommandos ersparen viel Suchen. Sie finden fehlende Pflichteigenschaften, Tippfehler in Namen und Werte außerhalb des erlaubten Bereichs – also genau die Klasse von Fehlern, die sich sonst erst zur Laufzeit als schweigender Treiber äußert.
Debugging: was tatsächlich im Gerät ankommt
Die wichtigste Erkenntnis beim Bring-up lautet, dass die Quelldatei auf dem Entwicklungsrechner und der Baum im laufenden Gerät zwei verschiedene Dinge sind. Dazwischen liegen Compiler, Bootloader und womöglich ein Overlay. Der Kernel legt den tatsächlich verwendeten Baum offen:
# Was der Kernel wirklich sieht
ls /sys/firmware/devicetree/base/ # /proc/device-tree zeigt auf dasselbe
# Den laufenden Baum zurück nach dts lesen
dtc -I fs -O dts /sys/firmware/devicetree/base > running.dts
# Wurde der Treiber gebunden?
ls /sys/bus/i2c/devices/1-0048/driver
dmesg | grep -i tmp102
Zeigt running.dts den Knoten nicht, ist das Problem im Build- oder Boot-Pfad zu suchen – falsches dtb, Overlay nicht angewandt, Bootloader lädt eine alte Datei. Ist der Knoten da, aber kein Treiber gebunden, liegt es an compatible, an fehlenden Ressourcen oder daran, dass der Treiber gar nicht im Kernel konfiguriert ist.
Häufige Fehlerbilder
| Symptom | Wahrscheinliche Ursache |
|---|---|
| Gerät taucht nirgends auf, keine Meldung | status steht noch auf disabled |
| Knoten vorhanden, kein Treiber gebunden | compatible passt zu keiner of_match_table; Treiber nicht einkompiliert |
probe bricht mit -EPROBE_DEFER ab | Abhängigkeit noch nicht bereit – meist Clock, Regulator oder GPIO-Controller |
| Bus funktioniert nicht, obwohl korrekt beschrieben | pinctrl fehlt oder mehrere Knoten beanspruchen denselben Pin |
| Interrupts kommen nie an | Falscher interrupt-parent, falsche Nummer oder falscher Auslösetyp |
| Sporadische Fehler unter Last | Taktrate am Bus zu hoch, Pull-ups oder Signalintegrität – nicht mehr allein ein Device-Tree-Thema |
Der Fall -EPROBE_DEFER verdient eine Einordnung: Er ist kein Fehler, sondern der reguläre Weg des Kernels, eine Initialisierung zu vertagen, bis Abhängigkeiten bereit sind. Problematisch wird er erst, wenn er dauerhaft bestehen bleibt – dann fehlt eine Ressource wirklich.
Overlays für Gerätevarianten
Erscheint dieselbe Grundplatine in mehreren Ausprägungen – ein anderes Display, ein optionales Funkmodul –, lässt sich der Unterschied als Overlay beschreiben statt als vollständige zweite dts. Ein Overlay wird zu .dtbo übersetzt und entweder vom Bootloader auf den Basisbaum angewandt oder zur Laufzeit geladen:
# U-Boot: Basisbaum laden und Overlay auftragen
fdt addr ${fdt_addr_r}
fdt resize 8192
fdt apply ${fdtoverlay_addr_r}
Das hält Varianten sauber getrennt und erlaubt es, die passende Ausprägung anhand einer erkannten Hardware-Kennung auszuwählen. Der Preis ist ein zusätzlicher Schritt im Boot-Pfad, der ebenfalls fehlschlagen kann – weshalb sich für ein Produkt mit genau einer Hardwareausprägung die einfache, vollständige dts meist besser rechnet.
Device Trees im Yocto-Projekt pflegen
In einem Yocto-Setup gehört der Board-Device-Tree in den eigenen Layer, nicht in eine lokal geänderte Kopie des Kernel-Baums. Üblich ist ein Eintrag der zu bauenden Bäume über KERNEL_DEVICETREE und die Zulieferung der eigenen Quelldatei oder eines Patches über ein Kernel-Bbappend. Der Vorteil zeigt sich beim nächsten Kernel-Sprung: Die eigene Beschreibung bleibt sichtbar, versioniert und getrennt vom Upstream-Stand. Wie wir solche Layer schneiden, steht unter Yocto BSP & Distributionen; die Wahl des Build-Systems selbst haben wir in Yocto oder Buildroot verglichen.
Ein letzter Hinweis aus der Praxis: Device-Tree-Änderungen sind billig zu machen und teuer zu verifizieren. Ob nach einer Anpassung noch alle Schnittstellen arbeiten, zeigt sich zuverlässig nur auf echter Hardware – der Grund, warum wir Bring-up-Arbeit mit automatisierten Läufen auf dem Embedded Testrack absichern.
Fazit
Der Device Tree ist kein notwendiges Übel, sondern die Stelle, an der eine Platine ihre Hardware erklärt. Behandeln Sie ihn entsprechend: Lesen Sie die Bindings, prüfen Sie mit dtbs_check, vergleichen Sie im Zweifel den laufenden Baum mit der Quelle, und halten Sie die Beschreibung im eigenen Layer statt im Kernel-Baum. Damit verschwindet der größte Teil des Rätselratens aus dem Bring-up. Wenn ein Board partout nicht durchstartet oder ein Treiber schweigt, schauen wir gerne mit Ihnen darauf – Device-Tree- und Kernel-Themen sind auch Teil unserer Schulungen.
Häufige Fragen
- Warum probed mein Treiber nicht, obwohl der Knoten im Device Tree steht?
- Drei Ursachen decken die meisten Fälle ab. Erstens `status`: Ein von einer dtsi geerbtes `disabled` muss im Board-dts ausdrücklich auf `okay` gesetzt werden. Zweitens der `compatible`-String, der exakt zu einem Eintrag in der `of_match_table` des Treibers passen muss. Drittens fehlende Ressourcen – ohne die erwarteten Clocks, Regulatoren oder pinctrl-Einträge bricht die probe-Funktion ab, meist mit einer Meldung im dmesg.
- Wann lohnt sich ein Device Tree Overlay gegenüber einer eigenen dts?
- Overlays lohnen sich, wenn dieselbe Grundplatine in Varianten erscheint oder Erweiterungen zur Laufzeit erkannt werden – etwa unterschiedliche Displays oder aufsteckbare Module. Für ein festes Produkt mit einer Hardware-Ausprägung ist eine vollständige, versionierte dts einfacher zu überblicken und zu prüfen.
- Gehört der Device Tree in den Kernel-Baum oder in den Yocto-Layer?
- In den eigenen Layer. Ein Board-dts als Patch im Kernel-Rezept oder als eigene Quelldatei im Produkt-Layer bleibt bei einem Kernel-Update nachvollziehbar, während Änderungen direkt im Kernel-Baum beim nächsten Versionssprung verloren gehen oder mühsam neu aufgetragen werden müssen.
Alexander Nassian
Geschäftsführer, bitshift dynamics
Entwickelt mit seinem Team hardwarenahe Software für Embedded-Produkte – C++, Qt/QML, Embedded Linux und das Yocto Project. bitshift dynamics arbeitet seit 2005 in diesem Feld.