AIDL dla HAL

Android 11 wprowadza możliwość wykorzystania AIDL dla HAL w Androidzie. Umożliwia to implementację części Androida bez HIDL. Przejść warstwy HAL tak, aby korzystały wyłącznie z AIDL, jeśli to możliwe (jeśli nadrzędne warstwy HAL korzystają z HIDL, należy użyć HIDL).

Warstwy HAL korzystające z AIDL do komunikacji między komponentami struktury, takimi jak te w system.img i komponentami sprzętowymi, takimi jak te w vendor.img , muszą używać stabilnego AIDL. Jednakże, aby komunikować się w ramach partycji, na przykład z jednej warstwy HAL do drugiej, nie ma ograniczeń co do mechanizmu IPC.

Motywacja

AIDL istnieje dłużej niż HIDL i jest używany w wielu innych miejscach, na przykład między komponentami platformy Android lub w aplikacjach. Teraz, gdy AIDL obsługuje stabilność, możliwe jest zaimplementowanie całego stosu w jednym środowisku wykonawczym IPC. AIDL ma również lepszy system wersjonowania niż HIDL.

  • Używanie jednego języka IPC oznacza, że ​​trzeba się uczyć tylko jednego, debugować, optymalizować i zabezpieczać.
  • AIDL obsługuje wersjonowanie lokalne dla właścicieli interfejsu:
    • Właściciele mogą dodawać metody na końcu interfejsów lub pola do pakietów. Oznacza to, że łatwiej jest wersjonować kod na przestrzeni lat, a także koszt roczny jest mniejszy (typy można zmieniać na miejscu i nie ma potrzeby instalowania dodatkowych bibliotek dla każdej wersji interfejsu).
    • Interfejsy rozszerzeń można dołączać w czasie wykonywania, a nie w systemie typów, więc nie ma potrzeby ponownego bazowania dalszych rozszerzeń na nowszych wersjach interfejsów.
  • Istniejący interfejs AIDL może być używany bezpośrednio, jeśli jego właściciel zdecyduje się go ustabilizować. Wcześniej cała kopia interfejsu musiała być utworzona w języku HIDL.

Napisanie interfejsu AIDL HAL

Aby interfejs AIDL mógł być używany pomiędzy systemem a dostawcą, interfejs wymaga dwóch zmian:

  • Każda definicja typu musi być opatrzona adnotacją @VintfStability .
  • Deklaracja aidl_interface musi zawierać stability: "vintf", .

Tych zmian może dokonać tylko właściciel interfejsu.

Po wprowadzeniu tych zmian interfejs musi znajdować się w manifeście VINTF , aby działał. Przetestuj to (i powiązane wymagania, takie jak sprawdzenie, czy zwolnione interfejsy są zablokowane) za pomocą testu VTS vts_treble_vintf_vendor_test . Można używać interfejsu @VintfStability bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability w zapleczu NDK, android::Stability::forceDowngradeToLocalStability w zapleczu C++ lub android.os.Binder#forceDowngradeToSystemStability w zapleczu Java na obiekcie segregatora przed jego wysłaniem do innego procesu. Obniżanie poziomu usługi do wersji stabilnej dostawcy nie jest obsługiwane w języku Java, ponieważ wszystkie aplikacje działają w kontekście systemowym.

Dodatkowo, aby uzyskać maksymalną przenośność kodu i uniknąć potencjalnych problemów, takich jak niepotrzebne dodatkowe biblioteki, wyłącz backend CPP.

Należy pamiętać, że użycie backends w poniższym przykładzie kodu jest prawidłowe, ponieważ istnieją trzy backendy (Java, NDK i CPP). Poniższy kod informuje, jak wybrać konkretnie backend CPP, aby go wyłączyć.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

Znajdowanie interfejsów AIDL HAL

Stabilne interfejsy AIDL AOSP dla warstw HAL znajdują się w tych samych katalogach podstawowych, co interfejsy HIDL, w folderach aidl .

  • sprzęt/interfejsy
  • frameworki/sprzęt/interfejsy
  • system/sprzęt/interfejsy

Powinieneś umieścić interfejsy rozszerzeń w innych podkatalogach hardware/interfaces w vendor lub hardware .

Interfejsy rozszerzeń

Android ma zestaw oficjalnych interfejsów AOSP w każdej wersji. Jeśli partnerzy systemu Android chcą dodać funkcjonalność do tych interfejsów, nie powinni ich bezpośrednio zmieniać, ponieważ oznaczałoby to, że ich środowisko wykonawcze systemu Android jest niezgodne ze środowiskiem wykonawczym AOSP systemu Android. W przypadku urządzeń GMS unikanie zmiany tych interfejsów zapewnia również dalsze działanie obrazu GSI.

