ION에서 DMA-BUF 힙으로 전환

Android 12에서는 GKI 2.0이 다음과 같은 이유로 ION 할당자를 DMA-BUF 힙으로 대체합니다.

  • 보안: 각 DMA-BUF 힙은 별도의 문자 기기이므로 각 힙 액세스를 sepolicy를 통해 별도로 제어할 수 있습니다. 이는 ION에서는 불가능했습니다. 힙에서의 할당에 /dev/ion 기기 액세스만 필요했기 때문입니다.
  • ABI 안정성: ION과 달리 DMA-BUF 힙 프레임워크의 IOCTL 인터페이스는 업스트림 Linux 커널에서 유지되므로 ABI 안정성이 보장됩니다.
  • 표준화: DMA-BUF 힙 프레임워크는 잘 정의된 UAPI를 제공합니다. ION은 각 기기의 ION 구현이 다르게 동작할 수 있어 공통 테스트 프레임워크 개발을 어렵게 하는 맞춤 플래그와 힙 ID를 허용했습니다.

Android 공통 커널의 android12-5.10 분기에서는 2021년 3월 1일 CONFIG_ION을 사용 중지했습니다.

배경

다음은 ION 힙과 DMA-BUF 힙을 간략하게 비교한 것입니다.

ION 힙 프레임워크와 DMA-BUF 힙 프레임워크의 유사점

  • ION 힙 프레임워크와 DMA-BUF 힙 프레임워크는 모두 힙 기반 DMA-BUF 내보내기 도구입니다.
  • 둘 다 각 힙이 자체 할당자와 DMA-BUF 작업을 정의하도록 허용합니다.
  • 할당 성능은 유사합니다. 두 체계 모두 할당을 위해 단일 IOCTL이 필요하기 때문입니다.

ION 힙 프레임워크와 DMA-BUF 힙 프레임워크의 차이점

ION 힙 DMA-BUF 힙
모든 ION 할당은 /dev/ion을 통해 실행됩니다. 각 DMA-BUF 힙은 /dev/dma_heap/<heap_name>에 있는 문자 기기입니다.
ION은 힙 비공개 플래그를 지원합니다. DMA-BUF 힙은 힙 비공개 플래그를 지원하지 않습니다. 다른 유형의 각 할당은 대신 다른 힙에서 실행됩니다. 예를 들어 캐시된 시스템 힙 변형과 캐시되지 않은 시스템 힙 변형은 /dev/dma_heap/system/dev/dma_heap/system_uncached에 있는 별도의 힙입니다.
힙 ID/마스크와 플래그는 할당을 위해 지정해야 합니다. 힙 이름은 할당에 사용됩니다.

다음 섹션에서는 ION을 다루는 구성요소를 나열하고 이를 DMA-BUF 힙 프레임워크로 전환하는 방법을 설명합니다.

커널 드라이버를 ION에서 DMA-BUF 힙으로 전환

ION 힙을 구현하는 커널 드라이버

ION 힙과 DMA-BUF 힙은 모두 각 힙에서 자체 할당자와 DMA-BUF 작업을 구현하도록 합니다. 따라서 다른 API 집합을 사용하여 힙을 등록해 ION 힙 구현에서 DMA-BUF 힙 구현으로 전환할 수 있습니다. 다음 표는 ION 힙 등록 API와 이에 상응하는 DMA-BUF 힙 API를 보여줍니다.

ION 힙 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 힙은 힙 비공개 플래그를 지원하지 않습니다. 따라서 힙의 각 변형은 dma_heap_add() API를 사용하여 개별적으로 등록해야 합니다. 코드 공유를 용이하게 하려면 같은 드라이버 내에 같은 힙의 모든 변형을 등록하는 것이 좋습니다. 이 dma-buf: system_heap 예는 캐시된 시스템 힙 변형과 캐시되지 않은 시스템 힙 변형의 구현을 보여줍니다.

dma-buf: heaps: example template을 사용하여 DMA-BUF 힙을 처음부터 만듭니다.

ION 힙에서 직접 할당하는 커널 드라이버

DMA-BUF 힙 프레임워크는 in-kernel 클라이언트를 위한 할당 인터페이스도 제공합니다. 힙 마스크와 플래그를 지정하여 할당 유형을 선택하는 대신 DMA-BUF 힙에서 제공하는 인터페이스는 힙 이름을 입력으로 사용합니다.

