Implementar atualizações A/B

Os OEMs e fornecedores de SoC que querem implementar atualizações do sistema A/B precisam garantir que o carregador de inicialização implemente o HAL boot_control e transmita os parâmetros corretos para o kernel.

Implementar a HAL de controle de inicialização

Os gerenciadores de inicialização compatíveis com A/B precisam implementar a HAL boot_control em hardware/libhardware/include/hardware/boot_control.h. É possível testar implementações usando o utilitário system/extras/bootctl e system/extras/tests/bootloader/.

Você também precisa implementar a máquina de estados mostrada abaixo:

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

Configurar o kernel

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

  1. Selecione as seguintes séries de patches do kernel (se necessário):
  2. Verifique se os argumentos da linha de comando do kernel contêm 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>"
    ... em que o valor <public-key-id> é o ID da chave pública usada para verificar a assinatura da tabela de verificação. Para mais 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 kernel. Se o certificado .X509 estiver formatado como um arquivo .pem, use o comando openssl a seguir 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 conjunto de chaves do sistema. Para verificar,verifique a entrada procfs (é necessário ativar KEYS_CONFIG_DEBUG_PROC_KEYS):
      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 conjunto de chaves do sistema (o destaque indica o ID da chave pública).
    3. Substitua o espaço por # e transmita-o como <public-key-id> na linha de comando do kernel. Por exemplo, transmita Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f em vez de <public-key-id>.

Definir variáveis de build

Os bootloaders compatíveis com A/B precisam atender aos seguintes critérios de variável de build:

Precisa ser definido para o destino A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    e outras partições atualizadas pelo update_engine (rádio, carregador de inicialização etc.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Confira um exemplo em /device/google/marlin/+/android-7.1.0_r1/device-common.mk. Você pode realizar a etapa dex2oat pós-instalação (mas antes da reinicialização) descrita em Compilação.
Altamente recomendado para o público-alvo do teste A/B
  • Definir TARGET_NO_RECOVERY := true
  • Definir BOARD_USES_RECOVERY_AS_BOOT := true
  • Não definir 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 builds de depuração PRODUCT_PACKAGES_DEBUG += update_engine_client

Definir partições (slots)

Os dispositivos A/B não precisam de uma partição de recuperação ou de cache porque o Android não usa mais essas partições. A partição de dados agora é usada para o pacote OTA transferido por download, 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 precisam ser nomeadas da seguinte maneira (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 que não são A/B, a partição de cache era usada para armazenar pacotes OTA transferidos por OTA e para armazenar blocos temporariamente durante a aplicação de atualizações. Nunca houve uma boa maneira de dimensionar a partição do cache: o tamanho necessário dependia das atualizações que você queria aplicar. O pior caso seria uma partição de cache do tamanho da imagem do sistema. Com as atualizações A/B, não é necessário armazenar blocos, porque você sempre grava em uma partição que não está sendo usada. Além disso, com o streaming A/B, não é necessário fazer o download de 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 na recuperação, o carregador de inicialização não pode colocar a opção skip_initramfs na linha de comando do kernel.

Para atualizações que não são A/B, a partição de recuperação contém o código usado para aplicar atualizações. As atualizações A/B são aplicadas por update_engine em execução na imagem do sistema inicializado normal. Ainda há um modo de recuperação usado para implementar a redefinição para a configuração original e o sideload de pacotes de atualização, de onde vem o nome "Recuperação". O código e os dados do modo de recuperação são armazenados na partição de inicialização normal em um ramdisk. Para inicializar a imagem do sistema, o carregador de inicialização instrui o kernel a pular o ramdisk. Caso contrário, o dispositivo será inicializado 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 de tamanho.

Fstab

O argumento slotselect precisa estar na linha das partições A/B. Exemplo:

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

Nenhuma partição pode 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 de slot atual precisa ser transmitido por um nó específico da árvore de dispositivos (DT, na sigla em inglês) (/firmware/android/slot_suffix) ou pela linha de comando do kernel androidboot.slot_suffix ou pelo argumento bootconfig.

Por padrão, o fastboot atualiza 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 vai atualizar essas imagens. As opções disponíveis incluem:

  • --slot SLOT. Substitua o comportamento padrão e solicite o Fastboot para atualizar o slot transmitido 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. Conferir detalhes sobre os comandos.

Se o carregador de inicialização implementar o Fastboot, ele precisará oferecer suporte ao comando set_active <slot>, que define o slot ativo atual para o slot especificado. Ele também precisa limpar a flag não inicializável para esse slot e redefinir a contagem de tentativas para os valores padrão. O carregador de inicialização também precisa oferecer suporte às seguintes variáveis:

  • has-slot:<partition-base-name-without-suffix>. Retorna "yes" se a partição especificada oferecer suporte a slots. Caso contrário, retorna "no".
  • current-slot: retorna o sufixo de slot que será inicializado a seguir.
  • slot-count. Retorna um número inteiro que representa o número de slots disponíveis. No momento, há suporte para dois slots, então esse valor é 2.
  • slot-successful:<slot-suffix>. Retorna "sim" se o slot tiver sido marcado como inicializado com êxito, ou "não", caso contrário.
  • slot-unbootable:<slot-suffix>. Retorna "yes" se o slot estiver marcado como não inicializável, ou "no", caso contrário.
  • slot-retry-count:<slot-suffix>. Número de novas tentativas restantes para tentar inicializar o slot especificado.

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

Gerar pacotes OTA

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

Exemplos:

  • Para gerar uma OTA completa:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Para gerar uma 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
    

Configurar 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 um sufixo por slot (como _a). A lista de partições para as quais o gerador de payload define uma atualização é configurada pela variável de criação AB_OTA_PARTITIONS.

Por exemplo, se um par de partições bootloader_a e booloader_b for incluído (_a e _b são os sufixos de slot), será possível 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 por update_engine não podem ser modificadas pelo restante do sistema. Durante 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 falhem na verificação durante o processo de atualização e, portanto, falhem na atualização.

Configurar a pós-instalação

É possível configurar a etapa pós-instalação de maneira diferente para cada partição atualizada usando um conjunto de pares de chave-valor. 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 transmitido 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

Compilar apps

Os apps podem ser compilados em segundo plano antes da reinicialização com a nova imagem do sistema. Para compilar apps em segundo plano, adicione o seguinte à configuração do dispositivo do produto (no device.mk do produto):

  1. Inclua os componentes nativos no build 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 a update_engine para que ele seja executado como uma etapa 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 receber ajuda na instalação dos arquivos preoptados na segunda partição do sistema não utilizada, consulte Primeira instalação de inicialização de arquivos DEX_PREOPT.