Transição de heaps ION para DMA-BUF (somente kernel 5.4)

No Android 12, o GKI 2.0 substitui o alocador ION por heaps DMA-BUF pelos seguintes motivos:

  • Segurança: como cada heap DMA-BUF é um dispositivo de caractere separado, o acesso a cada heap pode ser controlado separadamente com sepolicy. Isso não era possível com o ION, porque a alocação de qualquer heap só exigia acesso ao dispositivo /dev/ion.
  • Estabilidade da ABI: ao contrário do ION, a interface IOCTL do framework de heaps DMA-BUF é estável na ABI porque é mantida no kernel do Linux.
  • Padronização: o framework de heaps DMA-BUF oferece uma UAPI bem definida. O ION permitia flags e IDs de heap personalizados que impediam o desenvolvimento de um framework de teste comum, porque a implementação do ION de cada dispositivo podia se comportar de maneira diferente.

A ramificação android12-5.10 do kernel comum do Android desativou CONFIG_ION em 1º de março de 2021.

Contexto

Confira a seguir uma breve comparação entre o ION e os heaps DMA-BUF.

Semelhanças entre o ION e o framework de heaps DMA-BUF

  • Os frameworks de heaps ION e DMA-BUF são exportadores de DMA-BUF baseados em heap.
  • Ambos permitem que cada heap defina o próprio alocador e as operações DMA-BUF.
  • O desempenho da alocação é semelhante porque os dois esquemas precisam de um único IOCTL para alocação.

Diferenças entre o ION e o framework de heaps DMA-BUF

Heaps ION Heaps DMA-BUF
Todas as alocações de ION são feitas com /dev/ion. Cada heap DMA-BUF é um dispositivo de caractere presente em /dev/dma_heap/<heap_name>.
O ION oferece suporte a flags particulares de heap. Os heaps DMA-BUF não oferecem suporte a flags particulares de heap. Cada tipo diferente de alocação é feito de um heap diferente. Por exemplo, as variantes de heap do sistema armazenadas em cache e não armazenadas em cache são heaps separados localizados em /dev/dma_heap/system e /dev/dma_heap/system_uncached.
O ID/máscara e as flags de heap precisam ser especificados para alocação. O nome do heap é usado para alocação.

As seções a seguir listam os componentes que lidam com o ION e descrevem como alterná-los para o framework de heaps DMA-BUF.

Fazer a transição dos drivers de kernel do ION para heaps DMA-BUF

Drivers de kernel que implementam heaps ION

Os heaps ION e DMA-BUF permitem que cada heap implemente os próprios alocadores e operações DMA-BUF. Portanto, é possível alternar de uma implementação de heap ION para uma implementação de heap DMA-BUF usando um conjunto diferente de APIs para registrar o heap. Esta tabela mostra as APIs de registro de heap ION e as APIs de heap DMA-BUF equivalentes.

Heaps ION Heaps 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);

Os heaps DMA-BUF não oferecem suporte a flags particulares de heap. Portanto, cada variante do heap precisa ser registrada individualmente usando a dma_heap_add() API. Para facilitar o compartilhamento de código, recomendamos registrar todas as variantes do mesmo heap no mesmo driver. Este exemplo dma-buf: system_heap mostra a implementação das variantes armazenadas em cache e não armazenadas em cache do heap do sistema.

Use este modelo de exemplo dma-buf: heaps: para criar um heap DMA-BUF do zero.

Drivers de kernel que alocam diretamente de heaps ION

O framework de heaps DMA-BUF também oferece uma interface de alocação para clientes no kernel. Em vez de especificar a máscara e as flags de heap para selecionar o tipo de alocação, a interface oferecida pelos heaps DMA-BUF usa um nome de heap como entrada.

A seguir, mostramos a API de alocação ION no kernel e as APIs de alocação de heap DMA-BUF equivalentes. Os drivers de kernel podem usar a API dma_heap_find() para consultar a existência de um heap. A API retorna um ponteiro para uma instância de struct dma_heap, que pode ser transmitida como um argumento para a dma_heap_buffer_alloc() API.

Heaps ION Heaps 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)

