目录
android设备添加一个外围设备,不像嵌入式那么简单,系统越复杂加入的方式越杂,但是万物都有规矩可寻,有方法可走。这篇文章就说说HAL和用户之间的接口HIDL。
Google:https://source.android.google.cn/devices/architecture/hidl
google 官方这样解释 :HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。
HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是用于在可以独立编译的代码库之间进行通信的系统。
HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。
HIDL 可指定数据结构和方法签名,这些内容会整理归类到接口(与类相似)中,而接口会汇集到软件包中。尽管 HIDL 具有一系列不同的关键字,但 C++ 和 Java 程序员对 HIDL 的语法并不陌生。此外,HIDL 还使用 Java 样式的注释。
在Android8.0开始,google引入了Treble机制,为了方便Android系统的快速移植、升级、稳定,Android引入了HAL Binder机制,把framework和HAL进行隔离,减少了之间的耦合性,使得framework可以被覆盖升级,不用对HAL编译升级。
用图片展示一下两者的差别:
Android8.0之前,HAL的调用方式为Legacy HAL, HAL都是编译成so,然后动态链接到各个frameworks service中去,到了8.0,google为了给厂商一些修改时间,保持了Android的向前兼容性,另外设计了一套直通式(Passthrough)的HAL,保留动态库的HAL调用方式,
binderservice链接动态库Hal的实现。
8.0之后,google规定厂商新增的HAL都需要切换到绑定式HAL(Binderized)。
在从 libhardware
HAL 转换为 HIDL HAL 的过程中,HAL 实现成为服务器,而调用 HAL 的进程则成为客户端。默认实现可提供直通和 Binder 化 HAL,并可能会随着时间而发生变化:
Hal的发展历程
Legacy Hal:Android 8.0 之前版本的 HAL 都是编译成 so,然后动态链接到各个 frameworks service 中去。
Passthrough Hal:该模式是为了兼容旧版的 HAL,旧版 HAL 实现仍以动态库的方式提供,只是 binder service 链接了动态库 HAL 实现,即 binder service 通过 hw_get_module 链接了旧版的 hal 实现,而用户端通过与 binder service IPC 通信,间接实现了与旧版 HAL 的交互。
Binderized HAL:HAL 与 用户调用在不同的进程中,HAL 被写成 binder service,而用户接口如 frameworks 作为 binder client 通过 IPC 机制实现跨进程接口调用。
概念介绍到这里,了解更清楚的话,可以去google官网阅读相关内容。
实现一个回调案例
代码结构
├── bearpi
│ └── 1.0
│ ├── Android.bp
│ ├── current.txt
│ ├── default
│ │ ├── Android.bp
│ │ ├── android.hardware.bearpi@1.0-service.rc
│ │ ├── BearPi.cpp
│ │ ├── BearPi.h
│ │ └── service.cpp
│ ├── IBearPiClientCallback.hal
│ ├── IBearPi.hal
│ ├── test
│ │ ├── Android.bp
│ │ └── BearPiTest.cpp
│ └── types.hal
一 ,准备
我们需要用的hidl-gen 工具去生成文件,这个工具可以通过编译得到
source build/envsetup.sh
lunch xxx
make
先整编一下需要添加的aosp。
二,添加hal文件
在hardware/interfaces目录下新建bearpi,我们从1.0版本开始,default目录为实现hal的存放路径
mkdir -p bearpi/1.0/default
很多现有的HAL实现会与硬件异步通信,需要以异步的方式通知客户端发生的事件,比如,指令下发后返回执行状态。HIDL接口可以用作异步回调。
在bearpi/1.0路径下创建两个hal文件,
IBearPi.hal
package android.hardware.bearpi@1.0; ----------------------------------------------------------------------------------------------->软件包名
import IBearPiClientCallback; ---------------------------------------------------------------------------------------------------------------> import
语句是用于访问其他软件包中的软件包接口和类型的 HIDL 机制
interface IBearPi{
setNotify(IBearPiClientCallback callback) generates (uint64_t bearpiId); --------------------------------------------------->设置回调
test() generates (uint64_t testresult);
};
IBearPiClientCallback.hal
package android.hardware.bearpi@1.0;
interface IBearPiClientCallback {
oneway onResult(uint64_t deviceId, BearPiAcquiredInfo acquiredInfo, uint32_t vendorCode);------------------------>回调函数
};
types.hal ---------------------------------------------------------------------------------------------------------------------------------------------> types.hal
文件并不定义接口,而是定义软件包中每个接口可以访问的数据类型
package android.hardware.bearpi@1.0;
enum BearPiAcquiredInfo : int32_t {
ACQUIRED_GOOD = 0,
ACQUIRED_PARTIAL = 1,
ACQUIRED_INSUFFICIENT = 2,
ACQUIRED_IMAGER_DIRTY = 3,
ACQUIRED_TOO_SLOW = 4,
ACQUIRED_TOO_FAST = 5,
ACQUIRED_VENDOR = 6
};
注意软件包前缀对应的位置,android.hardware.* -----hardware/interfaces/*
软件包根目录可以自己创建,但是必须包含current.txt版本控制文件,通过hidl-gen生成哈希。
Android.bp
hidl_package_root {
name: "android.hardware",
path: "hardware/interfaces",
}
此bp文件就需要放在hardware/interfaces目录下,并且创建current.txt文件,使用下面指令生成哈希值,存放到current.txt中
hidl-gen -Lhash -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.bearpi@1.0
三,实现hal文件
HIDL 接口具有客户端和服务器实现:
- HIDL 接口的客户端实现是指通过在该接口上调用方法来使用该接口的代码。
- 服务器实现是指 HIDL 接口的实现,它可接收来自客户端的调用并返回结果(如有必要)。
通过如下指令
hidl-gen -o /home/ubuntu/data/LG/hardware/interfaces/bearpi/1.0/default/ -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.bearpi@1.0
实现接口文件
BearPi.h
BearPi.cpp
BearPi.h
// FIXME: your file license if you have one
#ifndef ANDROID_HARDWARE_BEARPI_V1_0_H
#define ANDROID_HARDWARE_BEARPI_V1_0_H
#include <android/hardware/bearpi/1.0/IBearPi.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>
#include <android/log.h>
#include <hardware/hardware.h>
#include <hardware/bearpi.h>
namespace android{
namespace hardware{
namespace bearpi{
namespace V1_0{
namespace implementation {
using ::android::hardware::bearpi::V1_0::IBearPi;
using ::android::hardware::bearpi::V1_0::IBearPiClientCallback;
using ::android::hardware::bearpi::V1_0::BearPiAcquiredInfo;
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 BearPi : public IBearPi {
// Methods from ::android::hardware::bearpi::V1_0::IBearPi follow.
public:
BearPi();
~BearPi();
static IBearPi * getInstance();
Return<uint64_t> setNotify(const sp<IBearPiClientCallback>& callback) override;
Return<uint64_t> test() override;
// Methods from ::android::hidl::base::V1_0::IBase follow.
private:
static bear_device_t * openHal();
static void notify(const bear_msg_t *msg);
static BearPiAcquiredInfo VendorAcquiredFilter(int32_t error, int32_t* vendorCode);
static BearPi* sInstance;
sp<IBearPiClientCallback> mClientCallback;
bear_device_t *mDevice;
};
// FIXME: most likely delete, this is only for passthrough implementations
// extern "C" IBearPi* HIDL_FETCH_IBearPi(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace bearpi
} // namespace hardware
} // namespace android
#endif
BearPi.cpp
// FIXME: your file license if you have one
#define LOG_TAG "fht"
#define LOG_VERBOSE "fht"
#include "BearPi.h"
#include <hardware/hardware.h>
#include <hardware/bearpi.h>
#include <inttypes.h>
#include <unistd.h>
namespace android{
namespace hardware{
namespace bearpi{
namespace V1_0{
namespace implementation {
BearPi *BearPi::sInstance = nullptr;
BearPi::BearPi() :mClientCallback(nullptr), mDevice(nullptr){
sInstance = this;
mDevice = openHal();
if(!mDevice){
ALOGE("can't open Hal module");
}
}
BearPi::~BearPi() {
ALOGV("~BearPi()");
if(mDevice == nullptr){
ALOGE("No valid device");
return;
}
mDevice = nullptr;
}
IBearPi* BearPi::getInstance() {
if(!sInstance) {
sInstance = new BearPi();
}
return sInstance;
}
bear_device_t* BearPi::openHal() {
int err;
const hw_module_t *hw_mdl= nullptr;
ALOGD("Opening bear hal library...");
err=hw_get_module(BEAR_HARDWARE_MODULE_ID, &hw_mdl); ----------------------------------------------------->hw_get_module 通过BEAR_HARDWARE_MODULE_ID找到hal
if(err != 0){
ALOGE("Can't open bear hw module. error :%d",err);
return nullptr;
}
if (hw_mdl == nullptr){
ALOGE("No valid bear module");
return nullptr;
}
bear_module_t const *module = reinterpret_cast<const bear_module_t*>(hw_mdl);
if (module->common.methods->open == nullptr){
ALOGE("No valid open method");
return nullptr;
}
hw_device_t *device = nullptr;
if (0 != (err=module->common.methods->open(hw_mdl, nullptr, &device))){
ALOGE("Can't open bear methods, error:%d", err);
return nullptr;
}
bear_device_t * bear_device = reinterpret_cast<bear_device_t*>(device);
if (0 != (err=bear_device->set_notify(bear_device, BearPi::notify))){ --------------------------------------------------------->hal设置回调监听,传函数指针notify
ALOGE("Can't register bear module callback, error: %d", err);
return nullptr;
}
return bear_device;
}
void BearPi::notify(const bear_msg_t *msg) {
BearPi* thisPtr = static_cast<BearPi*>(BearPi::getInstance());
if (thisPtr == nullptr || thisPtr->mClientCallback == nullptr){
ALOGE("Receiving callbacks before th client callback is registered.");
return;
}
const uint64_t devId=reinterpret_cast<uint64_t>(thisPtr->mDevice);
switch (msg->type) {
case BEAR_ERROR:
break;
case BEAR_ACQUIRED:
int32_t vendorCode=0;
BearPiAcquiredInfo result = VendorAcquiredFilter(msg->data.acquired.acquired_info, &vendorCode);
ALOGD("Acquired(%d)", result);
if(!thisPtr->mClientCallback->onResult(devId, result, vendorCode).isOk()) {---------------------------------------------------------->callback回调,客户端响应结果
ALOGE("failed to invoke bear onResult callback");
}
break;
}
}
BearPiAcquiredInfo BearPi::VendorAcquiredFilter(int32_t info, int32_t* vendorCode){
*vendorCode = 0;
switch(info) {
case BEAR_ACQUIRED_GOOD:
return BearPiAcquiredInfo::ACQUIRED_GOOD;
case BEAR_ACQUIRED_PARTIAL:
return BearPiAcquiredInfo::ACQUIRED_PARTIAL;
case BEAR_ACQUIRED_INSUFFICIENT:
return BearPiAcquiredInfo::ACQUIRED_INSUFFICIENT;
case BEAR_ACQUIRED_IMAGER_DIRTY:
return BearPiAcquiredInfo::ACQUIRED_IMAGER_DIRTY;
case BEAR_ACQUIRED_TOO_SLOW:
return BearPiAcquiredInfo::ACQUIRED_TOO_SLOW;
case BEAR_ACQUIRED_TOO_FAST:
return BearPiAcquiredInfo::ACQUIRED_TOO_FAST;
default:
if (info >= BEAR_ACQUIRED_VENDOR_BASE) {
// vendor specific code.
*vendorCode = info - BEAR_ACQUIRED_VENDOR_BASE;
return BearPiAcquiredInfo::ACQUIRED_VENDOR;
}
}
ALOGE("Unknown acquiredmsg from bear vendor library: %d", info);
return BearPiAcquiredInfo::ACQUIRED_INSUFFICIENT;
}
// Methods from ::android::hardware::bearpi::V1_0::IBearPi follow.
Return<uint64_t> BearPi::setNotify(const sp<IBearPiClientCallback>& callback) {
// TODO implement
mClientCallback = callback;
ALOGD("%s",__func__ );
return reinterpret_cast<uint64_t>(mDevice);
}
Return<uint64_t> BearPi::test() {
// TODO implement
ALOGD("%s",__func__ );
return mDevice->test(mDevice);
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
//IBearPi* HIDL_FETCH_IBearPi(const char* /* name */) {---------------------------------------------->打开就是直通模式,注释使用绑定式
//return new BearPi();
//}
//
} // namespace implementation
} // namespace V1_0
} // namespace bearpi
} // namespace hardware
} // namespace android
四,添加service
在default路径下创建service.cpp
#define LOG_TAG "fht"
#include <android/log.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <android/hardware/bearpi/1.0/IBearPi.h>
#include <android/hardware/bearpi/1.0/types.h>
#include "BearPi.h"
using android::hardware::bearpi::V1_0::IBearPi;
using android::hardware::bearpi::V1_0::implementation::BearPi;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::sp;
int main(){
ALOGD("Bearpi service start ");
android::sp<IBearPi> bear = BearPi::getInstance();
//绑定式服务
// 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.
//如果“callerWillJoin”为true,则表示调用方将加入线程池,因此内核需要少创建一个线程。
//如果“callerWillJoin”为false,我们仍然会在本地生成一个线程,我们还应该告诉内核创建的线程比这里请求的少一个。
configureRpcThreadpool(1, true /*callerWillJoin*/); // 用于设置 当前进程用于hwbinder通信的最大线程数
if (bear != nullptr) {
if (::android::OK != bear->registerAsService()) { //注册服务
return 1;
}
} else {
ALOGE("Can't create instance of bearpi, nullptr");
}
ALOGD("Bearpi service registerAsService ");
// 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
joinRpcThreadpool();
return 0; // should never get here
//直通式服务, defaultPassthroughServiceImplementation
将对提供的 -impl
库执行 dlopen()
操作,并将其作为 Binder 化服务提供
//return defaultPassthroughServiceImplementation<IBearPi>("BearPi");
}
五,启动rc文件
在default路径下新建 android.hardware.bearpi@1.0-service.rc
service vendor.bear_hal /vendor/bin/hw/android.hardware.bearpi@1.0-service
# "class hal" causes a race condition on some devices due to files created
# in /data. As a workaround, postpone startup until later in boot once
# /data is mounted.
class hal
user system
group system system
六,生成Android.bp文件
文件创建成功后,需要加入编译,下面我们使用hidl-gen生成对应的Android.bp文件
生成1.0框架接口库
hidl-gen -Landroidbp -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.bearpi@1.0
生成impl 库的 Android.bp
hidl-gen -Landroidbp-impl -o /home/ubuntu/data/LG/hardware/interfaces/bearpi/1.0/default/ -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.bearpi@1.0
修改default下面Android.bp,生成service bin
cc_binary {
name: "android.hardware.bearpi@1.0-service",
defaults: ["hidl_defaults"],
init_rc: ["android.hardware.bearpi@1.0-service.rc"],
vendor: true,
relative_install_path: "hw",
srcs: [
"BearPi.cpp",
"service.cpp",
],
shared_libs: [
"libcutils",
"liblog",
"libhidlbase",
"libhardware",
"libutils",
- "android.hardware.bearpi@1.0",
],
}
七,SELinux权限
以mtk平台为例,添加bearpi相应权限
路径:device/mediatek/sepolicy/basic/non_plat
1,新建bearpi.te
type bearpi, domain;
type bearpi_exec, exec_type, vendor_file_type, file_type;
hwbinder_use(bearpi);
init_daemon_domain(bearpi)
add_hwservice(bearpi, hal_bearpi_hwservice)
allow bearpi hwservicemanager_prop:file { read open getattr map };
2,file_contexts中添加
/(vendor|system/vendor)/bin/hw/android\.hardware\.bearpi@1\.0-service u:object_r:bearpi_exec:s0
3,hwservice.te中定义hwservice manager类型
type hal_bearpi_hwservice, hwservice_manager_type;
4,hwservice_contexts中添加接口访问权限
android.hardware.bearpi::IBearPi u:object_r:hal_bearpi_hwservice:s0
八,编译
1,添加系统编译,使系统整编后能主动编译,在device/xx/device.mk中添加包编译
PRODUCT_PACKAGES += \
android.hardware.bearpi@1.0-service \
android.hardware.bearpi@1.0 \
test_bearpi \
bearpi.default
DEVICE_MANIFEST_FILE += device/xiaomi/lancelot/manifest_bearpi.xml
2,在项目地下新建manifest_bearpi.xml接口注册文件,如果没有注册文件,客户端和服务端将无法通信,getService()将返回null。
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.bearpi</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IBearPi</name>
<instance>default</instance>
</interface>
</hal>
</manifest>
3,添加启动服务,在init.project.rc内添加bearpi 的rc文件,保证开机能够启动服务
import /vendor/etc/init/android.hardware.bearpi@1.0-service.rc
----------------------------------------------------------我------------是-------------分-----------割------------线---------------------------------------------------------------
如上服务端写好后,我们写客户端测试下
在1.0路径下新建test目录
创建BearPiTest.cpp
//
// Created by ubuntu on 2020/12/25.
//
#define LOG_TAG "fht"
#define LOG_VERBOSE "fht"
#include <android/hardware/bearpi/1.0/IBearPi.h>
#include <hidl/Status.h>
#include <log/log.h>
#include <android/log.h>
using android::hardware::bearpi::V1_0::BearPiAcquiredInfo;
using android::hardware::bearpi::V1_0::IBearPi;
using android::hardware::bearpi::V1_0::IBearPiClientCallback;
using android::sp;
using android::hardware::hidl_string;
using android::hardware::Return; // 命名空间
using android::hardware::Void; // Void函数
//实现回调
class BearPiClientCallback : public IBearPiClientCallback {
public:
BearPiClientCallback() {}
~BearPiClientCallback() {}
Return<void> onResult(uint64_t deviceId, const ::android::hardware::bearpi::V1_0::BearPiAcquiredInfo acquiredInfo,uint32_t vendorCode) override {
ALOGD("onResult deviceid =%lu, vendorCode = %d, info = %d", deviceId, vendorCode, acquiredInfo);
return Void();
};
};
int main() {
//获得服务
sp <IBearPi> bear = IBearPi::getService();
if (bear == nullptr) {
ALOGE("Can't find IBearPi service ...");
return -1;
}
ALOGD("IBearPi ON");
sp <BearPiClientCallback> callback = new BearPiClientCallback();
//注册回调
bear->setNotify(callback);
bear->test();
return 0;
}
在test目录下新建Android.bp
cc_binary {
name: "test_bearpi",
srcs: [
"BearPiTest.cpp",
],
shared_libs: [
"libcutils",
"liblog",
"libhidlbase",
"libhardware",
"libutils",
"android.hardware.bearpi@1.0",
],
}