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 l'HIDL. Transizione HAL per l'utilizzo esclusivo di AIDL, ove possibile (quando gli HAL upstream utilizzano HIDL, è necessario utilizzare HIDL).

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

Motivazione

L'AIDL è più lunga di quella HIDL e viene utilizzata in molti altri contesti, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL dispone del supporto di stabilità, è possibile implementare un intero stack con un singolo runtime IPC. AIDL ha anche un sistema di controllo delle versioni migliore rispetto a HIDL.

  • Utilizzare un unico linguaggio IPC significa avere una sola cosa da imparare, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo delle versioni in loco per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai pacchetti parcelable. Ciò significa che, nel corso degli anni, risulta più semplice eseguire le versioni del codice e, su base annua, il costo è inferiore (i tipi possono essere modificati in loco e non sono necessarie librerie aggiuntive per ogni versione dell'interfaccia).
    • Le interfacce delle estensioni possono essere collegate in fase di runtime anziché nel sistema dei tipi, quindi non è necessario ridefinire le estensioni downstream su versioni delle interfacce più recenti.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il suo proprietario sceglie di stabilizzarla. In precedenza, doveva essere creata una copia completa dell'interfaccia in HIDL.

Crea in base al runtime AIDL

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

Scrivi un'interfaccia AIDL HAL

Per poter utilizzare un'interfaccia AIDL tra il sistema e il fornitore, sono necessarie due modifiche:

  • Ogni definizione del 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, per funzionare, l'interfaccia deve essere nel manifest di VINTF. Testa questo (e i requisiti correlati, come la verifica del blocco delle interfacce rilasciate) 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++, oppure 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, disabilita il backend CPP.

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

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

Trova le interfacce AIDL HAL

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

  • hardware/interfacce
  • framework/hardware/interfacce
  • sistema/hardware/interfacce

Dovresti inserire le interfacce delle estensioni in altre sottodirectory hardware/interfaces in vendor o hardware.

Interfacce delle estensioni

Android dispone di una serie di interfacce AOSP ufficiali in ogni release. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android non è compatibile con il runtime Android AOSP. Per i dispositivi GMS, anche evitare di modificare queste interfacce garantisce che l'immagine GSI continui a funzionare.

Le estensioni possono essere registrate in due modi diversi:

  • per l'esecuzione, consulta le estensioni collegate.
  • indipendenti, registrati a livello globale e in VINTF.

Tuttavia, viene registrata un'estensione e quando i componenti specifici del fornitore (ovvero che non fanno parte di upstream AOSP) utilizzano l'interfaccia, non esiste la possibilità di conflitti di unione. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e sono consigliate le seguenti strategie:

  • le aggiunte all'interfaccia potranno essere upstreaming su AOSP nella prossima versione
  • nelle versioni successive, che consentono maggiore flessibilità, senza conflitti di unione

Estensione parcelableer: ParcelableHolder

ParcelableHolder è un Parcelable che può contenere un altro Parcelable. Il caso d'uso principale di ParcelableHolder è rendere un elemento Parcelable estensibile. Ad esempio, un'immagine che gli implementatori di dispositivi si aspettano di poter estendere un elemento Parcelable, AospDefinedParcelable definito per AOSP, per includere le proprie funzionalità a valore aggiunto.

In precedenza, senza ParcelableHolder, gli implementatori dei dispositivi non potevano modificare un'interfaccia AIDL stabile definita AOSP perché sarebbe 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 indicato nel codice precedente, questa pratica non funziona perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando viene rivisto Parcelable nelle successive release di Android.

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

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

Successivamente, gli utenti che implementano il dispositivo possono definire il proprio Parcelable per l'estensione.

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

Infine, il nuovo Parcelable può essere collegato all'elemento 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 istanze server AIDL HAL

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

Scrivi un server AIDL HAL

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

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

In caso contrario, dovrebbero registrare un servizio AIDL normalmente. Quando si eseguono i test VTS, prevedi che tutti gli HAL AIDL dichiarati siano disponibili.

Scrivi 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>

Conversione di un HAL esistente da HIDL ad AIDL

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

hidl2aidl funzionalità:

  • Crea file .aidl in base ai file .hal per il pacchetto specificato
  • Crea regole di build per il pacchetto AIDL appena creato con tutti i backend abilitati
  • Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL a quelli AIDL.
  • Crea regole di build per tradurre le librerie con le dipendenze richieste
  • Crea asserzioni statiche per garantire 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, procedi nel seguente modo:

  1. Crea lo strumento che si trova in system/tools/hidl/hidl2aidl.

    Creare questo strumento utilizzando le fonti più recenti offre l'esperienza più completa. Puoi utilizzare la versione più recente per convertire le interfacce su rami precedenti di release precedenti.

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

    Facoltativamente, utilizza l'argomento -l per aggiungere i contenuti di un nuovo file di licenza all'inizio di tutti i file generati. Assicurati di utilizzare la licenza e la data corrette.

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

    Ecco alcuni esempi:

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

    • conversion.log contiene prima problemi non gestiti da risolvere.
    • I file .aidl generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un intervento. Questi commenti iniziano con //.
    • Cogli l'opportunità per ripulire e apportare miglioramenti al pacchetto.
    • Controlla l'annotazione @JavaDerive per conoscere le funzionalità che potrebbero essere necessarie, come toString o equals.
  4. Crea solo i target di cui hai bisogno.

    • Disabilita i backend che non verranno utilizzati. Preferisci il backend NDK rispetto al backend CPP; consulta la sezione Scelta del runtime.
    • Rimuovi le librerie di traduzione o il codice generato che non verrà utilizzato.
  5. Consulta Principali differenze AIDL/HIDL.

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

SEPolicy per AIDL HAL

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

    type hal_foo_service, service_manager_type, hal_service_type;

Per la maggior parte dei servizi definiti dalla piattaforma, viene già aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default verrebbe già contrassegnato come hal_foo_service). Tuttavia, se un client framework supporta più nomi di istanza, è necessario aggiungere altri nomi di istanza 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 creiamo un nuovo tipo di HAL. Uno specifico attributo HAL potrebbe essere associato a più tipi di servizi (ognuno dei quali può avere più istanze, come abbiamo appena illustrato). 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, se il server di sistema è un client di questo HAL, corrisponde al criterio hal_client_domain(system_server, hal_foo). Un server HAL include in modo analogo hal_server_domain(my_hal_domain, hal_foo). In genere, per un determinato attributo HAL, creiamo anche un dominio come hal_foo_default per gli HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. La distinzione tra domini per più server è importante solo se abbiamo più server che gestiscono la stessa interfaccia e necessitano di un set di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è di fatto un oggetto sepolicy. 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 AIDL HAL utilizzando la macro hal_attribute_service (gli HAL HAL utilizzano la macro hal_attribute_hwservice). Ad esempio, hal_attribute_service(hal_foo, hal_foo_service). Ciò significa che i processi hal_foo_client possono accedere all'HAL e i processi hal_foo_server possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore di 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 generale, però, poiché questo implica che i servizi vengono sempre utilizzati insieme, potremmo valutare la rimozione del hal_foo2_service e l'utilizzo di hal_foo_service per tutti i contesti dei nostri servizi. La maggior parte degli HAL che impostano più hal_attribute_service perché il nome dell'attributo HAL originale non è abbastanza generico e non può essere modificato.

