1.背景
HIDL 的目标是,可以在无需重新构建 HAL 的情况下替换框架。HAL 将由供应商或 SOC 制造商构建,并放置在设备的/vendor分区中,这样一来,就可以在其自己的分区中通过 OTA 替换框架,而无需重新编译HAL。
2.操作实例
以实例着手,在MTK平台,用HIDL实现调用linux内核驱动,并测试。
本篇文章实现的功能如下:
a. 在linux驱动层实现helloworld驱动,功能打印”hello world!”。
b. 设计HIDL调用内核中helloworld驱动,并提供接口。
c. 测试程序调用HIDL接口,观察是否有打印“helloworld!”
d. 实现HIDL与驱动层的数据交互,HIDL往内核写入数据并读取。
3.流程
HIDL接口文件定义
有关HIDL接口与软件包规则,详见接口和软件包。
搭建attempt文件结构:
进入代码HAL层,自定义软件包,先以attempt命名这个例子。
1.创建HIDL目录
mkdir -p hardware/interfaces/attempt/1.0/default
2.接着创建Iattempt.hal
hardware/interfaces/attempt/1.0/IAttempt.hal
3.增加IAttempt.hal接口代码
/*定义一个Iattempt.hal接口文件,简单添加一个helloWorld接口,传入string,返回string,在之后会实现这个接口。*/
package android.hardware.attempt@1.0;
interface IAttempt {
hello_world(string name) generates (string result);
};
生成HAL相关文件
需要使用Android hidl-gen工具辅助生成。hidl-gen 源码包路径为:system/tools/hidl,需要编译才能生成可用的hidl-gen工具。
(1) 安装hidl-gen:
$ lunch
$ make hidl-gen
编译后hidl-gen生成路径:out/host/linux-x86/bin/hidl-gen
(2) 使用hidl-gen 生成根据.hal文件生成HIDL格式接口
$ LOC= hardware/interfaces/attempt/1.0/default
$ PACKAGE=[android.hardware.attempt@1.0](mailto:android.hardware.attempt@1.0)
/* 生成hardware/interfaces/attempt/1.0/default/(Attempt.hAttempt.cpp) */
$ hidl-gen -o $LOC-Lc++-impl -randroid.hardware:hardware/interfaces-randroid.hidl:system/libhidl/transport $PACKAGE
/* 生成hardware/interfaces/attempt/1.0/Android.bp*/
$ hidl-gen -o $LOC -Landroidbp-impl-randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport$PACKAGE
注: 自定义的hidl路径hardware/interfaces/attempt/1.0/default/ 、自定义的package包名android.hardware.attempt@1.0 ,都要与.hal代码一致
(3) update-makefile.sh 更新Makefile, 自动生成hardware/interfaces/attempt/Android.dp
$./hardware/interfaces/update-makefiles.sh
(4) 添加如下文件用于注册HIDL服务
$touch [hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc](mailto:hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc)
$touch hardware/interfaces/attempt/1.0/default/service.cpp
(5)查看一下目录结构
attempt/
└── 1.0
├── Android.bp
├── default
│ ├── Android.bp
│ ├── android.hardware.attempt@1.0-service.rc
│ ├── Attempt.cpp
│ ├── Attempt.h
│ └── service.cpp
└── IAttempt.hal
自此hal层目录结构搭建完成。
总结一下:
首先是写了一个IAttempt.hal 并定义需要的接口函数,然后通过hdil-gen 和update-makefiles.sh生成HIDL接口文件。最后创建用于注册接口文件的service.cpp,**rc文件。
[android.hardware.attempt@1.0-service.rc] // 用于注册HIDL服务
Attempt.cpp Attempt.h // 提供服务接口
Android.bp //编译文件
实现HIDL接口(共享端服务)
选择使用Passthrouugh模式
打开注释
hardware/interfaces/attempt/1.0/default/Attempt.h
extern "C" IAttempt*HIDL_FETCH_IAttempt(const char* name);
hardware/interfaces/attempt/1.0/default
//Methods from::android::hidl::base::V1_0::IBase follow.
IAttempt* HIDL_FETCH_IAttempt(const char* /*name */) {
return new Attempt();
}
实现hardware/interfaces/attempt/1.0/default/service.cpp
#include <android/hardware/attempt/1.0/IAttempt.h>
#include <hidl/LegacySupport.h>
using android::hardware::attempt::V1_0::IAttempt;
using android::hardware::defaultPassthroughServiceImplementation;
int main() {
return defaultPassthroughServiceImplementation<IAttempt>();
}
实现hardware/interfaces/attempt/1.0/default/android.hardware.attempt@1.0-service.rc
service attempt_hal_service /vendor/bin/hw/android.hardware.attempt@1.0-service
class hal
user root
group root
seclabel u:r:su:s0
添加如下代码,将service.cpp文件加载为入口文件,开机启动服务:
//path:hardware/interfaces/attempt/1.0/default/Android.bp
cc_binary {
name: "android.hardware.attempt@1.0-service",
defaults: ["hidl_defaults"],
relative_install_path: "hw",
init_rc: ["android.hardware.attempt@1.0-service.rc"],
vendor: true,
srcs: ["service.cpp"],
shared_libs: [
"android.hardware.attempt@1.0",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"liblog",
],
}
编译
mmm ./hardware/interfaces/attempt/1.0/
将自定义的attempt服务开机自启(不确定这一步是否多余)
//path: device/mediatek/mt6739/device.mk
PRODUCT_PACKAGES += \
android.hardware.attempt@1.0-impl
PRODUCT_PACKAGES += android.hardware.attempt@1.0-service
然后全编
发现报错:error: VNDK library list has been changed.
解决方法:将out/target/product/k39tv1_bsp_512/obj/PACKAGING/vndk_intermediates/libs.txt
比对到build/make/target/product/gsi/29.txt 和build/make/target/product/gsi/current.txt(以out目录下的为准,网上也有比对到28.txt,视工程报错log定,也可以都同步一下)
重新编译即可解决。
4.客户端测试
实例一个HIDL(C++)客户端:
hardware/interfaces/attempt/1.0/test/attempt_client.cpp
#include <android/hardware/attempt/1.0/IAttempt.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <stdio.h>
using ::android::hardware::attempt::V1_0::IAttempt;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;
int main()
{
printf("Entry attempt_client 10:02\n");
android::sp<IAttempt> client = IAttempt::getService();
if (client == nullptr) {
printf("Failed to get service\n");
return -1;
}
printf("Get service scuessfully!\n");
client->hello_world("test_20200608", [&](hidl_string result) {
printf("%s\n", result.c_str());
});
return 0;
}
hardware/interfaces/attempt/1.0/test/Android.bp
cc_binary {
relative_install_path: "hw",
defaults: ["hidl_defaults"],
name: "attempt_client",
proprietary: true,
srcs: ["attempt_client.cpp"],
shared_libs: [
"liblog",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"android.hardware.attempt@1.0",
],
}
编译
mmm ./hardware/interfaces/attempt/1.0/test
生成路径: out/target/product/[target_name]/vendor/bin/hw/attempt_client
执行
将可执行文件push到vendor/bin/hw/
查看attempt 服务是否运行
ps –A | grep attempt
执行客户端程序
./vendor/bin/hw/attempt_client
效果:
成功!!!!(如遇HIDL服务没有开机启动,详见问题2)
5.HIDL与内核驱动交互
上述只是成功实现HIDL接口的建立以及与上层的交互,本段主要介绍上层通过HIDL与kernel驱动层的数据交互。
(1) 创建一个自定义的kernel驱动,并注册到内核中,我这里自定义一个hello_world.c的驱动,注册一个/dev/hello_world设备,cat /proc/devices可见
5.1 驱动设备列表
(2) 在HIDL接口实现对驱动的访问,即使用open、read、write函数访问到内核hello_world.c驱动。主要HIDL接口、kernel驱动hello_world.c与HIDL测试文件主要代码如下:
hardware/interfaces/attempt/1.0/default/Attempt.cpp(HIDL接口文件)
Return<void> Attempt::hello_world(const hidl_string& name, hello_world_cb _hidl_cb) {
// TODO implement
char buf[100];
int fd = -1;
char write_val[100]= "I can do it 14:00";
char read_val[100] = {0,0};
::memset(buf, 0x00, 100);
::snprintf(buf, 100, "Hello World, %s", name.c_str());
printf("service hello world\n");
fd = open("/dev/hello_world", O_RDWR);
if(fd < 0){
printf("Open error\n");
memset(write_val, 0x41, sizeof(write_val));
}
write(fd, &write_val, sizeof(write_val));
memset(read_val, 0, sizeof(read_val));
read(fd, &read_val, sizeof(read_val));
close(fd);
hidl_string result(read_val);
_hidl_cb(result);
return Void();
}
kernel-4.14/drivers/misc/mediatek/dx_driver/hello_world/hello_world.c(内核驱动文件)
static ssize_t hello_world_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
memset(global_char, 0x00, sizeof(global_char));
ret = copy_from_user(global_char, buf, 20);
return 0;
}
ssize_t hello_world_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
// sprintf(global_char, "%s,%s", "believe yourself", global_char);
ret = copy_to_user(buf, global_char, ARRAY_SIZE(global_char));
return 0;
}
hardware/interfaces/attempt/1.0/test/attempt_client.cpp(HIDL测试文件)
int main()
{
printf("Entry attempt_client 10:02\n");
android::sp<IAttempt> client = IAttempt::getService();
if (client == nullptr) {
printf("Failed to get service\n");
return -1;
}
printf("Get service scuessfully!\n");
client->hello_world("test_20200608", [&](hidl_string result) {
printf("%s\n", result.c_str());
});
return 0;
}
(其中一些log,主要为测试使用,不必在意)
主要功能:HIDL先将write_val值写入内核驱动,内核驱动将接收到的数据赋值给global_char。然后HIDL从内核驱动读取global_char并赋值给read_val,最后将read_val返回给上层。上层测试文件则负责打印HIDL接口上传的数据。
在实际测试时,发现read_val的值为空,由于HIDL接口log无法吐出(不知什么原因),所以排查起来比较麻烦。期间写了一个可执行C文件调用驱动设备,才发现是因为驱动节点open失败,然后联想到可能是因为驱动节点的权限问题。在查询/dev/hellow_world权限后,看到其权限为crw------- 。果然!因此需要更改一下/dev/hello_world权限
system/core/rootdir/ueventd.rc
# HIDL test /dev/hello_wolrd
/dev/hello_world 0777 root root
全编之后,adb进入/vendor/bin/hw 执行./attempt_client
5.2 访问内核驱动
发现打印出I can do it ,即HIDL能够将数据写入内核,且可以读取出来。大功告成!
6.问题点
这里列举一下我遇到的且排查起来比较费时的问题:
问题1. 需要把attempt_client push到机器中执行,但是机器在adb remount失败,导致不能push文件。
这里将机器默认开启root权限:
device/mediateksample/k39tv1_bsp_512/system.prop
#root
ro.secure=0
问题2. 运行测试程序时出现卡死 (用的另一个HIDL测试)
解决:
查看log:发现是由于自定义的HIDL服务由于权限问题,开机没有启动起来
6.1 开机log
①关闭selinux模式(Android Q)
参考https://online.mediatek.com/FAQ#/SW/FAQ11484
system/core/init/selinux.cpp
bool IsEnforcing() {
return false; //2020-06-09 dongxiang close selinux
if(ALLOW_PERMISSIVE_SELINUX) {
return StatusFromCmdline() == SELINUX_ENFORCING;
}
return true;
}
②或者给访问文件添加权限
参考https://blog.csdn.net/tung214/article/details/72734086
虽然我最后添加了read、excute权限,但是还是会报没execute权限,搞不懂为什么,最后选择关掉selinux模式。
问题3:异常重启
内核驱动中,与上层产生数据交互时,需要调用copy_from_user和copy_to_user,而不能粗暴的选择字符串拷贝API,否则会导致机器异常重启!!!!!(!老泪纵横!)
参考:
https://source.android.google.cn/devices/architecture?hl=zh-cn
https://github.com/fadhel086/Android-HIDL
https://blog.csdn.net/shift_wwx/article/details/86525761
https://online.mediatek.com/FAQ#/SW/FAQ11484
https://blog.csdn.net/tung214/article/details/72734086
如有技术交流需要,请关注“开源519”公众号。
网友评论