AIDL dla HAL

W systemie Android 11 wprowadzono możliwość używania AIDL dla warstw HAL w systemie Android. Umożliwia to implementację części Androida bez HIDL. Przejściowe warstwy HAL do używania AIDL wyłącznie tam, gdzie to możliwe (gdy wcześniejsze warstwy HAL używają HIDL, należy użyć HIDL).

Warstwy HAL używające AIDL do komunikacji między komponentami struktury, takimi jak system.img , a komponentami sprzętowymi, takimi jak vendor.img , muszą używać stabilnego AIDL. Jednak aby komunikować się w obrębie 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, takich jak między komponentami platformy Android lub aplikacjami. Teraz, gdy AIDL ma obsługę stabilności, możliwe jest zaimplementowanie całego stosu za pomocą jednego środowiska wykonawczego IPC. AIDL ma również lepszy system wersjonowania niż HIDL.

  • Korzystanie z jednego języka IPC oznacza, że ​​musisz się nauczyć tylko jednej rzeczy, debugować, zoptymalizować i zabezpieczyć.
  • AIDL obsługuje wersjonowanie w miejscu dla właścicieli interfejsu:
    • Właściciele mogą dodawać metody na końcu interfejsów lub pola do działek. Oznacza to, że z biegiem lat łatwiej jest tworzyć wersje kodu, a także roczny koszt jest mniejszy (typy można zmieniać na miejscu i nie ma potrzeby stosowania 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 rozszerzeń podrzędnych na nowszych wersjach interfejsów.
  • Istniejący interfejs AIDL może być używany bezpośrednio, gdy jego właściciel zdecyduje się go ustabilizować. Wcześniej cała kopia interfejsu musiałaby zostać utworzona w HIDL.

Pisanie interfejsu AIDL HAL

Aby interfejs AIDL był używany mię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", .

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

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ą zamrożone) za pomocą testu VTS vts_treble_vintf_vendor_test . Możesz użyć interfejsu @VintfStability bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability w backendzie NDK, android::Stability::forceDowngradeToLocalStability w backendzie C++ lub android.os.Binder#forceDowngradeToSystemStability w backendzie Java na obiekcie bindera przed jego wysłaniem do innego procesu. Obniżenie usługi do poziomu stabilności 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.

Zauważ, że użycie backends w poniższym przykładzie jest poprawne, ponieważ istnieją trzy backendy (Java, NDK i CPP). Poniższy kod mówi, jak wybrać konkretnie backend CPP, aby go wyłączyć.

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

Znajdowanie interfejsów AIDL HAL

Stabilne interfejsy AOSP AIDL 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/interfejs

Interfejsy rozszerzeń należy umieścić w innych podkatalogach hardware/interfaces w katalogu vendor lub hardware .

Interfejsy rozszerzeń

Android ma zestaw oficjalnych interfejsów AOSP w każdym wydaniu. Gdy partnerzy systemu Android chcą dodać funkcje do tych interfejsów, nie powinni ich zmieniać bezpośrednio, ponieważ oznaczałoby to, że ich środowisko uruchomieniowe systemu Android jest niezgodne ze środowiskiem uruchomieniowym systemu Android AOSP. W przypadku urządzeń GMS unikanie zmiany tych interfejsów jest również tym, co zapewnia dalsze działanie obrazu GSI.

Rozszerzenia mogą rejestrować się na dwa różne sposoby:

Jednak rozszerzenie jest rejestrowane, gdy komponenty specyficzne dla dostawcy (tzn. nie będące częścią nadrzędnego AOSP) korzystają z interfejsu, nie ma możliwości wystąpienia konfliktu scalania. Jednak w przypadku dokonywania dalszych modyfikacji komponentów AOSP nadrzędnych mogą wystąpić konflikty scalania i zalecane są następujące strategie:

  • dodatki interfejsu można przesłać do AOSP w następnej wersji
  • dodatki interfejsu, które zapewniają większą elastyczność, bez konfliktów scalania, mogą być udostępniane w następnym wydaniu

Przedłużenie paczek: ParcelableHolder

ParcelableHolder to Parcelable , która może zawierać inną Parcelable . Głównym przypadkiem użycia ParcelableHolder jest rozszerzenie Parcelable . Na przykład obraz, który według realizatorów urządzeń będzie w stanie rozszerzyć zdefiniowaną Parcelable , AospDefinedParcelable , aby uwzględnić ich funkcje zwiększające wartość dodaną.

Wcześniej bez ParcelableHolder urządzeń nie mogli modyfikować stabilnego interfejsu AIDL zdefiniowanego w 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 zepsuta, ponieważ pola dodane przez realizatora urządzenia mogą powodować konflikt, gdy Parcelable zostanie poprawiony w kolejnych wersjach systemu Android.

Używając ParcelableHolder , właściciel parcelable 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 ich rozszerzenia.

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

Wreszcie, nowy Parcelable można dołączyć 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>();

Budowanie w oparciu o środowisko wykonawcze AIDL

AIDL ma trzy różne backendy: Java, NDK, CPP. Aby używać stabilnego AIDL, musisz zawsze używać systemowej kopii libbinder w system/lib*/libbinder.so i rozmawiać na /dev/binder . W przypadku kodu na obrazie dostawcy oznacza to, że libbinder (z VNDK) nie może być używany: ta biblioteka ma niestabilne API C++ i niestabilne elementy wewnętrzne. Zamiast tego, natywny kod dostawcy musi używać backendu NDK AIDL, łączyć się z libbinder_ndk (która jest wspierana przez system libbinder.so ) i łączyć się 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 warstwy HAL wibratora jest zarejestrowana jako android.hardware.vibrator.IVibrator/default .

Pisanie serwera AIDL HAL

