Control de versiones de la interfaz

El HIDL requiere que todas las interfaces escritas en el HIDL tengan un control de versiones. Después de un HAL interfaz de usuario, se bloquea y se deben realizar más cambios en la a la versión nueva de esa interfaz. Si bien una determinada interfaz publicada no se puede modificado, puede extenderse con otra interfaz.

Estructura del código HIDL

El código HIDL se organiza en reglas definidas por el usuario tipos, interfaces y paquetes:

  • Tipos definidos por el usuario (UDT). El HIDL brinda acceso a un conjunto de tipos de datos primitivos que pueden usarse para componer tipos más complejos a través de estructuras, uniones y enumeraciones. Las UDT se pasan a métodos de interfaces de usuario y pueden definirse a nivel de un paquete (común para todos interfaces) o de forma local a una interfaz.
  • Interfaces. Como componente básico del HIDL, una interfaz consta de declaraciones de UDT y de métodos. Las interfaces también pueden heredar de otra interfaz.
  • Paquetes. Organiza las interfaces HIDL relacionadas y los datos los tipos en los que operan. Un paquete se identifica con un nombre, una versión y incluye lo siguiente:
    • Archivo de definición de tipo de datos llamado types.hal.
    • Cero o más interfaces, cada una en su propio archivo .hal

El archivo de definición de tipo de datos types.hal contiene solo UDT (todas las UDT a nivel del paquete se mantienen en un solo archivo). Representaciones en el destino están disponibles para todas las interfaces del paquete.

Filosofía del control de versiones

Un paquete HIDL (como android.hardware.nfc), después de publicado para una versión determinada (como 1.0), es inmutable. it no se puede cambiar. Modificaciones en las interfaces del paquete o en cualquier los cambios en sus UDT solo se pueden producir en otro paquete.

En HIDL, el control de versiones se aplica a nivel de paquete, no a nivel de interfaz y todas las interfaces y UDT de un paquete comparten la misma versión. Paquete versiones siguen la estructura semántica control de versiones sin el nivel de parche ni componentes de metadatos de compilación. En un objeto paquete determinado, un cambio de prioridad de versión secundaria implica la nueva versión de el paquete es retrocompatible con el paquete anterior y un elemento cambio de versión implica que la versión nueva del paquete retrocompatible con el paquete anterior.

Conceptualmente, un paquete puede relacionarse con otro paquete de varias maneras:

  • Para nada.
  • Extensibilidad retrocompatible a nivel del paquete. Esta se produce para las nuevas versiones secundarias (próxima revisión incrementada) de un paquete el nuevo paquete tiene el mismo nombre y la misma versión principal que el paquete anterior, pero a una versión secundaria superior. Funcionalmente, el nuevo paquete es un superconjunto del antiguo package, es decir:
    • Las interfaces de nivel superior del paquete superior están presentes en el paquete nuevo. aunque las interfaces pueden tener nuevos métodos, nuevas UDT de interfaz local (las extensión a nivel de la interfaz que se describe a continuación) y nuevas UDT en types.hal
    • También se pueden agregar interfaces nuevas al paquete nuevo.
    • Todos los tipos de datos del paquete superior están presentes en el paquete nuevo. pueden controlarse mediante los métodos (posiblemente reimplementados) del paquete anterior.
    • También se pueden agregar nuevos tipos de datos para su uso con cualquier nuevo método de aumento a través de interfaces existentes o a través de interfaces nuevas.
  • Extensibilidad retrocompatible a nivel de la interfaz. La nueva herramienta también puede extender el paquete original al consistir de manera lógica que proporcionan funcionalidad adicional, no la principal. Para ello, es posible que te convenga lo siguiente:
    • Las interfaces del paquete nuevo deben recurrir a los tipos de datos del antiguo .
    • Las interfaces del paquete nuevo pueden extender las interfaces de uno o más paquetes antiguos paquetes.
  • Extiende la incompatibilidad original con versiones anteriores. Este es un el aumento de ingresos de la versión principal del paquete y no es necesario que haya ninguna correlación entre ambos. En la medida en que haya, se puede expresar con una combinación de de la versión anterior del paquete y la herencia de un subconjunto de con interfaces del paquete anterior.

Estructuración de la interfaz

Para lograr una interfaz bien estructurada, agregar nuevos tipos de funcionalidad que no sean parte del diseño original, deberían requerir una modificación en el HIDL interfaz de usuario. Por el contrario, si puedes o esperas realizar un cambio en ambos lados de la interfaz que presenta una nueva funcionalidad sin cambiar la interfaz entonces, la interfaz no está estructurada.

Treble admite componentes de proveedores y sistemas compilados por separado en los que el vendor.img en un dispositivo, y el system.img se puede compilarse por separado. Todas las interacciones entre vendor.img y system.img debe definirse de forma explícita y exhaustiva para que pueda seguirán funcionando durante muchos años. Esto incluye muchas plataformas de APIs, pero una gran es el mecanismo de IPC que usa HIDL para la comunicación entre procesos en la Límite de system.img/vendor.img.

Requisitos

Todos los datos que se pasan por HIDL deben definirse de manera explícita. Para garantizar un y el cliente pueden seguir trabajando juntos incluso cuando se compilan separadas o desarrolladas de forma independiente, los datos deben cumplir con lo siguiente requisitos:

  • Se puede describir en HIDL directamente (mediante structs de enumeraciones, etc.) con nombres semánticos y su significado.
  • Pueden describirse según una norma pública, como ISO/IEC 7816.
  • Puede describirse mediante un estándar de hardware o un diseño físico del hardware.
  • Pueden ser datos opacos (como claves públicas, IDs, etc.) si es necesario.

Si se usan datos opacos, solo un lado del HIDL debe leerlos. interfaz de usuario. Por ejemplo, si el código vendor.img proporciona un componente en el system.img un mensaje de cadena o vec<uint8_t> esos datos no pueden ser analizados por la system.img en sí; puede solo se deben volver a pasar a vendor.img para interpretar. Cuándo y pasa un valor de vendor.img al código del proveedor en system.img o a otro dispositivo, el formato de los datos y cómo debe interpretarse debe describirse exactamente y sigue siendo parte del interfaz de programación de aplicaciones.

Lineamientos

Deberías poder escribir una implementación o un cliente de una HAL solo con el uso los archivos .hal (es decir, no deberías tener que consultar los datos con los estándares necesarios). Te recomendamos que especifiques el comportamiento requerido exacto. Declaraciones como como “una implementación podría hacer A o B” alentar las implementaciones se relacionan con los clientes con los que se desarrollan.

Diseño del código HIDL

HIDL incluye paquetes principales y de proveedores.

Las interfaces principales del HIDL son las que especifica Google. Los paquetes a los que pertenecen. comiencen con android.hardware. y los nombra el subsistema, potencialmente con niveles anidados de nomenclatura. Por ejemplo, el paquete NFC se llama android.hardware.nfc y el paquete de la cámara es android.hardware.camera En general, un paquete principal tiene el nombre android.hardware.[name1].[name2]... Los paquetes de HIDL tienen una versión además de su nombre. Por ejemplo, el paquete android.hardware.camera puede estar en la versión 3.4. esto es importante, ya que la versión de un paquete afecta su ubicación en el árbol de fuentes.

Todos los paquetes principales se colocan en hardware/interfaces/, en la de compilación. El paquete android.hardware.[name1].[name2]... en la versión $m.$n es inferior hardware/interfaces/name1/name2/.../$m.$n/; paquete La versión 3.4 de android.hardware.camera está en el directorio hardware/interfaces/camera/3.4/. Existe una asignación hard-coded entre el prefijo del paquete android.hardware. y la ruta hardware/interfaces/

Los paquetes no principales (proveedores) son aquellos producidos por el proveedor del SoC o la ODM. El de los paquetes que no son principales es vendor.$(VENDOR).hardware., en la que $(VENDOR) se refiere a un proveedor de SoC, o bien a OEM/ODM. Esto se relaciona con la ruta vendor/$(VENDOR)/interfaces en el árbol (esta asignación también es hard-coded).

Nombres de tipo definidos por el usuario completamente calificados

En el HIDL, cada UDT tiene un nombre completamente calificado que consta del nombre de la UDT, el nombre del paquete en el que se define la UDT y la versión del paquete. El el nombre completamente calificado se usa solo cuando se declaran instancias del tipo y no donde se define el tipo en sí. Por ejemplo, supongamos que package La versión 1.0 de android.hardware.nfc, define una struct con el nombre NfcData. En el sitio de la declaración (ya sea en types.hal o dentro de la declaración de una interfaz), la declaración simplemente dice:

struct NfcData {
    vec<uint8_t> data;
};

Cuando declares una instancia de este tipo (ya sea dentro de una estructura de datos o como parámetro del método), usa el nombre del tipo completamente calificado:

android.hardware.nfc@1.0::NfcData

La sintaxis general es PACKAGE@VERSION::UDT, donde:

  • PACKAGE es el nombre separado por puntos de un paquete HIDL. (p.ej., android.hardware.nfc).
  • VERSION es la versión mayor.secundaria separada por puntos. formato del paquete (p.ej., 1.0).
  • UDT es el nombre separado por puntos de una UDT de HIDL. Dado que el HIDL admite UDT anidadas y las interfaces HIDL pueden contener UDT (un tipo de declaración anidada), se usan puntos para acceder a los nombres.

Por ejemplo, si la siguiente declaración anidada se definió en el lenguaje tipos de archivo en la versión del paquete android.hardware.example 1.0

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

El nombre completamente calificado de Bar es android.hardware.example@1.0::Foo.Bar Si, además de estar en en el paquete anterior, la declaración anidada estaba en una interfaz llamada IQuux:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

El nombre completamente calificado de Bar es android.hardware.example@1.0::IQuux.Foo.Bar

En ambos casos, Bar solo puede denominarse Bar. dentro del alcance de la declaración de Foo. En el paquete o nivel de interfaz, debes hacer referencia a Bar a través de Foo: Foo.Bar, como en la declaración del método doSomething arriba. Como alternativa, puedes declarar el método de forma más detallada de la siguiente manera:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

Valores de enumeración completamente calificados

Si una UDT es de tipo enum, cada valor del tipo enum tiene un nombre completamente calificado que comienza con el nombre completamente calificado del tipo enum, seguido de dos puntos y, luego, el nombre del valor de enumeración. Por ejemplo: se supone que el paquete android.hardware.nfc, versión 1.0 define un tipo de enumeración NfcStatus:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Cuando se hace referencia a STATUS_OK, el nombre completamente calificado es el siguiente:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

La sintaxis general es PACKAGE@VERSION::UDT:VALUE, En el ejemplo anterior, se ilustra lo siguiente:

  • PACKAGE@VERSION::UDT es el tiene exactamente el mismo nombre completamente calificado para el tipo enum.
  • VALUE es el nombre del valor.

Reglas de inferencia automática

No es necesario especificar un nombre de UDT completamente calificado. Un nombre de UDT puede omite con seguridad lo siguiente:

  • El paquete, p.ej., @1.0::IFoo.Type
  • Paquete y versión, p.ej., IFoo.Type

El HIDL intenta completar el nombre usando reglas de interferencia automática (regla inferior número significa mayor prioridad).

Regla 1

Si no se proporcionan el paquete y la versión, se intenta realizar una búsqueda de nombre local. Ejemplo:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

NfcErrorMessage se busca de forma local, y typedef por encima de ella. NfcData también se busca de forma local, pero tal como es no definidos localmente, se usan las reglas 2 y 3. @1.0::NfcStatus proporciona una versión, por lo que la regla 1 no se aplica.

Regla 2

Si la regla 1 falla y falta un componente del nombre completamente calificado (paquete, versión o paquete y versión), el componente se autocompleta con información del paquete actual. El compilador HIDL luego busca archivo actual (y todas las importaciones) para encontrar el nombre calificado completamente completado. Con el ejemplo anterior, supone la declaración de ExtendedNfcData. se realizó en el mismo paquete (android.hardware.nfc) con la misma versión (1.0) como NfcData, de la siguiente manera:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

El compilador HIDL completa el nombre del paquete y el nombre de la versión del Paquete actual para producir el nombre de la UDT completamente calificado android.hardware.nfc@1.0::NfcData Como el nombre existe en paquete actual (suponiendo que se haya importado correctamente), se utiliza para el declaración.

Un nombre en el paquete actual se importa solo si se cumple una de las siguientes condiciones: verdadero:

  • Se importa de manera explícita con una sentencia import.
  • Se define en types.hal en el paquete actual.

Se sigue el mismo proceso si NfcData fue calificado solo por el número de versión:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

Regla 3

Si la regla 2 no produce una coincidencia (la UDT no está definida en el ), el compilador HIDL busca una coincidencia dentro de todos los paquetes importados. Con el ejemplo anterior, supongamos que ExtendedNfcData se declara en versión 1.1 del paquete android.hardware.nfc, 1.1 importa 1.0 como corresponde (consulta extensiones a nivel de paquete) y la definición especifica solo el nombre de la UDT:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

El compilador busca cualquier UDT llamada NfcData y la encuentra en android.hardware.nfc en la versión 1.0, lo que da como resultado un UDT completamente calificada de android.hardware.nfc@1.0::NfcData. Si hay más más de una coincidencia para una UDT parcialmente calificada, el compilador HIDL arroja un error.

Ejemplo

Con la regla 2, se da prioridad a un tipo importado definido en el paquete actual sobre un tipo importado desde otro paquete:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S se interpola como android.hardware.bar@1.0::S y se encuentra en bar/1.0/types.hal (porque types.hal se actualiza automáticamente importados).
  • IFooCallback se interpola como android.hardware.bar@1.0::IFooCallback con la regla 2, pero No se puede encontrar porque no se importó bar/1.0/IFooCallback.hal. automáticamente (como types.hal). Por lo tanto, la regla 3 lo resuelve android.hardware.foo@1.0::IFooCallback, que se importa a través de import android.hardware.foo@1.0;).

tipos.hal

Cada paquete HIDL contiene un archivo types.hal que contiene UDT que se comparten entre todas las interfaces que participan en ese paquete. Tipos de HIDL siempre son públicos; independientemente de si una UDT se declara en types.hal o dentro de una declaración de interfaz, estos tipos son accesibles fuera del alcance en el que se definen. types.hal no está diseñado para describir la API pública de un paquete, sino para alojar UDT que usan todas las interfaces dentro del paquete. Debido a la naturaleza del HIDL, todas las UDT son parte de la interfaz.

types.hal consta de UDT y sentencias import. Debido a que types.hal está disponible para todas las interfaces de la (es una importación implícita), estas sentencias import se a nivel del paquete por definición. Las UDT en types.hal también pueden incorporar Así, se importan las UDT y las interfaces.

Por ejemplo, para IFoo.hal:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

Se importaron los siguientes elementos:

  • android.hidl.base@1.0::IBase (implícita)
  • android.hardware.foo@1.0::types (implícita)
  • Todo el contenido de android.hardware.bar@1.0 (incluidos todos interfaces y su types.hal)
  • types.hal de android.hardware.baz@1.0::types (las interfaces de android.hardware.baz@1.0 no se importan)
  • IQux.hal y types.hal de android.hardware.qux@1.0
  • Quuz desde android.hardware.quuz@1.0 (suponiendo que Quuz se define en types.hal, se define Se analiza el archivo types.hal, pero es de tipos distintos de Quuz no se importan).

Control de versiones a nivel de la interfaz

Cada interfaz dentro de un paquete reside en su propio archivo. El paquete que a la que pertenece la interfaz se declara en la parte superior de la interfaz a través del package. Después de la declaración del paquete, cero o más las importaciones a nivel de la interfaz (parciales o de paquete completo). Por ejemplo:

package android.hardware.nfc@1.0;

En HIDL, las interfaces pueden heredar de otras interfaces mediante Palabra clave extends. Para que una interfaz extienda otra, debe tener acceso a él mediante una sentencia import. El nombre del que se extiende (la interfaz base) sigue las reglas de type-name calificación que se explicó anteriormente. Una interfaz solo puede heredar contenido de una interfaz. El HIDL no admite la herencia múltiple.

Los siguientes ejemplos de control de versiones de uprev usan el siguiente paquete:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

Reglas de Uprev

Para definir un paquete package@major.minor, puede ser A o todos los de B. debe ser verdadero:

Regla A "Es una versión secundaria inicial": Todas las versiones secundarias anteriores, package@major.0, package@major.1, ..., No se debe definir package@major.(minor-1).
OR
Regla B

Todas las siguientes afirmaciones son verdaderas:

  1. "La versión secundaria anterior es válida": package@major.(minor-1) debe definirse y seguir la misma regla A (ninguna de package@major.0 a package@major.(minor-2) están definidos) o la regla B (si es un aumento de ingresos de @major.(minor-2));

    Y

  2. “Heredar al menos una interfaz con el mismo nombre”: interfaz package@major.minor::IFoo que extiende package@major.(minor-1)::IFoo (si el paquete anterior tiene una interfaz)

    Y

  3. “No hay una interfaz heredada con un nombre diferente”: No debe existir package@major.minor::IBar que extiende package@major.(minor-1)::IBaz, donde IBar y IBaz son dos nombres diferentes. Si hay una interfaz con el mismo nombre, package@major.minor::IBar debe extender package@major.(minor-k)::IBar de modo que no exista ningún IBar con un elemento k menor.

Debido a la regla A:

  • El paquete puede comenzar con cualquier número de versión secundario (por ejemplo, android.hardware.biometrics.fingerprint comienza en @2.1).
  • El requisito "android.hardware.foo@1.0 no está definido" significa el directorio hardware/interfaces/foo/1.0 ni siquiera debería existir.

Sin embargo, la regla A no afecta a un paquete con el mismo nombre, sino con un diferente versión principal (por ejemplo, android.hardware.camera.device tiene @1.0 y Se definió @3.2; No es necesario que @3.2 interactúe con @1.0). Por lo tanto, @3.2::IExtFoo puede extender @1.0::IFoo

Si el nombre del paquete es diferente, package@major.minor::IBar se puede extender desde una interfaz con un nombre diferente (por ejemplo, android.hardware.bar@1.0::IBar puede extender android.hardware.baz@2.2::IBaz). Si una interfaz no declarar explícitamente un supertipo con la palabra clave extend, extiende android.hidl.base@1.0::IBase (excepto IBase) a sí mismo).

B.2 y B.3 deben seguirse al mismo tiempo. Por ejemplo, incluso si android.hardware.foo@1.1::IFoo extiende android.hardware.foo@1.0::IFoo para pasar la regla B.2, si un android.hardware.foo@1.1::IExtBar extiende android.hardware.foo@1.0::IBar, todavía no es un uprev válido.

Interfaces de Uprev

Para aumentar los ingresos de android.hardware.example@1.0 (definidos anteriormente) a @1.1

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

Este es un nivel de paquete import de la versión 1.0 de android.hardware.example en types.hal Si bien no hay Las UDT se agregan en la versión 1.1 del paquete; las referencias a las UDT en la versión 1.0, por lo que la importación a nivel del paquete en types.hal. (El mismo efecto se podría haber logrado con un importación a nivel de interfaz en IQuux.hal).

En extends @1.0::IQuux, en la declaración de IQuux, especificamos la versión de IQuux que se implementará heredado (es necesario desambiguar porque IQuux se usa para declarar una interfaz y heredar de una interfaz). Como las declaraciones es simplemente nombres que heredan todos los atributos de paquetes y versiones en el sitio del la declaración, la desambiguación debe realizarse en el nombre de la interfaz base; nosotros también podría haber usado la UDT completamente calificada, pero redundantes.

La nueva interfaz IQuux no vuelve a declarar el método. fromFooToBar() que hereda de @1.0::IQuux; simplemente Enumera el método nuevo que agrega fromBarToFoo(). En HIDL, heredado Los métodos no se pueden volver a declarar en las interfaces secundarias, por lo que la interfaz IQuux no puede declarar fromFooToBar() de forma explícita.

Convenciones de Uprev

A veces, los nombres de las interfaces deben cambiar el nombre de la interfaz que se extiende. Recomendaciones que las extensiones de enumeración, structs y uniones tienen el mismo nombre que lo que extienden a menos que sean lo suficientemente diferentes como para justificar un nombre nuevo. Ejemplos:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

Si un método puede tener un nombre semántico nuevo (por ejemplo, fooWithLocation) y, luego, se prefiere. De lo contrario, debería ser de forma similar a lo que extiende. Por ejemplo, el método foo_1_1 en @1.1::IFoo puede reemplazar la funcionalidad del método foo en @1.0::IFoo si no hay una nombre alternativo.

Control de versiones a nivel de paquete

El control de versiones de HIDL se produce a nivel del paquete. después de que se publica un paquete, es inmutable (su conjunto de interfaces y UDT no se puede cambiar). Los paquetes pueden se relacionan entre sí de varias maneras, todas ellas se pueden expresar a través de un combinación de herencia a nivel de interfaz y creación de UDT por composición.

Sin embargo, hay un tipo de relación que está estrictamente definido y debe aplicarse: Herencia retrocompatible a nivel del paquete. En este caso, el parent. Es el paquete del que se heredará. child es el que extiende el superior. Nivel de paquete y las reglas de herencia retrocompatibles son las siguientes:

  1. Todas las interfaces de nivel superior del paquete superior heredan de las interfaces del del paquete secundario.
  2. Las interfaces nuevas también se pueden agregar al paquete nuevo (sin restricciones sobre relaciones con otras interfaces en otros paquetes).
  3. También se pueden agregar nuevos tipos de datos para su uso con cualquier nuevo método de aumento a través de interfaces existentes o a través de interfaces nuevas.

Estas reglas se pueden implementar mediante la herencia a nivel de la interfaz HIDL y la UDT composiciones, pero requieren conocimientos de metanivel para conocer estas relaciones constituyen una extensión de paquete compatible con versiones anteriores. Este conocimiento se infiere de la siguiente manera:

Si un paquete cumple con este requisito, hidl-gen aplica y las reglas de retrocompatibilidad.