Optymalizacja DTO

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Na tej stronie omówiono optymalizacje, które można wprowadzić w implementacji DTO, opisano ograniczenia dotyczące nakładania węzła głównego oraz szczegółowo opisano sposób konfigurowania skompresowanych nakładek w obrazie DTBO. Zawiera również przykładowe instrukcje i kod implementacji.

Wiersz poleceń jądra

Oryginalny wiersz poleceń jądra w drzewie urządzeń znajduje się w chosen/bootargs . Bootloader musi połączyć tę lokalizację z innymi źródłami wiersza poleceń jądra:

/dts-v1/;

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

DTO nie może łączyć wartości z głównego ID i nakładki ID, więc musisz umieścić wiersz poleceń jądra głównego ID w chosen/bootargs , a wiersz poleceń jądra nakładki ID w chosen/bootargs_ext . Bootloader może następnie połączyć te lokalizacje i przekazać wynik do jądra.

main.dts nakładka.dts
/dts-v1/;

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

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

libufdt

Chociaż najnowsza libfdt obsługuje DTO, zaleca się używanie libufdt do implementacji DTO (źródło AOSP na platform/system/libufdt ). libufdt buduje prawdziwą strukturę drzewa (niespłaszczone drzewo urządzeń lub ufdt ) ze spłaszczonego drzewa urządzeń (FDT), dzięki czemu może poprawić scalanie dwóch plików .dtb z O(N 2 ) do O(N), gdzie N to liczba węzłów w drzewie.

Test wydajności

W wewnętrznych testach Google użycie libufdt w węzłach DT 2405 .dtb i 283 .dtbo daje pliki o rozmiarach 70 618 i 8566 bajtów po kompilacji. W porównaniu z implementacją DTO przeniesioną z FreeBSD (124 ms runtime), czas wykonania libufdt DTO wynosi 10 ms.

Testy wydajności dla urządzeń Pixel porównały libufdt i libfdt . Efekt liczby węzłów bazowych jest podobny, ale zawiera następujące różnice:

  • 500 operacji nakładania (dodawania lub zastępowania) ma od 6x do 8x różnicę czasu
  • 1000 operacji nakładania (dodawania lub zastępowania) ma od 8x do 10x różnicę czasu

Przykład z liczbą dołączania ustawioną na X:

Rysunek 1. Liczba dołączeń to X

Przykład z zastępowaniem licznika ustawionym na X:

Rysunek 2. Zastępowanie liczby to X

libufdt jest rozwijany z niektórymi interfejsami API libfdt i strukturami danych. Korzystając z libufdt , musisz dołączyć i połączyć libfdt (jednak w swoim kodzie możesz użyć API libfdt do obsługi DTB lub DTBO).

libufdt DTO API

Główne API do DTO w libufdt wygląda następująco:

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

Parametr main_fdt_header to główny ID, a overlay_fdt to bufor zawierający zawartość pliku .dtbo . Wartość zwracana to nowy bufor zawierający scalony ID (lub null w przypadku błędu). Połączone DT jest sformatowane w FDT, które można przekazać do jądra podczas uruchamiania jądra.

Nowy bufor z wartości zwracanej jest tworzony przez dto_malloc() , które należy zaimplementować podczas przenoszenia libufdt do bootloadera. Implementacje referencyjne można znaleźć w sysdeps/libufdt_sysdeps_*.c .

Ograniczenia węzła głównego

Nie można nałożyć nowego węzła lub właściwości na węzeł główny głównego ID, ponieważ operacje nakładania opierają się na etykietach. Ponieważ główny ID musi definiować etykietę, a nakładkowy ID przypisuje węzły, które mają być nałożone na etykiety, nie można nadać etykiety węzłowi głównemu (a zatem nie można nakładać węzła głównego).

Dostawcy SoC muszą zdefiniować możliwość nakładania się głównego DT; Producenci ODM/OEM mogą jedynie dołączać lub zastępować węzły z etykietami zdefiniowanymi przez dostawcę SoC. Jako obejście można zdefiniować węzeł odm pod węzłem głównym w podstawowym ID, umożliwiając wszystkim węzłom ODM w nakładce ID dodanie nowych węzłów. Alternatywnie możesz umieścić wszystkie węzły związane z SoC w podstawowym ID w węźle soc w węźle głównym, jak opisano poniżej:

main.dts nakładka.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 {
        ...
    };
    ...
};

Używanie skompresowanych nakładek

Android 9 dodaje obsługę używania skompresowanych nakładek w obrazie DTBO podczas korzystania z wersji 1 nagłówka tabeli drzewa urządzeń. Podczas używania nagłówka DTBO v1, cztery najmniej znaczące bity pola flag w dt_table_entry wskazują format kompresji wpisu 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' will be 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 */
};

Obecnie obsługiwane są kompresje zlib i gzip .

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 dodaje obsługę testowania skompresowanych nakładek do testu VtsFirmwareDtboVerification , aby pomóc Ci zweryfikować poprawność aplikacji nakładki.

Przykładowe wdrożenie DTO

Poniższe instrukcje przeprowadzą Cię przez przykładową implementację DTO z libufdt (przykładowy kod poniżej).

Przykładowe instrukcje DTO

  1. Uwzględnij biblioteki. Aby użyć libufdt , libfdt dla struktur danych i API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Załaduj główny ID i nałóż ID. Załaduj .dtb i .dtbo z magazynu do pamięci (dokładne kroki zależą od projektu). W tym momencie powinieneś mieć bufor i rozmiar .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. Nałóż ID:
    1. Użyj ufdt_install_blob() , aby uzyskać nagłówek FDT dla głównego ID:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. Wywołaj ufdt_apply_overlay() do DTO, aby uzyskać scalony ID w formacie FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. Użyj merged_fdt , aby uzyskać rozmiar dtc_totalsize() :
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. Przekaż połączone ID, aby uruchomić jądro:
      my_kernel_entry(0, machine_type, merged_fdt);
      

Przykładowy kod 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);
}