Skip to main content

Device Tree: DTS, DTB и Overlays - краткое руководство

Зачем вообще нужен Device Tree

На обычном ПК операционная система сама находит всё оборудование: процессор, память, USB-порты, сетевые карты. Для этого существуют стандартные механизмы обнаружения - PCI, ACPI и другие.

На одноплатных компьютерах (ARM-платах вроде NAPI-C и NAPI2) всё иначе. Периферия припаяна прямо к процессору, стандартных механизмов обнаружения нет. Ядру Linux нужно заранее сообщить: какой процессор стоит на плате, сколько у него UART-портов, на каких адресах висит I2C, какие GPIO-пины к чему подключены, на какой частоте работает SPI, и так далее.

Именно для этого существует Device Tree - «дерево устройств». Это структурированное описание всего аппаратного обеспечения платы, которое ядро Linux читает при загрузке.

DTS - исходный текст описания

DTS (Device Tree Source) - это текстовый файл, который описывает оборудование платы в читаемом виде. Он похож на конфигурационный файл и выглядит примерно так:

/ {
model = "Napilab NAPI2";
compatible = "napilab,napi2", "rockchip,rk3568";

chosen {
stdout-path = "serial2:1500000n8";
};

leds {
compatible = "gpio-leds";
power-led {
gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
};

Здесь описано: название платы, совместимость с процессором RK3568, консольный порт (UART) и светодиод, подключённый к GPIO.

DTS-файлы могут подключать другие файлы через #include - общие описания процессора, контроллеров памяти и шин обычно вынесены в отдельные .dtsi файлы, а конкретная плата лишь дополняет и переопределяет нужные узлы.

DTB - скомпилированный файл для ядра

Ядро Linux не читает текстовые .dts файлы напрямую. Перед использованием их нужно скомпилировать в бинарный формат - DTB (Device Tree Blob). Это делает компилятор dtc (Device Tree Compiler):

DTS (текст) --[dtc]--> DTB (бинарный)

При сборке прошивки Armbian автоматически компилирует нужный DTB и помещает его в /boot/dtb/. Загрузчик U-Boot при старте передаёт этот DTB ядру, и ядро по нему настраивает драйверы для всего оборудования.

Посмотреть, какой DTB используется, можно командой:

cat /proc/device-tree/model

Overlay - «накладка» на основной DTB

Основной DTB описывает базовую конфигурацию платы. Но что если нужно подключить к плате дополнительный модуль - дисплей по SPI, датчик по I2C, CAN-контроллер, или просто переназначить функцию GPIO-пина?

Перекомпилировать весь DTB заново - неудобно и рискованно. Для этого существуют Device Tree Overlays (DTBO) - небольшие «накладки», которые добавляют или изменяют отдельные узлы основного дерева устройств.

Overlay компилируется в файл .dtbo и хранится в /boot/dtb/rockchip/overlay/.

Разбор реальных overlay на примере NAPI-C (RK3308)

Для NAPI-C поставляется набор overlay-файлов. Рассмотрим несколько из них, чтобы понять структуру.

Доступные overlay для NAPI-C

rk3308-i2c0.dts             - включить шину I2C0
rk3308-i2c0-ds1307.dts - часы реального времени DS1307 на I2C0
rk3308-i2c1.dts - включить шину I2C1
rk3308-i2c1-ds1307.dts - часы DS1307 на I2C1
rk3308-i2c1-ds1338.dts - часы DS1338 на I2C1
rk3308-i2c1-ds3231.dts - часы DS3231 на I2C1
rk3308-i2c3.dts - включить шину I2C3
rk3308-i2c3-m0.dts - I2C3, вариант пинов m0
rk3308-i2c3-m1.dts - I2C3, вариант пинов m1
rk3308-spi1-w5500.dts - Ethernet-модуль W5500 на SPI1
rk3308-spi1-w5500-s.dts - W5500 на SPI1, альтернативный вариант
rk3308-spi2-spidev.dts - SPI2 как пользовательское устройство
rk3308-uart0.dts - включить UART0
rk3308-uart1.dts - включить UART1
rk3308-uart2-m0.dts - UART2, вариант пинов m0
rk3308-uart2-m1.dts - UART2, вариант пинов m1
rk3308-uart3-m0.dts - UART3, вариант пинов m0
rk3308-uart3-m1.dts - UART3, вариант пинов m1
rk3308-uart4.dts - включить UART4
rk3308-usb20-host.dts - включить USB 2.0 Host
rk3308-usb-pcie-modem.dts - поддержка PCIe-модема через USB

Пример 1: Простой overlay - включение UART

Файл rk3308-uart0.dts:

/dts-v1/;
/plugin/;

/ {
fragment@0 {
target = <&uart0>;
__overlay__ {
status = "okay";
};
};
};

Разберём построчно:

  • /dts-v1/; - версия формата DTS.
  • /plugin/; - это overlay (накладка), а не самостоятельное описание платы.
  • target = <&uart0>; - указываем, к какому узлу основного дерева применяется накладка. &uart0 - это ссылка на контроллер UART0, уже описанный в основном DTB процессора RK3308.
  • status = "okay"; - включаем этот узел. По умолчанию в основном DTB большинство интерфейсов выключены (status = "disabled"), чтобы не занимать пины и не загружать ненужные драйверы.

Это минимальный overlay - он просто «включает» периферию, которая уже описана в основном DTB, но выключена.

Пример 2: Overlay с устройством - часы DS3231 на I2C1

Файл rk3308-i2c1-ds3231.dts:

/dts-v1/;
/plugin/;

/ {
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;

ds3231: rtc@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
};
};
};
};

