AIDL per HAL

Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android. In questo modo, è possibile implementare parti di Android senza HIDL. Passare alle HAL che utilizzano AIDL esclusivamente, se possibile (quando le HAL a monte utilizzano HIDL, deve essere utilizzato HIDL).

Gli HAL che utilizzano AIDL per comunicare tra i componenti del framework, come quelli in system.img, e i componenti hardware, come quelli in vendor.img, devono utilizzare AIDL stabile. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non ci sono limitazioni sul meccanismo IPC da utilizzare.

Motivazione

AIDL esiste da più tempo di HIDL e viene utilizzato in molti altri luoghi, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL supporta la stabilità, è possibile implementare un intero stack con un unico runtime IPC. AIDL ha anche un sistema di gestione delle versioni migliore di HIDL.

  • L'utilizzo di un unico linguaggio IPC significa avere un solo elemento da imparare, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo della versione in-place per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile gestire le versioni del codice nel corso degli anni e anche il costo annuale è inferiore (i tipi possono essere modificati in situ e non è necessaria alcuna libreria aggiuntiva per ogni versione dell'interfaccia).
    • Le interfacce di estensione possono essere collegate in fase di esecuzione anziché nel sistema di tipi, quindi non è necessario eseguire il rebase delle estensioni a valle sulle versioni più recenti delle interfacce.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente se il proprietario sceglie di stabilizzarla. In precedenza, era necessario creare un'intera copia dell'interfaccia in HIDL.

Esegui la compilazione in base al runtime AIDL

AIDL ha tre diversi backend: Java, NDK e CPP. Per utilizzare Stable AIDL, devi sempre utilizzare la copia di sistema di libbinder in system/lib*/libbinder.so e comunicare su /dev/binder. Per il codice nell'immagine del fornitore, ciò significa che non è possibile utilizzare libbinder (del VNDK): questa libreria ha un'API C++ instabile e componenti interni instabili. Il codice del fornitore nativo deve invece utilizzare il backend NDK di AIDL, eseguire il collegamento a libbinder_ndk (supportato dal sistema libbinder.so) e eseguire il collegamento alle librerie NDK create dalle voci aidl_interface. Per i nomi esatti dei moduli, consulta le regole di denominazione dei moduli.

Scrivere un'interfaccia HAL AIDL

Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, è necessario apportare due modifiche:

  • Ogni definizione di tipo deve essere annotata con @VintfStability.
  • La dichiarazione aidl_interface deve includere stability: "vintf",.

Solo il proprietario di un'interfaccia può apportare queste modifiche.

Quando apporti queste modifiche, l'interfaccia deve essere nel file manifest VINTF per funzionare. Testa questo requisito (e i relativi requisiti, ad esempio la verifica che le interfacce rilasciate siano bloccate) utilizzando il test VTS vts_treble_vintf_vendor_test. Puoi utilizzare un'interfaccia @VintfStability senza questi requisiti chiamando AIBinder_forceDowngradeToLocalStability nel backend NDK, android::Stability::forceDowngradeToLocalStability nel backend C++ o android.os.Binder#forceDowngradeToSystemStability nel backend Java su un oggetto binder prima che venga inviato a un altro processo. Il downgrade di un servizio alla stabilità del fornitore non è supportato in Java perché tutte le app vengono eseguite in un contesto di sistema.

Inoltre, per la massima portabilità del codice e per evitare potenziali problemi come librerie aggiuntive non necessarie, disattiva il backend CPP.

Tieni presente che l'utilizzo di backends nell'esempio di codice riportato di seguito è corretto, in quanto esistono tre backend (Java, NDK e CPP). Il codice riportato di seguito indica come selezionare in modo specifico il backend CPP per disattivarlo.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

Trovare le interfacce HAL AIDL

Le interfacce AIDL stabili di AOSP per gli HAL si trovano nelle stesse directory di base delle interfacce HIDL, nelle cartelle aidl.

  • hardware/interfaces: per le interfacce in genere fornite dall'hardware
  • frameworks/hardware/interfaces: per le interfacce di alto livello fornite all'hardware
  • system/hardware/interfaces: per le interfacce a basso livello fornite all'hardware

Devi inserire le interfacce di estensione in altre sottodirectory di hardware/interfaces in vendor o hardware.

Interfacce di estensione

Android ha un insieme di interfacce AOSP ufficiali con ogni release. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android è incompatibile con il runtime Android AOSP. Per i dispositivi GMS, evitare di modificare queste interfacce è anche ciò che garantisce il funzionamento dell'immagine GSI.

