HIDL

El lenguaje de definición de interfaz 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, recopilados en interfaces y paquetes. En términos más generales, HIDL es un sistema para comunicarse entre bases de código que pueden compilarse de forma independiente. A partir de Android 10, HIDL está obsoleto y Android está migrando para usar AIDL en todas partes.

HIDL está destinado a ser utilizado para la comunicación entre procesos (IPC). Los HAL creados con HDL se denominan HAL enlazados porque pueden comunicarse con otras capas de arquitectura mediante llamadas de comunicación entre procesos (IPC) de enlace. Los HAL enlazados se ejecutan en un proceso independiente del cliente que los utiliza. Para las bibliotecas que deben estar vinculadas a un proceso, también está disponible un modo de paso a través (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 resulta familiar para los programadores de C++ y Java, pero con un conjunto diferente de palabras clave. HIDL también utiliza anotaciones estilo Java.

Terminología

Esta sección utiliza los siguientes términos relacionados con HIDL:

encuadernado Indica que HIDL se está utilizando para llamadas a procedimientos remotos entre procesos, implementado a través de un mecanismo similar a Binder. Véase también paso a través .
devolución de llamada, asíncrona Interfaz servida por un usuario de HAL, pasada a HAL (usando un método HIDL) y llamada por HAL para devolver datos en cualquier momento.
devolución de llamada, sincrónica Devuelve datos de la implementación del método HIDL de un servidor al cliente. No se utiliza para métodos que devuelven vacío o un único valor primitivo.
cliente Proceso que llama a métodos de una interfaz particular. Un proceso de framework HAL o Android puede ser un cliente de una interfaz y un servidor de otra. Véase también paso a través .
se extiende Indica una interfaz que agrega métodos y/o tipos a otra interfaz. Una interfaz sólo puede ampliar otra interfaz. Se puede utilizar para un incremento de versión menor en el mismo nombre de paquete o para un paquete nuevo (por ejemplo, una extensión de proveedor) para construir sobre un paquete anterior.
genera Indica un método de interfaz que devuelve valores al cliente. Para devolver un valor no primitivo, o más de un valor, se genera una función de devolución de llamada sincrónica.
interfaz Colección de métodos y tipos. Traducido a una clase en C++ o Java. Todos los métodos en una interfaz se llaman en la misma dirección: un proceso cliente invoca métodos implementados por un proceso servidor.
Un camino Cuando se aplica a un método HIDL, indica que el método no devuelve valores y no bloquea.
paquete Colección de interfaces y tipos de datos que comparten una versión.
pasar por Modo de HIDL en el que el servidor es una biblioteca compartida, dlopen por el cliente. En el modo de paso a través, el cliente y el servidor son el mismo proceso pero con bases de código separadas. Se utiliza únicamente para incorporar bases de código heredadas al modelo HIDL. Véase también Encuadernado .
servidor Proceso que implementa métodos de una interfaz. Véase también paso a través .
transporte Infraestructura HIDL que mueve datos entre el servidor y el cliente.
versión Versión de un paquete. Consta de dos números enteros, mayor y menor. Los incrementos de versión menores pueden agregar (pero no cambiar) tipos y métodos.

diseño HIDL

El objetivo de HIDL es que el marco de Android pueda reemplazarse sin tener que reconstruir HAL. Los proveedores o fabricantes de SOC construirán los HAL y los colocarán en una partición /vendor en el dispositivo, lo que permitirá que el marco de trabajo de Android, en su propia partición, sea reemplazado por una OTA sin tener que volver a compilar los HAL.

El diseño HIDL equilibra las siguientes preocupaciones:

  • Interoperabilidad . Cree interfaces interoperables confiables entre procesos que puedan compilarse con varias arquitecturas, cadenas de herramientas y configuraciones de compilación. Las interfaces HIDL tienen versiones y no se pueden cambiar una vez publicadas.
  • Eficiencia . HIDL intenta minimizar el número 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 los RPC son inherentemente algo lentos, HIDL admite dos formas de transferir datos sin utilizar una llamada RPC: memoria compartida y una cola de mensajes rápida (FMQ).
  • Intuitivo . HIDL evita problemas espinosos de propiedad de la memoria al usar in parámetros para RPC (consulte Lenguaje de definición de interfaz de Android (AIDL) ); los valores que no se pueden devolver de manera eficiente desde los métodos se devuelven mediante funciones de devolución de llamada. Ni pasar datos a HIDL para su transferencia ni recibir datos de HIDL cambia la propiedad de los datos; la propiedad siempre permanece en la función de llamada. Los datos deben persistir solo mientras dure la función llamada y pueden destruirse inmediatamente después de que regrese la función llamada.

Usando el modo de paso a través

Para actualizar dispositivos que ejecutan versiones anteriores de Android a Android O, puede empaquetar HAL convencionales (y heredados) en una nueva interfaz HIDL que sirve el HAL en modos enlazados y del mismo proceso (paso a través). Esta envoltura es transparente tanto para HAL como para el marco de Android.

El modo Passthrough solo está disponible para implementaciones y clientes 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 inherentemente enlazados.

Cuando se compila un archivo .hal , hidl-gen produce un archivo de encabezado de paso adicional BsFoo.h además de los encabezados utilizados para la comunicación del enlazador; este encabezado define las funciones que se van dlopen . Como los HAL de paso a través se ejecutan en el mismo proceso en el que se llaman, en la mayoría de los casos los métodos de paso a través se invocan mediante una llamada de función directa (mismo hilo). Los métodos oneway se ejecutan en su propio subproceso, ya que no están destinados a esperar a que HAL los procese (esto significa que cualquier HAL que utilice métodos oneway en modo de paso debe ser seguro para subprocesos).

Dado un IFoo.hal , BsFoo.h envuelve los métodos generados por HIDL para proporcionar características adicionales (como hacer que las transacciones oneway se ejecuten en otro hilo). Este archivo es similar a BpFoo.h , sin embargo, en lugar de pasar llamadas IPC usando Binder, las funciones deseadas se invocan directamente. Las implementaciones futuras de HAL pueden proporcionar múltiples implementaciones, como FooFast HAL y FooAccurate HAL. En tales casos, se crearía un archivo para cada implementación adicional (por ejemplo, PTFooFast.cpp y PTFooAccurate.cpp ).

Aglutinante de HAL de paso

Puede vincular implementaciones HAL que admitan el modo de paso a través. Dada una interfaz HAL abcd@MN::IFoo , se crean dos paquetes:

  • abcd@MN::IFoo-impl . Contiene la implementación de HAL y expone la función IFoo* HIDL_FETCH_IFoo(const char* name) . En dispositivos heredados, este paquete está dlopen y se crea una instancia de la implementación usando HIDL_FETCH_IFoo . Puede generar el código base usando hidl-gen y -Lc++-impl y -Landroidbp-impl .
  • abcd@MN::IFoo-service . Abre el HAL de paso a través y se registra como un servicio enlazado, lo que permite utilizar la misma implementación de HAL como servicio de paso a través y enlazado.

Dado el tipo IFoo , puede llamar sp<IFoo> IFoo::getService(string name, bool getStub) para obtener acceso a una instancia de IFoo . Si getStub es verdadero, getService intenta abrir HAL solo en modo de transferencia. Si getStub es falso, getService intenta encontrar un servicio enlazado; si eso falla, intenta encontrar el servicio de transferencia. El parámetro getStub nunca debe usarse 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 HIDL

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

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

  • /** */ indica un comentario de documentación. Estos solo se pueden aplicar a declaraciones de tipo, método, campo y valor de enumeración.
  • /* */ indica un comentario de varias líneas.
  • // indica un comentario al final de la línea. Aparte de // , las nuevas líneas son iguales que cualquier otro espacio en blanco.
  • En el ejemplo de gramática siguiente, 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.
  • ? seguir 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 variados en HIDL.
  • Las comas separan los elementos de la secuencia.
  • El punto y coma terminan cada elemento, incluido el último elemento.
  • MAYÚSCULAS 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 Versiones HIDL .
  • words en minúsculas son símbolos 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