Android 8.0 或更高版本的设备必须使用 HIDL 语言编写 HAL1。HIDL HAL类型分为Binderized HALs(绑定式HAL)和Passthrough HALs(直通式HAL);拜读 AOSP对两种HAL的规定,总结:除了[email protected]
和 [email protected]
两个被强制规定采用直通式HAL外,其它的HAL采用绑定式HAL必然不会有 VTS 问题,绑定式HAL是google aosp主推方式;故本文仅介绍使用纯绑定式 HAL实现以下场景:
通过APP控制,最终在HAL中通过文件操作函数open()、close()、write()访问LED的设备节点,控制LED的亮灭。
本文涉及到的关键源码我已释放到github上面:https://github.com/LuciferZhu/android_drviver_demo,使用方法如下。
$ cd aosp/
$ git clone [email protected]:LuciferZhu/android_drviver_demo.git
$ mv android_drviver_demo my_demo
已发布的 HIDL 软件包的根目录为 hardware/interfaces
或 vendor/vendorName
(例如,对于 Pixel 设备,根目录为 vendor/google),但为了熟悉HIDL软件包从零到一的流程,我们把demo代码部署在全新的根目录my_demo/interfaces/
上面,执行命令:
$ croot # 这个命令在对源码进行source和lunch后才有
$ mkdir -p my_demo/interfaces/led/1.0/default
$ touch my_demo/interfaces/led/1.0/ILed.hal
上面创建的ILed.hal
是HAL接口描述文件,要在其中定义开放给上层的接口,内容如下。其中参数的数据类型的表示需参考HIDL基元与C++的数据类型对应关系表。
package my_demo.hardware.led@1.0; /* 整个HAL的软件包名称,包名前缀“my_demo.hardware”由后面要定义的my_demo/interfaces/Android.bp决定 */
interface ILed {
control(uint32_t enable) generates (int32_t retval); /* 调用接口会返回一个 int32_t 型数*/
};
① 使用aosp提供的hidl-gen
工具,通过ILed.hal
生成HAL的必要文件。
$ touch my_demo/interfaces/led/1.0/hidl-generate.sh
$ make hidl-gen -j6 # 手动生成 hidl-gen 工具,编译aosp时并没有默认生成
脚本hidl-generate.sh
内容如下所示。
#!/bin/bash
set -e
if [ ! -d $ANDROID_BUILD_TOP/my_demo/interfaces ] ; then
echo "Where is my_demo/interfaces?";
exit 1;
fi
if [ ! -d $ANDROID_BUILD_TOP/system/libhidl/transport ] ; then
echo "Where is system/libhidl/transport?";
exit 1;
fi
PACKAGE=[email protected]
LOC=$ANDROID_BUILD_TOP/my_demo/interfaces/led/1.0/default/
options="-rmy_demo.hardware:my_demo/interfaces \
-randroid.hidl:system/libhidl/transport"
#make hidl-gen -j6
hidl-gen -o $LOC -Lc++-impl $options $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl $options $PACKAGE
echo done!
如下所示执行脚本hidl-generate.sh
并查看my_demo/interfaces/led
目录的结构;可见my_demo/interfaces/led/1.0/default/
下新生成了三个文件。
$ chmod +x my_demo/interfaces/led/1.0/hidl-generate.sh
$ ./my_demo/interfaces/led/1.0/hidl-generate.sh
$ tree my_demo/interfaces/led/
my_demo/interfaces/led/
└── 1.0
├── default
│ ├── Android.bp
│ ├── Led.cpp
│ └── Led.h
├── hidl-generate.sh
└── ILed.hal
2 directories, 5 files
② 借助system/tools/hidl/update-makefiles-helper.sh
中的do_makefiles_update
函数生成HAL的必要make-files
,具体操作如下:
$ touch my_demo/interfaces/update-makefiles.sh
$ chmod +x my_demo/interfaces/update-makefiles.sh
编辑my_demo/interfaces/update-makefiles.sh
内容如下所示。其中第14行指定了以目录my_demo/interfaces
为软件包my_demo.hardware
根目录,进行更新或生成该软件包下子包的makefiles文件。
#!/bin/bash
# Script to update Android make-files for HAL and VTS modules.
set -e
if [ -z "$ANDROID_BUILD_TOP" ]; then
echo "Missing ANDROID_BUILD_TOP env variable. Run 'lunch' first."
exit 1
fi
source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh
do_makefiles_update \
"my_demo.hardware:my_demo/interfaces" \
"android.hidl:system/libhidl/transport"
函数do_makefiles_update
要求在Android源码根目录执行脚本update-makefiles.sh
,执行该脚本如下所示。可知新生成了my_demo/interfaces/led/1.0/Android.bp
,它将会控制编译生成HIDL接口[email protected]
,该软件包还会导出一些必要的中间件,具体参考《Google aosp > 设计 > 架构 > HIDL(C++) > 软件包 > 生成的文件》。
$ ./my_demo/interfaces/update-makefiles.sh
Updating makefiles for my_demo.hardware in /home/lucifer/samba_share/android_code/hikey960/aosp/my_demo/interfaces.
Updating [email protected]
$ tree my_demo/interfaces/
my_demo/interfaces/
├── led
│ └── 1.0
│ ├── Android.bp
│ ├── default
│ │ ├── Android.bp
│ │ ├── Led.cpp
│ │ └── Led.h
│ ├── hidl-generate.sh
│ └── ILed.hal
└── update-makefiles.sh
3 directories, 7 files
③ 指定目录my_demo/interfaces
作为HIDL软件包my_demo.hardware
的根目录。具体操作如下:
$ touch my_demo/interfaces/Android.bp
编辑my_demo/interfaces/Android.bp
内容如下:
hidl_package_root {
name: "my_demo.hardware",
path: "my_demo/interfaces",
}
④ 实现控制LED的纯绑定式hal代码。如下图所示my_demo/interfaces/led/1.0/default/Led.cpp
是前面通过aosp自带工具hidl-gen
生成的HAL核心代码。
我们在这个基础框架上填充我们的实现代码后如下所示。
// FIXME: your file license if you have one
#define LOG_TAG "[email protected]"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Led.h"
namespace my_demo {
namespace hardware {
namespace led {
namespace V1_0 {
namespace implementation {
static const char THE_DEVICE[] = "/sys/devices/platform/leds/leds/user_led3/brightness";
// Methods from ::my_demo::hardware::led::V1_0::ILed follow.
Led::Led() {
ALOGD("%s() ++++++++", __func__);
}
Led::~Led() {
ALOGD("%s() ++++++++", __func__);
}
Return<int32_t> Led::control(uint32_t enable) {
char ctrl_str[4];
int ret = 0;
ALOGD("%s() ++++++++ enable=%d", __func__, enable);
int fd = open(THE_DEVICE, O_RDWR);
if (fd < 0) {
ALOGE("open: %s", strerror(errno));
ret = -1;
goto err_open;
}
memset(ctrl_str, '\0', sizeof(ctrl_str));
if (enable)
strcpy(ctrl_str, "255");
else
strcpy(ctrl_str, "0");
ALOGD("ctrl_str='%s'", ctrl_str);
ret = write(fd, ctrl_str, strlen(ctrl_str)+1);
if (ret < 0) {
ALOGE("write: %s", strerror(errno));
ret = -1;
goto err_write;
}
close(fd);
return 0;
err_write:
close(fd);
err_open:
return int32_t {ret};
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
} // namespace implementation
} // namespace V1_0
} // namespace led
} // namespace hardware
} // namespace my_demo
上面可看到我添加了Led
结构体的构造函数Led::Led()
和析构函数Led::~Led()
,因此还要修改my_demo/interfaces/led/1.0/default/Led.h
成如下所示,其中第25、26行是新添加的函数声明。
// FIXME: your file license if you have one
#pragma once
#include
#include
#include
namespace my_demo {
namespace hardware {
namespace led {
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 Led : public ILed {
// Methods from ::my_demo::hardware::led::V1_0::ILed follow.
Led();
~Led();
Return<int32_t> control(uint32_t enable) override;
// Methods from ::android::hidl::base::V1_0::IBase follow.
};
} // namespace implementation
} // namespace V1_0
} // namespace led
} // namespace hardware
} // namespace my_demo
对于纯绑定式HAL,HAL的调用者将会通过HwBinder RPC
通信进行间接调用HAL接口,所以我们要构建一个守护进程用于处理这些HwBinder
通信请求。
① 创建并编写服务器代码my_demo/interfaces/led/1.0/default/service.cpp
内容如下所示。第16行调用configureRpcThreadpool()
设置当前进程用于hwbinder
通信的最大线程数;第20行的registerAsService()
方法是Led
结构体继承于自动生成的my_demo/hardware/led/1.0/ILed.h
中的ILed
结构体,它会向 hwservicemanager
注册HAL接口实现;第29行执行joinRpcThreadpool()
会将当前的通信加入HwBinder
的线程池进行循环。
#define LOG_TAG "[email protected]"
#include
#include
#include
#include
#include "Led.h"
using my_demo::hardware::led::V1_0::implementation::Led;
using android::sp;
using android::status_t;
int main(int /* argc */, char* /* argv */ []) {
// This function must be called before you join to ensure the proper
// number of threads are created. The threadpool will never exceed
// size one because of this call.
::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);
ALOGD("prepare for [email protected]");
sp<ILed> led = new Led();
const status_t status = led->registerAsService();
if (status != ::android::OK) {
ALOGE("Can't register service of Led");
return 1; // or handle error
}
// Adds this thread to the threadpool, resulting in one total
// thread in the threadpool. We could also do other things, but
// would have to specify 'false' to willJoin in configureRpcThreadpool.
::android::hardware::joinRpcThreadpool();
return 1; // joinRpcThreadpool should never return
}
② 创建并编辑my_demo/interfaces/led/1.0/default/[email protected]
,用于实现开机自启动上述守护进程。
service my_demo.led-1-0 /vendor/bin/hw/my_demo.hardware.led@1.0-service
class hal
user system
group system
③ 前面hidl-gen
自动生成的my_demo/interfaces/led/1.0/default/Android.bp
只适用于直通式HAL,需要修改成如下所示才适用于纯绑定式HAL服务器。将控制生成并部署到机器的/vendor/bin/hw/[email protected]
和/vendor/etc/init/[email protected]
。
cc_binary {
// 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 '-' etc to distinguish it.
name: "[email protected]",
relative_install_path: "hw",
defaults: ["hidl_defaults"],
vendor: true,
init_rc: ["[email protected]"],
srcs: [
"Led.cpp",
"service.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"liblog",
"[email protected]",
],
}
① 创建文件my_demo/jni/ledHidl_jni/ledNative.cpp
并填充如下代码。我们在 JNI 代码中实现HAL客户端,当要使用HAL接口时,需要调用静态函数ILed::getService();
来获取第三章注册到hwservicemanager
的服务器实例,通过智能指针如hidlService->control(1)
的形式调用服务器中的接口。
#define LOG_TAG "ledNative.cpp"
#include
#include
#include
#include
#include "jni.h"
using my_demo::hardware::led::V1_0::ILed;
using android::sp;
using android::status_t;
static const char *classPathName = "com/example/lowlevel/LedNative";
jint openLed(JNIEnv* /*env*/, jobject /*thiz*/)
{
ALOGD("------%s", __FUNCTION__);
return 0;
}
jint closeLed(JNIEnv* /*env*/, jobject /*thiz*/)
{
ALOGD("------%s", __FUNCTION__);
return 0;
}
jint ledOn(JNIEnv* /*env*/, jobject /*thiz*/)
{
jint ret;
ALOGD("------%s", __FUNCTION__);
sp<ILed> hidlService = ILed::getService();
if (hidlService == nullptr) {
ALOGE("can't not get ILed service");
return -1;
}
ret = hidlService->control(1);
if(ret < 0) {
ALOGE("hidlService->control(1) failed");
return -1;
}
return 0;
}
jint ledOff(JNIEnv* /*env*/, jobject /*thiz*/)
{
jint ret;
ALOGD("------%s", __FUNCTION__);
sp<ILed> hidlService = ILed::getService();
if (hidlService == nullptr) {
ALOGE("can't not get ILed service");
return -1;
}
ret = hidlService->control(0);
if(ret < 0) {
ALOGE("hidlService->control(0) failed");
return -1;
}
return 0;
}
static JNINativeMethod ledMethod[] = {
{"openDev", "()I", (void*)openLed},
{"closeDev", "()I", (void*)closeLed},
{"devOn", "()I", (void*)ledOn},
{"devOff", "()I", (void*)ledOff},
};
jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
jint ret;
JNIEnv* env = NULL;
ALOGD("------%s", __FUNCTION__);
ret = vm->GetEnv((void**)&env, JNI_VERSION_1_4);
if (ret != 0) {
ALOGE("vm->GetEnv error, ret = %d", ret);
return -1;
}
jclass cls = env->FindClass(classPathName);
if (cls == NULL) {
ALOGE("Native registration unable to find class '%s'", classPathName);
return JNI_FALSE;
}
ret = env->RegisterNatives(cls, ledMethod, sizeof(ledMethod)/sizeof(ledMethod[0]));
if (ret < 0) {
ALOGE("RegisterNatives failed for '%s'", classPathName);
return JNI_FALSE;
}
return JNI_VERSION_1_4;
}
② 创建文件 my_demo/jni/ledHidl_jni/Android.bp
并填充如下代码。其中第9、10行的libhidlbase
和 [email protected]
是HAL客户端模块必须连接的动态库样式。
cc_library_shared {
name: "libled_jni",
// All of the source files that we will compile.
srcs: ["ledNative.cpp"],
// All of the shared libraries we link against.
shared_libs: [
"liblog",
"libutils",
"libhidlbase",
"[email protected]",
],
// No static libraries.
static_libs: [],
cflags: [
"-Wall",
"-Werror",
],
}
我们在第四章中已经实现JNI代码,在app中只需要常规调用JNI接口即可间接控制LED。
① 使用Android studio创建工程LedControl
,创建native接口映射类LedControl/app/src/main/java/com/example/lowlevel/LedNative.java
内容如下。
package com.example.lowlevel;
public class LedNative {
static {
System.loadLibrary("led_jni");
}
public native int openDev();
public native int closeDev();
public native int devOn();
public native int devOff();
}
② 创建主程序LedControl/app/src/main/java/com/example/ledcontrol/MainActivity.java
代码如下:
package com.example.ledcontrol;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.lowlevel.LedNative;
public class MainActivity extends AppCompatActivity {
private final String TAG = "LedControl";
private Button btn_led = null;
LedNative ledNative = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
ledNative = new LedNative();
ledNative.openDev();
}
public void initView() {
btn_led = (Button) findViewById(R.id.btn_led);
}
public void onClickLed(View v) {
if (btn_led.getText().equals("开灯") ) {
Log.d(TAG, "turn on led");
btn_led.setText("关灯");
ledNative.devOn();
} else {
Log.d(TAG, "turn off led");
btn_led.setText("开灯");
ledNative.devOff();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ledNative.closeDev();
}
}
③ 创建app布局文件LedControl/app/src/main/res/layout/activity_main.xml
,界面较为简单,只有一个用来控制LED亮灭的按钮。
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_led"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开灯"
android:textSize="@android:dimen/app_icon_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="onClickLed"/>
android.support.constraint.ConstraintLayout>
④ 将整个LedControl
AS工程代码拷贝到目录aosp/my_demo/jni/ledHidl_jni/app
中,参考《android studio项目嵌入到Android系统源码》或者我的github源码的修改提交commit c45c72caf,对app代码进行修改,方可在集成在aosp代码中。
其中新增的模块编译控制文件my_demo/jni/ledHidl_jni/app/LedControl/Android.bp
如下所示。第三行certificate: "platform",
指定该APP为platform_app
,使得可在下一章中赋予访问HIDL接口的权限。
android_app {
name: "LedControl",
certificate: "platform",
//product_specific: true,
srcs: ["app/src/main/java/**/*.java"],
resource_dirs: ["app/src/main/res"],
manifest: "app/src/main/AndroidManifest.xml",
jni_libs: ["libled_jni"],
static_libs: [
"androidx-constraintlayout_constraintlayout",
"androidx-constraintlayout_constraintlayout-solver",
],
optimize: {
enabled: false,
},
sdk_version: "current",
dex_preopt: {
enabled: false,
},
}
HIDL源码和用于测试工具代码前面已经完成,还需要做些板级项目配置以及selinux配置,方可把测试程序正常运行起来。
① 把HIDL接口、服务器可执行程序、led jni库、app添加到板级项目配置的PRODUCT_PACKAGES
中,这些模块才真正被编进安卓镜像。配置文件device/linaro/hikey/hikey960/device-hikey960.mk
加入如下内容:
PRODUCT_PACKAGES += \
my_demo.hardware.led@1.0-service \
my_demo.hardware.led@1.0.so \
libled_jni \
LedControl
② 在第二章的HAL接口实现中,我们使用sysfs
中设备拓扑的led设备属性文件/sys/devices/platform/leds/leds/user_led3/brightness
,进行led的亮灭控制,默认情况下只有root用户才有权限访问改属性文件,所以我们要在on post-fs
阶段设置该设备属性文件的访问权限。修改文件device/linaro/hikey/hikey960/init.hikey960.rc
加入chmod 0666 /sys/devices/platform/leds/leds/user_led3/brightness
如下图所示。
③ 在文件 device/linaro/hikey/manifest.xml
中添加如下代码用以声明我们提供的 HAL 实例。更多细节参考《aosp.设计.测试.供应商测试套件(VTS).测试框架.HAL可测试性检查.确定支持哪些 HAL》
<hal format="hidl">
<name>my_demo.hardware.ledname>
<transport>hwbindertransport>
<version>1.0version>
<interface>
<name>ILedname>
<instance>defaultinstance>
interface>
hal>
④ 配置selinux。
修改或创建device/linaro/hikey/sepolicy/attributes
文件附加如下代码:
attribute hal_led;
attribute hal_led_client;
attribute hal_led_server;
创建编辑device/linaro/hikey/sepolicy/hal_led_default.te
。新建了一个hal_led_default
类型的域,目的是用于后面标识HAL服务器/vendor/bin/hw/[email protected]
。
第18行至22行,是赋予HAL服务器访问设备属性文件/sys/devices/platform/leds/leds/user_led3/brightness
的规则,该设备属性文件的上下文名字默认在system/sepolicy/prebuilts/api/29.0/private/genfs_contexts
被定义成genfscon sysfs /class/leds u:object_r:sysfs_leds:s0
;所以第18行至22行应根据实际HAL接口所访问的资源来编写。
type hal_led_default, domain, mlstrustedsubject;
type hal_led_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_led_default)
hal_server_domain(hal_led_default, hal_led)
# HwBinder IPC from client to server, and callbacks
binder_call(hal_led_client, hal_led_server)
binder_call(hal_led_server, hal_led_client)
add_hwservice(hal_led_server, hal_led_hwservice)
allow hal_led_client hal_led_hwservice:hwservice_manager find;
##########################################################
# allow HAL module to open/read/write/ioctl
# /sys/devices/platform/leds (/sys/class/leds)
##########################################################
allow hal_led sysfs_leds:lnk_file read;
allow hal_led sysfs_leds:file rw_file_perms;
allow hal_led sysfs_leds:dir r_dir_perms;
allow hal_led_default sysfs:file rw_file_perms;
在device/linaro/hikey/sepolicy/file_contexts
添加如下内容,用于声明守护进程/vendor/bin/hw/[email protected]
为前面新定义的hal_led_default_exec
type,从而这个进程属于hal_led_default
的安全上下文。
/vendor/bin/hw/my_demo\.hardware\.led@1\.0-service u:object_r:hal_led_default_exec:s0
修改或创建device/linaro/hikey/sepolicy/hwservice.te
文件附加如下代码。新定义了一个名为hal_led_hwservice
的type,用于后面声明my_demo.hardware.led::ILed
的安全上下文。
type hal_led_hwservice, hwservice_manager_type;
修改或创建device/linaro/hikey/sepolicy/hwservice_contexts
文件附加如下代码。
my_demo.hardware.led::ILed u:object_r:hal_led_hwservice:s0
修改或创建device/linaro/hikey/sepolicy/platform_app.te
文件附加如下代码。在第五章中我们给用户界面app签名成platform_app
,它虽然通过JNI来获取HIDL服务和接口,但这时JNI跟app在同样的安全上下文。在JNI中我们通过ILed::getService()
来获取第三章注册到hwservicemanager
的服务器实例,最后通过hwbinder
进行实际HAL接口的调用,所以需要赋予相应的权限才可顺利执行。
allow platform_app hal_led_hwservice:hwservice_manager { find };
allow platform_app hal_led_default:binder { call };
修改device/linaro/hikey/sepolicy/system_server.te
文件附加如下代码:
hal_client_domain(system_server, hal_led)
用户界面APP如下所示,其已作为platform_app
内置于系统。
点击按钮开灯
后,硬件呈现效果如下图所示。
本文通过C++实现了HIDL HAL的客户端,另外还可以通过JAVA实现客户端,可以越过实现JNI的步骤,具体参考我的下一篇文章《android HIDL HAL控制LED demo(二)之JAVA作客户端》。
Google aosp > 设计 > 架构 > 硬件抽象层(HAL) > HAL类型 ↩︎