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. Вот некоторые преимущества AIDL:

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

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

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

Как написать интерфейс HAL AIDL

Чтобы интерфейс 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 для объекта связывателя, прежде чем он будет отправлен в другой процесс.

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

В коде ниже показано, как отключить серверную часть CPP:

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

Как найти интерфейсы AIDL HAL

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

  • hardware/interfaces – для интерфейсов, обычно предоставляемых оборудованием.
  • frameworks/hardware/interfaces – для высокоуровневых интерфейсов, предоставляемых оборудованию.
  • system/hardware/interfaces – для низкоуровневых интерфейсов, предоставляемых оборудованию.

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

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

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

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

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

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

Объекты Parcelable для расширений: ParcelableHolder

ParcelableHolder – это экземпляр интерфейса Parcelable, который может содержать другой экземпляр Parcelable.

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

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

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>();

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

По соглашению сервисы HAL AIDL имеют название экземпляра в формате $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 ожидается, что все заявленные HAL-интерфейсы AIDL будут доступны.

Как написать клиент 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 (.aidl) на основе файлов HAL (.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. Подробнее о том, как выполнить сборку для среды выполнения AIDL…
    • Удалите библиотеки для перевода или любой сгенерированный ими код, который не будет использоваться.
  5. См. Основные различия между AIDL и HIDL:

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

SEPolicy для HAL-интерфейсов AIDL

Тип сервиса 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 связан с сервисами HAL AIDL с помощью макроса 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)

Интерфейсы прикрепленных расширений

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

Используйте прикрепленные расширения, когда расширение изменяет функциональность существующего 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 вместо handle.
  • HIDL использует основные версии для несовместимых изменений и второстепенные версии для совместимых изменений. В AIDL изменения, обеспечивающие обратную совместимость, вносятся на месте. В AIDL нет явного понятия основных версий. Вместо этого они включаются в названия пакетов. Например, AIDL может использовать название пакета bluetooth2.
  • AIDL не наследует приоритет реального времени по умолчанию. Чтобы включить наследование приоритета в реальном времени, для каждого связующего элемента нужно использовать функцию setInheritRt.

Тестирование HAL

В этом разделе описаны рекомендации по тестированию HAL. Эти рекомендации действуют, даже если интеграционный тест для вашего HAL не входит в VTS.

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

VTS для HAL состоит из двух основных частей.

1. Проверьте, что HAL на устройстве известны и ожидаются Android.

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

Этот набор тестов можно найти в разделе test/vts-testcase/hal/treble/vintf. Если вы работаете над реализацией HAL поставщика, используйте vts_treble_vintf_vendor_test, чтобы проверить ее. Вы можете запустить этот тест с помощью команды atest vts_treble_vintf_vendor_test.

Эти тесты проверяют:

  • Каждый интерфейс @VintfStability, объявленный в манифесте VINTF, заморожен в известной выпущенной версии. Это позволяет убедиться, что обе стороны интерфейса согласны с точным определением этой версии интерфейса. Это необходимо для базовой работы.
  • Все HAL, указанные в манифесте VINTF, доступны на устройстве. Любой клиент с достаточными разрешениями для использования объявленного сервиса HAL должен иметь возможность получать и использовать эти сервисы в любое время.
  • Все HAL, объявленные в манифесте VINTF, обслуживают версию интерфейса, которую они объявляют в манифесте.
  • На устройстве не используются устаревшие HAL. Android прекращает поддержку более ранних версий интерфейсов HAL, как описано в разделе Жизненный цикл FCM.
  • На устройстве присутствуют необходимые HAL. Некоторые HAL необходимы для правильной работы Android.

2. Проверьте ожидаемое поведение каждого HAL.

Для каждого интерфейса HAL есть собственные тесты VTS, которые проверяют ожидаемое поведение клиентов. Тестовые примеры выполняются для каждого экземпляра объявленного интерфейса HAL и обеспечивают определенное поведение в зависимости от версии реализованного интерфейса.

В C++ вы можете получить список всех HAL, установленных в системе, с помощью функции android::getAidlHalInstanceNames в libaidlvintf_gtest_helper. В Rust используйте binder::get_declared_instances.

Эти тесты охватывают все аспекты реализации HAL, на которые полагается или может полагаться в будущем платформа Android.

В частности, проверяется поддержка функций, обработка ошибок и другие аспекты работы сервиса, важные для клиента.

Этапы VTS для разработки HAL

При создании или изменении интерфейсов HAL Android необходимо поддерживать актуальность тестов VTS (или любых других тестов).

Тесты VTS должны быть завершены и готовы к проверке реализаций поставщиков до того, как они будут заморожены для выпусков Android Vendor API. Они должны быть готовы до заморозки интерфейсов, чтобы разработчики могли создать свои реализации, проверить их и предоставить отзывы разработчикам интерфейса HAL.

Тестирование на Cuttlefish

Если оборудование недоступно, Android использует Cuttlefish в качестве средства разработки для интерфейсов HAL. Это позволяет проводить масштабируемое интеграционное тестирование Android.

hal_implementation_test проверяет, реализованы ли в Cuttlefish последние версии интерфейсов HAL, чтобы убедиться, что Android готов к работе с новыми интерфейсами, а тесты VTS – к тестированию новых реализаций поставщиков, как только появится новое оборудование и устройства.