Implementa actualizaciones de A/B

Los OEM y los proveedores de SoC que deseen implementar actualizaciones del sistema A/B deben asegurarse de que su cargador de arranque implemente el HAL boot_control y pase los parámetros correctos al kernel.

Implementa el HAL de control de arranque

Los bootloaders compatibles con A/B deben implementar la HAL de boot_control en hardware/libhardware/include/hardware/boot_control.h. Puedes probar las implementaciones con las utilidades system/extras/bootctl y system/extras/tests/bootloader/.

También debes implementar la máquina de estados que se muestra a continuación:

Figura 1: Máquina de estado del bootloader

Configura el kernel

Para implementar actualizaciones del sistema A/B, haz lo siguiente:

  1. Selecciona los mejores elementos de la siguiente serie de parches del kernel (si es necesario):
  2. Asegúrate de que los argumentos de la línea de comandos del kernel contengan los siguientes argumentos adicionales:
    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>"
    … donde el valor de <public-key-id> es el ID de la clave pública que se usa para verificar la firma de la tabla de veracidad (para obtener más detalles, consulta dm-verity).
  3. Agrega el certificado .X509 que contiene la clave pública al llavero del sistema:
    1. Copia el certificado .X509 con formato .der en la raíz del directorio kernel. Si el certificado .X509 tiene el formato de un archivo .pem, usa el siguiente comando openssl para convertirlo del formato .pem al formato .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Compila zImage para incluir el certificado como parte del llavero del sistema. Para verificarlo,consulta la entrada procfs (requiere que KEYS_CONFIG_DEBUG_PROC_KEYS esté habilitado):
      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
      La inclusión correcta del certificado .X509 indica la presencia de la clave pública en el llavero del sistema (el resaltado denota el ID de la clave pública).
    3. Reemplaza el espacio por # y pásalo como <public-key-id> en la línea de comandos del kernel. Por ejemplo, pasa Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f en lugar de <public-key-id>.

Cómo establecer variables de compilación

Los bootloaders compatibles con A/B deben cumplir con los siguientes criterios de variables de compilación:

