Gerätebaum-Overlays optimieren

Auf dieser Seite werden Optimierungsmöglichkeiten für deine Gerätebaum-Overlays (Device Tree Overlays, DTOs) vorgestellt, Beschränkungen für Overlays auf dem Stammknoten beschrieben und Details zur Konfiguration komprimierter Overlays auf einem DTBO-Image (Device Tree Blob Overlay) erläutert. Außerdem werden Beispiel-Implementierungsanleitungen und ‑Code bereitgestellt.

Kernel-Befehlszeile

Die ursprüngliche Kernel-Befehlszeile im Gerätebaum befindet sich im chosen/bootargs-Knoten. Der Bootloader muss diesen Parameter mit anderen Quellen der Kernel-Befehlszeile verketten:

/dts-v1/;

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

Das DTO kann keine Werte aus dem Haupt-Gerätebaum und dem Overlay-Gerätebaum verketten, daher musst du die Kernel-Befehlszeile des Haupt-Gerätebaums in chosen/bootargs und die Kernel-Befehlszeile des Overlay-Gerätebaums in chosen/bootargs_ext speichern. Der Bootloader kann dann diese Parameter verketten und das Ergebnis an den Kernel übergeben.

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

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

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

libufdt

Die neueste Version von libfdt unterstützt zwar DTOs, aber es wird dennoch empfohlen, für die DTO-Implementierung libufdt zu verwenden (AOSP-Quelle unter platform/system/libufdt). libufdt erstellt aus dem zusammengeführten Gerätebaum (Flattened Device Tree, FDT) eine echte Baumstruktur (einen nicht zusammengeführten Gerätebaum oder ufdt), um das Zusammenführen der beiden .dtb-Dateien aus O(N2) zu O(N) zu verbessern, wobei N die Anzahl der Knoten im Baum ist.

Leistungstests

Bei den internen Tests von Google nach der Kompilierung führte die Verwendung von libufdt bei 2.405 .dtb- und 283 .dtbo-Gerätebaum-Knoten zu Dateigrößen von 70.618 und 8.566 Byte. Die von FreeBSD portierte DTO-Implementierung hatte eine Laufzeit von 124 ms, die libufdt-DTO-Laufzeit betrug 10 ms.

Bei Leistungstests von Pixel-Geräten wurden libufdt und libfdt miteinander verglichen. Die Auswirkung durch die Anzahl der Basis-Knoten ist ähnlich, aber es wurden folgende Unterschiede gemessen:

  • 500 Overlay-Operationen (Anhängen oder Überschreiben) dauerten 6- bis 8‑mal länger.
  • 1.000 Overlay-Operationen (Anhängen oder Überschreiben) dauerten 8- bis 10‑mal länger.

Beispiel mit der Anzahl der Operationen für das Anhängen auf der X-Achse:

Abbildung 1: Anzahl der Operationen für das Anhängen auf der X-Achse

Beispiel mit der Anzahl der Operationen für das Überschreiben auf der X-Achse:

Abbildung 2: Anzahl der Operationen für das Überschreiben auf der X-Achse

libufdt wird mit einigen APIs und Datenstrukturen von libfdt entwickelt. Wenn du libufdt verwendest, musst du libfdt implementiert und verlinkt haben. Du kannst in deinem Code aber die libfdt-API für DTB oder DTBO verwenden.

DTO-API in libufdt

Die Haupt-API für DTO in libufdt lautet:

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

Der Parameter main_fdt_header ist der Haupt-Gerätebaum und overlay_fdt ist der Zwischenspeicher mit den Inhalten einer .dtbo-Datei. Der zurückgegebene Wert ist ein neuer Zwischenspeicher mit dem zusammengeführten Gerätebaum (oder null bei einem Fehler). Der zusammengeführte Gerätebaum ist als FDT formatiert, sodass er beim Start an den Kernel übergeben werden kann.

Der neue Zwischenspeicher aus dem zurückgegebenen Wert wird mit dto_malloc() erstellt. Diese Funktion solltest du implementieren, wenn du libufdt in den Bootloader portierst. Referenzimplementierungen findest du unter sysdeps/libufdt_sysdeps_*.c.

Beschränkungen für den Stammknoten

Die Overlay-Operationen basieren auf Labels, daher kannst du in den Stammknoten des Haupt-Gerätebaums keine neuen Knoten oder Eigenschaften einfügen. Da für den Haupt-Gerätebaum ein Label definiert werden muss und bei dem Overlay-Gerätebaum die Labels über die Knoten gelegt werden, kannst du kein Label für den Stammknoten festlegen (und daher auch kein Overlay für den Stammknoten erstellen).

SoC-Anbieter müssen die Overlay-Funktion des Haupt-Gerätebaums definieren, ODM/OEMs können nur Knoten anhängen oder überschreiben, für die der SoC-Anbieter ein Label festgelegt hat. Als Workaround kannst du einen odm-Knoten im Stammknoten des Haupt-Gerätebaums festlegen, sodass für alle ODM-Knoten im Overlay-Gerätebaum neue Knoten hinzugefügt werden können. Alternativ kannst du alle SoC-Knoten im Haupt-Gerätebaum in einen soc-Knoten im Stammknoten verschieben. Das funktioniert folgendermaßen:

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 {
        ...
    };
    ...
};

Komprimierte Overlays verwenden

Bei Verwendung von Version 1 des Headers der Gerätebaum-Tabelle werden unter Android 9 auch komprimierte Overlays im DTBO-Image unterstützt. Wenn Version 1 des DTBO-Headers verwendet wird, geben die vier LSB (Least Significant Bits) im Feld „Flags“ im dt_table_entry das Komprimierungsformat des Gerätebaumeintrags an.

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 */
};

Derzeit werden zlib- und gzip-Komprimierungen unterstützt.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Unter Android 9 werden im Rahmen des VtsFirmwareDtboVerification-Tests auch Tests der komprimierten Overlays unterstützt, damit du die Overlay-App auf ihre Korrektheit überprüfen kannst.

Beispiel-DTO-Implementierung

Nachfolgend findest du die Anleitung für eine Beispielimplementierung des DTO mit libufdt (Beispielcode siehe unten).

Beispiel-DTO-Anleitung

  1. Binde die Bibliotheken ein. Wenn du libufdt verwenden möchtest, füge libfdt für Datenstrukturen und APIs hinzu:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
  2. Lade den Haupt- und den Overlay-Gerätebaum. Lade .dtb und .dtbo aus dem Speicher in den Arbeitsspeicher. Die einzelnen Schritte variieren je nach Design. Den Zwischenspeicher und die Größe von .dtb/.dtbo solltest du an dieser Stelle bereits kennen:
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
  3. Erstelle ein Overlay für die Gerätebäume:
    1. Rufe mithilfe von ufdt_install_blob() den FDT-Header für den Haupt-Gerätebaum ab:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
    2. Rufe ufdt_apply_overlay() für das DTO ab, um einen zusammengeführten Gerätebaum im FDT-Format zu erhalten:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
    3. Verwende merged_fdt, um die Größe von dtc_totalsize() abzurufen:
      merged_fdt_size = dtc_totalsize(merged_fdt);
    4. Übergib den zusammengeführten Gerätebaum, um den Kernel zu starten:
      my_kernel_entry(0, machine_type, merged_fdt);

Beispiel-DTO-Code

#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);
}