Implementando atualizações A/B

OEMs e fornecedores de SoC que desejam implementar atualizações de sistema A/B devem garantir que seu carregador de inicialização implemente o HAL boot_control e passe os parâmetros corretos para o kernel.

Implementando o HAL de controle de inicialização

Os carregadores de inicialização com capacidade A/B devem implementar o HAL boot_control em hardware/libhardware/include/hardware/boot_control.h . Você pode testar implementações usando o utilitário system/extras/bootctl e system/extras/tests/bootloader/ .

Você também deve implementar a máquina de estado mostrada abaixo:

Figura 1. Máquina de estado do carregador de inicialização

Configurando o núcleo

Para implementar atualizações do sistema A/B:

  1. Escolha a seguinte série de patches do kernel (se necessário):
  2. Certifique-se de que os argumentos da linha de comando do kernel contenham os seguintes argumentos extras:
    skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
    ... onde o valor <public-key-id> é o ID da chave pública usada para verificar a assinatura da tabela verity (para obter detalhes, consulte dm-verity ) .
  3. Adicione o certificado .X509 que contém a chave pública ao chaveiro do sistema:
    1. Copie o certificado .X509 formatado no formato .der para a raiz do diretório do kernel . Se o certificado .X509 estiver formatado como um arquivo .pem , use o seguinte comando openssl para converter do formato .pem para .der :
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Crie o zImage para incluir o certificado como parte do chaveiro do sistema. Para verificar, verifique a entrada procfs (requer que KEYS_CONFIG_DEBUG_PROC_KEYS esteja habilitado):
      angler:/# cat /proc/keys
      
      1c8a217e I------     1 perm 1f010000     0     0 asymmetri
      Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
      2d454e3e I------     1 perm 1f030000     0     0 keyring
      .system_keyring: 1/4
      A inclusão bem-sucedida do certificado .X509 indica a presença da chave pública no chaveiro do sistema (o realce indica o ID da chave pública).
    3. Substitua o espaço por # e passe-o como <public-key-id> na linha de comando do kernel. Por exemplo, passe Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f no lugar de <public-key-id> .

Como configurar variáveis ​​de compilação

Os carregadores de inicialização com capacidade A/B devem atender aos seguintes critérios de variável de compilação:

Deve definir para o destino A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
    boot \
    system \
    vendor
    e outras partições atualizadas por meio de update_engine (rádio, carregador de inicialização, etc.)
  • PRODUCT_PACKAGES += \
    update_engine \
    update_verifier
Para obter um exemplo, consulte /device/google/marlin/+/android-7.1.0_r1/device-common.mk . Opcionalmente, você pode conduzir a etapa dex2oat pós-instalação (mas pré-reinicialização) descrita em Compilando .
Fortemente recomendado para alvo A/B
  • Defina TARGET_NO_RECOVERY := true
  • Defina BOARD_USES_RECOVERY_AS_BOOT := true
  • Não defina BOARD_RECOVERYIMAGE_PARTITION_SIZE
Não é possível definir para o destino A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Opcional para compilações de depuração PRODUCT_PACKAGES_DEBUG += update_engine_client

Configurando partições (slots)

Os dispositivos A/B não precisam de uma partição de recuperação ou partição de cache porque o Android não usa mais essas partições. A partição de dados agora é usada para o pacote OTA baixado e o código da imagem de recuperação está na partição de inicialização. Todas as partições que são A/B-ed devem ser nomeadas da seguinte forma (os slots são sempre nomeados a , b , etc.): boot_a , boot_b , system_a , system_b , vendor_a , vendor_b .

Cache

Para atualizações não A/B, a partição de cache foi usada para armazenar pacotes OTA baixados e para armazenar blocos temporariamente durante a aplicação de atualizações. Nunca houve uma boa maneira de dimensionar a partição de cache: quão grande ela precisava depender de quais atualizações você queria aplicar. O pior caso seria uma partição de cache tão grande quanto a imagem do sistema. Com atualizações A/B não há necessidade de stash blocks (porque você está sempre gravando em uma partição que não está sendo usada no momento) e com streaming A/B não há necessidade de baixar todo o pacote OTA antes de aplicá-lo.

Recuperação

O disco RAM de recuperação agora está contido no arquivo boot.img . Ao entrar em recuperação, o bootloader não pode colocar a opção skip_initramfs na linha de comando do kernel.

