当前位置:   article > 正文

Android HIDL 介绍学习和实战应用

android hidl

什么是HIDL?

官方回答:

HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是用于在可以独立编译的代码库之间进行通信的系统。

HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。

HIDL 可指定数据结构和方法签名,这些内容会整理归类到接口(与类相似)中,而接口会汇集到软件包中。尽管 HIDL 具有一系列不同的关键字,但 C++ 和 Java 程序员对 HIDL 的语法并不陌生。此外,HIDL 还使用 Java 样式的注释。

我的理解:

其实就是谷歌为了解耦硬件与上层软件,摆脱传统的HAL需要依赖上层与底层硬件,这样分离开来便于底层上层人员各司其职,专于自己部分的开发。同时也便于代码的管理,当定制硬件发生改变的时候,不需要改动谷歌相关的代码,只需要芯片厂商或者odm修改硬件相关的代码即可,做ota升级时也可单独升级硬件。这样做还是很多好处的。这里还有一个比较晦涩的概念就是直通模式和binder模式,刚开始接触的时候一脸懵逼,其实后来的理解就是一个在同一个进程当中(直通模式)相当于传统hal模式的直接调用库的方式,这时候不牵扯到进程间通信。另一种就是不在同一个进程中(binder模式),相当于使用aidl模式的客户端与服务端的通信,必须要有一个底层的binder驱动来支持,当然hidl有自己的binder驱动叫做hwbinder其实大同小异。两种模式的共同目标都是用来实现对底层硬件的调用,具体的硬件实现就交给各个芯片厂商odm厂商自己去实现。感觉谷歌的目的是想做一个纯软件公司,硬件相关的东西都给剥离出来。

实现自己的一个HIDL

本文以Android10为例子进行编写,由于大部分硬件开发过程中都是以C++形式,故本文目前为止仅仅介绍C++形式的hidl实现。

我们假设自己新推出了一款硬件叫做Chao,现在需要将这个硬件添加到谷歌的工程当中,这时候我们需要做的第一步就是将这个硬件添加到hidl当中,首先我们需要在hardware/interfaces/中创建一个目录叫做chao

创建完这个目录以后创建我们的1.0版本,hidl是有严格的版本管理机制的方便以后的兼容,我们同样是要在chao目录下再创建一个1.0的目录,然后在1.0这个目录下才是代码创建的开始。同时这个目录下需要创建一个default的目录用来存放后续自动生成以及需要自己实现的C++代码,整个目录结构如下图:

首先需要创建的就是hal文件,这也是hidl诞生而来的新的文件格式,在这个文件中我们定义自己的函数,类似于在aidl中首先需要定义一个aidl后缀接口文件一样。

首先我们在IChao.hal中定义硬件函数内容如下:

注意其中的包名以及函数定义方式,具体的语法格式以及定义请查阅谷歌官网https://source.android.com/devices/architecture/hidl

然后我们需要的一些变量以及结构体的定义放到types.hal中内容如下:

  1. package android.hardware.chao@1.0;
  2. enum Status : int32_t {
  3. SUCCESS,
  4. CHAO_NOT_SUPPORTED,
  5. UNKNOWN,
  6. };
  7. enum Type : int32_t {
  8. BACKLIGHT,
  9. KEYBOARD,
  10. BUTTONS,
  11. BATTERY,
  12. NOTIFICATIONS,
  13. ATTENTION,
  14. BLUETOOTH,
  15. WIFI,
  16. CHAO,
  17. COUNT,
  18. };
  19. struct ChaoState {
  20. uint32_t chaoRen;
  21. uint32_t ironMan;
  22. };

当我们创建完以上的文件以后就需要下一步的自动代码生成了:

我们需要到源码目录下先source lunch,然后定义两个全局变量指定我们的包名以及代码路径,需要用到的命令如下:

  1. felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ PACKAGE=android.hardware.chao@1.0
  2. felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ LOC=hardware/interfaces/chao/1.0/default/
  3. felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ m -j hidl-gen

最后一个拉起hidl-gen工具。

当我们执行完上述命令以后,然后才是真正生成c++代码的命令如下:

  1. felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
  2. felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

(felix@felix:~/workspace/SA800U_Android10.0_R03_r200$为作者的电脑源码路径请自行忽略)

当我们执行完上述所有的命令以后可以去default目录查看已经生成了相关的c++代码以及Android.bp文件如下:

同时我们需要在1.0目录下同样生成一个Android.bp文件便于以后的编译命令如下:

hidl-gen -Landroidbp  -r android.hidl:system/libhidl/transport -r android.hardware:hardware/interfaces android.hardware.chao@1.0

此时我们的C++代码已经生成,这时候基本可以生成一个共享库,但是这个库没有什么功能,需要我们自己去添加,一般我们也是在这个生成的cpp文件中去连接旧版hal层的库文件,或者直接进行底层硬件的操作,这样可以跟旧版本的HAL进行兼容,在这里去调用旧版本承上启下。下一步我们还需要创建一个服务,用来绑定这个库,以便于客户端来调用他,一般我们习惯给这个服务源文件取名为service.cpp,在这个文件里才是真正区别于直通式还是绑定式的服务。

