Transición de ION a montones de DMA-BUF

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

  • Seguridad: Como cada montón de DMA-BUF es un dispositivo de caracteres independiente, el acceso a cada montón se puede controlar por separado con una política. 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, la interfaz IOCTL del framework de montón de DMA-BUF es estable de ABI porque se mantiene en el kernel de Linux ascendente.
  • Estandarización: El framework de montón de DMA-BUF ofrece una UAPI bien definida. ION permitía marcas personalizadas y IDs de montón que impedían desarrollar un framework de pruebas común, ya que 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 inhabilitó CONFIG_ION el 1 de marzo de 2021.

Segundo plano

A continuación, se muestra una breve comparación entre los montones de ION y DMA-BUF.

Similitudes entre el framework de ION y los montones de DMA-BUF

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

Diferencias entre el framework de ION y los grupos de DMA-BUF

Montones de ION Montones de DMA-BUF
Todas las asignaciones de ION se realizan con /dev/ion. Cada montón de DMA-BUF es un dispositivo de caracteres presente en /dev/dma_heap/<heap_name>.
ION admite marcas privadas del montón. Los montón de DMA-BUF no admiten marcas privadas de montón. En cambio, cada tipo de asignación se realiza desde un montón diferente. Por ejemplo, las variantes del montón del sistema almacenadas en caché y no almacenadas en caché son montos independientes ubicados en /dev/dma_heap/system y /dev/dma_heap/system_uncached.
Se deben especificar el ID o la máscara del montón y las marcas para la asignación. El nombre del montón se usa para la asignación.

En las siguientes secciones, se enumeran los componentes que se ocupan de ION y se describe cómo cambiarlos al framework de pilas de DMA-BUF.

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

Controladores de kernel que implementan montón de ION

Tanto los montones de ION como los de DMA-BUF permiten que cada montón implemente sus propios asignadores y operaciones de DMA-BUF. Por lo tanto, puedes cambiar de una implementación de montón de ION a una implementación de montón de DMA-BUF con un conjunto diferente de APIs para registrar el montón. En esta tabla, se muestran las APIs de registro del montón de ION y sus APIs de montón de DMA-BUF equivalentes.

Montones de ION Montones de 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 montón de DMA-BUF no admiten marcas privadas de montón. Por lo tanto, cada variante del montón se debe registrar de manera individual con la API de 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. En este ejemplo de dma-buf: system_heap, se muestra la implementación de las variantes almacenadas en caché y no almacenadas en caché del montón del sistema.

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

Controladores de kernel que se asignan directamente desde los montones de ION

El framework de los montones de DMA-BUF también ofrece una interfaz de asignación para clientes en el kernel. En lugar de especificar la máscara de montón y las marcas para seleccionar el tipo de asignación, la interfaz que ofrecen los montón de 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 APIs de asignación de montón de DMA-BUF equivalentes. Los controladores de kernel pueden usar la API de dma_heap_find() para consultar la existencia de un montón. La API muestra un puntero a una instancia de struct dma_heap, que luego se puede pasar como argumento a la API de dma_heap_buffer_alloc().

Montones de ION Montones de 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 solo importan DMA-BUF, ya que un búfer asignado desde un montón de ION se comporta exactamente igual que un búfer asignado desde un montón de DMA-BUF equivalente.

Realiza la transición de los clientes de espacio de usuario de ION a montones de DMA-BUF

Para facilitar la transición para los clientes de espacio de usuario de ION, hay disponible una biblioteca de abstracción llamada libdmabufheap. libdmabufheap admite la asignación en montones de DMA-BUF y montones de ION. Primero, comprueba si existe un montón de DMA-BUF del nombre especificado y, de lo contrario, recurre a un montón de ION equivalente, si existe uno.

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 el objeto BufferAllocator administra de forma interna los descriptores de archivos que se crean cuando se abre /dev/ion y /dev/dma_heap/<heap_name>.

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

  • Haz un seguimiento del nombre del montón que se usará para la asignación, en lugar del ID o la máscara de cabeza y la marca de montón.
  • Reemplaza la API de ion_alloc_fd(), que toma una máscara de montón y un argumento de marca, por la API de BufferAllocator::Alloc(), que toma un nombre de montón en su lugar.

En esta tabla, se ilustran estos cambios mostrando cómo libion y libdmabufheap realizan una asignación de montón del sistema sin almacenar en caché.

Tipo de asignación libion libdmabufheap
Asignación almacenada en caché del 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 del montón del sistema sin almacenar en caché está pendiente de aprobación en upstream, pero ya forma parte de la rama android12-5.10.

Para admitir la actualización de dispositivos, la API de MapNameToIonHeap() permite asignar un nombre de montón a los parámetros de montón de ION (nombre de montón o máscara y marcas) para permitir que esas interfaces usen asignaciones basadas en nombres. Este es 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 que lo usen los clientes de C.

Cómo hacer referencia a la implementación de Gralloc

