¿Qué es un Device Tree?

August 7, 2026 · visitantes

Si alguna vez has intentado levantar Linux en una placa embebida, tarde o temprano te encuentras con un archivo terminado en .dts. Normalmente en el peor momento posible: la placa no arranca, no sale nada por pantalla y alguien te dice que "revises tu device tree". Esto va sobre qué es ese archivo, por qué existe y qué estás buscando realmente dentro de él.

El Problema Que Resuelve

En un ordenador de escritorio, el kernel puede encontrar el hardware prácticamente solo. PCI y USB son buses formalmente especificados y enumerables (1). Los dispositivos se anuncian con un identificador de fabricante y de producto; un driver lista en una tabla los identificadores que soporta, y el kernel llama a la función probe() de ese driver para cada dispositivo que coincida con una entrada (2). En USB el mecanismo funciona igual, con su propia tabla de identificadores (3). Nadie tiene que contarle nada a nadie de antemano.

Las placas embebidas no tienen ese lujo. La propia documentación del kernel traza la línea con claridad: los periféricos integrados en un system-on-chip cuelgan de buses con una infraestructura mínima, "a diferencia de los grandes buses formalmente especificados como PCI o USB", y lo que esos dispositivos tienen en común es el direccionamiento directo desde un bus de CPU (1). Un sensor I²C o un controlador de pantalla colgado de SPI no se anuncia; simplemente está ahí, en esa dirección, cableado a esa línea de interrupción. La única forma de que el kernel lo sepa es que alguien se lo diga.

Durante mucho tiempo ese alguien fue el propio código. Cada placa tenía un archivo dentro del árbol del kernel describiendo en C qué dispositivo estaba conectado dónde — el kernel todavía documenta este camino, en el que el código de arranque específico de la placa registra los dispositivos uno a uno (1). En ARM estos archivos se multiplicaron tanto que en 2011 Linus Torvalds desmontó la situación en un correo, diciendo que el código ARM iba hacia un sitio insostenible. La salida que se adoptó fue el device tree.

La Idea

Un device tree es un archivo de datos que describe cómo está cableado el hardware. No es código — no se ejecuta y no se compila dentro del kernel. Es una descripción: hay un controlador en esta dirección, usa esta línea de interrupción, lo alimenta este reloj y estos dispositivos cuelgan por debajo.

El formato en sí se apoya en una especificación formal (4). Se llama árbol porque realmente lo es. Empieza en un nodo raíz, de él se ramifican los buses, y de esos buses se ramifican los dispositivos conectados — reflejando cómo está físicamente conectado el hardware:

En lugar de un ejemplo inventado, aquí va uno real. El nodo siguiente sale de k3-am62p-main.dtsi, en el árbol de U-Boot de la familia J722S/AM67A de TI — la descripción que realmente lee una placa AM67A al 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 línea aquí significa algo. reg da la dirección del controlador en el mapa de memoria — 0x20000000, con longitud 0x100. Las direcciones se escriben como pares de números porque el espacio de direcciones es de 64 bits; el primer número lleva los 32 bits altos. interrupts dice qué línea de interrupción se usa; nombres como GIC_SPI e IRQ_TYPE_LEVEL_HIGH vienen de cabeceras incluidas, así que aunque el fuente del device tree sea datos planos, pasa igualmente por el preprocesador de C.

Merece la pena fijarse en que compatible lleva dos valores: "ti,am64-i2c" y "ti,omap4-i2c". La lista se lee de lo más específico a lo más general. El kernel busca un driver específico para am64 y, si no lo encuentra, cae al driver omap4, más antiguo y general (5). Así un chip nuevo puede funcionar sin que nadie escriba un driver nuevo para él.

clocks y clock-names dicen de dónde saca el reloj el dispositivo: la salida 2 del dispositivo 102 de &k3_clks, que el driver encontrará bajo el nombre "fck". power-domains conecta el dominio de potencia con la misma lógica. Esto no son ajustes de frecuencia — describen el cableado, qué reloj va a dónde.

La última línea importa: status = "disabled". El fabricante del chip describe el periférico pero lo deja apagado, porque no puede saber si ese bus I²C se usa en tu placa. Encenderlo es tarea del .dts de la placa. En el archivo de placa del J722S EVM se ve así:

&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>;
    };
};

El &main_i2c0 del principio es una referencia al nodo de arriba — no crea un nodo nuevo, sobrescribe el existente. Se enciende status, se asignan los pines, se fija la velocidad en 400 kHz y se añade debajo un dispositivo real: un expansor GPIO TCA6424 en la dirección 0x23 del bus I²C. Como el controlador declaró #address-cells = <1>, esa dirección se escribe con un solo número, a diferencia del reg del propio controlador.

Así funciona un device tree en la práctica: el fabricante describe el SoC una vez, y tú dices qué hay conectado en tu placa.

La propiedad que realmente importa es compatible. Es donde mira el kernel para emparejar un driver con una pieza de hardware. Un driver dice "soy compatible con ti,tca6424", un nodo del device tree lleva la misma cadena, y el kernel une a los dos. Por eso las erratas fallan en silencio: si la cadena no coincide, no hay error — el dispositivo simplemente nunca aparece.

Conviene tenerlo presente: un device tree describe hardware, no la configuración de un driver. La guía del kernel para escribir bindings insiste especialmente en esta frontera (6). La línea se difumina constantemente en la práctica, y es el tema más discutido en las listas del kernel — "¿esto es realmente una propiedad del hardware, o es tu preferencia?"

