AIDL para HAL

Android 11 presenta la capacidad de usar AIDL para HAL en Android. Esto hace posible implementar partes de Android sin HIDL. Haga la transición de las HAL para usar AIDL exclusivamente donde sea posible (cuando las HAL ascendentes usan HIDL, se debe usar HIDL).

Las HAL que usan AIDL para comunicarse entre los componentes del marco, como los de system.img , y los componentes de hardware, como los de vendor.img , deben usar AIDL estable. Sin embargo, para comunicarse dentro de una partición, por ejemplo, de una HAL a otra, no hay restricciones sobre el mecanismo de IPC a utilizar.

Motivación

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

  • Usar un solo lenguaje IPC significa tener solo una cosa para aprender, depurar, optimizar y asegurar.
  • AIDL admite el control de versiones en el lugar para los propietarios de una interfaz:
    • Los propietarios pueden agregar métodos al final de las interfaces o campos para parcelables. Esto significa que es más fácil versionar el código a lo largo de los años, y también el costo anual 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 volver a basar las extensiones posteriores en versiones más nuevas de las interfaces.
  • Una interfaz AIDL existente se puede usar directamente cuando su propietario elige estabilizarla. Antes, se tendría que crear una copia completa de la interfaz en HIDL.

Escribir una interfaz AIDL HAL

Para que se use 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 de aidl_interface debe incluir la stability: "vintf", .

Solo el propietario de una interfaz puede realizar estos cambios.

Cuando realice estos cambios, la interfaz debe estar en el manifiesto VINTF para que funcione. Pruebe esto (y los requisitos relacionados, como verificar que las interfaces publicadas estén congeladas) usando 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 enlace antes de enviarlo a otro proceso. La degradación de un servicio a la estabilidad del proveedor no se admite en Java 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, deshabilite 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 deshabilitarlo.

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

Encontrar interfaces AIDL HAL

Las interfaces AIDL estables de AOSP para HAL se encuentran en los mismos directorios base que las interfaces HIDL, en las carpetas aidl .

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

Debe colocar las interfaces de extensión en otros subdirectorios de 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 deseen 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 se pueden registrar de dos maneras diferentes:

  • en tiempo de ejecución, consulte las extensiones adjuntas .
  • independiente, registrado globalmente y en VINTF.

Sin embargo, se registra una extensión, cuando los componentes específicos del proveedor (es decir, que no forman parte del AOSP ascendente) utilizan la interfaz, no hay posibilidad de conflicto de fusión. Sin embargo, cuando se realizan modificaciones descendentes a los componentes AOSP ascendentes, pueden surgir conflictos de fusión y se recomiendan las siguientes estrategias:

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

Paquetes de extensión: ParcelableHolder

ParcelableHolder es un Parcelable que puede contener otro Parcelable . El principal caso de uso de ParcelableHolder es hacer extensible un Parcelable . Por ejemplo, la imagen de 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 vio en el código anterior, esta práctica no se cumple porque los campos agregados por el implementador del dispositivo pueden tener un conflicto cuando se revisa Parcelable en las próximas versiones de Android.

Con 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 de dispositivos 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 de AIDL

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

Nombres de instancias 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 se registra 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ían registrar un servicio AIDL normalmente. Cuando se ejecutan pruebas VTS, se espera que todas las HAL AIDL declaradas estén disponibles.

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

Convertir 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 bibliotecas de traducción 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 a 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 usar 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.

    hidl2aidl -o <output directory> <package>
    

    Por ejemplo:

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

    • conversion.log contiene problemas no manejados para solucionar primero.
    • Los archivos .aidl generados pueden tener advertencias y sugerencias que pueden necesitar acción. Estos comentarios comienzan con // .
    • Aproveche la oportunidad para limpiar y hacer mejoras en el paquete.
  4. Cree solo los objetivos que necesita.

    • Deshabilite los backends que no se utilizarán. Prefiere el backend NDK sobre el backend CPP, consulta cómo elegir el tiempo de ejecución .
    • Elimine las bibliotecas de traducción o cualquiera de sus códigos generados que no se usarán.
  5. Ver Principales diferencias AIDL/HIDL .

    • El uso del Status y las excepciones integrados de AIDL generalmente mejoran la interfaz y eliminan la necesidad de otro tipo de estado específico de la interfaz.

Sepolicy para AIDL HAL

Un tipo de servicio AIDL que es visible para el código del proveedor debe tener el atributo de vendor_service . De lo contrario, la configuración de la política es la misma que la de cualquier otro servicio AIDL (aunque existen atributos especiales para las HAL). Aquí hay una definición de ejemplo de un contexto de servicio HAL:

    type hal_foo_service, service_manager_type, vendor_service;

Para la mayoría de los servicios definidos por la plataforma, ya se agregó 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 varios tipos de servicios (cada uno de los cuales puede tener varias 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 dado, las macros hal_client_domain y hal_server_domain asocian un dominio con un atributo HAL dado. Por ejemplo, el servidor del sistema como cliente de esta HAL corresponde a la política hal_client_domain(system_server, hal_foo) . De manera similar, un servidor HAL incluye hal_server_domain(my_hal_domain, hal_foo) . Por lo general, para un atributo HAL dado, también creamos un dominio como hal_foo_default para referencia o HAL de ejemplo. Sin embargo, algunos dispositivos usan estos dominios para sus propios servidores. Distinguir entre dominios para múltiples servidores solo importa si tenemos múltiples servidores que sirven 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 su lugar, estas macros utilizan este token para hacer referencia al grupo de atributos asociados con un par de servidor de 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 (las 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 la HAL y los procesos hal_foo_server pueden registrar la 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 se 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 las 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, vendor_service, 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_call(hal_foo_server, servicemanager)

    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
    binder_use(some_hal_server_domain)
    hal_server_domain(some_hal_server_domain, hal_foo)

Interfaces de extensión adjuntas

Se puede adjuntar una extensión a cualquier interfaz de enlace, 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 es el esperado. Las extensiones solo se pueden establecer desde el proceso que entrega un archivador.

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 del servicio. Las interfaces de extensión adjuntas tienen más sentido cuando se adjuntan a subinterfaces, porque estas jerarquías pueden ser profundas o de varias instancias. El uso de una extensión global para reflejar la jerarquía de la interfaz de enlace de otro servicio requeriría una contabilidad exhaustiva para proporcionar una funcionalidad equivalente a las extensiones conectadas directamente.

Para establecer una extensión en el cuaderno, use 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

Para obtener una extensión en un archivador, use 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

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 AIDL/HIDL

Cuando use AIDL HAL o interfaces AIDL HAL, tenga en cuenta las diferencias en comparación con escribir HIDL HAL.

  • La sintaxis del lenguaje AIDL está más cerca de Java. La sintaxis de HIDL es similar a C++.
  • Todas las interfaces AIDL tienen estados de error incorporados. En lugar de crear tipos de estado personalizados, cree enteros de estado constante 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 grupos de subprocesos cuando se envían objetos de enlace. Deben iniciarse manualmente (ver gestión de subprocesos ).
  • AIDL no aborta con errores de transporte no verificados (HIDL Return aborta con errores no verificados).
  • AIDL solo 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 usa un fd como tipo primitivo en lugar de handle.
  • HIDL utiliza versiones principales para cambios incompatibles y versiones secundarias para cambios compatibles. En AIDL, los cambios compatibles con versiones anteriores se realizan en su lugar. AIDL no tiene un concepto explícito de versiones principales; en cambio, esto se incorpora en los nombres de los paquetes. Por ejemplo, AIDL podría usar el nombre de paquete bluetooth2 .