Drivers de kernel que usam DMA-BUFs

Nenhuma mudança é necessária para drivers que importam apenas DMA-BUFs, porque um buffer alocado de um heap ION se comporta exatamente da mesma forma que um buffer alocado de um heap DMA-BUF equivalente.

Fazer a transição dos clientes de espaço do usuário do ION para heaps DMA-BUF

Para facilitar a transição para clientes de espaço do usuário do ION, uma abstração biblioteca chamada libdmabufheap está disponível. A libdmabufheap oferece suporte à alocação em heaps DMA-BUF e heaps ION. Primeiro, ela verifica se um heap DMA-BUF do nome especificado existe e, se não, volta para um heap ION equivalente, se houver.

Os clientes precisam inicializar um BufferAllocator objeto durante a inicialização em vez de abrir /dev/ion using ion_open(). Isso ocorre porque os descritores de arquivo criados pela abertura /dev/ion e /dev/dma_heap/<heap_name> são gerenciados internamente pelo BufferAllocator objeto.

Para alternar de libion para libdmabufheap, modifique o comportamento dos clientes da seguinte maneira:

  • Acompanhe o nome do heap a ser usado para alocação, em vez do ID/máscara e da flag do heap.
  • Substitua a API ion_alloc_fd(), que usa uma máscara de heap e um argumento de flag, pela API BufferAllocator::Alloc(), que usa um nome de heap.

Esta tabela ilustra essas mudanças mostrando como libion e libdmabufheap fazem uma alocação de heap do sistema não armazenada em cache.

Tipo de alocação libion libdmabufheap
Alocação armazenada em cache do heap do sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Alocação não armazenada em cache do heap do sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

A variante de heap do sistema não armazenada em cache está aguardando aprovação upstream, mas já faz parte da android12-5.10 ramificação.

Para oferecer suporte a dispositivos de upgrade, a API MapNameToIonHeap() permite mapear um nome de heap para parâmetros de heap ION (nome ou máscara e flags) para que essas interfaces usem alocações baseadas em nome. Confira um exemplo de alocação baseada em nome.

A documentação de cada API exposta por libdmabufheap está disponível. A biblioteca também expõe um arquivo principal para uso por clientes C.

Implementação de referência do Gralloc

A implementação do Hikey960 gralloc usa libdmabufheap. Portanto, é possível usá-la como uma implementação de referência.

Adições necessárias do ueventd

Para qualquer novo heap DMA-BUF específico do dispositivo criado, adicione uma nova entrada ao arquivo ueventd.rc do dispositivo. Este exemplo de configuração do ueventd para oferecer suporte a heaps DMA-BUF demonstra como isso é feito para o heap do sistema DMA-BUF.

Adições necessárias do sepolicy

Adicione permissões de sepolicy para permitir que um cliente do espaço do usuário acesse um novo heap DMA-BUF. Este exemplo de adição de permissões necessárias mostra as permissões de sepolicy criadas para vários clientes acessarem o heap do sistema DMA-BUF.

Acessar heaps de fornecedores do código do framework

Para garantir a conformidade com o Treble, o código do framework só pode alocar de categorias pré-aprovadas de heaps de fornecedores.

Com base no feedback recebido dos parceiros, o Google identificou duas categorias de heaps de fornecedores que precisam ser acessadas pelo código do framework:

  1. Heaps baseados no heap do sistema com otimizações de desempenho específicas do dispositivo ou do SoC.
  2. Heaps para alocar da memória protegida.

Heaps baseados no heap do sistema com otimizações de desempenho específicas do dispositivo ou do SoC

Para oferecer suporte a esse caso de uso, a implementação de heap do sistema de heap DMA-BUF padrão pode ser substituída.

  • CONFIG_DMABUF_HEAPS_SYSTEM está desativado em gki_defconfig para que seja um módulo do fornecedor.
  • Os testes de conformidade do VTS garantem que o heap exista em /dev/dma_heap/system. Os testes também verificam se o heap pode ser alocado e se o descritor do arquivo retornado (fd) pode ser mapeado na memória (mmapped) do espaço do usuário.