Se debe definir para el objetivo de la prueba A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    y otras particiones actualizadas a través de update_engine (radio, bootloader, etc.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Para ver un ejemplo, consulta /device/google/marlin/+/android-7.1.0_r1/device-common.mk. De manera opcional, puedes realizar el paso dex2oat posterior a la instalación (pero previo al reinicio) que se describe en Compilación.
Muy recomendado para el objetivo de la prueba A/B
  • Define TARGET_NO_RECOVERY := true
  • Define BOARD_USES_RECOVERY_AS_BOOT := true
  • No definas BOARD_RECOVERYIMAGE_PARTITION_SIZE.
No se puede definir para el objetivo de la prueba A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Opcional para las compilaciones de depuración PRODUCT_PACKAGES_DEBUG += update_engine_client

Establece particiones (espacios)

Los dispositivos A/B no necesitan una partición de recuperación ni una partición de caché porque Android ya no usa estas particiones. Ahora, la partición de datos se usa para el paquete OTA descargado y el código de la imagen de recuperación está en la partición de arranque. Todas las particiones que se sometan a pruebas A/B deben nombrarse de la siguiente manera (las ranuras siempre se nombran a, b, etcétera): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Caché

En el caso de las actualizaciones que no son A/B, la partición de caché se usaba para almacenar los paquetes de OTA descargados y para guardar bloques temporalmente mientras se aplicaban las actualizaciones. Nunca hubo una buena manera de determinar el tamaño de la partición de caché: el tamaño que necesitaba dependía de las actualizaciones que querías aplicar. El peor caso sería una partición de caché tan grande como la imagen del sistema. Con las actualizaciones A/B, no es necesario almacenar bloques (porque siempre escribes en una partición que no se usa actualmente) y, con las actualizaciones A/B de transmisión, no es necesario descargar todo el paquete de OTA antes de aplicarlo.

Recuperación

El disco RAM de recuperación ahora se incluye en el archivo boot.img. Cuando se ingresa al modo de recuperación, el bootloader no puede colocar la opción skip_initramfs en la línea de comandos del kernel.

En el caso de las actualizaciones que no son A/B, la partición de recuperación contiene el código que se usa para aplicar las actualizaciones. update_engine aplica las actualizaciones de A/B en la imagen del sistema iniciada normal. Aún existe un modo de recuperación que se usa para implementar el restablecimiento de la configuración de fábrica y la transferencia lateral de paquetes de actualización (de ahí proviene el nombre "recuperación"). El código y los datos del modo de recuperación se almacenan en la partición de arranque normal en un disco RAM. Para arrancar la imagen del sistema, el cargador de arranque le indica al kernel que omita el disco RAM (de lo contrario, el dispositivo arranca en el modo de recuperación). El modo de recuperación es pequeño (y gran parte de él ya estaba en la partición de arranque), por lo que el tamaño de la partición de arranque no aumenta.

Fstab

El argumento slotselect debe estar en la línea para las particiones de la prueba A/B. Por ejemplo:

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

Ninguna partición debe llamarse vendor. En su lugar, se seleccionará y se montará la partición vendor_a o vendor_b en el punto de montaje /vendor.

Argumentos de ranura del kernel

El sufijo de ranura actual se debe pasar a través de un nodo específico del árbol de dispositivos (DT) (/firmware/android/slot_suffix) o a través del argumento de línea de comandos del kernel androidboot.slot_suffix o de bootconfig.

De forma predeterminada, fastboot escribe en la memoria flash la ranura actual en un dispositivo A/B. Si el paquete de actualización también contiene imágenes para la otra ranura no actual, fastboot también las escribe en la memoria flash. Las opciones disponibles incluyen las siguientes:

  • --slot SLOT. Anula el comportamiento predeterminado y solicita a fastboot que escriba en la ranura que se pasa como argumento.
  • --set-active [SLOT]. Establece la ranura como activa. Si no se especifica ningún argumento opcional, se establece la ranura actual como activa.
  • fastboot --help. Obtén detalles sobre los comandos.

Si el cargador de arranque implementa fastboot, debe admitir el comando set_active <slot> que establece la ranura activa actual en la ranura determinada (esto también debe borrar la marca de no arranque para esa ranura y restablecer el recuento de reintentos a los valores predeterminados). El cargador de arranque también debe admitir las siguientes variables:

  • has-slot:<partition-base-name-without-suffix>. Devuelve "yes" si la partición determinada admite espacios y "no" en caso contrario.
  • current-slot: Devuelve el sufijo de ranura desde el que se realizará el próximo inicio.
  • slot-count. Devuelve un número entero que representa la cantidad de espacios disponibles. Actualmente, se admiten dos ranuras, por lo que este valor es 2.
  • slot-successful:<slot-suffix>. Devuelve "yes" si la ranura determinada se marcó como que se inició correctamente; de lo contrario, devuelve "no".
  • slot-unbootable:<slot-suffix>. Devuelve "yes" si la ranura determinada está marcada como no iniciable y "no" en caso contrario.
  • slot-retry-count:<slot-suffix>. Es la cantidad de reintentos restantes para intentar iniciar la ranura determinada.

Para ver todas las variables, ejecuta fastboot getvar all.

Cómo generar paquetes de OTA

Las herramientas de paquetes OTA siguen los mismos comandos que los comandos para dispositivos que no son A/B. El archivo target_files.zip se debe generar definiendo las variables de compilación para el destino de A/B. Las herramientas de paquetes OTA identifican y generan automáticamente paquetes en el formato del actualizador A/B.

Ejemplos:

  • Para generar una OTA completa, haz lo siguiente:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Para generar una OTA incremental, haz lo siguiente:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Configura particiones

El update_engine puede actualizar cualquier par de particiones A/B definidas en el mismo disco. Un par de particiones tiene un prefijo común (como system o boot) y un sufijo por ranura (como _a). La lista de particiones para las que el generador de cargas útiles define una actualización se configura con la variable de creación AB_OTA_PARTITIONS.

Por ejemplo, si se incluye un par de particiones bootloader_a y booloader_b (_a y _b son los sufijos de ranura), puedes actualizar estas particiones especificando lo siguiente en la configuración del producto o la placa:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

El resto del sistema no debe modificar todas las particiones actualizadas por update_engine. Durante las actualizaciones incrementales o de delta, los datos binarios de la ranura actual se usan para generar los datos en la ranura nueva. Cualquier modificación puede hacer que los datos de la nueva ranura no pasen la verificación durante el proceso de actualización y, por lo tanto, que la actualización falle.

Configura los parámetros posteriores a la instalación

Puedes configurar el paso posterior a la instalación de manera diferente para cada partición actualizada con un conjunto de pares clave-valor. Para ejecutar un programa ubicado en /system/usr/bin/postinst en una imagen nueva, especifica la ruta de acceso relativa a la raíz del sistema de archivos en la partición del sistema.

Por ejemplo, usr/bin/postinst es system/usr/bin/postinst (si no se usa un disco RAM). Además, especifica el tipo de sistema de archivos que se pasará a la llamada al sistema mount(2). Agrega lo siguiente a los archivos .mk del producto o dispositivo (si corresponde):

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

Compila apps

Las apps se pueden compilar en segundo plano antes de reiniciar el dispositivo con la nueva imagen del sistema. Para compilar apps en segundo plano, agrega lo siguiente a la configuración del dispositivo del producto (en el archivo device.mk del producto):

  1. Incluye los componentes nativos en la compilación para garantizar que la secuencia de comandos de compilación y los archivos binarios se compilen y se incluyan en la imagen del sistema.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Conecta la secuencia de comandos de compilación a update_engine de modo que se ejecute como un paso posterior a la instalación.
      # 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 obtener ayuda con la instalación de los archivos preoptimizados en la segunda partición del sistema sin usar, consulta Instalación de archivos DEX_PREOPT en el primer arranque.