HIDL

El lenguaje de definición de interfaz de HAL o HIDL es un lenguaje de descripción de interfaz (IDL) para especificar la interfaz entre un HAL y sus usuarios. HIDL permite especificar tipos y llamadas a métodos, que se recopilan en interfaces y paquetes. En términos más generales, HIDL es un sistema para comunicarse entre bases de código que se pueden compilar de forma independiente.

HIDL está diseñado para usarse en la comunicación entre procesos (IPC). Los HAL creados con HDL se llaman HAL vinculados, ya que pueden comunicarse con otras capas de arquitectura mediante llamadas de comunicación entre procesos (IPC) de Binder. Los HAL vinculados se ejecutan en un proceso independiente del cliente que los usa. Para las bibliotecas que deben vincularse a un proceso, también está disponible un modo de transferencia (no compatible con Java).

HIDL especifica estructuras de datos y firmas de métodos, organizadas en interfaces (similares a una clase) que se recopilan en paquetes. La sintaxis de HIDL les resulta familiar a los programadores de C++ y Java, pero con un conjunto diferente de palabras clave. HIDL también usa anotaciones de estilo Java.

Terminología

En esta sección, se usan los siguientes términos relacionados con HIDL:

Binderized Indica que se usa HIDL para las llamadas de procedimiento remoto entre procesos, que se implementan a través de un mecanismo similar a Binder. Consulta también transferencia.
devolución de llamada, asíncrona Interfaz que entrega un usuario de HAL, se pasa al HAL (con un método HIDL) y que llama el HAL para mostrar datos en cualquier momento.
devolución de llamada, síncrona Muestra datos de la implementación del método HIDL de un servidor al cliente. No se usa para métodos que muestran un valor nulo o un solo valor primitivo.
cliente Es el proceso que llama a los métodos de una interfaz en particular. Un proceso de HAL o de framework de Android puede ser un cliente de una interfaz y un servidor de otra. Consulta también transferencia.
se extiende Indica una interfaz que agrega métodos o tipos a otra interfaz. Una interfaz solo puede extender otra. Se puede usar para un incremento de versión menor en el mismo nombre de paquete o para un paquete nuevo (p.ej., una extensión de proveedor) que se compila en un paquete anterior.
genera Indica un método de interfaz que muestra valores al cliente. Para mostrar un valor no primitivo o más de uno, se genera una función de devolución de llamada síncrona.
interfaz Es una colección de métodos y tipos. Se traduce a una clase en C++ o Java. A todos los métodos de una interfaz se los llama en la misma dirección: un proceso cliente invoca métodos implementados por un proceso de servidor.
ida Cuando se aplica a un método HIDL, indica que el método no muestra ningún valor ni bloquea.
paquete Es una colección de interfaces y tipos de datos que comparten una versión.
transferencia Es el modo de HIDL en el que el servidor es una biblioteca compartida que dlopen el cliente. En el modo de transferencia, el cliente y el servidor son el mismo proceso, pero con bases de código separadas. Se usa solo para incorporar bases de código heredadas en el modelo HIDL. Consulta también Binderizado.
servidor Es un proceso que implementa métodos de una interfaz. Consulta también transferencia.
transporte Infraestructura de HIDL que mueve datos entre el servidor y el cliente.
version Es la versión de un paquete. Consiste en dos números enteros, mayor y menor. Los incrementos de versiones menores pueden agregar (pero no cambiar) tipos y métodos.

Diseño de HIDL

El objetivo de HIDL es que el framework de Android se pueda reemplazar sin tener que volver a compilar los HAL. Los proveedores o fabricantes de SoC compilan los HAL y los colocan en una partición /vendor en el dispositivo, lo que permite que el framework de Android, en su propia partición, se reemplace por una actualización OTA sin volver a compilar los HAL.

El diseño de HIDL equilibra las siguientes inquietudes:

  • Interoperabilidad. Crear interfaces interoperables de forma confiable entre procesos que se pueden compilar con varias arquitecturas, cadenas de herramientas y configuraciones de compilación Las interfaces HIDL tienen control de versión y no se pueden cambiar después de su publicación.
  • Eficiencia. HIDL intenta minimizar la cantidad de operaciones de copia. Los datos definidos por HIDL se entregan al código C++ en estructuras de datos de diseño estándar de C++ que se pueden usar sin descomprimir. HIDL también proporciona interfaces de memoria compartida y, como las RPC son, de forma inherente, algo lentas, HIDL admite dos formas de transferir datos sin usar una llamada a RPC: memoria compartida y una cola de mensajes rápida (FMQ).
  • Intuitivo: HIDL evita problemas espinosos de propiedad de la memoria, ya que solo usa parámetros in para RPC (consulta Lenguaje de definición de la interfaz de Android (AIDL)). Los valores que no se pueden mostrar de manera eficiente desde los métodos se muestran a través de funciones de devolución de llamada. Pasar datos a HIDL para la transferencia ni recibir datos de HIDL no cambia la propiedad de los datos, ya que la propiedad siempre permanece con la función de llamada. Los datos deben persistir solo durante el tiempo que se ejecuta la función llamada y se pueden destruir inmediatamente después de que se muestra la función llamada.