Le estensioni possono registrarsi in due modi diversi:

  • in fase di esecuzione, consulta le estensioni collegate.
  • autonomi, registrati a livello globale e in VINTF.

Indipendentemente da come viene registrata un'estensione, quando i componenti specifici del fornitore (ovvero non facenti parte dell'AOSP a monte) utilizzano l'interfaccia, non è possibile che si verifichino conflitti di unione. Tuttavia, quando vengono apportate modifiche a valle ai componenti AOSP a monte, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:

  • le aggiunte all'interfaccia possono essere trasferite ad AOSP nella prossima release
  • le aggiunte all'interfaccia che consentono ulteriore flessibilità, senza conflitti di unione, possono essere trasferite nella release successiva

Parcelable dell'estensione: ParcelableHolder

ParcelableHolder è un Parcelable che può contenere un altro Parcelable. Il caso d'uso principale di ParcelableHolder è rendere un Parcelable estensibile. Ad esempio, immagina che gli implementatori dei dispositivi si aspettino di poter estendere un Parcelable, AospDefinedParcelable definito da AOSP, per includere le loro funzionalità con valore aggiunto.

In precedenza, senza ParcelableHolder, gli implementatori dei dispositivi non potevano modificare un'interfaccia AIDL stabile definita da AOSP perché sarebbe stato un errore aggiungere altri campi:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Come si vede nel codice precedente, questa pratica non funziona perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando Parcelable viene rivisto nelle release successive di Android.

Utilizzando ParcelableHolder, il proprietario di un parcelable può definire un punto di estensione in un Parcelable.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Gli implementatori dei dispositivi possono quindi definire il proprio Parcelable per la propria estensione.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Infine, il nuovo Parcelable può essere collegato all'Parcelable originale con il campo ParcelableHolder.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Nomi delle istanze del server HAL AIDL

Per convenzione, i servizi HAL AIDL hanno un nome istanza del formato $package.$type/$instance. Ad esempio, un'istanza dell'HAL del vibratore è registrata come android.hardware.vibrator.IVibrator/default.

Scrivere un server HAL AIDL

@VintfStability I server AIDL devono essere dichiarati nel file manifest VINTF, ad esempio come segue:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

In caso contrario, deve registrare un servizio AIDL normalmente. Quando esegui test VTS, è previsto che tutti gli HAL AIDL dichiarati siano disponibili.

Scrivere un client AIDL

I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio come segue:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Convertire un HAL esistente da HIDL ad AIDL

Utilizza lo strumento hidl2aidl per convertire un'interfaccia HIDL in AIDL.

Funzionalità di hidl2aidl:

  • Crea file .aidl in base ai file .hal per il pacchetto specificato
  • Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend attivati
  • Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL
  • Crea regole di compilazione per le librerie di traduzione con le dipendenze richieste
  • Crea assert statici per assicurarti che gli enumeratori HIDL e AIDL abbiano gli stessi valori nei backend CPP e NDK

Per convertire un pacchetto di file .hal in file .aidl:

  1. Crea lo strumento in system/tools/hidl/hidl2aidl.

    La creazione di questo strumento dall'origine più recente offre l'esperienza più completa. Puoi utilizzare la versione più recente per convertire le interfacce dei branch precedenti delle release precedenti.

    m hidl2aidl
  2. Esegui lo strumento con una directory di output seguita dal pacchetto da convalidare.

    Se vuoi, utilizza l'argomento -l per aggiungere i contenuti di un nuovo file di licenza nella parte superiore di tutti i file generati. Assicurati di utilizzare la licenza e la data corrette.

    hidl2aidl -o <output directory> -l <file with license> <package>

    Ad esempio:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. Esamina i file generati e risolvi eventuali problemi di conversione.

    • conversion.log contiene eventuali problemi non gestiti da risolvere per primi.
    • I file .aidl generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un'azione. Questi commenti iniziano con //.
    • Cogli l'occasione per riorganizzare e migliorare il pacchetto.
    • Controlla l'annotazione @JavaDerive per le funzionalità che potrebbero essere necessarie, ad esempio toString o equals.
  4. Crea solo i target di cui hai bisogno.

    • Disattiva i backend che non verranno utilizzati. Se preferisci il backend NDK rispetto al backend CPP, consulta la sezione Scegliere il runtime.
    • Rimuovi le librerie di traduzione o il codice generato che non verrà utilizzato.
  5. Consulta Differenze principali tra AIDL/HIDL.

    • L'utilizzo di Status e delle eccezioni integrate di AIDL in genere migliora l'interfaccia e elimina la necessità di un altro tipo di stato specifico per l'interfaccia.
    • Gli argomenti dell'interfaccia AIDL nei metodi non sono @nullable per impostazione predefinita come accadeva in HIDL.

