在分析 HAL 层源码之前,我们先实现一个 Binderized HALs,对 HIDL HAL 有一个相对细节的认知。
Binderized HAL 以进程的形式存在,内部有一个 HwBinder 服务端对象,对外提供 HwBinder 远程调用服务。Framework 通过 HwBinder 远程调用到 HAL 中的函数,这些函数直接访问具体的驱动。
接下来,我们给我们之前自定义的 Product 添加一个 HAL 模块,Product 不太清楚可以参考添加 Product
Hal 文件实现
Hal 层的实现一般放在 hardware、vendor 或者 device 目录下。
我们的示例就放在 vendor 目录下。
创建目录:
bash
# 系统源码目录下
mkdir -p jelly/hardware/interfaces/hello_hidl/1.0
接着在 vendor/jelly/hardware/interface/hello_hidl/1.0
目录下创建 Hal 文件
java
//定义包名,最后跟一个版本号
package jelly.hardware.hello_hidl@1.0;
//定义 hidl 服务对外提供的接口
interface IHello {
//for test,generates 后面跟的是返回类型
addition_hidl(uint32_t a,uint32_t b) generates (uint32_t total);
//写 hello 驱动
write(string name) generates (uint32_t result);
//读 hello 驱动
read() generates (string name);
};
这里的 IHello.hal 定义了我们的服务对外提供了哪些函数。可以认为这就是我们服务的对外协议。协议一般定义好就不会再修改,以保持对外的稳定性。 关于 hal 的写法,可以参考官方的文档,另外也可以参考 hardware 目录下系统自带的 hal 的写法。
Hal 文件生成 CPP 代码
接着我们使用 hidl-gen 命令将我们写的 hal 文件转换为 C++ 文件:
在系统源码下依次执行下面的命令:
bash
source build/envsetup.sh
# 项目的完整包名
PACKAGE=jelly.hardware.hello_hidl@1.0
# 生成代码的存放位置
LOC=vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
# -o 选项指定生成的文件存放的位置
# -Lc++-impl 表示要生成 C++ 代码
# -rjelly.hardware:vendor/jelly/hardware/interfaces 用于指定包名与路径的对应关系
# 最后的 $PACKAGE 指定项目的完整的包名
hidl-gen -o $LOC -Lc++-impl -rjelly.hardware:vendor/jelly/hardware/interfaces $PACKAGE
执行完上面的命令后,在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
目录下会生成 Hello.cpp 和 Hello.h。
接着修改 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
目录下生成的 Hello.cpp:
cpp
#define LOG_TAG "HELLO_HAL"
#include "Hello.h"
#include <cutils/log.h>
namespace jelly {
namespace hardware {
namespace hello_hidl {
namespace V1_0 {
namespace implementation {
// Methods from ::jelly::hardware::hello_hidl::V1_0::IHello follow.
Return<uint32_t> Hello::addition_hidl(uint32_t a, uint32_t b) {
ALOGD("addition_hidl....a :%d,b:%d",a,b);
return uint32_t { a+ b };
}
Return<uint32_t> Hello::write(const hidl_string& name) {
ALOGD("write %");
return uint32_t {};
}
Return<void> Hello::read(read_cb _hidl_cb) {
ALOGD("read");
return Void();
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
//IHello* HIDL_FETCH_IHello(const char* /* name */) {
//return new Hello();
//}
//
} // namespace implementation
} // namespace V1_0
} // namespace hello_hidl
} // namespace hardware
} // namespace jelly
这里就简单打印点信息,实际的 HAL 实现,在这里回去访问具体的驱动程序。
服务端实现
接着我们需要写一个 Server 端来向 HwServiceManager 注册我们的服务。在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
目录下添加 service.cpp:
cpp
#include <hidl/HidlTransportSupport.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
#include <log/log.h>
#include "Hello.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using jelly::hardware::hello_hidl::V1_0::IHello;
using jelly::hardware::hello_hidl::V1_0::implementation::Hello;
int main() {
ALOGD("hello-hidl is starting...");
// 配置 Binder 的线程数
configureRpcThreadpool(4, true /* callerWillJoin */);
// 初始化一个 Hello 服务端对象
android::sp<IHello> service = new Hello();
// 注册服务
android::status_t ret = service->registerAsService();
if (ret != android::NO_ERROR) {
}
// 当前线程成为 HwBinder 线程
joinRpcThreadpool();
return 0;
//Passthrough模式
//return defaultPassthroughServiceImplementation<IHello>(4);
}
这里使用的是 libhwbinder 库来做实现,接口上与 libbinder 库大体类似。
我们的服务端需要在开机时启动,创建 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default/jelly.hardware.hello_hidl@1.0-service.rc
文件:
bash
service vendor_hello_hidl_service /vendor/bin/hw/jelly.hardware.hello_hidl@1.0-service
class hal
user system
group system
接着我们需要添加 VINTF 对象,对于注册到 hwservicemanager 的服务都需要添加一个 VINTF 对象。对于编码来说 VINTF 对象就是一个 xml 文件,创建 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default/jelly.hardware.hello_hidl@1.0-service.xml
文件:
xml
<manifest version="1.0" type="device">
<hal format="hidl">
<name>jelly.hardware.hello_hidl</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IHello</name>
<instance>default</instance>
</interface>
</hal>
</manifest>
接着我们使用 hidl-gen 命令来生成对应的 Android.bp 文件:
bash
# 注意和前面使用同一个终端
hidl-gen -o $LOC -Landroidbp-impl -rjelly.hardware:vendor/jelly/hardware/interfaces $PACKAGE
这个命令会在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
目录下生成一个 Android.bp,我们在生成的基础上稍作修改如下:
json
// FIXME: your file license if you have one
cc_library_shared {
name: "jelly.hardware.hello_hidl@1.0-impl",
relative_install_path: "hw",
proprietary: true,
srcs: [
"Hello.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"jelly.hardware.hello_hidl@1.0",
"liblog",
],
}
cc_binary {
name: "jelly.hardware.hello_hidl@1.0-service",
init_rc: ["jelly.hardware.hello_hidl@1.0-service.rc"],
// 这种方式添加 vintf ,在 Android11 以后才支持
vintf_fragments: ["jelly.hardware.hello_hidl@1.0-service.xml"],
defaults: ["hidl_defaults"],
relative_install_path: "hw",
vendor: true,
srcs: ["service.cpp", "Hello.cpp"],
shared_libs: [
"jelly.hardware.hello_hidl@1.0",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"liblog",
],
}
上面生成的 Android.bp 里面有一个依赖 jelly.hardware.hello_hidl@1.0
,目前编译系统中还没有这个库,接着我们来生成 jelly.hardware.hello_hidl@1.0
库对应的 Android.bp。
在 hardware/interfaces
目录下,将 update-makefiles.sh 拷贝到 vendor/jelly/hardware/interfaces/
目录下,并修改如下:
bash
#!/bin/bash
source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh
do_makefiles_update \
"jelly.hardware:vendor/jelly/hardware/interfaces"
接着在系统源码目录下执行:
bash
./vendor/jelly/hardware/interfaces/update-makefiles.sh
就会生成 vendor/jelly/hardware/interfaces/hello_hidl/1.0/Android.bp
:
json
hidl_interface {
name: "jelly.hardware.hello_hidl@1.0",
root: "jelly.hardware",
product_specific: true,
srcs: [
"IHello.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
}
其中的 hidl_interface 是 hidl 独有的,当编译源码时,它会将 out/soong/.intermediates/vendor/jelly/hardware/interfaces/hello_hidl/1.0/jelly.hardware.hello_hidl@1.0_genc++/gen/jelly/hardware/hello_hidl/1.0 和 out/soong/.intermediates/vendor/jelly/hardware/interfaces/hello_hidl/1.0/jelly.hardware.hello_hidl@1.0_genc++_headers/gen/jelly/hardware/hello_hidl/1.0 目录下的源码编译为 jelly.hardware.hello_hidl@1.0.so 文件,并预制到手机的 /vendor/lib 和 /vendor/lib64/ 目录下。
为了使编译通过,新建 vendor/jelly/hardware/interfaces/Android.bp
文件:
json
hidl_package_root {
name: "jelly.hardware",
path: "vendor/jelly/hardware/interfaces",
}
这个 Android.bp 的作用是告诉编译系统包名与路径的映射关系。
接着新建 vendor/jelly/hardware/interfaces/current.txt 文件,current.txt 记录了所有 hal 接口的 hash 值,接口有变化时,同时需要更新 current.txt 中的 hash 值,这是我们先随便设置一个 hash 值:
json
123456 jelly.hardware.hello_hidl@1.0::IHello
再执行一遍 update-makefiles.sh,这个时候就会发现提示 hash 值不正确了,同时会给出正确的 hash 值,我们把正确的 hash 值替换到 current.txt 即可。
最后修改 device/generic/goldfish/manifest.xml
,在其中添加:
xml
<hal format="hidl">
<name>jelly.hardware.hello_hidl</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IHello</name>
<instance>default</instance>
</interface>
</hal>
客户端实现
在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下创建如下的文件和文件夹:
其中 hello_hidl_test.cpp
:
cpp
#include <jelly/hardware/hello_hidl/1.0/IHello.h>
#include <hidl/LegacySupport.h>
#define LOG_TAG "hello_hidl"
#include <log/log.h>
using android::sp;
using jelly::hardware::hello_hidl::V1_0::IHello;
using android::hardware::Return;
using android::hardware::hidl_string;
int main(){
// 获取服务代理端对象
android::sp<IHello> hw_device = IHello::getService();
if (hw_device == nullptr) {
ALOGD("failed to get hello-hidl");
return -1;
}
ALOGD("success to get hello-hidl....");
// 通过代理端对象发起远程调用
Return<uint32_t> total = hw_device->addition_hidl(3,4);
hw_device->write("hello");
hw_device->read([&](hidl_string result){
ALOGD("%s\n", result.c_str());
});
return 0;
}
调用接口上与 binder 大体一致。
Android.bp:
json
cc_binary {
name: "hello_hidl_test",
srcs: ["hello_hidl_test.cpp"],
vendor: true,
shared_libs: [
"liblog",
"jelly.hardware.hello_hidl@1.0",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
],
}
selinux 配置
在 device/Jelly/Rice14/sepolicy 目录下添加:
hwservice.te:
bash
type hello_hidl_hwservice, hwservice_manager_type;
hello_hidl.te:
scss
type hello_hidl, domain;
type hello_hidl_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hello_hidl);
add_hwservice(hello_hidl, hello_hidl_hwservice)
hwbinder_use(hello_hidl)
allow hello_hidl hidl_base_hwservice:hwservice_manager { add };
binder_call(hello_hidl,hwservicemanager)
get_prop(hello_hidl,hwservicemanager_prop)
hwservice_contexts:
arduino
jelly.hardware.hello_hidl::IHello u:object_r:hello_hidl_hwservice:s0
hello_hidl_test.te:
scss
type hello_hidl_test, domain;
type hello_hidl_test_exec, exec_type, vendor_file_type, file_type;
domain_auto_trans(shell, hello_hidl_test_exec, hello_hidl_test);
get_prop(hello_hidl_test, hwservicemanager_prop)
allow hello_hidl_test hello_hidl_hwservice:hwservice_manager find;
hwbinder_use(hello_hidl_test);
在 file_contexts 中添加:
css
/vendor/bin/hw/jelly\.hardware\.hello_hidl@1\.0-service u:object_r:hello_hidl_exec:s0
编译执行
接着在 device/Jelly/Rice14/Rice14.mk 中添加如下内容:
Makefile
BOARD_SEPOLICY_DIRS += \
device/Jelly/Rice14/sepolicy
PRODUCT_PACKAGES += \
jelly.hardware.hello_hidl@1.0-service \
hello_hidl_test \
jelly.hardware.hello_hidl@1.0-impl \
然后整编系统:
bash
source build/envsetup.sh
lunch rice14-eng
make -j16
最后测试:
bash
# 执行客户端程序
hello_hidl_test &
# 查看 log
# logcat | grep hello
3-28 18:31:40.460 1542 1542 D : hello-hidl is starting...
03-28 18:31:40.461 1542 1542 I ServiceManagement: Registered jelly.hardware.hello_hidl@1.0::IHello/default (start delay of 77ms)
03-28 18:31:40.461 1542 1542 I ServiceManagement: Removing namespace from process name jelly.hardware.hello_hidl@1.0-service to hello_hidl@1.0-service.
03-28 18:32:35.299 3050 3050 D hello_hidl: success to get hello-hidl....
03-28 18:32:35.299 1542 1561 D HELLO_HAL: write hello
03-28 18:32:35.300 3050 3050 D hello_hidl: test