Transición de montones ION a DMA-BUF

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

  • Seguridad: debido a que cada montón DMA-BUF es un dispositivo de caracteres 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 ABI: a diferencia de ION, se garantiza que la interfaz IOCTL del marco de almacenamiento dinámico DMA-BUF será estable ABI porque se mantiene en el kernel de Linux ascendente.
  • Estandarización: el marco de almacenamiento dinámico DMA-BUF ofrece una UAPI bien definida. 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 montón ION y DMA-BUF

  • Los marcos de montón ION y DMA-BUF son ambos exportadores DMA-BUF basados ​​en montón.
  • 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 único IOCTL para la asignación.

Diferencias entre el marco de montón 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 indicadores privados de montón. Los montones DMA-BUF no admiten indicadores privados de montón. En cambio, cada tipo diferente de asignación se realiza desde un montón diferente. Por ejemplo, las variantes del montón del sistema almacenado en caché y sin caché son montones separados ubicados en /dev/dma_heap/system y /dev/dma_heap/system_uncached .
Es necesario especificar el ID/máscara del 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 tratan con ION y describen cómo cambiarlos al marco de almacenamiento dinámico DMA-BUF.

Transición de controladores del kernel de montones ION a DMA-BUF

Controladores del kernel que implementan montones de ION

Tanto el montón ION como el 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 montón ION a una implementación de montón DMA-BUF utilizando un conjunto diferente de API para registrar el montón. Esta tabla muestra las API de registro del montón ION y sus API de montón 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 indicadores privados de montón. Por lo tanto, cada variante del montón debe registrarse individualmente utilizando 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 en caché y sin 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 se asignan directamente desde montones de ION

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

A continuación se muestra la API de asignación de ION en el kernel y sus API de asignación de montón DMA-BUF equivalentes. Los controladores del kernel pueden utilizar 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 utilizan 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 de espacio de usuario de ION a montones DMA-BUF

Para facilitar la transición a los clientes de espacio de usuario de ION, está disponible una biblioteca de abstracción llamada libdmabufheap . libdmabufheap admite la asignación en montones DMA-BUF y montones ION. Primero comprueba si existe un montón DMA-BUF con el nombre especificado y, en caso contrario, 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 archivos 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:

  • Mantenga un registro del nombre del montón que se utilizará para la asignación, en lugar del ID/máscara del cabezal y el indicador del montón.
  • Reemplace la API ion_alloc_fd() , que toma una máscara de montón y un argumento de bandera, con la API BufferAllocator::Alloc() , que toma un nombre de montón.

Esta tabla ilustra estos cambios mostrando cómo libion ​​y libdmabufheap realizan una asignación del montón del sistema sin 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 sin caché del montón del sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

La variante del montón del sistema sin caché está pendiente de aprobación, 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 montón a parámetros de montón ION (nombre/máscara de montón e indicadores) para permitir que esas interfaces también utilicen asignaciones basadas en nombres. A continuación se muestra un ejemplo de asignación basada en nombres .

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

Implementación de Gralloc de referencia

La implementación gralloc de Hikey960 utiliza libdmabufheap , por lo que puede utilizarla como implementación de referencia .

Adiciones ueventd requeridas

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

Adiciones de política de seguridad requeridas

Agregue permisos de política de seguridad 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 creados para que varios clientes accedan al montón del sistema DMA-BUF.

Acceder a montones de proveedores desde el código marco

Para garantizar el cumplimiento de Treble, el código del marco solo se puede asignar desde categorías previamente aprobadas 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 dispositivo o del SoC.
  2. Montones para asignar desde la memoria protegida.

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

Para admitir este caso de uso, se puede anular la implementación del montón del sistema de montón 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 exista en /dev/dma_heap/system . Las pruebas también verifican que se pueda asignar el montón y que el descriptor de archivo devuelto ( fd ) se pueda mapear en memoria (mmap) desde el espacio del usuario.

Los puntos anteriores también son válidos para la variante sin caché del montón del sistema, aunque su existencia no es obligatoria para dispositivos totalmente coherentes con IO.

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

Asignador de códec 2 para montones DMA-BUF

Un asignador de codec2 para la interfaz de montón DMA-BUF está disponible en AOSP.

La interfaz del almacén de componentes que permite especificar parámetros de montón desde C2 HAL está disponible con el asignador de montón C2 DMA-BUF.

Flujo de transición de muestra para un montón de ION

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

Paso 1: crear 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 my_heap coincide exactamente con el comportamiento del montón ION con el indicador ION_FLAG_MY_FLAG deshabilitado.
  • El comportamiento my_heap_special coincide exactamente con el comportamiento del montón ION con el indicador ION_FLAG_MY_FLAG habilitado.

Paso 2: cree los cambios ueventd para los nuevos montones 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 utilice la API MapNameToIonHeap() para asignar la combinación <ION heap name/mask, flag> a nombres de montón 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 desde <ION heap mask, flag> a nombres de montón DMA-BUF equivalentes configurando el parámetro de nombre de montón ION en vacío.

Paso 4: Reemplace las invocaciones ion_alloc_fd() con BufferAllocator::Alloc() usando el nombre del montón apropiado.

Tipo de asignación libión libdmabufheap
Asignación de my_heap con bandera ION_FLAG_MY_FLAG no configurada ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
Asignación de my_heap con el 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 aún realiza asignaciones desde el montón ION porque no tiene los permisos de política necesarios para abrir el montón DMA-BUF.

Paso 5: Cree los permisos de política necesarios para que el cliente acceda a los nuevos montones DMA-BUF. El cliente ahora está completamente 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 ION my_heap en el kernel. Si no es necesario que el código del cliente admita dispositivos de actualización (cuyos kernels solo admitan montones de ION), también puede eliminar las invocaciones MapNameToIonHeap() .