Przejście z użytkowania stosu ION na stos DMA-BUF (tylko jądro 5.4)

W Androidzie 12 GKI 2.0 zastępuje alokator ION za pomocą stosów DMA-BUF z tych powodów:

  • Bezpieczeństwo: ponieważ każda pula DMA-BUF jest oddzielnym urządzeniem znakowym, dostęp do każdej z nich można kontrolować osobno za pomocą sepolicy. Nie było to możliwe w przypadku ION, ponieważ alokacja z dowolnego stosu wymagała tylko dostępu do urządzenia /dev/ion.
  • Stabilność ABI: w odróżnieniu od ION interfejs IOCTL w ramach platformy sterowania sterowania zasobami DMA-BUF jest stabilny pod względem ABI, ponieważ jest utrzymywany w górnym jądrze systemu Linux.
  • Standaryzacja: framework stosów DMA-BUF oferuje dobrze zdefiniowane UAPI. ION zezwalał na niestandardowe flagi i identyfikatory stosu, co uniemożliwiało opracowanie wspólnego frameworku testowego, ponieważ implementacja ION na każdym urządzeniu mogła się różnić.

Gałąź android12-5.10 jądra Android Common Kernel została wyłączona CONFIG_ION 1 marca 2021 r..

Tło

Poniżej znajduje się krótkie porównanie stosów ION i DMA-BUF.

Podobieństwa między architekturą stosów ION i DMA-BUF

  • Platformy ION i DMA-BUF to platformy eksportujące DMA-BUF oparte na stosie.
  • Oba te mechanizmy pozwalają każdej stercie zdefiniować własne funkcje alokacji i DMA-BUF.
  • Wydajność przydzielania jest podobna, ponieważ oba schematy wymagają do przydzielenia jednego polecenia IOCTL.

Różnice między stertą ION a stertą DMA-BUF

sterty ION Kopie zapasowe DMA-BUF
Wszystkie alokacje ION są wykonywane za pomocą /dev/ion. Każdy stos DMA-BUF to urządzenie znakowe obecne w miejscu /dev/dma_heap/<heap_name>.
ION obsługuje prywatne flagi stosu. Kopie zapasowe DMA-BUF nie obsługują flag prywatności kopii zapasowej. Każdy inny rodzaj alokacji jest natomiast wykonywany z użyciem innego stosu. Na przykład wersje stosu systemowego z pamięci podręcznej i bez pamięci podręcznej to osobne stosy znajdujące się w miejscach /dev/dma_heap/system/dev/dma_heap/system_uncached.
Do alokacji musisz podać identyfikator lub maskę sterty oraz flagi. Nazwa stosu jest używana do alokacji.

W następnych sekcjach wymieniono komponenty, które korzystają z ION, oraz opisano, jak przełączyć je na ramkę stosu DMA-BUF.

Przejście z sterowników jądra z ION na stosy DMA-BUF

Sterowniki jądra implementujące stosy ION

Zarówno stosy ION, jak i DMA-BUF umożliwiają każdemu stosowi implementowanie własnych alokacji i operacji DMA-BUF. Możesz więc przełączyć się z implementacji stosu ION na implementację stosu DMA-BUF, używając innego zestawu interfejsów API do zarejestrowania stosu. Ta tabela przedstawia interfejsy API rejestracji stosu ION i ich odpowiedniki w interfejsach API stosu DMA-BUF.

sterty ION Kopie zapasowe 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);

Kopie zapasowe DMA-BUF nie obsługują flag prywatności kopii zapasowej. Dlatego każdy wariant stosu musi być rejestrowany osobno za pomocą interfejsu API dma_heap_add(). Aby ułatwić udostępnianie kodu, zalecamy zarejestrowanie wszystkich wersji tego samego stosu w ramach tego samego sterownika. Ten przykład dma-buf: system_heap pokazuje implementację wersji pamięci podręcznej i niezapisanej pamięci podręcznej systemu.

Aby utworzyć stos DMA-BUF od podstaw, użyj tego dma-buf: heaps: example template.

Sterowniki jądra przydzielają bezpośrednio z kupy ION.

Framework stosów DMA-BUF udostępnia też interfejs alokacji dla klientów w rdzeniu. Zamiast określać maskę i flagi stosu, aby wybrać typ alokacji, interfejs oferowany przez stosy DMA-BUF przyjmuje jako dane wejściowe nazwę stosu.

Poniżej przedstawiono interfejs API alokacji ION w rdzeniu i odpowiadające mu interfejsy API alokacji pamięci DMA-BUF. Sterowniki jądra mogą używać interfejsu API dma_heap_find() do sprawdzania istnienia stosu. Interfejs API zwraca wskaźnik do instancji struktury dma_heap, którą można następnie przekazać jako argument do interfejsu API dma_heap_buffer_alloc().