Usa el modo de transferencia

Para actualizar dispositivos que ejecutan versiones anteriores de Android a Android O, puedes unir los HAL convencionales (y heredados) en una nueva interfaz HIDL que entregue el HAL en modos de Binder y de mismo proceso (transferencia). Este enlace es transparente para el HAL y el framework de Android.

El modo de transferencia solo está disponible para clientes y implementaciones de C++. Los dispositivos que ejecutan versiones anteriores de Android no tienen HAL escritos en Java, por lo que los HAL de Java están vinculados de forma inherente.

Cuando se compila un archivo .hal, hidl-gen produce un archivo de encabezado de transferencia adicional BsFoo.h, además de los encabezados que se usan para la comunicación de Binder. Este encabezado define las funciones que se dlopen. Como los HAL de transferencia se ejecutan en el mismo proceso en el que se llaman, en la mayoría de los casos, los métodos de transferencia se invocan mediante una llamada directa a la función (mismo subproceso). Los métodos oneway se ejecutan en su propio subproceso, ya que no están diseñados para esperar a que el HAL los procese (esto significa que cualquier HAL que use métodos oneway en modo de transferencia debe ser seguro para los subprocesos).

Dado un IFoo.hal, BsFoo.h une los métodos generados por HIDL para proporcionar funciones adicionales (como hacer que las transacciones de oneway se ejecuten en otro subproceso). Este archivo es similar a BpFoo.h. Sin embargo, en lugar de pasar llamadas IPC con Binder, se invocan directamente las funciones deseadas. Las implementaciones futuras de HALs pueden proporcionar varias implementaciones, como HAL de FooFast y HAL de FooAccurate. En esos casos, se crearía un archivo para cada implementación adicional (p.ej., PTFooFast.cpp y PTFooAccurate.cpp).

Binderización de HALs de transferencia

Puedes vincular implementaciones de HAL que admitan el modo de transferencia. Dada una interfaz HAL a.b.c.d@M.N::IFoo, se crean dos paquetes:

  • a.b.c.d@M.N::IFoo-impl: Contiene la implementación del HAL y expone la función IFoo* HIDL_FETCH_IFoo(const char* name). En los dispositivos heredados, este paquete se dlopen y se crea una instancia de la implementación con HIDL_FETCH_IFoo. Puedes generar el código base con hidl-gen, -Lc++-impl y -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service: Abre el HAL de transferencia y se registra como un servicio vinculado, lo que permite que la misma implementación de HAL se use como transferencia y vinculación.

Dado el tipo IFoo, puedes llamar a sp<IFoo> IFoo::getService(string name, bool getStub) para obtener acceso a una instancia de IFoo. Si getStub es verdadero, getService intentará abrir el HAL solo en modo de transferencia. Si getStub es falso, getService intenta encontrar un servicio vinculado. Si eso falla, intenta encontrar el servicio de transferencia. El parámetro getStub nunca se debe usar, excepto en defaultPassthroughServiceImplementation. (Los dispositivos que se inician con Android O son dispositivos completamente vinculados, por lo que no se permite abrir un servicio en modo de transferencia).

Gramática de HIDL

De forma predeterminada, el lenguaje HIDL es similar a C (pero no usa el preprocesador C). Todos los signos de puntuación que no se describen a continuación (además del uso obvio de = y |) forman parte de la gramática.

Nota: Para obtener detalles sobre el estilo de código de HIDL, consulta la Guía de estilo de código.

  • /** */ indica un comentario de documentación. Se pueden aplicar solo a las declaraciones de tipo, método, campo y valor de enumeración.
  • /* */ indica un comentario de varias líneas.
  • // indica un comentario hasta el final de la línea. Aparte de //, los saltos de línea son iguales a cualquier otro espacio en blanco.
  • En la gramática de ejemplo que aparece a continuación, el texto de // hasta el final de la línea no forma parte de la gramática, sino que es un comentario sobre ella.
  • [empty] significa que el término puede estar vacío.
  • ? después de un literal o término significa que es opcional.
  • ... indica una secuencia que contiene cero o más elementos con puntuación de separación como se indica. No hay argumentos variadic en HIDL.
  • Las comas separan los elementos de la secuencia.
  • Los puntos y coma terminan cada elemento, incluido el último.
  • UPPERCASE es un elemento no terminal.
  • italics es una familia de tokens, como integer o identifier (reglas de análisis estándar de C).
  • constexpr es una expresión constante de estilo C (como 1 + 1 y 1L << 3).
  • import_name es un nombre de paquete o interfaz, calificado como se describe en Control de versiones de HIDL.
  • Los words en minúsculas son tokens literales.

Ejemplo:

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