Implementación de actualizaciones A/B

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

Implementando el control de arranque HAL

Los cargadores de arranque compatibles con A/B deben implementar boot_control HAL en hardware/libhardware/include/hardware/boot_control.h . Puede probar las implementaciones usando la utilidad system/extras/bootctl y system/extras/tests/bootloader/ .

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

Figura 1. Máquina de estado del cargador de arranque

Configurando el núcleo

Para implementar actualizaciones del sistema A/B:

  1. Elija la siguiente serie de parches del kernel (si es necesario):
  2. Asegúrese 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 utilizada para verificar la firma de la tabla de verity (para obtener detalles, consulte dm-verity ) .
  3. Agregue el certificado .X509 que contiene la clave pública al conjunto de claves del sistema:
    1. Copie el certificado .X509 formateado en formato .der en la raíz del directorio del kernel . Si el certificado .X509 tiene formato de archivo .pem , use el siguiente comando openssl para convertir el formato .pem a .der :
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Genere zImage para incluir el certificado como parte del conjunto de claves del sistema. Para verificar, verifique la entrada de 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 exitosa del certificado .X509 indica la presencia de la clave pública en el conjunto de claves del sistema (el resaltado indica el ID de la clave pública).
    3. Reemplace el espacio con # y páselo como <public-key-id> en la línea de comando del kernel. Por ejemplo, pase Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f en lugar de <public-key-id> .

Configuración de variables de compilación

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

Debe definirse para el objetivo A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
    boot \
    system \
    vendor
    y otras particiones actualizadas a través update_engine (radio, gestor de arranque, etc.)
  • PRODUCT_PACKAGES += \
    update_engine \
    update_verifier
Para ver un ejemplo, consulte /device/google/marlin/+/android-7.1.0_r1/device-common.mk . Opcionalmente, puede realizar el paso de dex2oat posterior a la instalación (pero previo al reinicio) que se describe en Compilación .
Altamente recomendado para objetivos A/B
  • Definir TARGET_NO_RECOVERY := true
  • Definir BOARD_USES_RECOVERY_AS_BOOT := true
  • No definir BOARD_RECOVERYIMAGE_PARTITION_SIZE
No se puede definir para el objetivo A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Opcional para compilaciones de depuración PRODUCT_PACKAGES_DEBUG += update_engine_client

Configuración de particiones (ranuras)

Los dispositivos A/B no necesitan una partición de recuperación o una partición de caché porque Android ya no usa estas particiones. La partición de datos ahora 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-ed deben nombrarse de la siguiente manera (las ranuras siempre se denominan a , b , etc.): boot_a , boot_b , system_a , system_b , vendor_a , vendor_b .

Cache

Para las actualizaciones que no son A/B, la partición de caché se utilizó para almacenar paquetes OTA descargados y para ocultar bloques temporalmente mientras se aplicaban las actualizaciones. Nunca hubo una buena manera de dimensionar la partición del caché: el tamaño que debía tener dependía de las actualizaciones que deseaba aplicar. El peor de los casos sería una partición de caché tan grande como la imagen del sistema. Con las actualizaciones A/B no hay necesidad de almacenar bloques (porque siempre está escribiendo en una partición que no se usa actualmente) y con la transmisión A/B no hay necesidad de descargar todo el paquete OTA antes de aplicarlo.

Recuperación

El disco RAM de recuperación ahora está contenido en el archivo boot.img . Al entrar en recuperación, el gestor de arranque no puede poner 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 utilizado para aplicar las actualizaciones. Las actualizaciones A/B se aplican mediante update_engine ejecutándose en la imagen del sistema arrancado normal. Todavía hay un modo de recuperación que se usa para implementar el restablecimiento de datos de fábrica y la carga lateral de paquetes de actualización (que es de donde proviene el nombre "recuperación"). El código y los datos para el modo de recuperación se almacenan en la partición de arranque normal en un ramdisk; para arrancar en la imagen del sistema, el cargador de arranque le dice al núcleo que omita el ramdisk (de lo contrario, el dispositivo arranca en modo de recuperación. El modo de recuperación es pequeño (y gran parte ya estaba en la partición de arranque), por lo que la partición de arranque no aumenta en tamaño.

Fstab

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

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

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

Argumentos de la ranura del kernel

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

De forma predeterminada, fastboot muestra 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 muestra esas imágenes. Las opciones disponibles incluyen:

  • --slot SLOT . Anule el comportamiento predeterminado y solicite a fastboot que actualice la ranura que se pasa como argumento.
  • --set-active [ SLOT ] . Establecer la ranura como activa. Si no se especifica ningún argumento opcional, la ranura actual se establece como activa.
  • fastboot --help . Obtenga detalles sobre los comandos.

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

  • has-slot:<partition-base-name-without-suffix> . Devuelve "sí" si la partición dada admite ranuras, "no" de lo contrario.
  • current-slot . Devuelve el sufijo de la ranura desde la que se iniciará a continuación.
  • slot-count . Devuelve un número entero que representa el número de ranuras disponibles. Actualmente, se admiten dos ranuras, por lo que este valor es 2 .
  • slot-successful:<slot-suffix> . Devuelve "sí" si la ranura dada se ha marcado como un arranque exitoso, "no" de lo contrario.
  • slot-unbootable:<slot-suffix> . Devuelve "sí" si la ranura dada está marcada como no arrancable, "no" de lo contrario.
  • slot-retry-count . Número de reintentos restantes para intentar iniciar la ranura dada.

Para ver todas las variables, ejecute fastboot getvar all .

Generación de 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 de paquetes OTA identifican y generan automáticamente paquetes en el formato para el actualizador A/B.

Ejemplos:

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

Configuración de 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 ranura (como _a ). La lista de particiones para las que el generador de carga útil define una actualización está configurada por la variable make AB_OTA_PARTITIONS .

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

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Todas las particiones actualizadas por update_engine no deben ser modificadas por el resto del sistema. Durante las actualizaciones incrementales o delta , los datos binarios de la ranura actual se utilizan para generar los datos en la nueva ranura. Cualquier modificación puede hacer que los nuevos datos de la ranura no superen la verificación durante el proceso de actualización y, por lo tanto, la actualización falle.

Configuración posterior a la instalación

Puede configurar el paso posterior a la instalación de manera diferente para cada partición actualizada mediante un conjunto de pares clave-valor. Para ejecutar un programa ubicado en /system/usr/bin/postinst en una nueva imagen, especifique la ruta 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, especifique el tipo de sistema de archivos para pasar a la llamada del sistema mount(2) . Agregue 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

Compilando

Por motivos de seguridad, system_server no puede utilizar la compilación justo a tiempo (JIT) . Esto significa que debe compilar con anticipación los archivos odex para system_server y sus dependencias como mínimo; cualquier otra cosa es opcional.

Para compilar aplicaciones en segundo plano, debe agregar lo siguiente a la configuración del dispositivo del producto (en el archivo device.mk del producto):

  1. Incluya los componentes nativos en la compilación para garantizar que el script de compilación y los archivos binarios se compilen e incluyan en la imagen del sistema.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Conecte el script 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 con la instalación de los archivos preoptados en la segunda partición del sistema no utilizada, consulte Primera instalación de arranque de archivos DEX_PREOPT .