HIDL

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

O HIDL é 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) do binder. As HALs com Binderized são executadas em um processo separado do cliente que as usa. Para bibliotecas que precisam ser vinculadas a um processo, um modo de passagem também está disponível (não tem suporte em Java).

O HIDL especifica estruturas de dados e assinaturas de métodos, organizadas em interfaces (semelhantes a uma classe) que são coletadas em pacotes. A sintaxe do HIDL parece familiar para os 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 ao HIDL:

vinculado Indica que o HIDL está sendo usado para chamadas de procedimento remoto entre processos, implementado em um mecanismo semelhante a Binder. Consulte também passthrough.
callback, assíncrono Interface disponibilizada por um usuário da HAL, transmitida para a HAL (usando um método HIDL) e chamada pela HAL para retornar dados a qualquer momento.
callback, síncrono Retorna dados da implementação do método HIDL de um servidor ao cliente. Não utilizado para métodos que retornam nulo ou um único valor primitivo.
cliente Processo que chama métodos de uma interface específica. Um processo de framework da HAL ou do Android pode ser cliente de uma interface e servidor de outra. Consulte também o passthrough.
estende Indica uma interface que adiciona métodos e/ou tipos a outra interface. Uma interface só pode estender uma outra interface. Pode ser usado para um incremento de versão secundário no mesmo nome de pacote ou para um novo pacote (por exemplo, uma extensão de fornecedor) para criar a partir de 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 invoca 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 Conjunto de interfaces e tipos de dados que compartilham uma versão.
passagem Modo de HIDL em que o servidor é uma biblioteca compartilhada, dlopenedida pelo cliente. No modo de passagem, o cliente e o servidor são o mesmo processo, mas 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 o passthrough.
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, maior e menor. Incrementos de versão secundários podem adicionar (mas não alterar) tipos e métodos.

Design de HIDL

O objetivo do HIDL é que o framework do Android possa ser substituído sem precisar recriar as HALs. As HALs serão criadas por fornecedores ou fabricantes de SOC e colocadas 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 as HALs.

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

  • Interoperabilidade. Crie interfaces interoperáveis e confiáveis entre processos que podem ser compilados com várias arquiteturas, conjuntos de ferramentas e configurações de build. As interfaces HIDL têm controle de versão 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 pelo HIDL são entregues ao código C++ em estruturas de dados de layout C++ padrão que podem ser usadas sem descompactação. O HIDL também fornece interfaces de memória compartilhada e, como RPCs são inerentemente lentas, ele é compatível com duas maneiras de transferir dados sem usar uma chamada RPC: memória compartilhada e fila de mensagens rápidas (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 a Linguagem de definição de interface do Android (AIDL, na sigla em inglês). Os valores que não podem ser retornados de forma eficiente a partir de métodos são retornados por funções de callback. Nem transmitir dados ao HIDL para transferência nem receber dados do HIDL altera a propriedade dos dados. A propriedade sempre permanece com a função de chamada. Os dados precisam persistir apenas durante a função chamada e podem ser destruídos imediatamente após o retorno da função.

Como usar o modo de passagem

Para atualizar dispositivos que executam versões anteriores do Android para o Android O, é possível encapsular as HALs convencionais (e legadas) em uma nova interface HIDL que exibe a HAL em modos de vinculação e de mesmo processo (passagem). Esse wrapper é transparente tanto para a HAL quanto para o framework do Android.

O modo de passagem está disponível apenas para clientes e implementações do C++. Dispositivos que executam versões anteriores do Android não têm HALs escritas em Java. Portanto, elas são inerentemente vinculadas.

Quando um arquivo .hal é compilado, o hidl-gen produz um arquivo principal 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 dlopened. Como as HALs de passagem são executadas no mesmo processo em que são chamadas, 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 o processamento pela HAL. Isso significa que qualquer HAL que usa métodos oneway no modo de passagem precisa ser seguro para linhas de execução.

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

Binder HALs de passagem

É possível vincular implementações de HAL com suporte ao modo de passagem. 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 da HAL e expõe a função IFoo* HIDL_FETCH_IFoo(const char* name). Em dispositivos legados, esse pacote é definido como dlopen, e a implementação é instanciada usando HIDL_FETCH_IFoo. Você pode gerar o código base usando hidl-gen, -Lc++-impl e -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Abre a HAL de passagem e se registra como um serviço vinculado, permitindo que a mesma implementação de HAL seja usada como de passagem e de vinculação.

Com 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, o getService tenta abrir a HAL somente 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 pode ser usado, exceto em defaultPassthroughServiceImplementation. Os dispositivos iniciados com o Android O são dispositivos totalmente vinculados. Portanto, não é permitido abrir um serviço no modo de passagem.

Gramática HIDL

Por padrão, a linguagem HIDL é semelhante à C, mas não usa o pré-processador C. Todas as pontuações não descritas abaixo (exceto o uso óbvio de = e |) fazem parte da gramática.

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

  • /** */ indica um comentário da documentação. Eles podem ser aplicados apenas a declarações de valor de tipo, método, campo e enumeração.
  • /* */ indica um comentário de várias linhas.
  • // indica um comentário no fim da linha. Além de //, as novas linhas 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 sim um comentário sobre ela.
  • [empty] significa que o termo pode estar vazio.
  • ? depois de um literal ou termo significa que ele é opcional.
  • ... indica sequência que contém zero ou mais itens com pontuação separada, conforme indicado. Não há argumentos variados no HIDL.
  • Os elementos de sequência são separados por vírgulas.
  • Os pontos e vírgulas terminam cada elemento, incluindo o último.
  • MAIÚSCULAS é um não terminal.
  • italics é uma família de tokens, como integer ou identifier (regras padrão de análise de C).
  • constexpr é uma expressão constante de estilo C (como 1 + 1 e 1L << 3).
  • import_name é um nome de pacote ou interface, qualificado conforme descrito em Controle de versões do HIDL.
  • words em 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