Che Cos'è un Device Tree?
August 7, 2026 · – visitatori
Se hai mai provato a far partire Linux su una scheda embedded, prima o poi ti imbatti in un file che finisce per .dts. Di solito nel momento peggiore: la scheda non si avvia, sullo schermo non compare nulla, e qualcuno ti dice di "guardare il device tree". Questo articolo parla di cos'è quel file, perché esiste e cosa ci stai davvero cercando dentro.
Il Problema Che Risolve
Su un computer da scrivania il kernel trova l'hardware praticamente da solo. PCI e USB sono bus formalmente specificati ed enumerabili (1). I dispositivi si annunciano con un identificativo di produttore e di prodotto; un driver elenca in una tabella gli identificativi che supporta, e il kernel chiama la funzione probe() di quel driver per ogni dispositivo che corrisponde a una voce (2). Sul lato USB il meccanismo funziona allo stesso modo, con una propria tabella di identificativi (3). Nessuno deve dire niente a nessuno in anticipo.
Le schede embedded non hanno questo lusso. La documentazione del kernel traccia la distinzione senza giri di parole: le periferiche integrate in un system-on-chip pendono da bus con infrastruttura minima, "a differenza di quelli grandi e formalmente specificati come PCI o USB", e ciò che quei dispositivi hanno in comune è l'indirizzamento diretto da un bus della CPU (1). Un sensore I²C o un controller di display attaccato a SPI non si annuncia; sta semplicemente lì, a quell'indirizzo, cablato su quella linea di interrupt. L'unico modo perché il kernel lo sappia è che qualcuno glielo dica.
Per molto tempo quel qualcuno è stato il codice stesso. Ogni scheda aveva un file dentro l'albero del kernel che descriveva in C quale dispositivo fosse collegato dove — il kernel documenta ancora questa strada, in cui il codice di inizializzazione specifico della scheda registra i dispositivi uno a uno (1). Su ARM questi file si sono moltiplicati a tal punto che nel 2011 Linus Torvalds ha smontato la situazione in una e-mail, dicendo che il codice ARM stava andando verso qualcosa di insostenibile. La via d'uscita adottata è stata il device tree.
L'Idea
Un device tree è un file di dati che descrive come è cablato l'hardware. Non è codice — non viene eseguito e non viene compilato dentro il kernel. È una descrizione: a questo indirizzo c'è un controller, usa questa linea di interrupt, è alimentato da questo clock, e sotto di lui pendono questi dispositivi.
Il formato stesso poggia su una specifica formale (4). Si chiama albero perché lo è davvero. Parte da un nodo radice, da lì si diramano i bus, e da questi i dispositivi collegati — rispecchiando come l'hardware è fisicamente connesso.
Invece di un esempio inventato, eccone uno vero. Il nodo qui sotto viene da k3-am62p-main.dtsi, nell'albero U-Boot per la famiglia J722S/AM67A di TI — la descrizione che una scheda AM67A legge effettivamente all'avvio:
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";
};
Ogni riga qui significa qualcosa. reg dà l'indirizzo del controller nella mappa di memoria — 0x20000000, lunghezza 0x100. Gli indirizzi sono scritti come coppie di numeri perché lo spazio di indirizzamento è a 64 bit; il primo numero porta i 32 bit alti. interrupts dice quale linea di interrupt viene usata; nomi come GIC_SPI e IRQ_TYPE_LEVEL_HIGH vengono da header inclusi, quindi anche se il sorgente di un device tree è puro dato, passa comunque dal preprocessore C.
Vale la pena notare che compatible porta due valori: "ti,am64-i2c" e "ti,omap4-i2c". La lista si legge dal più specifico al più generico. Il kernel cerca un driver specifico per am64 e, se non lo trova, ripiega sul driver omap4, più vecchio e generale (5). Così un chip nuovo può funzionare senza che nessuno scriva un driver apposta per lui.
clocks e clock-names dicono da dove il dispositivo prende il clock: l'uscita 2 del dispositivo 102 su &k3_clks, che il driver troverà sotto il nome "fck". power-domains collega il dominio di alimentazione con la stessa logica. Non sono impostazioni di frequenza — descrivono il cablaggio, quale clock va dove.
L'ultima riga conta: status = "disabled". Il produttore del chip descrive la periferica ma la lascia spenta, perché non può sapere se quel bus I²C sia usato sulla tua scheda. Accenderla è compito del .dts della scheda. Nel file di scheda della J722S EVM appare così:
&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>;
};
};
Il &main_i2c0 iniziale è un riferimento al nodo di sopra — non crea un nodo nuovo, scrive sopra quello esistente. status viene acceso, i pin assegnati, la velocità fissata a 400 kHz, e sotto viene aggiunto un dispositivo reale: un espansore GPIO TCA6424 all'indirizzo 0x23 sul bus I²C. Poiché il controller ha dichiarato #address-cells = <1>, quell'indirizzo si scrive con un solo numero, a differenza del reg del controller stesso.
È così che un device tree funziona in pratica: il produttore descrive il SoC una volta, e tu dici cosa è collegato alla tua scheda.
La proprietà che conta davvero è compatible. È lì che il kernel guarda per accoppiare un driver a un pezzo di hardware. Un driver dice "sono compatibile con ti,tca6424", un nodo del device tree porta la stessa stringa, e il kernel unisce i due. Ecco perché i refusi falliscono in silenzio: se la stringa non corrisponde non c'è errore — il dispositivo semplicemente non compare mai.
Da tenere a mente: un device tree descrive hardware, non la configurazione di un driver. La guida del kernel alla scrittura dei binding insiste proprio su questo confine (6). Nella pratica la linea si confonde di continuo, ed è l'argomento più discusso sulle liste del kernel — "questa è davvero una proprietà dell'hardware, o è una tua preferenza?"
I File
Vedrai tre estensioni, ed è facile confonderle (11).
.dts è il sorgente che scrivi, la forma leggibile da un umano. .dtsi è un frammento che altri file possono includere — la "i" sta per include. Un produttore di chip descrive tipicamente l'intero SoC in un .dtsi; tu scrivi un .dts breve per la tua scheda, lo includi e aggiungi solo ciò che è specifico della tua.
.dtb è la forma compilata — il device tree blob. È questo che il kernel legge davvero. Il compilatore è dtc (7):
dtc -I dts -O dtb -o my-board.dtb my-board.dts
Funziona anche nel verso opposto, cosa preziosissima quando insegui un problema. Se hai un .dtb funzionante, puoi aprirlo e guardarci dentro:
dtc -I dtb -O dts -o decodificato.dts my-board.dtb
Su un sistema in funzione puoi anche leggere il device tree direttamente dal filesystem. Il kernel espone qui l'albero che sta effettivamente usando:
ls /proc/device-tree/
È la risposta più rapida a "il file che ho modificato è il file che è stato caricato?" In una percentuale sorprendente di casi la risposta è no.
Cosa Succede all'Avvio
L'ordine è questo: il bootloader — di solito U-Boot (8) — carica il .dtb in memoria, carica il kernel e, mentre lo avvia, gli passa l'indirizzo di memoria del blob in un registro. Su ARM quel registro e le condizioni che il bootloader deve rispettare sono fissati nel documento di avvio del kernel (9). Prima di avviare un solo driver, il kernel legge quell'albero, ne estrae i dispositivi e accoppia i driver in base alle stringhe compatible.
La conseguenza pratica è che kernel e device tree si possono aggiornare separatamente. Aggiungere un sensore raramente significa ricompilare il kernel; aggiornare il .dtb di solito basta. Ma taglia da entrambe le parti — essendo indipendenti, possono divergere. Avvia un kernel nuovo con un .dtb vecchio e la scheda si alza silenziosamente incompleta, e ci vuole un po' per capire perché.
C'è poi l'idea degli overlay: frammenti applicati sopra un albero esistente a runtime (10). È quello che succede quando innesti un HAT su un Raspberry Pi — l'albero principale resta com'è e un piccolo frammento gli si sovrappone. Utile quando la scheda base è sempre la stessa e cambia solo il modulo innestato.
Quando Qualcosa Non Funziona
I guasti di device tree hanno una sgradevolezza tutta loro, perché di solito non producono alcun errore. Il dispositivo semplicemente non c'è. Procedere in questo ordine fa risparmiare tempo.
Comincia col verificare se il dispositivo sia stato creato. Il nodo c'è sotto /proc/device-tree? Se no, il problema non è nel file che hai modificato — molto probabilmente viene caricato il .dtb sbagliato, oppure il nodo è spento un livello più su con status = "disabled". Questo secondo caso capita di continuo: le periferiche arrivano di norma disabilitate nei file .dtsi del produttore, e ci si aspetta che tu le accenda una a una con status = "okay" nel .dts della tua scheda.
Se il nodo c'è ma nessun driver si è agganciato, punta l'occhio sulla stringa compatible. Trova la tabella of_device_id nel sorgente del driver e confronta le stringhe carattere per carattere. Basta una virgola, un trattino.
E poi questo: gli avvisi di dtc arrivano disattivati nella maggior parte dei progetti, e dicono cose davvero utili. Discrepanze tra indirizzo e reg, un #address-cells mancante — esattamente il tipo di guasto che diventa avaria silenziosa all'avvio. Vale la pena tenere gli avvisi accesi quando compili.
Per Chiudere
Ciò che rende difficile afferrare un device tree è che nessuno dice all'inizio cosa sia davvero. Sembra un file di configurazione, ma non lo è; sembra un driver, ma nemmeno. L'inquadramento più utile è pensarlo come il contratto tra hardware e software: come la scheda è cablata, messo per iscritto in una lingua che il kernel capisce.
Una volta che si assesta in quella cornice, il resto segue. Perché compatible è così critico, perché il .dtb vive separato dal kernel, perché /proc/device-tree è il primo posto dove guardare quando manca qualcosa — tutto discende dalla stessa idea.
Letture Correlate
- la catena di avvio di Linux embedded — come il bootloader passa il controllo al kernel
- Cos'è il DFU? — scrivere il firmware via USB quando non resta altra via
Riferimenti
- Platform Devices and Drivers — perché le periferiche di un SoC non sono enumerabili come PCI e USB, e come le registra il codice specifico della scheda.
- How To Write Linux PCI Drivers — la tabella
pci_device_ide la chiamata aprobe()per i dispositivi corrispondenti. - USB Device Drivers — accoppiamento dei driver per identificativo sul lato USB.
- Devicetree Specification — la definizione formale del formato.
- Linux and the Devicetree — come il kernel legge l'albero e accoppia i driver.
- Writing Devicetree Bindings — il confine tra descrivere hardware e configurare un driver.
- dtc — Device Tree Compiler — il repository del compilatore e il suo uso.
- U-Boot: Devicetree Control — come U-Boot gestisce il device tree.
- Booting ARM Linux — quale registro usa il bootloader per passare il blob al kernel.
- Devicetree Overlay Notes — come vengono applicati gli overlay a runtime.
- Device Tree Reference (eLinux) — riferimento della comunità con esempi svolti.
Commenti