Implémenter les mises à jour A/B

Les OEM et les fournisseurs de SoC qui souhaitent implémenter des mises à jour système A/B doivent s'assurer que leur bootloader implémente le HAL boot_control et transmet les paramètres appropriés au kernel.

Implémenter le HAL de contrôle du démarrage

Les bootloaders compatibles avec A/B doivent implémenter le HAL boot_control à hardware/libhardware/include/hardware/boot_control.h. Vous pouvez tester les implémentations à l'aide de l'utilitaire system/extras/bootctl et de system/extras/tests/bootloader/.

Vous devez également implémenter la machine à états illustrée ci-dessous:

Figure 1 Machine d'état du bootloader

Configurer le noyau

Pour implémenter des mises à jour système A/B:

  1. Sélectionnez les séries de correctifs du kernel suivantes (si nécessaire) :
  2. Assurez-vous que les arguments de ligne de commande du kernel contiennent les arguments supplémentaires suivants:
    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>"
    ... où la valeur <public-key-id> correspond à l'ID de la clé publique utilisée pour valider la signature de la table Verity (pour en savoir plus, consultez dm-verity).
  3. Ajoutez le certificat .X509 contenant la clé publique au trousseau de clés système :
    1. Copiez le certificat .X509 au format .der à la racine du répertoire kernel. Si le certificat .X509 est au format .pem, utilisez la commande openssl suivante pour convertir le format .pem en .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Créez le zImage pour inclure le certificat dans le trousseau système. Pour vérifier,consultez l'entrée procfs (KEYS_CONFIG_DEBUG_PROC_KEYS doit être activé):
      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'inclusion réussie du certificat .X509 indique la présence de la clé publique dans le trousseau système (l'identification de la clé publique est mise en surbrillance).
    3. Remplacez l'espace par # et transmettez-le sous la forme <public-key-id> dans la ligne de commande du kernel. Par exemple, transmettez Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f au lieu de <public-key-id>.

Définir des variables de compilation

Les bootloaders compatibles avec A/B doivent répondre aux critères de variable de compilation suivants:

Doit être défini pour la cible A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    et d'autres partitions mises à jour via update_engine (radio, bootloader, etc.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Pour obtenir un exemple, consultez /device/google/marlin/+/android-7.1.0_r1/device-common.mk. Vous pouvez éventuellement effectuer l'étape dex2oat post-installation (mais avant le redémarrage) décrite dans la section Compilation.
Fortement recommandé pour la cible A/B
  • Définir TARGET_NO_RECOVERY := true
  • Définir BOARD_USES_RECOVERY_AS_BOOT := true
  • Ne pas définir BOARD_RECOVERYIMAGE_PARTITION_SIZE
Impossible de définir pour la cible A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Facultatif pour les builds de débogage PRODUCT_PACKAGES_DEBUG += update_engine_client

Définir des partitions (emplacements)

Les appareils A/B n'ont pas besoin d'une partition de récupération ni d'une partition de cache, car Android n'utilise plus ces partitions. La partition de données est désormais utilisée pour le package OTA téléchargé, et le code de l'image de récupération se trouve sur la partition de démarrage. Toutes les partitions A/B doivent être nommées comme suit (les emplacements sont toujours nommés a, b, etc.): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Cache

