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 bootloader implemente la HAL de boot_control y pase los parámetros correctos al kernel.

Implementa la 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 la system/extras/bootctl utilidad 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 estados 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 <public-key-id> es el ID de la clave pública que se usa para verificar la firma de la tabla de verity (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 kernel directorio. Si el certificado .X509 tiene formato de archivo .pem usa el siguiente openssl para convertirlo de .pem a .der formato:
      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 verificar,consulta la entrada procfs (requiere KEYS_CONFIG_DEBUG_PROC_KEYS que se habilite):
      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 indica 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>.

Establece variables de compilación

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

Debe definirse para el destino 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 obtener 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 anterior al reinicio) que se describe en Compilación.
Muy recomendado para el destino 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 destino A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Opcional para 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 son A/B deben nombrarse de la siguiente manera (los espacios siempre se denominan a, b, etc.): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Caché

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

Recuperación

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

Para las actualizaciones que no son A/B, la partición de recuperación contiene el código que se usa para aplicar actualizaciones. `update_engine` aplica las actualizaciones A/B que se ejecutan en la imagen del sistema iniciada de forma normal.update_engine Aún existe un modo de recuperación que se usa para implementar el restablecimiento de datos de configuración de fábrica y la transferencia 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 iniciar la imagen del sistema, el bootloader le indica al kernel que omita el disco RAM (de lo contrario, el dispositivo se inicia en el modo de recuperación mode. El modo de recuperación es pequeño (y gran parte 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 de las particiones 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 cambio, se seleccionará y activará la partición vendor_a o vendor_b en el punto de activación /vendor.

Argumentos de espacio del kernel

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

De forma predeterminada, fastboot actualiza el espacio actual en un dispositivo A/B. Si el paquete de actualización también contiene imágenes para el otro espacio no actual, fastboot también actualiza esas imágenes. Las opciones disponibles incluyen las siguientes:

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

Si el bootloader implementa fastboot, debe admitir el comando set_active <slot> que establece el espacio activo actual en el espacio determinado (esto también debe borrar la marca no iniciable para ese espacio y restablecer el recuento de reintentos a los valores predeterminados). El bootloader también debe admitir las siguientes variables:

  • has-slot:<partition-base-name-without-suffix>. Muestra "yes" si la partición determinada admite espacios y "no" en caso contrario.
  • current-slot. Muestra el sufijo de espacio desde el que se iniciará a continuación.
  • slot-count. Muestra un número entero que representa la cantidad de espacios disponibles. Actualmente, se admiten dos espacios, por lo que este valor es 2.
  • slot-successful:<slot-suffix>. Muestra "yes" si el espacio determinado se marcó como inicio correcto y "no" en caso contrario.
  • slot-unbootable:<slot-suffix>. Muestra "yes" si el espacio determinado se marcó como no iniciable y "no" en caso contrario.
  • slot-retry-count:<slot-suffix>. Es la cantidad de reintentos restantes para intentar iniciar el espacio determinado.

Para ver todas las variables, ejecuta fastboot getvar all.

Genera paquetes OTA

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

Ejemplos:

  • Para generar un OTA completo, haz lo siguiente:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Para generar un 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

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 espacio (como _a). La lista de particiones para las que el generador de carga útil 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 espacio), 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 ninguna de las particiones actualizadas por update_engine. Durante las actualizaciones incrementales o delta, los datos binarios del espacio actual se usan para generar los datos en el espacio nuevo. Cualquier modificación puede hacer que los datos del espacio nuevo no pasen la verificación durante el proceso de actualización y, por lo tanto, que falle la actualización.

Configura la instalación posterior

Puedes configurar el paso de instalación posterior 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 usas un disco RAM). Además, especifica el tipo de sistema de archivos para pasar a la mount(2) llamada al sistema. 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 del reinicio 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 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 para 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 para instalar los archivos preoptimizados en la segunda partición del sistema sin usar, consulta Instalación de inicio de archivos DEX_PREOPT.