Ottimizza i DTO

Questa pagina illustra le ottimizzazioni che puoi apportare all'implementazione dell'overlay ad albero dei dispositivi (DTO), descrive le limitazioni contro la sovrapposizione del nodo principale e descrive nel dettaglio come configurare gli overlay compressi nell'immagine DTBO. Fornisce inoltre istruzioni e codice di implementazione di esempio.

Riga di comando del kernel

La riga di comando del kernel originale nell'albero del dispositivo (DT) si trova nel chosen/bootargs nodo. Il bootloader deve concatenare questa posizione con altre origini della riga di comando del kernel:

/dts-v1/;

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

DTO non può concatenare i valori del DT principale e del DT in overlay, quindi devi inserire la riga di comando del kernel del DT principale in chosen/bootargs e la riga di comando del kernel del DT in overlay in chosen/bootargs_ext. Il bootloader può quindi concatenare queste posizioni e passare il risultato al kernel.

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

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

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

Libufdt

Anche se la versione più recente di libfdt supporta i DTO, è consigliabile utilizzare libufdt per implementarli (il codice sorgente AOSP è disponibile all'indirizzo platform/system/libufdt). libufdt crea una struttura ad albero reale (albero dei dispositivi non appiattito o ufdt) dall'albero dei dispositivi appiattito (FDT), in modo da migliorare l'unione di due file .dtb da O(N2) a O(N), dove N è il numero di nodi nell'albero.

Test delle prestazioni

Nei test interni di Google, l'utilizzo di libufdt su 2405 .dtb e 283 nodi DT .dtbo ha generato dimensioni dei file di 70.618 e 8.566 byte dopo la compilazione. Rispetto a un'implementazione DTO portata da FreeBSD (runtime di 124 ms), il runtime libufdt DTO è di 10 ms.

Test delle prestazioni per i dispositivi Pixel rispetto a libufdt e libfdt. L'effetto del numero di nodi di base è simile, ma include le seguenti differenze:

  • 500 operazioni di overlay (aggiunta o override) hanno una differenza di tempo da 6 a 8 volte
  • 1000 operazioni di overlay (aggiunta o sostituzione) hanno un tempo tra 8 e 10 volte superiore

Esempio con il conteggio dell'accodamento impostato su X:

Figura 1. Il conteggio aggiunto è X.

Esempio con conteggio per l'override impostato su X:

Figura 2. Il conteggio dell'override è X.

libufdt è sviluppato con alcune API libfdt e strutture di dati. Quando utilizzi libufdt, devi includere e collegare libfdt (tuttavia, nel codice puoi utilizzare l'API libfdt per gestire i dati di telemetria dei dati o i dati di telemetria dei dati di acquisto).

API DTO libufdt

L'API principale per DTO in libufdt è la seguente:

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

Il parametro main_fdt_header è il DT principale e overlay_fdt è il buffer contenente i contenuti di un file .dtbo. Il valore restituito è un nuovo buffer contenente il DT unito (o null in caso di errore). Il DT unito è formattato in FDT, che puoi passare al kernel all'avvio.

Il nuovo buffer dal valore restituito viene creato da dto_malloc(), che devi implementare durante il porting di libufdt nel bootloader. Per le implementazioni di riferimento, consulta sysdeps/libufdt_sysdeps_*.c.

Limitazioni del nodo principale

Non puoi sovrapporre un nuovo nodo o una nuova proprietà al nodo principale del DT principale perché le operazioni di sovrapposizione si basano sulle etichette. Poiché il DT principale deve definire un'etichetta e il DT in overlay assegna i nodi da sovrapporre con le etichette, non puoi assegnare un'etichetta al nodo principale (e quindi non puoi sovrapporre il nodo principale).

I fornitori di SoC devono definire la capacità di sovrapposizione del DT principale; gli ODM/OEM possono solo aggiungere o sostituire i nodi con le etichette definite dal fornitore di SoC. Come soluzione alternativa, puoi definire un nodo odm sotto il nodo principale nel DT di base, consentendo a tutti i nodi ODM nel DT overlay di aggiungere nuovi nodi. In alternativa, potresti mettere tutti i nodi correlati a SoC nel DT di base in un nodo soc sotto il nodo radice, come descritto di seguito:

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

Utilizzare overlay compressi

Android 9 aggiunge il supporto per l'utilizzo di overlay compressi nell'immagine DTBO quando si utilizza la versione 1 dell'intestazione della tabella DT. Quando utilizzi l'intestazione DTBO v1, i quattro bit meno significativi del campo flags in dt_table_entry indicano il formato di compressione della voce 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 */
};

Al momento sono supportate le compressioni zlib e gzip.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 aggiunge il supporto per il test delle sovrapposizioni compresse al test VtsFirmwareDtboVerification per aiutarti a verificare la correttezza dell'app in overlay.

Implementazione di esempio di DTO

Le istruzioni riportate di seguito illustrano un'implementazione di esempio di DTO con libufdt (codice di esempio di seguito).

Istruzioni di esempio per i DTO

  1. Includi le librerie. Per utilizzare libufdt, includi libfdt per le strutture di dati e le API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Carica il DT principale e il DT in overlay. Carica .dtb e .dtbo dallo spazio di archiviazione nella memoria (i passaggi esatti dipendono dal design). A questo punto, dovresti avere il buffer e le dimensioni di .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. Sovrapponi i DT:
    1. Utilizza ufdt_install_blob() per ottenere l'intestazione FDT per il DT principale:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. Chiama ufdt_apply_overlay() in DTO per ottenere un DT unito in formato FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. Utilizza merged_fdt per ottenere le dimensioni di dtc_totalsize():
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. Passa il DT unito per avviare il kernel:
      my_kernel_entry(0, machine_type, merged_fdt);
      

Codice DTO di esempio

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