Implementando A/B Virtual

Para implementar A/B virtual em um novo dispositivo ou para adaptar um dispositivo iniciado, você deve fazer alterações no código específico do dispositivo.

Construir sinalizadores

Os dispositivos que usam A/B virtual devem ser configurados como um dispositivo A/B e devem ser iniciados com partições dinâmicas .

Para dispositivos iniciados com A/B virtual, defina-os para herdar a configuração básica do dispositivo A/B virtual:

$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota.mk)

Dispositivos lançados com A/B virtual precisam apenas da metade do tamanho da placa para BOARD_SUPER_PARTITION_SIZE porque os slots B não estão mais em super. Ou seja, BOARD_SUPER_PARTITION_SIZE deve ser maior ou igual a sum(size of update groups) + overhead , que, por sua vez, deve ser maior ou igual a sum(size of partitions) + overhead .

Para habilitar snapshots compactados com Virtual A/B, herde a seguinte configuração básica:

$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)

Controle de inicialização HAL

O controle de inicialização HAL fornece uma interface para clientes OTA controlarem os slots de inicialização. O A/B virtual requer uma atualização de versão secundária do HAL de controle de inicialização porque APIs adicionais são necessárias para garantir que o carregador de inicialização seja protegido durante o flashing/redefinição de fábrica. Consulte IBootControl.hal e types.hal para obter a versão mais recente da definição HAL.

// hardware/interfaces/boot/1.1/types.hal
enum MergeStatus : uint8_t {
    NONE, UNKNOWN, SNAPSHOTTED, MERGING, CANCELLED };

// hardware/interfaces/boot/1.1/IBootControl.hal
package android.hardware.boot@1.1;
interface IBootControl extends @1.0::IBootControl {
    setSnapshotMergeStatus(MergeStatus status)
        generates (bool success);
    getSnapshotMergeStatus()
        generates (MergeStatus status);
}
// Recommended implementation

Return<bool> BootControl::setSnapshotMergeStatus(MergeStatus v) {
    // Write value to persistent storage
    // e.g. misc partition (using libbootloader_message)
    // bootloader rejects wipe when status is SNAPSHOTTED
    // or MERGING
}

Mudanças de Fstab

A integridade da partição de metadados é essencial para o processo de inicialização, especialmente logo após a aplicação de uma atualização OTA. Portanto, a partição de metadados deve ser verificada antes que first_stage_init a monte. Para garantir que isso aconteça, adicione o sinalizador check fs_mgr à entrada para /metadata . O seguinte fornece um exemplo:

/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,sync wait,formattable,first_stage_mount,check

Requisitos do kernel

Para habilitar o snapshot, defina CONFIG_DM_SNAPSHOT como true .

Para dispositivos que usam F2FS, inclua o sinalizador f2fs: export FS_NOCOW_FL para o patch do kernel do usuário para corrigir a fixação do arquivo. Inclua o f2fs: suporte ao patch de kernel de arquivo fixado alinhado também.

O A/B virtual conta com recursos adicionados no kernel versão 4.3: o bit de status de estouro no snapshot e destinos snapshot-merge . Todos os dispositivos iniciados com Android 9 e posterior já devem ter a versão 4.4 ou posterior do kernel.

Para habilitar instantâneos compactados, a versão mínima do kernel com suporte é 4.19. Defina CONFIG_DM_USER=m ou CONFIG_DM_USER=y . Se estiver usando o primeiro (um módulo), o módulo deve ser carregado no ramdisk do primeiro estágio. Isso pode ser feito adicionando a seguinte linha ao Makefile do dispositivo:

BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko

Retrofit em dispositivos que atualizam para o Android 11

Ao atualizar para o Android 11, os dispositivos iniciados com partições dinâmicas podem adaptar opcionalmente o A/B virtual. O processo de atualização é basicamente o mesmo para dispositivos iniciados com A/B virtual, com algumas pequenas diferenças:

  • Localização dos arquivos COW — Para dispositivos de inicialização, o cliente OTA usa todo o espaço vazio disponível na superpartição antes de usar o espaço em /data . Para dispositivos de retrofit, sempre há espaço suficiente na superpartição para que o arquivo COW nunca seja criado em /data .

  • Sinalizadores de recursos em tempo de compilação — para dispositivos que adaptam A/B virtual, tanto PRODUCT_VIRTUAL_AB_OTA quanto PRODUCT_VIRTUAL_AB_OTA_RETROFIT são definidos como true , conforme mostrado abaixo:

    (call inherit-product, \
        (SRC_TARGET_DIR)/product/virtual_ab_ota_retrofit.mk)
    
  • Tamanho da superpartição — Dispositivos iniciados com A/B virtual podem cortar BOARD_SUPER_PARTITION_SIZE pela metade porque os slots B não estão na superpartição. Os dispositivos que adaptam o A/B virtual mantêm o antigo tamanho da superpartição, então BOARD_SUPER_PARTITION_SIZE é maior ou igual a 2 * sum(size of update groups) + overhead , que por sua vez é maior ou igual a 2 * sum(size of partitions) + sobrecarga .

