HIDL

Język definicji interfejsu HAL lub HIDL to język opisu interfejsu (IDL) służący do określania interfejsu między warstwą HAL a jej użytkownikami. HIDL umożliwia określanie typów i wywołań metod, zebranych w interfejsy i pakiety. Mówiąc szerzej, HIDL to system komunikacji między bazami kodu, które mogą być kompilowane niezależnie. Począwszy od Androida 10, HIDL jest przestarzały, a Android migruje, aby używać AIDL wszędzie.

HIDL jest przeznaczony do komunikacji międzyprocesowej (IPC). Warstwy HAL utworzone za pomocą HDL są nazywane powiązanymi warstwami HAL, ponieważ mogą komunikować się z innymi warstwami architektury za pomocą wywołań komunikacji międzyprocesowej (IPC) powiązania. Powiązane warstwy HAL są uruchamiane w oddzielnym procesie od klienta, który ich używa. W przypadku bibliotek, które muszą być połączone z procesem, dostępny jest również tryb przekazywania (nieobsługiwany w Javie).

HIDL określa struktury danych i sygnatury metod, zorganizowane w interfejsy (podobne do klas), które są gromadzone w pakietach. Składnia HIDL wygląda znajomo dla programistów C++ i Java, ale z innym zestawem słów kluczowych. HIDL używa również adnotacji w stylu Java.

Terminologia

W tej sekcji zastosowano następujące terminy związane z HIDL:

spojony Wskazuje, że HIDL jest używany do zdalnych wywołań procedur między procesami, zaimplementowanych za pośrednictwem mechanizmu podobnego do Bindera. Zobacz także przejście .
wywołanie zwrotne, asynchroniczne Interfejs obsługiwany przez użytkownika warstwy HAL, przekazywany do warstwy HAL (za pomocą metody HIDL) i wywoływany przez warstwę HAL w celu zwrócenia danych w dowolnym momencie.
wywołanie zwrotne, synchroniczne Zwraca dane z implementacji metody HIDL serwera do klienta. Nieużywane w przypadku metod, które zwracają void lub pojedynczą wartość pierwotną.
klient Proces wywołujący metody określonego interfejsu. Proces platformy HAL lub Android może być klientem jednego interfejsu i serwerem innego. Zobacz także przejście .
rozciąga się Wskazuje interfejs, który dodaje metody i/lub typy do innego interfejsu. Interfejs może rozszerzać tylko jeden inny interfejs. Może być używany do przyrostu podrzędnej wersji w tej samej nazwie pakietu lub do nowego pakietu (np. rozszerzenia dostawcy) w celu zbudowania na starszym pakiecie.
generuje Wskazuje metodę interfejsu, która zwraca wartości do klienta. Aby zwrócić jedną wartość inną niż prymitywna lub więcej niż jedną wartość, generowana jest synchroniczna funkcja wywołania zwrotnego.
interfejs Zbiór metod i typów. Przetłumaczone na klasę w C++ lub Javie. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku: proces klienta wywołuje metody zaimplementowane przez proces serwera.
jednokierunkowa W przypadku zastosowania do metody HIDL wskazuje, że metoda nie zwraca żadnych wartości i nie blokuje.
pakiet Zbiór interfejsów i typów danych współdzielących wersję.
Przejść przez Tryb dlopen , w którym serwerem jest biblioteka współdzielona, ​​otwierana przez klienta. W trybie tranzytowym klient i serwer to ten sam proces, ale oddzielne bazy kodu. Używany tylko do przeniesienia starszych baz kodu do modelu HIDL. Zobacz także zbindowane .
serwer Proces implementujący metody interfejsu. Zobacz także przejście .
transport Infrastruktura HIDL, która przenosi dane między serwerem a klientem.
wersja Wersja pakietu. Składa się z dwóch liczb całkowitych, major i minor. Drobne przyrosty wersji mogą dodawać (ale nie zmieniać) typy i metody.

Projekt HIDL

Celem HIDL jest to, aby framework Androida mógł zostać zastąpiony bez konieczności przebudowywania warstw HAL. Warstwy HAL będą budowane przez dostawców lub twórców SOC i umieszczane na partycji /vendor na urządzeniu, umożliwiając zastąpienie platformy Android we własnej partycji przez OTA bez ponownej kompilacji warstw HAL.

