Transición de montones de ION a DMA-BUF

En Android 12, GKI 2.0 reemplaza el asignador de ION con montones de DMA-BUF por los siguientes motivos:

  • Seguridad: debido a que cada montón DMA-BUF es un dispositivo de carácter independiente, el acceso a cada montón se puede controlar por separado con sepolicy. Esto no era posible con ION porque la asignación desde cualquier montón solo requería acceso al dispositivo /dev/ion .
  • Estabilidad de ABI: a diferencia de ION, se garantiza que la interfaz IOCTL del marco de montones DMA-BUF es estable en ABI porque se mantiene en el kernel de Linux ascendente.
  • Estandarización: el marco de almacenamiento dinámico DMA-BUF ofrece un UAPI bien definido. ION permitía indicadores personalizados e ID de montón que impedían desarrollar un marco de prueba común porque la implementación de ION de cada dispositivo podía comportarse de manera diferente.

La rama android12-5.10 del kernel común de Android deshabilitó CONFIG_ION el 1 de marzo de 2021 .

Fondo

La siguiente es una breve comparación entre los montones ION y DMA-BUF.

Similitudes entre el marco de pilas ION y DMA-BUF

  • Los marcos de almacenamiento dinámico ION y DMA-BUF son exportadores DMA-BUF basados ​​en almacenamiento dinámico.
  • Ambos permiten que cada montón defina su propio asignador y operaciones DMA-BUF.
  • El rendimiento de la asignación es similar porque ambos esquemas necesitan un solo IOCTL para la asignación.

Diferencias entre el marco de pilas ION y DMA-BUF

montones de iones Montones DMA-BUF
Todas las asignaciones de ION se realizan con /dev/ion . Cada montón DMA-BUF es un dispositivo de caracteres que está presente en /dev/dma_heap/<heap_name> .
ION admite banderas privadas de almacenamiento dinámico. Los montones DMA-BUF no admiten marcas privadas de montón. En cambio, cada tipo diferente de asignación se realiza desde un montón diferente. Por ejemplo, las variantes de almacenamiento dinámico del sistema en caché y sin almacenamiento en caché son almacenamientos dinámicos independientes ubicados en /dev/dma_heap/system y /dev/dma_heap/system_uncached .
Es necesario especificar el ID/máscara de montón y los indicadores para la asignación. El nombre del montón se utiliza para la asignación.

Las siguientes secciones enumeran los componentes que se ocupan de ION y describen cómo cambiarlos al marco de almacenamiento dinámico DMA-BUF.

Transición de controladores de kernel de ION a pilas DMA-BUF

Controladores de kernel que implementan montones de ION

Tanto los montones ION como DMA-BUF permiten que cada montón implemente sus propios asignadores y operaciones DMA-BUF. Por lo tanto, puede cambiar de una implementación de almacenamiento dinámico ION a una implementación de almacenamiento dinámico DMA-BUF mediante el uso de un conjunto diferente de API para registrar el almacenamiento dinámico. Esta tabla muestra las API de registro de almacenamiento dinámico ION y sus API de almacenamiento dinámico DMA-BUF equivalentes.

montones de iones Montones DMA-BUF
void ion_device_add_heap(struct ion_heap *heap) struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
void ion_device_remove_heap(struct ion_heap *heap) void dma_heap_put(struct dma_heap *heap);

Los montones DMA-BUF no admiten marcas privadas de montón. Por lo tanto, cada variante del montón debe registrarse individualmente mediante la API dma_heap_add() . Para facilitar el uso compartido de código, se recomienda registrar todas las variantes del mismo montón dentro del mismo controlador. Este ejemplo de dma-buf: system_heap muestra la implementación de las variantes almacenadas y no almacenadas en caché del montón del sistema.

Utilice esta plantilla de ejemplo dma-buf: heaps para crear un montón DMA-BUF desde cero.

Controladores de kernel que asignan directamente desde montones de ION

El marco de montones DMA-BUF también ofrece una interfaz de asignación para clientes en el kernel. En lugar de especificar la máscara de almacenamiento dinámico y los indicadores para seleccionar el tipo de asignación, la interfaz que ofrecen los almacenamientos dinámicos DMA-BUF toma un nombre de almacenamiento dinámico como entrada.

A continuación, se muestra la API de asignación de ION en el kernel y sus API de asignación de almacenamiento dinámico DMA-BUF equivalentes. Los controladores del kernel pueden usar la API dma_heap_find() para consultar la existencia de un montón. La API devuelve un puntero a una instancia de struct dma_heap , que luego se puede pasar como argumento a la API dma_heap_buffer_alloc() .

