O Que É um Device Tree?
August 7, 2026 · – visitantes
Se você já tentou subir Linux numa placa embarcada, mais cedo ou mais tarde encontra um arquivo terminado em .dts. Normalmente no pior momento possível: a placa não arranca, nada aparece na tela, e alguém diz para você "olhar o device tree". Este texto é sobre o que é esse arquivo, por que ele existe e o que você está de fato procurando dentro dele.
O Problema Que Ele Resolve
Num computador de mesa, o kernel encontra o hardware praticamente sozinho. PCI e USB são barramentos formalmente especificados e enumeráveis (1). Os dispositivos se anunciam com um identificador de fabricante e de produto; um driver lista numa tabela os identificadores que suporta, e o kernel chama a função probe() desse driver para cada dispositivo que casa com uma entrada (2). No lado USB o mecanismo funciona igual, com sua própria tabela de identificadores (3). Ninguém precisa avisar nada a ninguém de antemão.
Placas embarcadas não têm esse luxo. A própria documentação do kernel traça a linha sem rodeios: periféricos integrados a um system-on-chip pendem de barramentos com infraestrutura mínima, "em oposição aos grandes barramentos formalmente especificados como PCI ou USB", e o que esses dispositivos têm em comum é o endereçamento direto a partir de um barramento de CPU (1). Um sensor I²C ou um controlador de display pendurado no SPI não se anuncia; ele simplesmente está ali, naquele endereço, ligado àquela linha de interrupção. A única forma de o kernel saber disso é alguém dizer.
Por muito tempo esse alguém foi o próprio código. Cada placa tinha um arquivo dentro da árvore do kernel descrevendo em C qual dispositivo estava ligado onde — o kernel ainda documenta esse caminho, em que o código de inicialização específico da placa registra os dispositivos um a um (1). No ARM esses arquivos se multiplicaram tanto que em 2011 Linus Torvalds desmontou a situação num e-mail, dizendo que o código ARM caminhava para algo insustentável. A saída adotada foi o device tree.
A Ideia
Um device tree é um arquivo de dados que descreve como o hardware está ligado. Não é código — não executa e não é compilado dentro do kernel. É uma descrição: há um controlador neste endereço, ele usa esta linha de interrupção, é alimentado por este clock, e estes dispositivos pendem abaixo dele.
O formato em si se apoia numa especificação formal (4). Chama-se árvore porque realmente é uma. Começa num nó raiz, dele se ramificam os barramentos, e desses se ramificam os dispositivos ligados — espelhando como o hardware está fisicamente conectado.
Em vez de um exemplo inventado, aqui vai um real. O nó abaixo vem de k3-am62p-main.dtsi, na árvore do U-Boot para a família J722S/AM67A da TI — a descrição que uma placa AM67A de fato lê ao arrancar:
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";
};
Cada linha aqui significa algo. reg dá o endereço do controlador no mapa de memória — 0x20000000, com comprimento 0x100. Os endereços são escritos como pares de números porque o espaço de endereçamento é de 64 bits; o primeiro número carrega os 32 bits altos. interrupts diz qual linha de interrupção é usada; nomes como GIC_SPI e IRQ_TYPE_LEVEL_HIGH vêm de cabeçalhos incluídos, ou seja, embora o fonte de um device tree seja dado puro, ele ainda passa pelo pré-processador de C.
Vale notar que compatible carrega dois valores: "ti,am64-i2c" e "ti,omap4-i2c". A lista é lida do mais específico ao mais genérico. O kernel procura um driver específico para am64 e, não achando, recai no driver omap4, mais antigo e geral (5). Assim um chip novo pode funcionar sem que ninguém escreva um driver novo para ele.
clocks e clock-names dizem de onde o dispositivo tira seu clock: a saída 2 do dispositivo 102 em &k3_clks, que o driver encontrará sob o nome "fck". power-domains liga o domínio de energia com a mesma lógica. Isso não são ajustes de frequência — descrevem a fiação, qual clock vai para onde.
A última linha importa: status = "disabled". O fabricante do chip descreve o periférico mas o deixa desligado, porque não tem como saber se aquele barramento I²C é usado na sua placa. Ligá-lo é tarefa do .dts da placa. No arquivo de placa do J722S EVM fica assim:
&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>;
};
};
O &main_i2c0 no início é uma referência ao nó acima — não cria um nó novo, escreve por cima do existente. status é ligado, os pinos são atribuídos, a velocidade é fixada em 400 kHz, e um dispositivo real é acrescentado embaixo: um expansor GPIO TCA6424 no endereço 0x23 do barramento I²C. Como o controlador declarou #address-cells = <1>, esse endereço é escrito com um único número, diferente do reg do próprio controlador.
É assim que um device tree funciona na prática: o fabricante descreve o SoC uma vez, e você diz o que está ligado na sua placa.
A propriedade que realmente importa é compatible. É onde o kernel olha para casar um driver com uma peça de hardware. Um driver diz "sou compatível com ti,tca6424", um nó no device tree carrega a mesma string, e o kernel junta os dois. Por isso erros de digitação falham em silêncio: se a string não bate, não há erro — o dispositivo simplesmente nunca aparece.
Vale manter em mente: um device tree descreve hardware, não a configuração de um driver. O guia do kernel para escrever bindings insiste justamente nessa fronteira (6). A linha se borra o tempo todo na prática, e é o assunto mais discutido nas listas do kernel — "isto é mesmo uma propriedade do hardware, ou é a sua preferência?"
Os Arquivos
Você verá três extensões, e é fácil confundi-las (11).
.dts é o fonte que você escreve, a forma legível por humanos. .dtsi é um fragmento que outros arquivos podem incluir — o "i" é de include. Um fabricante de chip tipicamente descreve todo o SoC num .dtsi; você escreve um .dts curto para a sua placa, o inclui e acrescenta só o que é específico dela.
.dtb é a forma compilada — o device tree blob. É isso que o kernel de fato lê. O compilador é o dtc (7):
dtc -I dts -O dtb -o my-board.dtb my-board.dts
Ele também funciona no sentido inverso, o que é valiosíssimo ao caçar um problema. Se você tem um .dtb que funciona, dá para abrir e olhar dentro:
dtc -I dtb -O dts -o decodificado.dts my-board.dtb
Num sistema em funcionamento você também pode ler o device tree direto do sistema de arquivos. O kernel expõe aqui a árvore que está realmente usando:
ls /proc/device-tree/
Esta é a resposta mais rápida para "o arquivo que editei é o arquivo que foi carregado?" Numa proporção surpreendente das vezes, a resposta é não.
O Que Acontece no Arranque
A ordem é esta: o bootloader — normalmente o U-Boot (8) — carrega o .dtb na memória, carrega o kernel e, ao iniciá-lo, lhe passa o endereço de memória do blob num registrador. No ARM, esse registrador e as condições que o bootloader deve cumprir estão fixados no documento de arranque do kernel (9). Antes de subir qualquer driver, o kernel lê essa árvore, extrai dela os dispositivos e casa os drivers pelas strings compatible.
A consequência prática é que kernel e device tree podem ser atualizados separadamente. Acrescentar um sensor raramente significa recompilar o kernel; atualizar o .dtb costuma bastar. Mas isso corta dos dois lados — por serem independentes, podem descolar um do outro. Arranque um kernel novo com um .dtb antigo e a placa sobe silenciosamente incompleta, e leva um tempo até você descobrir por quê.
Há também a ideia de overlays: fragmentos aplicados sobre uma árvore existente em tempo de execução (10). É o que acontece quando você pluga um HAT num Raspberry Pi — a árvore principal permanece como está e um fragmento pequeno é sobreposto. Útil quando a placa base é sempre a mesma e só muda o módulo encaixado.
Quando Algo Não Funciona
Falhas de device tree têm um incômodo particular, porque em geral não produzem erro nenhum. O dispositivo simplesmente não está lá. Percorrer nesta ordem economiza tempo.
Comece verificando se o dispositivo chegou a ser criado. O nó está sob /proc/device-tree? Se não, o problema não está no arquivo que você editou — muito provavelmente o .dtb errado está sendo carregado, ou o nó está desligado um nível acima com status = "disabled". Este segundo caso acontece o tempo todo: periféricos costumam vir desabilitados nos arquivos .dtsi do fabricante, e espera-se que você os ligue um a um com status = "okay" no .dts da sua placa.
Se o nó está lá mas nenhum driver se prendeu, ponha o olho na string compatible. Ache a tabela of_device_id no fonte do driver e compare as strings caractere a caractere. Uma vírgula, um hífen bastam.
E ainda isto: os avisos do dtc chegam desligados na maioria dos projetos, e dizem coisas genuinamente úteis. Descompassos entre endereço e reg, um #address-cells faltando — exatamente o tipo de falha que vira avaria silenciosa no arranque. Vale manter os avisos ligados ao compilar.
Para Fechar
O que torna um device tree difícil de apreender é que ninguém diz de saída o que ele de fato é. Parece um arquivo de configuração, mas não é; parece um driver, mas também não é. O enquadramento mais útil é pensá-lo como o contrato entre hardware e software: como a placa está ligada, posto por escrito numa linguagem que o kernel entende.
Uma vez que ele assenta nesse quadro, o resto decorre. Por que compatible é tão crítico, por que o .dtb vive separado do kernel, por que /proc/device-tree é o primeiro lugar a olhar quando algo falta — tudo sai da mesma ideia.
Leituras Relacionadas
- A Cadeia de Boot do Linux Embarcado — como o bootloader entrega o controle ao kernel
- O Que É DFU? — gravar firmware por USB quando não há outro caminho
Referências
- Platform Devices and Drivers — por que periféricos de um SoC não são enumeráveis como PCI e USB, e como o código específico da placa os registra.
- How To Write Linux PCI Drivers — a tabela
pci_device_ide a chamada deprobe()para dispositivos que casam. - USB Device Drivers — casamento de drivers por identificador no lado USB.
- Devicetree Specification — a definição formal do formato.
- Linux and the Devicetree — como o kernel lê a árvore e casa os drivers.
- Writing Devicetree Bindings — a fronteira entre descrever hardware e configurar um driver.
- dtc — Device Tree Compiler — o repositório do compilador e seu uso.
- U-Boot: Devicetree Control — como o U-Boot lida com o device tree.
- Booting ARM Linux — qual registrador o bootloader usa para passar o blob ao kernel.
- Devicetree Overlay Notes — como os overlays são aplicados em tempo de execução.
- Device Tree Reference (eLinux) — referência da comunidade com exemplos trabalhados.
Comentários