Переход с ION на кучи DMA-BUF

В Android 12 GKI 2.0 заменяет распределитель ION кучами DMA-BUF по следующим причинам:

  • Безопасность: поскольку каждая куча DMA-BUF является отдельным символьным устройством, доступ к каждой куче можно контролировать отдельно с помощью sepolicy. Это было невозможно с ION, потому что выделение из любой кучи требовало доступа только к устройству /dev/ion .
  • Стабильность ABI: в отличие от ION, интерфейс IOCTL инфраструктуры кучи DMA-BUF гарантированно будет стабильным ABI, поскольку он поддерживается в вышестоящем ядре Linux.
  • Стандартизация: структура куч DMA-BUF предлагает четко определенный UAPI. ION допускал пользовательские флаги и идентификаторы кучи, что мешало разработке общей среды тестирования, поскольку реализация ION для каждого устройства могла вести себя по-разному.

android12-5.10 общего ядра Android отключила CONFIG_ION 1 марта 2021 года .

Задний план

Ниже приводится краткое сравнение куч ION и DMA-BUF.

Сходства между инфраструктурой куч ION и DMA-BUF

  • Среды кучи ION и DMA-BUF являются экспортерами DMA-BUF на основе кучи.
  • Они оба позволяют каждой куче определять свой собственный распределитель и операции DMA-BUF.
  • Производительность распределения аналогична, поскольку обеим схемам для выделения требуется один IOCTL.

Различия между инфраструктурой кучи ION и DMA-BUF

ИОННЫЕ отвалы кучи DMA-BUF
Все распределения ION выполняются с помощью /dev/ion . Каждая куча DMA-BUF — это символьное устройство, присутствующее в /dev/dma_heap/<heap_name> .
ION поддерживает приватные флаги кучи. Кучи DMA-BUF не поддерживают частные флаги кучи. Вместо этого каждый другой вид распределения выполняется из другой кучи. Например, кешированные и некэшированные варианты системной кучи — это отдельные кучи, расположенные в /dev/dma_heap/system и /dev/dma_heap/system_uncached .
Для выделения необходимо указать идентификатор/маску кучи и флаги. Имя кучи используется для распределения.

В следующих разделах перечислены компоненты, имеющие дело с ION, и описано, как переключить их на структуру куч DMA-BUF.

Переход драйверов ядра с ION на кучи DMA-BUF

Драйверы ядра, реализующие кучи ION

Кучи ION и DMA-BUF позволяют каждой куче реализовать свои собственные распределители и операции DMA-BUF. Таким образом, вы можете переключиться с реализации кучи ION на реализацию кучи DMA-BUF, используя другой набор API для регистрации кучи. В этой таблице показаны API-интерфейсы регистрации кучи ION и эквивалентные им API-интерфейсы кучи DMA-BUF.

ИОННЫЕ отвалы кучи 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);

Кучи DMA-BUF не поддерживают частные флаги кучи. Таким образом, каждый вариант кучи должен быть зарегистрирован отдельно с помощью API dma_heap_add() . Для облегчения совместного использования кода рекомендуется зарегистрировать все варианты одной и той же кучи в одном драйвере. В этом примере dma-buf: system_heap показана реализация кэшированного и некэшированного вариантов системной кучи.

Используйте этот пример шаблона dma-buf: heaps: для создания кучи DMA-BUF с нуля.

Драйверы ядра, непосредственно выделяющие из кучи ION

Платформа куч DMA-BUF также предлагает интерфейс распределения для клиентов в ядре. Вместо указания маски кучи и флагов для выбора типа выделения интерфейс, предлагаемый кучами DMA-BUF, принимает в качестве входных данных имя кучи.

Ниже показан API-интерфейс выделения ION в ядре и эквивалентные ему API-интерфейсы выделения кучи DMA-BUF. Драйверы ядра могут использовать API dma_heap_find() для запроса существования кучи. API возвращает указатель на экземпляр struct dma_heap , который затем может быть передан в качестве аргумента API dma_heap_buffer_alloc() .