先把自动生成的文件做稍微的修改,由于本例当中并没有真正的硬件操作,我们到此为止,简单进行函数的返回,修改后的代码如下:

Chao.cpp

  1. // FIXME: your file license if you have one
  2. #include "Chao.h"
  3. namespace android {
  4. namespace hardware {
  5. namespace chao {
  6. namespace V1_0 {
  7. namespace implementation {
  8. // Methods from ::android::hardware::chao::V1_0::IChao follow.
  9. Return<::android::hardware::chao::V1_0::Status> Chao::setChao(::android::hardware::chao::V1_0::Type type, const ::android::hardware::chao::V1_0::ChaoState& state) {
  10. if(type != Type::CHAO && state.chaoRen != 1){
  11. }
  12. // TODO implement
  13. return ::android::hardware::chao::V1_0::Status::SUCCESS;
  14. }
  15. // Methods from ::android::hidl::base::V1_0::IBase follow.
  16. IChao* HIDL_FETCH_IChao(const char* /* name */) {
  17. return new Chao();
  18. }
  19. //
  20. } // namespace implementation
  21. } // namespace V1_0
  22. } // namespace chao
  23. } // namespace hardware
  24. } // namespace android

此处没有去操作真正的硬件,读者需自己添加硬件操作,本例只做演示使用

Chao.h

  1. // FIXME: your file license if you have one
  2. #pragma once
  3. #include <android/hardware/chao/1.0/IChao.h>
  4. #include <hidl/MQDescriptor.h>
  5. #include <hidl/Status.h>
  6. namespace android {
  7. namespace hardware {
  8. namespace chao {
  9. namespace V1_0 {
  10. namespace implementation {
  11. using ::android::hardware::hidl_array;
  12. using ::android::hardware::hidl_memory;
  13. using ::android::hardware::hidl_string;
  14. using ::android::hardware::hidl_vec;
  15. using ::android::hardware::Return;
  16. using ::android::hardware::Void;
  17. using ::android::sp;
  18. struct Chao : public IChao {
  19. // Methods from ::android::hardware::chao::V1_0::IChao follow.
  20. Return<::android::hardware::chao::V1_0::Status> setChao(::android::hardware::chao::V1_0::Type type, const ::android::hardware::chao::V1_0::ChaoState& state) override;
  21. // Methods from ::android::hidl::base::V1_0::IBase follow.
  22. };
  23. // FIXME: most likely delete, this is only for passthrough implementations
  24. extern "C" IChao* HIDL_FETCH_IChao(const char* name);
  25. } // namespace implementation
  26. } // namespace V1_0
  27. } // namespace chao
  28. } // namespace hardware
  29. } // namespace android

到此为止我们已经可以对这个目录进行简单的编译的,可以在源码目录下执行 mmma hardware/interfaces/chao/1.0/ -j8

执行结果如下:

此时可以看到我们的库已经生成了,具体目录如下图:

就像文章开头所说实现了解耦,这里的库默认也跑到了vendor目录下了,这样跟安卓framework完全脱离,system.img和vendor.img的分离,硬件适配全在vendor.img里,这也是后来Android10版本vendor.img不能随便替换的原因,否则容易不开机,因为跟硬件息息相关,反而system.img变的比较容易替换,因为已经脱离了基本的硬件依赖。

接下来我们来编写service.cpp,我们本例按照直通式的方式来实现,目录文件如下:

service.cpp内容如下:

  1. #include <android/hardware/chao/1.0/IChao.h>
  2. #include <hidl/LegacySupport.h>
  3. #include <hwbinder/ProcessState.h>
  4. using android::hardware::chao::V1_0::IChao;
  5. using android::hardware::defaultPassthroughServiceImplementation;
  6. int main() {
  7. #ifdef ARCH_ARM_32
  8. android::hardware::ProcessState::initWithMmapSize((size_t)(32768));
  9. #endif
  10. return defaultPassthroughServiceImplementation<IChao>();
  11. }

这里的代码非常简单直通式的会调用系统预置的接口帮我们完成注册,我们直需把我们的接口名称传入defaultPassthroughServiceImplementation模板即可。

接下来还要自己创建一个android.hardware.chao@1.0-service.rc文件以便于开机的时候将我们的service.cpp驱动起来成为我们这个服务的守护进程,内容如下:

  1. service vendor.chao-hal-1-0 /vendor/bin/hw/android.hardware.chao@1.0-service
  2. interface android.hardware.chao@1.0::IChao default
  3. class hal
  4. user system
  5. group system

