AIDL для HAL

В Android 11 появилась возможность использовать AIDL для HAL в Android. Это позволяет реализовать части Android без HIDL. Переходите HAL на использование исключительно AIDL, где это возможно (когда восходящие HAL используют HIDL, необходимо использовать HIDL).

HAL, использующие AIDL для связи между компонентами платформы, например, в system.img , и аппаратными компонентами, например, vendor.img , должны использовать Stable AIDL. Однако для связи внутри раздела, например, от одного HAL к другому, нет никаких ограничений на использование механизма IPC.

Мотивация

AIDL существует дольше, чем HIDL, и используется во многих других местах, например, между компонентами платформы Android или в приложениях. Теперь, когда AIDL имеет поддержку стабильности, можно реализовать весь стек с помощью одной среды выполнения IPC. AIDL также имеет лучшую систему управления версиями, чем HIDL.

  • Использование одного языка IPC означает, что нужно изучать, отлаживать, оптимизировать и защищать только одну вещь.
  • AIDL поддерживает управление версиями на месте для владельцев интерфейса:
    • Владельцы могут добавлять методы в конец интерфейсов или поля в объекты. Это означает, что с годами легче версионировать код, а также годовые затраты меньше (типы можно изменять на месте, и нет необходимости в дополнительных библиотеках для каждой версии интерфейса).
    • Интерфейсы расширений можно подключать во время выполнения, а не в системе типов, поэтому нет необходимости перебазировать последующие расширения на более новые версии интерфейсов.
  • Существующий интерфейс AIDL может использоваться напрямую, когда его владелец решит его стабилизировать. Раньше в HIDL приходилось создавать полную копию интерфейса.

Написание интерфейса AIDL HAL

Чтобы интерфейс AIDL мог использоваться между системой и поставщиком, в интерфейс необходимо внести два изменения:

  • Каждое определение типа должно быть аннотировано @VintfStability .
  • Объявление aidl_interface должно включать stability: "vintf", .

Эти изменения может вносить только владелец интерфейса.

Когда вы вносите эти изменения, для работы интерфейс должен присутствовать в манифесте VINTF . Проверьте это (и связанные с ним требования, например проверку того, что выпущенные интерфейсы заморожены) с помощью теста VTS vts_treble_vintf_vendor_test . Вы можете использовать интерфейс @VintfStability без этих требований, вызвав AIBinder_forceDowngradeToLocalStability в серверной части NDK, android::Stability::forceDowngradeToLocalStability в внутренней части C++ или android.os.Binder#forceDowngradeToSystemStability в внутренней части Java для объекта связывания перед его отправкой. в другой процесс. Понижение уровня службы до стабильности поставщика не поддерживается в Java, поскольку все приложения выполняются в системном контексте.

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

Обратите внимание, что использование backends в приведенном ниже примере кода является правильным, поскольку их три (Java, NDK и CPP). В приведенном ниже коде показано, как конкретно выбрать серверную часть CPP, чтобы отключить ее.

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

Поиск интерфейсов AIDL HAL

Стабильные интерфейсы AIDL AOSP для HAL находятся в тех же базовых каталогах, что и интерфейсы HIDL, в папках aidl .

  • оборудование/интерфейсы
  • фреймворки/аппаратное обеспечение/интерфейсы
  • система/оборудование/интерфейсы

Вам следует поместить интерфейсы расширения в другие подкаталоги hardware/interfaces в vendor или hardware .

Интерфейсы расширения

В каждом выпуске Android имеется набор официальных интерфейсов AOSP. Когда партнеры Android хотят добавить функциональность в эти интерфейсы, им не следует изменять их напрямую, поскольку это будет означать, что их среда выполнения Android несовместима со средой выполнения AOSP Android. Для устройств GMS отказ от изменения этих интерфейсов также является гарантией продолжения работы образа GSI.

Расширения могут регистрироваться двумя разными способами:

Однако расширение зарегистрировано, когда компоненты, специфичные для поставщика (то есть не являющиеся частью восходящего AOSP), используют интерфейс, вероятность конфликта слияния отсутствует. Однако при внесении изменений в вышестоящие компоненты AOSP могут возникнуть конфликты слияния, поэтому рекомендуются следующие стратегии:

  • дополнения к интерфейсу могут быть переданы в AOSP в следующем выпуске.
  • дополнения к интерфейсу, обеспечивающие дополнительную гибкость без конфликтов слияния, могут быть добавлены в следующую версию.

Расширение Parcelables: ParcelableHolder

ParcelableHolder — это Parcelable , который может содержать другой Parcelable . Основной вариант использования ParcelableHolder — сделать Parcelable расширяемым. Например, представьте, что разработчики устройств ожидают возможности расширить Parcelable , определенный AOSP, AospDefinedParcelable , включив в него дополнительные функции.

