HIDL

A linguagem de definição de interface HAL ou HIDL é uma linguagem de descrição de interface (IDL, na sigla em inglês) para especificar a interface entre uma HAL e os usuários. O HIDL permite especificar tipos e chamadas de método, coletados em interfaces e pacotes. De forma mais ampla, o HIDL é um sistema para comunicação entre bases de código que podem ser compiladas de forma independente.

O HIDL é destinado a ser usado para comunicação entre processos (IPC). As HALs criadas com HDL são chamadas de HALs vinculadas, porque podem se comunicar com outras camadas de arquitetura usando chamadas de comunicação entre processos (IPC) de vinculação. As HALs vinculadas são executadas em um processo separado do cliente que as usa. Para bibliotecas que precisam ser vinculadas a um processo, um modo de transmissão também está disponível (não há suporte para Java).

O HIDL especifica estruturas de dados e assinaturas de método, organizadas em interfaces (semelhantes a uma classe) que são coletadas em pacotes. A sintaxe de HIDL é familiar para programadores C++ e Java, mas com um conjunto diferente de palavras-chave. O HIDL também usa anotações no estilo Java.

Terminologia

Esta seção usa os seguintes termos relacionados a HIDL:

encadernado Indica que o HIDL está sendo usado para chamadas de procedimento remoto entre processos, implementado em um mecanismo semelhante ao Binder. Consulte também Transmissão.
callback, assíncrono Interface atendida por um usuário HAL, transmitida ao HAL (usando um método HIDL) e chamada pelo HAL para retornar dados a qualquer momento.
callback, síncrono Retorna dados da implementação do método HIDL de um servidor para o cliente. Não é usado para métodos que retornam void ou um único valor primitivo.
cliente Processo que chama métodos de uma interface específica. Um processo HAL ou do framework Android pode ser um cliente de uma interface e um servidor de outra. Consulte também transmissão.
estende Indica uma interface que adiciona métodos e/ou tipos a outra interface. Uma interface pode estender apenas uma outra interface. Pode ser usado para um incremento de versão menor no mesmo nome de pacote ou para um novo pacote (por exemplo, uma extensão do fornecedor) para criar um pacote mais antigo.
gera Indica um método de interface que retorna valores para o cliente. Para retornar um valor não primitivo ou mais de um valor, uma função de callback síncrona é gerada.
interface Coleção de métodos e tipos. Traduzido para uma classe em C++ ou Java. Todos os métodos em uma interface são chamados na mesma direção: um processo cliente invocou métodos implementados por um processo do servidor.
viagem de ida Quando aplicado a um método HIDL, indica que o método não retorna valores e não bloqueia.
pacote Coleção de interfaces e tipos de dados que compartilham uma versão.
passagem Modo de HIDL em que o servidor é uma biblioteca compartilhada, dlopened pelo cliente. No modo de transferência, o cliente e o servidor são o mesmo processo, mas têm bases de código separadas. Usado apenas para trazer bases de código legadas para o modelo HIDL. Consulte também Binderized.
servidor Processo que implementa métodos de uma interface. Consulte também transmissão.
transporte Infraestrutura HIDL que move dados entre o servidor e o cliente.
versão Versão de um pacote. Consiste em dois números inteiros, principais e secundários. Incrementos de versões menores podem adicionar (mas não mudar) tipos e métodos.

Design HIDL

O objetivo do HIDL é que o framework do Android possa ser substituído sem precisar reconstruir HALs. Os HALs são criados por fornecedores ou fabricantes de SOC e colocados em uma partição /vendor no dispositivo, permitindo que o framework do Android, na própria partição, seja substituído por um OTA sem recompilar os HALs.

O design de HIDL equilibra as seguintes preocupações:

  • Interoperabilidade. Crie interfaces interoperáveis confiáveis entre processos que podem ser compilados com várias arquiteturas, cadeias de ferramentas e configurações de build. As interfaces HIDL têm versões e não podem ser alteradas depois de publicadas.
  • Eficiência. O HIDL tenta minimizar o número de operações de cópia. Os dados definidos por HIDL são enviados para o código C++ em estruturas de dados de layout padrão do C++, que podem ser usadas sem descompactação. O HIDL também oferece interfaces de memória compartilhada e, como as RPCs são inerentemente um pouco lentas, o HIDL oferece suporte a duas maneiras de transferir dados sem usar uma chamada de RPC: memória compartilhada e uma fila de mensagens rápida (FMQ, na sigla em inglês).
  • Intuitivo. O HIDL evita problemas complicados de propriedade de memória usando apenas parâmetros in para RPC (consulte Linguagem de definição de interface do Android (AIDL)). Os valores que não podem ser retornados de forma eficiente dos métodos são retornados por funções de callback. Nem a transmissão de dados para o HIDL nem o recebimento de dados do HIDL mudam a propriedade dos dados. A propriedade sempre permanece com a função de chamada. Os dados precisam permanecer apenas durante a duração da função chamada e podem ser destruídos imediatamente após o retorno da função chamada.

