HIDL

Il linguaggio di definizione dell'interfaccia HAL, o HIDL, è un linguaggio di descrizione dell'interfaccia (IDL, Interface Description Language) 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. Più in generale, HIDL è un sistema di comunicazione tra i codebase che possono essere compilati in modo indipendente.

HIDL è destinato a essere utilizzato per la comunicazione tra processi (IPC). Gli HAL creati con HDL sono chiamati HAL binderizzati in quanto possono comunicare con altri livelli di architettura tramite chiamate di comunicazione tra processi (IPC) di binder. Gli HAL Binderizzati vengono eseguiti in un processo separato da quello del client che li utilizza. Per le librerie che devono essere collegate a un processo, è disponibile anche una modalità passthrough (non supportata in Java).

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

Terminologia

Questa sezione utilizza i seguenti termini correlati all'HIDL:

legato Indica che l'HIDL viene utilizzato per le chiamate di procedure remote tra i processi, implementata su un meccanismo simile a Binder. Vedi anche passthrough.
il callback, il callback 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 i dati dall'implementazione del metodo HIDL di un server al client. Non utilizzato per i metodi che restituiscono un valore vuoto o un singolo valore primitivo.
client Processo che chiama metodi di una particolare interfaccia. Un processo framework HAL o Android potrebbe 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 solo un'altra interfaccia. Può essere utilizzato per un incremento della versione minore nello stesso nome di pacchetto o per un nuovo pacchetto (ad es. un'estensione del fornitore) al fine di creare su un pacchetto meno recente.
genera Indica un metodo di interfaccia che restituisce valori al client. Per restituire un valore non primitivo o più di un valore, viene generata una funzione di callback sincrona.
interfaccia Raccolta di metodi e tipi. Tradotto in una classe in C++ o Java. Tutti i metodi in un'interfaccia vengono chiamati nella stessa direzione: un processo client richiama metodi implementati da un processo server.
solo andata Se applicato a un metodo HIDL, indica che il metodo non restituisce valori e non viene bloccato.
Pacco Raccolta di interfacce e tipi di dati che condividono una versione.
passthrough Modalità HIDL in cui il server è una libreria condivisa dlopendal client. In modalità passthrough, client e server sono lo stesso processo, ma codebase separati. Utilizzato solo per inserire codebase legacy nel modello HIDL. Vedi anche Binderized.
server Processo che implementa 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, maggiori e minori. Incrementi di versioni minori possono aggiungere tipi e metodi, ma non modificarli.

Design HIDL

L'obiettivo dell'HIDL è che il framework Android possa essere sostituito senza dover ricreare gli HAL. Gli HAL verranno creati da fornitori o produttori di SOC e inseriti in una partizione /vendor sul dispositivo, in modo da poter sostituire il framework Android nella sua partizione con un'OTA senza ricompilare gli HAL.

Il design HIDL bilancia i seguenti aspetti:

  • Interoperabilità. Creare interfacce interoperabili in modo affidabile tra i processi, che possono essere compilati con varie architetture, toolchain e configurazioni di build. Le interfacce HIDL vengono sottoposte al controllo delle versioni 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++ nelle strutture di dati di layout standard C++ che possono essere utilizzate senza decomprimere. HIDL fornisce anche interfacce di memoria condivisa e, poiché gli RPC sono intrinsecamente lenti, HIDL supporta due modi per trasferire i dati senza utilizzare una chiamata RPC: la memoria condivisa e una Fast Message Queue (FMQ).
  • Intuitive. HIDL evita problemi spinosi di proprietà della memoria utilizzando solo i parametri in per RPC (vedi Android Interface Definition Language (AIDL)); i valori che non possono essere restituiti in modo efficiente dai metodi vengono restituiti tramite le funzioni di callback. Né il trasferimento dei dati all'HIDL per il trasferimento né la ricezione dei dati dall'HIDL ne modificano la proprietà: la proprietà rimane sempre con la funzione chiamante. I dati devono persistere solo per la durata della funzione chiamata e possono essere eliminati immediatamente dopo la restituzione della funzione chiamata.

Utilizzo della modalità passthrough

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

