一个简单的HIDL开发笔记

Posted by 陈宇瀚 on August 4, 2021

一个简单的HIDL开发笔记

个人学习总结

一、创造HIDL实例相关文件

创建自己的HAL层文件目录

这里我以我自己的源码目录platform/vendor/mediatek/hardware/interfaces目录为例,在该目录下创建cyhhidl目录;

1
2
cd vendor/mediatek/hardware/interfaces
mkdir -p cyhhidl/1.0/default

在版本目录(即1.0)中创建接口描述文件ICyhHidl.hal; 文件内容如下:

 package vendor.mediatek.hardware.cyhhidl@1.0;

    interface ICyhHidl {
      helloWorld(string name) generates (string result);
  };

内部实现了一个helloWorld方法,传入string类型对象并返回string类型对象。

使用工具和脚本生成所需文件

使用hidl-gen生成HAL相关文件,使用脚本更新Makefile文件,自动生成Android.bpAndroid.mk

根据自身情况设定好对应的值,其中TT是固定的,不用修改

1
2
3
4
LOC=vendor/mediate/hardware/interfaces/cyhhidl/1.0/default
PACKAGE=vendor.mediatek.hardware.cyhhidl@1.0
SS=vendor.mediatek.hardware:vendor/mediatek/hardware/interfaces
TT=android.hidl:system/libhidl/transport

hidl-gen工具代码路径在system/tools/hidl,通过mmm编译后会在out 下生成out/host/linux-x86/bin/hidl-gen,之后在命令行中执行(编译hidl-gen开始就在源码根目录,vendor目录下要修改相应的内容)

1
  ./out/host/linux-x86/bin/hidl-gen -o . -Landroidbp -r$SS -r$TT $PPACKAGE

这样就在1.0目录下生成了Android.bp文件,修改hal文件接口后,需要使用hidl-gen添加对应的哈希:

1
 ./out/host/linux-x86/bin/hidl-gen -L hash -r$SS -r$TT $PPACKAGE

一般是在vendor/mediatek/hardware/interfaces/current.txt文件内按字母升序添加,接下来生成default目录用于实现服务端的c++代码及bp描述文件,执行以下命令

1
./out/host/linux-x86/bin/hidl-gen -o $LOC -Lc++-impl -r$SS -r$TT $PPACKAGE

这样就在default目录生成了CyhHidl.cppCyhHidl.h文件,接着执行以下命令:

1
./out/host/linux-x86/bin/hidl-gen -o $LOC -Landroidbp-impl -r$SS -r$TT $PPACKAGE

这样就在default目录下生成了Android.bp文件。 命令行中的-o 需要视情况使用,将会覆盖生成代码,以免造成已修改的服务端代码被覆盖掉。 至此就生成了HIDL服务所需要的文件: 1.0/Android.bp1.0/default/CyhHidl.h1.0/default/CyhHidl.cpp,其中1.0/Android.bp内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// This file is autogenerated by hidl-gen -Landroidbp.

hidl_interface {
    name: "vendor.mediatek.hardware.cyhhidl@1.0",
    root: "vendor.mediatek.hardware",
    srcs: [
        "ICyhHidl.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
}

之后我们就要具体实现内部的方法

二、实现HAL

CyhHidl.h文件内可以定义实现的实例是哪种模式(直通Passthrough模式绑定Binderized模式,使用直通式HIDL 需要打开屏蔽了的HIDL_FETCH_name方法,并在实现返回。),之后实现对应的.cpp文件内的函数;这里不对CyhHidl.h做改动,使用Binderized模式1.0/default/CyhHidl.h

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef VENDOR_MEDIATEK_HARDWARE_CYHHIDL_V1_0_CYHHIDL_H
#define VENDOR_MEDIATEK_HARDWARE_CYHHIDL_V1_0_CYHHIDL_H

#include <vendor/mediatek/hardware/cyhhidl/1.0/ICyhHidl.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

namespace vendor {
namespace mediatek {
namespace hardware {
namespace cyhhidl {
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 CyhHidl : public ICyhHidl {
    // Methods from ::vendor::mediatek::hardware::cyhhidl::V1_0::ICyhHidl follow.
    Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override;

    // Methods from ::android::hidl::base::V1_0::IBase follow.

};

// FIXME: most likely delete, this is only for passthrough implementations
// extern "C" ICyhHidl* HIDL_FETCH_ICyhHidl(const char* name);

}  // namespace implementation
}  // namespace V1_0
}  // namespace cyhhidl
}  // namespace hardware
}  // namespace mediatek
}  // namespace vendor