Что здесь происходит:

  • target = <&i2c1> - накладка применяется к шине I2C1.
  • status = "okay" - включаем шину I2C1.
  • #address-cells = <1>; #size-cells = <0>; - стандартные параметры для устройств на шине I2C (адрес - 1 ячейка, размер - не используется).
  • rtc@68 - дочерний узел, описывающий устройство на шине. 68 - это I2C-адрес микросхемы DS3231 (в шестнадцатеричном формате: 0x68).
  • compatible = "maxim,ds3231" - самое важное свойство. По нему ядро находит нужный драйвер. Формат: "производитель,модель". Ядро ищет драйвер, который «знает» устройство maxim,ds3231.
  • reg = <0x68> - I2C-адрес устройства.

После применения этого overlay ядро загружает драйвер RTC для DS3231, и в системе появляется устройство /dev/rtc1, которое можно использовать для аппаратных часов.

Пример 3: Overlay с pinmux - Ethernet W5500 на SPI

Файл rk3308-spi1-w5500.dts (упрощённо):

/dts-v1/;
/plugin/;

/ {
fragment@0 {
target = <&spi1>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins &spi1_cs0>;

w5500: ethernet@0 {
compatible = "wiznet,w5500";
reg = <0>;
spi-max-frequency = <30000000>;
interrupt-parent = <&gpio2>;
interrupts = <10 2>; /* GPIO2_B2, по спаду */
};
};
};
};

Новые элементы:

  • pinctrl-names и pinctrl-0 - настройка мультиплексирования пинов (pinmux). Указываем, какие физические пины процессора нужно переключить в режим SPI.
  • spi-max-frequency = <30000000> - максимальная частота SPI (30 МГц).
  • interrupt-parent = <&gpio2> и interrupts = <10 2> - модуль W5500 сообщает процессору о новых сетевых пакетах через прерывание на определённом GPIO-пине.

Это уже более сложный overlay: он включает SPI, настраивает пины, описывает подключённое устройство и его прерывание.

Что означает суффикс «m0» и «m1» в именах

У процессора RK3308 многие интерфейсы имеют несколько вариантов разводки пинов (mux-вариантов). Например:

  • rk3308-uart3-m0.dts - UART3 выведен на одну группу пинов
  • rk3308-uart3-m1.dts - тот же UART3, но на другой группе пинов

Какой вариант использовать, зависит от того, на какие пины разъёма платы выведен интерфейс. Это определяется схемотехникой конкретной модели платы.

Разные overlay для разных модификаций платы

Платы NAPI-C выпускаются в нескольких модификациях. Хотя на всех стоит один и тот же процессор RK3308, схемотехника разводки отличается - интерфейсы выведены на разные пины, подключены разные модули. Поэтому для каждой модификации нужен свой набор overlay.

Пример: FCC3308 и FCU3308P

FCC3308 и FCU3308P - это разные модификации платы на базе NAPI-C. У них:

  • I2C, UART и SPI могут быть выведены на разные физические пины (разные mux-варианты - m0 или m1)
  • Могут отличаться подключённые периферийные модули (один вариант имеет модуль W5500 для Ethernet, другой - нет; на одном часы RTC на I2C0, на другом - на I2C1)
  • Разное назначение GPIO - пин, который на одной плате управляет светодиодом, на другой может быть подключён к кнопке или датчику

