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ślenie 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 i Android jest migrowany, aby wszędzie używać AIDL .

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

HIDL określa struktury danych i sygnatury metod zorganizowane w interfejsy (podobne do klasy), które są zbierane w pakiety. Składnia HIDL wygląda znajomo programistom C++ i Java, ale ma inny zestaw 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:

spoiwowe Wskazuje, że HIDL jest używany do zdalnych wywołań procedur między procesami, zaimplementowanych za pomocą mechanizmu podobnego do Bindera. Zobacz także przejście .
wywołanie zwrotne, asynchroniczne Interfejs obsługiwany przez użytkownika HAL, przekazywany do HAL (przy użyciu metody HIDL) i wywoływany przez 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 zwracających void lub pojedynczą wartość pierwotną.
klient Proces wywołujący metody określonego interfejsu. Proces frameworku 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żna go użyć do mniejszej wersji pakietu o tej samej nazwie lub do nowego pakietu (np. rozszerzenia dostawcy), aby zbudować na starszym pakiecie.
generuje Wskazuje metodę interfejsu, która zwraca wartości klientowi. Aby zwrócić jedną wartość niepierwotną 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 języku C++ lub Java. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku: proces klienta wywołuje metody zaimplementowane przez proces serwera.
jednokierunkowa Po zastosowaniu 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 HIDL, w którym serwer jest biblioteką współdzieloną dlopen przez klienta. W trybie przekazywania klient i serwer to ten sam proces, ale oddzielne bazy kodu. Używany tylko do przenoszenia starszych baz kodu do modelu HIDL. Zobacz także Oprawiony .
serwer Proces implementujący metody interfejsu. Zobacz także przejście .
transport Infrastruktura HIDL, która przenosi dane pomiędzy serwerem a klientem.
wersja Wersja pakietu. Składa się z dwóch liczb całkowitych, większej i mniejszej. Niewielkie przyrosty wersji mogą dodawać (ale nie zmieniać) typy i metody.

Projekt HIDL

Celem HIDL jest umożliwienie wymiany frameworku Android bez konieczności przebudowywania warstw HAL. Warstwy HAL będą tworzone przez dostawców lub twórców SOC i umieszczane w partycji /vendor na urządzeniu, co umożliwi zastąpienie platformy Android na jej własnej partycji przez wersję OTA bez konieczności ponownej kompilacji warstw HAL.

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

  • Interoperacyjność . Twórz niezawodnie interoperacyjne interfejsy pomiędzy procesami, które można kompilować przy użyciu różnych architektur, łańcuchów narzędzi i konfiguracji kompilacji. Interfejsy HIDL są wersjonowane i nie można ich zmieniać po ich opublikowaniu.
  • Efektywność . HIDL stara się minimalizować liczbę operacji kopiowania. Dane zdefiniowane w HIDL są dostarczane do kodu C++ w standardowych strukturach danych C++, których można używać bez rozpakowywania. HIDL zapewnia także interfejsy pamięci współdzielonej, a ponieważ procedury RPC są z natury nieco powolne, HIDL obsługuje dwa sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzielona i szybka kolejka 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 Język definicji interfejsu Androida (AIDL) ); wartości, których nie można efektywnie zwrócić z metod, zwracane są za pomocą 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łasnością funkcji wywołującej. Dane muszą trwać tylko przez czas trwania wywoływanej funkcji i mogą zostać zniszczone natychmiast po powrocie wywołanej funkcji.

Korzystanie z trybu przejścia

Aby zaktualizować urządzenia z wcześniejszymi wersjami Androida do Androida O, możesz zawinąć zarówno konwencjonalne (i starsze) warstwy HAL w nowy interfejs HIDL, który obsługuje warstwę HAL w trybach powiązanych i tego samego procesu (przejściowego). To opakowanie jest przezroczyste zarówno dla środowiska HAL, jak i platformy 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 HAL Java są z natury powiązane.

Kiedy kompilowany jest plik .hal , hidl-gen tworzy dodatkowy plik nagłówkowy tranzytowy BsFoo.h oprócz nagłówków używanych do komunikacji w ramach segregatora; ten nagłówek definiuje funkcje, które mają być dlopen ed. Ponieważ warstwy HAL z przekazywaniem działają w tym samym procesie, w którym są wywoływane, w większości przypadków metody przejściowe 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 korzystająca z metod oneway w trybie przekazywania musi być bezpieczna dla wątków).

Biorąc pod uwagę IFoo.hal , BsFoo.h otacza metody wygenerowane przez HIDL, aby zapewnić dodatkowe funkcje (takie jak wykonywanie transakcji oneway w innym wątku). Plik ten jest podobny do BpFoo.h , jednak zamiast przekazywać wywołania IPC za pomocą bindera, żądane funkcje są wywoływane bezpośrednio. Przyszłe implementacje warstw HAL mogą zapewniać wiele implementacji, takich jak FooFast HAL i FooAccurate HAL. W takich przypadkach dla każdej dodatkowej implementacji zostanie utworzony plik (np. PTFooFast.cpp i PTFooAccurate.cpp ).

Binderizujące przejścia HAL

Można powiązać implementacje HAL obsługujące tryb przekazywania. 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 dlopen i tworzona jest implementacja za pomocą HIDL_FETCH_IFoo . Możesz wygenerować kod podstawowy za pomocą hidl-gen i -Lc++-impl i -Landroidbp-impl .
  • abcd@MN::IFoo-service . Otwiera tranzytową warstwę HAL i rejestruje się jako usługa powiązana, umożliwiając użycie tej samej implementacji HAL zarówno jako przejściowej, 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 przekazu. Jeśli getStub ma wartość false, getService próbuje znaleźć usługę powiązaną; jeśli to się nie powiedzie, próbuje znaleźć usługę przekazywania. Parametr getStub nie powinien być nigdy 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 przekazującym jest niedozwolone).

Gramatyka HIDL

Z założenia język HIDL jest podobny do C (ale nie wykorzystuje preprocesora C). Cała interpunkcja nieopisana poniżej (poza oczywistym użyciem = i | ) jest częścią gramatyki.

Uwaga: szczegółowe informacje na temat stylu kodu HIDL można znaleźć w Przewodniku po stylu kodu .

  • /** */ wskazuje komentarz do dokumentacji. Można je zastosować tylko do deklaracji typu, metody, pola i wartości wyliczeniowej.
  • /* */ oznacza komentarz wielowierszowy.
  • // wskazuje komentarz na końcu linii. Oprócz // znaki nowej linii są takie same jak inne białe znaki.
  • W poniższym przykładzie gramatyki tekst od // do końca wiersza nie jest częścią gramatyki, ale jest komentarzem do gramatyki.
  • [empty] oznacza, że ​​termin może być pusty.
  • ? po literale lub określeniu oznacza, że ​​jest to opcjonalne.
  • ... wskazuje sekwencję zawierającą zero lub więcej elementów oddzielających je znakami interpunkcyjnymi, jak wskazano. W języku HIDL nie ma argumentów variadic.
  • Przecinki oddzielają elementy sekwencji.
  • Średniki kończą każdy element, łącznie z ostatnim elementem.
  • WIELKIE LITERY nie są terminalami.
  • italics to rodzina tokenów, taka jak integer lub identifier (standardowe reguły analizy C).
  • constexpr to wyrażenie stałe w stylu C (takie 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ą symbolami dosłownymi.

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