Раньше без ParcelableHolder разработчики устройств не могли модифицировать стабильный интерфейс AIDL, определенный AOSP, поскольку добавление дополнительных полей было бы ошибкой:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Как видно из предыдущего кода, эта практика нарушена, поскольку поля, добавленные разработчиком устройства, могут иметь конфликт при обновлении Parcelable в следующих выпусках Android.

Используя ParcelableHolder , владелец Parcelable может определить точку расширения в Parcelable .

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Затем разработчики устройств могут определить свой собственный Parcelable для своего расширения.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Наконец, новый Parcelable можно прикрепить к исходному Parcelable через поле ParcelableHolder .


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Создание на основе среды выполнения AIDL

AIDL имеет три разных бэкэнда: Java, NDK, CPP. Чтобы использовать Stable AIDL, вы всегда должны использовать системную копию libbinder по адресу system/lib*/libbinder.so и разговаривать по /dev/binder . Для кода в вендорном образе это означает, что libbinder (из VNDK) использовать нельзя: у этой библиотеки нестабильный C++ API и нестабильные внутренние компоненты. Вместо этого собственный код поставщика должен использовать серверную часть NDK AIDL, ссылаться на libbinder_ndk (которая поддерживается системой libbinder.so ) и ссылаться на библиотеки -ndk_platform , созданные aidl_interface .

Имена экземпляров сервера AIDL HAL

По соглашению службы AIDL HAL имеют имя экземпляра в формате $package.$type/$instance . Например, экземпляр вибратора HAL зарегистрирован как android.hardware.vibrator.IVibrator/default .

Написание сервера AIDL HAL

@VintfStability Серверы AIDL должны быть объявлены в манифесте VINTF, например так:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

В противном случае им следует зарегистрировать службу AIDL обычным способом. При запуске тестов VTS ожидается, что все объявленные AIDL HAL доступны.

Написание клиента AIDL

Клиенты AIDL должны объявить себя в матрице совместимости, например так:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Преобразование существующего HAL из HIDL в AIDL

Используйте инструмент hidl2aidl для преобразования интерфейса HIDL в AIDL.

Возможности hidl2aidl :

  • Создайте файлы .aidl на основе файлов .hal для данного пакета.
  • Создайте правила сборки для вновь созданного пакета AIDL со всеми включенными серверными модулями.
  • Создайте методы перевода в серверных модулях Java, CPP и NDK для перевода из типов HIDL в типы AIDL.
  • Создайте правила сборки для библиотек перевода с необходимыми зависимостями.
  • Создайте статические утверждения, чтобы гарантировать, что перечислители HIDL и AIDL имеют одинаковые значения в серверных модулях CPP и NDK.

Выполните следующие действия, чтобы преобразовать пакет файлов .hal в файлы .aidl:

  1. Создайте инструмент, расположенный в system/tools/hidl/hidl2aidl .

    Создание этого инструмента из новейшего источника обеспечивает наиболее полный опыт. Вы можете использовать последнюю версию для преобразования интерфейсов в старых ветках из предыдущих выпусков.

    m hidl2aidl
    
  2. Запустите инструмент, указав выходной каталог, за которым следует конвертируемый пакет.

    При необходимости используйте аргумент -l , чтобы добавить содержимое нового файла лицензии вверху всех созданных файлов. Обязательно используйте правильную лицензию и дату.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Например:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Прочитайте сгенерированные файлы и исправьте любые проблемы с преобразованием.

    • conversion.log содержит все необработанные проблемы, которые необходимо устранить в первую очередь.
    • Созданные файлы .aidl могут содержать предупреждения и предложения, которые могут потребовать действий. Эти комментарии начинаются с // .
    • Воспользуйтесь возможностью почистить и улучшить пакет.
    • Проверьте аннотацию @JavaDerive на наличие функций, которые могут потребоваться, например toString или equals .
  4. Стройте только те цели, которые вам нужны.

    • Отключите серверные части, которые не будут использоваться. Предпочитайте бэкэнд NDK бэкэнду CPP, см. Выбор среды выполнения .
    • Удалите библиотеки перевода или любой их сгенерированный код, который не будет использоваться.
  5. См. Основные различия AIDL/HIDL .

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

Sepolicy для AIDL HAL

