aosp hal开发

Android-HAL与HIDL分析使用总结

HAL接口定义语言(简称HIDL)

适用于指定HAL和其用户之间的接口的一种接口描述语言(IDL),HIDL允许指定类型和方法调用。
HIDL旨在用于进程间通信(IPC)。进程之间的通信经过Binder化。对于必须与进程相关联的代码库,还可以使用直通模式。
HIDL可指定数据结构和方法签名,这些内容会整理归类到接口中,而接口会汇集到软件包中。
尽管HIDL具有一系列不同的关键字,C++和JAVA程序员对HIDL的语法并不陌生。此外,HIDL还是用JAVA样式和注释。

Android 8.0引入hidl,目的是为了将hal从system.img移除出去,方便android版本升级。
Google每次更新Android大版本,基本上都是framework的升级,与vendor改的代码理论上是可以独立开来的,
所以Google尝试通过Treble来独立更新system.img来帮助vendor更快的移植新的Android版本。

以前HAL是以so的形式存在的,作为一系列标准接口,供Android framework调用,无论是通过jni还是别的途径,
如果要被framework调用,那这些so就一定要存在于system分区,但是我们现在要把system分区独立开来,
这样vendor修改的代码全部要在vendor分区,所以引入HIDL来解决这个问题,vendor设计的HAL都以独立的service存在,
每一个HAL模块都是一个独立的binder server进程,Android framework想要调用HAL的接口就必须作为binder的client来调用.

AOSP有哪些HAL:

Camera
Audio
Sensor
等等
  • Google理论上只关心Android的框架层和上层软件,但是上层软件依赖于底层的硬件实现,每家手机厂商,
    或者说是CPU厂商底层硬件的实现都是不一样的,所以这个HAL层基本都是手机厂商或者CPU厂商去实现的

  • Google只是作为一个框架的指导,和Framework层API的接口定义,这些接口的实现都得由HAL去完成。

  • 底层硬件都是由Linux kernel驱动控制的,提供文件读写就可以简单控制驱动,在此以虚拟驱动为例,省略kernel driver的实现;

HIDL 接口文件定义 server端

  • 我们假设 DemoHal 作为标准AOSP的HAL,进入代码目录创建HIDL目录

mkdir -p hardware/interfaces/demohal/1.0/default

  • 接着创建接口描述文件 IDemoHal.hal
package android.hardware.demohal@1.0;
interface IDemoHal {
    helloWorld(string name) generates (string result);
};

这里我们定义了一个 IDemoHal 接口文件,简单的添加了一个 helloWorld 接口,入参 string name,返回string,后面再来实现这个接口。

  • 生成HAL 相关代码实现和编译文件

Google提供了一些工具来生成HAL层相关的代码框架和代码实例,我们只需关心接口实现部分。

生成hidl-gen工具
source source ./build/envsetup.sh
lunch your_project
make hidl-gen -j4

hidl_interface name
# PACKAGE=android.hardware.demohal@1.0  
.hal文件位置
# LOC=hardware/interfaces/demohal/1.0/c++/

1: 生成c++空实现
-r{hidl_package_root name}:{hidl_package_root path}
-randroid.hidl:system/libhidl/transport
# hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport ${PACKAGE}

2: 生成 c++代码的Android.bp
# hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport ${PACKAGE}

3.touch hardware/interfaces/demohal/1.0/c++/service.cpp
现在我们的代码目录: 
hardware/interface/demohal:
├── 1.0
│   ├── Android.bp
│   ├── Android.mk
│   ├── c++
│   │   ├── Android.bp
│   │   ├── android.hardware.demohal@1.0-service.rc
│   │   ├── DemoHal.cpp
│   │   ├── DemoHal.h
│   │   └── service.cpp
│   └── IDemoHal.hal
└── Android.bp

我们写代码就写了一个IDemoHal.hal,其余代码都是自动生成的,
特别是 DemoHal.cpp 和 DemoHal.h 这两个文件是实现接口的关键文件。实现HAL实现端的共享库

  • 开始写代码
打开 DemoHal.h 文件

