Device Tree

Device Tree.

Возможности пользователя: загрузка системы с деревом устройств.

  • Базовый синтаксис дерева устройств и его компиляция.
  • Простой пример фрагмента дерева устройств.
  • Полная организация дерева устройств.
  • Пример использования дерева устройств.
  • Общие соображения о дереве устройств в Linux.

Возможности пользователя: до дерева устройств.

  • Ядро включает полное описание аппаратной платформы.
  • Загрузчик загружает одиночный бинарный файл, образ ядра, и выполняет это файл – uImage или zImage.
  • Загрузчик подготавливает некоторую дополнительную информацию, вызывает ATAGS, адрес ядра передается в регистр r2. Дополнительную информация – мета информация такая как, размер памяти и расположение, командная строка ядра, и др.
  • Загрузчик говорит ядру для этой платы, это загрузиться через целей машинный тип данных (integer), передав в регистр r1.
  • U-Boot команда: bootm < адрес образа ядра >
  • Barebox переменная: bootm.image

device_tree_1

Возможности пользователя: загрузка с деревом устройств.

  • Ядро не длиннее содержания описания аппаратной части, дерево устройств расположено в отдельном бинарном файле: device tree blob.
  • Загрузчик загружает два бинарных файла: образ ядра и DTB. Название образа ядра остается uImage или zImage. DTB расположено в arch/arm/boot/dts, один файл для платы “название платы”.dtb.
  • Загрузчик передает DTB адрес через r2. Это предложено чтобы приспособить DTB с информацией о памяти, командной строкой ядра и потенциально другой информацией.
  • Нет больше машинного типа.
  • bootm < адрес образа ядра > – < dtb адрес >.
  • Barebox переменная: bootm.image, bootm.oftree.

device_tree_2

Возможности пользователя: режим совместимости для DT загрузки.

  • Некоторые загрузчики имеют не специфичную поддержку для дерева устройств, или версия использования для контрактного устройства уже устарела, но должна поддерживаться.
  • Упрощен переход, был добавлен совместимы механизм: CONFIG_ARM_APPENDED_DTB. Сообщаем ядру посмотреть на DTB после образа ядра. Нет встроенного правила для Makefile чтобы собрать такое ядра, так что, процедуру нужно делать вручную. cat arch/arm/boot/zImage arch/arm/boot/dts/myboard.dtb > my-zImage
    mkimage … -d my-zImage my-uImage
  • Кроме того, дополнительная опция CONFIG_ARM_ATAG_DTB_COMPAT сообщает ядру прочитать ATAGS информацию из загрузчика и обновить DT, используя данную информацию.

Что такое дерево устройств?

  • Цитируемым из Power.org стандарт требований для встраивания мощных архитектурных платформ (Standard for Embedded Power Architecture Platform Requirements – ePAPR)
  • ePAPR спецификация – это концепция вызова дерева устройств в описании системного железа (платформы, аппаратной части). Загрузчик загружает дерево устройств в память клиентской программы и отдает указатель на дерево устройств клиенту.
  • ePAPR совместимое дерева устройств описывает информацию об устройстве в системе, что не может быть динамически определено с помощью клиентской программы.

Синтаксис базового дерева устройств.

device_tree_3

Путь от исходного файла к бинарному.

  • На всех ARM, все исходные файлы дерева устройств (Device Tree Source – DTS) имеют следующее расположение arch/arm/boot/dts. .dts
    файлы уровня описания платформы. .dtsi файлы включают файлы, как правило содержащие уровень SoC описания.
  • Инструмент, компилятор дерева устройств (Device Tree Compiler) компилирует из исходных файлов, бинарный файл. Исходных код расположен в scripts/dtc.
  • DTB создает компилятор, и бинарный файл загружается через загрузчик, в свою очередь его анализирует ядро во время загрузки.
  • arch/arm/boot/dts/Makefile содержит список DTB которые должны быть собраны.
dtb-$(CONFIG_ARCH_MVEBU) += armada-370-db.dtb \
      armada-370-mirabox.dtb \
...

Простой пример дерева устройств.

auart0: serial@8006a000 {
    Описание "программируемая модель" для устройства. 
    Позволяет операционной системе идентифицировать соответствующий драйвер устройства. 
    compatible = "fsl,imx28-aurt", "fsl,imx23-aurt";
    Адрес и длина области регистров
    reg = <0x8006a000 0x2000>;
    Номер прерывания.
    interrupts = <112>;
    Механизм DMA и каналы, с именами.
    dmas = <&dma_apbx 8>, <&dma_apbx 9>;
    dma-names = "rx", "tx";
    Описание тактирования.
    clocks = <&clks 45>;
    Устройство не используется.
    status = "disabled";
};
Пример взят из arch/arm/boot/dts/imx28.dtsi