@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 HAL AIDL są dostępne.

Pisanie klienta AIDL

Klienci AIDL muszą zadeklarować się w macierzy kompatybilności, na przykład tak:

    <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 :

  • Twó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
  • Twórz reguły kompilacji dla bibliotek tłumaczących z wymaganymi zależnościami
  • Twórz statyczne potwierdzenia, aby zapewnić, że moduły wyliczające HIDL i AIDL mają te same wartości w backendach CPP i NDK

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

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

    Budowanie tego narzędzia z najnowszego źródła zapewnia najbardziej kompletne wrażenia. 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 następuje pakiet, który ma zostać przekonwertowany.

    hidl2aidl -o <output directory> <package>
    

    Na przykład:

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

    • conversion.log zawiera wszelkie nieobsłużone problemy, które należy najpierw naprawić.
    • 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.
  4. Buduj tylko te cele, których potrzebujesz.

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

    • Korzystanie z wbudowanego Status AIDL i wyjątków zazwyczaj poprawia interfejs i eliminuje potrzebę stosowania innego typu statusu specyficznego dla interfejsu.

Sepolicy dla AIDL HAL

Typ usługi AIDL, który jest widoczny dla kodu dostawcy, musi mieć atrybut vendor_service . 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, vendor_service;

W przypadku większości usług zdefiniowanych przez platformę kontekst usługi z poprawnym typem jest już dodany (na przykład android.hardware.foo.IFoo/default będzie już oznaczony jako hal_foo_service ). Jeśli jednak klient struktury obsługuje wiele nazw wystąpień, dodatkowe nazwy wystąpień należy dodać 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 wystąpień, 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 kojarzą domenę z danym atrybutem HAL. Na przykład serwer systemowy będący klientem tej warstwy HAL odpowiada hal_client_domain(system_server, hal_foo) . Podobnie serwer HAL 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ładowe HAL. Jednak niektóre urządzenia używają tych domen na swoich własnych serwerach. Rozróżnianie domen dla wielu serwerów ma znaczenie tylko wtedy, gdy mamy wiele serwerów, które obsługują ten sam interfejs i potrzebują innego zestawu uprawnień w swoich implementacjach. We wszystkich tych makrach hal_foo nie jest w rzeczywistości obiektem sepolicy. Zamiast tego ten token jest używany przez te makra do odwoływania się do grupy atrybutów skojarzonych z parą klient-serwer.

Jednak do tej pory nie skojarzyliśmy hal_foo_service i hal_foo (pary atrybutów z hal_attribute(foo) ). Atrybut HAL jest powiązany z usługami AIDL HAL za pomocą makra hal_attribute_service (HAL HAL HiDL używają 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. Egzekwowanie tych reguł rejestracji jest wykonywane przez menedżera kontekstu ( servicemanager ). Zauważ, że nazwy usług mogą nie zawsze odpowiadać atrybutom warstwy 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 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, vendor_service, 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_call(hal_foo_server, servicemanager)

    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
    binder_use(some_hal_server_domain)
    hal_server_domain(some_hal_server_domain, hal_foo)

Dołączone interfejsy rozszerzeń

Rozszerzenie można dołączyć do dowolnego interfejsu spinacza, 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. Pobierając rozszerzenie, musisz potwierdzić, że typ rozszerzenia jest zgodny z oczekiwaniami. Rozszerzenia można ustawić tylko z procesu obsługującego spinacz.

Dołączonych rozszerzeń należy używać zawsze, gdy rozszerzenie modyfikuje funkcjonalność istniejącej warstwy HAL. Gdy potrzebna jest całkowicie nowa funkcjonalność, ten mechanizm nie musi być używany, a interfejs rozszerzenia można zarejestrować bezpośrednio u menedżera usług. Dołączone interfejsy rozszerzeń mają największy sens, gdy są dołączone do podinterfejsów, ponieważ te hierarchie mogą być głębokie lub wieloinstancyjne. Korzystanie z globalnego rozszerzenia do odzwierciedlenia hierarchii interfejsu bindera innej usługi wymagałoby obszernej księgowości, aby zapewnić funkcjonalność równoważną bezpośrednio dołączanym rozszerzeniom.

Aby ustawić rozszerzenie w spinaczu, 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

Aby uzyskać rozszerzenie w spinaczu, 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

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

Główne różnice AIDL/HIDL

Używając AIDL HAL lub interfejsów AIDL HAL, należy pamiętać o różnicach w porównaniu z pisaniem 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 statusu, utwórz stałe int statusu w plikach interfejsu i użyj EX_SERVICE_SPECIFIC w backendach CPP/NDK i ServiceSpecificException w backendzie Java. Zobacz Obsługa błędów .
  • AIDL nie uruchamia automatycznie pul wątków, gdy wysyłane są obiekty wiążące. Muszą być uruchamiane ręcznie (patrz zarządzanie wątkami ).
  • AIDL nie przerywa w przypadku niesprawdzonych błędów transportu (HIDL Return przerywa działanie w przypadku niesprawdzonych błędów).
  • AIDL może deklarować tylko jeden typ na plik.
  • Argumenty AIDL można określić jako in/out/inout oprócz parametru wyjściowego (nie ma „synchronicznych wywołań zwrotnych”).
  • AIDL używa fd jako typu podstawowego zamiast uchwytu.
  • HIDL używa wersji głównych dla niezgodnych zmian i wersji pomocniczych dla zgodnych zmian. W AIDL wprowadzane są zmiany zgodne z poprzednimi wersjami. AIDL nie ma wyraźnej koncepcji głównych wersji; zamiast tego jest to włączone do nazw pakietów. Na przykład AIDL może używać nazwy pakietu bluetooth2 .