struct DemoHal : public IDemoHal {
    // Methods from IDemoHal 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" IDemoHal* HIDL_FETCH_IDemoHal(const char* name);

HIDL的实现有两种方式,一种是Binderized模式,另一种是Passthrough模式,
我们看到上面有两行注释掉的代码,看来这个代码是关键,来选择实现方式是Binderized还是Passthrough。

我们这里使用Passthrough模式来演示,这两种方式本质是一样的,目前大部分厂商使用的是Passthrough来延续以前的很多代码,但是慢慢的都会被改掉的。

DemoHal.h
# ifndef ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# define ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/MQDescriptor.h>
# include <hidl/Status.h>
 
namespace android {
namespace hardware {
namespace demohal {
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 DemoHal : public IDemoHal {
    // Methods from IDemoHal 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" IDemoHal* HIDL_FETCH_IDemoHal(const char* name);
 
}  // namespace implementation
}  // namespace V1_0
}  // namespace demohal
}  // namespace hardware
}  // namespace android
 
# endif  // ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
DemoHal.cpp 实现 DemoHal::helloWorld() 接口;

# include "DemoHal.h"
 
namespace android {
namespace hardware {
namespace demohal {
namespace V1_0 {
namespace implementation {
 
// Methods from IDemoHal follow.
Return<void> DemoHal::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.
 
IDemoHal* HIDL_FETCH_IDemoHal(const char* /* name */) {
    return new DemoHal();
}
 
}  // namespace implementation
}  // namespace V1_0
}  // namespace demohal
}  // namespace hardware
}  // namespace android
  1. 我们打开了 HIDL_FETCH 的注释,让我们的 HIDL 使用 Passthrough 方式实现;
  2. 添加helloWorld函数的实现(返回拼接后的字符串);

查看一下Android.bp文件

cc_library_shared {
    name: "android.hardware.demohal@1.0-impl",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "DemoHal.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "android.hardware.demohal@1.0",
    ],
}

用mmm编译最终会在/vendor/lib64/hw/下生成一个 android.hardware.demohal@1.0-impl.so

$ mmm hardware/interfaces/demohal/1.0/default/

  • 调用流程

上面完成了实现端的代码和编译,接下来看一下整个HIDL的调用流程。
HIDL软件包中自动生成的文件会链接到与软件包同名的单个共享库。
该共享库还会导出单个头文件 IDemoHal.h,用于在binder客户端和服务端声明的接口文件;

我们这个实例会用到以下几个模块:

  • android.hardware.demohal@1.0-impl.so: DemoHal模块实现端的代码编译生成,binder server端
  • android.hardware.demohal@1.0.so: DemoHal模块调用端的代码,binder client端
  • demohal_hal_service: 通过直通式注册binder service,暴露接口给client调用
  • android.hardware.demohal@1.0-service.rc: Android native 进程入口
  • 启动binder server端进程

回到之前创建的两个文件,先来看一下rc文件

hardware/interfaces/demohal/1.0/default/android.hardware.demohal@1.0-service.rc
 
service demohal_hal_service /vendor/bin/hw/android.hardware.demohal@1.0-service
    class hal
    user system
    group system

在设备启动的时候执行 /vendor/bin/hw/android.hardware.demohal@1.0-service 程序

hardware/interfaces/demohal/1.0/default/service.cpp
# define LOG_TAG "android.hardware.demohal@1.0-service"
# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/LegacySupport.h>
 
using android::hardware::demohal::V1_0::IDemoHal;
using android::hardware::defaultPassthroughServiceImplementation;
 
int main() {
    return defaultPassthroughServiceImplementation<IDemoHal>();
}

这个service注册了IDemoHal接口文件中定义的接口作为binder server端,就一条return语句;
因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService啦。
编译后可以在, vendor/bin/hw/下找到对应的文件。

OK,我们server端的进程和实现端共享库已经完成了。

但是这个时候你如果烧录镜像,会发现这个进程会启动失败,原因是因为没有给这个进程配sepolicy,
所以正确的做法是要给他加上selinux的权限,可先略过,暂时可以用root权限去手动起这个service;
手动运行service

# vendor/bin/hw/android.hardware.demohal@1.0-service

在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入:

<hal format="hidl">
    <name>android.hardware.demohal</name>
    <transport>hwbinder</transport>
    <version>1.0</version>
    <interface>
        <name>IDemoHal</name>
        <instance>default</instance>
    </interface>
