HIDL

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

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

El HIDL especifica las estructuras de datos y las firmas de métodos, organizadas en interfaces (similares a una clase) que se recopilan en paquetes. La sintaxis del 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 el HIDL:

encubierto Indica que el HIDL se está usando para llamadas de procedimiento remoto entre procesos, implementado 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, que se pasa a la HAL (con un método HIDL) y que la HAL llama para mostrar datos en cualquier momento.
devolución de llamada, síncrona Muestra datos al cliente desde la implementación del método HIDL de un servidor. No se usa para métodos que muestran un valor nulo o un solo valor primitivo.
cliente Proceso que llama a los métodos de una interfaz en particular. Un proceso del framework de HAL o de Android puede ser un cliente de una interfaz y un servidor de otra. Consulta también transferencia.
extiende Indica una interfaz que agrega métodos o tipos a otra interfaz. Una interfaz solo puede extender una interfaz más. 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) para compilar 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 un valor, se genera una función de devolución de llamada síncrona.
interfaz Colección de métodos y tipos. Traducido a una clase en C++ o Java. Todos los métodos de una interfaz se llaman en la misma dirección: un proceso del cliente invoca a los métodos implementados por un proceso del servidor.
ida Cuando se aplica a un método HIDL, indica que el método no muestra valores y no se bloquea.
paquete Es una colección de interfaces y tipos de datos que comparten una versión.
transferencia Modo de HIDL en el que el servidor es una biblioteca compartida, dlopen adjuntado por el cliente. En el modo de transferencia, el cliente y el servidor son el mismo proceso, pero bases de código separadas. Solo se usa para incorporar bases de código heredadas en el modelo HIDL. Consulta también enlazadas.
servidor Proceso que implementa métodos de una interfaz. Consulta también transferencia.
transporte Infraestructura de HIDL que traslada datos entre el servidor y el cliente
version Es la versión de un paquete. Consiste en dos números enteros, uno mayor y otro menor. Los incrementos de versión menores pueden agregar tipos y métodos (pero no cambiar).

Diseño HIDL

El objetivo del HIDL es que el framework de Android se pueda reemplazar sin tener que volver a compilar las HAL. Los proveedores o fabricantes de SOC compilarán las HAL y las colocarán en una partición /vendor en el dispositivo, lo que permitirá reemplazar el framework de Android, en su propia partición, por una OTA sin volver a compilar las HAL.

El diseño del HIDL equilibra las siguientes inquietudes:

  • Interoperabilidad. Crea interfaces interoperables entre procesos que se puedan compilar con varias arquitecturas, cadenas de herramientas y configuraciones de compilación. Las interfaces HIDL tienen control de versiones y no se pueden cambiar después de que se publican.
  • Eficiencia. El HIDL intenta minimizar la cantidad de operaciones de copia. Los datos definidos por el HIDL se entregan al código C++ en las estructuras de datos de diseño estándar de C++ que se pueden usar sin desempaquetar. El HIDL también proporciona interfaces de memoria compartida y, como las RPC son intrínsecamente lentas, HIDL admite dos formas de transferir datos sin usar una llamada RPC: memoria compartida y una cola de mensajes rápidos (FMQ).
  • Son intuitivas. El HIDL evita problemas complejos de propiedad de la memoria mediante el uso solo de 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. Ni pasar datos a HIDL para la transferencia ni recibir datos de HIDL cambia la propiedad de los datos; la propiedad siempre permanece con la función que realiza la llamada. Los datos deben conservarse solo mientras dure la función llamada y pueden destruirse inmediatamente después de que se muestre la función llamada.

Cómo usar el modo de transferencia

Para actualizar los dispositivos que ejecutan versiones anteriores de Android a Android O, puedes unir las HAL convencionales (y las heredadas) en una nueva interfaz HIDL que entrega la HAL en modos de vinculación y del mismo proceso (transferencia). Esta unión es transparente para la HAL y el framework de Android.

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

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

Dado un IFoo.hal, BsFoo.h une los métodos generados con 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 a IPC mediante Binder, las funciones deseadas se invocan directamente. Las implementaciones futuras de HALs pueden proporcionar varias implementaciones, como la HAL de FooFast y una HAL de FooPreciso. En esos casos, se crearía un archivo para cada implementación adicional (p.ej., PTFooFast.cpp y PTFooAccurate.cpp).

Vinculación de HAL de transferencia

Puedes enlazar implementaciones de HAL que admiten el modo de transferencia. En el caso de 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 de la HAL y expone la función IFoo* HIDL_FETCH_IFoo(const char* name). En dispositivos heredados, se crea un dlopen de este paquete y se crea una instancia de la implementación usando 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 la HAL de transferencia y se registra como un servicio vinculado, lo que permite usar la misma implementación de HAL como de transferencia y vinculada.

Con 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 intenta abrir la 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 debe usarse, excepto en defaultPassthroughServiceImplementation. (Los dispositivos que se lanzan con Android O son dispositivos completamente vinculados, por lo que no está permitido abrir un servicio en modo de transferencia).

Gramática de HIDL

Por diseño, el lenguaje HIDL es similar al C (pero no usa el preprocesador de C). Toda la puntuación que no se describe a continuación (excepto el uso obvio de = y |) es parte de la gramática.

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

  • /** */ indica un comentario de documentación. Solo se pueden aplicar a declaraciones de tipo, método, campo y valor enum.
  • /* */ indica un comentario de varias líneas.
  • // indica un comentario hasta el final de la línea. Además de //, los saltos de línea son los mismos que cualquier otro espacio en blanco.
  • En la gramática de ejemplo que aparece a continuación, el texto desde // hasta el final de la línea no forma parte de la gramática, sino que es un comentario sobre la gramática.
  • [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 separada como se indica. No hay argumentos variádicos en el HIDL.
  • La coma separa los elementos de secuencia.
  • El punto y coma termina cada elemento, incluido el último.
  • MAYÚSCULA es un 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.
  • words en minúscula 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