AIDL para HAL

Android 11 introduce la posibilidad de utilizar AIDL para HAL en Android. Esto hace posible implementar partes de Android sin HIDL. Haga la transición de los HAL para que utilicen AIDL exclusivamente cuando sea posible (cuando los HAL ascendentes utilizan HIDL, se debe utilizar HIDL).

Los HAL que utilizan AIDL para comunicarse entre componentes del marco, como los de system.img , y componentes de hardware, como los de vendor.img , deben utilizar Stable AIDL. Sin embargo, para comunicarse dentro de una partición, por ejemplo de un HAL a otro, no hay restricciones sobre el mecanismo IPC a utilizar.

Motivación

AIDL existe desde hace más tiempo que HIDL y se usa en muchos otros lugares, como entre componentes del marco de trabajo de Android o en aplicaciones. Ahora que AIDL tiene soporte de estabilidad, es posible implementar una pila completa con un único tiempo de ejecución de IPC. AIDL también tiene un mejor sistema de versiones que HIDL.

  • Usar un único lenguaje IPC significa tener solo una cosa que aprender, depurar, optimizar y proteger.
  • AIDL admite versiones locales para los propietarios de una interfaz:
    • Los propietarios pueden agregar métodos al final de las interfaces o campos a los parcelables. Esto significa que es más fácil versionar el código a lo largo de los años y, además, el costo año tras año es menor (los tipos se pueden modificar en el lugar y no hay necesidad de bibliotecas adicionales para cada versión de la interfaz).
    • Las interfaces de extensión se pueden adjuntar en tiempo de ejecución en lugar de en el sistema de tipos, por lo que no es necesario cambiar la base de las extensiones posteriores a versiones más nuevas de las interfaces.
  • Una interfaz AIDL existente se puede utilizar directamente cuando su propietario decide estabilizarla. Antes, era necesario crear una copia completa de la interfaz en HIDL.

Escribir una interfaz AIDL HAL

Para que se utilice una interfaz AIDL entre el sistema y el proveedor, la interfaz necesita dos cambios:

  • Cada definición de tipo debe anotarse con @VintfStability .
  • La declaración aidl_interface debe incluir stability: "vintf", .

Sólo el propietario de una interfaz puede realizar estos cambios.

Cuando realiza estos cambios, la interfaz debe estar en el manifiesto VINTF para poder funcionar. Pruebe esto (y los requisitos relacionados, como verificar que las interfaces publicadas estén congeladas) utilizando la prueba VTS vts_treble_vintf_vendor_test . Puede usar una interfaz @VintfStability sin estos requisitos llamando a AIBinder_forceDowngradeToLocalStability en el backend de NDK, android::Stability::forceDowngradeToLocalStability en el backend de C++ o android.os.Binder#forceDowngradeToSystemStability en el backend de Java en un objeto de carpeta antes de enviarlo. a otro proceso. Java no admite la degradación de un servicio a la estabilidad del proveedor porque todas las aplicaciones se ejecutan en un contexto de sistema.

Además, para obtener la máxima portabilidad del código y evitar posibles problemas, como bibliotecas adicionales innecesarias, desactive el backend de CPP.

Tenga en cuenta que el uso de backends en el ejemplo de código siguiente es correcto, ya que hay tres backends (Java, NDK y CPP). El siguiente código indica cómo seleccionar específicamente el backend de CPP para desactivarlo.

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

Encontrar interfaces AIDL HAL

Las interfaces AOSP Stable AIDL para HAL están en los mismos directorios base que las interfaces HIDL, en carpetas aidl .

  • hardware/interfaces
  • marcos/hardware/interfaces
  • sistema/hardware/interfaces

Debe colocar las interfaces de extensión en otros subdirectorios hardware/interfaces en vendor o hardware .

Interfaces de extensión

Android tiene un conjunto de interfaces AOSP oficiales con cada versión. Cuando los socios de Android quieran agregar funcionalidad a estas interfaces, no deben cambiarlas directamente porque esto significaría que su tiempo de ejecución de Android es incompatible con el tiempo de ejecución de Android AOSP. Para los dispositivos GMS, evitar cambiar estas interfaces también es lo que garantiza que la imagen GSI pueda seguir funcionando.