#endif  // VENDOR_MEDIATEK_HARDWARE_CYHHIDL_V1_0_CYHHIDL_H

1.0/default/CyhHidl.cpp

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
26
27
28
29
30
31
32
33
34
35
#include "CyhHidl.h"

namespace vendor {
namespace mediatek {
namespace hardware {
namespace cyhhidl {
namespace V1_0 {
namespace implementation {

// Methods from ::vendor::mediatek::hardware::cyhhidl::V1_0::ICyhHidl follow.
Return<void> CyhHidl::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
    // TODO implement
    char buf[100];
    ::memset(buf, 0x00, 100);
    ::snprintf(buf, 100, "Hello World, %s", name.c_str());
    hidl_string result(buf);

    _hidl_cb(result);
    return Void();
}


// Methods from ::android::hidl::base::V1_0::IBase follow.

//ICyhHidl* HIDL_FETCH_ICyhHidl(const char* /* name */) {
    //return new CyhHidl();
//}
//
}  // namespace implementation
}  // namespace V1_0
}  // namespace cyhhidl
}  // namespace hardware
}  // namespace mediatek
}  // namespace vendor

之后通过1.0/default/Android.bp文件看出之后会通过这几个文件生成一个vendor.mediatek.hardware.cyhhide@1.0-impl.so的库,在vendor/lib64/hw/目录下。

1.0/default/Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cc_library_shared {
    name: "vendor.mediatek.hardware.cyhhidl@1.0-impl",
    relative_install_path: "hw",
   
    proprietary: true,
    srcs: [
        "CyhHidl.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "vendor.mediatek.hardware.cyhhidl@1.0",
    ],

}

三、调用HAL流程

HIDL软件包中自动生成的文件会链接到与软件包同名的单个共享库,该共享库还会导出单个头文件ICyhHidl.h,用于在binder客户端服务端的接口文件,下图是官网对IFoo.hal(对应ICyhHidlTest.hal)编译后生成的文件走向:

image

  1. IFoo.h(对应ICyhHidl.h):描述C++类中的IFoo(ICyhHidl)接口;包含IFoo.hal(对应ICyhHidl.hal)文件中的IFoo(ICyhHidl)接口中所定义的方法和类型,必要时会转换成C++类型。不包含与用于实现此接口的RPC机制(例如HwBinder)相关的详细信息。类的命名空间包含软件包名称和版本号(例::android:hardware::samples::IFoo::V1_0)。客户端和服务端都包含此标头:客户端用它来调用方法,服务器用它来实现这些方法;
  2. IHwFoo.h:头文件,其中包含用于对接口中使用的数据类型进行序列化的函数声明。开发者不得包含其标头(它不包含任何类); 3.BpFoo.h:从IFoo继承的类,可描述接口的HwBinder代理(客户端)实现。开发者不得直接引用此类;
  3. BnFoo.h:保存对IFoo实现的应用类,可描述接口的HwBinder存根(服务器端)实现,开发者不得直接引用此类;
  4. FooAll.cpp:包含HwBinder代理和HwBinder存根的实现类。当客户端调用接口方法时,代理会自动从客户端封装参数,并将事务发送到绑定内核驱动程序,该内核驱动程序会将事务传送到另一端的存根(该存根随后会调用实际服务器实现)。