Сопоставимая строка используемая для связывания устройства с драйвером.

static struct of_device_id mxs_auart_dt_ids[] = {
    {
        .compatible = "fsl,imx28-auart",
        .data = &mxs_auart_devtype[IMX28_AUART]
    }, {
        .compatible = "fsl,imx23-auart",
        .data = &mxs_auart_devtype[IMX23_AUART]
    }, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_auart_dt_ids);
[...]
static struct platform_driver mxs_auart_driver = {
    .probe = mxs_auart_probe,
    .remove = mxs_auart_remove,
    .driver = {
        .name = "mxs-auart",
        .of_match_table = mxs_auart_dt_ids,
    },
};

Пример взят из drivers/tty/serial/mxs-auart.c

  • of_match_device позволяет получить сопоставимый вход для таблицы mxs_auart_dt_ids.
  • Полезно получать драйвер-спецификацию поля data, типичное использование изменение поведение драйвера зависящего от различных вариантов подключенных устройств.
static int mxs_auart_probe(struct platform_device *pdev)
{
    const struct of_device_id *of_id =
            of_match_device(mxs_auart_dt_ids, &pdev->dev);
    if (of_id) {
        /* Use of_id->data here */
        [...]
    }
    [...]
}
  • Получение описания тактирования. Описание свойств тактирования. s->clk = clk_get(&pdev->dev, NULL);
  • Получение ресурсов Вх./Вых. регистров. Описание свойств регистров. r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  • Получение прерывания. Описание свойств прерывания. s->irq = platform_get_irq(pdev, 0);
  • Получение DMA канала. Описание свойств DMA. s->rx_dma_chan = dma_request_slave_channel(s->dev, “rx”); s->tx_dma_chan = dma_request_slave_channel(s->dev, “tx”);
  • Проверка некоторых пользовательских свойств. struct device_node *np = pdev->dev.of_node; if (of_get_property(np, “fsl,uart-has-rtscts”, NULL))

Присоединение дерева устройств.

  • Файлы дерева устройств не монолитны, они могут быть разделены на несколько частей в нескольких файлах, включая каждый из них.
  • .dtsi файлы включают файлы платформы, пока .dts файлы не определят конечный вид дерева устройств.
  • Типичная связь, .dtsi файлы будут включать информацию для SoC уровня(или иногда общее определение к некоторым почти идентичным платам).
  • .dts файл включает информацию уровня отладочной платы (платы разработки).
  • Присоединение работает следующим образом, происходит наложение файлов описывающих платформу (включенных файлов в проект) и формирование единого описания для платформы.
  • Присоединение использует DT(Device Tree) оператор /include/, или с некоторых выпусков ядра, DTS через процессорную обработку #include (рекомендуется).

Пример присоединение дерева устройств.

device_tree_4

device_tree_5

Концепция присоединение дерева устройств.

  • Цитируем ePAPR: данный раздел содержит требования, известные как присоединение (привязки) для специфики типов и классов устройств, представленных в дереве устройств.
  • Совместимое свойство узла устройства описывает специфическое связывание, к которому соответствует узел.
  • При создании нового представления дерева устройств для аппаратной платформы, должна быть создана привязка, которая полностью описывает требуемые свойства и значения устройства. Этот набор свойств должен быть достаточно описательным, чтобы обеспечить драйвер устройства с необходимыми атрибутами устройств.

Документация по связям дерева устройств.

  • Все дерево устройств имеет связи в ядре и имеет описание в документации Documentation/devicetree/bindings.
  • Каждая связь в документации описывает какие свойства могут быть разрешены, с какими значениями, какие свойства обязательные, а какие свойства используются опционально.
  • Все новые связи дерева устройств должны быть описаны в поддержке дерева устройств, и после отправлено на devicetree@vger.kernel.org. Должна гарантироваться корректность и последовательность через связи.
OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS
M: Rob Herring <rob.herring@calxeda.com>
M: Pawel Moll <pawel.moll@arm.com>
M: Mark Rutland <mark.rutland@arm.com>
M: Stephen Warren <swarren@wwwdotorg.org>
M: Ian Campbell <ijc+devicetree@hellion.org.uk>
L: devicetree@vger.kernel.org

Пример по связям дерева устройств.

