Implementando atualizações A/B

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

Implementando o HAL de controle de inicialização

Os carregadores de inicialização compatíveis com A/B devem implementar o boot_control HAL 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 Kernel

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

  1. Cherrypick 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 contendo 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. Construa o zImage para incluir o certificado como parte do chaveiro do sistema. Para verificar, verifique a entrada procfs (requer KEYS_CONFIG_DEBUG_PROC_KEYS para ser ativado):
      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 destaque 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> .

Definindo variáveis ​​de compilação

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

Deve definir para destino A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
    boot \
    system \
    vendor
    e outras partições atualizadas através do update_engine (rádio, bootloader, 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 de dex2oat pós-instalação (mas pré-reinicialização) descrita em Compilando .
Fortemente recomendado para segmentação A/B
  • Definir TARGET_NO_RECOVERY := true
  • Definir BOARD_USES_RECOVERY_AS_BOOT := true
  • Não defina BOARD_RECOVERYIMAGE_PARTITION_SIZE
Não é possível definir para 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

Definir 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: o tamanho necessário dependia de quais atualizações você deseja aplicar. O pior caso seria uma partição de cache tão grande quanto a imagem do sistema. Com as atualizações A/B não há necessidade de armazenar blocos (porque você está sempre gravando em uma partição que não é 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 existe 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 regular em um ramdisk; para inicializar na imagem do sistema, o bootloader informa ao kernel para ignorar o ramdisk (caso contrário, o dispositivo inicializa no modo de recuperação. O modo de recuperação é pequeno (e muito dele já estava na partição de inicialização), portanto, a partição de inicialização não aumenta de 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 vendor . Em vez disso, a partição vendor_a ou vendor_b será selecionada e montada no ponto de montagem /vendor .

Argumentos do slot do kernel

O sufixo do slot atual deve ser passado por meio de um nó de árvore de dispositivo (DT) específico ( /firmware/android/slot_suffix ) ou por meio da linha de comando do kernel androidboot.slot_suffix ou do 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 implementa 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 para esse slot e redefinir a contagem de novas tentativas para os valores padrão). O bootloader também deve suportar as seguintes variáveis:

  • has-slot:<partition-base-name-without-suffix> . Retorna “yes” se a partição fornecida suportar slots, “no” caso contrário.
  • current-slot . Retorna o sufixo do slot que será inicializado a partir do próximo.
  • slot-count . Retorna um inteiro representando o número de slots disponíveis. Atualmente, dois slots são suportados, portanto, esse valor é 2 .
  • slot-successful:<slot-suffix> . Retorna "yes" se o slot fornecido foi marcado como inicializando com sucesso, "no" caso contrário.
  • slot-unbootable:<slot-suffix> . Retorna “yes” se o slot fornecido estiver marcado como não inicializável, “no” caso contrário.
  • slot-retry-count . Número de novas 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 automaticamente pacotes no formato para o 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 um 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 de slot), você pode 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 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.

Configurando a pós-instalação

Você pode configurar a etapa pós-instalação de forma diferente para cada partição atualizada usando um conjunto de pares 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 passado para a chamada do 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

Os aplicativos podem ser compilados em segundo plano antes da reinicialização com a nova imagem do sistema. Para compilar aplicativos em segundo plano, adicione 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 modo que 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 obter ajuda na instalação dos arquivos pré-optados na segunda partição do sistema não utilizada, consulte Primeira instalação de inicialização dos arquivos DEX_PREOPT .