Optimiza las DTO

En esta página, se analizan las optimizaciones que puedes realizar en la implementación de la superposición del árbol de dispositivos (DTO), se describen las restricciones para superponer el nodo raíz y se detalla cómo configurar las superposiciones comprimidas en la imagen de DTBO. También proporciona instrucciones y código de implementación de muestra.

Línea de comandos del kernel

La línea de comandos original del kernel en el árbol de dispositivos (DT) se encuentra en el nodo chosen/bootargs. El bootloader debe concatenar esta ubicación con otras fuentes de la línea de comandos del kernel:

/dts-v1/;

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

El DTO no puede concatenar valores del DTO principal y del DTO de superposición, por lo que debes colocar la línea de comandos del kernel del DTO principal en chosen/bootargs y la línea de comandos del kernel del DTO de superposición en chosen/bootargs_ext. Luego, el bootloader puede concatenar estas ubicaciones y pasar el resultado al kernel.

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

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

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

libufdt

Si bien la versión más reciente de libfdt admite DTO, se recomienda usar libufdt para implementar DTO (fuente de AOSP en platform/system/libufdt). libufdt compila una estructura de árbol real (árbol de dispositivos sin aplanar o ufdt) a partir del árbol de dispositivos aplanado (FDT), por lo que puede mejorar la combinación de dos archivos .dtb de O(N2) a O(N), donde N es la cantidad de nodos del árbol.

Pruebas de rendimiento

En las pruebas internas de Google, el uso de libufdt en 2405 nodos .dtb y 283 nodos .dtbo de DT genera tamaños de archivo de 70,618 y 8,566 bytes después de la compilación. En comparación con una implementación de DTO transferida de FreeBSD (124 ms de tiempo de ejecución), el tiempo de ejecución de libufdt DTO es de 10 ms.

Pruebas de rendimiento para dispositivos Pixel en comparación con libufdt y libfdt. El efecto de la cantidad de nodos base es similar, pero incluye las siguientes diferencias:

  • Las operaciones de 500 superposiciones (agregar o anular) tienen una diferencia de tiempo de 6 a 8 veces.
  • Las operaciones de superposición (agregar o anular) de 1,000 tienen una diferencia de tiempo de 8 a 10 veces.

Ejemplo con el recuento de anexos establecido en X:

Figura 1: El recuento de anexos es X.

Ejemplo con la cantidad de anulaciones establecida en X:

Figura 2: El recuento de anulación es X.

libufdt se desarrolla con algunas APIs de libfdt y estructuras de datos. Cuando uses libufdt, debes incluir y vincular libfdt (sin embargo, en tu código, puedes usar la API de libfdt para operar DTB o DTBO).

API de DTO de libufdt

La API principal para DTO en libufdt es la siguiente:

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

El parámetro main_fdt_header es el DT principal y overlay_fdt es el búfer que contiene el contenido de un archivo .dtbo. El valor que se devuelve es un búfer nuevo que contiene el DT combinado (o null en caso de error). El DT combinado se formatea en FDT, que puedes pasar al kernel cuando lo inicies.

dto_malloc() crea el nuevo búfer a partir del valor de devolución, que debes implementar cuando transfieras libufdt al cargador de arranque. Para obtener implementaciones de referencia, consulta sysdeps/libufdt_sysdeps_*.c.

Restricciones del nodo raíz

No puedes superponer un nodo o una propiedad nuevos en el nodo raíz del DT principal, ya que las operaciones de superposición dependen de las etiquetas. Dado que el DT principal debe definir una etiqueta y el DT de superposición asigna las etiquetas a los nodos que se superpondrán, no puedes asignar una etiqueta al nodo raíz (y, por lo tanto, no puedes superponer el nodo raíz).

Los proveedores de SoC deben definir la capacidad de superposición del DT principal. Los ODM/OEM solo pueden agregar o anular nodos con etiquetas definidas por el proveedor de SoC. Como solución alternativa, puedes definir un nodo odm en el nodo raíz del DT base, lo que permite que todos los nodos ODM en el DT de superposición agreguen nodos nuevos. Como alternativa, puedes colocar todos los nodos relacionados con el SoC en el DT base en un nodo soc debajo del nodo raíz, como se describe a continuación:

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

Usa superposiciones comprimidas

Android 9 agrega compatibilidad para usar superposiciones comprimidas en la imagen de DTBO cuando se usa la versión 1 del encabezado de la tabla de DT. Cuando se usa el encabezado DTBO v1, los cuatro bits menos significativos del campo de marcas en dt_table_entry indican el formato de compresión de la 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 */
};

Actualmente, se admiten las compresiones zlib y gzip.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 agrega compatibilidad para probar superposiciones comprimidas en la prueba VtsFirmwareDtboVerification para ayudarte a verificar la exactitud de la app de superposición.

Ejemplo de implementación de DTO

Las siguientes instrucciones te guiarán por una implementación de muestra del DTO con libufdt (código de muestra a continuación).

Instrucciones de DTO de muestra

  1. Incluye bibliotecas. Para usar libufdt, incluye libfdt para las estructuras de datos y las APIs:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
  2. Carga la DT principal y la DT de superposición. Carga .dtb y .dtbo desde el almacenamiento a la memoria (los pasos exactos dependen de tu diseño). En este punto, deberías tener el búfer y el tamaño 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. Superpón las DT:
    1. Usa ufdt_install_blob() para obtener el encabezado del FDT para el DT principal:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
    2. Llama a ufdt_apply_overlay() para obtener un DT combinado en formato de FDT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
    3. Usa merged_fdt para obtener el tamaño de dtc_totalsize():
      merged_fdt_size = dtc_totalsize(merged_fdt);
    4. Pasa el DT combinado para iniciar el kernel:
      my_kernel_entry(0, machine_type, merged_fdt);

Código de DTO de muestra

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