Os pontos anteriores também são verdadeiros para a variante não armazenada em cache do heap do sistema, embora a existência dela não seja obrigatória para dispositivos totalmente coerentes com E/S.

Heaps para alocar da memória protegida

As implementações de heap seguro precisam ser específicas do fornecedor, já que o kernel comum do Android não oferece suporte a uma implementação de heap seguro genérica.

  • Registre suas implementações específicas do fornecedor como /dev/dma_heap/system-secure<vendor-suffix>.
  • Essas implementações de heap são opcionais.
  • Se os heaps existirem, os testes do VTS vão garantir que as alocações possam ser feitas neles.
  • Os componentes do framework recebem acesso a esses heaps para que possam ativar o uso de heaps pelo HAL/não-binderizado do Codec2, HALs do mesmo processo. No entanto, os recursos genéricos do framework do Android não podem depender deles devido à variabilidade nos detalhes da implementação. Se uma implementação de heap seguro genérica for adicionada ao kernel comum do Android no futuro, ela precisará usar uma ABI diferente para evitar conflitos com dispositivos de upgrade.

Alocador do Codec 2 para heaps DMA-BUF

Um alocador codec2 para a interface de heaps DMA-BUF está disponível na AOSP.

A interface do repositório de componentes que permite que os parâmetros de heap sejam especificados no HAL C2 está disponível com o alocador de heap DMA-BUF C2.

Fluxo de transição de amostra para um heap ION

Para facilitar a transição do ION para heaps DMA-BUF, libdmabufheap permite alternar um heap por vez. As etapas a seguir demonstram um fluxo de trabalho sugerido para a transição de um heap ION não legado chamado my_heap que oferece suporte a uma flag, ION_FLAG_MY_FLAG.

Etapa 1:crie equivalentes do heap ION no framework DMA-BUF. Neste exemplo, como o heap ION my_heap oferece suporte a uma flag ION_FLAG_MY_FLAG, registramos dois heaps DMA-BUF:

  • O comportamento de my_heap corresponde exatamente ao comportamento do heap ION com a flag ION_FLAG_MY_FLAG desativada.
  • O comportamento de my_heap_special corresponde exatamente ao comportamento do heap ION com a flag ION_FLAG_MY_FLAG ativada.

Etapa 2: crie as mudanças do ueventd para os novos heaps DMA-BUF my_heap e my_heap_special. Neste ponto, os heaps ficam visíveis como /dev/dma_heap/my_heap e /dev/dma_heap/my_heap_special, com as permissões pretendidas.

Etapa 3:para clientes que alocam de my_heap, modifique os arquivos make para vincular a libdmabufheap. Durante a inicialização do cliente, instancie um BufferAllocator objeto e use a MapNameToIonHeap() API para mapear a <ION heap name/mask, flag> combinação para nomes de heap DMA-BUF equivalentes.

Exemplo:

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

Em vez de usar a API MapNameToIonHeap() com os parâmetros de nome e flag, é possível criar o mapeamento de <ION heap mask, flag> para nomes de heap DMA-BUF equivalentes definindo o parâmetro de nome de heap ION como vazio.

Etapa 4:substitua as invocações ion_alloc_fd() por BufferAllocator::Alloc() usando o nome de heap apropriado.

Tipo de alocação libion libdmabufheap
Alocação de my_heap com a flag ION_FLAG_MY_FLAG não definida ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Alocação de my_heap com a flag ION_FLAG_MY_FLAG definida ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

Neste ponto, o cliente é funcional, mas ainda está alocando do heap ION porque não tem as permissões de sepolicy necessárias para abrir o heap DMA-BUF.

Etapa 5:crie as permissões de sepolicy necessárias para que o cliente acesse os novos heaps DMA-BUF. O cliente agora está totalmente equipado para alocar do novo heap DMA-BUF.

Etapa 6: verifique se as alocações estão ocorrendo no novo heap DMA-BUF examinando o logcat.

Etapa 7:desative o heap ION my_heap no kernel. Se o código do cliente não precisar oferecer suporte a dispositivos de upgrade (cujos kernels podem oferecer suporte apenas a heaps ION), também será possível remover as invocações MapNameToIonHeap().