ИОННЫЕ отвалы кучи 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)

Драйверы ядра, использующие DMA-BUF

Никаких изменений не требуется для драйверов, которые импортируют только DMA-BUF, потому что буфер, выделенный из кучи ION, ведет себя точно так же, как буфер, выделенный из эквивалентной кучи DMA-BUF.

Перевод клиентов пользовательского пространства ION на кучи DMA-BUF

Чтобы упростить переход для клиентов ION в пользовательском пространстве, доступна библиотека абстракций под названием libdmabufheap . libdmabufheap поддерживает размещение в кучах DMA-BUF и ION. Сначала он проверяет, существует ли куча DMA-BUF с указанным именем, и если нет, возвращается к эквивалентной куче ION, если она существует.

Клиенты должны инициализировать объект BufferAllocator во время своей инициализации вместо открытия /dev/ion using ion_open() . Это связано с тем, что файловые дескрипторы, созданные путем открытия /dev/ion и /dev/dma_heap/<heap_name> , управляются внутри объекта BufferAllocator .

Чтобы переключиться с libion на libdmabufheap , измените поведение клиентов следующим образом:

  • Следите за именем кучи, используемым для выделения, вместо идентификатора/маски головки и флага кучи.
  • Замените API ion_alloc_fd() , который принимает маску кучи и аргумент флага, на BufferAllocator::Alloc() , который вместо этого принимает имя кучи.

Эта таблица иллюстрирует эти изменения, показывая, как libion и libdmabufheap выполняют выделение некэшируемой системной кучи.

Тип распределения либион libdmabufheap
Кэшированное выделение из системной кучи ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Некэшированное выделение из системной кучи ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

Некэшированный вариант системной кучи ожидает утверждения в восходящем потоке, но уже является частью ветки android12-5.10 .

Для поддержки обновления устройств API MapNameToIonHeap() позволяет сопоставлять имя кучи с параметрами кучи ION (имя/маска кучи и флаги), чтобы позволить этим интерфейсам также использовать выделения на основе имени. Вот пример выделения на основе имени .

Доступна документация для каждого API, предоставляемого libdmabufheap . Библиотека также предоставляет заголовочный файл для использования C-клиентами.

Эталонная реализация Gralloc

Реализация Gralloc Hikey960 использует libdmabufheap , поэтому вы можете использовать ее в качестве эталонной реализации .

Необходимые дополнения ueventd

Для любых новых созданных куч DMA-BUF для конкретных устройств добавьте новую запись в файл ueventd.rc устройства. Этот пример настройки ueventd для поддержки кучи DMA-BUF демонстрирует, как это делается для системной кучи DMA-BUF.

Необходимые дополнения sepolicy

Добавьте разрешения sepolicy, чтобы позволить клиенту пользовательского пространства получить доступ к новой куче DMA-BUF. В этом примере добавления необходимых разрешений показаны разрешения sepolicy, созданные для различных клиентов для доступа к системной куче DMA-BUF.

Доступ к кучам поставщиков из кода фреймворка

Чтобы обеспечить соответствие Treble, код платформы может выделять ресурсы только из заранее утвержденных категорий кучи поставщиков.

Основываясь на отзывах, полученных от партнеров, Google определил две категории кучи поставщиков, к которым необходимо получить доступ из кода фреймворка:

  1. Кучи, основанные на системной куче с оптимизацией производительности для конкретных устройств или SoC.
  2. Кучи для выделения из защищенной памяти.

Кучи на основе системной кучи с оптимизацией производительности для конкретных устройств или SoC

Для поддержки этого варианта использования можно переопределить реализацию кучи системы кучи DMA-BUF по умолчанию.

  • CONFIG_DMABUF_HEAPS_SYSTEM отключен в gki_defconfig , чтобы позволить ему быть модулем поставщика.
  • Тесты соответствия VTS гарантируют, что куча существует в /dev/dma_heap/system . Тесты также проверяют, что куча может быть выделена, и что возвращаемый файловый дескриптор ( fd ) может быть отображен в памяти (mmapped) из пользовательского пространства.