sterty ION Kopie zapasowe 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)

Sterowniki jądra korzystające z DMA-BUF

W przypadku sterowników, które importują tylko bufory DMA-BUF, nie trzeba wprowadzać żadnych zmian, ponieważ bufor przypisany do stosu ION zachowuje się dokładnie tak samo jak bufor przypisany do odpowiednich buforów DMA-BUF.

Przejście z klientów ION w przestrzeni użytkownika do stosów DMA-BUF

Aby ułatwić przejście klientom ION w przestrzeni użytkownika, udostępniono bibliotekę abstrakcji libdmabufheap. libdmabufheap obsługuje alokację w strukturach DMA-BUF i ION. Najpierw sprawdza, czy istnieje stos DMA-BUF o określonej nazwie. Jeśli nie, przechodzi do równoważnego stosu ION (jeśli istnieje).

Klienci powinni zainicjować obiekt BufferAllocator podczas inicjalizacji zamiast otwierać /dev/ion using ion_open(). Dzieje się tak, ponieważ opisami plików utworzonymi przez otwieranie (/dev/ion) i zamykanie (/dev/dma_heap/<heap_name>) zarządza wewnętrznie obiekt BufferAllocator.

Aby przejść z poziomu libion na poziom libdmabufheap, zmień zachowanie klientów w ten sposób:

  • Zamiast identyfikatora głowy/maski i flagi stosu śledź nazwę stosu, która ma być używana do alokacji.
  • Zastąp interfejs API ion_alloc_fd(), który przyjmuje argumenty maska stosu i flaga, interfejsem API BufferAllocator::Alloc(), który przyjmuje zamiast tego nazwę stosu.

Tabela ilustruje te zmiany, pokazując, jak libionlibdmabufheap wpływają na przydzielanie niewykorzystanego miejsca w systemowym stosie pamięci.

Typ przydziału libion libdmabufheap
Alokacja z bufora systemowego w pamięci podręcznej ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Niebuforowana alokacja z stosu systemowego ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

Niezcacheowany wariant stosu systemowego czeka na zatwierdzenie w głównym gałęzi, ale jest już częścią gałęzi android12-5.10.

Aby obsługiwać uaktualnianie urządzeń, interfejs API MapNameToIonHeap() umożliwia zmapowanie nazwy stosu na parametry stosu ION (nazwa stosu lub maska i flagi), aby te interfejsy mogły używać alokacji na podstawie nazwy. Oto przykład przydziału na podstawie nazwy.

Dostępna jest dokumentacja każdego interfejsu API udostępnianego przez libdmabufheap. Biblioteka udostępnia też plik nagłówka do użycia przez klientów C.

Referencyjna implementacja Gralloc

Implementacja gralloc w Hikey960 korzysta z libdmabufheap, więc możesz ją wykorzystać jako implementację referencyjną.

Wymagane dodane ustawienia

W przypadku nowych stert DMA-BUF utworzonych na potrzeby konkretnego urządzenia dodaj nowy wpis do pliku ueventd.rc urządzenia. Ten przykład konfiguracji obsługi stosu DMA-BUF pokazuje, jak to zrobić w przypadku stosu systemowego DMA-BUF.

Wymagane dodatki do zasad bezpieczeństwa

Dodaj uprawnienia sepolicy, aby umożliwić klientowi w przestrzeni użytkownika dostęp do nowego stosu DMA-BUF. Ten przykład dodawania wymaganych uprawnień pokazuje uprawnienia sepolicy utworzone dla różnych klientów w celu uzyskania dostępu do stosu systemu DMA-BUF.

Dostęp do stosów dostawcy z poziomu kodu frameworku

Aby zapewnić zgodność z Treble, kod frameworku może przydzielać zasoby tylko z zaaprobowanych kategorii stosów dostawcy.

Na podstawie opinii otrzymanych od partnerów zidentyfikowaliśmy 2 kategorie stosów dostawców, do których należy uzyskać dostęp z kodu frameworku:

  1. stosy oparte na stosie systemowym z optymalizacjami wydajności dla danego urządzenia lub układu SoC;
  2. Stosy do przydzielenia z chronionej pamięci.

stosy oparte na stosie systemowym z optymalizacjami wydajności dla konkretnego urządzenia lub układu SoC;

Aby obsługiwać ten przypadek użycia, można zastąpić implementację stosu domyślnego systemu stosu DMA-BUF.

  • W komponencie gki_defconfig funkcja CONFIG_DMABUF_HEAPS_SYSTEM jest wyłączona, aby był on modułem dostawcy.
  • Testy zgodności VTS sprawdzają, czy stos istnieje w miejscu /dev/dma_heap/system. Testy sprawdzają też, czy można przydzielić pamięć z kopca i czy zwracany deskryptor pliku (fd) może być mapowany w pamięci z przestrzeni użytkownika.