Projekt HIDL równoważy następujące kwestie:

  • Interoperacyjność . Twórz niezawodne interoperacyjne interfejsy między procesami, które można kompilować z różnymi architekturami, łańcuchami narzędzi i konfiguracjami kompilacji. Interfejsy HIDL są wersjonowane i nie można ich zmienić po ich opublikowaniu.
  • Wydajność . HIDL próbuje zminimalizować liczbę operacji kopiowania. Dane zdefiniowane w HIDL są dostarczane do kodu C++ w standardowych strukturach danych układu C++, których można używać bez rozpakowywania. HIDL udostępnia również interfejsy pamięci współdzielonej, a ponieważ wywołania RPC są z natury nieco powolne, HIDL obsługuje dwa sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzieloną i szybką kolejkę komunikatów (FMQ).
  • Intuicyjny . HIDL pozwala uniknąć drażliwych problemów związanych z własnością pamięci, używając tylko in dla RPC (patrz Android Interface Definition Language (AIDL) ); wartości, których nie można wydajnie zwrócić z metod, są zwracane za pośrednictwem funkcji wywołania zwrotnego. Ani przekazywanie danych do HIDL w celu przesłania, ani odbieranie danych z HIDL nie zmienia własności danych — własność zawsze pozostaje w gestii funkcji wywołującej. Dane muszą być przechowywane tylko przez czas trwania wywołanej funkcji i mogą zostać zniszczone natychmiast po powrocie wywołanej funkcji.

Korzystanie z trybu przekazywania

Aby zaktualizować urządzenia z wcześniejszymi wersjami Androida do Androida O, można umieścić zarówno konwencjonalne (i starsze) warstwy HAL w nowym interfejsie HIDL, który obsługuje warstwę HAL w trybach binderized i tym samym procesie (przekazywanie). To opakowanie jest przezroczyste zarówno dla środowiska HAL, jak i systemu Android.

Tryb przekazywania jest dostępny tylko dla klientów i implementacji języka C++. Urządzenia z wcześniejszymi wersjami Androida nie mają warstw HAL napisanych w języku Java, więc warstwy Java HAL są z natury powiązane.

Kiedy plik .hal jest kompilowany, hidl-gen tworzy dodatkowy plik nagłówkowy BsFoo.h oprócz nagłówków używanych do komunikacji bindera; ten nagłówek definiuje funkcje, które mają zostać dlopen . Ponieważ HAL-y przekazujące działają w tym samym procesie, w którym są wywoływane, w większości przypadków metody przekazujące są wywoływane przez bezpośrednie wywołanie funkcji (ten sam wątek). metody oneway działają we własnym wątku, ponieważ nie mają czekać, aż warstwa HAL je przetworzy (oznacza to, że każda warstwa HAL, która używa metod oneway w trybie przekazywania, musi być bezpieczna dla wątków).

Biorąc pod uwagę IFoo.hal , BsFoo.h zawija metody generowane przez oneway , aby zapewnić dodatkowe funkcje (takie jak uruchamianie transakcji jednokierunkowych w innym wątku). Ten plik jest podobny do BpFoo.h , jednak zamiast przekazywania połączeń IPC za pomocą bindera, żądane funkcje są wywoływane bezpośrednio. Przyszłe implementacje HAL mogą zapewniać wiele implementacji, takich jak FooFast HAL i FooAccurate HAL. W takich przypadkach tworzony byłby plik dla każdej dodatkowej implementacji (np. PTFooFast.cpp i PTFooAccurate.cpp ).

Wiążące przekazywanie HAL

Implementacje HAL, które obsługują tryb przekazywania, można wiązać. Biorąc pod uwagę interfejs HAL abcd@MN::IFoo , tworzone są dwa pakiety:

  • abcd@MN::IFoo-impl . Zawiera implementację warstwy HAL i udostępnia funkcję IFoo* HIDL_FETCH_IFoo(const char* name) . Na starszych urządzeniach ten pakiet jest otwierany, a instancja implementacji jest HIDL_FETCH_IFoo dlopen Możesz wygenerować kod podstawowy za pomocą hidl-gen i -Lc++-impl i -Landroidbp-impl .
  • abcd@MN::IFoo-service . Otwiera warstwę HAL przekazującą i rejestruje się jako usługa powiązana, umożliwiając użycie tej samej implementacji warstwy HAL zarówno jako tranzytowej, jak i powiązanej.