SEPolicy per HAL AIDL

Un tipo di servizio AIDL visibile al codice del fornitore deve avere l'attributohal_service_type. In caso contrario, la configurazione di sepolicy è la stessa di qualsiasi altro servizio AIDL (anche se esistono attributi speciali per gli HAL). Ecco un esempio di definizione di un contesto di servizio HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Per la maggior parte dei servizi definiti dalla piattaforma, è già stato aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default sarebbe già contrassegnato come hal_foo_service). Tuttavia, se un client del framework supporta più nomi di istanze, è necessario aggiungere altri nomi di istanze nei file service_contexts specifici del dispositivo.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Gli attributi HAL devono essere aggiunti quando viene creato un nuovo tipo di HAL. Un attributo HAL specifico può essere associato a più tipi di servizi (ognuno dei quali può avere più istanze, come abbiamo appena discusso). Per un HAL, foo, abbiamo hal_attribute(foo). Questa macro definisce gli attributi hal_foo_client e hal_foo_server. Per un determinato dominio, le macro hal_client_domain e hal_server_domain associano un dominio a un determinato attributo HAL. Ad esempio, il server di sistema che è un client di questo HAL corrisponde al criteriohal_client_domain(system_server, hal_foo). Analogamente, un server HAL includehal_server_domain(my_hal_domain, hal_foo). In genere, per un determinato attributo HAL, creiamo anche un dominio come hal_foo_default per riferimento o HAL di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. Distinguere i domini per più server è importante solo se abbiamo più server che forniscono la stessa interfaccia e richiedono un insieme di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è un oggetto sepolicy. Questo token viene invece utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia di server client.

Tuttavia, finora non abbiamo associato hal_foo_service e hal_foo (la coppia di attributi di hal_attribute(foo)). Un attributo HAL è associato ai servizi HAL AIDL utilizzando la macro hal_attribute_service (i HAL HIDL utilizzano la macro hal_attribute_hwservice). Ad esempio, hal_attribute_service(hal_foo, hal_foo_service). Ciò significa che hal_foo_client i processi possono acquisire l'HAL e hal_foo_server i processi possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore del contesto (servicemanager). Tieni presente che i nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL. Ad esempio, potremmo vedere hal_attribute_service(hal_foo, hal_foo2_service). In genere, poiché questo implica che i servizi vengono sempre utilizzati insieme, potremmo prendere in considerazione la rimozione di hal_foo2_service e l'utilizzo di hal_foo_service per tutti i contesti dei nostri servizi. La maggior parte degli HAL che imposta più hal_attribute_service è dovuta al fatto che il nome dell'attributo HAL originale non è abbastanza generico e non può essere modificato.

Mettendo tutto insieme, un HAL di esempio ha il seguente aspetto:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Interfacce di estensioni collegate

Un'estensione può essere collegata a qualsiasi interfaccia del binder, che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ricevi un'estensione, devi verificare che il tipo di estensione sia come previsto. Le estensioni possono essere impostate solo dal processo che serve un binder.

Le estensioni allegate devono essere utilizzate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, non è necessario utilizzare questo meccanismo e un'interfaccia di estensione può essere registrata direttamente con il gestore del servizio. Le interfacce di estensioni collegate hanno più senso quando sono collegate a sottointerfacce, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia del binder di un altro servizio richiederebbe un'ampia contabilità per fornire funzionalità equivalenti alle estensioni collegate direttamente.

Per impostare un'estensione sul binder, utilizza le seguenti API:

  • Nel backend NDK: AIBinder_setExtension
  • Nel backend Java: android.os.Binder.setExtension
  • Nel backend CPP: android::Binder::setExtension
  • Nel backend Rust: binder::Binder::set_extension

Per ottenere un'estensione per un binder, utilizza le seguenti API:

  • Nel backend NDK: AIBinder_getExtension
  • Nel backend Java: android.os.IBinder.getExtension
  • Nel backend CPP: android::IBinder::getExtension
  • Nel backend Rust: binder::Binder::get_extension

Puoi trovare ulteriori informazioni su queste API nella documentazione della funzione getExtension nel backend corrispondente. Un esempio di come utilizzare le estensioni è disponibile in hardware/interfaces/tests/extension/vibrator.

Differenze principali tra AIDL e HIDL

