Переход от ION к кучам DMA-BUF (только ядро ​​5.4)

В 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 Common Kernel отключила 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 возвращает указатель на экземпляр структуры 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() , который принимает маску кучи и аргумент флага, на API 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 гарантируют, что из них можно сделать выделения.
  • Компонентам фреймворка предоставляется доступ к этим кучам, чтобы они могли использовать кучи через HAL-базы Codec2/непривязанные к памяти HAL в том же процессе. Однако общие функции фреймворка Android не могут зависеть от них из-за вариативности деталей их реализации. Если в будущем в общее ядро ​​Android будет добавлена ​​реализация универсальной безопасной кучи, она должна использовать другой ABI, чтобы избежать конфликтов с обновляемыми устройствами.

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

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

Интерфейс хранилища компонентов, позволяющий указывать параметры кучи из 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 для новых куч DMA-BUF my_heap и my_heap_special . На этом этапе кучи видны как /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() .