La implementación de gralloc de Hikey960 usa libdmabufheap, por lo que puedes usarlo como implementación de referencia.

Adiciones obligatorias de ueventd

Para cualquier montón de DMA-BUF específico del dispositivo nuevo que se cree, agrega una entrada nueva al archivo ueventd.rc del dispositivo. En este ejemplo de configuración para admitir montón de DMA-BUF, se demuestra cómo se hace para el montón del sistema de DMA-BUF.

Adiciones obligatorias de sepolicy

Agrega permisos de políticas para permitir que un cliente de espacio de usuario acceda a un montón de DMA-BUF nuevo. En este ejemplo de adición de permisos necesarios, se muestran los permisos de sepolicy creados para varios clientes para acceder al montón del sistema DMA-BUF.

Accede a los grupos de almacenamiento del proveedor desde el código del framework

Para garantizar el cumplimiento de Treble, el código del framework solo puede asignarse a partir de categorías previamente aprobadas de pilas de proveedores.

En función de los comentarios recibidos de los socios, Google identificó dos categorías de pilas de proveedores a las que se debe acceder desde el código del framework:

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

Heaps basados en el heap 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 de DMA-BUF predeterminado.

  • CONFIG_DMABUF_HEAPS_SYSTEM se desactiva en gki_defconfig para 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 desde el montón y que el descriptor de archivo que se muestra (fd) se pueda asignar a la memoria (mapeado de memoria) desde el espacio del usuario.

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

Montones para asignar desde la memoria protegida

Las implementaciones de montón segura deben ser específicas del proveedor, ya que el kernel común de Android no admite una implementación de montón segura genérica.

  • Registra tus 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 se aseguran de que se puedan realizar asignaciones a partir de ellos.
  • Los componentes del framework tienen acceso a estos grupos para que puedan habilitar el uso de grupos a través del HAL de Codec2 o los HAL del mismo proceso no vinculados. Sin embargo, las funciones genéricas del framework de Android no pueden depender de ellas debido a la variabilidad en los detalles de su implementación. Si en el futuro se agrega una implementación de montón seguro genérico al kernel común de Android, debe usar una ABI diferente para evitar conflictos con los dispositivos que se actualizan.

Asignador de Codec 2 para montones de DMA-BUF

Hay un asignador codec2 para la interfaz de los montones de DMA-BUF disponible en AOSP.

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

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

Para suavizar la transición de los montón de ION a DMA-BUF, libdmabufheap permite cambiar de un montón a la vez. En los siguientes pasos, se demuestra un flujo de trabajo sugerido para realizar la transición de un montón de ION no heredado llamado my_heap que admite una marca, ION_FLAG_MY_FLAG.

Paso 1: Crea equivalentes del montón de ION en el framework de DMA-BUF. En este ejemplo, como el montón de ION my_heap admite una marca ION_FLAG_MY_FLAG, registramos dos montones de DMA-BUF:

  • El comportamiento de my_heap coincide exactamente con el del montón de ION con la marca ION_FLAG_MY_FLAG inhabilitada.
  • El comportamiento de my_heap_special coincide exactamente con el comportamiento del montón de ION con la marca ION_FLAG_MY_FLAG habilitada.

Paso 2: Crea los cambios ueventd para los nuevos montos de DMA-BUF my_heap y my_heap_special. En este punto, los montones se pueden ver como /dev/dma_heap/my_heap y /dev/dma_heap/my_heap_special, con los permisos deseados.

Paso 3: En el caso de los clientes que asignen desde my_heap, modifica sus archivos de configuración para vincularlos a libdmabufheap. Durante la inicialización del cliente, crea una instancia de un objeto BufferAllocator y usa la API de 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 usar la API de MapNameToIonHeap() con los parámetros de nombre y marca, puedes crear la asignación de <ION heap mask, flag> a nombres del montón de DMA-BUF equivalentes si configuras el parámetro del nombre del montón de ION en vacío.

Paso 4: Reemplaza las invocaciones de ion_alloc_fd() por BufferAllocator::Alloc() con el nombre de montón apropiado.

Tipo de asignación libion libdmabufheap
Asignación de my_heap con la marca ION_FLAG_MY_FLAG no establecida ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Asignación de my_heap con la marca ION_FLAG_MY_FLAG establecida 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 se asigna desde el montón de ION porque no tiene los permisos de sepolicy necesarios para abrir el montón de DMA-BUF.

Paso 5: Crea los permisos de sepolicy necesarios para que el cliente acceda a los nuevos grupos de DMA-BUF. El cliente ahora está completamente equipado para realizar la asignación desde el nuevo montón de DMA-BUF.

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

Paso 7: Inhabilita el montón de ION my_heap en el kernel. Si el código cliente no necesita admitir dispositivos de actualización (cuyos kernels solo pueden admitir pilas de ION), también puedes quitar las invocaciones de MapNameToIonHeap().