Из-за этого нельзя просто скопировать строку overlays= с одной модификации на другую - нужно использовать именно те overlay, которые соответствуют разводке вашей конкретной платы.

Пример конфигурации для FCC3308 (/boot/armbianEnv.txt):

overlays=rk3308-uart0 rk3308-i2c1 rk3308-i2c3-m0 rk3308-uart3-m0

Пример для FCU3308P:

overlays=rk3308-uart1 rk3308-i2c1-ds3231 rk3308-i2c3-m1 rk3308-uart3-m1 rk3308-spi1-w5500

Обратите внимание на разницу:

  • Разные UART-порты включены - на FCC3308 используется UART0, на FCU3308P - UART1
  • Разные mux-варианты - FCC3308 использует i2c3-m0 и uart3-m0, а FCU3308P - i2c3-m1 и uart3-m1, потому что интерфейсы выведены на разные пины
  • FCU3308P дополнительно подключает часы DS3231 и Ethernet W5500

Используйте набор overlay, указанный в документации к вашей конкретной модификации платы. Если вы не уверены - посмотрите маркировку на плате и обратитесь к поставщику.

Строка overlays= в armbianEnv.txt

Чтобы overlay применился при загрузке, его нужно указать в файле /boot/armbianEnv.txt в строке overlays=:

overlays=rk3308-uart3-m0 rk3308-i2c3-m0 rk3308-spi1-w5500

Здесь перечислены имена overlay-файлов через пробел (без расширения .dtbo). Загрузчик U-Boot при старте находит соответствующие .dtbo файлы и «накладывает» их поверх основного DTB, после чего передаёт итоговое дерево ядру.

Когда нужно менять строку overlays=

Строку overlays= нужно редактировать, когда вы:

  • Подключаете внешний модуль - датчик, дисплей, CAN-шину, RS-485, GPS-приёмник и т.д. Нужно добавить overlay, который включает соответствующий интерфейс (I2C, SPI, UART) и описывает подключённое устройство.

  • Включаете или выключаете аппаратный интерфейс - например, по умолчанию UART3 выключен, чтобы использовать пины для GPIO. Если вам нужен UART3 - добавляете overlay. Если перестали использовать - убираете.

  • Меняете функцию пинов (pinmux) - один и тот же физический пин процессора может работать как GPIO, UART, I2C или SPI. Overlay переключает пин в нужный режим. Два overlay, которые хотят использовать один и тот же пин для разных функций, конфликтуют - нельзя включить оба одновременно.

  • Настраиваете параметры оборудования - частоту SPI, режим работы PWM, полярность пинов и т.п.

Какие overlay доступны

Посмотреть все установленные overlay для вашей платы:

ls /boot/dtb/rockchip/overlay/

Имена файлов обычно говорят сами за себя: rk3308-uart3-m0.dtbo, rk3308-i2c3.dtbo, rk3308-spi1-w5500.dtbo и т.д.

User Overlays - пользовательские накладки

Помимо стандартных overlay, в Armbian есть механизм user overlays - пользовательских накладок, которые вы пишете сами и которые не зависят от обновлений системы.

Зачем нужны user overlays

Стандартные overlay покрывают типовые сценарии. Но если у вас:

  • нестандартный модуль, которого нет в поставке,
  • нужно включить конкретное устройство на конкретных пинах с конкретными параметрами,
  • вы разработали собственную плату расширения,

то вам нужно написать свой overlay.

Способ 1: Утилита armbian-add-overlay (рекомендуется)

Armbian предоставляет утилиту armbian-add-overlay, которая автоматизирует все шаги: компиляцию, копирование и регистрацию overlay.

Использование:

armbian-add-overlay my-device.dts

Что делает утилита:

  1. Компилирует .dts файл в .dtbo (с нужными флагами)
  2. Копирует скомпилированный .dtbo в /boot/overlay-user/
  3. Добавляет имя overlay в строку user_overlays= в /boot/armbianEnv.txt

После этого достаточно перезагрузить плату - overlay применится автоматически.

Пример: вы написали overlay для своего датчика температуры на I2C:

armbian-add-overlay my-temp-sensor.dts
reboot

Готово - не нужно вручную вызывать dtc, копировать файлы и редактировать armbianEnv.txt.

