Implementare gli aggiornamenti A/B

I produttori OEM e i fornitori di SoC che vogliono implementare gli aggiornamenti di sistema A/B devono assicurarsi che il bootloader implementi l'HAL boot_control e trasmetta i parametri corretti al kernel.

Implementa l'HAL di controllo dell'avvio

I bootloader compatibili con A/B devono implementare l'HAL boot_control in hardware/libhardware/include/hardware/boot_control.h. Puoi testare le implementazioni utilizzando l'utilità system/extras/bootctl e system/extras/tests/bootloader/.

Devi anche implementare la macchina a stati mostrata di seguito:

Figura 1. Bootloader state machine

Configurare il kernel

Per implementare gli aggiornamenti di sistema A/B:

  1. Seleziona le seguenti serie di patch del kernel (se necessario):
  2. Assicurati che gli argomenti della riga di comando del kernel contengano gli argomenti extra seguenti:
    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>"
    ... dove il valore <public-key-id> è l'ID della chiave pubblica utilizzata per verificare la firma della tabella di integrità (per i dettagli, vedi dm-verity).
  3. Aggiungi il certificato .X509 contenente la chiave pubblica al keyring di sistema:
    1. Copia il certificato .X509 formattato nel formato .der nella radice della directory kernel. Se il certificato .X509 è formattato come file .pem, utilizza il seguente comando openssl per convertire il formato .pem in .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Crea zImage in modo da includere il certificato nel keyring di sistema. Per verificare,controlla la voce procfs (richiede l'abilitazione di 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
      L'inclusione riuscita del certificato .X509 indica la presenza della chiave pubblica nel keyring di sistema (l'evidenziazione indica l'ID della chiave pubblica).
    3. Sostituisci lo spazio con # e passalo come <public-key-id> nella riga di comando del kernel. Ad esempio, passa Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f al posto di <public-key-id>.

Impostare le variabili di build

I bootloader compatibili con A/B devono soddisfare i seguenti criteri per le variabili di build:

Deve essere definito per il target A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    e altre partizioni aggiornate tramite update_engine (radio, bootloader, ecc.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Per un esempio, consulta /device/google/marlin/+/android-7.1.0_r1/device-common.mk. Se vuoi, puoi eseguire il passaggio dex2oat post-installazione (ma pre-riavvio) descritto in Compilazione.
Vivamente consigliato per il target A/B
  • Definisci TARGET_NO_RECOVERY := true
  • Definisci BOARD_USES_RECOVERY_AS_BOOT := true
  • Non definire BOARD_RECOVERYIMAGE_PARTITION_SIZE
Impossibile definire il target A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Facoltativo per le build di debug PRODUCT_PACKAGES_DEBUG += update_engine_client

Impostare le partizioni (slot)

I dispositivi A/B non richiedono una partizione di ripristino o una partizione della cache perché Android non utilizza più queste partizioni. La partizione dei dati viene ora utilizzata per il pacchetto OTA scaricato e il codice dell'immagine di ripristino si trova nella partizione di avvio. Tutte le partizioni sottoposte a test A/B devono essere denominate nel seguente modo (gli slot sono sempre denominati a, b e così via): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Cache

Per gli aggiornamenti non A/B, la partizione della cache veniva utilizzata per archiviare i pacchetti OTA scaricati e per memorizzare temporaneamente i blocchi durante l'applicazione degli aggiornamenti. Non è mai esistito un modo efficace per dimensionare la partizione della cache: la sua dimensione dipendeva dagli aggiornamenti che volevi applicare. Lo scenario peggiore sarebbe una partizione della cache grande quanto l'immagine di sistema. Con gli aggiornamenti A/B non è necessario memorizzare i blocchi (perché scrivi sempre in una partizione non attualmente utilizzata) e con gli aggiornamenti A/B in streaming non è necessario scaricare l'intero pacchetto OTA prima di applicarlo.

Ripristino

Il disco RAM di ripristino è ora contenuto nel file boot.img. Quando si passa alla modalità di ripristino, il bootloader non può inserire l'opzione skip_initramfs nella riga di comando del kernel.

Per gli aggiornamenti non A/B, la partizione di ripristino contiene il codice utilizzato per applicare gli aggiornamenti. Gli aggiornamenti A/B vengono applicati da update_engine in esecuzione nell'immagine di sistema avviata regolarmente. Esiste ancora una modalità di ripristino utilizzata per implementare il ripristino dei dati di fabbrica e il sideloading dei pacchetti di aggiornamento (da cui deriva il nome "ripristino"). Il codice e i dati per la modalità di ripristino sono archiviati nella partizione di avvio normale in un ramdisk; per avviare l'immagine di sistema, il bootloader indica al kernel di ignorare il ramdisk (altrimenti il dispositivo si avvia in modalità di ripristino). La modalità di ripristino è piccola (e gran parte era già presente nella partizione di avvio), quindi la partizione di avvio non aumenta di dimensioni.

Fstab

L'argomento slotselect deve trovarsi sulla riga delle partizioni sottoposte a test A/B. Ad esempio:

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

Nessuna partizione deve essere denominata vendor. Verrà invece selezionata e montata la partizione vendor_a o vendor_b nel punto di montaggio /vendor.

Argomenti dello slot del kernel

Il suffisso dello slot corrente deve essere passato tramite un nodo specifico dell'albero dei dispositivi (DT) (/firmware/android/slot_suffix) o tramite l'argomento della riga di comando del kernel o di bootconfig androidboot.slot_suffix.

Per impostazione predefinita, fastboot esegue il flashing dello slot corrente su un dispositivo A/B. Se il pacchetto di aggiornamento contiene anche immagini per l'altro slot non corrente, fastboot esegue il flash anche di queste immagini. Le opzioni disponibili includono:

  • --slot SLOT. Esegui l'override del comportamento predefinito e chiedi a fastboot di eseguire il flash dello slot passato come argomento.
  • --set-active [SLOT]. Imposta lo slot come attivo. Se non viene specificato alcun argomento facoltativo, lo slot corrente viene impostato come attivo.
  • fastboot --help. Ottieni dettagli sui comandi.

Se il bootloader implementa fastboot, deve supportare il comando set_active <slot> che imposta lo slot attivo corrente sullo slot specificato (questo deve anche cancellare il flag di avvio non riuscito per lo slot e reimpostare il conteggio dei tentativi sui valori predefiniti). Il bootloader deve supportare anche le seguenti variabili:

  • has-slot:<partition-base-name-without-suffix>. Restituisce "yes" se la partizione specificata supporta gli slot, altrimenti restituisce "no".
  • current-slot. Restituisce il suffisso dello slot da cui verrà eseguito l'avvio successivo.
  • slot-count. Restituisce un numero intero che rappresenta il numero di slot disponibili. Al momento sono supportati due slot, quindi questo valore è 2.
  • slot-successful:<slot-suffix>. Restituisce "yes" se lo slot specificato è stato contrassegnato come avviato correttamente, "no" in caso contrario.
  • slot-unbootable:<slot-suffix>. Restituisce "yes" se lo slot specificato è contrassegnato come non avviabile, altrimenti restituisce "no".
  • slot-retry-count:<slot-suffix>. Numero di tentativi rimanenti per tentare di avviare lo slot specificato.

Per visualizzare tutte le variabili, esegui fastboot getvar all.

Genera pacchetti OTA

Gli strumenti per i pacchetti OTA seguono gli stessi comandi dei comandi per i dispositivi non A/B. Il file target_files.zip deve essere generato definendo le variabili di build per il target A/B. Gli strumenti del pacchetto OTA identificano e generano automaticamente i pacchetti nel formato per l'aggiornamento A/B.

Esempi:

  • Per generare un aggiornamento OTA completo:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Per generare un aggiornamento OTA incrementale:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Configurare le partizioni

update_engine può aggiornare qualsiasi coppia di partizioni A/B definita nello stesso disco. Una coppia di partizioni ha un prefisso comune (ad esempio system o boot) e un suffisso per slot (ad esempio _a). L'elenco delle partizioni per le quali il generatore di payload definisce un aggiornamento è configurato dalla variabile make AB_OTA_PARTITIONS.

Ad esempio, se sono incluse una coppia di partizioni bootloader_a e booloader_b (_a e _b sono i suffissi dello slot), puoi aggiornarle specificando quanto segue nella configurazione del prodotto o della scheda:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Tutte le partizioni aggiornate da update_engine non devono essere modificate dal resto del sistema. Durante gli aggiornamenti incrementali o delta, i dati binari dello slot corrente vengono utilizzati per generare i dati nel nuovo slot. Qualsiasi modifica potrebbe causare la mancata verifica dei nuovi dati dello slot durante la procedura di aggiornamento e, di conseguenza, l'aggiornamento non andrà a buon fine.

Configura post-installazione

Puoi configurare il passaggio post-installazione in modo diverso per ogni partizione aggiornata utilizzando un insieme di coppie chiave-valore. Per eseguire un programma che si trova in /system/usr/bin/postinst in una nuova immagine, specifica il percorso relativo alla radice del file system nella partizione di sistema.

Ad esempio, usr/bin/postinst è system/usr/bin/postinst (se non utilizzi un disco RAM). Inoltre, specifica il tipo di file system da passare alla chiamata di sistema mount(2). Aggiungi quanto segue ai file .mk del prodotto o del dispositivo (se applicabile):

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

Compilare app

Le app possono essere compilate in background prima del riavvio con la nuova immagine di sistema. Per compilare le app in background, aggiungi quanto segue alla configurazione del dispositivo del prodotto (in device.mk del prodotto):

  1. Includi i componenti nativi nella build per assicurarti che lo script di compilazione e i file binari vengano compilati e inclusi nell'immagine di sistema.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Collega lo script di compilazione a update_engine in modo che venga eseguito come passaggio post-installazione.
      # 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
    

Per assistenza con l'installazione dei file preottimizzati nella seconda partizione di sistema inutilizzata, consulta Installazione al primo avvio dei file DEX_PREOPT.