Otimizar DTOs

Esta página discute as otimizações que você pode fazer na implementação da sobreposição da árvore de dispositivos (DTO, na sigla em inglês), descreve as restrições contra a sobreposição do nó raiz e detalha como configurar sobreposições compactadas na imagem DTBO. Ela 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 (DT, na sigla em inglês) está localizada no chosen/bootargs nó. O carregador de inicialização precisa concatenar esse local com outras fontes da linha de comando do kernel:

/dts-v1/;

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

A DTO não pode concatenar valores da DT principal e da DT de sobreposição. Portanto, coloque a linha de comando do kernel da DT principal em chosen/bootargs e a linha de comando do kernel da DT de sobreposição em chosen/bootargs_ext. O carregador de inicialização pode concatenar esses locais e transmitir o resultado ao kernel.

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

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

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

libufdt

Embora a libfdt mais recente ofereça suporte à DTO, recomendamos usar a libufdt para implementar a DTO (fonte do AOSP em platform/system/libufdt). A libufdt cria uma estrutura de árvore real (árvore de dispositivos não achatada, ou ufdt) da árvore de dispositivos achatada (FDT, na sigla em inglês). Assim, ela pode melhorar a mesclagem de dois arquivos .dtb de O(N2) para O(N), em que N é o número de nós na árvore.

Teste de desempenho

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

O teste de desempenho para dispositivos Pixel comparou libufdt e libfdt. O número de nós de base tem um efeito semelhante, mas inclui as seguintes diferenças:

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

Exemplo com a contagem de anexação definida como X:

Figura 1. A contagem de anexação é X.

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

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

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

API DTO libufdt

A principal API para DTO em 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 é a DT principal, e overlay_fdt é o buffer que contém o conteúdo de um .dtbo arquivo. O valor de retorno é um novo buffer que contém a DT mesclada (ou null em caso de erro). A DT mesclada é formatada em FDT, que pode ser transmitida ao kernel ao iniciá-lo.

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

Restrições de nó raiz

Não é possível sobrepor um novo nó ou propriedade ao nó raiz da DT principal porque as operações de sobreposição dependem de rótulos. Como a DT principal precisa definir um rótulo e a DT de sobreposição atribui os nós a serem sobrepostos com rótulos, você não pode dar um rótulo para o nó raiz (e, portanto, não pode sobrepor o nó raiz ).

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

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

Usar sobreposições compactadas

O Android 9 adiciona suporte ao uso de sobreposições compactadas na imagem DTBO ao usar a versão 1 do cabeçalho da tabela DT. Ao usar o cabeçalho DTBO v1, os quatro bits menos significativos do campo de flags 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' 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 */
};

Atualmente, as compactações zlib e gzip são compatíveis.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

O Android 9 adiciona suporte para testar sobreposições compactadas ao teste VtsFirmwareDtboVerification para ajudar você a verificar a correção do app de sobreposição.

Implementação de DTO de amostra

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

Instruções de DTO de amostra

  1. Inclua bibliotecas. Para usar libufdt, inclua libfdt para estruturas de dados e APIs:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
  2. Carregue a DT principal e a DT de sobreposição. Carregue .dtb e .dtbo do armazenamento para a memória. As etapas exatas dependem do seu design. Nesse momento, você precisa 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 as DTs:
    1. Use ufdt_install_blob() para receber o cabeçalho FDT da 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 receber uma DT mesclada no formato FDT format:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
    3. Use merged_fdt para receber o tamanho de dtc_totalsize():
      merged_fdt_size = dtc_totalsize(merged_fdt);
    4. Transmita a DT mesclada para iniciar o kernel:
      my_kernel_entry(0, machine_type, merged_fdt);

Código de DTO de amostra

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