La modalità passthrough è disponibile solo per i client e le implementazioni C++. I dispositivi che eseguono versioni precedenti di Android non dispongono di HAL scritti in Java, quindi gli HAL Java sono intrinsecamente binderizzati.

Quando viene compilato un file .hal, hidl-gen produce un file di intestazione passthrough aggiuntivo BsFoo.h oltre alle intestazioni utilizzate per la comunicazione binder; questa intestazione definisce le funzioni da dlopeneseguire. Poiché gli HAL passthrough vengono eseguiti nello stesso processo in cui vengono chiamati, nella maggior parte dei casi i metodi passthrough vengono richiamati da una chiamata di funzione diretta (stesso thread). I metodi oneway vengono eseguiti nel proprio thread poiché non sono destinati ad attendere che l'HAL li elabori (questo significa che qualsiasi HAL che utilizza metodi oneway in modalità passthrough deve essere sicuro per thread).

Dato un valore IFoo.hal, BsFoo.h esegue il wrapping dei metodi generati dall'HIDL per fornire funzionalità aggiuntive (ad esempio l'esecuzione delle transazioni oneway in un altro thread). Questo file è simile a BpFoo.h, tuttavia, invece di passare le chiamate IPC tramite binder, le funzioni desiderate vengono richiamate direttamente. Le implementazioni future degli HAL possono fornire più implementazioni, ad esempio FooFast HAL e FooAccurate HAL. In questi casi, viene creato un file per ogni implementazione aggiuntiva (ad es. PTFooFast.cpp e PTFooAccurate.cpp).

Associazione degli HAL passthrough

Puoi vincolare le implementazioni HAL che supportano la modalità passthrough. Data l'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 ed espone la funzione IFoo* HIDL_FETCH_IFoo(const char* name). Sui dispositivi legacy, questo pacchetto è dlopened e l'implementazione è verificata utilizzando HIDL_FETCH_IFoo. Puoi generare il codice di base utilizzando hidl-gen, -Lc++-impl e -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Apre l'HAL passthrough e si registra come servizio binderizzato, consentendo di utilizzare la stessa implementazione HAL sia come passthrough che come binderizzata.

Dato il tipo IFoo, puoi chiamare sp<IFoo> IFoo::getService(string name, bool getStub) per ottenere l'accesso a un'istanza di IFoo. Se getStub è true, getService tenta di aprire l'HAL solo in modalità passthrough. Se getStub è false, getService tenta di trovare un servizio binderizzato; se non riesce, prova a trovare il servizio passthrough. Il parametro getStub non deve mai essere utilizzato, tranne che in defaultPassthroughServiceImplementation. (I dispositivi che vengono lanciati con Android O sono dispositivi completamente vincolati, quindi l'apertura di un servizio in modalità passthrough non è consentita.)

Grammatica HIDL

In base alla progettazione, il linguaggio HIDL è simile al linguaggio C (ma non utilizza il preprocessore C). Tutta la punteggiatura non descritta di seguito (a parte l'uso ovvio di = e |) fa parte della grammatica.

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

  • /** */ indica un commento alla documentazione. Queste possono essere applicate solo alle dichiarazioni di tipo, metodo, campo ed enum.
  • /* */ indica un commento su più righe.
  • // indica un commento alla fine della riga. A parte //, i caratteri a capo sono uguali a tutti gli altri spazi vuoti.
  • Nell'esempio grammaticale riportato di seguito, il testo da // alla fine della riga non fa parte della grammatica, ma è un commento alla grammatica.
  • [empty] significa che il termine potrebbe essere vuoto.
  • ? dopo un valore letterale o termine significa che è facoltativo.
  • ... indica una sequenza contenente zero o più elementi con separazione di punteggiatura, come indicato. Non ci sono argomenti variadi in HIDL.
  • Le virgole separano gli elementi di sequenza.
  • I punti e virgola terminano ogni elemento, incluso l'ultimo.
  • MAIUSCOLO è un non terminale.
  • italics è una famiglia di token come integer o identifier (regole di analisi C standard).
  • constexpr è un'espressione costante di stile C (come 1 + 1 e 1L << 3).
  • import_name è un nome di pacchetto o interfaccia, qualificato come descritto in Controllo delle versioni 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