</hal>

HIDL Client测试代码 client.cpp

首先要注意包含HIDL接口实现端的头文件 android/hardware/demohal/1.0/IDemoHal.h

# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/Status.h>
# include <hidl/LegacySupport.h>
# include <utils/misc.h>
# include <hidl/HidlSupport.h>
# include <stdio.h>
 
using android::hardware::demohal::V1_0::IDemoHal;
using android::sp;
using android::hardware::hidl_string;
 
int main()
{
    int ret;
 
    android::sp<IDemoHal> service = IDemoHal::getService();
    if(service == nullptr) {
        printf("Failed to get service\n");
        return -1;
    }
 
    service->helloWorld("JayZhang", [&](hidl_string result) {
                                        printf("%s\n", result.c_str());
                                    }
                       );
    return 0;
}

客户端通过 IDemoHal::getService() 获取binder server端代理服务对象,
然后就可以调用他的方法了,我们这里调用 helloWorld 接口,然后通过callback获取结果。

Android.mk
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE := demohal_test
LOCAL_SRC_FILES := \
    client.cpp \
 
LOCAL_SHARED_LIBRARIES := \
   liblog \
   libhidlbase \
   libutils \
   android.hardware.demohal@1.0 \
 
include $(BUILD_EXECUTABLE)

创建HIDL的server端和client端到此结束。
以上实现了 client 端到 server 端的接口调用过程;
如果需要在 server 端调用 client 端的接口来传递和处理数据,需要注册回调函数给 server 端;

注册回调

我们把HAL独立为一个单独的进程,client也是一个单独的进程,那么对于一般的模块而言,都是需要从底层(HAL以及以下)获取数据,
比如sensor,需要获取sensor数据,Camera,需要获取camera的raw、yuv等数据流,

对于软件设计而言,如果是同步的话,通过getXXX()函数来获取即可,
但是如果是异步的,比如底层的实现是中断的机制,不知道什么时候会来数据,那么这个时候通常会通过 callback 来实现异步的回调。

写一个简单的HAL模块,然后在.hal文件里面加入一个 setCallback 函数,传入一个 callback 指针,
当HAL的server端起来的时候会起一个线程,每隔5秒钟时间调用一下传入的这个回调函数,实现回调的机制。看一下 HIDL 接口 IHello.hal

package vendor.sample.hello@1.0;
import IHelloCallback;
 
interface IHello {
    init();
    release();
    setCallback(IHelloCallback callback);
};
定义了三个接口
init:做一些初始化的动作
release:做一些释放的动作
setCallback:让client端设置一个callback方法到server端

下面来看看这个callback里面都定义了些啥,我们要为这个callback定义一个接口

IHelloCallback.hal
package vendor.sample.hello@1.0;
interface IHelloCallback {
 oneway onNotify(HalEvent event);
};
回调函数里面有一个回调方法,可以让server传一个 HalEvent 的结构体到client端,这个结构体也是自定义的,在types.hal,
可以定义自己喜欢的类型,这里是一个简单的int成员变量。
types.hal 文件

package vendor.sample.hello@1.0;
struct HalEvent {
 int32_t value;
};
OK,HIDL的接口定义好之后,使用hidl-gen工具生成代码框架

$ hidl-gen -o vendor/xxx/common/sample/hidl-impl/sample/ \
    -Lc++-impl -rvendor.sample:vendor/xxx/common/sample/interfaces \
    -randroid.hidl:system/libhidl/transport vendor.sample.hello@1.0
生成了代码:

├── Android.bp 
│ ├── HelloCallback.cpp
│ ├── HelloCallback.h
│ ├── Hello.cpp
│ └── Hello.h
└── interfaces
​ ├── Android.bp
​ └── hello
​ └── 1.0
​ ├── Android.bp
​ ├── IHelloCallback.hal
​ ├── IHello.hal
​ └── types.hal

其中有一个代码是用不到的,HelloCallback.h和HelloCallback.cpp,删掉他们。
注意,要把hidl-impl/sample/Android.bp里面的HelloCallback.cpp也要删掉