Rozszerzenia można zarejestrować na dwa różne sposoby:

Jednakże rozszerzenie jest rejestrowane, gdy komponenty specyficzne dla dostawcy (czyli niebędące częścią nadrzędnego AOSP) korzystają z interfejsu, nie ma możliwości wystąpienia konfliktu scalania. Jednakże w przypadku wprowadzenia dalszych modyfikacji w komponentach AOSP znajdujących się wyżej, mogą wystąpić konflikty scalania, w związku z czym zalecane jest zastosowanie następujących strategii:

  • dodatki do interfejsu można przesłać do AOSP w następnej wersji
  • dodatki do interfejsu, które pozwolą na większą elastyczność, bez konfliktów scalania, mogą zostać przesłane w następnej wersji

Rozszerzenie Paczki: ParcelableHolder

ParcelableHolder to Parcelable , który może zawierać inny Parcelable . Głównym przypadkiem użycia ParcelableHolder jest umożliwienie rozszerzenia Parcelable . Na przykład wyobraź sobie, że osoby wdrażające urządzenia będą mogły rozszerzyć zdefiniowany przez AOSP Parcelable , AospDefinedParcelable , aby uwzględnić funkcje o wartości dodanej.

Wcześniej bez ParcelableHolder osoby wdrażające urządzenia nie mogły modyfikować stabilnego interfejsu AIDL zdefiniowanego przez AOSP, ponieważ dodanie większej liczby pól byłoby błędem:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Jak widać w poprzednim kodzie, ta praktyka jest zła, ponieważ pola dodane przez implementującego urządzenie mogą powodować konflikt, gdy Parcelable zostanie poprawiony w kolejnych wersjach Androida.

Używając ParcelableHolder , właściciel działki może zdefiniować punkt rozszerzenia w Parcelable .

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Następnie realizatorzy urządzeń mogą zdefiniować własne Parcelable dla swojego rozszerzenia.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Wreszcie nowy Parcelable może zostać dołączona do oryginalnej Parcelable za pomocą pola ParcelableHolder .


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Kompilowanie w oparciu o środowisko wykonawcze AIDL

AIDL ma trzy różne backendy: Java, NDK, CPP. Aby używać stabilnego AIDL, musisz zawsze używać kopii systemowej libbindera pod system/lib*/libbinder.so i rozmawiać na /dev/binder . W przypadku kodu na obrazie dostawcy oznacza to, że nie można użyć libbinder (z VNDK): ta biblioteka ma niestabilny interfejs API C++ i niestabilne elementy wewnętrzne. Zamiast tego natywny kod dostawcy musi korzystać z zaplecza NDK AIDL, łączyć się z libbinder_ndk (która jest wspierana przez systemowy libbinder.so ) i łączyć z bibliotekami -ndk_platform utworzonymi przez wpisy aidl_interface .

Nazwy instancji serwera AIDL HAL

Zgodnie z konwencją, usługi AIDL HAL mają nazwę instancji w formacie $package.$type/$instance . Na przykład instancja wibratora HAL jest zarejestrowana jako android.hardware.vibrator.IVibrator/default .

Pisanie serwera AIDL HAL

Serwery @VintfStability AIDL muszą być zadeklarowane w manifeście VINTF, na przykład tak:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

W przeciwnym razie powinni normalnie zarejestrować usługę AIDL. Podczas uruchamiania testów VTS oczekuje się, że wszystkie zadeklarowane warstwy AIDL HAL będą dostępne.

Pisanie klienta AIDL

Klienci AIDL muszą zadeklarować się w macierzy zgodności, na przykład w następujący sposób:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Konwersja istniejącej warstwy HAL z HIDL na AIDL

Użyj narzędzia hidl2aidl , aby przekonwertować interfejs HIDL na AIDL.

Funkcje hidl2aidl :

  • Utwórz pliki .aidl na podstawie plików .hal dla danego pakietu
  • Utwórz reguły kompilacji dla nowo utworzonego pakietu AIDL z włączonymi wszystkimi backendami
  • Twórz metody tłumaczenia w backendach Java, CPP i NDK do tłumaczenia z typów HIDL na typy AIDL
  • Utwórz reguły kompilacji dla bibliotek tłumaczeń z wymaganymi zależnościami
  • Twórz statyczne potwierdzenia, aby mieć pewność, że moduły wyliczające HIDL i AIDL mają te same wartości w backendach CPP i NDK

