Google is committed to advancing racial equity for Black communities. See how.
Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Управление версиями

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.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 находится в разделе hardware/interfaces/name1/name2//$m.$n/ ; Пакет android.hardware.camera версии 3.4 находится в каталоге hardware/interfaces/camera/3.4/. Между префиксом пакета android.hardware. существует жестко запрограммированное сопоставление 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-версии пакета (например, 1.0 ).
  • UDT - это разделенное точками имя HIDL UDT. Поскольку HIDL поддерживает вложенные UDT, а интерфейсы HIDL могут содержать UDT (тип вложенного объявления), для доступа к именам используются точки.

Например, если следующее вложенное объявление было определено в файле общих типов в пакете android.hardware.example версии 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 - это то же самое полное имя для типа перечисления.
  • 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) не должны определяться.
ИЛИ
Правило B

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

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

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

  • Пакет может начинаться с любого второстепенного номера версии (например, 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 в types.hal . Хотя новые UDT не добавляются в версию 1.1 пакета, ссылки на UDT в версии 1.0 по-прежнему необходимы, поэтому импорт уровня пакета в types.hal . (Такого же эффекта можно было бы достичь с помощью импорта уровня интерфейса в IQuux.hal .)

В extends @1.0::IQuux в объявлении 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 применяет правила обратной совместимости.