1 HIDL 简介
HIDL 读作 hide-l,全称是 Hardware Interface Definition Language。它在 Android Project Treble 中被起草,在 Android 8.0 中被全面使用,其诞生目的是使 Android 可以在不重新编译 HAL 的情况下对 Framework 进行 OTA 升级。
使用 HIDL 描述的 HAL 描述文件替换旧的用头文件描述的 HAL 文件的过程称为 HAL 的 binder 化(binderization)
。所有运行 Android O 的设备都必须只支持 binder 化后的 HAL 模块。
HIDL是一种接口定义语言,描述了HAL和它的用户之间的接口。同aidi类似,我们只需要为hal定义相关接口,然后通过hidl-gen工具即可自动编译生成对应的C++或者java源文件,定义hal接口的文件命名为xxx.hal,为了编译这些.hal文件,需要编写相应的Android.bp或者Android.mk文件
- Android.bp文件用于编译C++
- Android.mk文件用于编译Java
1.1 HIDL HAL 类型
1.1.1 直通模式(Passthrough)
和 Framework 工作在同一个进程当中,因没有 service 进程,服务也没有被事先注册到
hwservicemanager,Framework 通过 Binder 得到的是同一个进程中的实例。在 manifest.xml 中 transport 类型为 passthrough。以 android.hardware.graphics.composer@2.1
为例,在 Android.bp 中,Hwc.cpp 被编译为 android.hardware.graphics.composer@2.1-impl.so
,其 HIDL_FETCH_IComposer 方法中会使用 hw_get_module() 方法去加载传统 HAL。
1.1.2 绑定模式(Binderized)
而将直通模式中的直通式 HAL,添加上 service 进程,修改 transport 类型为 hwbinder,就成为了绑定模式。service 的注册使用的是 defaultPassthroughServiceImplementation() 方法。
1.1.3 纯绑定模式
service 的注册方法都是 registerAsService()
,在 manifest.xml 中的 transport 类型为 hwbinder,不再单独编译 *-impl.so
,而是全编译进 service 中。以 android.hardware.power@1.1
默认实现为例。android.hardware.power@1.1-service
服务启动时,服务的注册方法是 registerAsService()。在 Android.bp 中将两个源文件编译为 android.hardware.power@1.1-service
。有意思的是在 service 的 main 方法中居然会去 hw_get_module(POWER_HARDWARE_MODULE_ID, &hw_module) 加载传统 HAL,那这个默认实现 1.1 版相对于 1.0 版就没有意义了。也因并没有厂商使用这个默认版,谷歌干脆移除了。
2 HIDL 编程规范
HIDL 代码样式类似于 Android 框架中的 C++ 代码,缩进 4 个空格,并且采用混用大小写的文件名。软件包声明、导入和文档字符串与 Java 中的类似,只有些微差别。
下面针对 IFoo.hal
和 types.hal
的示例展示了 HIDL 代码样式。
hardware/interfaces/foo/1.0/IFoo.hal
1 | /* |
hardware/interfaces/foo/1.0/types.hal
1 | /* |
2.1 命令规范
2.1.1 目录结构和文件命令
目录结构应如下所示:
- Root-dir
- Module
- SubModule
- Version
- Android.bp
- IInterface_1.hal
- IInterface_2.hal
- …
- IInterface_n.hal
- types.hal (可选)
- Version
- SubModule
- Module
其中:
Root-dir
- hardware/interfaces 核心HIDL软件包
- vendor/VENDOR/interfaces 供应商软件包
Module
描述系统的小写字词。如果多个字词,需使用嵌套式SubModule
Version
版本号,格式:major.minor
IInterface_x
接口名称
2.1.2 软件包名称
软件包名称必须采用完全限定名称(FQN)格式(称为:PACKAGE-NAME)。格式如下:
1 | PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION |
2.1.3 版本
格式如下:
1 | MAJOR.MINOR |
MAJOR 和 MINOR 版本都应该是一个整数。
2.1.4 导入
格式如下:
完整软件包导入
import PACKAGE-NAME
部分导入
import PACKAGE-NAME::UDT;
(或者,如果导入的类型是在同一个软件包中,则为import UDT;
)。仅类型导入
import PACKAGE-NAME::types;
如果当前软件包types.hal存在,则自动导入。
在软件包声明之后(在导入之前),添加一个空行。每个导入都应占用一行,且不能缩进。按一下顺序对导入进行分组:
- android.harder 软件包(使用完全限定名称)
- vendor.VENDOR软件包(使用完全限定名称)
- 每个供应商应为一组
- 按字母顺序对供应商进行排序
- 源自同一个软件包的其他接口导入(使用简单名称)
在组与组之间添加一行空行。每个组内,按照字母顺序对导入进行排序。
2.1.5 接口名称
接口名称必须以 I
开头,后跟 UpperCamelCase
/PascalCase
名称。名称为 IFoo
的接口必须在文件 IFoo.hal
中定义。此文件只能包含 IFoo
接口的定义(接口 INAME
应位于 INAME.hal
中)。
2.1.6 函数
对于函数名称、参数和返回变量名称,请使用 lowerCamelCase
。例如:
1 | open(INfcClientCallback clientCallback) generates (int32_t retVal); |
2.1.7 结构体/联合字段名称
对于结构体/联合字段名称,请使用 lowerCamelCase
。例如:
1 | struct FooReply { |
2.1.8 类型名称
类型名称指结构体/联合定义、枚举类型定义和 typedef
。对于这些名称,请使用 UpperCamelCase
/PascalCase
。例如:
1 | enum NfcStatus : int32_t { |
2.1.9 枚举值
枚举值应为 UPPER_CASE_WITH_UNDERSCORES
。将枚举值作为函数参数传递以及作为函数返回项返回时,请使用实际枚举类型(而不是基础整数类型)。例如:
1 | enum NfcStatus : int32_t { |
注意:
枚举类型的基础类型是在冒号后显式声明的。因为它不依赖于编译器,所以使用实际枚举类型会更明晰
2.2 备注
2.2.1 文件备注
每个文件的开头都应为相应的许可通知。对于核心 HAL,该通知应为 development/docs/copyright-templates/c.txt 中的 AOSP Apache 许可。请务必更新年份,并使用 /* */
样式的多行备注(如上所述)。
您可以视需要在许可通知后空一行,后跟变更日志/版本编号信息。使用 /* */ 样式的多行备注(如上所述),在变更日志后空一行,后跟软件包声明。
2.2.2 TODO 备注
TODO 备注应包含全部大写的字符串 TODO
,后跟一个冒号。例如:
1 | // TODO: remove this code before foo is checked in. |
只有在开发期间才允许使用 TODO 备注;TODO 备注不得存在于已发布的接口中。
2.2.3 接口/函数备注
对于多行和单行文档字符串,请使用 /** */
。对于文档字符串,请勿使用 //
。
接口的文档字符串应描述接口的一般机制、设计原理、目的等。函数的文档字符串应针对特定函数(软件包级文档位于软件包目录下的 README 文件中)。
1 | /** |
必须为每个参数/返回值添加 @param
和 @return
:
- 必须为每个参数添加
@param
。其后应跟参数的名称,然后是文档字符串 - 必须为每个返回值添加
@return
。其后应跟返回值的名称,然后是文档字符串
1 | /** |
2.3 格式
一般格式规则包括:
行长:每行文字最长不应超过 80 列
空格:各行不得包含尾随空格;空行不得包含空格。
空格与制表符:仅使用空格
缩进大小:数据块缩进 4 个空格,换行缩进 8 个空格。
大括号:(注释值除外)左大括号与前面的代码在同一行,右大括号与后面的分号占一整行。例如:
1
2
3interface INfc {
close();
};
2.3.1 软件包声明
软件包声明应位于文件顶部,在许可通知之后,应占一整行,并且不应缩进。声明软件包时需采用以下格式(有关名称格式,请参阅软件包名称):
1 | package PACKAGE-NAME; |
例如:
1 | package android.hardware.fingerprint@2.1; |
2.3.2 函数声明
函数名称、参数、generates
和返回值应在同一行中(如果放得下)。例如:
1 | interface IFoo { |
如果一行中放不下,则尝试按相同的缩进量放置参数和返回值,并突出 generate
,以便读取器快速看到参数和返回值。例如:
1 | interface IFoo { |
其他细节:
- 左括号始终与函数名称在同一行
- 函数名称和左括号之间不能有空格
- 括号和参数之间不能有空格,它们之间出现换行时除外。
- 如果
generates
与前面的右括号在同一行,则前面加一个空格。如果generates
与接下来的左括号在同一行,则后面加一个空格。 - 将所有参数和返回值对齐(如果可能)。
- 默认缩进 4 个空格。
- 将换行的参数与上一行的第一个参数对齐,如果不能对齐,则这些参数缩进 8 个空格。
2.3.3 注释
格式如下:
1 | @annotate(keyword = value, keyword = {value, value, value}) |
按字母顺序对注释进行排序,并在等号两边加空格。例如:
1 | @callflow(key = value) |
如果注释在同一行中放不下,则缩进 8 个空格。例如:
1 | @annotate( |
如果整个值数组在同一行中放不下,则在左大括号 {
后和数组内的每个逗号后换行。在最后一个值后紧跟着添加一个右括号。如果只有一个值,请勿使用大括号。
如果整个值数组可以放到同一行,则请勿在左大括号后和右大括号前加空格,并在每个逗号后加一个空格。例如:
1 | // Good |
注释和函数声明之间不得有空行。例如:
1 | // Good |
2.3.4 枚举声明
对于结构体声明,遵循如下规则:
- 如果与其他软件包共用枚举声明,则将声明放在
types.hal
中,而不是嵌入到接口内。 - 在冒号前后加空格,并在基础类型后和左大括号前加空格。
- 最后一个枚举值可以有也可以没有额外的逗号。
2.3.5 结构体声明
对于结构体声明,遵循如下规则:
如果与其他软件包共用结构体声明,则将声明放在
types.hal
中,而不是嵌入到接口内。在结构体类型名称后和左大括号前加空格。
对齐字段名称(可选)。例如:
1
2
3
4struct MyStruct {
vec<uint8_t> data;
int32_t someInt;
}
2.3.6 数组声明
请勿在以下内容之间加空格:
- 元素类型和左方括号
- 左方括号和数组大小
- 数组大小和右方括号
- 右方括号和接下来的左方括号(如果存在多个维度)。
例如:
1 | // Good |
2.3.7 矢量声明
请勿在以下内容之间加空格:
vec
和左尖括号。- 左尖括号和元素类型(例外情况:元素类型也是
vec
)。 - 元素类型和右尖括号(例外情况:元素类型也是
vec
)。
例如:
1 | // Good |
3 hidl-gen 使用
系统定义的所有的.hal
接口,都是通过hidl-gen
工具转换成对应的代码。hidl-gen
源码路径:system/tools/hidl,是在ubuntu上可执行的二进制文件
3.1 编译工具
1 | make -j18 hidl-gen |
编译之后会在out 下生成,详细看out/host/linux-x86/bin/hidl-gen。
3.2 使用方法
1 | hidl-gen -o output_path -L language (-r interface:root) hidl_name |
-L
语言类型,包括c++, c++-headers, c++-sources, export-header, c++-impl, java, java-constants, vts, makefile, androidbp, androidbp-impl, hash等。hidl-gen可根据传入的语言类型产生不同的文件。
hidl_name
完全限定名称的输入文件。格式:
package@version
-r
格式:package:path,可选,对hidl_name对应的文件来说,用来指定包名和文件所在的目录到Android系统源码根目录的路径。如果没有制定,前缀默认是:android.hardware,目录是Android源码的根目录
-o
存放hidl-gen产生的中间文件的路径
3.3 实例
在
hardware/interfaces/
目录下新建han/1.0/
目录,并创建接口文件IHan.hal
,目录结构如下:1
2└── 1.0
└── IHan.hal在IHal.hal文件中只有一个接口IHan和一个方法helloWorld(string name),代码如下:
1
2
3
4
5package android.hardware.han@2.0;
interface IHan{
helloWorld(string name) generates (string result);
};自动生成对应的C++模板文件
1
2
3PACKAGE=android.hardware.han@1.0
LOC=hardware/interfaces/han/1.0/default/
hidl-gen -o $LOC -Lc++-impl -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport $PACKAGE自动生成模板后目录结构如下:
1
2
3
4
5└── 1.0
├── default
│ ├── Han.cpp
│ └── Han.h
└── IHan.halC++ 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace android {
namespace hardware {
namespace han {
namespace V1_0 {
namespace implementation {
// Methods from ::android::hardware::han::V1_0::IHan follow.
Return<void> Han::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
// TODO implement
return Void();
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
//IHan* HIDL_FETCH_IHan(const char* /* name */) {
//return new Han();
//}
//
} // namespace implementation
} // namespace V1_0
} // namespace han
} // namespace hardware
} // namespace android如果是直通模式HAL,需要开启HIDL_FETCH_IHan。
自动生成C++模板对应的Android.bp文件
1
hidl-gen -o $LOC -Landroidbp-impl -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport $PACKAGE
执行命令后目录结构如下:
1
2
3
4
5
6└── 1.0
├── default
│ ├── Android.bp
│ ├── Han.cpp
│ └── Han.h
└── IHan.halAndroid.bp内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25cc_library_shared {
// FIXME: this should only be -impl for a passthrough hal.
// In most cases, to convert this to a binderized implementation, you should:
// - change '-impl' to '-service' here and make it a cc_binary instead of a
// cc_library_shared.
// - add a *.rc file for this module.
// - delete HIDL_FETCH_I* functions.
// - call configureRpcThreadpool and registerAsService on the instance.
// You may also want to append '-impl/-service' with a specific identifier like
// '-vendor' or '-<hardware identifier>' etc to distinguish it.
name: "android.hardware.han@1.0-impl",
relative_install_path: "hw",
// FIXME: this should be 'vendor: true' for modules that will eventually be
// on AOSP.
proprietary: true,
srcs: [
"Han.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"android.hardware.han@1.0",
],
}默认适合直通模式HAL,如果是绑定模式需要按照提示进行修改。
使用脚本
update-makefiles.sh
,来更新Makefile。自动在hardware/interfaces/han/1.0
目录下生成Android.bp1
2
3
4
5
6
7└── 1.0
├── Android.bp
├── default
│ ├── Android.bp
│ ├── Han.cpp
│ └── Han.h
└── IHan.hal在
hardware/interfaces/han/1.0/default
目录下新建service.cpp
android.hardware.han@1.0-service.rc
。android.hardware.han@1.0-service.rc
实现1
2
3
4service han_hal_service /vendor/bin/hw/android.hardware.han@1.0-service
class hal
user system
group systemservice.cpp
实现1
2
3
4
5
6
7
8
9
using android::hardware::han::V1_0::IHan;
using android::hardware::defaultPassthroughServiceImplementation;
int main(){
return defaultPassthroughServiceImplementation<IHan> ();
}在
hardware/interfaces/han/1.0/default
的Android.bp中增加如下内容:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21cc_binary {
name: "android.hardware.han@1.0-service",
init_rc: ["android.hardware.han@1.0-service.rc"],
vendor: true,
proprietary: true
relative_install_path: "hw",
srcs: [
"service.cpp",
],
shared_libs: [
"libcutils",
"liblog",
"libhidlbase",
"libhidltransport",
"libhardware",
"libutils",
"android.hardware.han@1.0",
],
}至此更HAL相关代码已经实现完成。
编译生成服务端和客服端要用的各种库文件。
1
2./hardware/interfaces/update-makefiles.sh
mmm hardware/interfaces/han/1.0
在
manifest.xml
文件里添加接口定义1
2
3
4
5
6
7
8
9<hal format="hidl">
<name>android.hardware.han</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IHan</name>
<instance>default</instance>
</interface>
</hal>
使用C++实现客户端调用
在
hardware/interfaces/han/1.0
目录下创建test目录。并在test目录下新建HanTest.cpp Android.bpC++实现客户端调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using ::android::hardware::hidl_string;
using ::android::sp;
using android::hardware::han::V1_0::IHan;
int main(){
android::sp<IHan> service = IHan::getService();
if(service == NULL){
printf("Failed to get service\n");
return -1;
}
service->helloWorld("Hello Woprld");
return 0;
}Android.bp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17cc_binary {
name: "han_client",
defaluts: ["hidl_defaults"],
relative_install_path: "hw",
proprietary: true,
srcs: [
"HanTest.cpp",
],
shared_libs: [
"liblog",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"android.hardware.han@1.0",
],
}