AIDL dla licencji HAL

Android 11 wprowadza możliwość korzystania z AIDL dla HAL-i w Androidzie, co umożliwia implementowanie części Androida bez HIDL. Przejście na używanie wyłącznie interfejsów HAL z AIDL (gdy interfejsy HAL na wyższych poziomach używają HIDL, należy używać HIDL).

Interfejsy HAL korzystające z AIDL do komunikacji między komponentami platformy, takimi jak te w system.img, i komponentami sprzętowymi, takimi jak te w vendor.img, muszą używać stabilnej wersji AIDL. Jednak w ramach partycji, na przykład z jednego HAL do drugiego, nie ma żadnych ograniczeń dotyczących używanego mechanizmu IPC.

Motywacja

Interfejs AIDL istnieje dłużej niż HIDL i jest używany w wielu innych miejscach, np. między komponentami platformy Androida lub w aplikacjach. Teraz, gdy AIDL zapewnia stabilność, można wdrożyć cały pakiet za pomocą pojedynczego środowiska wykonawczego IPC. AIDL ma też lepszy system wersji niż HIDL. Oto kilka zalet AIDL:

  • Korzystanie z jednego języka IPC oznacza, że trzeba się uczyć, debugować, optymalizować i chronić tylko jedną rzecz.
  • AIDL obsługuje wersjonowanie na miejscu dla właścicieli interfejsu:
    • Właściciele mogą dodawać metody na końcu interfejsów lub pola do obiektów Parcelable. Oznacza to, że łatwiej jest wersjonować kod na przestrzeni lat, a także że koszty w ciągu roku są mniejsze (typy można zmieniać na miejscu i nie trzeba tworzyć 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 trzeba ponownie bazować rozszerzeń na nowszych wersjach interfejsów.
  • Dotychczasowy interfejs AIDL może być używany bezpośrednio, gdy jego właściciel zdecyduje się na jego stabilizację. Wcześniej trzeba było utworzyć całą kopię interfejsu w HIDL.

Kompilowanie w środowisku wykonawczym AIDL

AIDL ma 3 różne backendy: Java, NDK i CPP. Aby korzystać ze stabilnej wersji AIDL, zawsze używaj kopii systemu libbinder na stronie system/lib*/libbinder.so i rozmawiaj na stronie /dev/binder. W przypadku kodu w obrazu vendor oznacza to, że nie można używać biblioteki libbinder (z VNDK), ponieważ ma ona niestabilny interfejs API w C++ i niestabilne wewnętrzne elementy. Zamiast tego natywny kod dostawcy musi używać backendu NDK w AIDL, linkować się do biblioteki libbinder_ndk (która jest obsługiwana przez system libbinder.so) oraz do bibliotek NDK utworzonych przez wpisy aidl_interface. Dokładne nazwy modułów znajdziesz w zasadach nazewnictwa modułów.

Tworzenie interfejsu AIDL HAL

Aby interfejs AIDL mógł być używany przez system i dostawcę, należy wprowadzić w nim 2 zmiany:

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

Tylko właściciel interfejsu może wprowadzać te zmiany.

Aby interfejs działał, po wprowadzeniu tych zmian musisz go umieścić w pliku manifestu VINTF. Testuj to (i powiązane wymagania, takie jak weryfikacja, czy opublikowane interfejsy są zamrożone) za pomocą testu VTS vts_treble_vintf_vendor_test. Możesz używać interfejsu @VintfStability bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability w backendzie NDK, android::Stability::forceDowngradeToLocalStability w backendzie C++, android.os.Binder#forceDowngradeToSystemStability w backendzie Java na obiekcie bindera, zanim zostanie on wysłany do innego procesu.

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

Użycie w tym przykładzie kodu funkcji backends jest poprawne, ponieważ istnieją 3 backendy (Java, NDK i CPP). Kod pokazuje, jak wyłączyć backend CPP:

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

Znajdowanie interfejsów AIDL HAL

Stabilne interfejsy AIDL AOSP dla HAL znajdują się w folderach aidl w tych samych katalogach bazy danych co interfejsy HIDL:

  • hardware/interfaces to interfejsy zwykle udostępniane przez sprzęt.
  • frameworks/hardware/interfaces służy do obsługi interfejsów wysokiego poziomu udostępnianych sprzętowi.
  • system/hardware/interfaces służy do interfejsów niskiego poziomu udostępnianych sprzętowi.

Umieść interfejsy rozszerzeń w innych hardware/interfaces podkatalogach w vendor lub hardware.

Interfejsy rozszerzeń

Android ma zestaw oficjalnych interfejsów AOSP w każdej wersji. Jeśli partnerzy Androida chcą dodać funkcje do tych interfejsów, nie powinni ich zmieniać bezpośrednio, ponieważ spowoduje to niezgodność ich środowiska wykonawczego Androida z środowiskiem wykonawczym AOSP. Unikaj zmiany tych interfejsów, aby obraz GSI mógł nadal działać.

Rozszerzenia mogą się rejestrować na 2 sposoby:

Rozszerzenie jest jednak zarejestrowane, a jeśli komponenty specyficzne dla dostawcy (czyli niebędące częścią wstępnej wersji AOSP) korzystają z interfejsu, nie ma możliwości wystąpienia konfliktów podczas scalania. Jeśli jednak w dodatkowych komponentach wstępnej wersji AOSP zostaną wprowadzone zmiany, mogą wystąpić konflikty podczas scalania. W takich przypadkach zalecamy stosowanie tych strategii:

  • Prześlij zmiany interfejsu do AOSP w kolejej wersji.
  • Dodatki do interfejsu upstream, które zapewniają większą elastyczność (bez konfliktów podczas scalania) w następnej wersji.

Obiekty parcelable rozszerzenia: ParcelableHolder

ParcelableHolder to instancja interfejsu Parcelable, która może zawierać inną instancję Parcelable.

Głównym zastosowaniem ParcelableHolder jest umożliwienie rozszerzalności Parcelable. Na przykład implementatorzy urządzeń oczekują, że będą mogli rozszerzyć definiowane przez AOSP Parcelable, AospDefinedParcelable, aby uwzględnić w nich funkcje zwiększające wartość.

Użyj interfejsu ParcelableHolder, aby rozszerzyć Parcelable o funkcje zwiększające wartość. Interfejs ParcelableHolder zawiera instancję Parcelable. Jeśli spróbujesz dodać pola bezpośrednio do Parcelable, spowoduje to błąd:

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 metoda nie działa, ponieważ pola dodane przez implementatora urządzenia mogą powodować konflikt, gdy Parcelable zostanie zmienione w kolejnych wersjach Androida.

Za pomocą ParcelableHolder właściciel obiektu Parcelable może zdefiniować punkt rozszerzenia w instancji Parcelable:

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

Następnie implementatorzy urządzeń mogą zdefiniować własne wystąpienie Parcelable dla swojego rozszerzenia:

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

Nowe wystąpienie pola Parcelable można dołączyć do pierwotnego wystąpienia 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>();

Nazwa 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.

Tworzenie serwera AIDL HAL

@VintfStability Serwery AIDL muszą być zadeklarowane w pliku manifestu VINTF, na przykład:

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

W przeciwnym razie należy zarejestrować usługę AIDL w zwykły sposób. Podczas uruchamiania testów VTS należy mieć dostępne wszystkie zadeklarowane interfejsy API AIDL.

Tworzenie klienta AIDL

Klienci AIDL muszą zadeklarować się w macierz zgodności, na przykład:

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

Konwertowanie istniejącego interfejsu HAL z HIDL na AIDL

Aby przekonwertować interfejs HIDL na AIDL, użyj narzędzia hidl2aidl.

hidl2aidl funkcji:

  • Utwórz pliki AIDL (.aidl) na podstawie plików HAL (.hal) dla danego pakietu.
  • Utwórz reguły kompilacji dla nowo utworzonego pakietu AIDL ze wszystkimi włączonymi backendami.
  • Utwórz metody tłumaczenia w backendach Java, CPP i NDK, aby przekształcać typy HIDL w typy AIDL.
  • Utwórz reguły kompilacji dla bibliotek tłumaczenia z wymaganymi zależnościami.
  • Utwórz statyczne stwierdzenia, aby mieć pewność, że enumeratory HIDL i AIDL mają te same wartości w backendach CPP i NDK.

Aby przekonwertować pakiet plików HAL na pliki AIDL:

  1. Utwórz narzędzie dostępne w sekcji system/tools/hidl/hidl2aidl.

    Utworzenie tego narzędzia na podstawie najnowszego źródła zapewnia najbardziej kompletne wrażenia. Najnowszej wersji możesz używać do konwertowania interfejsów w starszych gałęziach z poprzednich wersji:

    m hidl2aidl
  2. Uruchom narzędzie, podając katalog wyjściowy, a następnie pakiet, który ma zostać przekonwertowany.

    Opcjonalnie możesz użyć argumentu -l, aby dodać zawartość nowego pliku licencji do górnej części wszystkich wygenerowanych plików. Upewnij się, że używasz prawidłowej licencji i daty:

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

    Przykład:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. Przejrzyj wygenerowane pliki i rozwiąż problemy z konwersją:

    • conversion.log zawiera wszystkie problemy, które należy rozwiązać jako pierwsze.
    • Wygenerowane pliki AIDL mogą zawierać ostrzeżenia i sugestie, które wymagają podjęcia działań. Te komentarze zaczynają się od //.
    • Oczyść i ulepszaj pakiet.
    • Sprawdź adnotację @JavaDerive, aby dowiedzieć się, jakie funkcje mogą być potrzebne, np. toString lub equals.
  4. Utwórz tylko te cele, których potrzebujesz:

  5. Najważniejsze różnice między AIDL a HIDL:

    • Korzystanie z wbudowanych Status i wyjątków AIDL zwykle polepsza interfejs i eliminuje potrzebę korzystania z innego typu stanu związanego z interfejsem.
    • Argumenty interfejsu AIDL w metodach nie są domyślnie @nullable, tak jak w HIDL.

SEPolicy dla interfejsów HAL AIDL

Typ usługi AIDL, który jest widoczny dla kodu dostawcy, musi mieć atrybut hal_service_type. Poza tym konfiguracja sepolicy jest taka sama jak w przypadku innych usług AIDL (chociaż istnieją specjalne atrybuty dla interfejsów HAL). Oto przykładowa definicja kontekstu usługi HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

W przypadku większości usług zdefiniowanych przez platformę kontekst usługi z odpowiednim typem jest już dodany (na przykład android.hardware.foo.IFoo/default jest już oznaczony jako hal_foo_service). Jeśli jednak klient frameworku obsługuje wiele nazw instancji, dodatkowe nazwy instancji należy dodać w plikach service_contexts dotyczących urządzenia:

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

Podczas tworzenia nowego typu HAL musisz dodać atrybuty HAL. Określony atrybut HAL może być powiązany z wieloma typami usług (każdy z nich może mieć wiele wystąpień, jak już wspomniano). W przypadku HAL foo jest hal_attribute(foo). To makro definiuje atrybuty hal_foo_clienthal_foo_server. W przypadku danej domeny makro hal_client_domainhal_server_domain kojarzą domenę z danym atrybutem HAL. Na przykład serwer systemowy jest klientem tego interfejsu HAL zgodnie z zasadami hal_client_domain(system_server, hal_foo). Serwer HAL zawiera również hal_server_domain(my_hal_domain, hal_foo).

Zwykle dla danego atrybutu HAL tworzysz też domenę, taką jak hal_foo_default, na potrzeby referencyjnych lub przykładowych atrybutów HAL. Niektóre urządzenia używają jednak tych domen dla własnych serwerów. Rozróżnianie domen na wielu serwerach ma znaczenie tylko wtedy, gdy jest wiele serwerów, które obsługują ten sam interfejs i potrzebują innego zestawu uprawnień w swoich implementacjach. We wszystkich tych makronach hal_foo nie jest obiektem sepolicy. Zamiast tego te tokeny są używane przez te makropolecenia do odwoływania się do grupy atrybutów powiązanych z parą klient-serwer.

Jednak do tej pory atrybuty hal_foo_service i hal_foo (para atrybutów z hal_attribute(foo)) nie są powiązane. Atrybut HAL jest powiązany z usługami HAL AIDL za pomocą makra hal_attribute_service (makrom hal_attribute_hwservice używają HAL-e HIDL), na przykład hal_attribute_service(hal_foo, hal_foo_service). Oznacza to, że procesy hal_foo_client mogą uzyskać dostęp do HAL, a procesy hal_foo_server mogą zarejestrować HAL. Egzekwowanie tych zasad rejestracji jest realizowane przez menedżera kontekstu (servicemanager).

Nazwy usług nie zawsze odpowiadają atrybutom HAL, np. hal_attribute_service(hal_foo, hal_foo2_service). Ogólnie, ponieważ zakłada to, że usługi są zawsze używane razem, możesz usunąć hal_foo2_service i użyć hal_foo_service we wszystkich kontekstach usług. Jeśli HALs ustawia wiele wystąpień hal_attribute_service, oznacza to, że pierwotna nazwa atrybutu HAL nie jest wystarczająco ogólna i nie można jej zmienić.

Połączenie tych wszystkich elementów w przykładzie HAL wygląda tak:

    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że być dołączone do dowolnego interfejsu Binder, niezależnie od tego, czy jest to interfejs najwyższego poziomu zarejestrowany bezpośrednio w menedżerze usługi, czy interfejs podrzędny. Podczas uzyskiwania rozszerzenia musisz potwierdzić, że jego typ jest zgodny z oczekiwaniami. Rozszerzenia możesz ustawiać tylko w procesie obsługijącym pakiet.

Używaj załączonych rozszerzeń, gdy rozszerzenie zmienia działanie istniejącego interfejsu HAL. Jeśli potrzebna jest zupełnie nowa funkcja, nie musisz stosować tego mechanizmu i możesz zarejestrować interfejs rozszerzenia bezpośrednio w menedżerze usługi. Dołączone interfejsy rozszerzeń mają największy sens, gdy są dołączone do interfejsów podrzędnych, ponieważ te hierarchie mogą być głębokie lub wieloinstancyjne. Korzystanie z globalnego rozszerzenia do odzwierciedlenia hierarchii interfejsu bindera innej usługi wymaga rozbudowanej księgowości, aby zapewnić funkcje równoważne bezpośrednio dołączonym rozszerzeniom.

Aby ustawić rozszerzenie w binderze, użyj tych interfejsów API:

  • Backend NDK: AIBinder_setExtension
  • Java backend: android.os.Binder.setExtension
  • Backend CPP: android::Binder::setExtension
  • Backend Rust: binder::Binder::set_extension

Aby uzyskać rozszerzenie bindera, użyj tych interfejsów API:

  • Backend NDK: AIBinder_getExtension
  • Java backend: android.os.IBinder.getExtension
  • Backend CPP: android::IBinder::getExtension
  • Backend Rust: binder::Binder::get_extension

Więcej informacji o tych interfejsach API znajdziesz w dokumentacji funkcji getExtension w odpowiednim backendzie. Przykład użycia rozszerzeń znajdziesz w artykule hardware/interfaces/tests/extension/vibrator.

Najważniejsze różnice między AIDL a HIDL

Podczas korzystania z interfejsów HAL AIDL lub interfejsów AIDL HAL należy pamiętać o różnicach w porównaniu z pisaniem interfejsów HAL HIDL.

  • Składnia języka AIDL jest zbliżona do składni języka Java. Składnia HIDL jest podobna do C++.
  • Wszystkie interfejsy AIDL mają wbudowane stany błędów. Zamiast tworzyć niestandardowe typy stanu, utwórz w plikach interfejsu stałe typu int o stanie i użyj ich w backendzie w języku C++ i NDK oraz w backendzie w języku Java.EX_SERVICE_SPECIFICServiceSpecificException Zobacz obsługę błędów.
  • AIDL nie uruchamia automatycznie puli wątków podczas wysyłania obiektów bindera. Musisz je uruchomić ręcznie (patrz Zarządzanie wątkiem).
  • AIDL nie przerywa operacji w przypadku niezaznaczonych błędów transportu (HIDL Return przerywa operacje w przypadku niezaznaczonych błędów).
  • AIDL może zadeklarować tylko jeden typ na plik.
  • Argumenty AIDL można określić jako in, out lub inout oprócz parametru wyjściowego (nie ma wywołań zwrotnych synchronicznych).
  • AIDL używa typu prymitywnego fd zamiast handle.
  • HIDL używa wersji głównych w przypadku zmian niezgodnych i wersji podrzędnych w przypadku zmian zgodnych. W AIDL zmiany zgodne wstecznie są wprowadzane na miejscu. AIDL nie ma wyraźnej koncepcji wersji głównych; zamiast tego są one uwzględniane w nazwach pakietów. Na przykład AIDL może używać nazwy pakietu bluetooth2.
  • Domyślnie AIDL nie dziedziczy priorytetu w czasie rzeczywistym. Aby umożliwić dziedziczenie priorytetów w czasie rzeczywistym, należy użyć funkcji setInheritRt w każdym binderze.

Pakiet testów dostawcy (VTS) dla HAL

Android korzysta z pakietu testów dostawcy (VTS) do weryfikowania oczekiwanych implementacji HAL. VTS zapewnia zgodność wsteczną Androida ze starszymi implementacjami dostawców. W przypadku implementacji, które nie przechodzą testów VTS, występują znane problemy ze zgodnością, które mogą uniemożliwić im działanie w przyszłych wersjach systemu operacyjnego.

VTS dla HAL składa się z 2 głównych części.

1. Sprawdź, czy interfejsy HAL na urządzeniu są znane i oczekywane przez Androida

Ten zestaw testów znajdziesz w sekcji test/vts-testcase/hal/treble/vintf. Te testy służą do sprawdzania:

  • Każdy interfejs @VintfStability zadeklarowany w pliku manifestu VINTF jest zamrożony w znanej wersji. Dzięki temu obie strony interfejsu zgadzają się co do dokładnej definicji tej wersji interfejsu. Jest to konieczne do podstawowego działania.
  • Na tym urządzeniu dostępne są wszystkie HAL-e zadeklarowane w manifeście VINTF. Każdy klient, który ma wystarczające uprawnienia do korzystania z deklarowanej usługi HAL, musi mieć możliwość uzyskania dostępu do tych usług i korzystania z nich w dowolnym momencie.
  • Wszystkie HAL-e zadeklarowane w pliku manifestu VINTF wyświetlają wersję interfejsu, którą zadeklarowano w pliku manifestu.
  • Na urządzeniu nie są wyświetlane żadne przestarzałe interfejsy HAL. Android przestaje obsługiwać starsze wersje interfejsów HAL zgodnie z opisem w cyklu życia FCM.
  • Wymagane interfejsy HAL są obecne na urządzeniu. Niektóre interfejsy HAL są wymagane do prawidłowego działania Androida.

2. Sprawdź oczekiwane działanie każdego interfejsu HAL.

Każdy interfejs HAL ma własne testy VTS, które sprawdzają oczekiwane działanie klientów. Przypadki testowe są uruchamiane w przypadku każdej instancji zadeklarowanego interfejsu HAL i wymuszają określone zachowanie na podstawie wersji zaimplementowanego interfejsu.

Testy te obejmują wszystkie aspekty implementacji HAL, na których opiera się framework Androida lub które mogą być używane w przyszłości.

Testy te obejmują weryfikację obsługi funkcji, obsługi błędów i innych zachowań, których klient może oczekiwać od usługi.

Kamienie milowe VTS dotyczące rozwoju HAL

Testy VTS powinny być aktualizowane podczas tworzenia lub modyfikowania interfejsów HAL systemu Android.

Testy VTS muszą być ukończone i gotowe do weryfikacji implementacji dostawców przed zamrożeniem wersji interfejsu API dostawcy Androida. Muszą być gotowe przed zamrożeniem interfejsów, aby deweloperzy mogli tworzyć swoje implementacje, weryfikować je i przekazywać opinie deweloperom interfejsu HAL.

VTS w Cuttlefish

Gdy sprzęt jest niedostępny, Android używa Cuttlefish jako narzędzia do tworzenia interfejsów HAL. Umożliwia to testowanie VTS i testowanie integracji Androida na dużą skalę.

hal_implementation_test sprawdza, czy Cuttlefish ma implementacje najnowszych wersji interfejsu HAL, aby upewnić się, że Android jest gotowy do obsługi nowych interfejsów, oraz czy testy VTS są gotowe do testowania implementacji nowych dostawców, gdy tylko pojawią się nowe wersje sprzętu i urządzeń.