Powyższe punkty dotyczą również niewykorzystanego wariantu stosu systemowego, chociaż jego istnienie nie jest wymagane w przypadku urządzeń o pełnej spójności operacji wejścia/wyjścia.

stosy do przydzielenia z zabezpieczonej pamięci;

Implementacje bezpiecznego stosu muszą być specyficzne dla dostawcy, ponieważ wspólny jądro Androida nie obsługuje ogólnej implementacji bezpiecznego stosu.

  • Zarejestruj implementacje specyficzne dla dostawcy jako /dev/dma_heap/system-secure<vendor-suffix>.
  • Implementacje stosu są opcjonalne.
  • Jeśli stosy istnieją, testy VTS sprawdzają, czy można z nich dokonywać przydziałów.
  • Komponenty frameworku mają dostęp do tych stosów, aby umożliwić ich użycie za pomocą interfejsu Codec2 HAL lub interfejsów HAL w ramach tego samego procesu, ale nie w ramach Bindera. Ogólne funkcje Android Framework nie mogą jednak być od nich zależne ze względu na zmienność szczegółów ich implementacji. Jeśli w przyszłości do jądra Android Common Kernel zostanie dodana ogólna bezpieczna sterta, musi ona używać innego ABI, aby uniknąć konfliktów z urządzeniami w trakcie aktualizacji.

Przydzielacz 2 kodery dla stosów DMA-BUF

W AOSP jest dostępny algorytm alokacji codec2 dla interfejsu DMA-BUF heaps.

Interfejs repozytorium komponentów, który umożliwia określenie parametrów stosu z poziomu interfejsu C2 HAL, jest dostępny w ramach modułu alokacji pamięci podręcznej C2 DMA-BUF.

Przykładowy przepływ danych w strukturze ION

Aby płynnie przejść z kupy ION na kupę DMA-BUF, libdmabufheap umożliwia przełączanie jednej kupy naraz. Poniższe kroki pokazują sugerowany proces przejścia na niestarszą pamięć podręczną ION o nazwie my_heap, która obsługuje jeden flagę, ION_FLAG_MY_FLAG.

Krok 1. Utwórz odpowiedniki stosu ION w ramach interfejsu DMA-BUF. W tym przykładzie, ponieważ stos ION my_heap obsługuje flagę ION_FLAG_MY_FLAG, rejestrujemy 2 stosy DMA-BUF:

  • Zachowanie my_heap dokładnie odpowiada działaniu stosu ION z wyłączoną flagą ION_FLAG_MY_FLAG.
  • Zachowanie my_heap_special jest identyczne ze zbiorem ION z włączoną flagą ION_FLAG_MY_FLAG.

Krok 2. Wprowadź zmiany w plikach my_heapmy_heap_special DMA-BUF. W tym momencie stosy są widoczne jako /dev/dma_heap/my_heap/dev/dma_heap/my_heap_special z odpowiednimi uprawnieniami.

Krok 3. W przypadku klientów, którzy przydzielają z poziomu my_heap, zmodyfikuj ich pliki make, aby zawierały link do libdmabufheap. Podczas inicjowania klienta utwórz instancję obiektu BufferAllocator i za pomocą interfejsu API MapNameToIonHeap() mapuj kombinację <ION heap name/mask, flag> na równe nazwy stosu DMA-BUF.

Przykład:

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

Zamiast używać interfejsu API MapNameToIonHeap() z parametrami name i flag, możesz utworzyć mapowanie z <ION heap mask, flag> na odpowiadające nazwy stosu DMA-BUF, ustawiając parametr nazwy stosu ION na pusty.

Krok 4. Zastąp wywołania ion_alloc_fd() wywołaniem BufferAllocator::Alloc(), używając odpowiedniej nazwy stosu.

Typ przydziału libion libdmabufheap
Przydział z my_heap z nieustawioną flagą ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Przydział z my_heap z ustawioną flagą 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)

W tym momencie klient działa, ale nadal przydziela pamięć z kupy ION, ponieważ nie ma wymaganych uprawnień sepolicy do otwierania kupy DMA-BUF.

Krok 5. Utwórz uprawnienia sepolicy wymagane przez klienta do uzyskania dostępu do nowych stosów DMA-BUF. Klient jest teraz w pełni gotowy do przydzielenia pamięci z nowej puli DMA-BUF.

Krok 6. Sprawdź, czy alokacje są wykonywane z nowej puli DMA-BUF, analizując logcat.

Krok 7. Wyłącz stos ION my_heap w rdzeniu. Jeśli kod klienta nie musi obsługiwać urządzeń z aktualizacją (których jądra obsługują tylko stosy ION), możesz też usunąć wywołania MapNameToIonHeap().