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
. - В новый пакет также могут быть добавлены новые интерфейсы.
- Все типы данных родительского пакета присутствуют в новом пакете и могут обрабатываться (возможно, переопределенными) методами из старого пакета.
- Новые типы данных также могут быть добавлены для использования либо новыми методами обновленных существующих интерфейсов, либо новыми интерфейсами.
- Интерфейсы верхнего уровня родительского пакета присутствуют в новом пакете, хотя интерфейсы могут иметь новые методы, новые локальные UDT интерфейса (расширение уровня интерфейса, описанное ниже) и новые UDT в
- Расширяемость с обратной совместимостью на уровне интерфейса . Новый пакет также может расширять исходный пакет, состоящий из логически отдельных интерфейсов, которые просто обеспечивают дополнительную функциональность, а не базовую. Для этого может быть желательно следующее:
- Интерфейсы в новом пакете требуют обращения к типам данных старого пакета.
- Интерфейсы в новом пакете могут расширять интерфейсы одного или нескольких старых пакетов.
- Расширение исходной обратной несовместимости . Это предыдущая версия пакета, и между ними не должно быть никакой корреляции. В той степени, в какой это есть, это может быть выражено комбинацией типов из более старой версии пакета и наследованием подмножества интерфейсов старого пакета.
Структурирование интерфейсов
Для хорошо структурированного интерфейса добавление новых типов функциональности, не являющихся частью исходного дизайна, должно потребовать модификации интерфейса 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 | Верно все следующее:
|
---|
Из-за правила 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 по композиции.
Однако один тип отношений строго определен и должен применяться: наследование с обратной совместимостью на уровне пакета . В этом сценарии родительский пакет - это пакет, от которого наследуется, а дочерний пакет - это пакет, расширяющий родительский. Правила наследования с обратной совместимостью на уровне пакета следующие:
- Все интерфейсы верхнего уровня родительского пакета наследуются интерфейсами дочернего пакета.
- Новые интерфейсы также могут быть добавлены в новый пакет (без ограничений относительно отношений с другими интерфейсами в других пакетах).
- Новые типы данных также могут быть добавлены для использования либо новыми методами обновленных существующих интерфейсов, либо новыми интерфейсами.
Эти правила могут быть реализованы с использованием наследования на уровне интерфейса HIDL и композиции UDT, но для того, чтобы знать, что эти отношения составляют обратно совместимое расширение пакета, требуется знание метауровня. Это знание выводится следующим образом:
Если пакет соответствует этому требованию, hidl-gen
применяет правила обратной совместимости.