Android 8.1 从零开始写 HAL -- (1) 定义接口

Android 8.1 从零开始写 HAL – (1) 定义接口

注意:本文基于 Android 8.1 进行分析
Qidi 2020.07.17 (Markdown & Haroopad)


【前言】

我们都知道从 Android 8.0 开始, Google 启动了 Treble 项目,自此开始推行 Binder 化的 HAL 实现。除少数类型 HAL 外,在 Android 9.0 及其之后的版本,Google 甚至要求大部分外设(peripherals) 必须支持使用 Binder 化的 HAL,否则都不再视为合规[1]。因此,本文中的 HAL 指的是用 HIDL 语言描述、使用 Binder 方式实现的 HAL。

在阅读本系列文章前,如果你已经大致了解 Android SELinux 概念(比如 *.te 文件和 file_contexts),以及 Binder 的基本调用方法(比如知道序列化的概念、 transact()/onTransact()的调用时机),对于理解本文的内容会很有帮助。

根据项目不同,有的时候我们也许希望在设备上使用一种新的外设,比如某种传感器;或者有的时候,我们可能需要在 Android native 层运行一个特定进程,用来辅助处理来自 Android Frameworks 的数据,或者用于和其它 native 层的进程进行交互。对于前一种场景,我们需要为外设编写全新的 HAL;对于后一种场景,我们不需要编写 HAL,但需要实现一个很类似的 vendor service。这二者的实现方式极其类似,区别只在于他们使用的设备节点不同(一个使用 /dev/hwbinder,另一个使用 /dev/vndbinder),还有各自申请的 sepolicy 权限有别。

以下正文以实现一个全新的 HAL 为例进行说明,但并不涉及对设备节点的操作。


一、明确 HAL 接口

Android 大量采用面向接口编程的理念,HAL 也不例外。常用的 HAL 模块接口已经由 Google 和业界充分讨论并预定义,比如 Audio 和 Camera,这些模块需要支持的功能也已经明确,其接口描述可以在 /hardware/interfaces/ 目录下找到。

同理,我们实现自己的 HAL 时,也应该先明确新 HAL 要支持的功能,再根据需求需要设计要暴露给给其它进程的接口。

为了说明方便,我们不以真实的外设 HAL 来描述,而是做一系列假设,自己给自己设计需求。虽然这样的 HAL 不对应具体的使用场景,但道理是相通的,完全可以照猫画虎、举一反三。

我们假设新 HAL 需要支持 3 个功能:

  • 允许其它进程主动设置状态
  • 允许其它进程注册回调函数
  • 允许其它进程注销回调函数

给新外设取名为 demoComponent,给新 HAL 的进程取名为 demoService。给支持上述 3 项功能的接口分别取名为 setStatus()registerCallback()unregisterCallback()。假设其它进程所设置的“状态”是一个 DemoData 结构体,如下:

struct {
    string  name;
    int     value;
} DemoData;

假设回调函数方法为 onCallbackEvent(), 参数同样为 DemoData 结构。


二、编写接口描述文件

通常,经过前述步骤明确接口之后,我们就可以写出完整的头文件来了,继而对应实现各接口。但是在 HAL 实现过程中,我们首先要写的不是头文件,而是创作出用 HIDL 语言编写的 *.hal 接口描述文件。(不过,在 Android R 上将支持用 AIDL 语言来编写接口描述文件,以后可能逐步废弃 HIDL 语言[2])

按照目前的开发规范,我们的自定义接口描述文件通常放在 /vendor//hardware/interfaces//// 目录下。 根据我们前文的假设,这里的 就是 demoComponent 可有可无,在这个例子中对应的是 demoServiceVersionCode 表示 HAL 接口的版本号,因为我们编写的是一个全新的 HAL,所以版本号是 1.0

