HIDL

Il linguaggio di definizione dell'interfaccia HAL o HIDL è un linguaggio di descrizione dell'interfaccia (IDL) per specificare l'interfaccia tra un HAL e i suoi utenti. HIDL consente di specificare tipi e chiamate di metodi, raccolti in interfacce e pacchetti. In termini più generali, HIDL è un sistema per la comunicazione tra codebase che possono essere compilate in modo indipendente.

HIDL è progettato per essere utilizzato per la comunicazione tra processi (IPC). Gli HAL creati con HDL sono chiamati HAL con binder perché possono comunicare con altri livelli dell'architettura utilizzando chiamate di comunicazione inter-processo (IPC) del binder. Gli HAL con binder vengono eseguiti in un processo separato dal client che li utilizza. Per le librerie che devono essere collegate a un processo, è disponibile anche una modalità di passaggio (non supportata in Java).

HIDL specifica strutture di dati e firme di metodi, organizzate in interfacce (simili a una classe) che vengono raccolte in pacchetti. La sintassi di HIDL è familiare ai programmatori C++ e Java, ma con un insieme diverso di parole chiave. HIDL utilizza anche le annotazioni in stile Java.

Terminologia

Questa sezione utilizza i seguenti termini relativi a HIDL:

in formato binderized Indica che HIDL viene utilizzato per le chiamate di procedura remota tra processi, implementate tramite un meccanismo simile a Binder. Vedi anche passthrough.
callback, asincrono Interfaccia fornita da un utente HAL, passata all'HAL (utilizzando un metodo HIDL) e chiamata dall'HAL per restituire i dati in qualsiasi momento.
callback, sincrono Restituisce al client i dati dall'implementazione del metodo HIDL di un server. Non utilizzato per i metodi che restituiscono un valore void o un singolo valore primitivo.
client Processo che chiama i metodi di una determinata interfaccia. Un processo HAL o del framework Android può essere un client di un'interfaccia e un server di un'altra. Vedi anche passthrough.
si estende Indica un'interfaccia che aggiunge metodi e/o tipi a un'altra interfaccia. Un'interfaccia può estendere una sola altra interfaccia. Può essere utilizzato per un incremento minore della versione nello stesso nome del pacchetto o per un nuovo pacchetto (ad es. un'estensione del fornitore) da sviluppare su un pacchetto precedente.
genera Indica un metodo dell'interfaccia che restituisce valori al client. Per restituire un valore non primitivo o più valori, viene generata una funzione di callback sincrona.
interfaccia Raccolta di metodi e tipi. Tradotto in una classe in C++ o Java. Tutti i metodi di un'interfaccia vengono chiamati nella stessa direzione: un processo client invoca metodi implementati da un processo server.
solo andata Se applicato a un metodo HIDL, indica che il metodo non restituisce valori e non si blocca.
Pacco Raccolta di interfacce e tipi di dati che condividono una versione.
passthrough Modalità di HIDL in cui il server è una libreria condivisa, dlopene dal client. In modalità passthrough, il client e il server sono lo stesso processo, ma con basi di codice separate. Utilizzato solo per importare le codebase precedenti nel modello HIDL. Vedi anche Binderized.
server Processo che implementa i metodi di un'interfaccia. Vedi anche passthrough.
trasporto Infrastruttura HIDL che sposta i dati tra il server e il client.
versione Versione di un pacchetto. È composto da due numeri interi, maggiore e minore. Gli incrementi di versioni minori possono aggiungere (ma non modificare) tipi e metodi.

Progettazione HIDL

Lo scopo di HIDL è che il framework Android possa essere sostituito senza dover ricostruire le HAL. Gli HAL vengono creati da fornitori o produttori di SoC e inseriti in una /vendor partizione sul dispositivo, consentendo di sostituire il framework Android, nella sua partizione, con un aggiornamento OTA senza ricompilare gli HAL.

Il design di HIDL bilancia i seguenti aspetti:

  • Interoperabilità. Creare interfacce interoperabili in modo affidabile tra processi che possono essere compilati con varie architetture, toolchain e configurazioni di compilazione. Le interfacce HIDL sono versionate e non possono essere modificate dopo la pubblicazione.
  • Efficienza. HIDL cerca di ridurre al minimo il numero di operazioni di copia. I dati definiti da HIDL vengono inviati al codice C++ in strutture di dati con layout standard C++ che possono essere utilizzate senza scompattamento. HIDL fornisce anche interfacce di memoria condivisa e, poiché le RPC sono intrinsecamente un po' lente, HIDL supporta due modi per trasferire dati senza utilizzare una chiamata RPC: la memoria condivisa e una coda di messaggi rapida (FMQ).
  • Intuitiva. HIDL evita i problemi spinosi della proprietà della memoria utilizzando solo parametri in per l'RPC (consulta Android Interface Definition Language (AIDL)); i valori che non possono essere restituiti in modo efficiente dai metodi vengono restituiti tramite funzioni di callback. Né il passaggio di dati in HIDL per il trasferimento né la ricezione di dati da HIDL modificano la proprietà dei dati: la proprietà rimane sempre della funzione di chiamata. I dati devono essere mantenuti solo per la durata della funzione chiamata e possono essere distrutti immediatamente dopo il ritorno della funzione chiamata.