Предыдущие пункты также верны для некэшируемого варианта системной кучи, хотя его существование не является обязательным для полностью когерентных устройств ввода-вывода.

Кучи для выделения из защищенной памяти

Реализации безопасной кучи должны зависеть от поставщика, поскольку общее ядро ​​Android не поддерживает общую реализацию безопасной кучи.

  • Зарегистрируйте свои реализации для конкретного поставщика как /dev/dma_heap/system-secure<vendor-suffix> .
  • Эти реализации кучи являются необязательными.
  • Если кучи существуют, тесты VTS гарантируют, что из них могут быть выделены ресурсы.
  • Компонентам платформы предоставляется доступ к этим кучам, чтобы они могли использовать кучи через Codec2 HAL/несвязанные HAL того же процесса. Однако общие функции платформы Android не могут зависеть от них из-за различий в деталях их реализации. Если в будущем общая реализация защищенной кучи будет добавлена ​​в общее ядро ​​Android, она должна использовать другой ABI, чтобы избежать конфликтов при обновлении устройств.

Распределитель кодека 2 для куч DMA-BUF

Распределитель codec2 для интерфейса куч DMA-BUF доступен в AOSP.

Интерфейс хранилища компонентов, который позволяет указывать параметры кучи из C2 HAL, доступен с распределителем кучи C2 DMA-BUF.

Пример потока перехода для кучи ION

Чтобы сгладить переход от куч ION к кучам DMA-BUF, libdmabufheap позволяет переключать одну кучу за раз. Следующие шаги демонстрируют предлагаемый рабочий процесс для перехода неустаревшей кучи ION с именем my_heap , который поддерживает один флаг, ION_FLAG_MY_FLAG .

Шаг 1: Создайте эквиваленты кучи ION в среде DMA-BUF. В этом примере, поскольку куча ION my_heap поддерживает флаг ION_FLAG_MY_FLAG , мы регистрируем две кучи DMA-BUF:

  • Поведение my_heap точно соответствует поведению кучи ION с отключенным флагом ION_FLAG_MY_FLAG .
  • Поведение my_heap_special точно соответствует поведению кучи ION с включенным флагом ION_FLAG_MY_FLAG .

Шаг 2: Создайте изменения ueventd для новых my_heap и my_heap_special DMA-BUF. На данный момент кучи видны как /dev/dma_heap/my_heap и /dev/dma_heap/my_heap_special с предполагаемыми разрешениями.

Шаг 3: Для клиентов, которые выделяют ресурсы из my_heap , измените свои make-файлы, чтобы они ссылались на libdmabufheap . Во время инициализации клиента создайте экземпляр объекта BufferAllocator и используйте API MapNameToIonHeap() для сопоставления комбинации <ION heap name/mask, flag> с эквивалентными именами кучи DMA-BUF.

Например:

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

Вместо использования API MapNameToIonHeap() с параметрами имени и флага можно создать сопоставление из <ION heap mask, flag> в эквивалентные имена кучи DMA-BUF , установив для параметра имени кучи ION значение пустое.

Шаг 4: Замените ion_alloc_fd() на BufferAllocator::Alloc() , используя соответствующее имя кучи.

Тип распределения либион libdmabufheap
Распределение из my_heap со снятым флагом ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
Выделение из my_heap с установленным флагом ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

На этом этапе клиент работает, но все еще выделяет память из кучи ION, поскольку у него нет необходимых разрешений sepolicy для открытия кучи DMA-BUF.

Шаг 5: Создайте разрешения sepolicy, необходимые клиенту для доступа к новым кучам DMA-BUF. Теперь клиент полностью готов к выделению памяти из новой кучи DMA-BUF.

Шаг 6: Убедитесь, что выделения происходят из новой кучи DMA-BUF, изучив logcat .

Шаг 7: Отключите кучу ION my_heap в ядре. Если клиентский код не должен поддерживать обновление устройств (ядра которых могут поддерживать только кучи ION), вы также можете удалить MapNameToIonHeap() .