因为我们的实现涉及 HAL 进程、回调函数、参数数据 3 个部分,所以对应的 hal 文件也有 3 个,分别是:

  • IDemoServiceDef.hal:

    /*
     *  Copyright Notice:
     *  Copyright (C) 2020, Qidi.Huang
     *  All Rights Reserved.
     *
     *  @author [email protected]
     */
    
    package [email protected];
    
    import IDemoCallback;
    
    /**
     * HIDL interface of demo service.
     * By using these APIs, client side can call into demo service,
     * and can receive callback.
     */
    interface IDemoServiceDef {
        /**
         * Set a status to demo service.
         *
         * @param  data   status to pass to demo service.
         * @return status 0 on success, -1 on failure.
         */
        setStatus(DemoData data) generates (int32_t status);
    
        /**
         * Registercallback function in demo service.
         *
         * @param  cb      callback function extends IDemoCallback interface
         *                 to be registered
         * @return status  0 on success, -1 on failure.
         */
        registerCallback(IDemoCallback cb) generates (int32_t status);
    
        /**
         * Unregister callback function in demo service.
         *
         * @param  cb      callback function extends IDemoCallback interface
         *                 to be unregistered
         * @return status  0 on success, -1 on failure.
         */
        unregisterCallback(IDemoCallback cb) generates (int32_t status);
    };
    
  • IDemoCallback.hal:

    /*
     *  Copyright Notice:
     *  Copyright (C) 2020 Qidi.Huang
     *  All Rights Reserved.
     *
     *  @author [email protected]
     */
    
    package [email protected];
    
    /**
     * HIDL interface of demo service callback function.
     */
    interface IDemoCallback {
    
       /**
        * Notify client side about status change
        *
        * @param  payload  data reflects status
        * @return status   return 0 on success, -1 on failed
        */
       onCallbackEvent(DemoData payload) generates (int32_t status);
    };
    
  • types.hal:

    /*
     *  Copyright Notice:
     *  Copyright (C) 2020, Qidi.Huang
     *  All Rights Reserved.
     *
     *  @author  [email protected]
     */
    
    package [email protected];
    
    struct DemoData
    {
        string  name;
        int32_t value;
    };
    

这里有四个基本点需要注意。

其一,数据类型描述文件的文件名是个固定名称 types.hal;接口描述文件的文件名要以大写字母 I 开头写作 IXxxx.hal,并且相应地以 interface IXxxx {} 进行描述;generates 后加数据类型表示接口的返回值类型

其二,每个 *.hal 文件都需要在文件头声明它所属的包。这里的包名为 package [email protected];

其三,HIDL 语言所使用的数据类型和 C/C++/Java 的数据类型稍有区别。举个简单例子,对比 DemoData 结构体的原始写法与 HIDL 写法,可以看到 int32 被替换成了 int32_t 再比如说,HIDL 数据类型也不支持 C/C++ 的原始指针。关于 HIDL 数据类型的详细介绍可以参考《HIDL 数据类型》。

另外,*.hal 文件之间相互引用时,使用 import 关键字进行声明。

这些固定形式是由 HIDL 框架决定的,我们必须遵从。

编写完成后用 ls 命令查看,就是下图中 3 个绿色文件的样子(Android.* 是自动生成的,下一节就要讲到。default/ 的含义和作用留待下一篇文章进行介绍):
接口描述文件ls视图


三、生成 Makefile

编写好接口描述文件后,我们需要执行脚本 /vendor//hardware/interfaces/update-makefiles.sh 为接口描述文件自动生成 2 个 Makefile —— Android.bpAndroid.mk

有了这 2 个 Makefile,在编译阶段就可以自动从接口描述文件生成 Binder 框架的源文件、头文件以和对应的库,而无需我们手动敲一遍,十分方便。


【结语】

既然接口已经定义好,那么接下来要做的就是实现 HAL 服务的主体,也即下一篇文章 《Android 8.1 从零开始写 HAL – (2) 实现 HAL 主体》的内容。


【参考资料】

[1] [2] 《HAL 类型》, https://source.android.com/devices/architecture/hal-types

你可能感兴趣的:(嵌入式,Android)