Способ 2: Ручная установка

Если вы хотите контролировать процесс вручную:

  1. Напишите .dts файл с вашим overlay.

  2. Скомпилируйте его в .dtbo:

dtc -@ -I dts -O dtb -o my-device.dtbo my-device.dts

Флаг -@ обязателен - он включает генерацию символьных ссылок, без которых overlay не сможет найти целевой узел в основном дереве.

  1. Поместите скомпилированный файл в /boot/overlay-user/:
cp my-device.dtbo /boot/overlay-user/
  1. Укажите его в /boot/armbianEnv.txt в строке user_overlays=:
user_overlays=my-device

Несколько пользовательских overlay через пробел:

user_overlays=my-device my-sensor
  1. Перезагрузите плату.

Отличие overlays= от user_overlays=

overlays=user_overlays=
Где лежат файлы/boot/dtb/rockchip/overlay//boot/overlay-user/
Кто создаётПоставщик системы (Armbian, Napilab)Пользователь
Обновления системыМогут перезаписатьНе затрагиваются
УстановкаВручную в armbianEnv.txtarmbian-add-overlay или вручную

Ключевое отличие: файлы в /boot/overlay-user/ не перезаписываются при обновлении ядра или DTB, поэтому это безопасное место для ваших кастомных настроек.

Пример user overlay: включение LED на GPIO

Файл my-led.dts:

/dts-v1/;
/plugin/;

/ {
fragment@0 {
target-path = "/";
__overlay__ {
my-leds {
compatible = "gpio-leds";
status = "okay";
my-led {
label = "my-custom-led";
gpios = <&gpio3 4 0>; /* GPIO3_A4, активный HIGH */
default-state = "off";
};
};
};
};
};

Установка одной командой:

armbian-add-overlay my-led.dts
reboot

После перезагрузки в /sys/class/leds/ появится my-custom-led, которым можно управлять:

echo 1 > /sys/class/leds/my-custom-led/brightness   # включить
echo 0 > /sys/class/leds/my-custom-led/brightness # выключить

Как проверить, что overlay применился

Посмотреть итоговое дерево устройств, которое видит ядро:

dtc -I fs /proc/device-tree 2>/dev/null | less

Или проверить конкретный узел:

ls /proc/device-tree/my-leds/

Если узел существует - overlay применился.

Проверить в логах загрузки:

dmesg | grep -i overlay

Частые ошибки

Overlay не применяется после перезагрузки. Проверьте, что имя в overlays= или user_overlays= совпадает с именем файла (без .dtbo). Проверьте, что файл лежит в правильной директории.

Конфликт пинов. Два overlay пытаются использовать одни и те же пины для разных функций. Например, нельзя одновременно включить rk3308-uart3-m0 и rk3308-i2c3-m0, если они используют одни и те же пины. Решение - убрать один из конфликтующих overlay или использовать другой mux-вариант (например, m1 вместо m0).

Неправильный mux-вариант. Включили rk3308-uart3-m1, а на вашей плате UART3 выведен на пины варианта m0. Интерфейс не заработает, хотя overlay применился без ошибок. Проверьте документацию на вашу модификацию платы.

Overlay скомпилирован без флага -@. Без этого флага overlay не содержит символов для привязки к узлам основного дерева, и U-Boot не сможет его применить. Используйте armbian-add-overlay - она вызывает dtc с правильными параметрами.

Устройство не появляется в системе. Overlay применился, но в системе нет соответствующего /dev/.... Возможные причины: отсутствует драйвер в ядре, неправильный compatible в overlay, или устройство физически не подключено.

Скопировали overlays= с другой модификации платы. На FCC3308 и FCU3308P разная разводка - overlay от одной не подойдут к другой. Используйте набор overlay, соответствующий именно вашей плате.

Краткая шпаргалка

DTS  → исходник (текст), описывает железо
DTB → скомпилированный бинарник для ядра
DTBO → overlay (накладка), добавляет/меняет узлы

/boot/armbianEnv.txt:
overlays=xxx yyy ← стандартные overlay из /boot/dtb/.../overlay/
user_overlays=aaa bbb ← пользовательские из /boot/overlay-user/

armbian-add-overlay file.dts ← компиляция + установка user overlay
dtc -@ -I dts -O dtb -o file.dtbo file.dts ← ручная компиляция overlay
dtc -I fs /proc/device-tree ← посмотреть текущее дерево