* Freescale MXS Application UART (AUART)
Required properties:
- compatible : Should be "fsl,<soc>-auart". The supported SoCs include
imx23 and imx28.
- reg : Address and length of the register set for the device
- interrupts : Should contain the auart interrupt numbers
- dmas: DMA specifier, consisting of a phandle to DMA controller node
and AUART DMA channel ID.
Refer to dma.txt and fsl-mxs-dma.txt for details.
- dma-names: "rx" for RX channel, "tx" for TX channel.
Example:
auart0: serial@8006a000 {
    compatible = "fsl,imx28-auart", "fsl,imx23-auart";
    reg = <0x8006a000 0x2000>;
    interrupts = <112>;
    dmas = <&dma_apbx 8>, <&dma_apbx 9>;
    dma-names = "rx", "tx";
};
Note: Each auart port should have an alias correctly numbered in "aliases"
node.
Example:
[...]

Организация дерева устройств на наивысшем уровне описания узла.

Под корнем дерева устройств, используется следующая организация уровня узла дерева устройств:

  • Узел cpus(центрального процессора), который имеет под-узел описания кажущего CPU в системе.
  • Узел memory(узел памяти), который определяет расположение и размер ОЗУ(RAM).
  • Узел chosen(выбора), который определяет параметры выбора или определения времени загрузки системной прошивки. В практике, используется для передачи управления командной строки ядра.
  • Узел aliases(прозвища), по сути дела определение горячих клавиш для основного узла.
  • Один или много узлов определения шин в SoC (система на кристалле).
  • Один или много узлов определения устройств платы.

Организация дерева устройств на imx28.dtsi.

arch/arm/boot/dts/imx28.dtsi
/ {
    aliases { ... };
    cpus { ... };
    apb@80000000 {
        apbh@80000000 {
        /* Some devices */
        };
        apbx@80040000 {
        /* Some devices */
        };
    };
    ahb@80080000 {
        /* Some devices */
    };
};

Организация дерева шин в i.MX28.

device-tree-bus

Организация дерева устройств на imx28.dts.

arch/arm/boot/dts/imx28-evk.dts
/ {
    model = "Freescale i.MX28 Evaluation Kit";
    compatible = "fsl,imx28-evk", "fsl,imx28";
    memory {
        reg = <0x40000000 0x08000000>;
    };
    apb@80000000 {
        apbh@80000000 { ... };
        apbx@80040000 { ... };
    };
    ahb@80080000 { ... };
    sound { ... };
    leds { ... };
    backlight { ... };
};

Верхний уровень совместимости свойств.

  • Верхний уровень совместимости свойств в основном описывается совместимой строкой для платы, и для SoC.
  • Значение всегда дается в первом случае в наиболее корректной форме, в последнем случае наименее корректной форме (не специфичной).
  • Использование соответствия с dt_compat поля DT_MACHINE структуры.
static const char *mxs_dt_compat[] __initdata = {
    "fsl,imx28",
    "fsl,imx23",
    NULL,
};
DT_MACHINE_START(MXS, "Freescale MXS (Device Tree)")
    .dt_compat = mxs_dt_compat,
    [...]
  • Можно всегда использовать с кодом для тестирования платы.
if (of_machine_is_compatible("fsl,imx28-evk"))
    imx28_evk_init();

Шины, адреса ячеек и размер ячеек.

Для внутренней шины необходимо определить следующие свойства:

  • Совместимые свойства, которые идентифицируют контроллер шины (для случаев I2C, SPI, PCI и других). Особое значение compatible = “simple-bus” способствует простой адресации памяти шины для не специфического обработчика или драйвера. Узел-ребенок может быть зарегистрирован для платформы.
  • Свойство #address-cells показывает, сколько ячеек памяти (32 битных значений) необходимо для части базовой адресации для свойств reg.
  • #size-cells одинаковый, для части размера свойства reg.
  • В свойство ranges(диапазон) может описываться адрес трансляции между шиной-ребенок и шиной-родитель. Когда просто объявляется диапазон, простое объявление имеет ввиду что транслятор идентифицирует трансляцию.

simple-bus, адреса ячеек и размер ячеек.

apbh@80000000 {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0x80000000 0x3c900>;
    ranges;
    [...]
    hsadc: hsadc@80002000 {
        reg = <0x80002000 0x2000>;
        interrupts = <13>;
        dmas = <&dma_apbh 12>;
        dma-names = "rx";
        status = "disabled";
    };
    [...]
};

Шина I2C, адреса ячеек и размер ячеек.