Los Archivos

Verás tres extensiones y es fácil confundirlas (11).

.dts es el fuente que escribes, la forma legible por humanos. .dtsi es un fragmento que otros archivos pueden incluir — la "i" es de include. Un fabricante de chips describe típicamente todo el SoC en un .dtsi; tú escribes un .dts corto para tu placa, lo incluyes y añades solo lo específico de tu placa.

.dtb es la forma compilada — el device tree blob. Esto es lo que el kernel lee de verdad. El compilador es dtc (7):

dtc -I dts -O dtb -o my-board.dtb my-board.dts

También funciona en sentido inverso, lo cual es valiosísimo cuando persigues un problema. Si tienes un .dtb que funciona, puedes abrirlo y mirar dentro:

dtc -I dtb -O dts -o decodificado.dts my-board.dtb

En un sistema en marcha también puedes leer el device tree directamente del sistema de archivos. El kernel expone aquí el árbol que está usando realmente:

ls /proc/device-tree/

Esta es la respuesta más rápida a "¿el archivo que edité es el archivo que se cargó?" Una proporción sorprendente de las veces, la respuesta es no.

Qué Ocurre al Arrancar

El orden es este: el bootloader — normalmente U-Boot (8) — carga el .dtb en memoria, carga el kernel y, al arrancarlo, le pasa la dirección de memoria del blob en un registro. En ARM ese registro y las condiciones que debe cumplir el bootloader están fijadas en el documento de arranque del kernel (9). Antes de levantar ninguno de sus drivers, el kernel lee ese árbol, extrae de él los dispositivos y empareja los drivers según las cadenas compatible.

La consecuencia práctica es que el kernel y el device tree se pueden actualizar por separado. Añadir un sensor nuevo rara vez implica recompilar el kernel; normalmente basta con actualizar el .dtb. Pero eso corta por los dos lados — al ser independientes, pueden desincronizarse. Arranca un kernel nuevo con un .dtb viejo y la placa levanta incompleta en silencio, y cuesta un rato averiguar por qué.

También existe la idea de los overlays: fragmentos aplicados sobre un árbol existente en tiempo de ejecución (10). Es lo que ocurre cuando enchufas un HAT en una Raspberry Pi — el árbol principal se queda como está y se superpone un fragmento pequeño. Útil cuando la placa base es siempre la misma y solo cambia el módulo que se le conecta.

Cuando Algo No Funciona

Los fallos de device tree tienen un tipo particular de fastidio, porque normalmente no producen ningún error. El dispositivo simplemente no está. Recorrerlo en este orden ahorra tiempo.

Empieza comprobando si el dispositivo llegó a crearse. ¿Está el nodo bajo /proc/device-tree? Si no, el problema no está en el archivo que editaste — lo más probable es que se esté cargando el .dtb equivocado, o que el nodo esté apagado un nivel más arriba con status = "disabled". Esto segundo pasa constantemente: los periféricos suelen venir deshabilitados en los archivos .dtsi del fabricante, y se espera que los enciendas uno a uno con status = "okay" en el .dts de tu placa.

Si el nodo está ahí pero ningún driver se ha enganchado, pon el ojo en la cadena compatible. Busca la tabla of_device_id en el fuente del driver y compara las cadenas carácter a carácter. Basta una coma, un guion.

Y además esto: los avisos de dtc llegan desactivados en la mayoría de proyectos, y dicen cosas genuinamente útiles. Desajustes entre dirección y reg, un #address-cells que falta — exactamente el tipo de fallo que se convierte en avería silenciosa al arrancar. Merece la pena mantener los avisos activados al compilar.

Reflexión Final

Lo que hace difícil de captar un device tree es que nadie dice de entrada qué es realmente. Parece un archivo de configuración, pero no lo es; parece un driver, pero tampoco es eso. El encuadre más útil es pensarlo como el contrato entre hardware y software: cómo está cableada la placa, puesto por escrito en un lenguaje que el kernel entiende.

Una vez que encaja en ese marco, el resto se sigue solo. Por qué compatible es tan crítico, por qué el .dtb vive separado del kernel, por qué /proc/device-tree es el primer sitio donde mirar cuando falta algo — todo sale de la misma idea.

Lecturas Relacionadas

Referencias

  1. Platform Devices and Drivers — por qué los periféricos de un SoC no son enumerables como PCI y USB, y cómo los registra el código específico de la placa.
  2. How To Write Linux PCI Drivers — la tabla pci_device_id y la llamada a probe() para los dispositivos que coinciden.
  3. USB Device Drivers — emparejamiento de drivers por identificador en el lado USB.
  4. Devicetree Specification — la definición formal del formato.
  5. Linux and the Devicetree — cómo lee el kernel el árbol y empareja los drivers.
  6. Writing Devicetree Bindings — la frontera entre describir hardware y configurar un driver.
  7. dtc — Device Tree Compiler — el repositorio del compilador y su uso.
  8. U-Boot: Devicetree Control — cómo maneja U-Boot el device tree.
  9. Booting ARM Linux — qué registro usa el bootloader para pasarle el blob al kernel.
  10. Devicetree Overlay Notes — cómo se aplican los overlays en tiempo de ejecución.
  11. Device Tree Reference (eLinux) — referencia comunitaria con ejemplos resueltos.

Comentarios