다음은 커널 내 ION 할당 API와 이에 상응하는 DMA-BUF 힙 할당 API를 보여줍니다. 커널 드라이버는 dma_heap_find() API를 사용하여 힙의 존재를 쿼리할 수 있습니다. 이 API는 struct dma_heap 인스턴스를 가리키는 포인터를 반환하고 이 인스턴스는 dma_heap_buffer_alloc() API의 인수로 전달될 수 있습니다.

ION 힙 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의 user-space 클라이언트를 DMA-BUF 힙으로 전환

ION의 user-space 클라이언트를 쉽게 전환하려면 libdmabufheap이라는 추상화 라이브러리를 사용하면 됩니다. libdmabufheap은 DMA-BUF 힙과 ION 힙에서의 할당을 지원합니다. 먼저 지정된 이름의 DMA-BUF 힙이 있는지 확인하고 없으면 이에 상응하는 ION 힙(있는 경우)으로 대체합니다.

클라이언트는 /dev/ion using ion_open()을 여는 대신 초기화 중에 BufferAllocator 객체를 초기화해야 합니다. 이는 /dev/ion/dev/dma_heap/<heap_name>을 열어 만들어진 파일 설명자가 내부적으로 BufferAllocator 객체에서 관리되기 때문입니다.

libion에서 libdmabufheap으로 전환하려면 다음과 같이 클라이언트의 동작을 수정합니다.

  • 헤드 ID/마스크와 힙 플래그 대신 할당에 사용할 힙 이름을 추적합니다.
  • 힙 마스크와 플래그 인수를 사용하는 ion_alloc_fd() API를 대신 힙 이름을 사용하는 BufferAllocator::Alloc() API로 대체합니다.

이 표는 libionlibdmabufheap이 캐시되지 않은 시스템 힙 할당을 실행하는 방법을 보여줌으로써 이러한 변경사항을 보여줍니다.

할당 유형 libion 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 분기의 일부입니다.

기기 업그레이드를 지원하기 위해 MapNameToIonHeap() API는 힙 이름을 ION 힙 매개변수(힙 이름/마스크, 플래그)에 매핑하여 이러한 인터페이스가 이름 기반 할당도 사용할 수 있도록 합니다. 다음은 이름 기반 할당 예를 보여줍니다.

libdmabufheap에서 노출된 모든 API 문서가 제공됩니다. 라이브러리도 C 클라이언트가 사용할 헤더 파일을 노출합니다.

참조 Gralloc 구현

Hikey960 gralloc 구현은 libdmabufheap을 사용하므로 참조 구현으로 사용할 수 있습니다.

필요한 ueventd 추가

새로 만들어진 기기별 DMA-BUF 힙의 경우 새 항목을 기기의 ueventd.rc 파일에 추가합니다. DMA-BUF 힙을 지원하는 이 설정 ueventd 예는 DMA-BUF 시스템 힙에서 이를 실행하는 방법을 보여줍니다.

필요한 sepolicy 추가

userspace 클라이언트가 새 DMA-BUF 힙에 액세스할 수 있도록 sepolicy 권한을 추가합니다. 이 필수 권한 추가 예는 다양한 클라이언트가 DMA-BUF 시스템 힙에 액세스하도록 만들어진 sepolicy 권한을 보여줍니다.

프레임워크 코드에서 공급업체 힙에 액세스

Treble 규정 준수를 보장하기 위해 프레임워크 코드는 사전 승인된 공급업체 힙 카테고리에서만 할당할 수 있습니다.

파트너로부터 받은 의견에 기반하여 Google에서는 프레임워크 코드에서 액세스해야 하는 공급업체 힙의 두 카테고리를 확인했습니다.

  1. 기기 또는 SoC 관련 성능 최적화가 적용된 시스템 힙에 기반하는 힙
  2. 보호된 메모리에서 할당할 힙

기기 또는 SoC 관련 성능 최적화가 적용된 시스템 힙에 기반하는 힙

이 사용 사례를 지원하기 위해 기본 DMA-BUF 힙 시스템의 힙 구현이 재정의될 수 있습니다.

  • CONFIG_DMABUF_HEAPS_SYSTEM은 공급업체 모듈이 될 수 있도록 gki_defconfig에서 사용 중지됩니다.
  • VTS 규정 준수 테스트는 힙이 /dev/dma_heap/system에 있는지 확인합니다. 힙을 할당할 수 있고 반환된 파일 설명자(fd)가 사용자 공간에서 메모리 매핑(mmapped)될 수 있는지도 확인합니다.

위의 사항은 캐시되지 않은 시스템 힙 변형에도 적용되지만 완전히 IO에 일관된 기기의 경우 이 존재가 필수는 아닙니다.