i2c0: i2c@80058000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx28-i2c";
    reg = <0x80058000 0x2000>;
    interrupts = <111>;
    [...]
    sgtl5000: codec@0a {
        compatible = "fsl,sgtl5000";
        reg = <0x0a>;
        VDDA-supply = <&reg_3p3v>;
        VDDIO-supply = <&reg_3p3v>;
        clocks = <&saif0>;
    };
    at24@51 {
        compatible = "at24,24c32";
        pagesize = <32>;
        reg = <0x51>;
    };
};

Обработка прерываний.

  • interrupt-controller (контроллер прерывания) бинарное свойство, показывает что текущий узел контроллер прерывания.
  • #interrupt-cells (ячейка прерывания) показывает номер ячеек в свойстве прерываний, для менеджера прерываний, с помощью выбора контроллера прерывания.
  • interrupt-parent (родитель-прерывания) phandle указатель на контроллер прерывания для текущего узла. Основной наивысший уровень родитель-прерывания описывает поведение для главного контроллера прерывания.

Пример прерывания imx28.dtsi.

/
{
    interrupt - parent = <&icoll>;
    apb @80000000
    {
        apbh @80000000
        {
            icoll: interrupt - controller @80000000 {
                compatible = "fsl,imx28-icoll", "fsl,icoll";
                interrupt - controller;
                #interrupt - cells = < 1 > ;
                reg = <0x80000000 0x2000>;
            };
            ssp0: ssp @80010000 {
                [...] interrupts = <96>;
            };
        };
    };
};

Пример совместимости на Tegra 20.

tegra-20

Пример прерывания tegra20.dtsi.

/
{
    interrupt - parent = <&intc>;
    intc: interrupt - controller {
        compatible = "arm,cortex-a9-gic";
        reg = <0x50041000 0x1000 0x50040100 0x0100>;
        interrupt - controller;
        #interrupt - cells = < 3 > ;
    };
    i2c @7000c000 {
        compatible = "nvidia,tegra20-i2c";
        reg = <0x7000c000 0x100>;
        interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
        #address - cells = < 1 > ;
        #size - cells = < 0 > ;
        [...]
    };
    gpio: gpio {
        compatible = "nvidia,tegra20-gpio";
        reg = <0x6000d000 0x1000>;
        interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>, [...],
        <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
        #gpio - cells = < 2 > ;
        gpio - controller;
        #interrupt - cells = < 2 > ;
        interrupt - controller;
    };
};

Пример прерывания tegra20-harmony.dts.

i2c @7000c000
{
    status = "okay";
    clock - frequency = <400000>;
    wm8903:wm8903 @1a
    {
        compatible = "wlf,wm8903";
        reg = <0x1a>;
        interrupt - parent = <&gpio>;
        interrupts = <TEGRA_GPIO(X, 3) IRQ_TYPE_LEVEL_HIGH>;
        gpio - controller;
        #gpio - cells = < 2 > ;
        micdet - cfg = <0>;
        micdet - delay = <100>;
        gpio - cfg = <0xffffffff 0xffffffff 0 0xffffffff 0xffffffff>;
    };
};

Пример дерева тактирования, Marvell Armada XP.

device-tree-clock

Создание экземпляра объекта тактирования.

soc
{
    coreclk: mvebu - sar @18230 {
        compatible = "marvell,armada-xp-core-clock";
        reg = <0x18230 0x08>;
        #clock - cells = < 1 > ;
    };
    cpuclk: clock - complex @18700 {
        #clock - cells = < 1 > ;
        compatible = "marvell,armada-xp-cpu-clock";
        reg = <0x18700 0xA0>;
        clocks = <&coreclk 1>;
    };
    gateclk: clock - gating - control @18220 {
        compatible = "marvell,armada-xp-gating-clock";
        reg = <0x18220 0x4>;
        clocks = <&coreclk 0>;
        #clock - cells = < 1 > ;
    };
}
clocks
{
/* 25 MHz reference crystal */
    refclk:oscillator {
        compatible = "fixed-clock";
        #clock - cells = < 0 > ;
        clock - frequency = <25000000>;
    };
};

Пример тактирования: распределение тактирования.

CPU, использует cpuclk

cpu@0 {
    device_type = "cpu";
    compatible = "marvell,sheeva-v7";
    reg = <0>;
    clocks = <&cpuclk 0>;
};

Таймер, использует один из 2-х сигналов тактирования coreclk или refclk.

timer@20300 {
    compatible = "marvell,armada-xp-timer";
    clocks = <&coreclk 2>, <&refclk>;
    clock-names = "nbclk", "fixed";
};

USB, использует gateclk

