基本架构
#include <hardware/hardware.h>
#include <fcntl.h>
#include <cutils/log.h>
//HAL规定不能直接使用hw_module_t结构体
//因此需要在hw_module_t外再套一层结构体,这也是HAL要求的
struct my_module_t
{
//hw_module_t结构体表示HAL模块的基本信息,成员变量可以任意起,
//但hw_module_t结构体必须是led_module_t结构体的第一个成员,因为可以直接拿地址进行类型转换
struct hw_module_t hw_module;
}
//自定义的结构体,该结构体的第1个成员变量的类型必须是hw_ device_t
//在该结构体中还需要定义控制设备的函数指针
struct my_device_t{
struct hw_device_t hw_device;
int (*自己的A函数指针成员)(my_device_t dev,int32_t i);
}
//为HAL模块定义一个ID,需要通过这个ID来查找HAL模块
#define XXX_HARDWARE_MODULE_ID "xxx_hal"
int A函数(my_device_t dev,int32_t i){
//……
}
static int 自己的open函数(const struct hw_module_t* module,const char* name,struct hw_device_t** device){
struct my_device_t *dev;
dev=(struct my_control_device_t*)malloc(sizeof(*dev));
memset(dev,0,sizeof(*dev));
dev->hw_device.tag=HARDWARE_DEVICE_TAG;
//
dev->hw_device.version=0;
dev->hw_device.module=0;
dev->hw_device.close=自己的close函数;
dev->自己的A函数指针成员=A函数;
*device=(hw_device_t*)dev;
//一些初始化操作……
return 0;
}
static struct hw_module_methods_t module_methods={
.open= 自己的open函数,
}
struct my_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = 1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = XXX_HARDWARE_MODULE_ID,
.name = "Example Hardware Module",
.author = "Example Author",
.methods = &module_methods,
};
hw_module_t是最先使用到的,然后通过hw_module_t.methods找到hw_module_methods_t.open函数,并调用该
函数。这个open函数相当于HAL模块的入口。一般会在这个函数里打开设备文件、初始化hw_device_t结构体以及一些控制硬件设备的函数
系统如何找到HAL_MODULE_INFO_SYM?
https://blog.csdn.net/badbayyj/article/details/115826477
Android.mk
编写完成后,还需要个Android.mk文件。
模板如下。
LOCAL PATH :$(call my-dir)
include $(SCLEAR VARS)
LOCAL_PRELINK_MODULE := faise
LOCAL_MODULE_PATH :=$(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES :=XXX_hal.c
LOCAL_MODULE :=XXX_hal.default
LOCAL_MODULE_TAGS :=eng
include $(BUILD_SHARED_LIBRARY)
使用mm编译后,将生成的.default.so文件push到板子的/system/lib/hw文件夹下即可使用,或者将其release到对应文件夹,打包生成image,烧录进去。
HAL Service
单单有.so库是不行的,还需要一个服务端来调用这个.default.so文件。
如果该Service是由JAVA调用的话,这个Service的对外接口要写成JNI函数。在安卓中的audio hal的server是采用一些IPC手段来编写的,并不是直接被JAVA调用。所以以下仅仅是个人笔记,可以不看。后续有机会会补充audio hal的结构。
JNI的模板如下:
#include<对应的hal.h>
//my_device_t结构体会通过open方法返回
struct my_device_t* my_hal_device=NULL;
//JNI函数
static jboolen 函数A(JNIEnv* env,jboject this,jint i)
{
//如果未成功获取my_device_t,直接返回
if(my_hal_device==Null)
{
return -1;
}else
{
//调用HAL层函数
return my_hal_device->自己的A函数指针成员(my_hal_device,i);
}
}
static inline int my_hal_open(const struct hw_module_t* module,
struct my_device_t** device)
{
//调用my_module_t.hw_module.mothods.open函数进行一些初始化工作
//open函数会获取my_device_t结构体
return module->methods->open(module,XXX_HARDWARE_MODULE_ID,
(struct hw_device_t**)device);
}
//JNI,在该方法中,会通过XXX_HARDWARE_MODULE_ID来找到对应的HAL模块。
//并通过my_hal_open来获取my_device_t结构体
static jboolean my_init(JNIEnv *env,jclass class)
{
my_module_t* module;
if(hw_get_module_open(&module->hw_module,&my_hal_device)==0)
{
return 0;
}
}
//定义JNI函数的映射
static const JNINativeMethod methods[]=
{
{"_init","()Z",(void*)my_init},
{"_set_on"}//todo
}
//将JNI程序库与java类绑定
int register_my_hal_jni(JNIEnv* env)
{
//必须由该类调用当前的JNI程序库
static const char* const kClassName="mobile/";//todo
jclass clazz;
//获取HalService类的jclass对象
clazz=env->FindClass(kClassName);
if(clazz==NULL)
{
return -1;
}
//调用RegisterNatives方法将函数(到JAVA类中就成为方法了)注册到MyHalService类中
//其中sizeof(methods)/sizeof(methods[0])是计算methods数组长度
//RegisterNatives方法的定义:jint RegisterNatives(jclass clazz,const JNINativemethod*)
//methods,jint nMethods)
//最后一个参数nMethods表示要映射的函数(方法)数量,也就是methods数组长度
if(env->RegisterNatives(clazz,methods,sizeof(methods)/sizeof(methods[0]))!=JNI_OK)
{
return -1;
}
return 0;//成功
}
//系统在成功装载JNI共享库后,会自动调用JNI_OnLoad函数,改函数一般用于初始化JNI模块
jint JNI_OnLoad(JavaVM* vm,void* reserved)
{
JNIEnv *env=NULL;
jint reselt=-1;
//获取JNIEnv结构体的指针,(还可以在这里判断一下J2SE版本)
//来说明当前模块对J2SE版本的要求。
if(vm->GetEnv((void**)&env,JNI_VERSION_1_4)!=JNI_OK)
{
return -1;
}
//将java类与JNI函数绑定
register_my_hal_jni(env);
//返回JNI_VERSION_1_4,表明只能运行在对应版本以上java
return JNI_VERSION_1_4;
}
HAL模块的存放路径和命名规则
HAL模块的存放路径和命名规则在Android系统中是经过精心设计的,以确保系统能够正确加载和使用这些模块。
存放路径
HAL模块的库文件(通常是.so文件)存放在以下两个路径之一:
- /system/lib/hw
- /vendor/lib/hw
hw_get_module函数会首先在/system/lib/hw目录中查找HAL模块的库文件。如果在该目录中未找到,则会继续在/vendor/lib/hw目录中查找。
命名规则
HAL模块库文件的命名规则是:ID.suffix.so
- ID:通过hw_get_module函数的id参数指定。例如,led_hal。
- suffix:后缀,通过属性文件指定。如果没有在属性文件中找到suffix,则使用默认的suffix,即default。
例如,假设ID是led_hal,那么库文件名可能是:led_hal.default.so
(默认情况)、led_hal.abc.so
(如果属性文件中定义了abc作为suffix)
属性文件
Android系统的属性文件用于定义各种系统属性和配置。HAL模块的suffix就是通过这些属性文件来确定的。以下是Android系统中用于定义属性的四个主要文件:
- /default.prop
- /system/build.prop
- /system/default.prop
- /data/local.prop
Android在启动时会自动加载这些属性文件。如果在多个属性文件中定义了相同的Key和Value,那么只会使用第一个被读取的Key。例如:如果在/default.prop文件中定义了ro.product.board的值为abc,而在/system/build.prop文件中定义了ro.product.board的值为xyz,那么hw_get_module函数会将/default.prop文件中的abc作为HAL模块库文件的后缀,而不会再读取/system/build.prop文件中的xyz。因此,HAL模块的库文件名将是led_hal.abc.so。
属性文件的定义
上述四个属性文件的定义可以在以下文件中找到:/working/android2.3.4_src/bionic/libc/include/sys/_system_properties.h
打开_system_properties.h文件后,可以看到定义了四个宏,分别对应上述四个属性文件。