独立于HIDL使用的RPC机制的唯一一个自动生成的文件时IFoo.h,其他所有文件都与HIDL使用的HwBinder RPC机制相关联。因此客户端和服务端不得直接引用除IFoo之外的任何内容,所以需要只包含IFoo.h并链接到生成的共享库。

开发的实例会用到以下的几个模块

  1. vendor.mediatek.hardware.cyhhidl@1.0-impl.so:CyhHidl模块实现端的代码,编译生成,binder server端;
  2. vendor.mediatek.hardware.cyhhidl@1.0.so:cyhHidl模块调用端的代码,binder clinent端;
  3. cyhHidl_hal_service:通过直通式注册binder service,暴露接口给lclient调用;
  4. vendor.mediatek.hardware.cyhhidl@1.0-service.rc:Android natice进程入口

四、HIDL Service和Client端测试代码实现

Service端测试代码service.cpp 1.0/default/service.cpp

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
#define LOG_TAG "vendor.mediatek.hardware.cyhhidl@1.0-service"
#define LOG_NDEBUG 0

#include <log/log.h>
#include <android-base/logging.h>
#include <hidl/LegacySupport.h>
#include <hidl/HidlTransportSupport.h>

#include "CyhHidl.h"
using vendor::mediatek::hardware::cyhhidl::V1_0::implementation::CyhHidl;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;

int main() {
	configureRpcThreadpool(4, true);

    CyhHidl cyhhidl;
    auto status = cyhhidl.registerAsService();
    CHECK_EQ(status, android::OK) << "Failed to register cyhhidl HAL implementation";

    ALOGD("register cyhhidl HAL success");

    joinRpcThreadpool();
    return 0; // joinRpcThreadpool shouldn't exit
}

Clint端测试代码client.cpp 1.0/default/client.cpp

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
26
27
#define LOG_TAG "vendor.mediatek.hardware.cyhhidl@1.0-client"

#include <vendor/mediatek/hardware/cyhhidl/1.0/ICyhHidl.h>
#include <hidl/LegacySupport.h>
#include <stdio.h>

// Generated HIDL files
using vendor::mediatek::hardware::cyhhidl::V1_0::ICyhHidl;
using android::sp;
using android::hardware::hidl_string;

int main() {
    android::sp<ICyhHidl> service = ICyhHidl::getService();

    if (service == nullptr) {
	    printf("Failed get cyhhidl service!\n");
	    return -1;
    }

    printf("Success get cyhhidl service!\n");

    service->helloWorld("Cyh",[&](hidl_string result){
		printf("%s\n",result.c_str());
	});

    return 0;
}

Android.bp文件内加入编译配置 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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
cc_binary {
    name: "vendor.mediatek.hardware.cyhhdil@1.0-service",
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],
    init_rc: ["vendor.mediatek.hardware.cyhhidl@1.0-service.rc"],
    srcs: ["service.cpp","CyhHidl.cpp"],
    proprietary: true,

    shared_libs: [
         "libbase",
        "liblog",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "libutils",
	"vendor.mediatek.hardware.cyhhidl@1.0",
    ],
    cflags: [
        "-Werror",
        "-Wno-unused-parameter",
    ],

   
}

// 添加客户端测试时使用
cc_binary {
    name: "vendor.mediatek.hardware.cyhhidl@1.0-client",
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],
    srcs: ["client.cpp"],

    shared_libs: [
        "liblog",
        "libbase",
        "libdl",
        "libutils",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "vendor.mediatek.hardware.cyhhidl@1.0",
    ],

}

五、启动binder server进程 创建服务的rc文件 ,设定服务开机自启,vendor.mediatek.hardware.cyhhidl@1.0-service.rc,文件内容如下:

1
2
3
4
service hal-cyhhidl-1-0 /vendor/bin/hw/vendor.mediatek.hardware.cyhhidl@1.0-service
class hal
user system
group system

设定服务的selinux权限,在device/mediatek/mt2712/sepolicy/non_plat目录下: 在file_contexts文件上下文添加定义:

