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

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

  • Безопасность: Поскольку каждая куча DMA-BUF представляет собой отдельное символьное устройство, доступ к каждой куче можно контролировать отдельно с помощью sepolicy. Это было невозможно с ION, поскольку выделение памяти из любой кучи требовало доступа только к устройству /dev/ion .
  • Стабильность ABI: В отличие от ION, интерфейс IOCTL в рамках фреймворка DMA-BUF heaps стабилен по 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 heaps также предлагает интерфейс выделения памяти для клиентов, работающих в ядре. Вместо указания маски кучи и флагов для выбора типа выделения, интерфейс, предлагаемый DMA-BUF heaps, принимает в качестве входных данных имя кучи.

Ниже показан 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 измените поведение клиентов следующим образом:

  • Вместо идентификатора/маски заголовка и флага кучи, отслеживайте имя кучи, используемое для выделения памяти.
  • Замените функцию 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, чтобы разрешить клиенту пользовательского пространства доступ к новой куче DMA-BUF. В этом примере добавления необходимых разрешений показаны разрешения sepolicy, созданные для различных клиентов для доступа к системной куче DMA-BUF.

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

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

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

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

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

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

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

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

Выделение памяти из защищенной области памяти кучей.

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

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

Распределитель памяти Codec 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() с параметрами name и flag, вы можете создать сопоставление из <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() .