HIDL

Язык описания интерфейса HAL (HIDL) – это язык описания интерфейса (IDL), который позволяет задавать интерфейс между HAL и его пользователями. HIDL позволяет указывать типы и вызовы методов, собранные в интерфейсы и пакеты. В более широком смысле HIDL – это система для обмена данными между базами кода, которые могут компилироваться независимо друг от друга.

HIDL предназначен для межпроцессного взаимодействия (IPC). HAL, созданные с помощью HDL, называются binderized HAL, поскольку они могут взаимодействовать с другими уровнями архитектуры с помощью вызовов межпроцессного взаимодействия (IPC) связывателя. HAL-интерфейсы, использующие Binder, выполняются в отдельном процессе от клиента, который их использует. Для библиотек, которые должны быть связаны с процессом, также доступен режим сквозной передачи (не поддерживается в Java).

HIDL определяет структуры данных и сигнатуры методов, организованные в интерфейсы (похожие на классы), которые собираются в пакеты. Синтаксис HIDL похож на C++ и Java, но с другим набором ключевых слов. В HIDL также используются аннотации в стиле Java.

Терминология

В этом разделе используются следующие термины, связанные с HIDL:

binderized Указывает, что HIDL используется для вызовов удаленных процедур между процессами, реализованных с помощью механизма, похожего на Binder. См. также передачу.
обратный вызов, асинхронный Интерфейс, предоставляемый пользователем HAL, передаваемый в HAL (с помощью метода HIDL) и вызываемый HAL для возврата данных в любое время.
callback, синхронный Возвращает данные из реализации метода HIDL сервера клиенту. Не используется для методов, которые возвращают пустое значение или одно примитивное значение.
client Процесс, который вызывает методы определенного интерфейса. Процесс HAL или фреймворка Android может быть клиентом одного интерфейса и сервером другого. См. также passthrough.
extends Указывает на интерфейс, который добавляет методы и/или типы в другой интерфейс. Интерфейс может расширять только один другой интерфейс. Может использоваться для промежуточной версии в том же пакете или для нового пакета (например, расширения поставщика), созданного на основе старого.
генерирует Указывает метод интерфейса, который возвращает значения клиенту. Чтобы вернуть одно непримитивное значение или несколько значений, генерируется синхронная функция обратного вызова.
интерфейс Набор методов и типов. Преобразуется в класс на C++ или Java. Все методы в интерфейсе вызываются в одном направлении: клиентский процесс вызывает методы, реализованные серверным процессом.
oneway При применении к методу HIDL указывает, что метод не возвращает значений и не блокирует работу.
пакет Набор интерфейсов и типов данных, имеющих одну версию.
сквозная передача Режим HIDL, в котором сервер является общей библиотекой, dlopenзагружаемой клиентом. В режиме сквозной передачи клиент и сервер представляют собой один и тот же процесс, но разные базы кода. Используется только для переноса устаревших баз кода в модель HIDL. См. также Binderized.
сервер Процесс, реализующий методы интерфейса. См. также passthrough.
transport Инфраструктура HIDL, которая передает данные между сервером и клиентом.
версия Версия пакета. Состоит из двух целых чисел: основного и дополнительного. В промежуточных версиях могут добавляться (но не изменяться) типы и методы.

Дизайн HIDL

Цель HIDL – обеспечить возможность замены фреймворка Android без пересборки HAL. HAL создаются поставщиками или производителями процессоров и помещаются в раздел /vendor на устройстве. Это позволяет заменять фреймворк Android, который находится в собственном разделе, с помощью OTA-обновлений без перекомпиляции HAL.

При разработке HIDL учитывались следующие факторы:

  • Совместимость. Создавайте надежные совместимые интерфейсы между процессами, которые могут быть скомпилированы с использованием различных архитектур, цепочек инструментов и конфигураций сборки. Интерфейсы HIDL имеют версии и не могут быть изменены после публикации.
  • Эффективность. HIDL пытается минимизировать количество операций копирования. Данные, определенные в HIDL, передаются в код C++ в виде структур данных стандартной раскладки C++, которые можно использовать без распаковки. HIDL также предоставляет интерфейсы общей памяти, а поскольку RPC по своей сути довольно медленные, HIDL поддерживает два способа передачи данных без использования вызова RPC: общую память и очередь быстрых сообщений (FMQ).
  • Интуитивно понятный. HIDL позволяет избежать сложных проблем с владением памятью, используя для RPC только параметры in (см. Android Interface Definition Language (AIDL)). Значения, которые нельзя эффективно вернуть из методов, возвращаются с помощью функций обратного вызова. Передача данных в HIDL и получение данных из HIDL не меняют права собственности на данные. Они всегда остаются у вызывающей функции. Данные должны сохраняться только на время выполнения вызванной функции и могут быть удалены сразу после ее завершения.