montones de iones Montones DMA-BUF
struct dma_buf *ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)

struct dma_heap *dma_heap_find(const char *name)

struct dma_buf *struct dma_buf *dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, unsigned int fd_flags, unsigned int heap_flags)

Controladores de kernel que usan DMA-BUF

No se requieren cambios para los controladores que importan solo DMA-BUF, porque un búfer asignado desde un montón ION se comporta exactamente igual que un búfer asignado desde un montón DMA-BUF equivalente.

Transición de los clientes del espacio de usuario de ION a pilas DMA-BUF

Para facilitar la transición a los clientes del espacio de usuario de ION, está disponible una biblioteca de abstracción llamada libdmabufheap . libdmabufheap admite la asignación en pilas DMA-BUF y pilas ION. Primero comprueba si existe un montón DMA-BUF con el nombre especificado y, si no, recurre a un montón ION equivalente, si existe.

Los clientes deben inicializar un objeto BufferAllocator durante su inicialización en lugar de abrir /dev/ion using ion_open() . Esto se debe a que los descriptores de archivo creados al abrir /dev/ion y /dev/dma_heap/<heap_name> son administrados internamente por el objeto BufferAllocator .

Para cambiar de libion a libdmabufheap , modifique el comportamiento de los clientes de la siguiente manera:

  • Realice un seguimiento del nombre del montón que se usará para la asignación, en lugar del identificador/máscara del encabezado y el indicador del montón.
  • Reemplace la API ion_alloc_fd() , que toma una máscara de montón y un argumento de marca, con la BufferAllocator::Alloc() , que toma un nombre de montón en su lugar.

Esta tabla ilustra estos cambios al mostrar cómo libion y libdmabufheap realizan una asignación de almacenamiento dinámico del sistema no almacenada en caché.

Tipo de asignación libión libdmabufheap
Asignación en caché desde el montón del sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Asignación no almacenada en caché del montón del sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

La variante de almacenamiento dinámico del sistema sin almacenar en caché está esperando aprobación en sentido ascendente, pero ya forma parte de la rama android12-5.10 .

Para admitir la actualización de dispositivos, la API MapNameToIonHeap() permite asignar un nombre de almacenamiento dinámico a parámetros de almacenamiento dinámico ION (nombre/máscara de almacenamiento dinámico e indicadores) para permitir que esas interfaces también utilicen asignaciones basadas en nombres. Este es un ejemplo de asignación basada en el nombre .

La documentación de cada API expuesta por libdmabufheap está disponible. La biblioteca también expone un archivo de encabezado para que lo usen los clientes C.

Implementación de Gralloc de referencia

La implementación de Hikey960 gralloc usa libdmabufheap , por lo que puede usarla como una implementación de referencia .

Adiciones requeridas de ueventd

Para cualquier nuevo almacenamiento dinámico DMA-BUF específico del dispositivo creado, agregue una nueva entrada al archivo ueventd.rc del dispositivo. Este ejemplo de configuración ueventd para admitir montones DMA-BUF demuestra cómo se hace esto para el montón del sistema DMA-BUF.

Adiciones requeridas a la política

Agregue permisos de política de privacidad para permitir que un cliente de espacio de usuario acceda a un nuevo montón DMA-BUF. Este ejemplo de agregar permisos requeridos muestra los permisos de política de seguridad creados para que varios clientes accedan al montón del sistema DMA-BUF.

Acceder a los montones de proveedores desde el código del marco

Para garantizar el cumplimiento de Treble, el código del marco solo puede asignarse de categorías preaprobadas de montones de proveedores.

Según los comentarios recibidos de los socios, Google identificó dos categorías de montones de proveedores a los que se debe acceder desde el código del marco:

  1. Montones que se basan en el montón del sistema con optimizaciones de rendimiento específicas del SoC o del dispositivo.
  2. Montones para asignar desde la memoria protegida.

Montones basados ​​en el montón del sistema con optimizaciones de rendimiento específicas del SoC o del dispositivo

Para admitir este caso de uso, se puede anular la implementación de almacenamiento dinámico del sistema de almacenamiento dinámico DMA-BUF predeterminado.

  • CONFIG_DMABUF_HEAPS_SYSTEM está desactivado en gki_defconfig para permitir que sea un módulo de proveedor.
  • Las pruebas de cumplimiento de VTS garantizan que el montón existe en /dev/dma_heap/system . Las pruebas también verifican que el montón se puede asignar desde y que el descriptor de archivo devuelto ( fd ) se puede asignar en memoria (mapeado) desde el espacio del usuario.