1
  /system/bin/hw/android\.hardware\.cyhhidl@1.\0-service u:object_r:hal_cyhhidl_default_exec:s0

hwservice_contexts文件上下文添加定义

1
  vendor.mediatek.hardware.cyhhidl::ICyhHidl u:object_r:hal_cyhhidl_hwservice:s0

attributes文件上下文添加属性定义

1
2
3
4
# hal cyhhidl
#attribute hal_cyhhidl;
#attribute hal_cyhhidl_client;
#attribute hal_cyhhidl_server;

新增hal_cyhhidl.te权限文件

1
2
3
4
5
6
7
8
# HwBinder IPC from client to server, and callbacks
binder_call(hal_cyhhidl_client, hal_cyhhidl_server);
binder_call(hal_cyhhidl_server, hal_cyhhidl_client);

add_hwservice(hal_cyhhidl_server, hal_cyhhidl_hwservice);
allow hal_cyhhidl_client hal_cyhhidl_hwservice:hwservice_manager find;
allow shell vendor_file:file { getattr };
allow shell hal_cyhhidl_hwservice:hwservice { find };

新增hal_cyhhidl_default.te**默认权限文件

1
2
3
4
5
type hal_cyhhidl_default, domain,coredomain;
hal_server_domain(hal_cyhhidl_default, hal_cyhhidl)

type hal_cyhhidl_default_exec, exec_type, file_type;
init_daemon_domain(hal_cyhhidl_default)

添加hidl配置,在device/mediatek/mt2712/manifest.xml和manifest_ab.xml

1
2
3
4
5
6
7
8
9
<hal format="hidl">
     <name>vendor.mediatek.hardware.cyhhidl</name>
     <transport>hwbinder</transport>
     <version>1.0</version>
     <interface>
         <name>ICyhHidl</name>
         <instance>default</instance>
     </interface>
</hal>

device.mk配置添加编译服务端

1
2
PRODUCT_PACKAGES += \
    vendor.mediatek.hardware.cyhhidl@1.0-service

六、运行结果

开机执行

1
2
adb shell
ps -A | grep cyhhidl

image

输入

1
2
su
./vendor/bin/hw/vendor.mediatek.hardware.cyhhidl@1.0-client

执行客户端程序,显示如下:

image

过程中遇到的问题

VNDK验证问题

VNDK只针对hardware下生成的so库,在vendor下开发的HIDL不需要

编译时在设置SELinux权限时报错

1
2
libsepol.report_failure: neverallow on line 1016 of system/sepolicy/public/domain.te (or line 11338 of policy.conf) violated by allow hal_cyhhidl_default hal_cyhhidl_default_exec:file { execute };
libsepol.check_assertions: 1 neverallow failures occurred

这是hal_cyhhidl_default.te文件内部的语法错误导致,之后修改后就没有再出现。

服务编译不过,报出错误error: undefined reference to ‘VTT for android::hardware:xxxx

原因在于我的service.cpp引入了CyhHidl虚函数,但是没有引入对应的资源文件,

所以之后在Android.bp文件内对应的srcs改为 srcs: [“service.cpp”,”CyhHidl.cpp”],相当于把implservice整合,其实就不需要impl了,可以去除;

之后会有error: unused parameter ‘_hidl_cb’ [-Werror,-Wunused-parameter]错误,在Android.bp内加入

1
2
3
4
    cflags: [
        "-Werror",
        "-Wno-unused-parameter",
    ],

则成功编译通过

服务开机没有自启动

发现生成的文件是在vendor/bin/hw目录下,而rc文件内写成system/bin/hw下,这个生成目录位置要研究一下,修改下rc文件启动的文件目录即可启动service

修改rc文件后开机没有自启

最后发现定义属性的时候要在/device/mediatek/mt2712/sepolicy/plat_public/attributes文件下,之前是在/device/mediatek/mt2712/sepolicy/non_plat/attributes,应该HIDL服务在vendorhardware开发的区别,之后有待验证

再次编译后烧录,之后就可以看到服务正在运行