Тип службы AIDL, видимый коду поставщика, должен иметь атрибут hal_service_type . В остальном конфигурация sepolicy такая же, как и у любой другой службы AIDL (хотя для HAL существуют специальные атрибуты). Вот пример определения контекста службы HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Для большинства служб, определенных платформой, контекст службы с правильным типом уже добавлен (например, android.hardware.foo.IFoo/default уже будет помечен как hal_foo_service ). Однако если клиент платформы поддерживает несколько имен экземпляров, дополнительные имена экземпляров необходимо добавить в файлы service_contexts для конкретного устройства.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Атрибуты HAL необходимо добавлять при создании нового типа HAL. Определенный атрибут HAL может быть связан с несколькими типами служб (каждый из которых может иметь несколько экземпляров, как мы только что обсуждали). Для HAL foo у нас есть hal_attribute(foo) . Этот макрос определяет атрибуты hal_foo_client и hal_foo_server . Для данного домена макросы hal_client_domain и hal_server_domain связывают домен с данным атрибутом HAL. Например, системный сервер, являющийся клиентом этого HAL, соответствует политике hal_client_domain(system_server, hal_foo) . Сервер HAL аналогичным образом включает hal_server_domain(my_hal_domain, hal_foo) . Обычно для данного атрибута HAL мы также создаем домен, например hal_foo_default для справки или примеров HAL. Однако некоторые устройства используют эти домены для своих собственных серверов. Различие между доменами для нескольких серверов имеет значение только в том случае, если у нас есть несколько серверов, которые обслуживают один и тот же интерфейс и нуждаются в разных наборах разрешений в своих реализациях. Во всех этих макросах hal_foo на самом деле не является объектом sepolicy. Вместо этого этот токен используется этими макросами для ссылки на группу атрибутов, связанных с парой клиент-сервер.

Однако до сих пор мы не связали hal_foo_service и hal_foo (пару атрибутов из hal_attribute(foo) ). Атрибут HAL связан со службами AIDL HAL с помощью макроса hal_attribute_service (HAL HIDL использует макрос hal_attribute_hwservice ). Например, hal_attribute_service(hal_foo, hal_foo_service) . Это означает, что процессы hal_foo_client могут получить доступ к HAL, а процессы hal_foo_server могут зарегистрировать HAL. Применение этих правил регистрации осуществляется менеджером контекста ( servicemanager ). Обратите внимание: имена служб не всегда могут соответствовать атрибутам HAL. Например, мы можем увидеть hal_attribute_service(hal_foo, hal_foo2_service) . Однако в целом, поскольку это подразумевает, что службы всегда используются вместе, мы могли бы рассмотреть возможность удаления hal_foo2_service и использования hal_foo_service для всех наших контекстов служб. В большинстве HAL, которые устанавливают несколько hal_attribute_service , исходное имя атрибута HAL недостаточно общее и не может быть изменено.

Объединив все это, пример HAL выглядит следующим образом:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Присоединенные интерфейсы расширения

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

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

Чтобы установить расширение для подшивки, используйте следующие API:

  • В бэкэнде NDK: AIBinder_setExtension
  • В серверной части Java: android.os.Binder.setExtension .
  • В бэкэнде CPP: android::Binder::setExtension
  • В бэкэнде Rust: binder::Binder::set_extension

Чтобы получить расширение для подшивки, используйте следующие API:

  • В бэкэнде NDK: AIBinder_getExtension
  • В серверной части Java: android.os.IBinder.getExtension .
  • В серверной части CPP: android::IBinder::getExtension
  • В бэкэнде Rust: binder::Binder::get_extension

Дополнительную информацию об этих API можно найти в документации функции getExtension в соответствующем бэкэнде. Пример использования расширений можно найти в hardware/interfaces/tests/extension/vibrator .

Основные различия AIDL/HIDL

При использовании AIDL HAL или интерфейсов AIDL HAL помните о различиях по сравнению с написанием HIDL HAL.

  • Синтаксис языка AIDL ближе к Java. Синтаксис HIDL аналогичен синтаксису C++.
  • Все интерфейсы AIDL имеют встроенные статусы ошибок. Вместо создания пользовательских типов состояния создайте целые числа постоянного состояния в файлах интерфейса и используйте EX_SERVICE_SPECIFIC в бэкэндах CPP/NDK и ServiceSpecificException в бэкэнде Java. См. Обработка ошибок .
  • AIDL не запускает автоматически пулы потоков при отправке объектов связывания. Их необходимо запускать вручную (см. управление потоками ).
  • AIDL не прерывается при непроверенных ошибках транспорта (HIDL Return прерывается при непроверенных ошибках).
  • AIDL может объявлять только один тип для каждого файла.
  • Аргументы AIDL могут быть указаны как in/out/inout в дополнение к выходному параметру (нет «синхронных обратных вызовов»).
  • AIDL использует fd в качестве примитивного типа вместо дескриптора.
  • HIDL использует основные версии для несовместимых изменений и второстепенные версии для совместимых изменений. В AIDL обратно совместимые изменения выполняются на месте. AIDL не имеет четкого понятия основных версий; вместо этого это включается в имена пакетов. Например, AIDL может использовать имя пакета bluetooth2 .
  • AIDL по умолчанию не наследует приоритет реального времени. Функцию setInheritRt необходимо использовать для каждой привязки, чтобы включить наследование приоритета в реальном времени.