Otimizando DTOs

Esta página discute as otimizações que você pode fazer em sua implementação de DTO, descreve as restrições contra a sobreposição do nó raiz e detalha como configurar sobreposições compactadas na imagem DTBO. Ele também fornece instruções e código de implementação de amostra.

Linha de comando do kernel

A linha de comando original do kernel na árvore de dispositivos está localizada no nó chosen/bootargs . O bootloader deve concatenar este local com outras fontes de linha de comando do kernel:

/dts-v1/;

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

O DTO não pode concatenar valores do DT principal e do DT de sobreposição, portanto, você deve colocar a linha de comando do kernel do DT principal em chosen/bootargs e a linha de comando do kernel do DT de sobreposição em chosen/bootargs_ext . O Bootloader pode então concatenar esses locais e passar o resultado para o kernel.

main.dts sobreposição.dts
/dts-v1/;

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

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

libufdt

Embora o libfdt mais recente suporte DTO, é recomendável usar libufdt para implementar DTO (fonte AOSP em platform/system/libufdt ). libufdt constrói uma estrutura de árvore real (un-flattened device tree, ou ufdt ) a partir da flattened device tree (FDT), para que possa melhorar a fusão de dois arquivos .dtb de O(N 2 ) para O(N), onde N é o número de nós na árvore.

Teste de performance

Nos testes internos do Google, usar libufdt em nós 2405 .dtb e 283 .dtbo DT resulta em tamanhos de arquivo de 70.618 e 8.566 bytes após a compilação. Comparado com uma implementação DTO portada do FreeBSD (tempo de execução de 124 ms), o tempo de execução do libufdt DTO é de 10 ms.

Testes de desempenho para dispositivos Pixel compararam libufdt e libfdt . O efeito do número de nós base é semelhante, mas inclui as seguintes diferenças:

  • 500 operações de sobreposição (anexar ou substituir) têm uma diferença de tempo de 6x a 8x
  • 1000 operações de sobreposição (anexar ou substituir) têm uma diferença de tempo de 8x a 10x

Exemplo com contagem anexada definida como X:

Figura 1. A contagem anexa é X

Exemplo com contagem de substituição definida como X:

Figura 2. A contagem de substituição é X

libufdt é desenvolvido com algumas APIs libfdt e estruturas de dados. Ao usar libufdt , você deve incluir e vincular libfdt (no entanto, em seu código você pode usar a API libfdt para operar DTB ou DTBO).

API libufdt DTO

A principal API para DTO no libufdt é a seguinte:

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

O parâmetro main_fdt_header é o DT principal e overlay_fdt é o buffer que contém o conteúdo de um arquivo .dtbo . O valor de retorno é um novo buffer contendo o DT mesclado (ou null em caso de erro). O DT mesclado é formatado em FDT, que você pode passar para o kernel ao iniciar o kernel.

O novo buffer do valor de retorno é criado por dto_malloc() , que você deve implementar ao portar libufdt para o bootloader. Para implementações de referência, consulte sysdeps/libufdt_sysdeps_*.c .

Restrições do nó raiz

Você não pode sobrepor um novo nó ou propriedade no nó raiz do DT principal porque as operações de sobreposição dependem de rótulos. Como o DT principal deve definir um rótulo e o DT de sobreposição atribui os nós a serem sobrepostos com rótulos, você não pode fornecer um rótulo para o nó raiz (e, portanto, não pode sobrepor o nó raiz).

Os fornecedores de SoC devem definir a capacidade de sobreposição do DT principal; ODM/OEMs só podem anexar ou substituir nós com rótulos definidos pelo fornecedor do SoC. Como solução alternativa, você pode definir um nó odm sob o nó raiz no DT base, permitindo que todos os nós ODM no DT de sobreposição adicionem novos nós. Alternativamente, você pode colocar todos os nós relacionados ao SoC no DT base em um nó soc sob o nó raiz, conforme descrito abaixo:

main.dts sobreposição.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 {
        ...
    };
    ...
};

Usando sobreposições compactadas

O Android 9 adiciona suporte para usar sobreposições compactadas na imagem DTBO ao usar a versão 1 do cabeçalho da tabela de árvore do dispositivo. Ao usar o cabeçalho DTBO v1, os quatro bits menos significativos do campo de sinalizadores em dt_table_entry indicam o formato de compactação da entrada 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 */
};

Atualmente, as compactações zlib e gzip são suportadas.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

O Android 9 adiciona suporte para testar sobreposições compactadas ao teste VtsFirmwareDtboVerification para ajudá-lo a verificar a exatidão do aplicativo de sobreposição.

Exemplo de implementação de DTO

As instruções a seguir orientam você por uma implementação de exemplo de DTO com libufdt (código de exemplo abaixo).

Instruções de exemplo de DTO

  1. Incluir bibliotecas. Para usar libufdt , inclua libfdt para estruturas de dados e APIs:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. Carregue o DT principal e o DT de sobreposição. Carregue .dtb e .dtbo do armazenamento na memória (as etapas exatas dependem do seu design). Neste ponto, você deve ter o buffer e o tamanho de .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. Sobreponha os DTs:
    1. Use ufdt_install_blob() para obter o cabeçalho FDT para o DT principal:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. Chame ufdt_apply_overlay() para DTO para obter um DT mesclado no formato FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. Use merged_fdt para obter o tamanho de dtc_totalsize() :
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. Passe o DT mesclado para iniciar o kernel:
      my_kernel_entry(0, machine_type, merged_fdt);
      

Exemplo de código 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);
}