Структура двоичного FDT-файла


Flattened Devicetree (DTB) Format

Формат Device Tree Blob (DTB) - это плоское двоичное кодирование данных дерева устройств. Он использовался для обмена данными дерева устройств между программами. Например, при загрузке операционной системы микропрограмма передает DTB ядру ОС.

IEEE1275 (IEEE Standard for Boot, Initialization, Configuration Firmware) - стандарт IEEE для загрузки, конфигурация и инициализации встроенного ПО, описывающий Open Firmware - не определяет формат DTB. На большинстве платформ, совместимых с Open Firmware, дерево устройств извлекается путем вызова методов встроенного ПО для просмотра древовидной структуры.

Формат DTB кодирует данные дерева устройств в одной линейной структуре данных без указателей. Он состоит из небольшого заголовка (см. Заголовок), за которым следуют три раздела переменного размера:

  • блок резервирования памяти (см. Блок резервирования памяти ),
  • блок структуры (см. Блок структуры ),
  • блок строк (см. Блок строк ).

    Они должны присутствовать в плоском дереве устройства именно в таком порядке. Таким образом, структура дерева устройств в целом при загрузке в память по адресу будет напоминать такую схему, здесь вверху показаны младшие (нижние) адреса.

    Разделы free space (свободное пространство) могут отсутствовать, хотя в некоторых случаях они могут потребоваться для удовлетворения ограничений выравнивания отдельных блоков (см. Выравнивание ) .


    1. Управление версиями

    Несколько версий плоской структуры дерева устройств были определены с момента первоначального определения формата. Поля в заголовке указывают версию, чтобы клиентская программа могла определить, закодировано ли дерево устройств в совместимом формате.

    В этом документе описывается только версия 17 формата, совместимого с загрузочными программами, которые должны предоставлять дерево устройств версии 17 или более поздней, а также должны предоставлять дерево устройств версии, которая обратно совместима с версией 16.

    Примечание: Версия относится к двоичной структуре дерева устройств, а не к его содержимому.


    2. Заголовок (Header)

    Макет заголовка для дерева устройств определяется следующей С-структурой. Все поля заголовка представляют собой 32-битные целые числа, хранящиеся в формате big-endian.

    Flattened Devicetree Header Fields
    struct fdt_header {
        uint32_t magic;
        uint32_t totalsize;
        uint32_t off_dt_struct;
        uint32_t off_dt_strings;
        uint32_t off_mem_rsvmap;
        uint32_t version;
        uint32_t last_comp_version;
        uint32_t boot_cpuid_phys;
        uint32_t size_dt_strings;
        uint32_t size_dt_struct;
    };
    
  • magic

    Это поле должно содержать значение 0xd00dfeed (big-endian - прямой порядок байтов)/

  • totalsize

    Это поле должно содержать общий размер в байтах структуры данных дерева устройства. Этот размер должен охватывать все секции конструкции: заголовок, блок резервирования памяти, блок структуры и блок строк, а также любые пробелы в свободном пространстве между блоками или после последнего блока.

  • off_dt_struct

    Это поле должно содержать смещение в байтах блока структуры (см. Блок структуры ) от начала заголовка.

  • off_dt_strings

    Это поле должно содержать смещение в байтах блока строк (см. Блок строк ) от начала заголовка.

  • off_mem_rsvmap

    Это поле должно содержать смещение в байтах блока резервирования памяти (см. Блок резервирования памяти ) от начала заголовка.

  • version

    Это поле должно содержать версию структуры данных дерева устройств. Версия - 17, если используется структура, определенная в этом документе. Программа загрузки может предоставить дерево устройств более поздней версии, и в этом случае это поле должно содержать версию число, определенное в каком-либо более позднем документе, дает подробности этой версии.

  • last_comp_version

    Это поле должно содержать самую низкую версию структуры данных дерева устройств, с которой используемая версия имеет обратную совместимость. Таким образом, для структуры, определенной в этом документе (версия 17), это поле должно содержать 16, потому что версия 17 обратно совместима с версией 16, но не более ранними версиями. Согласно разделу versioning, программа загрузки должна предоставлять дерево устройств в формате, который обратно совместим с версией 16, и, следовательно, это поле всегда должно содержать 16.

  • boot_cpuid_phys

    Это поле должно содержать физический идентификатор загрузочного ЦП системы. Он должен быть идентичен физическому идентификатору, указанному в свойстве reg этого узла ЦП в дереве устройства.

  • size_dt_strings

    Это поле должно содержать длину в байтах части блока строк в BLOB (большом двоичном объекте) дерева устройства.

  • size_dt_struct Это поле должно содержать длину в байтах раздела блока структурs в BLOB дерева устройства.
    Примеры заголовков:

    Заголовки бинарных файла DTB и DTBO OrangePi Zero2



    3. Блок резервирования памяти

    Блок резервирования памяти предоставляет клиентской программе список областей в физической памяти, которые зарезервированы, то есть, которые не должны использоваться для общего распределения памяти.

    Он используется для защиты жизненно важных структур данных от перезаписи клиентской программой. Например, в некоторых системах с IOMMU TCE, инициализированных таким образом, необходимо защитить загрузочную программу.

    Аналогичным образом, любой код или данные загрузочной программы, используемые во время выполнения клиентской программы, должны быть зарезервированы. Например, RTAS на платформах Open Firmware не требует, чтобы программа загрузки предоставляла такие компоненты среды выполнения, но не запрещает реализации, сделав это как расширение.

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

    Методы, с помощью которых программа загрузки может указывать клиентской программе на конкретное использование зарезервированной памяти, могут появиться в этом документе, в дополнительных его расширениях или в документации для конкретной платформы.

    Зарезервированные области, предоставляемые программой загрузки, могут, но не обязательно, включать в себя blob (большой двоичный объект) дерева устройства. Клиентская программа должна гарантировать, что она не перезапишет эту структуру данных до того, как она будет используется, независимо от того, находится ли он в зарезервированных зонах.

    Любая память, объявленная в узле памяти и доступная для программы загрузки или вызываемая программой загрузки после входа клиента, должна быть зарезервирована. Примеры этого типа доступа включают, например, рискованное чтение памяти через незащищенную виртуальную страницу.

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

    Любые обращения к зарезервированной памяти загрузочной программой или вызванные ею должны выполняться, поскольку кэширование не запрещено и требуется согласованность mcmory (т.е. WIMG=0bx01x), и, кроме того, для реализаций Book III-S, поскольку запись не требуется (т.е. WIMG=0b001x). Кроме того, если поддерживается атрибут хранилища VLE, все обращения к зарезервированной памяти должны выполняться как VLE = 0.

    Это требование необходимо, потому что клиентской программе разрешено отображать память с атрибутами хранения, указанными как не требуется сквозная запись, не запрещено кэширование и требуется согласованность памяти. (т. е. WIMG = 0b001x) и VLE = 0, если поддерживается.

    Клиентская программа может использовать большие виртуальные страницы, содержащие зарезервированную память. Однако клиентская программа может не изменять зарезервированную память, поэтому программа загрузки может выполнять доступ к зарезервированной памяти, если требуется запись, в случае конфликта значения для этого атрибута хранения допустимы с архитектурной точки зрения.

    Формат

    Блок резервирования памяти состоит из списка пар 64-битных целых чисел с прямым порядком байтов, каждая пара представлена следующей C-структурой.

    struct fdt_reserve_entry {
            uint64_t address;
            uint64_t size;
    };
    

    Каждая пара дает физический адрес и размер в байтах зарезервированной области памяти. Эти данные регионы не должны перекрывать друг друга. Список зарезервированных блоков должен заканчиваться записью, в которой и адрес, и размер равны 0. Обратите внимание, что значения адреса и размера всегда 64-битные. На 32-битных процессорах верхние 32 бита значения игнорируются.

    Каждый uint64_t в блоке резервирования памяти и, следовательно, блок резервирования памяти в целом, должен располагаться по 8-байтовому выровненному смещению от начала большого двоичного объекта дерева устройства (см. Выравнивание ).


    4. Блок структуры

    Блок структуры описывает структуру и содержимое самого дерева устройств. Он состоит из последовательности токенов с данными, как описано ниже. Они организованы в линейную древовидную структуру, как описано ниже.

    Каждый токен в структурном блоке и, следовательно, сам структурный блок должен располагаться в 4-байтовое выровненное смещение от начала большого двоичного объекта дерева устройства (см. Выравнивание ).


    4.1. Лексическая структура

    Блок структуры состоит из последовательности частей, каждая из которых начинается токеном, представляющим собой 32-битное целое число с прямым порядком байтов. За некоторыми токенами следуют дополнительные данные, формат которых определяется значением токена. Все токены должны быть выровнены по 32-битной границе, что может потребовать добавления байтов заполнения (со значением 0x0) после данных предыдущего токена.

    Пять типов токенов:

  • FDT_BEGIN_NODE (0x00000001)

    Токен FDT_BEGIN_NODE отмечает начало представления узла. За ним должно следовать имя узла в качестве дополнительных данных. Имя хранится в виде строки с завершающим нулем и должно включать адрес устройства, если таковой имеется.

    За именем узла следуют нулевые байты заполнения, если это необходимо для выравнивания, а затем следующий токен, который может быть любым токеном, кроме FDT_END.

  • FDT_END_NODE (0x00000002)

    Токен FDT_END_NODE отмечает конец представления узла. У этого токена нет дополнительных данных, поэтому сразу за ним следует следующий токен, который может быть любым токеном, кроме FDT_PROP.

  • FDT_PROP (0x00000003)

    Токен FDT_PROP отмечает начало представления одного свойства в дереве устройств. За ним должны следовать дополнительные данные, описывающие значения свойства. Эти данные состоят в первую очередь из длины и имени свойства, представленного в виде следующей C-структуры

     struct {
         uint32_t len;
         uint32_t nameoff;
    }
    

    Оба поля в этой структуре представляют собой 32-bit big-endian integers (32-битные целые числа с прямым порядком байтов).

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

    После этой структуры лежит значение свойства в виде байтовой строки длиной len.

    За этим значением следуют нулевые байты заполнения (при необходимости) для выравнивания по следующей 32-битной границе, а затем следующий токен, который может быть любым токеном, кроме FDT_END.

  • FDT_NOP (0x00000004)

    Токен FDT_NOP будет проигнорирован любой программой, анализирующей дерево устройств. У этого токена нет дополнительных данных; поэтому сразу за ним следует следующий токен, который может быть любым допустимым токеном.

    Определение свойства или узла в дереве может быть перезаписано токенами FDT_NOP, чтобы удалить его из дерева без необходимости перемещать другие разделы представления дерева в большом двоичном объекте дерева устройств.

  • FDT_END (0x00000009)

    Токен FDT_END отмечает конец блока структуры.

    Должен быть только один токен FDT_END, и он должен быть последним токеном в блоке структуры. У него нет дополнительных данных, поэтому байт сразу после токена FDT_END имеет смещение от начала блока структуры, равное значению поля size_dt_struct в заголовке blob дерева устройств.


    Примеры блоков структуры (узлы):

    Блок структуры файла DTB OrangePi Zero2     Блок структуры файла DTBO(overlay) OrangePi Zero2



    4.2 Структура дерева

    Структура дерева устройств представлена в виде линейного дерева. Представление каждого узла начинается с токена FDT_BEGIN_NODE и заканчивается токеном FDT_END_NODE.

    Свойства и подузлы узла (если есть) представлены перед FDT_END_NODE, поэтому токены FDT_BEGIN_NODE и FDT_END_NODE для этих подузлов вложены внутри родительского.

    Блок структуры в целом состоит из представления корневого узла, содержащего представления для всех других узлов, за которым следует токен FDT_END для обозначения конца структурного блока в целом.

    Представление каждого узла состоит из следующих компонентов:

    1) Любое количество токенов FDT_NOP (необязательно)

    2) Токен FDT_BEGIN_NODE

    • Имя узла в виде строки с нулевым символом в конце
    • [нулевые байты заполнения для выравнивания по 4-байтовой границе]

    3) Для каждого свойства узла:

    • Любое количество токенов FDT_NOP (необязательно)
    • Токен FDT_PROP
    • [нулевые байты заполнения для выравнивания по 4-байтовой границе]

    4) Представления всех дочерних узлов в этом формате

    5) Любое количество токенов FDT_NOP (необязательно)

    6) Токен FDT_END_NODE


    Обратите внимание, что этот процесс требует, чтобы все определения свойств для конкретного узла предшествовали любым определениям подузлов для этого узла (т.е. сначала идут свойства, затем - подузлы).

    Хотя структура не была бы неоднозначной, если бы свойства и подузлы были смешанными, код, необходимый для обработки плоского дерева, упрощается этим требованием.


    Примечание ред. На мой взгляд, на самом деле такая "структура" плохо структурирована и крайне неудобна для анализа:
    1. Значения токенов являются простыми числами (1,2,3,4), при этом такие же числа могут встречаться в значения свойств. Токен - это целых 4 байта, что мешало сделать их уникальными... ? В этом случае парсинг узлов и свойств можно было бы сделать гораздо проще, с конвертацией в XML или любое другое представление.
    2. Значение любого свойства - это просто цепочка байтов с нулем на конце. При этом отсутствует явное указание на тип данных (строка, числа и т.д.), что делает его определение не всегда однозначным.


    5. Блок строк

    Блок строк содержит строки, представляющие все имена свойств, используемые в дереве.

    Эти строки с завершающим нулем просто объединяются вместе в этом разделе, и на них ссылаются из блока структуры по смещению в блоке строк.

    Блок строк не имеет ограничений выравнивания и может появляться при любом смещении от начала blob-объекта дерева устройств.


    Примечание ред. Анализ бинарного файла FDT устройства Orange Pi Zero 2 показывает, что в блоке строк некоторые имена свойств представлены не самостоятельной строкой, а подстрокой другого, более сложного имени, например:
    phandle - часть имени linux,phandle
    clocks - assigned-clocks
    type - device_type
    max-frequency spi-max-frequency

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


    Примеры блоков строк:

    Блок строк файла DTB OrangePi Zero2     Блок строк файла DTBO(overlay) OrangePi Zero2



    6. Выравнивание

    Чтобы данные в блоках резервирования и структуры памяти использовались без невыровненных обращений к памяти, они должны располагаться по соответствующим образом выровненным адресам памяти. В частности, блок резервирования памяти должен быть выровнен по 8-байтовой границе, а структурный блок - по 4-х байтовой границе.

    Кроме того, blob-объект дерева устройств в целом может быть перемещен без нарушения выравнивания подблоков.

    Как описано в предыдущих разделах, блоки структуры и строк должны иметь выровненные смещения от начала большого двоичного объекта дерева устройства. Чтобы обеспечить выравнивание блоков в памяти, достаточно убедиться, что дерево устройства в целом загружается по адресу, выровненному по наибольшему выравниванию любого из подблоков, то есть по 8-байтовой границе.

    Совместимая программа загрузки должна загружать blob-объект дерева устройства. по такому выровненному адресу перед передачей его клиентской программе.

    Если клиентская программа перемещает большой двоичный объект дерева устройства в памяти, она должна сделать это только на другой 8-байтовый выровненный адрес.


    Примечание ред..

    Этот текст подготовлен на основе файла RST-формата devicetree-specification/source/flattened-format.rst ( GitHub ), который, в свою очередь, отражает содержание раздела 5 спецификации: Devicetree Specification v0.2