Usar o modo de passagem

Para atualizar dispositivos com versões anteriores do Android para o Android O, é possível unir as HALs convencionais (e legados) em uma nova interface HIDL que serve a HAL nos modos binderizado e de mesmo processo (de passagem). Esse agrupamento é transparente para a HAL e o framework do Android.

O modo de transferência direta está disponível apenas para clientes e implementações C++. Os dispositivos com versões anteriores do Android não têm HALs escritas em Java. As HALs do Java são inerentemente vinculadas.

Quando um arquivo .hal é compilado, o hidl-gen produz um arquivo de cabeçalho de passagem extra BsFoo.h, além dos cabeçalhos usados para a comunicação do binder. Esse cabeçalho define as funções a serem dlopendas. Como os HALs de passagem são executados no mesmo processo em que são chamados, na maioria dos casos, os métodos de passagem são invocados por chamada de função direta (mesma linha de execução). Os métodos oneway são executados na própria linha de execução, porque não precisam esperar que o HAL os processe. Isso significa que qualquer HAL que use métodos oneway no modo de passagem precisa ser thread-safe.

Com um IFoo.hal, o BsFoo.h envolve os métodos gerados pelo HIDL para fornecer recursos adicionais, como fazer com que as transações oneway sejam executadas em outra linha de execução. Esse arquivo é semelhante a BpFoo.h, mas, em vez de transmitir chamadas IPC usando o binder, as funções desejadas são invocadas diretamente. Futuras implementações de HALs podem fornecer várias implementações, como a HAL FooFast e a HAL FooAccurate. Nesses casos, um arquivo para cada implementação adicional seria criado (por exemplo, PTFooFast.cpp e PTFooAccurate.cpp).

Vincular HALs de passagem

É possível vincular implementações de HAL que oferecem suporte ao modo de transferência. Com uma interface HAL a.b.c.d@M.N::IFoo, dois pacotes são criados:

  • a.b.c.d@M.N::IFoo-impl: contém a implementação do HAL e expõe a função IFoo* HIDL_FETCH_IFoo(const char* name). Em dispositivos legados, esse pacote é dlopen e a implementação é instanciada usando HIDL_FETCH_IFoo. É possível gerar o código base usando hidl-gen e -Lc++-impl e -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Abre o HAL de passagem e se registra como um serviço vinculado, permitindo que a mesma implementação de HAL seja usada como passagem e vinculada.

Considerando o tipo IFoo, é possível chamar sp<IFoo> IFoo::getService(string name, bool getStub) para ter acesso a uma instância de IFoo. Se getStub for verdadeiro, getService tentará abrir o HAL apenas no modo de passagem. Se getStub for falso, getService tentará encontrar um serviço vinculado. Se isso falhar, ele tentará encontrar o serviço de passagem. O parâmetro getStub nunca deve ser usado, exceto em defaultPassthroughServiceImplementation. Os dispositivos lançados com o Android O são totalmente vinculados, portanto, não é possível abrir um serviço no modo de transferência.

Gramática do HIDL

Por design, a linguagem HIDL é semelhante ao C, mas não usa o pré-processador C. Todos os sinais de pontuação não descritos abaixo (exceto o uso óbvio de = e |) fazem parte da gramática.

Observação:para detalhes sobre o estilo de código HIDL, consulte o guia de estilo de código.

  • /** */ indica um comentário de documentação. Elas podem ser aplicadas apenas a declarações de tipo, método, campo e valor de tipo enumerado.
  • /* */ indica um comentário de várias linhas.
  • // indica um comentário até o fim da linha. Além de //, as quebras de linha são iguais a qualquer outro espaço em branco.
  • No exemplo de gramática abaixo, o texto de // até o final da linha não faz parte da gramática, mas é um comentário sobre ela.
  • [empty] significa que o termo pode estar vazio.
  • ? após um literal ou termo significa que ele é opcional.
  • ... indica uma sequência que contém zero ou mais itens com pontuação de separação, conforme indicado. Não há argumentos variáveis no HIDL.
  • As vírgulas separam os elementos da sequência.
  • Os pontos e vírgulas encerram cada elemento, incluindo o último.
  • UPPERCASE é um não terminal.
  • italics é uma família de tokens, como integer ou identifier (regras de análise C padrão).
  • constexpr é uma expressão constante no estilo C, como 1 + 1 e 1L << 3.
  • import_name é um nome de pacote ou interface, qualificado conforme descrito em Versionamento HIDL.
  • words em letras minúsculas são tokens literais.

Exemplo:

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