usb@52000 {
    compatible = "marvell,orion-ehci";
    reg = <0x52000 0x500>;
    interrupts = <47>;
    clocks = <&gateclk 20>;
    status = "disabled";
};

Связи pinctrl: клиентская сторона.

  • Подсистема pinctrl позволяет управлять мультиплексированием ног микропроцессора.
  • В дереве устройств, ноги микропроцессора должны быть мультиплексированы и для них должна быть объявлена конфигурация pinctrl.
  • pinctrl- – свойства позволяющие дать список pinctrl, конфигурация нужная для основного состояния устройства.
  • pinctrl-names свойство позволяющее дать имя каждому состоянию.
  • Когда устройство проверено, начальное pinctrl состояние использует определение по умолчанию, после, состояние определяется с помощью запроса.
ssp0: ssp@80010000 {
    pinctrl-names = "default";
    pinctrl-0 = <&mmc0_8bit_pins_a
                &mmc0_cd_cfg &mmc0_sck_cfg>;
    [...]
};

Конфигурация pinctrl.

  • Конфигурация pinctrl предоставляет список ножек микроконтроллера и их конфигурацию.
  • Подобная конфигурация определяет под-узел pinctrl устройства, для одного из уровня, уровня SoC или уровня платы.
  • Связи для такой конфигурации очень зависят от спецификации pinctrl используемого устройства.

i.MX28

mmc0_8bit_pins_a: mmc0-8bit@0 {
    fsl,pinmux-ids = <
        0x2000 /* MX28_PAD_SSP0_DATA0__SSP0_D0 */
        0x2010 /* MX28_PAD_SSP0_DATA1__SSP0_D1 */
        [...]
        0x2090 /* MX28_PAD_SSP0_DETECT__SSP0_... */
        0x20a0 /* MX28_PAD_SSP0_SCK__SSP0_SCK */
    >;
    fsl,drive-strength = <1>;
    fsl,voltage = <1>;
    fsl,pull-up = <1>;
};

Marvell Kirkwood

pmx_nand: pmx-nand {
    marvell,pins = "mpp0", "mpp1", "mpp2", "mpp3",
                "mpp4", "mpp5", "mpp18",
                "mpp19";
    marvell,function = "nand";
};

DT – описание железа, не конфигурация.

  • Дерево устройств – настоящий язык описания железа.
  • Необходимо описать уровень(слой) железа и понимать как это работает.
  • Но нет конкретного описания, конфигурации, железа нацеленного именно под ваши интересы.
  • Для примера. Вы можете описать DT который может использовать DMA или Вы наоборот можете не использовать DMA. Также Вы можете не описывать DT для DMA, если в DMA нет необходимости.

DT связи для ABI(application binary interface – бинарный интерфейс приложений).

  • С тех пор как появилось дерево устройств, ядро операционной системы стало более независимое.
  • Данная оригинальная идея позволяет записать DTB на на различные устройства производителей, так же данный факт позволяет записать пользователю любой дистрибутив операционной системы.
  • Дерево устройств связано в DTB и после этого не может быть изменено.
  • Данная вещь обычно обозначает, что связь дерева устройств становиться частью ядра ABI, необходимо с осторожностью обращаться с данным функционалом.
  • Однако, разработчики ядра не понимают, что этого очень тяжело добиться и замедляют интеграцию драйверов.
  • На саммите ARM ядра ведутся дебаты по поводу упрощения правил. Добавляются дискуссионные вопросы для саммита ядра, и после публикуются отчеты.

Базовое руководство для проектирования связей.

  • Точная совместимость строки лучше, чем расплывчатая одна. У Вас есть драйвер который может быть наложен поверх вариантов T320 и T330 для Вашего железа. Вы можете временно использовать foo, t3xx для совместной строки описания. Плохая идея, что если T340 немного отличается, в широком смысле? Лучше будет использовать foo, t320 для обеих T320 и T330.
  • Не создавайте слишком большого описания деталей железа в дереве устройств. Когда две платформы отличаются незначительно, разработчики могут добавить все изменения в дерево устройств, это может быть включаемые регистры смещения, битовые маски или др.
  • Плохая идея: создавать связи для большого комплекса устройств, это может привести к проблемам в будущем. Для этого лучше использовать 2 строки совмещения и обработчик для различных устройств.

Направление развития на будущее.

  • Построения более разнообразных вариантов подсистем связей дерева устройств.
  • Инструмент который проверит систему дерева устройств.
  • Уменьшить количество вычислений за пределами ядра.

Оригинал статьи на английском языке по ссылке.