Конфигурация устройств

Введение #

Для того, чтобы понять, что такое “Конфигурация устрой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>;
          };
          // Остальные уровни...
       };
    };
};

В этом фрагменте мы видим основную структуру:

  1. thermal-zones — основной узел для определения термических зон. Каждая термическая зона представляет собой группу датчиков температуры и связанных с ними политик охлаждения.
  2. cpu-thermal — узел, встраиваемый в thermal-zones и описывающий термическую зону, связанную с центральным процессором (CPU). Включает настройки задержек, термодатчиков, уровней температуры и способов охлаждения.

Если продвинуться далее, то мы увидим дополнительные узлы: trips и cooling-maps, а также некоторые свойства для cpu-thermal: polling-delay-passive, polling-delay и thermal-sensors. Дадим их краткую характеристику:

  1. Параметр polling-delay-passive — задает время (в миллисекундах) между периодическими опросами температуры, когда температура ниже критической, но находится в пределах, требующих пассивного охлаждения.
  2. Параметр polling-delay — задает общее время опроса температуры, когда система работает в нормальном состоянии.
  3. Параметр thermal-sensors — ссылается на термодатчик (thermal sensor), который используется для измерения температуры.
  4. Узел trips — описывает температурные пороги (так называемые "трип-поинты"), при достижении которых предпринимаются действия по снижению температуры.
  5. Узел 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";
       };
    };
};

Рассмотрим данный оверлей подробнее:

  1. /dts-v1/; — указывает версию Device Tree Source. В нашем случае это первая версия формата Device Tree.
  2. /plugin/; — директива означающая, что данный файл является оверлеем (plugin), который применяется к существующему дереву устройств. Это дополнение к базовой конфигурации, а не самостоятельное описание.
  3. / (корневой узел) — cодержит все определенные в оверлее фрагменты (fragments), которые добавляют или изменяют конфигурацию устройств.

Давайте разберем конкретный fragment:

  1. target — указывает целевое устройство, к которому будет применен оверлей.
  2. _overlay_ — этот блок содержит параметры, которые будут добавлены или заменены в целевом узле.
  3. pinctrl-names — указывает, что для UART2 используется набор управляющих сигналов (pin control) с именем default. Это имя используется для выбора набора пинов из описания pinctrl.
  4. pinctrl-0 — указывает ссылку на узел uart2_pins (может быть определен в основном Device Tree), который описывает конкретные выводы микроконтроллера, задействованные для UART2.
  5. 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, то произойдет загрузка двух оверлеев:

  1. /boot/overlays/i2c1.dtbo
  2. /boot/overlays/uart1.dtbo
load mmc ${devnum} 0x41000000 boot/Image
booti 0x41000000 - 0x44000000

И завершающий фрагмент, в котором происходит загрузка ядра Linux по пути /boot/Image и передача ему управления.

Работу скрипта boot.scr можно продемонстрировать в виде изображения, показанного ниже:


67 просмотров0 комментариев

Комментарии (0)

Для участия в обсуждении Вы должны быть авторизованным пользователем
Разделы

Навигация

ВойтиРегистрация