cc_library_shared {
 proprietary: true,
 srcs: [
 "Hello.cpp",
 ],
 shared_libs: [
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

接下来就是写代码了
在vendor分区,要起一个service来handle这个 HIDL 接口

service.cpp
#include <vendor/sample/hello/1.0/IHello.h>
​#include <hidl/LegacySupport.h>
​
using vendor::sample::hello::V1_0::IHello;
using android::hardware::defaultPassthroughServiceImplementation;
​
int main()
{
 return defaultPassthroughServiceImplementation<IHello>();
}

然后是makefile

cc_binary {
 name: "vendor.sample.hello@1.0-service",
 relative_install_path: "hw",
 defaults: ["hidl_defaults"],
 vendor: true,
 init_rc: ["vendor.sample.hello@1.0-service.rc"],
 srcs: [
 "service.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

然后编译,应该就能看到生产impl的库和一个可执行程序用来起server的。

下面的代码,是主体实现端的代码

#define LOG_TAG     "Sample"
​
#include "Hello.h"
#include <log/log.h>
​
namespace vendor {
namespace sample {
namespace hello {
namespace V1_0 {
namespace implementation {
​
sp<IHelloCallback> Hello::mCallback = nullptr;
​
// Methods from ::vendor::sample::hello::V1_0::IHello follow.
Return<void> Hello::init() {
 mExit = false;
 run("sample");
 return Void();
}
​
Return<void> Hello::release() {
 mExit = true;
 return Void();
}
​
Return<void> Hello::setCallback(const sp<::vendor::sample::hello::V1_0::IHelloCallback>& callback) {
 mCallback = callback;
 if(mCallback != nullptr) {
  ALOGD("setCallback: done");
 }
​
return Void();
​
}
​
bool Hello::threadLoop()
{
 static int32_t count = 0;
 HalEvent event;
 while(!mExit) {
  ::sleep(1);
  event.value = count ++;
  if(mCallback != nullptr) {
   mCallback->onNotify(event);
  }
 }
 ALOGD("threadLoop: exit");
 return false;
}
​
// Methods from ::android::hidl::base::V1_0::IBase follow.
​
IHello* HIDL_FETCH_IHello(const char* /* name */) {
 return new Hello();
}
//
}  // namespace implementation
}  // namespace V1_0
}  // namespace hello
}  // namespace sample
}  // namespace vendor

run函数就相当于是启动了一个线程,sample是线程的名字,具体可以看一下继承的父类 android::Thread
在init函数里面调用run方法启动线程,线程的主体是threadLoop函数,可以看到在线程里面,会每隔1秒钟循环去callback一次方法;下面是client的实现

#define LOG_TAG "TestHello"​
#include <log/log.h>
#include <vendor/sample/hello/1.0/types.h>
#include <vendor/sample/hello/1.0/IHello.h>
#include <vendor/sample/hello/1.0/IHelloCallback.h>
#include <hidl/Status.h>
#include <hidl/HidlSupport.h>
​
using android::sp;
using android::hardware::Return;
using android::hardware::Void;
​
using vendor::sample::hello::V1_0::HalEvent;
using vendor::sample::hello::V1_0::IHello;
using vendor::sample::hello::V1_0::IHelloCallback;
​
class HelloCallback: public IHelloCallback {
public:
 HelloCallback() {
 
}
​
~HelloCallback() {
 
}
​
Return<void> onNotify(const HalEvent& event) {
​
 ALOGD("onNotify: value = %d", event.value);
​
 return Void();
}
​
};
​
int main(void)
{
 sp<IHello> service = IHello::getService();
 if(service == nullptr) {
  ALOGE("main: failed to get hello service");
  return -1;
 }
​
sp<HelloCallback> callback = new HelloCallback();
service->setCallback(callback);
service->init();
​
::sleep(10);
service->release();
​
return 0;
​
}

在client端就是简单的打印了callback回来的event里面的数据

下面是makefile的代码

cc_binary {
 name: "test_hello",
 srcs: [
 "test_hello.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

编译后手动运行测试程序
并通过logcat来获取log输出并观察;

adb 调试

https://developer.android.google.cn/studio/releases/platform-tools.html
https://blog.csdn.net/t567g123/article/details/127744602

adb shell
adb push 

android.bp 文件

Android.mk可以引用Android.bp中的模块,反之Android.bp不能引用Android.mk中的模块

cc_library_shared :编译成动态库,类似于Android.mk中的BUILD_SHARED_LIBRARY
cc_binary:编译成可执行文件,类似于Android.mk中的BUILD_EXECUTABLE
name :编译出的模块的名称,类似于Android.mk中的LOCAL_MODULE
srcs:源文件,类似于Android.mk中的LOCAL_SRC_FILES
local_include_dirs:指定路径查找头文件,类似于Android.mk中的LOCAL_C_INCLUDES
shared_libs:编译所依赖的动态库,类似于Android.mk中的LOCAL_SHARED_LIBRARIES
static_libs:编译所依赖的静态库,类似于Android.mk中的LOCAL_STATIC_LIBRARIES
cflags:编译flag,类似于Android.mk中的LOCAL_CFLAGS

Android.mk语法

Android.mk
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE := demohal_test
LOCAL_SRC_FILES := \
    client.cpp \
 
LOCAL_SHARED_LIBRARIES := \
   liblog \
   libhidlbase \
   libutils \
   android.hardware.demohal@1.0 \
 
include $(BUILD_EXECUTABLE)
LOCAL_PATH := $(call my-dir)

每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在项目树中查找源文件(所以这里应该指定源文件路径,本例中源文件路径是和Android.mk文件路径在同一目录下的)。
宏my-dir则由Build System提供。返回Android.mk的目录路径
include $(CLEAR_VARS)

CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多的LOCAL_xxx。

例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等。但不清理LOCAL_PATH。

这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能避免相互影响。相当于是解析此mk文件的时候GNU Make相关的全局变量会被赋值,如果其他mk文件没有指定某个LOCAL_xxx,那么make相应的全局变量会继续沿用之前mk文件的赋值,因此相互产生影响。
LOCAL_MODULE    := hello-jni 

LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。
Build System会自动添加适当的前缀和后缀。例如,foo,要产生动态库,则生成libfoo.so. 但请注意:如果模块名被定为:libfoo.则生成libfoo.so. 不再加前缀。
LOCAL_SRC_FILES := hello-jni.c 

LOCAL_SRC_FILES变量必须包含将要打包如模块的C/C++ 源码, 可以通过空格包含多个源文件。
不必列出头文件,build System 会自动帮我们找出依赖文件。
缺省的C++源码的扩展名为.cpp. 也可以修改,通过LOCAL_CPP_EXTENSION。
include $(BUILD_SHARED_LIBRARY) 

BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。
它负责收集自从上次调用 include $(CLEAR_VARS)  后的所有LOCAL_XXX信息。并决定编译为什么。

BUILD_STATIC_LIBRARY:编译为静态库。 
BUILD_SHARED_LIBRARY :编译为动态库 
BUILD_EXECUTABLE:编译为Native C可执行程序 
LOCAL_SHARED_LIBRARIES: 要链接到本模块的动态库。     
LOCAL_VENDOR_MODULE := true 安装到vendor中

LOCAL_PRODUCT_MODULE := true  安装到product中

常用编译命令

m

相当于在源码树的根目录执行make,并且该命令不一定要在根目录下执行

mm

编译当前目录路径下的所有模块(包括include进来的,但是不包括存在依赖关系模块)

mma

编译当前目录路径下的所有模块(包括include进来的,且包括存在依赖关系模块)

编译frameworks/base需要使用该命令,否则编译失败。

mmm[module_path]

编译指定目录路径下的所有模块(包括include进来的,但是不包括存在存在依赖关系模块)

mmma[module_path]

编译指定目录路径下的所有模块(包括include进来的,包括存在存在依赖关系模块)

cgrep

对C/C++文件执行grep(即grep的时候只搜寻C/C++文件类型,注意这里也包括.h文件类型)

jgrep

对Java文件执行grep(即grep的时候只搜寻Java文件类型)

————————————————
原文链接:https://blog.csdn.net/xiaopangcame/article/details/124774005
https://blog.csdn.net/liujun3512159/article/details/124562606
https://blog.csdn.net/paradox_1_0/article/details/94727750
https://blog.csdn.net/u012514113/article/details/125443131

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值