Partições de inicialização do fornecedor

O Android 11 introduziu o conceito de imagem genérica do kernel (GKI, na sigla em inglês). Para permitir a inicialização de um dispositivo arbitrário com o GKI, os dispositivos Android 11 podem usar a versão 3 do cabeçalho da imagem de inicialização. Na versão 3, todas as informações específicas do fornecedor são desconsideradas da partição boot e realocadas para uma nova partição vendor_boot. Um dispositivo ARM64 iniciado com o Android 11 no kernel do Linux 5.4 precisa oferecer suporte à partição vendor_boot e ao formato de partição boot atualizado para passar nos testes com a GKI.

Os dispositivos com o Android 12 podem usar o cabeçalho da imagem de inicialização versão 4, que oferece suporte à inclusão de vários ramdisks de fornecedores na partição vendor_boot. Vários fragmentos do ramdisk do fornecedor são concatenados um após o outro na seção do ramdisk do fornecedor. Uma tabela de ramdisk do fornecedor é usada para descrever o layout da seção de ramdisk do fornecedor e os metadados de cada fragmento de ramdisk do fornecedor.

Estrutura da partição

A partição de inicialização do fornecedor é A/B com A/B virtual e protegida pela inicialização verificada do Android.

Versão 3

A partição consiste em um cabeçalho, o ramdisk do fornecedor e o blob da árvore de dispositivos (DTB, na sigla em inglês).

Seção Número de páginas
Cabeçalho de inicialização do fornecedor (n páginas) n = (2112 + page_size - 1) / page_size
Ramdisk do fornecedor (páginas o) o = (vendor_ramdisk_size + page_size - 1) / page_size
DTB (páginas p) p = (dtb_size + page_size - 1) / page_size

Versão 4

A partição consiste em um cabeçalho, a seção do ramdisk do fornecedor (que consiste em todos os fragmentos do ramdisk do fornecedor, concatenados), o blob da árvore de dispositivos (DTB, na sigla em inglês) e a tabela do ramdisk do fornecedor.

Seção Número de páginas
Cabeçalho de inicialização do fornecedor (n páginas) n = (2128 + page_size - 1) / page_size
Fragmentos do ramdisk do fornecedor (páginas) o = (vendor_ramdisk_size + page_size - 1) / page_size
DTB (páginas p) p = (dtb_size + page_size - 1) / page_size
Tabela do ramdisk do fornecedor (páginas q) q = (vendor_ramdisk_table_size + page_size - 1) / page_size
Bootconfig (páginas r) r = (bootconfig_size + page_size - 1) / page_size

Cabeçalho de inicialização do fornecedor

O conteúdo do cabeçalho da partição de inicialização do fornecedor consiste principalmente em dados que foram realocados para lá do cabeçalho da imagem de inicialização. Ele também contém informações sobre o ramdisk do fornecedor.

Versão 3

struct vendor_boot_img_hdr_v3
{
#define VENDOR_BOOT_MAGIC_SIZE 8
    uint8_t magic[VENDOR_BOOT_MAGIC_SIZE];
    uint32_t header_version;
    uint32_t page_size;           /* flash page size we assume */

    uint32_t kernel_addr;         /* physical load addr */
    uint32_t ramdisk_addr;        /* physical load addr */

    uint32_t vendor_ramdisk_size; /* size in bytes */

#define VENDOR_BOOT_ARGS_SIZE 2048
    uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE];

    uint32_t tags_addr;           /* physical addr for kernel tags */

#define VENDOR_BOOT_NAME_SIZE 16
    uint8_t name[VENDOR_BOOT_NAME_SIZE]; /* asciiz product name */
    uint32_t header_size;         /* size of vendor boot image header in
                                   * bytes */
    uint32_t dtb_size;            /* size of dtb image */
    uint64_t dtb_addr;            /* physical load address */

};

Versão 4

struct vendor_boot_img_hdr_v4
{
#define VENDOR_BOOT_MAGIC_SIZE 8
    uint8_t magic[VENDOR_BOOT_MAGIC_SIZE];
    uint32_t header_version;
    uint32_t page_size;           /* flash page size we assume */

    uint32_t kernel_addr;         /* physical load addr */
    uint32_t ramdisk_addr;        /* physical load addr */

    uint32_t vendor_ramdisk_size; /* size in bytes */

#define VENDOR_BOOT_ARGS_SIZE 2048
    uint8_t cmdline[VENDOR_BOOT_ARGS_SIZE];