Las extensiones pueden registrarse de dos maneras diferentes:

Independientemente de cómo se registre una extensión, cuando los componentes específicos del proveedor (es decir, que no forman parte de AOSP ascendente) utilizan la interfaz, no hay posibilidad de que se produzca un conflicto de fusión. Sin embargo, cuando se realizan modificaciones posteriores a los componentes AOSP anteriores, pueden producirse conflictos de fusión y se recomiendan las siguientes estrategias:

  • Las adiciones de la interfaz se pueden actualizar a AOSP en la próxima versión.
  • Las adiciones de interfaz que permiten una mayor flexibilidad, sin conflictos de fusión, se pueden incorporar en la próxima versión.

Parcelables de extensión: ParcelableHolder

ParcelableHolder es un Parcelable que puede contener otro Parcelable . El principal caso de uso de ParcelableHolder es hacer que Parcelable sea extensible. Por ejemplo, imagine que los implementadores de dispositivos esperan poder extender un Parcelable definido por AOSP, AospDefinedParcelable , para incluir sus características de valor agregado.

Anteriormente, sin ParcelableHolder , los implementadores de dispositivos no podían modificar una interfaz AIDL estable definida por AOSP porque sería un error agregar más campos:

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

Como se ve en el código anterior, esta práctica no funciona porque los campos agregados por el implementador del dispositivo pueden tener un conflicto cuando se revisa Parcelable en las próximas versiones de Android.

Usando ParcelableHolder , el propietario de un Parcelable puede definir un punto de extensión en un Parcelable .

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

Luego, los implementadores del dispositivo pueden definir su propio Parcelable para su extensión.

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

Finalmente, el nuevo Parcelable se puede adjuntar al Parcelable original a través del 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>();

Construyendo contra el tiempo de ejecución AIDL

AIDL tiene tres backends diferentes: Java, NDK, CPP. Para usar AIDL estable, siempre debe usar la copia del sistema de libbinder en system/lib*/libbinder.so y hablar en /dev/binder . Para el código de la imagen del proveedor, esto significa que no se puede utilizar libbinder (del VNDK): esta biblioteca tiene una API de C++ inestable y componentes internos inestables. En su lugar, el código del proveedor nativo debe usar el backend NDK de AIDL, vincularse con libbinder_ndk (que está respaldado por el sistema libbinder.so ) y vincularse con las bibliotecas -ndk_platform creadas por las entradas aidl_interface .

Nombres de instancia del servidor AIDL HAL

Por convención, los servicios AIDL HAL tienen un nombre de instancia con el formato $package.$type/$instance . Por ejemplo, una instancia del vibrador HAL está registrada como android.hardware.vibrator.IVibrator/default .

Escribir un servidor AIDL HAL

Los servidores @VintfStability AIDL deben declararse en el manifiesto VINTF, por ejemplo así:

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

De lo contrario, deberán registrar un servicio AIDL normalmente. Al ejecutar pruebas VTS, se espera que todos los HAL AIDL declarados estén disponibles.

Escribir un cliente AIDL

Los clientes AIDL deben declararse en la matriz de compatibilidad, por ejemplo así:

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

Conversión de un HAL existente de HIDL a AIDL

Utilice la herramienta hidl2aidl para convertir una interfaz HIDL a AIDL.

características hidl2aidl :

  • Cree archivos .aidl basados ​​en los archivos .hal para el paquete dado
  • Cree reglas de compilación para el paquete AIDL recién creado con todos los backends habilitados
  • Cree métodos de traducción en los backends de Java, CPP y NDK para traducir de los tipos HIDL a los tipos AIDL.
  • Cree reglas de compilación para traducir bibliotecas con las dependencias requeridas
  • Cree afirmaciones estáticas para garantizar que los enumeradores HIDL y AIDL tengan los mismos valores en los backends de CPP y NDK.