Riassumendo, 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 delle estensioni collegate

È possibile collegare un'estensione a qualsiasi interfaccia binder, sia che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio, sia che si tratti di un'interfaccia secondaria. Quando ricevi un'estensione, devi confermarne il tipo come previsto. Le estensioni possono essere impostate solo dal processo che gestisce un binder.

Le estensioni collegate 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 puoi registrare un'interfaccia di estensione direttamente con il gestore del servizio. Le interfacce con estensioni collegate sono quelle più pertinenti quando sono collegate a interfacce secondarie, in quanto queste gerarchie possono essere profonde o multi-istanza. L'utilizzo di un'estensione globale per eseguire il mirroring della gerarchia dell'interfaccia di binder di un altro servizio richiederebbe una gestione ampia per fornire una funzionalità equivalente alle estensioni collegate direttamente.

Per impostare un'estensione su 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 su un binder, usa 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. Puoi trovare un esempio di utilizzo delle estensioni in hardware/interfaces/tests/extension/vibrator.

Principali differenze tra AIDL e HIDL

Quando utilizzi AIDL HAL o le interfacce AIDL HAL, 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 integrati. Anziché creare tipi di stato personalizzati, crea int di stato costanti nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP/NDK e ServiceSpecificException nel backend Java. Vedi Gestione degli errori.
  • AIDL non avvia automaticamente i pool di thread quando vengono inviati oggetti binder. Devono essere avviati manualmente (vedi la gestione dei thread).
  • AIDL non viene interrotta in caso di errori di trasporto non controllati (il metodo HIDL Return viene interrotto in caso di errori non selezionati).
  • AIDL può dichiarare un solo tipo per file.
  • Gli argomenti AIDL possono essere specificati come in/out/inout oltre che come parametro di output (non sono presenti "callback sincroni").
  • AIDL utilizza un fd come tipo primitivo invece di handle.
  • HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per le modifiche compatibili. In AIDL vengono applicate modifiche compatibili con le versioni precedenti. AIDL non ha un concetto esplicito di versioni principali; viene invece incorporata 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 raccoglitore per abilitare l'ereditarietà della priorità in tempo reale.