Wykonaj poniższe kroki, aby przekonwertować pakiet plików .hal na pliki .aidl:

  1. Zbuduj narzędzie znajdujące się w system/tools/hidl/hidl2aidl .

    Tworzenie tego narzędzia z najnowszego źródła zapewnia najbardziej kompletne środowisko. Możesz użyć najnowszej wersji do konwersji interfejsów na starszych gałęziach z poprzednich wydań.

    m hidl2aidl
    
  2. Uruchom narzędzie z katalogiem wyjściowym, po którym znajduje się pakiet do konwersji.

    Opcjonalnie użyj argumentu -l , aby dodać zawartość nowego pliku licencji na górę wszystkich wygenerowanych plików. Pamiętaj, aby użyć prawidłowej licencji i daty.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Na przykład:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Przeczytaj wygenerowane pliki i napraw wszelkie problemy z konwersją.

    • conversion.log zawiera wszelkie nierozwiązane problemy, które należy rozwiązać w pierwszej kolejności.
    • Wygenerowane pliki .aidl mogą zawierać ostrzeżenia i sugestie, które mogą wymagać działania. Te komentarze zaczynają się od // .
    • Skorzystaj z okazji, aby posprzątać i wprowadzić ulepszenia w pakiecie.
    • Sprawdź adnotację @JavaDerive pod kątem funkcji, które mogą być potrzebne, takich jak toString lub equals .
  4. Buduj tylko te cele, których potrzebujesz.

    • Wyłącz backendy, które nie będą używane. Preferuj backend NDK zamiast CPP, zobacz wybieranie środowiska wykonawczego .
    • Usuń biblioteki tłumaczeń lub dowolny wygenerowany przez nie kod, który nie będzie używany.
  5. Zobacz Główne różnice AIDL/HIDL .

    • Korzystanie z wbudowanego Status i wyjątków AIDL zazwyczaj poprawia interfejs i eliminuje potrzebę stosowania innego typu statusu specyficznego dla interfejsu.
    • Argumenty interfejsu AIDL w metodach domyślnie nie @nullable , tak jak miało to miejsce w języku HIDL.

Polityka dotycząca AIDL HAL

Typ usługi AIDL widoczny dla kodu dostawcy musi mieć atrybut hal_service_type . W przeciwnym razie konfiguracja sepolicy jest taka sama, jak w przypadku każdej innej usługi AIDL (chociaż istnieją specjalne atrybuty dla warstw HAL). Oto przykładowa definicja kontekstu usługi HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Dla większości usług zdefiniowanych przez platformę dodany jest już kontekst usługi odpowiedniego typu (np. android.hardware.foo.IFoo/default byłby już oznaczony jako hal_foo_service ). Jeśli jednak klient platformy obsługuje wiele nazw instancji, należy dodać dodatkowe nazwy instancji w plikach service_contexts specyficznych dla urządzenia.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Atrybuty HAL należy dodać, gdy tworzymy nowy typ HAL. Określony atrybut HAL może być powiązany z wieloma typami usług (z których każdy może mieć wiele instancji, jak właśnie omówiliśmy). Dla HAL, foo , mamy hal_attribute(foo) . To makro definiuje atrybuty hal_foo_client i hal_foo_server . Dla danej domeny makra hal_client_domain i hal_server_domain wiążą domenę z danym atrybutem HAL. Na przykład serwer systemowy będący klientem tej warstwy HAL odpowiada polityce hal_client_domain(system_server, hal_foo) . Serwer HAL podobnie zawiera hal_server_domain(my_hal_domain, hal_foo) . Zazwyczaj dla danego atrybutu HAL tworzymy również domenę taką jak hal_foo_default w celach informacyjnych lub przykładowych HAL. Jednak niektóre urządzenia używają tych domen jako własnych serwerów. Rozróżnienie domen dla wielu serwerów ma znaczenie tylko wtedy, gdy mamy wiele serwerów obsługujących ten sam interfejs i potrzebujemy różnych zestawów uprawnień w ich implementacjach. We wszystkich tych makrach hal_foo nie jest w rzeczywistości obiektem sepolicy. Zamiast tego ten token jest używany przez te makra w odniesieniu do grupy atrybutów powiązanych z parą klient-serwer.

Jednak jak dotąd nie powiązaliśmy hal_foo_service i hal_foo (para atrybutów z hal_attribute(foo) ). Atrybut HAL jest powiązany z usługami AIDL HAL przy użyciu makra hal_attribute_service (w warstwach HAL HIDL używa się makra hal_attribute_hwservice ). Na przykład hal_attribute_service(hal_foo, hal_foo_service) . Oznacza to, że procesy hal_foo_client mogą uzyskać dostęp do warstwy HAL, a procesy hal_foo_server mogą zarejestrować warstwę HAL. Egzekwowaniem tych zasad rejestracji zajmuje się menedżer kontekstu ( servicemanager ). Należy zauważyć, że nazwy usług nie zawsze odpowiadają atrybutom HAL. Na przykład możemy zobaczyć hal_attribute_service(hal_foo, hal_foo2_service) . Ogólnie jednak, ponieważ oznacza to, że usługi są zawsze używane razem, możemy rozważyć usunięcie hal_foo2_service i użycie hal_foo_service dla wszystkich naszych kontekstów usług. Większość warstw HAL, które ustawiają wiele hal_attribute_service , wynika z tego, że oryginalna nazwa atrybutu HAL nie jest wystarczająco ogólna i nie można jej zmienić.