Siga estos pasos para convertir un paquete de archivos .hal en archivos .aidl:

  1. Cree la herramienta ubicada en system/tools/hidl/hidl2aidl .

    La creación de esta herramienta a partir de la fuente más reciente proporciona la experiencia más completa. Puede utilizar la última versión para convertir interfaces en ramas más antiguas de versiones anteriores.

    m hidl2aidl
    
  2. Ejecute la herramienta con un directorio de salida seguido del paquete a convertir.

    Opcionalmente, utilice el argumento -l para agregar el contenido de un nuevo archivo de licencia al principio de todos los archivos generados. Asegúrese de utilizar la licencia y la fecha correctas.

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

    Por ejemplo:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Lea los archivos generados y solucione cualquier problema con la conversión.

    • conversion.log contiene cualquier problema no controlado que debe solucionarse primero.
    • Los archivos .aidl generados pueden tener advertencias y sugerencias que podrían necesitar acción. Estos comentarios comienzan con // .
    • Aprovecha para limpiar y hacer mejoras en el paquete.
    • Consulte la anotación @JavaDerive para conocer las funciones que podrían ser necesarias, como toString o equals .
  4. Construya sólo los objetivos que necesita.

    • Deshabilite los backends que no se utilizarán. Prefiera el backend NDK al backend CPP; consulte Elegir tiempo de ejecución .
    • Elimine las bibliotecas de traducción o cualquiera de sus códigos generados que no se utilizarán.
  5. Consulte Principales diferencias entre AIDL y HIDL .

    • El uso del Status y las excepciones integrados de AIDL generalmente mejora la interfaz y elimina la necesidad de otro tipo de estado específico de la interfaz.
    • Los argumentos de la interfaz AIDL en los métodos no son @nullable de forma predeterminada como lo eran en HIDL.

Política de seguridad para AIDL HAL

Un tipo de servicio AIDL que sea visible para el código del proveedor debe tener el atributo hal_service_type . De lo contrario, la configuración de sepolicy es la misma que la de cualquier otro servicio AIDL (aunque existen atributos especiales para HAL). A continuación se muestra una definición de ejemplo de un contexto de servicio HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Para la mayoría de los servicios definidos por la plataforma, ya se agrega un contexto de servicio con el tipo correcto (por ejemplo, android.hardware.foo.IFoo/default ya estaría marcado como hal_foo_service ). Sin embargo, si un cliente de marco admite varios nombres de instancia, se deben agregar nombres de instancia adicionales en archivos service_contexts específicos del dispositivo.

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

Los atributos HAL deben agregarse cuando creamos un nuevo tipo de HAL. Un atributo HAL específico puede estar asociado con múltiples tipos de servicios (cada uno de los cuales puede tener múltiples instancias, como acabamos de comentar). Para un HAL, foo , tenemos hal_attribute(foo) . Esta macro define los atributos hal_foo_client y hal_foo_server . Para un dominio determinado, las macros hal_client_domain y hal_server_domain asocian un dominio con un atributo HAL determinado. Por ejemplo, el hecho de que el servidor del sistema sea cliente de este HAL corresponde a la política hal_client_domain(system_server, hal_foo) . Un servidor HAL incluye de manera similar hal_server_domain(my_hal_domain, hal_foo) . Normalmente, para un atributo HAL determinado, también creamos un dominio como hal_foo_default como referencia o HAL de ejemplo. Sin embargo, algunos dispositivos utilizan estos dominios para sus propios servidores. Distinguir entre dominios para múltiples servidores solo importa si tenemos múltiples servidores que sirven a la misma interfaz y necesitan un conjunto de permisos diferente en sus implementaciones. En todas estas macros, hal_foo no es en realidad un objeto de política. En cambio, estas macros utilizan este token para hacer referencia al grupo de atributos asociados con un par de servidor cliente.

Sin embargo, hasta ahora, no hemos asociado hal_foo_service y hal_foo (el par de atributos de hal_attribute(foo) ). Un atributo HAL está asociado con los servicios AIDL HAL mediante la macro hal_attribute_service (los HAL HIDL utilizan la macro hal_attribute_hwservice ). Por ejemplo, hal_attribute_service(hal_foo, hal_foo_service) . Esto significa que los procesos hal_foo_client pueden obtener el HAL y los procesos hal_foo_server pueden registrar el HAL. La aplicación de estas reglas de registro la realiza el administrador de contexto ( servicemanager ). Tenga en cuenta que es posible que los nombres de los servicios no siempre correspondan con los atributos HAL. Por ejemplo, podríamos ver hal_attribute_service(hal_foo, hal_foo2_service) . Sin embargo, en general, dado que esto implica que los servicios siempre se usan juntos, podríamos considerar eliminar hal_foo2_service y usar hal_foo_service para todos nuestros contextos de servicio. La mayoría de los HAL que establecen múltiples hal_attribute_service se deben a que el nombre del atributo HAL original no es lo suficientemente general y no se puede cambiar.