我们刚才的service.cpp以及rc文件都是后来自己创建的,我们需要把他们加到Android.bp当中参加系统的编译,所以在1.0目录下的Android.bp添加以下内容:

  1. // FIXME: your file license if you have one
  2. cc_library_shared {
  3. // FIXME: this should only be -impl for a passthrough hal.
  4. // In most cases, to convert this to a binderized implementation, you should:
  5. // - change '-impl' to '-service' here and make it a cc_binary instead of a
  6. // cc_library_shared.
  7. // - add a *.rc file for this module.
  8. // - delete HIDL_FETCH_I* functions.
  9. // - call configureRpcThreadpool and registerAsService on the instance.
  10. // You may also want to append '-impl/-service' with a specific identifier like
  11. // '-vendor' or '-<hardware identifier>' etc to distinguish it.
  12. name: "android.hardware.chao@1.0-impl",
  13. relative_install_path: "hw",
  14. // FIXME: this should be 'vendor: true' for modules that will eventually be
  15. // on AOSP.
  16. proprietary: true,
  17. srcs: [
  18. "Chao.cpp",
  19. ],
  20. shared_libs: [
  21. "libhidlbase",
  22. "libhidltransport",
  23. "libutils",
  24. "android.hardware.chao@1.0",
  25. ],
  26. }
  27. cc_defaults {
  28. name: "chao_service_defaults",
  29. relative_install_path: "hw",
  30. defaults: ["hidl_defaults"],
  31. vendor: true,
  32. shared_libs: [
  33. "liblog",
  34. "libbase",
  35. "libdl",
  36. "libutils",
  37. "libhwbinder",
  38. "libhardware",
  39. "libhidlbase",
  40. "libhidltransport",
  41. "android.hardware.chao@1.0",
  42. ],
  43. arch: {
  44. arm: {
  45. cflags: [
  46. "-DARCH_ARM_32"
  47. ],
  48. },
  49. },
  50. }
  51. cc_binary {
  52. name: "android.hardware.chao@1.0-service",
  53. defaults: ["chao_service_defaults"],
  54. init_rc: ["android.hardware.chao@1.0-service.rc"],
  55. srcs: ["service.cpp"],
  56. }

这样我们就添加完成。

至此server端的实现基本完成,最后需要更新以下编译脚本,然后编译一下,执行如下命令:

felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ ./hardware/interfaces/update-makefiles.sh

felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ mmma hardware/interfaces/chao/1.0/ -j8

执行完命令以后会生成相应的bin文件以及rc文件到out目录

接下来我们还要把我们添加的服务注册到系统中去,这也是hidl区别于aidl的地方,使用一个manifest.xml来管理

我们在device/qcom/sdm845/manifest.xml中添加如下内容,这个路经不同平台为止不同需自行添加,有了这段内容才能够让客户端找到正确的版本进行绑定。
<hal format="hidl">
        <name>android.hardware.chao</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IChao</name>
            <instance>default</instance>
        </interface>
    </hal>

如果需要全局编译,需要添加兼容性适配将以上内容同样添加到hardware\interfaces\compatibility_matrices\compatibility_matrix.3.xml

暴力编译:

diff --git a/build/make/target/product/gsi/Android.mk b/build/make/target/product/gsi/Android.mk
index eaaa051..9418ace 100644
--- a/build/make/target/product/gsi/Android.mk
+++ b/build/make/target/product/gsi/Android.mk
@@ -62,7 +62,7 @@ $(check-vndk-list-timestamp): $(INTERNAL_VNDK_LIB_LIST) $(LATEST_VNDK_LIB_LIST)
          --new-line-format="Added %L" \
          --unchanged-line-format="" \
          $(LATEST_VNDK_LIB_LIST) $(INTERNAL_VNDK_LIB_LIST) \
-         || ( echo -e $(_vndk_check_failure_message); exit 1 ))
+         || ( echo -e $(_vndk_check_failure_message); ))
        $(hide) mkdir -p $(dir $@)
        $(hide) touch $@

温柔编译:

修改SA800U_Android10.0_R03_r200\build\target\product\gsi\current.txt

以及SA800U_Android10.0_R03_r200\build\target\product\gsi\29.txt

这几个文件跟版本有关,可以全部添加

如下内容:

如仍然遇到编译错误,将out目录生成的对比文件先删掉,然后重新编译

此时的服务还不能真正起来因为我们还没有配置seliunx权限,配置如下:

添加新的te文件hal_chao_default.te 

内容如下:

type hal_chao_default, domain;
#hal_server_domain(hal_chao_default, hal_chao)

type hal_chao_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_chao_default)

当我们刷入机器此时可以看到:

服务已经起来 

生成hash值

felix@felix:~/workspace/SA800U_Android10.0_R03_r200$ hidl-gen -L hash -r androdi.harware:chao/1.0/default -r android.hardware:hardware/interfaces -r android.hidl:syste
m/libhidl/transport android.hardware.chao@1.0
d854281eec40e3f6adcdf50b2005d2b283839bb13b953a79d2641a2873cbd65a android.hardware.chao@1.0::types
518585848374af8138ca6de3d676db249eec5e055000647c1edab0e627cf4bd6 android.hardware.chao@1.0::IChao

至此我们服务端的建立算是完成,下篇文章再继续讲解客户端的调用。

客户端调用已完成见如下链接

Android HIDL 介绍学习之客户端调用_Super Jang的博客-CSDN博客

创作不易,各位看官想要支持请扫如下二维码:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/255956
推荐阅读
相关标签
  

闽ICP备14008679号