Optymalizacja DTO

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 i szczegółowo opisano sposób konfigurowania skompresowanych nakładek w obrazie DTBO. Zawiera także przykładowe instrukcje i kod implementacji.

Wiersz poleceń jądra

Oryginalna linia poleceń jądra w drzewie urządzeń znajduje się w chosen/bootargs . Program ładujący 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 DT, więc musisz umieścić linię poleceń jądra głównego DT w chosen/bootargs i linię poleceń jądra nakładki DT w chosen/bootargs_ext . Program ładujący może następnie połączyć te lokalizacje i przekazać wynik do jądra.

główne.dts nakładka.dts
/dts-v1/;

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

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

libufdt

Chociaż najnowsza libfdt obsługuje DTO, czy zaleca się używanie libufdt do implementacji DTO (źródło AOSP w platform/system/libufdt ). libufdt tworzy prawdziwą strukturę drzewa (niespłaszczone drzewo urządzeń, ufdt ) ze spłaszczonego drzewa urządzeń (FDT), dzięki czemu może usprawnić łączenie dwóch plików .dtb od 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 biblioteki libufdt w węzłach DT 2405 .dtb i 283 .dtbo DT po kompilacji dało rozmiary plików 70 618 i 8566 bajtów. W porównaniu z implementacją DTO przeniesioną z FreeBSD (czas działania 124 ms), czas wykonania libufdt DTO wynosi 10 ms.

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

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

Przykład z dodaną liczbą ustawioną na X:

Rysunek 1. Liczba dołączeń to X

Przykład z nadrzędną liczbą ustawioną na X:

Rysunek 2. Licznik zastępujący to X

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

API DTO libufdt

Główne API DTO w libufdt jest następujące:

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 jest głównym ID, a overlay_fdt jest buforem zawierającym zawartość pliku .dtbo . Wartość zwracana to nowy bufor zawierający scalony identyfikator (lub null w przypadku błędu). Połączony ID jest sformatowany w formacie FDT, który można przekazać do jądra podczas uruchamiania jądra.

Nowy bufor ze zwracanej wartości jest tworzony przez dto_malloc() , którą powinieneś 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 nałożony ID przypisuje węzły, na które mają zostać nałożone etykiety, nie można nadać etykiety węzłowi głównemu (i dlatego nie można nałożyć węzła głównego).

Dostawcy SoC muszą zdefiniować możliwość nakładania się głównego ID; Producenci ODM/OEM mogą dołączać lub zastępować węzły wyłącznie z etykietami zdefiniowanymi przez dostawcę SoC. W ramach obejścia 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ładkowym ID dodawanie 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:

główne.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

W systemie Android 9 dodano obsługę skompresowanych nakładek w obrazie DTBO podczas korzystania z wersji 1 nagłówka tabeli drzewa urządzeń. Gdy używany jest nagłówek 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
};

W systemie Android 9 dodano obsługę testowania skompresowanych nakładek do testu VtsFirmwareDtboVerification , który pomaga zweryfikować poprawność aplikacji nakładek.

Przykładowa implementacja 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 , dołącz libfdt dla struktur danych i interfejsów API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Załaduj główny ID i nałożony ID. Załaduj .dtb i .dtbo z pamięci 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 funkcję ufdt_apply_overlay() do DTO, aby uzyskać połączony identyfikator 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łączony identyfikator, 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);
}