Pour les mises à jour autres que A/B, la partition de cache était utilisée pour stocker les packages OTA téléchargés et pour mettre en cache temporairement des blocs lors de l'application des mises à jour. Il n'a jamais été possible de dimensionner correctement la partition de cache: sa taille dépendait des mises à jour que vous souhaitiez appliquer. Le pire des cas est une partition de cache aussi grande que l'image système. Avec les mises à jour A/B, il n'est pas nécessaire de mettre en cache des blocs (car vous écrivez toujours sur une partition qui n'est pas actuellement utilisée), et avec le streaming A/B, il n'est pas nécessaire de télécharger l'intégralité du package OTA avant de l'appliquer.

Récupération

Le disque RAM de récupération est désormais contenu dans le fichier boot.img. Lors de la récupération, le bootloader ne peut pas placer l'option skip_initramfs sur la ligne de commande du noyau.

Pour les mises à jour autres que celles de type A/B, la partition de récupération contient le code utilisé pour appliquer les mises à jour. Les mises à jour A/B sont appliquées par update_engine exécuté dans l'image système démarrée normalement. Un mode de récupération est toujours utilisé pour implémenter le rétablissement de la configuration d'usine et le téléchargement parallèle de packages de mise à jour (d'où le nom "recovery"). Le code et les données du mode de récupération sont stockés dans la partition de démarrage standard dans un ramdisk. Pour démarrer dans l'image système, le bootloader indique au noyau d'ignorer le ramdisk (sinon, l'appareil démarre en mode de récupération). Le mode de récupération est petit (et une grande partie se trouvait déjà sur la partition de démarrage), de sorte que la taille de la partition de démarrage n'augmente pas.

Fstab

L'argument slotselect doit se trouver sur la ligne des partitions A/B. Exemple :

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

Aucune partition ne doit être nommée vendor. À la place, la partition vendor_a ou vendor_b sera sélectionnée et montée sur le point d'installation /vendor.

Arguments de l'emplacement du noyau

Le suffixe d'emplacement actuel doit être transmis via un nœud d'arborescence d'appareil (/firmware/android/slot_suffix) spécifique ou via la ligne de commande du kernel androidboot.slot_suffix ou l'argument bootconfig.

Par défaut, fastboot flashe l'emplacement actuel sur un appareil A/B. Si le package de mise à jour contient également des images pour l'autre emplacement non actuel, fastboot flashe également ces images. Les options disponibles sont les suivantes:

  • --slot SLOT. Ignorez le comportement par défaut et invitez fastboot à flasher l'emplacement transmis en tant qu'argument.
  • --set-active [SLOT]. Définissez l'emplacement comme actif. Si aucun argument facultatif n'est spécifié, l'emplacement actuel est défini comme actif.
  • fastboot --help. Obtenez des informations sur les commandes.

Si le bootloader implémente Fastboot, il doit prendre en charge la commande set_active <slot> qui définit l'emplacement actif actuel sur l'emplacement donné (cela doit également effacer l'indicateur de non-démarrage de cet emplacement et réinitialiser le nombre de tentatives sur les valeurs par défaut). Le bootloader doit également prendre en charge les variables suivantes:

  • has-slot:<partition-base-name-without-suffix>. Renvoie "yes" si la partition donnée est compatible avec les emplacements, "no" sinon.
  • current-slot. Renvoie le suffixe d'emplacement à partir duquel le démarrage sera effectué la prochaine fois.
  • slot-count. Renvoie un entier représentant le nombre d'emplacements disponibles. Actuellement, deux emplacements sont acceptés. Cette valeur est donc 2.
  • slot-successful:<slot-suffix>. Renvoie "yes" si l'emplacement donné a été marqué comme démarré avec succès, ou "no" dans le cas contraire.
  • slot-unbootable:<slot-suffix>. Renvoie "yes" si l'emplacement donné est marqué comme non amorçable, ou "no" dans le cas contraire.
  • slot-retry-count:<slot-suffix>. Nombre de tentatives restantes pour tenter de démarrer l'emplacement donné.

Pour afficher toutes les variables, exécutez fastboot getvar all.

Générer des packages OTA

Les outils de package OTA suivent les mêmes commandes que celles des appareils autres qu'A/B. Le fichier target_files.zip doit être généré en définissant les variables de compilation pour la cible A/B. Les outils de package OTA identifient et génèrent automatiquement des packages au format de l'outil de mise à jour A/B.

Exemples :

  • Pour générer une mise à jour OTA complète:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Pour générer une mise à jour OTA incrémentielle:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Configurer des partitions

update_engine peut mettre à jour n'importe quelle paire de partitions A/B définies sur le même disque. Une paire de partitions comporte un préfixe commun (par exemple, system ou boot) et un suffixe par emplacement (par exemple, _a). La liste des partitions pour lesquelles le générateur de charge utile définit une mise à jour est configurée par la variable de compilation AB_OTA_PARTITIONS.

Par exemple, si une paire de partitions bootloader_a et booloader_b est incluse (_a et _b sont les suffixes d'emplacement), vous pouvez mettre à jour ces partitions en spécifiant les éléments suivants dans la configuration du produit ou de la carte:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Toutes les partitions mises à jour par update_engine ne doivent pas être modifiées par le reste du système. Lors des mises à jour incrémentielles ou delta, les données binaires de l'emplacement actuel sont utilisées pour générer les données du nouvel emplacement. Toute modification peut entraîner l'échec de la validation des nouvelles données de l'emplacement pendant le processus de mise à jour, ce qui entraîne l'échec de la mise à jour.

Configurer la post-installation

Vous pouvez configurer l'étape post-installation différemment pour chaque partition mise à jour à l'aide d'un ensemble de paires clé-valeur. Pour exécuter un programme situé à /system/usr/bin/postinst dans une nouvelle image, spécifiez le chemin d'accès par rapport à la racine du système de fichiers dans la partition système.

Par exemple, usr/bin/postinst est system/usr/bin/postinst (si vous n'utilisez pas de disque RAM). Spécifiez également le type de système de fichiers à transmettre à l'appel système mount(2). Ajoutez ce qui suit aux fichiers .mk du produit ou de l'appareil (le cas échéant):

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

Compiler des applications

Les applications peuvent être compilées en arrière-plan avant le redémarrage avec la nouvelle image système. Pour compiler des applications en arrière-plan, ajoutez ce qui suit à la configuration de l'appareil du produit (dans le fichier device.mk du produit):

  1. Incluez les composants natifs dans la compilation pour vous assurer que le script de compilation et les binaires sont compilés et inclus dans l'image système.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Connectez le script de compilation à update_engine afin qu'il s'exécute comme étape post-installation.
      # 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
    

Pour savoir comment installer les fichiers préoptimisés dans la deuxième partition système inutilisée, consultez la section Installation au premier démarrage des fichiers DEX_PREOPT.