Alterações do carregador de inicialização

Durante a etapa de mesclagem de uma atualização, /data contém a única instância inteira do sistema operacional Android. Depois que a migração é iniciada, as partições nativas do system , vendor e product ficam incompletas até que a cópia seja concluída. Se o dispositivo for redefinido de fábrica durante esse processo, seja por recuperação ou por meio da caixa de diálogo de configurações do sistema, o dispositivo não poderá ser inicializado.

Antes de apagar /data , finalize a mesclagem em recuperação ou reversão dependendo do estado do dispositivo:

  • Se a nova compilação inicializou com êxito antes, conclua a migração.
  • Caso contrário, volte para o slot antigo:
    • Para partições dinâmicas, retorne ao estado anterior.
    • Para partições estáticas, defina o slot ativo para o slot antigo.

Tanto o bootloader quanto o fastbootd podem apagar a partição /data se o dispositivo estiver desbloqueado. Embora fastbootd possa forçar a conclusão da migração, o bootloader não pode. O bootloader não sabe se uma mesclagem está em andamento ou quais blocos em /data constituem as partições do SO. Os dispositivos devem impedir que o usuário inconscientemente torne o dispositivo inoperável (bricking) fazendo o seguinte:

  1. Implemente o HAL de controle de inicialização para que o carregador de inicialização possa ler o valor definido pelo método setSnapshotMergeStatus() .
  2. Se o status de mesclagem for MERGING , ou se o status de mesclagem for SNAPSHOTTED e o slot tiver mudado para o slot recém-atualizado, as solicitações para limpar userdata , metadata ou a partição que armazena o status de mesclagem devem ser rejeitadas no carregador de inicialização.
  3. Implemente o comando fastboot snapshot-update cancel para que os usuários possam sinalizar ao bootloader que desejam ignorar esse mecanismo de proteção.
  4. Modifique as ferramentas ou scripts de flash personalizados para emitir fastboot snapshot-update cancel ao fazer o flash de todo o dispositivo. Isso é seguro para emitir porque a atualização de todo o dispositivo remove o OTA. As ferramentas podem detectar esse comando em tempo de execução implementando fastboot getvar snapshot-update-status . Este comando ajuda a diferenciar entre as condições de erro.

Exemplo

struct VirtualAbState {
    uint8_t StructVersion;
    uint8_t MergeStatus;
    uint8_t SourceSlot;
};

bool ShouldPreventUserdataWipe() {
    VirtualAbState state;
    if (!ReadVirtualAbState(&state)) ...
    return state.MergeStatus == MergeStatus::MERGING ||
           (state.MergeStatus == MergeStatus::SNAPSHOTTED &&
            state.SourceSlot != CurrentSlot()));
}

Mudanças nas ferramentas do Fastboot

O Android 11 faz as seguintes alterações no protocolo fastboot:

  • getvar snapshot-update-status — Retorna o valor que o controle de inicialização HAL comunicou ao bootloader:
    • Se o estado for MERGING , o carregador de inicialização deve retornar merging .
    • Se o estado for SNAPSHOTTED , o carregador de inicialização deverá retornar snapshotted .
    • Caso contrário, o bootloader deve retornar none .
  • snapshot-update merge — Conclui uma operação de merge, inicializando para recovery/fastbootd se necessário. Este comando é válido somente se snapshot-update-status for merging e só tem suporte em fastbootd.
  • snapshot-update cancel — Configura o status de mesclagem do HAL do controle de inicialização para CANCELLED . Este comando é inválido quando o dispositivo está bloqueado.
  • erase or wipe — Um erase ou wipe de metadata , userdata ou uma partição que mantém o status de mesclagem para o controle de inicialização HAL deve verificar o status de mesclagem do instantâneo. Se o status for MERGING ou SNAPSHOTTED , o dispositivo deverá abortar a operação.
  • set_active — Um comando set_active que altera o slot ativo deve verificar o status de mesclagem do instantâneo. Se o status for MERGING , o dispositivo deve abortar a operação. O slot pode ser alterado com segurança no estado SNAPSHOTTED .

Essas alterações foram projetadas para evitar que um dispositivo não inicialize acidentalmente, mas podem prejudicar as ferramentas automatizadas. Quando os comandos são usados ​​como um componente de flash de todas as partições, como executar fastboot flashall , é recomendável usar o seguinte fluxo:

  1. Consulte getvar snapshot-update-status .
  2. Se merging ou tirar um snapshotted , emita snapshot-update cancel .
  3. Prossiga com as etapas piscando.

Reduzindo os requisitos de armazenamento

Dispositivos que não possuem armazenamento A/B completo alocado em super e esperam usar /data conforme necessário, são altamente recomendados para usar a ferramenta de mapeamento de blocos. A ferramenta de mapeamento de bloco mantém a alocação de bloco consistente entre compilações, reduzindo gravações desnecessárias no instantâneo. Isso está documentado em Reduzir o tamanho de OTA .