Оптимизация DTO

На этой странице обсуждаются оптимизации, которые можно внести в реализацию наложения дерева устройств (DTO), описываются ограничения на наложение корневого узла и подробно описывается, как настроить сжатые наложения в образе DTBO. Также приводятся примеры инструкций по реализации и кода.

Командная строка ядра

Исходная командная строка ядра в дереве устройств (DT) находится в узле chosen/bootargs . Загрузчик должен объединить это местоположение с другими источниками командной строки ядра:

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

DTO не может объединять значения из основного DT и наложенного DT, поэтому необходимо поместить командную строку ядра основного DT в chosen/bootargs , а командную строку ядра наложенного DT — в chosen/bootargs_ext . После этого загрузчик сможет объединить эти значения и передать результат ядру.

main.dts overlay.dts
/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};
/dts-v1/;
/plugin/;

&chosen {
  bootargs_ext = "...";
};

либуфдт

Хотя последняя версия libfdt поддерживает DTO, рекомендуется ли использовать libufdt для реализации DTO (исходный код AOSP находится в platform/system/libufdt )? libufdt строит реальную древовидную структуру (несглаженное дерево устройств, или ufdt ) из сглаженного дерева устройств (FDT), поэтому она может улучшить слияние двух файлов .dtb со O( ) до O(N), где N — количество узлов в дереве.

Тестирование производительности

В ходе внутреннего тестирования Google использование libufdt на 2405 узлах DT с .dtb и 283 узлах DT с файлами .dtbo привело к размерам файлов 70 618 и 8 566 байт после компиляции. По сравнению с реализацией DTO , портированной из FreeBSD (время выполнения 124 мс), время выполнения DTO libufdt составляет 10 мс.

В ходе тестирования производительности устройств Pixel сравнивались libufdt и libfdt . Влияние количества базовых узлов аналогично, но имеет следующие отличия:

  • 500 операций наложения (добавления или переопределения) имеют разницу во времени от 6 до 8 раз.
  • Для 1000 операций наложения (добавления или переопределения) разница во времени составляет от 8 до 10 раз.

Пример с установленным значением счетчика добавления X:

Рисунок 1. Количество добавлений равно X.

Пример с установленным на X значением счетчика, определяющим значение по умолчанию:

Рисунок 2. Переопределяющий счетчик — X.

libufdt разработана с использованием некоторых API и структур данных libfdt . При использовании libufdt необходимо включить и скомпоновать libfdt (однако в вашем коде вы можете использовать API libfdt для работы с DTB или DTBO).

API DTO libufdt

Основной API для работы с DTO в libufdt выглядит следующим образом:

struct fdt_header *ufdt_apply_overlay(
        struct fdt_header *main_fdt_header,
        size_t main_fdt_size,
        void *overlay_fdt,
        size_t overlay_size);

Параметр main_fdt_header — это основной DT, а overlay_fdt — буфер, содержащий содержимое файла .dtbo . Возвращаемое значение — это новый буфер, содержащий объединенный DT (или null в случае ошибки). Объединенный DT отформатирован в формате FDT, который можно передать ядру при запуске ядра.

Новый буфер из возвращаемого значения создаётся функцией dto_malloc() , которую следует реализовать при портировании libufdt в загрузчик. Примеры реализаций см. в файле sysdeps/libufdt_sysdeps_*.c .

Ограничения корневого узла

Нельзя наложить новый узел или свойство на корневой узел основного дерева данных, поскольку операции наложения зависят от меток. Поскольку основное дерево данных должно определять метку, а дерево данных наложения назначает узлы, которые нужно наложить, метками, нельзя задать метку для корневого узла (и, следовательно, нельзя наложить метку на корневой узел).

Производители SoC должны определить возможности наложения основного дерева решений (DT); ODM/OEM могут добавлять или переопределять узлы только с метками, определенными производителем SoC. В качестве обходного пути можно определить узел odm под корневым узлом в базовом дереве решений, что позволит всем узлам ODM в наложенном дереве решений добавлять новые узлы. В качестве альтернативы можно поместить все узлы, связанные с SoC, в базовом дереве решений в узел soc под корневым узлом, как описано ниже:

main.dts overlay.dts
/dts-v1/;

/ {
    compatible = "corp,bar";
    ...

    chosen: chosen {
        bootargs = "...";
    };

    /* nodes for all soc nodes */
    soc {
        ...
        soc_device@0: soc_device@0 {
            compatible = "corp,bar";
            ...
        };
        ...
    };

    odm: odm {
        /* reserved for overlay by odm */
    };
};
/dts-v1/;
/plugin/;

/ {
};

&chosen {
    bootargs_ex = "...";
};

&odm {
    odm_device@0 {
        ...
    };
    ...
};

Используйте сжатые наложения

В Android 9 добавлена ​​поддержка использования сжатых наложений в образе DTBO при использовании заголовка таблицы DT версии 1. При использовании заголовка DTBO версии 1 четыре младших бита поля flags в dt_table_entry указывают формат сжатия записи DT.

struct dt_table_entry_v1 {
  uint32_t dt_size;
  uint32_t dt_offset;  /* offset from head of dt_table_header */
  uint32_t id;         /* optional, must be zero if unused */
  uint32_t rev;        /* optional, must be zero if unused */
  uint32_t flags;      /* For version 1 of dt_table_header, the 4 least significant bits
                        of 'flags' are used to indicate the compression
                        format of the DT entry as per the enum 'dt_compression_info' */
  uint32_t custom[3];  /* optional, must be zero if unused */
};

В настоящее время поддерживаются алгоритмы сжатия zlib и gzip .

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

В Android 9 добавлена ​​поддержка тестирования сжатых наложений в тесте VtsFirmwareDtboVerification , что поможет вам проверить корректность работы приложения-наложения.

Пример реализации DTO

Следующие инструкции демонстрируют пример реализации DTO с использованием libufdt (пример кода ниже).

Пример инструкций DTO

  1. Включите библиотеки. Для использования libufdt необходимо включить libfdt , содержащую структуры данных и API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
  2. Загрузите основной DT и наложенный DT. Загрузите файлы .dtb и .dtbo из хранилища в память (точные шаги зависят от вашего проекта). На этом этапе у вас должны быть буфер и размер файлов .dtb / .dtbo :
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
  3. Наложите DT-элементы друг на друга:
    1. Используйте функцию ufdt_install_blob() для получения заголовка FDT для основного DT:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
    2. Вызовите функцию ufdt_apply_overlay() для DTO, чтобы получить объединенный DT в формате FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
    3. Используйте merged_fdt , чтобы получить размер dtc_totalsize() :
      merged_fdt_size = dtc_totalsize(merged_fdt);
    4. Передайте объединенный DT для запуска ядра:
      my_kernel_entry(0, machine_type, merged_fdt);

Пример кода DTO

#include <libfdt.h>
#include <ufdt_overlay.h>



{
  struct fdt_header *main_fdt_header;
  struct fdt_header *merged_fdt;

  /* load main dtb into memory and get the size */
  main_size = my_load_main_dtb(main_buf, main_buf_size);

  /* load overlay dtb into memory and get the size */
  overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);

  /* overlay */
  main_fdt_header = ufdt_install_blob(main_buf, main_size);
  main_fdt_size = main_size;
  merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                  overlay_buf, overlay_size);
  merged_fdt_size = dtc_totalsize(merged_fdt);

  /* pass to kernel */
  my_kernel_entry(0, machine_type, merged_fdt);
}