??了解了 HIDL 是什么之后,以一个简单的 HelloWorld 来做一个小实战。
??HAL 层本来的工作应该包括向下实现控制硬件的代码,并向上层提供接口。但是这里我们只是做一个简单的 HelloWorld,省略了控制硬件的部分,只是完成了向上提供一个接口,上层调用的时候返回一个 HelloWorld 字符串。
??学习资料主要是官网文档:https://source.android.com/devices/architecture/hidl-cpp
准备工作
- Android 代码
- Android BSP 编译环境
- 测试设备
??HIDL 的步骤: ???1. 定义接口文件(.hal) ???2. 工具生成相应文件 ???3. 编写文件并编译 ???4. 测试验证
定义接口文件
??我们需要在标准 HAL 层中创建接口文件,标准 HAL 层的路径是 hardware/interfaces 。
??首先,进入 hardware/interfaces 目录,新建一个文件夹 helloworld,在该文件夹中再新建一个 1.0 文件夹。
mkdir -p ./hardware/interfaces/helloworld/1.0/
?&emps;1.0 这个目录表示的是版本。
??在 1.0 文件夹中新建一个接口描述文件 IHelloWorld.hal,该文件内容如下。
package android.hardware.helloworld@1.0;
interface IHelloWorld {
helloWorld(string name) generates (string result);
};
??看起来既熟悉又陌生是吧,这其实是一个 Google 定义的语言格式,C++ 和 Java 的结合体。 ??generates(string result) 相当于返回值为 string。
??还是要吐槽下语言这个事,做 Andorid BSP 开发的啥都得会。 ??汇编:bootloader 和 kernel 中可能会用到。 ??C 语言:Linux Kernel。 ??C++:HAL层和 Library。 ??Java:Framework 层和 Application 层。 ??Python:编译相关。 ??Makefile:mk 文件。
??目录结构:
├── hardware/interfaces/helloworld/1.0
│ └── IHelloWorld.hal
??现在我们新建了一个接口文件 IHelloWorld.hal,传入 string 参数,返回一个 string 参数。下面我们利用工具来生成相关文件。
工具生成相应文件
??1. 在代码的根目录打开终端,执行下面命令。
source build/envsetup.sh
lunch db410c
make hidl-gen
??2. 在终端中执行下面命令,来利用 hidl-gen 工具生成相关文件。
PACKAGE=android.hardware.helloworld@1.0
LOC=hardware/interfaces/helloworld/1.0/default/
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
??此时的目录结构:
├── hardware/interfaces/helloworld/1.0
│ ├── default
│ │ ├── Android.bp
│ │ ├── HelloWorld.cpp
│ │ └── HelloWorld.h
│ └── IHelloWorld.hal
??如果提示不支持 -L,PACKAGE 和 LOC 没有配置。
??3. 执行下面命令来使用脚本更新 Makefile,自动更新 Android.mk 文件并在 1.0 目录下生成 Android.bp。
./hardware/interfaces/update-makefiles.sh
??此时的目录结构:
├── hardware/interfaces/helloworld/1.0
│ ├── IHelloWorld.hal
│ ├── default
│ │ ├── Android.bp
│ │ ├── HelloWorld.cpp
│ │ └── HelloWorld.h
│ └── Android.bp
??4. 创建两个空文件。
touch hardware/interfaces/helloworld/1.0/default/android.hardware.helloworld@1.0-service.rc
touch hardware/interfaces/helloworld/1.0/default/service.cpp
??.rc 文件是启动脚本,在系统启动时 init 的时候会加载这个文件,以便启动服务。service.cpp 则是服务代码,用来注册服务的。
??OK,相关的文件都已经生成了,除了 cpp 文件和 rc 文件,其他文件都不需要我们编写。下面我们来实现这两个文件的编写。
编辑文件并编译
??在这一步中,我们需要编写的文件有 helloworld.cpp、service.cpp、android.hardware.helloworld@1.0-service.rc。
helloworld.cpp
??helloworld.h 不需要修改,主要的代码编写就是在 helloworld.cpp 文件,这是实现接口的文件。
??helloworld.h 内容如下所示。
#pragma once
#include <android/hardware/helloworld/1.0/IHelloWorld.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
namespace android {
namespace hardware {
namespace helloworld {
namespace V1_0 {
namespace implementation {
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;
struct HelloWorld : public IHelloWorld {
Return<void> helloWorld(const hidl_string& name, helloString_cb _hidl_cb) override;
};
}
}
}
}
}
??如果取消 HelloWorld.h 中下面的注释,表示使用直通模式。这里我们采用绑定式,因此不需要修改。
??helloworld.cpp 生成的内容如下所示。
#include "HelloWorld.h"
namespace android {
namespace hardware {
namespace helloworld {
namespace V1_0 {
namespace implementation {
Return<void> HelloWorld::helloString(const hidl_string& name, helloString_cb _hidl_cb) {
return Void();
}
}
}
}
}
}
??如果取消 HelloWorld.cpp 中下面三行的注释是采用直通模式。同样的,这里我们不需要修改。
??下面来完善 HelloWorld.cpp 代码,返回一个 “Hello, World” 字符串。
Return<void> HelloWorld::helloString(const hidl_string& name, helloString_cb _hidl_cb) {
char buf[128];
::memset(buf, 0, 128);
::snprintf(buf, 128, "Hello World, %s", name.c_str());
hidl_string result(buf);
_hidl_cb(result);
return Void();
}
service.cpp
??接下来我们来完善服务代码,因为 HIDL 将 HAL 层作为服务启动,而 service.cpp 这个文件是用来注册 HIDL 服务的。
#define LOG_TAG "android.hardware.helloworld@1.0-service"
#include <android/hardware/helloworld/1.0/IHelloWorld.h>
#include <hidl/HidlTransportSupport.h>
#include "HelloWorld.h"
using android::hardware::helloworld::V1_0::IHelloWorld;
using android::hardware::helloworld::V1_0::implementation::HelloWorld;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::sp;
using android::status_t;
int main() {
configureRpcThreadpool(1 , true );
sp<IHelloWorld> helloworld = new HelloWorld();
const status_t status = helloworld->registerAsService();
if (status != ::android::OK) {
return 1;
}
joinRpcThreadpool();
return 1;
}
将 service.cpp 添加到 Android.bp 中
??完成上一步只是写好了 service.cpp,但是并没有 Android.bp 中配置其编译,所以需要在在 default 文件夹中的 Android.bp 文件中添加下面的配置。
cc_binary {
name: "android.hardware.helloworld@1.0-service",
defaults: ["hidl_defaults"],
proprietary: true,
relative_install_path: "hw",
srcs: ["service.cpp",
"HelloWorld.cpp"],
init_rc: ["android.hardware.helloworld@1.0-service.rc"],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"liblog",
"android.hardware.helloworld@1.0",
],
}
.rc 文件
??.rc 文件主要是用于开机时启动服务的。
??.rc 文件的内容如下:
service helloworld_service /vendor/bin/hw/android.hardware.helloworld@1.0-service
class hal
user system
group system
PS:在测试时,因为一些权限问题,服务无法自动启动,所以我先用了手动启动服务来调试,测试成功之后尝试改为自动启动,需要设置自动启动的话可以看下我后面的问题总结。
编译代码
mmm hardware/interfaces/helloworld/1.0/default
??编译成功之后,会给我们一个路径,编译之后的 so 库就在这个路径下面。 ??out/target/product/硬件名/vendor/lib/hw/android.hardware.helloworld@1.0-impl.so 这个是服务端使用的 so 文件,是实现的 so 文件。 ??out/target/product/硬件名/system/lib/android.hardware.helloworld@1.0.so 这个是客户端使用的 so 文件,是接口的 so 文件。 ??out/target/product/硬件名/vendor/bin/hw/android.hardware.helloworld@1.0-service 。
??把这三个文件 push 到 硬件名 之后的路径。
测试验证
??启动服务 ??如果配置 sepolicy 应该是自动启动服务,如果没有添加 sepolicy 需要手动启动服务,/vendor/bin/hw/android.hardware.helloworld@1.0-service 。
??客户端验证 ??在另一个终端中输入,测试代码的 mk 文件中,LOCAL_MODULE 的值。
问题总结
1. 手动启动服务时,提示 XXX.helloworld@1.0-service.so 找不到
??解决方案:将 so 库文件添加到 /system/etc/public.libraries.txt 中,这样就把 so 库放到了公共库里。
2. 客户端无法访问服务端
??服务启动后客户端无法获取服务,说明是 selinux 权限问题,可以修改下面文件,将 selinux 设置为宽容模式。
??文件路径: device/linaro/dragonboard/db410c32_only/BoardConfig.mk。
BOARD_KERNEL_BASE := 0x80008000
BOARD_MKBOOTIMG_ARGS := --ramdisk_offset 0x0
BOARD_KERNEL_CMDLINE := firmware_class.path=/system/vendor/firmware/ androidboot.hardware=db410c
BOARD_KERNEL_CMDLINE += printk.devkmsg=on
BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 1288491008
BOARD_USERDATAIMAGE_PARTITION_SIZE := 5653544960
TARGET_COPY_OUT_VENDOR := vendor
3. 服务自启动设置
vendor 下的服务
??如果服务编译并刷机之后是在 vendor/bin 下,修改下面4个文件即可。
??如果还没有编译,可以根据编译之后文件在那个位置来进行选择。
??1. 在 system\sepolicy\vendor\file_contexts 文件最下面加上一句:
/(vendor|system/vendor)/bin/hw/android\.hardware\.helloworld@1\.0-service
??2. 在 system\sepolicy\vendor 目录下新增一个文件 hal_helloworld.te
type hal_helloworld, domain;
type hal_helloworld_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_helloworld)
??3. 在 system\sepolicy\prebuilts\api\26.0\private\file_contexts 里,增加如下一条:
/(vendor|system/vendor)/bin/hw/android\.hardware\.helloworld@1\.0-service u:object_r:hal_helloworld_exec:s0
??4. 在 system\sepolicy\prebuilts\api\26.0\private 下新增一个文件 hal_helloworld.te
type hal_helloworld, domain;
type hal_helloworld_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_avi_memory)
??这样配置后,再全编译刷机,以后开机就可以自动启动我们的服务了。
system 下的服务
??如果要启动的是 system/bin 下的服务,需要配置的4个文件如下。
??1. 在 system\sepolicy\prebuilts\api\26.0\private\file_contexts 里新增如下一行代码:
/system/bin/autotimestampserver u:object_r:autotimestampserver_exec:s0
??2. 在 sepolicy\prebuilts\api\26.0\private 下新增文件 autotimestampserver.te
type autotimestampserver, coredomain;
type autotimestampserver_exec, exec_type, file_type;
init_daemon_domain(autotimestampserver)
??在 system\sepolicy\private\file_contexts 里新增如下一行代码:
/system/bin/autotimestampserver u:object_r:autotimestampserver_exec:s0
??在 system\sepolicy\private 下新增文件 autotimestampserver.te
type autotimestampserver, coredomain;
type autotimestampserver_exec, exec_type, file_type;
init_daemon_domain(autotimestampserver)
??完成上面的配置后,即可开机启动服务了。
|