HIDL

Język definiowania interfejsu HAL (HIDL) to język opisywania interfejsu (IDL) służący do określania interfejsu między HAL a jego użytkownikami. HIDL umożliwia określanie typów i wywołań metod zebranych w interfejsach i pakietach. W szerszym znaczeniu HIDL to system komunikacji między bazami kodu, które mogą być kompilowane niezależnie.

HIDL jest przeznaczony do komunikacji między procesami (IPC). Interfejsy HAL utworzone za pomocą HDL są nazywane binderized HALs, ponieważ mogą komunikować się z innymi warstwami architektury za pomocą wywołań interprocess communication (IPC) bindera. Binderized HALs działają w oddzielnym procesie od klienta, który ich używa. W przypadku bibliotek, które muszą być powiązane z procesem, dostępna jest też opcja przepuszczania (passthrough) (nieobsługiwana w języku Java).

HIDL określa struktury danych i nazwy metod zorganizowane w interfejsach (podobnych do klas), które są zbierane w pakiety. Składnia HIDL jest znajoma programistom C++ i Java, ale ma inny zestaw słów kluczowych. HIDL używa też adnotacji w stylu Java.

Terminologia

W tej sekcji używamy następujących terminów związanych z HIDL:

binderized Wskazuje, że HIDL jest używany do wywoływania procedur zdalnych między procesami, co jest realizowane za pomocą mechanizmu podobnego do Binder. Zobacz też przepuszczanie.
wywołanie zwrotne, asynchroniczne Interfejs obsługiwany przez użytkownika HAL, przekazywany do HAL (za pomocą metody HIDL) i wywoływany przez HAL w dowolnym momencie w celu zwrócenia danych.
callback, synchroniczne Zwraca dane z implementacji metody HIDL serwera do klienta. Nieużywane w przypadku metod, które zwracają wartość void lub pojedynczą wartość prymitywną.
client Proces, który wywołuje metody określonego interfejsu. Proces HAL lub Android Framework może być klientem jednego interfejsu i serwerem drugiego. Zobacz też przechodzenie.
rozszerza Wskazuje interfejs, który dodaje metody lub typy do innego interfejsu. Interfejs może rozszerzać tylko jeden inny interfejs. Można go użyć do wdrożenia małej aktualizacji wersji w ramach tej samej nazwy pakietu lub do utworzenia nowego pakietu (np. rozszerzenia dostawcy) na podstawie starszego pakietu.
generuje Wskazuje metodę interfejsu, która zwraca wartości do klienta. 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 C++ lub Javie. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku: proces klienta wywołuje metody zaimplementowane przez proces serwera.
w jedną stronę Gdy jest stosowana do metody HIDL, oznacza, że metoda nie zwraca żadnych wartości i nie blokuje.
paczka Zbiór interfejsów i typów danych, które mają tę samą wersję.
przekazywanie Tryb HIDL, w którym serwer jest biblioteką wspólną, dlopenedytowaną przez klienta. W trybie przekazywania klient i serwer to ten sam proces, ale z oddzielnymi bazami kodu. Służy tylko do przenoszenia starszych baz kodu do modelu HIDL. Zobacz też Binderized.
serwer Proces, który implementuje metody interfejsu. Zobacz też przechodzenie.
transport Infrastruktura HIDL, która przesyła dane między serwerem a klientem.
Wersja Wersja pakietu. Składa się z 2 liczb całkowitych: głównej i mniejszej. Mniejsze aktualizacje wersji mogą dodawać (ale nie zmieniać) typów i metod.

Projekt HIDL

Celem HIDL jest możliwość zastąpienia platformy Android bez konieczności ponownego tworzenia interfejsów HAL. Interfejsy HAL są tworzone przez dostawców lub producentów SOC i przechowywane na partycji /vendor na urządzeniu. Umożliwia to zastąpienie platformy Androida (na własnej partycji) za pomocą aktualizacji OTA bez ponownego kompilowania interfejsów HAL.

Projekt HIDL zapewnia równowagę w zakresie tych kwestii:

  • Interoperacyjność. Tworzenie niezawodnych interfejsów interoperacyjnych pomiędzy procesami, które mogą być kompilowane z różnymi architekturami, zestawami narzędzi i konfiguracjami kompilacji. Interfejce HIDL są wersjonowane i nie można ich zmieniać po opublikowaniu.
  • Skuteczność. HIDL stara się zminimalizować 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 udostępnia też interfejsy pamięci współdzielonej. Ponieważ wywołania RPC są z zasady dość powolne, HIDL obsługuje 2 sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzielona i szybka kolejka komunikatów (Fast Message Queue, FMQ).
  • Intuicyjny. HIDL pozwala uniknąć kłopotliwych problemów z własnością pamięci, ponieważ w przypadku wywołań RPC używa tylko parametrów in (patrz język definiowania interfejsów Androida (AIDL)); wartości, których nie można efektywnie zwracać z metod, są zwracane za pomocą funkcji wywołania zwrotnego. Ani przekazywanie danych do HIDL w celu ich przeniesienia, ani odbieranie danych z HIDL nie zmienia ich własności – własność danych zawsze pozostaje przy funkcji wywołującej. Dane muszą być przechowywane tylko przez czas działania wywoływanej funkcji i mogą zostać zniszczone bezpośrednio po jej zakończeniu.

Korzystanie z trybu przekazywania