Podsumowując, przykładowy HAL wygląda następująco:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Dołączone interfejsy rozszerzeń

Rozszerzenie można dołączyć do dowolnego interfejsu segregatora, niezależnie od tego, czy jest to interfejs najwyższego poziomu zarejestrowany bezpośrednio w menedżerze usług, czy jest to interfejs podrzędny. Uzyskując rozszerzenie, musisz potwierdzić, że typ rozszerzenia jest zgodny z oczekiwaniami. Rozszerzenia można ustawić tylko z poziomu procesu obsługującego segregator.

Dołączonych rozszerzeń należy używać zawsze, gdy rozszerzenie modyfikuje funkcjonalność istniejącej warstwy HAL. Gdy potrzebna jest zupełnie nowa funkcjonalność, nie ma potrzeby korzystania z tego mechanizmu, a interfejs rozszerzeń można zarejestrować bezpośrednio u menedżera serwisu. Dołączone interfejsy rozszerzeń mają największy sens, gdy są dołączone do podinterfejsów, ponieważ te hierarchie mogą być głębokie lub wieloinstancyjne. Używanie rozszerzenia globalnego do odzwierciedlenia hierarchii interfejsu segregatora innej usługi wymagałoby obszernej księgowości, aby zapewnić równoważną funkcjonalność bezpośrednio podłączonym rozszerzeniom.

Aby ustawić rozszerzenie w segregatorze, użyj następujących interfejsów API:

  • W backendzie NDK: AIBinder_setExtension
  • W backendzie Java: android.os.Binder.setExtension
  • W backendzie CPP: android::Binder::setExtension
  • W backendie Rusta: binder::Binder::set_extension

Aby uzyskać rozszerzenie w segregatorze, użyj następujących interfejsów API:

  • W backendzie NDK: AIBinder_getExtension
  • W backendzie Java: android.os.IBinder.getExtension
  • W backendzie CPP: android::IBinder::getExtension
  • W backendie Rusta: binder::Binder::get_extension

Więcej informacji na temat tych interfejsów API można znaleźć w dokumentacji funkcji getExtension w odpowiednim backendie. Przykład użycia rozszerzeń można znaleźć w hardware/interfaces/tests/extension/vibrator .

Główne różnice AIDL/HIDL

Korzystając z warstw AIDL HAL lub interfejsów AIDL HAL, należy pamiętać o różnicach w porównaniu z pisaniem warstw HIDL HAL.

  • Składnia języka AIDL jest bliższa Javie. Składnia HIDL jest podobna do C++.
  • Wszystkie interfejsy AIDL mają wbudowane statusy błędów. Zamiast tworzyć niestandardowe typy statusów, utwórz stałe int stanu w plikach interfejsu i użyj EX_SERVICE_SPECIFIC w backendach CPP/NDK i ServiceSpecificException w backendach Java. Zobacz Obsługa błędów .
  • AIDL nie uruchamia automatycznie puli wątków, gdy wysyłane są obiekty spoiwa. Muszą zostać uruchomione ręcznie (zobacz zarządzanie wątkami ).
  • AIDL nie przerywa w przypadku niesprawdzonych błędów transportu (HIDL Return przerywa w przypadku niesprawdzonych błędów).
  • AIDL może zadeklarować tylko jeden typ na plik.
  • Oprócz parametru wyjściowego argumenty AIDL można określić jako in/out/inout (nie ma „synchronicznych wywołań zwrotnych”).
  • AIDL używa fd jako typu pierwotnego zamiast uchwytu.
  • HIDL używa wersji głównych w przypadku niekompatybilnych zmian i wersji mniejszych w przypadku zgodnych zmian. W AIDL zmiany kompatybilne wstecz są wprowadzane na miejscu. AIDL nie ma wyraźnej koncepcji wersji głównych; zamiast tego jest to uwzględniane w nazwach pakietów. Na przykład AIDL może używać nazwy pakietu bluetooth2 .
  • AIDL domyślnie nie dziedziczy priorytetu w czasie rzeczywistym. Aby włączyć dziedziczenie priorytetów w czasie rzeczywistym, należy użyć funkcji setInheritRt dla każdego elementu wiążącego.