Biorąc pod uwagę typ IFoo , możesz wywołać sp<IFoo> IFoo::getService(string name, bool getStub) aby uzyskać dostęp do instancji IFoo . Jeśli getStub ma wartość true, getService próbuje otworzyć warstwę HAL tylko w trybie przekazywania. Jeśli getStub ma wartość false, getService próbuje znaleźć powiązaną usługę; jeśli to się nie powiedzie, próbuje znaleźć usługę przekazywania. Parametr getStub nigdy nie powinien być używany, z wyjątkiem defaultPassthroughServiceImplementation . (Urządzenia uruchamiane z systemem Android O są urządzeniami w pełni powiązanymi, więc otwieranie usługi w trybie przekazywania jest niedozwolone).

gramatyki HIDL

Z założenia język HIDL jest podobny do C (ale nie korzysta z preprocesora C). Wszystkie znaki interpunkcyjne nieopisane poniżej (oprócz oczywistego użycia = i | ) są częścią gramatyki.

Uwaga: Aby uzyskać szczegółowe informacje na temat stylu kodu HIDL, zobacz Przewodnik po stylach kodu .

  • /** */ oznacza komentarz do dokumentacji. Można je zastosować tylko do deklaracji typu, metody, pola i wartości wyliczenia.
  • /* */ oznacza komentarz wielowierszowy.
  • // wskazuje komentarz do końca linii. Oprócz // znaki nowej linii są takie same jak wszystkie inne spacje.
  • W poniższej przykładowej gramatyce tekst od // do końca wiersza nie jest częścią gramatyki, ale jest komentarzem do gramatyki.
  • [empty] oznacza, że ​​termin może być pusty.
  • ? następujący po dosłownym lub terminie oznacza, że ​​jest opcjonalny.
  • ... wskazuje sekwencję zawierającą zero lub więcej elementów z rozdzielającymi znakami interpunkcyjnymi, jak wskazano. W języku HIDL nie ma zmiennych argumentów.
  • Przecinki oddzielają elementy sekwencji.
  • Średniki kończą każdy element, w tym ostatni element.
  • WIELKIE LITERY to nieterminal.
  • italics to rodzina tokenów, taka jak integer lub identifier (standardowe reguły analizy składni C).
  • constexpr jest wyrażeniem stałym w stylu C (takim jak 1 + 1 i 1L << 3 ).
  • import_name to nazwa pakietu lub interfejsu kwalifikowana zgodnie z opisem w sekcji Wersjonowanie HIDL .
  • words pisane małymi literami są dosłownymi tokenami.

Przykład:

ROOT =
    PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... }  // not for types.hal
  | PACKAGE IMPORTS ITEM ITEM...  // only for types.hal; no method definitions

ITEM =
    ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;
  |  safe_union identifier { UFIELD; UFIELD; ...};
  |  struct identifier { SFIELD; SFIELD; ...};  // Note - no forward declarations
  |  union identifier { UFIELD; UFIELD; ...};
  |  enum identifier: TYPE { ENUM_ENTRY, ENUM_ENTRY ... }; // TYPE = enum or scalar
  |  typedef TYPE identifier;

VERSION = integer.integer;

PACKAGE = package android.hardware.identifier[.identifier[...]]@VERSION;

PREAMBLE = interface identifier EXTENDS

EXTENDS = <empty> | extends import_name  // must be interface, not package

GENERATES = generates (FIELD, FIELD ...)

// allows the Binder interface to be used as a type
// (similar to typedef'ing the final identifier)
IMPORTS =
   [empty]
  |  IMPORTS import import_name;

TYPE =
  uint8_t | int8_t | uint16_t | int16_t | uint32_t | int32_t | uint64_t | int64_t |
 float | double | bool | string
|  identifier  // must be defined as a typedef, struct, union, enum or import
               // including those defined later in the file
|  memory
|  pointer
|  vec<TYPE>
|  bitfield<TYPE>  // TYPE is user-defined enum
|  fmq_sync<TYPE>
|  fmq_unsync<TYPE>
|  TYPE[SIZE]

FIELD =
   TYPE identifier

UFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...};
  |  struct identifier { FIELD; FIELD; ...};
  |  union identifier { FIELD; FIELD; ...};
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SIZE =  // Must be greater than zero
     constexpr

ANNOTATIONS =
     [empty]
  |  ANNOTATIONS ANNOTATION

ANNOTATION =
  |  @identifier
  |  @identifier(VALUE)
  |  @identifier(ANNO_ENTRY, ANNO_ENTRY  ...)

ANNO_ENTRY =
     identifier=VALUE

VALUE =
     "any text including \" and other escapes"
  |  constexpr
  |  {VALUE, VALUE ...}  // only in annotations

ENUM_ENTRY =
     identifier
  |  identifier = constexpr