보호된 메모리에서 할당할 힙

보안 힙 구현은 공급업체별로 이루어져야 합니다. Android 공통 커널이 일반 보안 힙 구현을 지원하지 않기 때문입니다.

  • 공급업체별 구현을 /dev/dma_heap/system-secure<vendor-suffix>로 등록합니다.
  • 이러한 힙 구현은 선택사항입니다.
  • 힙이 존재하는 경우 VTS 테스트는 힙에서 할당이 이루어질 수 있는지 확인합니다.
  • 프레임워크 구성요소는 이러한 힙 액세스 권한을 제공받으므로 Codec2 HAL/비 바인더화 동일 프로세스 HAL을 통해 힙 사용을 사용 설정할 수 있습니다. 그러나 일반적인 Android 프레임워크 기능은 구현 세부정보의 가변성으로 인해 종속될 수 없습니다. 일반 보안 힙 구현이 향후 Android 공통 커널에 추가된다면 다른 ABI를 사용하여 기기 업그레이드와의 충돌을 방지해야 합니다.

DMA-BUF 힙의 Codec 2 할당자

DMA-BUF 힙 인터페이스를 위한 codec2 할당자가 AOSP에서 제공됩니다.

힙 매개변수가 C2 HAL에서 지정되도록 하는 구성요소 저장소 인터페이스는 C2 DMA-BUF 힙 할당자와 함께 사용할 수 있습니다.

ION 힙의 전환 흐름 샘플

ION에서 DMA-BUF 힙으로의 전환을 원활하게 하기 위해 libdmabufheap에서는 한 번에 힙을 하나씩 전환할 수 있습니다. 다음 단계에서는 ION_FLAG_MY_FLAG라는 플래그 하나를 지원하는 my_heap이라는 비 기존 ION 힙을 전환하는 데 권장되는 워크플로를 보여줍니다.

1단계: DMA-BUF 프레임워크에서 ION 힙에 상응하는 항목을 만듭니다. 이 예에서는 ION 힙 my_heap이 플래그 ION_FLAG_MY_FLAG를 지원하므로 DMA-BUF 힙 두 개를 등록합니다.

  • my_heap 동작은 ION_FLAG_MY_FLAG 플래그가 사용 중지된 ION 힙의 동작과 정확히 일치합니다.
  • my_heap_special 동작은 ION_FLAG_MY_FLAG 플래그가 사용 설정된 ION 힙의 동작과 정확히 일치합니다.

2단계:my_heapmy_heap_special DMA-BUF 힙을 위한 ueventd 변경사항을 만듭니다. 이제 힙이 의도된 권한과 함께 /dev/dma_heap/my_heap/dev/dma_heap/my_heap_special로 표시됩니다.

3단계: my_heap에서 할당하는 클라이언트의 경우 makefile을 수정하여 libdmabufheap에 연결합니다. 클라이언트 초기화 중에 BufferAllocator 객체를 인스턴스화하고 MapNameToIonHeap() API를 사용하여 <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 */ )

이름 및 플래그 매개변수와 함께 MapNameToIonHeap() API를 사용하는 대신 ION 힙 이름 매개변수를 공백으로 설정하여 <ION heap mask, flag>에서 이에 상응하는 DMA-BUF 힙 이름으로의 매핑을 만들 수 있습니다.

4단계: 적절한 힙 이름을 사용하여 ion_alloc_fd() 호출을 BufferAllocator::Alloc()로 바꿉니다.

할당 유형 libion libdmabufheap
ION_FLAG_MY_FLAG 플래그가 설정되지 않은 my_heap에서의 할당 ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
ION_FLAG_MY_FLAG 플래그가 설정된 my_heap에서의 할당 ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

이제 클라이언트가 작동하지만 여전히 ION 힙에서 할당합니다. DMA-BUF 힙을 여는 데 필요한 sepolicy 권한이 없기 때문입니다.

5단계: 클라이언트가 새 DMA-BUF 힙에 액세스하는 데 필요한 sepolicy 권한을 만듭니다. 이제 클라이언트는 새 DMA-BUF 힙에서 할당할 준비를 마쳤습니다.

6단계: logcat을 검사하여 새 DMA-BUF 힙에서 할당이 이루어지는지 확인합니다.

7단계: 커널에서 ION 힙 my_heap을 사용 중지합니다. 클라이언트 코드에서 기기 업그레이드를 지원하지 않아도 된다면(커널이 ION 힙만 지원할 수도 있음) MapNameToIonHeap() 호출을 삭제할 수도 있습니다.