Quando utilizzi HAL AIDL o le interfacce HAL AIDL, tieni presente le differenze rispetto alla scrittura di HAL HIDL.

  • La sintassi del linguaggio AIDL è più simile a Java. La sintassi HIDL è simile a quella di C++.
  • Tutte le interfacce AIDL hanno stati di errore incorporati. Invece di creare tipi di stato personalizzati, crea interi di stato costanti nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP/NDK e ServiceSpecificException nel backend Java. Consulta la sezione Gestione degli errori.
  • AIDL non avvia automaticamente i threadpool quando vengono inviati gli oggetti binder. Devono essere avviati manualmente (vedi Gestione dei thread).
  • AIDL non viene interrotto in caso di errori di trasporto non controllati (HIDL Return si interrompe in caso di errori non controllati).
  • AIDL può dichiarare un solo tipo per file.
  • Gli argomenti AIDL possono essere specificati come in/out/inout oltre al parametro output (non sono presenti "richiami sincroni").
  • AIDL utilizza un fd come tipo primitivo anziché handle.
  • HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per quelle compatibili. In AIDL, le modifiche compatibili con le versioni precedenti vengono eseguite in blocco. AIDL non ha un concetto esplicito di versioni principali, ma questo è incorporato nei nomi dei pacchetti. Ad esempio, AIDL potrebbe utilizzare il nome del pacchetto bluetooth2.
  • AIDL non eredita la priorità in tempo reale per impostazione predefinita. La funzione setInheritRt deve essere utilizzata per ogni binder per attivare l'eredità della priorità in tempo reale.

Test della suite di test del fornitore (VTS) per gli HAL

Android si basa sulla Vendor Test Suite (VTS) per verificare le implementazioni HAL previste. Il VTS contribuisce a garantire che Android possa essere compatibile con le implementazioni precedenti dei fornitori. Le implementazioni che non superano il VTS presentano problemi di compatibilità noti che potrebbero impedirne il funzionamento con le future versioni del sistema operativo.

I VTS per gli HAL sono composti da due parti principali.

1. Verifica che gli HAL sul dispositivo siano noti e previsti da Android.

Questo insieme di test è disponibile in test/vts-testcase/hal/treble/vintf. Sono responsabili di verificare:

  • Ogni interfaccia @VintfStability dichiarata in un manifest VINTF viene bloccata in una versione rilasciata nota. In questo modo, entrambi i lati dell'interfaccia concordano sulla definizione esatta della versione dell'interfaccia. Questo è necessario per il funzionamento di base.
  • Tutti gli HAL dichiarati in un manifest VINTF sono disponibili su quel dispositivo. Qualsiasi client con autorizzazioni sufficienti per utilizzare un servizio HAL dichiarato deve essere in grado di ottenere e utilizzare questi servizi in qualsiasi momento.
  • Tutti gli HAL dichiarati in un file manifest VINTF pubblicano la versione dell'interfaccia dichiarata nel manifest.
  • Non sono presenti HAL ritirati su un dispositivo. Android non supporta più le versioni precedenti delle interfacce HAL, come descritto nel ciclo di vita di FCM.
  • Sul dispositivo sono presenti gli HAL richiesti. Alcuni HAL sono obbligatori per il corretto funzionamento di Android.

2. Verifica il comportamento previsto di ogni HAL

Ogni interfaccia HAL ha i propri test VTS per verificare il comportamento previsto dei suoi client. I casi di test vengono eseguiti su ogni istanza di un'interfaccia HAL dichiarata e applicano un comportamento specifico in base alla versione dell'interfaccia implementata.

Questi test tentano di coprire ogni aspetto dell'implementazione dell'HAL su cui si basa o potrebbe basarsi in futuro il framework Android.

Questi test includono la verifica del supporto delle funzionalità, la gestione degli errori e qualsiasi altro comportamento che un client potrebbe aspettarsi dal servizio.

Traguardi VTS per lo sviluppo HAL

I test VTS devono essere mantenuti aggiornati quando si creano o modificano le interfacce HAL di Android.

I test VTS devono essere completati e pronti per verificare le implementazioni dei fornitori prima di essere bloccati per le release dell'API Android for Vendor. Devono essere pronti prima che le interfacce vengano bloccate in modo che gli sviluppatori possano creare le proprie implementazioni, verificarle e fornire feedback agli sviluppatori dell'interfaccia HAL.

VTS su Cuttlefish

Quando l'hardware non è disponibile, Android utilizza Cuttlefish come veicolo di sviluppo per le interfacce HAL. Ciò consente di eseguire test di verifica della validità e di integrazione scalabili di Android. hal_implementation_test verifica che Cuttlefish abbia implementazioni delle ultime versioni dell'interfaccia HAL per assicurarsi che Android sia pronto a gestire le nuove interfacce e che i test VTS siano pronti a testare le implementazioni dei nuovi fornitori non appena saranno disponibili nuovi hardware e dispositivi.