Implementa actualizaciones A/B

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

Implementa el HAL de control de inicio

Los bootloaders compatibles con A/B deben implementar el HAL de boot_control en hardware/libhardware/include/hardware/boot_control.h. Puedes probar las implementaciones con la utilidad 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

Cómo configurar el kernel

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

  1. Selecciona las siguientes series de parches del kernel (si es necesario):
  2. Asegúrate de que los argumentos de 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 información, 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 de .pem a .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Compila el zImage para incluir el certificado como parte del llavero del sistema. Para verificar,revisa 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 (la parte destacada 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>.

Cómo configurar 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 objetivo de 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 de dex2oat posterior a la instalación (pero antes del reinicio) que se describe en Compilación.
Muy recomendable para el objetivo 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 pruebas A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Opcional para compilaciones de depuración PRODUCT_PACKAGES_DEBUG += update_engine_client

Cómo establecer particiones (ranuras)

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. 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 inicio. Todas las particiones que se someten a pruebas A/B deben tener los siguientes nombres (las ranuras siempre se denominan 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 de A/B, la partición de caché se usaba para almacenar paquetes OTA descargados y ocultar bloques temporalmente mientras se aplicaban las actualizaciones. Nunca hubo una buena manera de determinar el tamaño de la partición de caché: su tamaño 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 ocultar bloques (porque siempre escribes en una partición que no se usa actualmente) y, con la transmisión A/B, no es necesario descargar todo el paquete OTA antes de aplicarlo.

Recuperación

El disco RAM de recuperación ahora se encuentra 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 ejecuta las actualizaciones A/B en la imagen del sistema iniciada de forma normal. Todavía hay 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 ramdisk. Para iniciar la imagen del sistema, el bootloader le indica al kernel que omita el ramdisk (de lo contrario, el dispositivo se inicia en el modo de recuperación). El modo de recuperación es pequeño (y gran parte ya estaba en la partición de inicio), por lo que el tamaño de la partición de inicio no aumenta.

Fstab

El argumento slotselect debe estar en la línea para las particiones con 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á la partición vendor_a o vendor_b y se activará en el punto de activación /vendor.

Argumentos de ranura del kernel

El sufijo de ranura actual se debe pasar 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 comandos del kernel androidboot.slot_suffix o el argumento bootconfig.

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

  • --slot SLOT. Anula el comportamiento predeterminado y solicita a fastboot que escriba en 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 zócalo activo actual en el zócalo determinado (esto también debe borrar la marca de no inicio de ese zócalo 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 “sí” si la partición proporcionada admite espacios, “no” en caso contrario.
  • current-slot. Muestra el sufijo del zócalo desde el que se iniciará el siguiente.
  • slot-count. Muestra un número entero que representa la cantidad de ranuras disponibles. Actualmente, se admiten dos ranuras, por lo que este valor es 2.
  • slot-successful:<slot-suffix>. Muestra “sí” si el zócalo determinado se marcó como que se inició correctamente, “no” en caso contrario.
  • slot-unbootable:<slot-suffix>. Muestra “sí” si el zócalo determinado está marcado como no inicializable, y “no” en caso contrario.
  • slot-retry-count:<slot-suffix>. Cantidad de reintentos restantes para intentar iniciar el espacio determinado.

Para ver todas las variables, ejecuta fastboot getvar all.

Genera paquetes inalámbricos

Las herramientas de paquetes OTA siguen los mismos comandos que los comandos para dispositivos que no son A/B. Para generar el archivo target_files.zip, debes definir las variables de compilación del objetivo 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 actualización 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 ranura (como _a). La variable de compilación AB_OTA_PARTITIONS configura la lista de particiones para las que el generador de carga útil define una actualización.

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 que actualizó update_engine. Durante las actualizaciones incrementales o delta, se usan los datos binarios del espacio actual para generar los datos del espacio nuevo. Cualquier modificación puede hacer que los datos de los nuevos segmentos no se verifiquen durante el proceso de actualización y, por lo tanto, que esta falle.

Configura la postinstalación

Puedes configurar el paso de postinstalación de forma 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 en relación con 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 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 device.mk del producto):

  1. Incluye los componentes nativos en la compilación para asegurarte de que la secuencia de comandos de compilación y los objetos 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 preopcionados en la segunda partición del sistema sin usar, consulta Primera instalación de arranque de archivos DEX_PREOPT.