Aby zaktualizować urządzenia z Androidem w starszych wersjach do Androida O, możesz opakować zarówno konwencjonalne (i starsze) interfejsy HAL w nowym interfejsie HIDL, który obsługuje interfejs HAL w trybie binderized i w tym samym procesie (przesyłającym). Ta otoczka jest przezroczysta zarówno dla HAL, jak i ramy Androida.

Tryb przekazywania jest dostępny tylko w przypadku klientów i implementacji w C++. Urządzenia z wersjami Androida wcześniejszymi niż 10.0 nie mają interfejsów HAL napisanych w języku Java, więc interfejsy HAL w języku Java są z założenia obsługiwane przez Binder.

Podczas kompilowania pliku .hal funkcja hidl-gen generuje dodatkowy plik nagłówka przekazywania dalej BsFoo.h, który uzupełnia nagłówki używane do komunikacji z binderem. Ten nagłówek definiuje funkcje, które mają być przekazywane dalej. Ponieważ funkcje przekazywania dalej HAL działają w tym samym procesie, w którym są wywoływane, w większości przypadków są one wywoływane przez bezpośrednie wywołanie funkcji (w tym samym wątku).dlopen Metody oneway są wykonywane w ramach własnego wątku, ponieważ nie mają czekać na przetworzenie przez HAL (co oznacza, że każdy interfejs HAL, który używa metod oneway w trybie przekazywania, musi być bezpieczny w zakresie wątków).

Funkcja IFoo.hal otacza metody wygenerowane przez HIDL, aby udostępnić dodatkowe funkcje (np. uruchamianie transakcji oneway w innym wątku).BsFoo.h Plik ten jest podobny do pliku BpFoo.h, ale zamiast przekazywania wywołań IPC za pomocą bindera wywołuje bezpośrednio żądane funkcje. Przyszłe implementacje HAL mogą zawierać wiele implementacji, np. FooFast HAL i FooAccurate HAL. W takich przypadkach dla każdej dodatkowej implementacji zostanie utworzony plik (np. PTFooFast.cppPTFooAccurate.cpp).

Binderyzacja interfejsów HAL typu passthrough

Implementacje HAL, które obsługują tryb przekazywania, możesz zbindować. W przypadku interfejsu HAL a.b.c.d@M.N::IFoo tworzone są 2 pakiety:

  • a.b.c.d@M.N::IFoo-impl. Zawiera implementację HAL i funkcję IFoo* HIDL_FETCH_IFoo(const char* name). Na starszych urządzeniach ten pakiet jest dlopen, a implementacja jest tworzona za pomocą HIDL_FETCH_IFoo. Kod podstawowy możesz wygenerować, używając hidl-gen-Lc++-impl oraz -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Otwiera interfejs HAL z przepuszczaniem i rejestruje się jako usługa z binderem, co umożliwia użycie tej samej implementacji interfejsu HAL zarówno w trybie przepuszczania, jak i z binderem.

W przypadku typu IFoo możesz wywołać metodę sp<IFoo> IFoo::getService(string name, bool getStub), aby uzyskać dostęp do instancji IFoo. Jeśli getStub ma wartość prawda, getService próbuje otworzyć HAL tylko w trybie przekazywania. Jeśli getStub ma wartość false, getService próbuje znaleźć usługę zbinderem; jeśli się to nie uda, próbuje znaleźć usługę przelotową. Parametru getStubnie należy używać w żadnym innym przypadku niż w pozycji defaultPassthroughServiceImplementation. (urządzenia z Androidem O są w pełni zintegrowane z binderem, więc otwieranie usługi w trybie przekazywania nie jest dozwolone).

Gramatyka HIDL

Język HIDL jest z założenia podobny do języka C (ale nie używa preprocesora C). Wszystkie znaki interpunkcyjne, które nie zostały opisane poniżej (z wyjątkiem oczywistego użycia znaków =|), są częścią gramatyki.

Uwaga: szczegółowe informacje o stylu kodu HIDL znajdziesz w przewodniku po stylu kodu.

  • /** */ oznacza komentarz do dokumentacji. Można ich używać tylko w przypadku deklaracji typu, metody, pola i wartości wyliczenia.
  • /* */ oznacza komentarz wielowierszowy.
  • // oznacza komentarz do końca wiersza. Poza // nowe wiersze są takie same jak inne odstępy.
  • W przykładowej gramatyce poniżej tekst od // do końca wiersza nie jest częścią gramatyki, ale komentarzem do niej.
  • [empty] oznacza, że termin może być pusty.
  • ? po literalu lub terminie oznacza, że jest to opcjonalne.
  • ... wskazuje sekwencję zawierającą co najmniej 1 element z znakami interpunkcyjnymi w określonej kolejności. W HIDL nie ma argumentów zmiennoargumentowych.
  • Elementy sekwencji są rozdzielane przecinkami.
  • Pomiędzy elementami, w tym po ostatnim, należy umieszczać średniki.
  • WIELKIE_LITERY to symbol nieterminalny.
  • italics to rodzina tokenów, np. integer lub identifier (standardowe reguły analizowania C).
  • constexpr to wyrażenie stałe w stylu C (takie jak 1 + 11L << 3).
  • import_name to nazwa pakietu lub interfejsu, kwalifikowana zgodnie z opisem w  wersji sterownika HIDL.
  • Małe litery words to tokeny dosłowne.

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