Как использовать режим трансляции

Чтобы обновить устройства с более ранними версиями Android до Android O, вы можете упаковать как обычные, так и устаревшие HAL в новый интерфейс HIDL, который обслуживает HAL в режимах связывания и сквозной передачи. Такая упаковка прозрачна как для HAL, так и для фреймворка Android.

Режим сквозной передачи доступен только для клиентов и реализаций на языке C++. Устройства с более ранними версиями Android не имеют HAL, написанных на Java, поэтому HAL Java по своей природе связаны.

При компиляции файла .hal hidl-gen создает дополнительный сквозной заголовочный файл BsFoo.h в дополнение к заголовкам, используемым для связи с Binder. Этот заголовок определяет функции, которые должны быть dlopen. Поскольку сквозные HAL выполняются в том же процессе, в котором они вызываются, в большинстве случаев сквозные методы вызываются прямым вызовом функции (в том же потоке). Методы oneway выполняются в собственном потоке, поскольку они не предназначены для ожидания их обработки HAL (это означает, что любой HAL, использующий методы oneway в режиме сквозной передачи, должен быть потокобезопасным).

Используя IFoo.hal, BsFoo.h обертывает методы, сгенерированные HIDL, чтобы предоставить дополнительные функции (например, чтобы транзакции oneway выполнялись в другом потоке). Этот файл похож на BpFoo.h, но вместо передачи вызовов IPC с помощью binder вызываются нужные функции. В будущих реализациях HAL может быть несколько реализаций, например FooFast HAL и FooAccurate HAL. В таких случаях создается файл для каждой дополнительной реализации (например, PTFooFast.cpp и PTFooAccurate.cpp).

Как перевести HAL со сквозной передачей на Binder

Вы можете использовать binder для реализаций HAL, поддерживающих режим сквозной передачи. Для интерфейса HAL a.b.c.d@M.N::IFoo создаются два пакета:

  • a.b.c.d@M.N::IFoo-impl – содержит реализацию HAL и предоставляет функцию IFoo* HIDL_FETCH_IFoo(const char* name). На устройствах устаревших версий этот пакет dlopen, а реализация создается с помощью HIDL_FETCH_IFoo. Вы можете сгенерировать базовый код с помощью hidl-gen, -Lc++-impl и -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Открывает HAL сквозной передачи и регистрирует себя как сервис Binder, позволяя использовать одну и ту же реализацию HAL как для сквозной передачи, так и для Binder.

Учитывая тип IFoo, вы можете вызвать sp<IFoo> IFoo::getService(string name, bool getStub), чтобы получить доступ к экземпляру IFoo. Если getStub имеет значение true, getService пытается открыть HAL только в режиме сквозной передачи. Если значение getStub – false, getService пытается найти сервис, связанный с Binder, а если это не удается, то сервис с передачей данных. Параметр getStub никогда не следует использовать, кроме как в defaultPassthroughServiceImplementation. (Устройства, выпущенные с Android O, полностью поддерживают Binder, поэтому открывать сервис в режиме сквозной передачи запрещено.)

Грамматика HIDL

По замыслу язык HIDL похож на C (но не использует препроцессор C). Все знаки препинания, не описанные ниже (кроме очевидного использования = и |), относятся к грамматике.

Примечание. Подробные сведения о стиле кода HIDL можно найти в руководстве по стилю кода.

  • /** */ – комментарий к документации. Их можно применять только к объявлениям типа, метода, поля и значения перечисления.
  • /* */ – многострочный комментарий.
  • // указывает на комментарий в конце строки. Помимо //, символы новой строки ничем не отличаются от других пробельных символов.
  • В примере грамматики ниже текст от // до конца строки не является частью грамматики, а представляет собой комментарий к ней.
  • [empty] означает, что термин может быть пустым.
  • ? после литерала или термина означает, что он необязателен.
  • ... – последовательность, содержащая ноль или более элементов с указанными знаками препинания. В HIDL нет аргументов переменной длины.
  • Элементы последовательности разделяются запятыми.
  • Точка с запятой ставится после каждого элемента, включая последний.
  • ВЕРХНИЙ РЕГИСТР – нетерминальный символ.
  • italics – это семейство токенов, например integer или identifier (стандартные правила синтаксического анализа C).
  • constexpr  – это константное выражение в стиле C (например, 1 + 1 и 1L << 3).
  • import_name – название пакета или интерфейса, соответствующее требованиям, описанным в разделе Версии HIDL.
  • Токены в нижнем регистре words являются литеральными.

Пример:

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