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
Что делает утилита:
- Компилирует
.dtsфайл в.dtbo(с нужными флагами) - Копирует скомпилированный
.dtboв/boot/overlay-user/ - Добавляет имя overlay в строку
user_overlays=в/boot/armbianEnv.txt
После этого достаточно перезагрузить плату - overlay применится автоматически.
Пример: вы написали overlay для своего датчика температуры на I2C:
armbian-add-overlay my-temp-sensor.dts
reboot
Готово - не нужно вручную вызывать dtc, копировать файлы и редактировать armbianEnv.txt.
Способ 2: Ручная установка
Если вы хотите контролировать процесс вручную:
-
Напишите
.dtsфайл с вашим overlay. -
Скомпилируйте его в
.dtbo:
dtc -@ -I dts -O dtb -o my-device.dtbo my-device.dts
Флаг -@ обязателен - он включает генерацию символьных ссылок, без которых overlay не сможет найти целевой узел в основном дереве.
- Поместите скомпилированный файл в
/boot/overlay-user/:
cp my-device.dtbo /boot/overlay-user/
- Укажите его в
/boot/armbianEnv.txtв строкеuser_overlays=:
user_overlays=my-device
Несколько пользовательских overlay через пробел:
user_overlays=my-device my-sensor
- Перезагрузите плату.
Отличие overlays= от user_overlays=
overlays= | user_overlays= | |
|---|---|---|
| Где лежат файлы | /boot/dtb/rockchip/overlay/ | /boot/overlay-user/ |