Para atualizações não A/B, a partição de recuperação contém o código usado para aplicar as atualizações. As atualizações A/B são aplicadas pelo update_engine em execução na imagem normal do sistema inicializado. Ainda há um modo de recuperação usado para implementar a redefinição de dados de fábrica e o sideload de pacotes de atualização (de onde veio o nome "recuperação"). O código e os dados para o modo de recuperação são armazenados na partição de inicialização normal em um ramdisk; para inicializar na imagem do sistema, o bootloader diz ao kernel para pular o ramdisk (caso contrário, o dispositivo inicializa no modo de recuperação. O modo de recuperação é pequeno (e grande parte dele já estava na partição de inicialização), então a partição de inicialização não aumenta no tamanho.

Fstab

O argumento slotselect deve estar na linha para as partições A/B-ed. Por exemplo:

<path-to-block-device>/vendor  /vendor  ext4  ro
wait,verify=<path-to-block-device>/metadata,slotselect

Nenhuma partição deve ser nomeada como vendor . Em vez disso, a partição vendor_a ou vendor_b será selecionada e montada no ponto de montagem /vendor .

Argumentos de slot do kernel

O sufixo do slot atual deve ser passado por meio de um nó específico da árvore de dispositivos (DT) ( /firmware/android/slot_suffix ) ou pela linha de comando do kernel androidboot.slot_suffix ou argumento bootconfig.

Por padrão, o fastboot pisca o slot atual em um dispositivo A/B. Se o pacote de atualização também contiver imagens para o outro slot não atual, o fastboot também exibirá essas imagens. As opções disponíveis incluem:

  • --slot SLOT . Substitua o comportamento padrão e solicite que o fastboot atualize o slot que é passado como um argumento.
  • --set-active [ SLOT ] . Defina o slot como ativo. Se nenhum argumento opcional for especificado, o slot atual será definido como ativo.
  • fastboot --help . Obtenha detalhes sobre os comandos.

Se o bootloader implementar o fastboot, ele deve suportar o comando set_active <slot> que define o slot ativo atual para o slot fornecido (isso também deve limpar o sinalizador não inicializável desse slot e redefinir a contagem de tentativas para os valores padrão). O bootloader também deve suportar as seguintes variáveis:

  • has-slot:<partition-base-name-without-suffix> . Retorna “sim” se a partição fornecida suportar slots, “não” caso contrário.
  • current-slot . Retorna o sufixo do slot que será inicializado a partir do próximo.
  • slot-count . Retorna um número inteiro que representa o número de slots disponíveis. Atualmente, dois slots são suportados, portanto, esse valor é 2 .
  • slot-successful:<slot-suffix> . Retorna "sim" se o slot especificado foi marcado como inicializando com sucesso, "não" caso contrário.
  • slot-unbootable:<slot-suffix> . Retorna "sim" se o slot especificado estiver marcado como não inicializável, "não" caso contrário.
  • slot-retry-count . Número de tentativas restantes para tentar inicializar o slot fornecido.

Para visualizar todas as variáveis, execute fastboot getvar all .

Gerando pacotes OTA

As ferramentas do pacote OTA seguem os mesmos comandos que os comandos para dispositivos não A/B. O arquivo target_files.zip deve ser gerado definindo as variáveis ​​de construção para o destino A/B. As ferramentas de pacote OTA identificam e geram pacotes automaticamente no formato do atualizador A/B.

Exemplos:

  • Para gerar um OTA completo:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Para gerar um OTA incremental:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Configurando partições

O update_engine pode atualizar qualquer par de partições A/B definidas no mesmo disco. Um par de partições tem um prefixo comum (como system ou boot ) e sufixo por slot (como _a ). A lista de partições para as quais o gerador de carga útil define uma atualização é configurada pela variável make AB_OTA_PARTITIONS .

Por exemplo, se um par de partições bootloader_a e booloader_b estiver incluído ( _a e _b são os sufixos do slot), você poderá atualizar essas partições especificando o seguinte na configuração do produto ou da placa:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Todas as partições atualizadas pelo update_engine não devem ser modificadas pelo resto do sistema. Durante as atualizações incrementais ou delta , os dados binários do slot atual são usados ​​para gerar os dados no novo slot. Qualquer modificação pode fazer com que os dados do novo slot falhe na verificação durante o processo de atualização e, portanto, falhe na atualização.

Configurando a pós-instalação

Você pode configurar a etapa de pós-instalação de maneira diferente para cada partição atualizada usando um conjunto de pares de valores-chave. Para executar um programa localizado em /system/usr/bin/postinst em uma nova imagem, especifique o caminho relativo à raiz do sistema de arquivos na partição do sistema.

Por exemplo, usr/bin/postinst é system/usr/bin/postinst (se não estiver usando um disco RAM). Além disso, especifique o tipo de sistema de arquivos a ser passado para a chamada de sistema mount(2) . Adicione o seguinte aos arquivos .mk do produto ou dispositivo (se aplicável):

AB_OTA_POSTINSTALL_CONFIG += \
  RUN_POSTINSTALL_system=true \
  POSTINSTALL_PATH_system=usr/bin/postinst \
  FILESYSTEM_TYPE_system=ext4

Compilando

Por motivos de segurança, system_server não pode usar compilação just-in-time (JIT) . Isso significa que você deve compilar com antecedência os arquivos odex para system_server e suas dependências no mínimo; qualquer outra coisa é opcional.

Para compilar aplicativos em segundo plano, você deve adicionar o seguinte à configuração do dispositivo do produto (no device.mk do produto):

  1. Inclua os componentes nativos na compilação para garantir que o script de compilação e os binários sejam compilados e incluídos na imagem do sistema.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Conecte o script de compilação ao update_engine de forma que seja executado como uma etapa de pós-instalação.
      # A/B OTA dexopt update_engine hookup
      AB_OTA_POSTINSTALL_CONFIG += \
        RUN_POSTINSTALL_system=true \
        POSTINSTALL_PATH_system=system/bin/otapreopt_script \
        FILESYSTEM_TYPE_system=ext4 \
        POSTINSTALL_OPTIONAL_system=true
    

Para obter ajuda para instalar os arquivos pré-optados na segunda partição do sistema não utilizada, consulte Primeira instalação de inicialização dos arquivos DEX_PREOPT .