Usare la modalità passthrough

Per aggiornare i dispositivi con versioni precedenti di Android ad Android O, puoi eseguire il wrapping sia degli HAL convenzionali (che legacy) in una nuova interfaccia HIDL che fornisce l'HAL in modalità binderizzate e nello stesso processo (passthrough). Questo wrapping è trasparente sia per l'HAL sia per il framework Android.

La modalità di passaggio è disponibile solo per i client e le implementazioni C++. I dispositivi con versioni precedenti di Android non dispongono di HAL scritte in Java, pertanto le HAL Java sono intrinsecamente legate.

Quando viene compilato un file .hal, hidl-gen produce un file di intestazione passthrough aggiuntivo BsFoo.h oltre alle intestazioni utilizzate per la comunicazione del binder. Questa intestazione definisce le funzioni da dlopen. Poiché gli HAL passthrough vengono eseguiti nello stesso processo in cui vengono chiamati, nella maggior parte dei casi i metodi passthrough vengono invocati tramite chiamata diretta della funzione (stesso thread). I metodi oneway vengono eseguiti nel proprio thread poiché non sono progettati per attendere l'elaborazione da parte dell'HAL (ciò significa che qualsiasi HAL che utilizza i metodi oneway in modalità passthrough deve essere a prova di thread).

Dato un IFoo.hal, BsFoo.h esegue il wrapping dei metodi generati da HIDL per fornire funzionalità aggiuntive (ad esempio, l'esecuzione di transazioni oneway in un altro thread). Questo file è simile aBpFoo.h, ma anziché passare le chiamate IPC utilizzando il binder, le funzioni richieste vengono richiamate direttamente. Le implementazioni future degli HAL potrebbero fornire più implementazioni, ad esempio HAL FooFast e un HAL FooAccurate. In questi casi, verrà creato un file per ogni implementazione aggiuntiva (ad es. PTFooFast.cpp e PTFooAccurate.cpp).

Binderizzazione degli HAL passthrough

Puoi eseguire il binding delle implementazioni HAL che supportano la modalità passthrough. Data un'interfaccia HAL a.b.c.d@M.N::IFoo, vengono creati due pacchetti:

  • a.b.c.d@M.N::IFoo-impl. Contiene l'implementazione dell'HAL e espone la funzione IFoo* HIDL_FETCH_IFoo(const char* name). Sui dispositivi legacy, questo pacchetto è dlopen e l'implementazione viene attivata utilizzando HIDL_FETCH_IFoo. Puoi generare il codice di base utilizzando hidl-gen e -Lc++-impl e -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Apre l'HAL passthrough e si registra come servizio con binder, consentendo di utilizzare la stessa implementazione HAL sia come passthrough sia come con binder.

Dato il tipo IFoo, puoi chiamare sp<IFoo> IFoo::getService(string name, bool getStub) per accedere a un'istanza di IFoo. Se getStub è true, getService tenta di aprire l'HAL solo in modalità di passthrough. Se getStub è false, getService tenta di trovare un servizio incapsulato; se questo tentativo non va a buon fine, tenta di trovare il servizio passthrough. Il parametro getStub non deve mai essere utilizzato tranne che in defaultPassthroughServiceImplementation. I dispositivi lanciati con Android O sono completamente incapsulati, pertanto l'apertura di un servizio in modalità passthrough non è consentita.

Grammatica HIDL

Per impostazione predefinita, il linguaggio HIDL è simile al C (ma non utilizza il precompilatore C). Tutti i segni di punteggiatura non descritti di seguito (a parte l'uso evidente di = e |) fanno parte della grammatica.

Nota:per informazioni dettagliate sullo stile del codice HIDL, consulta la Guida allo stile del codice.

  • /** */ indica un commento della documentazione. Possono essere applicati solo alle dichiarazioni di tipo, metodo, campo e valore enum.
  • /* */ indica un commento su più righe.
  • // indica un commento alla fine della riga. A parte//, le interruzioni di riga sono uguali a qualsiasi altro spazio vuoto.
  • Nella grammatica di esempio riportata di seguito, il testo da // alla fine della linea non fa parte della grammatica, ma è un commento sulla grammatica.
  • [empty] indica che il termine può essere vuoto.
  • ? dopo un valore letterale o un termine indica che è facoltativo.
  • ... indica una sequenza contenente zero o più elementi con la punteggiatura di separazione indicata. In HIDL non sono presenti argomenti variadici.
  • Le virgole separano gli elementi della sequenza.
  • I punti e virgola terminano ogni elemento, incluso l'ultimo.
  • UPPERCASE è un non terminato.
  • italics è una famiglia di token comeinteger o identifier (regole di analisi sintattica C standard).
  • constexpr è un'espressione costante in stile C (ad es.1 + 1 e 1L << 3).
  • import_name è un nome di pacchetto o interfaccia, qualificato come descritto in Versionamento HIDL.
  • words minuscolo sono token letterali.

Esempio:

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