AIDL для HAL

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

HAL, использующие AIDL для связи между компонентами платформы, такими как в system.img , и аппаратными компонентами, такими как в vendor.img , должны использовать стабильный 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

Интерфейсы AOSP Stable AIDL для 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 расширяемым. Например, представьте, что разработчики устройств ожидают, что они смогут расширить определенный AOSP Parcelable , 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 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. Чтобы использовать стабильный AIDL, вы всегда должны использовать системную копию libbinder в system/lib*/libbinder.so и говорить в /dev/binder 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. Запустите инструмент с выходным каталогом, за которым следует конвертируемый пакет.

    hidl2aidl -o <output directory> <package>
    

    Например:

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

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

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

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

Sepolicy для AIDL HAL

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

    type hal_foo_service, service_manager_type, vendor_service;

Для большинства служб, определенных платформой, контекст службы с правильным типом уже добавлен (например, 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, vendor_service, 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_call(hal_foo_server, servicemanager)

    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
    binder_use(some_hal_server_domain)
    hal_server_domain(some_hal_server_domain, hal_foo)

Подключенные интерфейсы расширения

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

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

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

  • В бэкэнде NDK: AIBinder_setExtension
  • В бэкэнде Java: android.os.Binder.setExtension
  • В бэкэнде CPP: android::Binder::setExtension

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

  • В бэкэнде NDK: AIBinder_getExtension
  • В бэкэнде Java: android.os.IBinder.getExtension
  • В бэкэнде CPP: android::IBinder::getExtension

Дополнительную информацию об этих 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 .