    uint32_t tags_addr;           /* physical addr for kernel tags */

#define VENDOR_BOOT_NAME_SIZE 16
    uint8_t name[VENDOR_BOOT_NAME_SIZE]; /* asciiz product name */
    uint32_t header_size;         /* size of vendor boot image header in
                                   * bytes */
    uint32_t dtb_size;            /* size of dtb image */
    uint64_t dtb_addr;            /* physical load address */

    uint32_t vendor_ramdisk_table_size; /* size in bytes for the vendor ramdisk table */
    uint32_t vendor_ramdisk_table_entry_num; /* number of entries in the vendor ramdisk table */
    uint32_t vendor_ramdisk_table_entry_size; /* size in bytes for a vendor ramdisk table entry */
    uint32_t bootconfig_size; /* size in bytes for the bootconfig section */
};

#define VENDOR_RAMDISK_TYPE_NONE 0
#define VENDOR_RAMDISK_TYPE_PLATFORM 1
#define VENDOR_RAMDISK_TYPE_RECOVERY 2
#define VENDOR_RAMDISK_TYPE_DLKM 3

struct vendor_ramdisk_table_entry_v4
{
    uint32_t ramdisk_size; /* size in bytes for the ramdisk image */
    uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */
    uint32_t ramdisk_type; /* type of the ramdisk */
#define VENDOR_RAMDISK_NAME_SIZE 32
    uint8_t ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */

#define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16
    // Hardware identifiers describing the board, soc or platform which this
    // ramdisk is intended to be loaded on.
    uint32_t board_id[VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE];
};
  • vendor_ramdisk_size é o tamanho total de todos os fragmentos do ramdisk do fornecedor.
  • ramdisk_type indica o tipo de ramdisk. Os valores possíveis são:
    • VENDOR_RAMDISK_TYPE_NONE indica que o valor não foi especificado.
    • As ramdisks VENDOR_RAMDISK_TYPE_PLATFORM contêm bits específicos da plataforma. O carregador de inicialização precisa sempre carregá-los na memória.
    • VENDOR_RAMDISK_TYPE_RECOVERY ramdisks contêm recursos de recuperação. O carregador de inicialização precisa carregá-los na memória ao inicializar a recuperação.
    • As ramdisks VENDOR_RAMDISK_TYPE_DLKM contêm módulos de kernel carregáveis dinâmicos.
  • ramdisk_name é um nome exclusivo do ramdisk.
  • board_id é um vetor de identificadores de hardware definidos pelo fornecedor.

Suporte a carregador de inicialização

Como a partição de inicialização do fornecedor contém informações (como tamanho da página flash, kernel, endereços de carregamento de ramdisk e o próprio DTB) que existiam anteriormente na partição de inicialização, o carregador de inicialização precisa acessar as partições de inicialização e de inicialização do fornecedor para ter dados suficientes para concluir a inicialização.

O carregador de inicialização precisa carregar o ramdisk genérico na memória imediatamente após o ramdisk do fornecedor. Os formatos CPIO, Gzip e lz4 oferecem suporte a esse tipo de concatenação. Não alinhe a página à imagem genérica do ramdisk nem introduza outro espaço entre ela e o final do ramdisk do fornecedor na memória. Depois que o kernel é descompactado, ele extrai o arquivo concatenado em um initramfs, o que resulta em uma estrutura de arquivo que é um ramdisk genérico sobreposto à estrutura do arquivo do ramdisk do fornecedor.

Como o ramdisk genérico e o ramdisk do fornecedor são concatenados, eles precisam estar no mesmo formato. A imagem de inicialização do GKI usa um ramdisk genérico compactado com lz4. Portanto, um dispositivo compatível com o GKI precisa usar um ramdisk de fornecedor compactado com lz4. A configuração para isso é mostrada abaixo.

Os requisitos do carregador de inicialização para oferecer suporte ao bootconfig são explicados em Implementar o bootconfig.

Vários ramdisks do fornecedor (versão 4)

Com a versão 4 do cabeçalho da imagem de inicialização, o carregador de inicialização pode selecionar um subconjunto ou todos os ramdisks do fornecedor para carregar como initramfs durante a inicialização. A tabela de ramdisk do fornecedor contém os metadados de cada ramdisk e pode ajudar o bootloader a decidir quais ramdisks carregar. O carregador de inicialização pode decidir a ordem de carregamento dos ramdisks de fornecedores selecionados, desde que o ramdisk genérico seja carregado por último.

