Was ist ein Device Tree?
August 7, 2026 · – Besucher
Wer schon einmal Linux auf einem eingebetteten Board zum Laufen bringen wollte, stößt früher oder später auf eine Datei mit der Endung .dts. Meist im denkbar ungünstigsten Moment: Das Board bootet nicht, auf dem Bildschirm passiert nichts, und jemand sagt, man solle „mal in den Device Tree schauen". Dieser Text handelt davon, was diese Datei ist, warum es sie gibt und wonach man darin eigentlich sucht.
Das Problem Dahinter
Auf einem Desktop findet der Kernel die Hardware weitgehend selbst. PCI und USB sind formal spezifizierte, aufzählbare Busse (1). Geräte melden sich mit einer Hersteller- und Produktkennung; ein Treiber listet die von ihm unterstützten Kennungen in einer Tabelle, und der Kernel ruft für jedes passende Gerät die probe()-Funktion dieses Treibers auf (2). Auf der USB-Seite läuft dasselbe über eine eigene Kennungstabelle (3). Niemand muss irgendjemandem vorab etwas mitteilen.
Eingebettete Boards haben diesen Luxus nicht. Die Kernel-Dokumentation zieht die Grenze unmissverständlich: In ein System-on-Chip integrierte Peripherie hängt an Bussen mit minimaler Infrastruktur, „im Gegensatz zu großen, formal spezifizierten wie PCI oder USB", und was diese Geräte gemeinsam haben, ist die direkte Adressierung von einem CPU-Bus aus (1). Ein I²C-Sensor oder ein an SPI hängender Displaycontroller meldet sich nicht; er sitzt einfach dort, an dieser Adresse, verdrahtet mit dieser Interruptleitung. Der Kernel erfährt davon nur, wenn es ihm jemand sagt.
Lange Zeit war dieses „jemand" der Code selbst. Für jedes Board gab es eine Datei im Kernel-Baum, die in C beschrieb, welches Gerät wo angeschlossen ist — der Kernel beschreibt diesen Weg bis heute, bei dem boardspezifischer Initialisierungscode die Geräte einzeln registriert (1). Auf ARM vermehrten sich diese Dateien so stark, dass Linus Torvalds die Lage 2011 in einer E-Mail scharf auseinandernahm und den ARM-Code für nicht mehr tragfähig erklärte. Der Ausweg, der sich durchsetzte, war der Device Tree.
Die Idee
Ein Device Tree ist eine Datendatei, die beschreibt, wie die Hardware verdrahtet ist. Es ist kein Code — er läuft nicht und wird nicht in den Kernel kompiliert. Es ist eine Beschreibung: An dieser Adresse sitzt ein Controller, er nutzt diese Interruptleitung, er wird von diesem Takt gespeist, und darunter hängen diese Geräte.
Das Format selbst beruht auf einer formalen Spezifikation (4). Baum heißt es, weil es wirklich einer ist. Er beginnt an einem Wurzelknoten, davon zweigen die Busse ab, und von diesen wiederum die daran angeschlossenen Geräte — ein Abbild der physischen Verdrahtung.
Statt eines erfundenen Beispiels hier ein echtes. Der folgende Knoten stammt aus k3-am62p-main.dtsi im U-Boot-Baum für TIs J722S/AM67A-Familie — also aus genau der Beschreibung, die ein AM67A-Board beim Booten tatsächlich liest:
main_i2c0: i2c@20000000 {
compatible = "ti,am64-i2c", "ti,omap4-i2c";
reg = <0x00 0x20000000 0x00 0x100>;
interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
power-domains = <&k3_pds 102 TI_SCI_PD_EXCLUSIVE>;
clocks = <&k3_clks 102 2>;
clock-names = "fck";
status = "disabled";
};
Jede Zeile hier bedeutet etwas. reg gibt die Adresse des Controllers in der Speicherkarte an — 0x20000000, Länge 0x100. Dass die Adressen als Zahlenpaare geschrieben werden, liegt am 64-Bit-Adressraum; die erste Zahl trägt die oberen 32 Bit. interrupts sagt, welche Interruptleitung genutzt wird; Namen wie GIC_SPI und IRQ_TYPE_LEVEL_HIGH stammen aus eingebundenen Header-Dateien — der Quelltext eines Device Tree ist also zwar reine Datenbeschreibung, läuft aber trotzdem durch den C-Präprozessor.
Bemerkenswert ist, dass compatible zwei Werte trägt: "ti,am64-i2c" und "ti,omap4-i2c". Die Liste wird vom Speziellsten zum Allgemeinsten gelesen. Der Kernel sucht einen auf am64 zugeschnittenen Treiber und fällt, wenn er keinen findet, auf den älteren, allgemeineren omap4-Treiber zurück (5). So kann ein neuer Chip laufen, ohne dass jemand einen neuen Treiber für ihn schreibt.
clocks und clock-names sagen, woher das Gerät seinen Takt bezieht: Ausgang 2 von Gerät 102 an &k3_clks, den der Treiber unter dem Namen "fck" findet. power-domains bindet nach derselben Logik die Power-Domain an. Das sind keine Frequenz-Einstellungen — sie beschreiben die Verdrahtung, welcher Takt wohin geht.
Die letzte Zeile ist wichtig: status = "disabled". Der Chiphersteller beschreibt die Peripherie, lässt sie aber ausgeschaltet, weil er nicht wissen kann, ob dieser I²C-Bus auf Ihrem Board überhaupt genutzt wird. Das Einschalten ist Sache der .dts des Boards. In der Board-Datei des J722S EVM sieht das so aus:
&main_i2c0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&main_i2c0_pins_default>;
clock-frequency = <400000>;
exp1: gpio@23 {
compatible = "ti,tca6424";
reg = <0x23>;
gpio-controller;
#gpio-cells = <2>;
};
};
Das führende &main_i2c0 ist eine Referenz auf den obigen Knoten — es legt keinen neuen an, sondern überschreibt den vorhandenen. status wird eingeschaltet, die Pins werden zugewiesen, die Geschwindigkeit auf 400 kHz gesetzt, und darunter kommt ein echtes Gerät: ein TCA6424-GPIO-Expander an Adresse 0x23 auf dem I²C-Bus. Weil der Controller #address-cells = <1> deklariert hat, wird diese Adresse mit einer einzigen Zahl geschrieben, anders als das reg des Controllers selbst.
So funktioniert ein Device Tree in der Praxis: Der Hersteller beschreibt das SoC einmal, und Sie sagen, was auf Ihrem Board daran hängt.
Die Eigenschaft, auf die es wirklich ankommt, ist compatible. Hier schaut der Kernel nach, um Treiber und Hardware zusammenzubringen. Ein Treiber sagt „ich bin kompatibel mit ti,tca6424", ein Knoten im Device Tree trägt dieselbe Zeichenkette, und der Kernel führt beide zusammen. Deshalb scheitern Tippfehler lautlos: Passt die Zeichenkette nicht, gibt es keinen Fehler — das Gerät taucht schlicht nie auf.
Im Hinterkopf behalten sollte man: Ein Device Tree beschreibt Hardware, nicht die Konfiguration eines Treibers. Der Leitfaden des Kernels zum Schreiben von Bindings betont genau diese Grenze (6). In der Praxis verschwimmt sie ständig, und sie ist das meistdiskutierte Thema auf den Kernel-Listen — „ist das wirklich eine Eigenschaft der Hardware, oder ist es Ihre Vorliebe?"
Die Dateien
Man sieht drei Endungen, und sie lassen sich leicht verwechseln (11).
.dts ist der Quelltext, den Sie schreiben, die für Menschen lesbare Form. .dtsi ist ein Fragment, das andere Dateien einbinden können — das „i" steht für include. Ein Chiphersteller beschreibt typischerweise das gesamte SoC in einer .dtsi; Sie schreiben eine kurze .dts für Ihr Board, binden jene ein und ergänzen nur das Boardspezifische.
.dtb ist die kompilierte Form — der Device Tree Blob. Das ist, was der Kernel tatsächlich liest. Der Compiler heißt dtc (7):
dtc -I dts -O dtb -o my-board.dtb my-board.dts
Er läuft auch in die andere Richtung, was bei der Fehlersuche Gold wert ist. Haben Sie eine funktionierende .dtb, können Sie hineinschauen:
dtc -I dtb -O dts -o dekodiert.dts my-board.dtb
Auf einem laufenden System lässt sich der Device Tree auch direkt aus dem Dateisystem lesen. Der Kernel legt den tatsächlich verwendeten Baum hier offen:
ls /proc/device-tree/
Das ist die schnellste Antwort auf die Frage „ist die Datei, die ich bearbeitet habe, auch die geladene?" Erstaunlich oft lautet sie nein.
Was Beim Booten Passiert
Die Reihenfolge ist diese: Der Bootloader — meist U-Boot (8) — lädt die .dtb in den Speicher, lädt den Kernel und übergibt ihm beim Start die Speicheradresse des Blobs in einem Register. Auf ARM sind dieses Register und die Bedingungen, die der Bootloader erfüllen muss, im Boot-Dokument des Kernels festgehalten (9). Bevor der Kernel irgendeinen Treiber hochfährt, liest er diesen Baum, entnimmt ihm die Geräte und ordnet die Treiber anhand der compatible-Zeichenketten zu.
Die praktische Folge: Kernel und Device Tree lassen sich getrennt aktualisieren. Ein neuer Sensor bedeutet selten, den Kernel neu zu bauen; meist genügt es, die .dtb zu aktualisieren. Das schneidet aber in beide Richtungen — weil sie unabhängig sind, können sie auseinanderlaufen. Startet man einen neuen Kernel mit einer alten .dtb, kommt das Board still und unvollständig hoch, und es dauert, bis man versteht, warum.
Dann gibt es noch Overlays: Fragmente, die zur Laufzeit auf einen bestehenden Baum gelegt werden (10). Genau das passiert, wenn Sie ein HAT auf einen Raspberry Pi stecken — der Hauptbaum bleibt, wie er ist, und ein kleines Fragment legt sich darüber. Nützlich, wenn das Basisboard immer dasselbe ist und nur das aufgesteckte Modul wechselt.
Wenn Etwas Nicht Funktioniert
Device-Tree-Fehler haben eine eigene Art von Tücke, weil sie meist gar keine Fehlermeldung erzeugen. Das Gerät ist einfach nicht da. In dieser Reihenfolge vorzugehen spart Zeit.
Prüfen Sie zuerst, ob das Gerät überhaupt angelegt wurde. Ist der Knoten unter /proc/device-tree vorhanden? Wenn nicht, liegt das Problem nicht in der Datei, die Sie bearbeitet haben — wahrscheinlich wird die falsche .dtb geladen, oder der Knoten ist eine Ebene höher mit status = "disabled" abgeschaltet. Letzteres kommt ständig vor: Peripherie kommt in den .dtsi-Dateien des Herstellers meist deaktiviert, und es wird erwartet, dass Sie sie in der .dts Ihres Boards einzeln mit status = "okay" einschalten.
Ist der Knoten da, hat sich aber kein Treiber angehängt, richtet sich der Blick auf die compatible-Zeichenkette. Suchen Sie die of_device_id-Tabelle im Quelltext des Treibers und vergleichen Sie Zeichen für Zeichen. Ein Komma, ein Bindestrich genügt.
Und noch etwas: dtc-Warnungen sind in den meisten Projekten abgeschaltet, dabei sagen sie wirklich Nützliches. Abweichungen zwischen Adresse und reg, ein fehlendes #address-cells — genau die Sorte Fehler, die beim Booten zum stillen Ausfall wird. Es lohnt sich, die Warnungen beim Bauen eingeschaltet zu lassen.
Zum Schluss
Was einen Device Tree schwer fassbar macht, ist, dass niemand vorab sagt, was er eigentlich ist. Er sieht aus wie eine Konfigurationsdatei, ist aber keine; er sieht aus wie ein Treiber, ist aber auch das nicht. Am hilfreichsten ist es, ihn als Vertrag zwischen Hardware und Software zu begreifen: wie das Board verdrahtet ist, aufgeschrieben in einer Sprache, die der Kernel versteht.
Sitzt er einmal in diesem Rahmen, folgt der Rest von selbst. Warum compatible so entscheidend ist, warum die .dtb getrennt vom Kernel lebt, warum /proc/device-tree die erste Anlaufstelle ist, wenn etwas fehlt — alles ergibt sich aus derselben Idee.
Weiterführende Artikel
- Die Boot-Kette von eingebettetem Linux — wie der Bootloader die Kontrolle an den Kernel übergibt
- Was ist DFU? — Firmware über USB flashen, wenn kein anderer Weg bleibt
Quellen
- Platform Devices and Drivers — warum SoC-Peripherie nicht wie PCI und USB aufzählbar ist und wie boardspezifischer Code sie registriert.
- How To Write Linux PCI Drivers — die
pci_device_id-Tabelle und derprobe()-Aufruf für passende Geräte. - USB Device Drivers — kennungsbasierte Treiberzuordnung auf der USB-Seite.
- Devicetree Specification — die formale Definition des Formats.
- Linux and the Devicetree — wie der Kernel den Baum liest und Treiber zuordnet.
- Writing Devicetree Bindings — die Grenze zwischen Hardwarebeschreibung und Treiberkonfiguration.
- dtc — Device Tree Compiler — das Repository des Compilers und seine Verwendung.
- U-Boot: Devicetree Control — wie U-Boot mit dem Device Tree umgeht.
- Booting ARM Linux — in welchem Register der Bootloader dem Kernel den Blob übergibt.
- Devicetree Overlay Notes — wie Overlays zur Laufzeit angewendet werden.
- Device Tree Reference (eLinux) — Community-Referenz mit durchgearbeiteten Beispielen.
Kommentare