Poniendo todo esto junto, un ejemplo de HAL se ve así:

    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)

Interfaces de extensión adjuntas

Se puede adjuntar una extensión a cualquier interfaz de carpeta, ya sea una interfaz de nivel superior registrada directamente con el administrador de servicios o una subinterfaz. Al obtener una extensión, debe confirmar que el tipo de extensión sea el esperado. Las extensiones solo se pueden configurar desde el proceso que sirve una carpeta.

Las extensiones adjuntas deben usarse siempre que una extensión modifique la funcionalidad de una HAL existente. Cuando se necesita una funcionalidad completamente nueva, no es necesario utilizar este mecanismo y se puede registrar una interfaz de extensión directamente con el administrador de servicios. Las interfaces de extensión adjuntas tienen más sentido cuando están adjuntas a subinterfaces, porque estas jerarquías pueden ser profundas o tener múltiples instancias. El uso de una extensión global para reflejar la jerarquía de la interfaz de carpeta de otro servicio requeriría una contabilidad exhaustiva para proporcionar una funcionalidad equivalente a las extensiones conectadas directamente.

Para configurar una extensión en la carpeta, utilice las siguientes API:

  • En el backend del NDK: AIBinder_setExtension
  • En el backend de Java: android.os.Binder.setExtension
  • En el backend de CPP: android::Binder::setExtension
  • En el backend de Rust: binder::Binder::set_extension

Para obtener una extensión en una carpeta, utilice las siguientes API:

  • En el backend del NDK: AIBinder_getExtension
  • En el backend de Java: android.os.IBinder.getExtension
  • En el backend de CPP: android::IBinder::getExtension
  • En el backend de Rust: binder::Binder::get_extension

Puede encontrar más información sobre estas API en la documentación de la función getExtension en el backend correspondiente. Puede encontrar un ejemplo de cómo usar extensiones en hardware/interfaces/tests/extension/vibrator .

Principales diferencias entre AIDL y HIDL

Cuando utilice AIDL HAL o utilice interfaces AIDL HAL, tenga en cuenta las diferencias en comparación con la escritura de HIDL HAL.

  • La sintaxis del lenguaje AIDL está más cerca de Java. La sintaxis HIDL es similar a la de C++.
  • Todas las interfaces AIDL tienen estados de error integrados. En lugar de crear tipos de estado personalizados, cree entradas de estado constantes en archivos de interfaz y use EX_SERVICE_SPECIFIC en los backends de CPP/NDK y ServiceSpecificException en el backend de Java. Consulte Manejo de errores .
  • AIDL no inicia automáticamente los grupos de subprocesos cuando se envían objetos de carpeta. Deben iniciarse manualmente (ver gestión de subprocesos ).
  • AIDL no cancela ante errores de transporte no verificados (HIDL Return cancela ante errores de transporte no verificados).
  • AIDL sólo puede declarar un tipo por archivo.
  • Los argumentos AIDL se pueden especificar como in/out/inout además del parámetro de salida (no hay "devoluciones de llamada sincrónicas").
  • AIDL utiliza un fd como tipo primitivo en lugar de identificador.
  • HIDL utiliza versiones principales para cambios incompatibles y versiones menores para cambios compatibles. En AIDL, se realizan cambios compatibles con versiones anteriores. AIDL no tiene un concepto explícito de versiones principales; en cambio, esto se incorpora a los nombres de los paquetes. Por ejemplo, AIDL podría usar el nombre de paquete bluetooth2 .
  • AIDL no hereda la prioridad en tiempo real de forma predeterminada. La función setInheritRt debe usarse por carpeta para habilitar la herencia de prioridad en tiempo real.