Por exemplo, o carregador de inicialização pode omitir o carregamento de ramdisks do fornecedor do tipo VENDOR_RAMDISK_TYPE_RECOVERY durante a inicialização normal para economizar recursos. Assim, apenas ramdisks do fornecedor do tipo VENDOR_RAMDISK_TYPE_PLATFORM e VENDOR_RAMDISK_TYPE_DLKM são carregadas na memória. Por outro lado, as ramdisks do fornecedor do tipo VENDOR_RAMDISK_TYPE_PLATFORM, VENDOR_RAMDISK_TYPE_RECOVERY e VENDOR_RAMDISK_TYPE_DLKM são carregadas na memória ao inicializar no modo de recuperação.

Como alternativa, o carregador de inicialização pode ignorar a tabela do ramdisk do fornecedor e carregar toda a seção do ramdisk do fornecedor. Isso tem o mesmo efeito que o carregamento de todos os fragmentos do ramdisk do fornecedor na partição vendor_boot.

Suporte ao desenvolvimento

Para implementar o suporte de inicialização do fornecedor para um dispositivo:

  • Defina BOARD_BOOT_HEADER_VERSION como 3 ou mais.

  • Defina BOARD_RAMDISK_USE_LZ4 como true se o dispositivo for compatível com GKI ou se ele usar um ramdisk genérico compactado com lz4.

  • Defina BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE com um tamanho adequado para seu dispositivo, considerando os módulos do kernel que precisam ir para o ramdisk do fornecedor.

  • Atualize AB_OTA_PARTITIONS para incluir vendor_boot e todas as listas de partições OTA específicas do fornecedor no dispositivo.

  • Copie o fstab do dispositivo para /first_stage_ramdisk na partição vendor_boot, não na boot. Por exemplo: $(LOCAL_PATH)/fstab.hardware:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.$(PRODUCT_PLATFORM).

Para incluir vários ramdisks do fornecedor em vendor_boot:

  • Defina BOARD_BOOT_HEADER_VERSION como 4.
  • Defina BOARD_VENDOR_RAMDISK_FRAGMENTS como uma lista de nomes de fragmentos de ramdisk de fornecedor lógico para serem incluídos em vendor_boot.

  • Para adicionar um ramdisk do fornecedor pré-criado, defina BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).PREBUILT para o caminho pré-criado.

  • Para adicionar um ramdisk de fornecedor do DLKM, defina BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).KERNEL_MODULE_DIRS na lista de diretórios de módulos do kernel a serem incluídos.

  • Defina BOARD_VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk).MKBOOTIMG_ARGS como argumentos mkbootimg. Estes são os argumentos --board_id[0-15] e --ramdisk_type para o fragmento de ramdisk do fornecedor. Para o ramdisk do fornecedor do DLKM, o --ramdisk_type padrão será DLKM se não for especificado de outra forma.

Para criar recursos de recuperação como um ramdisk independente recovery em vendor_boot:

  • Defina BOARD_BOOT_HEADER_VERSION como 4.
  • Defina BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT como true.
  • Defina BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT como true.
  • Isso adiciona um fragmento do ramdisk do fornecedor em que ramdisk_name é recovery e ramdisk_type é VENDOR_RAMDISK_TYPE_RECOVERY. O ramdisk contém todos os arquivos de recuperação, que são arquivos instalados em $(TARGET_RECOVERY_ROOT_OUT).

Argumentos do mkbootimg

Argumento Descrição
--ramdisk_type O tipo do ramdisk pode ser NONE, PLATFORM, RECOVERY ou DLKM.
--board_id[0-15] Especifique o vetor board_id, que é definido como 0 por padrão.

Confira a seguir um exemplo de configuração:

BOARD_KERNEL_MODULE_DIRS := foo bar baz
BOARD_BOOT_HEADER_VERSION := 4
BOARD_VENDOR_RAMDISK_FRAGMENTS := dlkm_foobar
BOARD_VENDOR_RAMDISK_FRAGMENT.dlkm_foobar.KERNEL_MODULE_DIRS := foo bar
BOARD_VENDOR_RAMDISK_FRAGMENT.dlkm_foobar.MKBOOTIMG_ARGS := --board_id0 0xF00BA5 --board_id1 0xC0FFEE

O vendor_boot resultante conteria dois fragmentos de ramdisk do fornecedor. O primeiro é o ramdisk "padrão", que contém o diretório baz do DLKM e o restante dos arquivos em $(TARGET_VENDOR_RAMDISK_OUT). O segundo é o ramdisk dlkm_foobar, que contém os diretórios foo e bar do DLKM e o --ramdisk_type padrão é DLKM.