Optimierung von Device Tree Overlays

Auf dieser Seite werden Optimierungen für die Implementierung des Device Tree Overlays (DTO) beschrieben. Außerdem werden Einschränkungen für das Überlagern des Stammknotens und die Konfiguration komprimierter Overlays im DTBO-Image erläutert. Außerdem finden Sie hier eine Beispielimplementierung und Code.

Kernel-Befehlszeile

Die ursprüngliche Kernel-Befehlszeile in der Gerätestruktur (DT) befindet sich im Knoten chosen/bootargs. Der Bootloader muss diesen Speicherort mit anderen Quellen der Kernel-Befehlszeile verknüpfen:

/dts-v1/;

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

DTO kann keine Werte aus dem Haupt-DT und dem Overlay-DT zusammenführen. Sie müssen daher die Kernel-Befehlszeile des Haupt-DT in chosen/bootargs und die Kernel-Befehlszeile des Overlay-DT in chosen/bootargs_ext einfügen. Der Bootloader kann diese Speicherorte dann zusammenführen 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 DTO. Wir empfehlen jedoch, libufdt zum Implementieren von DTO zu verwenden (AOSP-Quelldatei unter platform/system/libufdt). Mit libufdt wird eine echte Baumstruktur (nicht flacher Device Tree, ufdt) aus dem flachen Device Tree (FDT) erstellt. So kann das Zusammenführen von zwei .dtb-Dateien von O(N2) auf O(N) verbessert werden, wobei N die Anzahl der Knoten im Baum ist.

Leistungstests

Bei den internen Tests von Google führte die Verwendung von libufdt für 2.405 .dtb- und 283 .dtbo-DT-Knoten nach der Kompilierung zu Dateigrößen von 70.618 und 8.566 Byte. Im Vergleich zu einer DTO-Implementierung, die von FreeBSD portiert wurde (124 ms Laufzeit), beträgt die DTO-Laufzeit von libufdt 10 ms.

Leistungstests für Pixel-Geräte im Vergleich zum libufdt und libfdt. Die Anzahl der Basisknoten hat einen ähnlichen Effekt, aber es gibt folgende Unterschiede:

  • 500 Overlay-Vorgänge (Anhängen oder Überschreiben) haben eine sechs- bis achtfache Zeitdifferenz
  • 1.000 Overlay-Vorgänge (Anhängen oder Überschreiben) haben einen 8- bis 10-fachen Zeitunterschied

Beispiel mit einer Anzahl von Anhängen, die auf X festgelegt ist:

Abbildung 1: Die Anzahl der angehängten Elemente ist X.

Beispiel mit der Anzahl der Überschreibungen auf X:

Abbildung 2. Die überschreibende Anzahl ist X.

libufdt wird mit einigen libfdt-APIs und Datenstrukturen entwickelt. Wenn Sie libufdt verwenden, müssen Sie libfdt einbinden und verknüpfen. In Ihrem Code können Sie jedoch die libfdt API verwenden, um DTB oder DTBO auszuführen.

libufdt DTO API

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

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-DT und overlay_fdt ist der Zwischenspeicher, der den Inhalt einer .dtbo-Datei enthält. Der Rückgabewert ist ein neuer Zwischenspeicher, der den zusammengeführten DT enthält (oder null im Falle eines Fehlers). Das zusammengeführte DT ist im FDT-Format formatiert, das Sie beim Starten des Kernels an den Kernel übergeben können.

Der neue Zwischenspeicher aus dem Rückgabewert wird von dto_malloc() erstellt. Diesen Zwischenspeicher solltest du implementieren, wenn du libufdt in den Bootloader migrierst. Referenzimplementierungen finden Sie unter sysdeps/libufdt_sysdeps_*.c.

Einschränkungen für Stammknoten

Sie können keinen neuen Knoten oder keine neue Property in den Stammknoten der Haupt-DT einfügen, da Overlay-Vorgänge auf Labels basieren. Da der Haupt-DT ein Label definieren muss und der Overlay-DT den Knoten zuweist, die mit Labels überlagert werden, können Sie dem Stammknoten kein Label zuweisen (und daher den Stammknoten nicht überlagern).

SoC-Anbieter müssen die Überlagerungsfunktion des Haupt-DT definieren. ODMs/OEMs können Knoten nur mit Labels anhängen oder überschreiben, die vom SoC-Anbieter definiert wurden. Als Problemumgehung können Sie einen odm-Knoten unter dem Stammknoten im Basis-DT definieren, damit alle ODM-Knoten im Overlay-DT neue Knoten hinzufügen können. Alternativ können Sie alle SoC-bezogenen Knoten in der Basis-DT in einen soc-Knoten unter dem Stammknoten platzieren, wie unten beschrieben:

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

Android 9 unterstützt die Verwendung komprimierter Overlays im DTBO-Image, wenn Version 1 des DT-Tabellen-Headers verwendet wird. Bei Verwendung des DTBO-Headers v1 geben die vier niedrigstwertigen Bits des Flags-Felds in dt_table_entry das Komprimierungsformat des DT-Eintrags 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 die Komprimierungen zlib und gzip unterstützt.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 unterstützt den Test von komprimierten Overlays im VtsFirmwareDtboVerification-Test, um die Richtigkeit der Overlay-App zu überprüfen.

Beispiel für eine DTO-Implementierung

In der folgenden Anleitung wird eine Beispielimplementierung von DTO mit libufdt veranschaulicht (Beispielcode unten).

Beispiel für eine DTO-Anleitung

  1. Fügen Sie Bibliotheken hinzu. Wenn Sie libufdt verwenden möchten, fügen Sie libfdt für Datenstrukturen und APIs ein:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Laden Sie das Haupt-DT und das Overlay-DT. Laden Sie .dtb und .dtbo aus dem Speicher in den Arbeitsspeicher. Die genauen Schritte hängen von Ihrem Design ab. Sie sollten jetzt den Puffer und die Größe von .dtb/.dtbo haben:
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
    
  3. DTs überlagern:
    1. Verwende ufdt_install_blob(), um den FDT-Header für das Haupt-DT zu erhalten:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. Rufen Sie ufdt_apply_overlay() auf DTO auf, um einen zusammengeführten DT im FDT-Format zu erhalten:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. Mit merged_fdt die Größe von dtc_totalsize() ermitteln:
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. Übergeben Sie den zusammengeführten DT, 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);
}