Los puntos anteriores también son válidos para la variante no almacenada en caché del montón del sistema, aunque su existencia no es obligatoria para los dispositivos totalmente coherentes con E/S.

Montones para asignar desde la memoria protegida

Las implementaciones de montón seguro deben ser específicas del proveedor, ya que Android Common Kernel no admite una implementación de montón seguro genérica.

  • Registre sus implementaciones específicas del proveedor como /dev/dma_heap/system-secure<vendor-suffix> .
  • Estas implementaciones de montón son opcionales.
  • Si los montones existen, las pruebas de VTS garantizan que se puedan realizar asignaciones a partir de ellos.
  • Los componentes del marco cuentan con acceso a estos montones para que puedan habilitar el uso de montones a través de Codec2 HAL/HAL del mismo proceso no vinculado. Sin embargo, las funciones genéricas del marco de trabajo de Android no pueden depender de ellas debido a la variabilidad en los detalles de implementación. Si se agrega una implementación genérica de almacenamiento dinámico seguro al Kernel común de Android en el futuro, debe usar una ABI diferente para evitar conflictos con la actualización de dispositivos.

Asignador de códec 2 para pilas DMA-BUF

Un asignador de códec2 para la interfaz de pilas DMA-BUF está disponible en AOSP.

La interfaz de almacenamiento de componentes que permite especificar parámetros de almacenamiento dinámico desde C2 HAL está disponible con el asignador de almacenamiento dinámico C2 DMA-BUF.

Ejemplo de flujo de transición para un montón ION

Para suavizar la transición de montones ION a DMA-BUF, libdmabufheap permite cambiar un montón a la vez. Los siguientes pasos muestran un flujo de trabajo sugerido para la transición de un montón ION no heredado denominado my_heap que admite un indicador, ION_FLAG_MY_FLAG .

Paso 1: Cree equivalentes del montón ION en el marco DMA-BUF. En este ejemplo, debido a que el montón ION my_heap admite un indicador ION_FLAG_MY_FLAG , registramos dos montones DMA-BUF:

  • El comportamiento de my_heap coincide exactamente con el comportamiento del montón de ION con el indicador ION_FLAG_MY_FLAG deshabilitado.
  • El comportamiento de my_heap_special coincide exactamente con el comportamiento del montón de ION con el indicador ION_FLAG_MY_FLAG habilitado.

Paso 2: Cree los cambios de ueventd para los nuevos my_heap y my_heap_special DMA-BUF. En este punto, los montones son visibles como /dev/dma_heap/my_heap y /dev/dma_heap/my_heap_special , con los permisos previstos.

Paso 3: para los clientes que asignan desde my_heap , modifique sus archivos MAKE para vincularlos a libdmabufheap . Durante la inicialización del cliente, cree una instancia de un objeto BufferAllocator y use la API MapNameToIonHeap() para asignar la combinación <ION heap name/mask, flag> a nombres de almacenamiento dinámico DMA-BUF equivalentes.

Por ejemplo:

allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )

En lugar de utilizar la API MapNameToIonHeap() con los parámetros de nombre y marca, puede crear la asignación de <ION heap mask, flag> a nombres de almacenamiento dinámico DMA-BUF equivalentes configurando el parámetro de nombre de almacenamiento dinámico ION en vacío.

Paso 4: Reemplace las invocaciones de ion_alloc_fd() con BufferAllocator::Alloc() utilizando el nombre de almacenamiento dinámico adecuado.

Tipo de asignación libión libdmabufheap
Asignación de my_heap con bandera ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
Asignación de my_heap con indicador ION_FLAG_MY_FLAG establecido ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

En este punto, el cliente es funcional pero sigue asignando desde el montón de ION porque no tiene los permisos de política necesarios para abrir el montón de DMA-BUF.

Paso 5: Cree los permisos de política de seguridad necesarios para que el cliente acceda a los nuevos montones de DMA-BUF. El cliente ahora está totalmente equipado para realizar asignaciones desde el nuevo montón DMA-BUF.

Paso 6: verifique que las asignaciones se realicen desde el nuevo montón DMA-BUF examinando logcat .

Paso 7: Deshabilite el montón de ION my_heap en el kernel. Si el código del cliente no necesita admitir la actualización de dispositivos (cuyos kernels solo pueden admitir montones de ION), también puede eliminar las invocaciones de MapNameToIonHeap() .