Transición de ION a montones de DMA-BUF (solo kernel 5.4)

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

  • Seguridad: Debido a que cada montón de 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, ya que 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 montones de DMA-BUF es estable en ABI porque se mantiene en el kernel de Linux ascendente.
  • Estandarización: El framework de montones de DMA-BUF ofrece una UAPI bien definida. ION permitía marcas personalizadas y IDs de montón que impedían el desarrollo de un framework de prueba 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.

Fondo

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

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

  • Los frameworks de ION y de montones de DMA-BUF son exportadores de DMA-BUF basados en montones.
  • Ambos permiten que cada montón defina sus propias operaciones de asignador y 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 el de montones 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 que está presente en /dev/dma_heap/<heap_name>.
ION admite marcas privadas de montón. Los montones de 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 montón del sistema almacenadas en caché y sin caché son montones separados 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 montones de DMA-BUF.

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

Controladores del kernel que implementan montones de ION

Tanto ION como los montones 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 usando un conjunto diferente de APIs para registrar el montón. En esta tabla, se muestran las APIs de registro de 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 montones de DMA-BUF no admiten marcas privadas de montón. Por lo tanto, cada variante del montón se debe registrar de forma individual con la dma_heap_add() API. 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 sin caché del montón del sistema.

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

Controladores del kernel que asignan directamente desde montones de ION

El framework de montones de DMA-BUF también ofrece una interfaz de asignación para clientes en el kernel. En lugar de especificar la máscara y las marcas del montón para seleccionar el tipo de asignación, la interfaz que ofrecen los montones 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 del 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 dma_heap_buffer_alloc() API.

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 del kernel que usan DMA-BUFs

No se requieren cambios para los controladores que solo importan DMA-BUFs, 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.

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

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

Los clientes deben inicializar un BufferAllocator objeto 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> se administran internamente con el objeto BufferAllocator.

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 del encabezado y la marca del montón.
  • Reemplaza la API de ion_alloc_fd(), que toma un argumento de máscara y marca de montón, 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 caché.

Tipo de asignación libion libdmabufheap
Asignación almacenada 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é desde el montón del sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

La variante de montón del sistema sin caché está a la espera de la aprobación ascendente, pero ya forma parte de la android12-5.10 rama.

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 o máscara y marcas de montón) para permitir que esas interfaces usen asignaciones basadas en nombres. Este es un ejemplo de asignación basada en nombres.

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 de C.

Implementación de referencia de Gralloc

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

Adiciones obligatorias de ueventd

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

Adiciones obligatorias de sepolicy

Agrega permisos de sepolicy para permitir que un cliente del espacio de usuario acceda a un nuevo montón de DMA-BUF. En este ejemplo de agregar permisos obligatorios , se muestran los permisos de sepolicy creados para que varios clientes accedan al montón del sistema DMA-BUF.

Accede a los montones del proveedor desde el código del framework

Para garantizar el cumplimiento de Treble, el código del framework solo puede asignar desde categorías preaprobadas de montones del proveedor.

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

  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 DMA-BUF predeterminado.

  • CONFIG_DMABUF_HEAPS_SYSTEM está desactivado en gki_defconfig para permitir que sea un módulo del 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 (mmapped) desde el espacio de 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 los dispositivos completamente coherentes con la E/S.

Montones para asignar desde la memoria protegida

Las implementaciones de montón seguro deben ser específicas del proveedor, ya que el kernel común de Android no admite una implementación de montón seguro 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 existen los montones, las pruebas de VTS garantizan que se puedan realizar asignaciones desde ellos.
  • Los componentes del framework tienen acceso a estos montones para que puedan habilitar el uso de montones a través de las HAL de Codec2 o las HAL no vinculadas y del mismo proceso. Sin embargo, las funciones genéricas del framework de Android no pueden depender de ellas debido a la variabilidad en los detalles de implementación. Si se agrega una implementación de montón seguro genérica 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 Codec 2 para montones de DMA-BUF

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

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

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

Para facilitar la transición de ION a los montones de DMA-BUF, libdmabufheap permite cambiar un montón a la vez. En los siguientes pasos, se muestra un flujo de trabajo sugerido para 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, debido a que 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 comportamiento 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 de ueventd para los nuevos montones de DMA-BUF my_heap y my_heap_special. 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, modifica sus archivos makefile para vincularlos a libdmabufheap. Durante la inicialización del cliente, crea una instancia de un BufferAllocator objeto y usa la MapNameToIonHeap() API para asignar la <ION heap name/mask, flag> combinación a nombres de montón de 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 montón de DMA-BUF equivalente nombres configurando el parámetro de nombre de montón de ION como vacío.

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

Tipo de asignación libion libdmabufheap
Asignación desde my_heap con la marca ION_FLAG_MY_FLAG sin configurar ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Asignación desde my_heap con la marca ION_FLAG_MY_FLAG configurada 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 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 montones de DMA-BUF. El cliente ahora está completamente equipado para asignar 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 del cliente no necesita admitir la actualización de dispositivos (cuyos kernels solo pueden admitir montones de ION), también puedes quitar las invocaciones de MapNameToIonHeap().