Введение #
Для того, чтобы понять, что такое “Конфигурация устройcтв“ и для чего она нужна - необходимо понять базовый принцип работы с одноплатными компьютерами. Он заключается в том, что одноплатные компьютеры семейства Orange Pi, Repka Pi и другие используют в качестве процессора - систему на кристалле (далее SoC). Для работы SoC необходимо только внешнее ОЗУ. При этом в одном чипе SoC реализуются множества разных и взаимоисключающих интерфейсов ввода-вывода. Но в таком случае необходим механизм, который позволил бы передать информацию ядру Linux, какие интерфейсы существуют на SoC - этот механизм называется Device Tree.
Дерево устройств (Device Tree, DT) - это структура данных в системе Linux, состоящая из именованных узлов и свойств, описывающих оборудование (интерфейсы), которое невозможно обнаружить путем опроса. Ниже приведен фрагмент описания температурных зон, взятый из дерева устройств Repka Pi 3:
thermal-zones {
cpu-thermal {
polling-delay-passive = <250>;
polling-delay = <1000>;
thermal-sensors = <&ths 0>;
trips {
cpu_warm_level_0: cpu_warm_level_0 {
hysteresis = <2000>;
type = "passive";
};
// Остальные уровни...
};
cooling-maps {
cpu_warm_level_0_map {
trip = <&cpu_warm_level_0>;
cooling-device = <&cpu0 THERMAL_NO_LIMIT 1>;
};
// Остальные уровни...
};
};
};
В этом фрагменте мы видим основную структуру:
- thermal-zones — основной узел для определения термических зон. Каждая термическая зона представляет собой группу датчиков температуры и связанных с ними политик охлаждения.
- cpu-thermal — узел, встраиваемый в thermal-zones и описывающий термическую зону, связанную с центральным процессором (CPU). Включает настройки задержек, термодатчиков, уровней температуры и способов охлаждения.
Если продвинуться далее, то мы увидим дополнительные узлы: trips и cooling-maps, а также некоторые свойства для cpu-thermal: polling-delay-passive, polling-delay и thermal-sensors. Дадим их краткую характеристику:
- Параметр polling-delay-passive — задает время (в миллисекундах) между периодическими опросами температуры, когда температура ниже критической, но находится в пределах, требующих пассивного охлаждения.
- Параметр polling-delay — задает общее время опроса температуры, когда система работает в нормальном состоянии.
- Параметр thermal-sensors — ссылается на термодатчик (thermal sensor), который используется для измерения температуры.
- Узел trips — описывает температурные пороги (так называемые "трип-поинты"), при достижении которых предпринимаются действия по снижению температуры.
- Узел cooling-maps — связывает трип-поинты с механизмами охлаждения.
Device Tree Overlay (DT Overlay) — это механизм, позволяющий динамически модифицировать или дополнять основное дерево устройств (Device Tree) без необходимости перекомпиляции или полной замены его описания. Используется для динамического описания периферийных устройств, подключаемых к универсальным интерфейсам, таким как 40-пиновый разъем GPIO, распространенный на одноплатных компьютерах. Ниже представлен фрагмент оверлея, который включает интерфейс UART2:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&uart2>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&uart2_pins>;
status = "okay";
};
};
};
Рассмотрим данный оверлей подробнее:
- /dts-v1/; — указывает версию Device Tree Source. В нашем случае это первая версия формата Device Tree.
- /plugin/; — директива означающая, что данный файл является оверлеем (plugin), который применяется к существующему дереву устройств. Это дополнение к базовой конфигурации, а не самостоятельное описание.
- / (корневой узел) — cодержит все определенные в оверлее фрагменты (fragments), которые добавляют или изменяют конфигурацию устройств.
Давайте разберем конкретный fragment:
- target — указывает целевое устройство, к которому будет применен оверлей.
- _overlay_ — этот блок содержит параметры, которые будут добавлены или заменены в целевом узле.
- pinctrl-names — указывает, что для UART2 используется набор управляющих сигналов (pin control) с именем default. Это имя используется для выбора набора пинов из описания pinctrl.
- pinctrl-0 — указывает ссылку на узел uart2_pins (может быть определен в основном Device Tree), который описывает конкретные выводы микроконтроллера, задействованные для UART2.
- status — включает узел UART2 в системе, делая его активным.
Конфигурация устройств в Repka OS #
В предыдущем разделе мы познакомились с основным механизмом конфигурации устройств в Linux. Теперь же перейдем к знакомству с реализацией данного механизма внутри Repka OS.
Начнем рассмотрение с точки зрения пользователя. У вас есть repka-config, внутри которого присутствует раздел “3 Interface Options“, который предоставляет возможность выбрать одну из нескольких подготовленных распиновок для 40-pin разъема или же собрать свою (кастомизированную) распиновку. Но что же происходит, когда мы выбираем какую-то распиновку (подготовленную или свою)?
В ранних версиях Repka OS для Repka Pi 3 не было возможности собрать свою распиновку, так как не использовался механизм оверлеев. Под каждую из поставляемых распиновок были сделаны отдельные собранные (бинарные) дерева устройств. И в момент смены распиновки подменялись именно деревья устройств. Какие проблемы давал данный подход? Представьте, у вас: 5 распиновок, 3 профиля троттлинга и 4 варианта максимальной частоты процессора — перемножив мы получим 60 файлов с собранным деревом устройств. А теперь представим, как эти 60 файлов подменять — не очень-то и удобно. Но в данный момент мы отказались от этого в пользу оверлеев.
Вернемся к нашему предыдущему вопросу — что же происходит теперь при выборе новой распиновки. Для этого посмотрим на изображение ниже:
Система состоит из двух утилит — repka-config
и repka-control
, а также файла /boot/repkaEnv.txt
. Их взаимодействие представлено стрелками, направленными вправо, что показывает последовательность вызовов. Утилита repka-config
, отвечающая за выбор распиновки, передает данные в repka-control
. В свою очередь, repka-control
записывает информацию о том, какие оверлеи необходимо применить при следующей загрузке, в файл /boot/repkaEnv.txt
.
repka-control
— CLI-утилита, предоставляющая открытый интерфейс для взаимодействия хранилищем настроек запуска (в данном случае /boot/repkaEnv.txt).
/boot/repkaEnv.txt
— файл, имеющий определенную структуру, показанную ниже:
overlays=
loglevel=7
console=both
overlays — параметр, который содержит перечисленные подряд названия оверлеев (напр., uart1 i2c1
).
loglevel — параметр, устанавливающий уровень логирования ядра Linux (задается также через repka-config
).
console — параметр, указывающий ядру Linux, куда необходимо выводить свои логи. Имеет 3 варианта значения: 1) both - для вывода на дисплей (/dev/ttyS6
) и последовательный порт (/dev/ttyS0
); 2) serial - вывод только в последовательный порт (/dev/ttyS0
); 3) display - вывод только на дисплей (/dev/ttyS6
).
Изменения в /boot/repkaEnv.txt
зафиксировались. Но как данный файл связан с загрузкой Repka OS? В какой момент он читается, а главное кем? И тем-более, как данные параметры могут повлиять на загрузку?
Ответ на данный вопрос находится в этой же директории /boot
, а именно файл boot.scr
boot.scr
— это скрипт начальной загрузки, используемый загрузчиком U-Boot для настройки параметров загрузки операционной системы. Он представляет собой бинарный файл, сгенерированный из текстового файла (обычно boot.cmd
, который находится в этой же директории) с помощью утилиты mkimage
. Ниже показан пример компиляции boot.scr
:
mkimage -A arm -T script -C none -n 'Boot script' -d /boot/boot.cmd /boot/boot.scr
Этот скрипт используется для автоматизации процесса загрузки, включая установку переменных среды и выполнение команд загрузчика.
Давайте рассмотрим несколько фрагментов из него:
if test -e mmc ${devnum} boot/repkaEnv.txt; then
load mmc ${devnum} ${load_addr} boot/repkaEnv.txt && echo "repkaEnv.txt loaded successfully" || echo "Failed to load repkaEnv.txt"
env import -t ${load_addr} ${filesize}
fi
В данном фрагменте происходит проверка существования файла /boot/repkaEnv.txt
и в случае, если он есть происходит зачитывание и импортирование переменных (это именно наши overlays
, loglevel
и console
).
load mmc ${devnum} 0x44000000 boot/repka-pi.dtb && echo "repka-pi.dtb loaded successfully" || echo "Failed to load sun50i-h5-repka-pi3.dtb"
В следующем фрагменте происходит загрузка базового дерева устройств (обращаю внимание на его название repka-pi.dtb
и расположение в директории /boot
).
echo "overlays: ${overlays}"
for overlay_file in ${overlays}; do
load mmc ${devnum} ${load_addr} boot/overlays/${overlay_file}.dtbo && echo "${overlay_file}.dtbo loaded successfully" || echo "Failed to load ${overlay_file}.dtbo"
fdt apply ${load_addr} || setenv overlay_error "true"
done
Вот и основной фрагмент, который нас интересует. Здесь в цикле на основе переменной overlays происходит загрузка и применение оверлеев из директории /boot/overlays/
— обратите внимание, что оверлеи имеют расширение .dtbo
. То есть, в случае если в repkaEnv.txt
указано overlays=i2c1 uart1
, то произойдет загрузка двух оверлеев:
- /boot/overlays/i2c1.dtbo
- /boot/overlays/uart1.dtbo
load mmc ${devnum} 0x41000000 boot/Image
booti 0x41000000 - 0x44000000
И завершающий фрагмент, в котором происходит загрузка ядра Linux по пути /boot/Image
и передача ему управления.
Работу скрипта boot.scr можно продемонстрировать в виде изображения, показанного ниже: