Google стремится продвигать расовую справедливость для черных сообществ. Смотри как.
Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Versioning

HIDL требует, чтобы каждый интерфейс, написанный на HIDL, был версионным. После публикации интерфейса HAL он замораживается, и необходимо внести любые дальнейшие изменения в новую версию этого интерфейса. Хотя данный опубликованный интерфейс не может быть изменен, он может быть расширен другим интерфейсом.

Структура кода HIDL

Код HIDL организован в определяемые пользователем типы, интерфейсы и пакеты:

  • Пользовательские типы (UDT) . HIDL предоставляет доступ к набору примитивных типов данных, которые можно использовать для составления более сложных типов через структуры, объединения и перечисления. UDT передаются методам интерфейсов и могут быть определены на уровне пакета (общего для всех интерфейсов) или локально для интерфейса.
  • Интерфейсы Как основной строительный блок HIDL, интерфейс состоит из UDT и объявлений методов. Интерфейсы также могут наследоваться от другого интерфейса.
  • Пакеты Организует связанные интерфейсы HIDL и типы данных, с которыми они работают. Пакет идентифицируется по имени и версии и включает в себя следующее:
    • Файл определения типа данных с именем types.hal .
    • Ноль или более интерфейсов, каждый в своем собственном .hal файле.

Файл определения типов данных types.hal содержит только UDT (все UDT уровня пакета хранятся в одном файле). Представления на целевом языке доступны для всех интерфейсов в пакете.

Философия версий

Пакет HIDL (такой как android.hardware.nfc ) после публикации для данной версии (например, 1.0 ) является неизменным; это не может быть изменено. Модификации интерфейсов в пакете или любые изменения его UDT могут иметь место только в другом пакете.

В HIDL управление версиями применяется на уровне пакета, а не на уровне интерфейса, и все интерфейсы и UDT в пакете имеют одну и ту же версию. Версии пакета следуют за семантическим версионированием без компонентов уровня исправления и метаданных сборки. Внутри данного пакета второстепенный выпад версии подразумевает, что новая версия пакета обратно совместима со старым пакетом, а основной выпуклый вариант подразумевает, что новая версия пакета не обратно совместима со старым пакетом.

Концептуально пакет может относиться к другому пакету одним из следующих способов:

  • Совсем нет .
  • Возможность обратной совместимости на уровне пакетов . Это происходит для новых версий младшей версии (следующей увеличенной версии) пакета; новый пакет имеет то же имя и основную версию, что и старый, но более младшую версию. Функционально новый пакет является надмножеством старого пакета, что означает:
    • Интерфейсы верхнего уровня родительского пакета присутствуют в новом пакете, хотя интерфейсы могут иметь новые методы, новые локальные интерфейсы UDT (расширение уровня интерфейса, описанное ниже) и новые UDT в types.hal .
    • Новые интерфейсы также могут быть добавлены в новый пакет.
    • Все типы данных родительского пакета присутствуют в новом пакете и могут обрабатываться (возможно, переопределенными) методами из старого пакета.
    • Новые типы данных также могут быть добавлены для использования либо новыми методами обновленных существующих интерфейсов, либо новыми интерфейсами.
  • Обратно-совместимая расширяемость на уровне интерфейса . Новый пакет также может расширять оригинальный пакет, состоящий из логически отдельных интерфейсов, которые просто предоставляют дополнительные функциональные возможности, а не основной. Для этой цели может быть желательно следующее:
    • Интерфейсы в новом пакете должны обращаться к типам данных старого пакета.
    • Интерфейсы в новом пакете могут расширять интерфейсы одного или нескольких старых пакетов.
  • Расширьте оригинальную обратную несовместимость . Это основная версия пакета, и между ними не должно быть никакой корреляции. В той степени, в которой они есть, это можно выразить комбинацией типов из более старой версии пакета и наследованием подмножества интерфейсов старого пакета.

Структурирование интерфейсов

Для хорошо структурированного интерфейса добавление новых типов функциональности, которые не являются частью первоначального дизайна, должно потребовать модификации интерфейса HIDL. И наоборот, если вы можете или ожидаете внести изменения в обе стороны интерфейса, которые вводят новые функциональные возможности без изменения самого интерфейса, то интерфейс не структурирован.

Treble поддерживает отдельно скомпилированные компоненты vendor и system, в которых vendor.img на устройстве и system.img могут быть скомпилированы отдельно. Все взаимодействия между vendor.img и system.img должны быть четко и полностью определены, чтобы они могли продолжать работать в течение многих лет. Это включает в себя множество поверхностей API, но основная поверхность - это механизм IPC, который HIDL использует для межпроцессного взаимодействия на границе system.img / vendor.img .

Требования

Все данные, передаваемые через HIDL, должны быть явно определены. Чтобы гарантировать, что реализация и клиент могут продолжать совместную работу, даже если они компилируются отдельно или разрабатываются независимо, данные должны соответствовать следующим требованиям:

  • Может быть описан в HIDL напрямую (с использованием структурных перечислений и т. Д.) С семантическими именами и значением.
  • Может быть описано публичным стандартом, таким как ISO / IEC 7816.
  • Может быть описан аппаратным стандартом или физической компоновкой аппаратного обеспечения.
  • При необходимости могут быть непрозрачными данными (такими как открытые ключи, идентификаторы и т. Д.).

Если используются непрозрачные данные, они должны быть прочитаны только одной стороной интерфейса HIDL. Например, если код vendor.img дает компоненту system.img строковое сообщение или данные vec<uint8_t> , эти данные не могут быть проанализированы самим system.img ; его можно только передать обратно в vendor.img для интерпретации. При передаче значения из vendor.img в код поставщика на system.img или на другое устройство, формат данных и способ их интерпретации должны быть точно описаны и все еще являются частью интерфейса .

Руководящие указания

Вы должны быть в состоянии написать реализацию или клиент HAL, используя только файлы .hal (т.е. вам не нужно смотреть на источник Android или общедоступные стандарты). Мы рекомендуем указать точное требуемое поведение. Такие выражения, как «реализация может выполнять A или B», побуждают реализации переплетаться с клиентами, с которыми они разрабатываются.

Макет кода HIDL

HIDL включает в себя пакеты ядра и поставщиков.

Базовые интерфейсы HIDL - те, которые определены Google. Пакеты, к которым они относятся, начинаются с android.hardware. и именуются подсистемой, потенциально с вложенными уровнями именования. Например, пакет NFC называется android.hardware.nfc а пакет камеры - android.hardware.camera . В общем, основной пакет имеет название android.hardware. [ name1 ]. [ name2 ]…. Пакеты HIDL имеют версию в дополнение к своему имени. Например, пакет android.hardware.camera может быть версии 3.4 ; это важно, поскольку версия пакета влияет на его размещение в исходном дереве.

Все базовые пакеты помещаются в hardware/interfaces/ в системе сборки Пакет android.hardware. [ name1 ]. [ name2 ]… в версии $m.$n находится в /$m.$n/ hardware/interfaces/name1/name2//$m.$n/ ; Пакет android.hardware.camera версии 3.4 находится в каталоге hardware/interfaces/camera/3.4/. Существует жестко закодированное сопоставление между префиксом пакета android.hardware. и путь hardware/interfaces/ .

Неосновные (вендорные) пакеты - это пакеты, производимые поставщиком SoC или ODM. Префикс для неосновных пакетов - vendor.$(VENDOR).hardware. где $(VENDOR) относится к поставщику SoC или OEM / ODM. Это отображается на пути vendor/$(VENDOR)/interfaces в дереве (это отображение также жестко запрограммировано).

Полностью определенные имена пользовательских типов

В HIDL каждый UDT имеет полное имя, которое состоит из имени UDT, имени пакета, в котором определен UDT, и версии пакета. Полное имя используется только тогда, когда объявляются экземпляры типа, а не там, где определен сам тип. Например, предположим, что пакет android.hardware.nfc, версия 1.0 определяет структуру с именем NfcData . На сайте объявления (будь то в types.hal или в объявлении интерфейса) объявление просто заявляет:

struct NfcData {
    vec<uint8_t> data;
};

При объявлении экземпляра этого типа (будь то в структуре данных или в качестве параметра метода) используйте полное имя типа:

android.hardware.nfc@1.0::NfcData

Общий синтаксис PACKAGE @ VERSION :: UDT , где:

  • PACKAGE - это разделенное точками имя пакета HIDL (например, android.hardware.nfc ).
  • VERSION - это формат Major.minor-version, разделенный точками (например, 1.0 ).
  • UDT - это имя HIDL UDT, разделенное точками. Поскольку HIDL поддерживает вложенные UDT, а интерфейсы HIDL могут содержать UDT (тип вложенного объявления), для доступа к именам используются точки.

Например, если в файле общих типов в пакете android.hardware.example version 1.0 определено следующее вложенное объявление:

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

Полное имя для Bar - android.hardware.example@1.0::Foo.Bar . Если в дополнение к указанному выше пакету вложенное объявление было в интерфейсе под названием IQuux :

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

Полное имя для Bar - android.hardware.example@1.0::IQuux.Foo.Bar .

В обоих случаях Bar может упоминаться как Bar только в рамках объявления Foo . На уровне пакета или интерфейса вы должны обратиться к Bar через Foo : Foo.Bar , как в объявлении метода doSomething выше. В качестве альтернативы, вы можете более подробно описать метод как:

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

Полностью определенные значения перечисления

Если UDT является типом перечисления, то каждое значение типа перечисления имеет полное имя, начинающееся с полного имени типа перечисления, за которым следует двоеточие, а затем имя значения перечисления. Например, предположим, что пакет android.hardware.nfc, версии 1.0 определяет тип перечисления NfcStatus :

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

При ссылке на STATUS_OK , полное имя:

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

Общий синтаксис: PACKAGE @ VERSION :: UDT : VALUE , где:

  • PACKAGE @ VERSION :: UDT - это точно такое же полное имя для типа enum.
  • VALUE - это имя значения.

Правила авто-вывода

Полное имя UDT указывать не нужно. Имя UDT может безопасно пропустить следующее:

  • Пакет, например, @1.0::IFoo.Type
  • И пакет, и версия, например, IFoo.Type

HIDL пытается завершить имя, используя правила автоинтерференции (более низкий номер правила означает более высокий приоритет).

Правило 1

Если пакет и версия не указаны, выполняется поиск локального имени. Пример:

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

NfcErrorMessage локально, а typedef над ним находится. NfcData также NfcData локально, но поскольку он не определен локально, используются правила 2 и 3. @1.0::NfcStatus предоставляет версию, поэтому правило 1 не применяется.

Правило 2

Если правило 1 не выполнено и компонент с полным именем отсутствует (пакет, версия или пакет и версия), компонент автоматически заполняется информацией из текущего пакета. Компилятор HIDL затем просматривает текущий файл (и все операции импорта), чтобы найти полностью заполненное имя с автозаполнением. Используя приведенный выше пример, предположим, что объявление ExtendedNfcData было сделано в том же пакете ( android.hardware.nfc ) в той же версии ( 1.0 ), что и NfcData , следующим образом:

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

Компилятор HIDL заполняет имя пакета и имя версии из текущего пакета, чтобы получить полное имя UDT android.hardware.nfc@1.0::NfcData . Поскольку имя существует в текущем пакете (при условии, что оно импортировано правильно), оно используется для объявления.

Имя в текущем пакете импортируется, только если выполняется одно из следующих условий:

  • Это импортируется явно с оператором import .
  • Это определено в types.hal в текущем пакете

Тот же процесс выполняется, если NfcData был квалифицирован только номером версии:

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

Правило 3

Если по правилу 2 не удается найти совпадение (UDT не определен в текущем пакете), компилятор HIDL проверяет совпадение во всех импортированных пакетах. Используя приведенный выше пример, предположим, что ExtendedNfcData объявлен в версии 1.1 пакета android.hardware.nfc , 1.1 импортирует 1.0 как и должно (см. Расширения уровня пакета ), и в определении указано только имя UDT:

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

Компилятор ищет любой UDT с именем NfcData и находит его в android.hardware.nfc в версии 1.0 , в результате чего получается полностью квалифицированный UDT android.hardware.nfc@1.0::NfcData . Если найдено несколько совпадений для данного частично квалифицированного UDT, компилятор HIDL выдает ошибку.

пример

Используя правило 2, импортированный тип, определенный в текущем пакете, предпочтительнее импортированного типа из другого пакета:

// 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 интерполируется как android.hardware.bar@1.0::S и находится в bar/1.0/types.hal (потому что types.hal автоматически импортируется).
  • IFooCallback интерполируется как android.hardware.bar@1.0::IFooCallback с использованием правила 2, но не может быть найден, потому что bar/1.0/IFooCallback.hal не импортируется автоматически (как types.hal ). Таким образом, правило 3 разрешает его вместо android.hardware.foo@1.0::IFooCallback , который импортируется через import android.hardware.foo@1.0; ).

types.hal

Каждый пакет HIDL содержит файл types.hal содержащий UDT, которые являются общими для всех интерфейсов, участвующих в этом пакете. Типы HIDL всегда общедоступны; независимо от того, объявлен ли UDT в types.hal или в объявлении интерфейса, эти типы доступны вне области, в которой они определены. types.hal предназначен не для описания общедоступного API пакета, а скорее для размещения UDT, используемых всеми интерфейсами в пакете. Из-за природы HIDL все UDT являются частью интерфейса.

types.hal состоит из UDT и операторов import . Поскольку types.hal доступен каждому интерфейсу пакета (это неявный импорт), эти операторы import по определению являются пакетными. UDT в types.hal также могут включать в себя UDT и интерфейсы, импортированные таким образом.

Например, для 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;

Следующие импортируются:

  • android.hidl.base@1.0::IBase (неявно)
  • android.hardware.foo@1.0::types (неявно)
  • Все в android.hardware.bar@1.0 (включая все интерфейсы и его types.hal )
  • types.hal из android.hardware.baz@1.0::types (интерфейсы в android.hardware.baz@1.0 не импортируются)
  • IQux.hal и types.hal от android.hardware.qux@1.0
  • Quuz от android.hardware.quuz@1.0 (при условии, что Quuz определен в types.hal , types.hal весь файл types.hal , но типы, отличные от Quuz , не импортируются).

Управление версиями на уровне интерфейса

Каждый интерфейс в пакете находится в своем собственном файле. Пакет, к которому принадлежит интерфейс, объявляется в верхней части интерфейса с помощью оператора package . После объявления пакета может быть указан ноль или более импортов уровня интерфейса (частичный или полный пакет). Например:

package android.hardware.nfc@1.0;

В HIDL интерфейсы могут наследоваться от других интерфейсов, используя ключевое слово extends . Чтобы интерфейс расширял другой интерфейс, он должен иметь доступ к нему через оператор import . Имя расширяемого интерфейса (базовый интерфейс) следует правилам уточнения имени типа, описанным выше. Интерфейс может наследоваться только от одного интерфейса; HIDL не поддерживает множественное наследование.

Приведенные ниже примеры версий uprev используют следующий пакет:

// 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);
}

Правила упрев

Чтобы определить пакет package@major.minor , либо A, либо все B должны быть истинными:

Правило А «Является стартовой минорной версией»: все предыдущие минорные версии, package@major.0 , package@major.(minor-1) package@major.1 ,…, package@major.(minor-1) package@major.1 package@major.(minor-1) не должны быть определены.
ИЛИ
Правило Б

Все следующее верно:

  1. «Предыдущая минорная версия действительна»: должен быть определен package@major.(minor-2) package@major.0 package@major.(minor-1) и следовать тому же правилу A (не определено ни одно из package@major.0 package@major.(minor-2) package@major.0 package@major.(minor-2) ) или правилу B (если это uprev от @major.(minor-2) );

    И

  2. «Унаследовать хотя бы один интерфейс с тем же именем»: существует интерфейс package@major.minor::IFoo который расширяет package@major.(minor-1)::IFoo (если предыдущий пакет имеет интерфейс);

    И

  3. «Нет унаследованного интерфейса с другим именем»: не должно существовать package@major.minor::IBar , расширяющего package@major.(minor-1)::IBaz , где IBar и IBaz - это два разных имени. Если существует интерфейс с таким же именем, package@major.minor::IBar должен расширять package@major.(minor-k)::IBar package@major.minor::IBar package@major.(minor-k)::IBar , чтобы не было IBar с меньшим k.

Из-за правила А:

  • Пакет может начинаться с любого младшего номера версии (например, android.hardware.biometrics.fingerprint начинается с @2.1 .)
  • Требование « android.hardware.foo@1.0 не определено» означает, что каталог hardware/interfaces/foo/1.0 даже не должен существовать.

Однако правило A не влияет на пакет с тем же именем пакета, но с другой основной версией (например, для android.hardware.camera.device @1.0 и @3.2 ; @3.2 не нужно взаимодействовать с @1.0 .) Следовательно, @3.2::IExtFoo может расширять @1.0::IFoo .

Если имя пакета отличается, package@major.minor::IBar может расширяться из интерфейса с другим именем (например, android.hardware.bar@1.0::IBar может расширять android.hardware.baz@2.2::IBaz ). Если интерфейс не явно объявлять супер типа с extend ключевое слово, оно будет распространяться android.hidl.base@1.0::IBase (кроме IBase сам).

B.2 и B.3 должны соблюдаться одновременно. Например, даже если android.hardware.foo@1.1::IFoo расширяет android.hardware.foo@1.0::IFoo для передачи правила B.2, если android.hardware.foo@1.1::IExtBar расширяет android.hardware.foo@1.0::IBar , это все еще недействительный uprev.

Обновление интерфейсов

Чтобы увеличить android.hardware.example@1.0 (определено выше) до @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);
}

Это import на уровне пакета версии 1.0 types.hal android.hardware.example в types.hal . Хотя в версии 1.1 пакета не добавляются новые UDT, ссылки на UDT в версии 1.0 по-прежнему необходимы, следовательно, импорт на уровне пакета в types.hal . (Такой же эффект мог бы быть достигнут при импорте на уровне интерфейса в IQuux.hal .)

В extends @1.0::IQuux в объявлении IQuux мы указали версию IQuux , которая наследуется (устранение неоднозначности требуется, поскольку IQuux используется для объявления интерфейса и наследования от интерфейса). Поскольку объявления - это просто имена, которые наследуют все атрибуты пакета и версии на сайте объявления, устранение неоднозначности должно быть в имени базового интерфейса; мы могли бы также использовать полностью квалифицированный UDT, но это было бы излишним.

Новый интерфейс IQuux не повторно объявляет метод fromFooToBar() он наследует от @1.0::IQuux ; он просто перечисляет новый метод, который он добавляет из fromBarToFoo() . В HIDL унаследованные методы не могут быть снова объявлены в дочерних интерфейсах, поэтому интерфейс IQuux не может явно объявить метод fromFooToBar() .

Упревские соглашения

Иногда имена интерфейсов должны переименовывать расширяющий интерфейс. Мы рекомендуем, чтобы расширения, структуры и объединения перечислений имели то же имя, что и они, если они не достаточно отличаются, чтобы гарантировать новое имя. Примеры:

// 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 };

Если метод может иметь новое семантическое имя (например, fooWithLocation ), тогда это предпочтительнее. В противном случае он должен быть назван аналогично тому, что он расширяет. Например, метод foo_1_1 в @1.1::IFoo может заменить функциональность метода foo в @1.0::IFoo если нет лучшего альтернативного имени.

Управление версиями на уровне пакетов

Управление версиями HIDL происходит на уровне пакета; после публикации пакета он остается неизменным (его набор интерфейсов и UDT нельзя изменить). Пакеты могут относиться друг к другу несколькими способами, каждый из которых может быть выражен посредством комбинации наследования на уровне интерфейса и построения UDT по составу.

Тем не менее, один тип отношений строго определен и должен применяться принудительно: обратно-совместимое наследование на уровне пакета . В этом сценарии родительский пакет является наследуемым пакетом, а дочерний - расширяющим родительский. Обратно-совместимые правила наследования на уровне пакетов:

  1. Все интерфейсы верхнего уровня родительского пакета наследуются интерфейсами дочернего пакета.
  2. Новые интерфейсы также могут быть добавлены в новый пакет (без ограничений по отношению к другим интерфейсам в других пакетах).
  3. Новые типы данных также могут быть добавлены для использования либо новыми методами обновленных существующих интерфейсов, либо новыми интерфейсами.

Эти правила могут быть реализованы с использованием наследования на уровне интерфейса HIDL и композиции UDT, но требуют знания метауровня, чтобы знать, что эти отношения составляют обратно-совместимое расширение пакета. Эти знания заключаются в следующем:

Если пакет соответствует этому требованию, hidl-gen применяет правила обратной совместимости.