一、 预备知识及说明:
1、硬件抽象层模块向上层提供接口,以便用户空间的程序通过该模块提供的接口访问内核空间的驱动程序。
2、硬件抽象层模块是动态链接库文件(.so),它不属于内核层的,它是属于android系统Linux内核层的上层,因此它的源文件不能放在goldfish里。
3、硬件抽象层模块的头文件源代码放在 /android/hardware/libhardware/include/hardware目录下
实现文件源代码放在/android/hardware/libhardware/module/目录下
生成的动态链接库.so文件,放在/android/out/target/product/gerneric/system/lib/hw/目录下
4、本例的硬件抽象层模块,是作为前例中的 /dev/testdev这个字符设备驱动的硬件抽象层模块来实现的
二、在/android/hardware/libhardware/include/hardware目录下,添加test.h头文件
#ifndef ANDROID_TEST_INTERFACE_H
#define ANDROID_TEST_INTERFACE_H
#include <hardware/hardware.h>
//扩充C语言在编译的时候按照C++编译器进行统一处理
__BEGIN_DECLS
//按照硬件抽象层模块规范要求,定义模块ID,
#define TEST_HARDWARE_MODULE_ID "test"
//按照规范要求,定义硬件抽象层模块结构体,代表硬件抽象层模块
struct test_module_t {
struct hw_module_t common; //硬件抽象层标准硬件模块结构体
};
//按照规范要求,定义硬件接口结构体,代表硬件设备
struct test_device_t {
struct hw_device_t common; //标准的硬件接口
int fd; //设备文件描述符
//接口函数,供上层调用
int (*set_val)(struct test_device_t* dev, int val);
int (*get_val)(struct test_device_t* dev, int* val);
};
__END_DECLS
#endif
说明:硬件抽象层模块头文件编写规范要求必须包含:模块ID、模块本身、模块接口三部分,具体如下:
1、硬件模块ID: XXX_HARDWARE_MODULE_ID
2、硬件模块结构体:xxx_module_t,结构体中必须包含硬件抽象层标准模块结构:hw_module_t
3、硬件设备结构体:xxx_device_t ,结构体中包含标准的硬件设备结构(hw_device_t )、硬件设备接口、硬件设备文件描述符
三、在/android/hardware/libhardware/module/目录下,创建test目录,添加test.c文件
test.c文件如下:
//hardware.h是HAL模块必须包含的头文件
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <hardware/test.h>
//定义该模块在android系统日志中显示的标签为TestStub
#define LOG_TAG "TestStub"
//对应的硬件设备文件名
#define DEVICE_NAME "/dev/testdev"
//本模块名
#define MODULE_NAME "Test"
//模块开发者
#define MODULE_AUTHOR "tongruan"
//设备打开和关闭接口
static int test_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
static int test_device_close(struct hw_device_t* device);
//设备访问接口
static int test_set_val(struct test_device_t* dev, int val);
static int test_get_val(struct test_device_t* dev, int* val);
//模块方法表: 列表中只列出了打开函数
static struct hw_module_methods_t test_module_methods = {
open: test_device_open
};
//模块实例变量:实例化模块,并赋初值
//变量名必须为HAL_MODULE_INFO_SYM,这是强制要求的,你要写Android的HAL就得遵循这个游戏规则
struct test_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG, //硬件模块标签
version_major: 1, //版本号
version_minor: 0,
id: TEST_HARDWARE_MODULE_ID, //模块ID,在头文件中定义
name: MODULE_NAME, //模块名
author: MODULE_AUTHOR, //模块作者
methods: &test_module_methods, //模块方法表
}
};
说明:
上面有HAL_MODULE_INFO_SYM变量的成员common中包含一个模块方法表test_module_methods。那么这个模块方法表是不是就是HAL向上层提供函数的地方呢?很失望,不是在这里,向上层提供的接口函数是在头文件的test_device_t中定义的,这个test_module_methods实际上只提供了一个open函数,就相当于只提供了一个模块初始化函数
.那么HAL又到底是怎么将test_device_t中定义的接口提供到上层去的呢?
且看下面模块方法表中唯一的一个函数test_device_open实现:
//设备打开初始化函数实现:添加向上层提供的接口函数
static int test_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
{
//硬件设备定义、分配内存空间、初始化
struct test_device_t* dev;
dev = (struct test_device_t*)malloc(sizeof(struct test_device_t));
if(!dev){LOGE("TEST Stub: failed to alloc space"); return -EFAULT;}
memset(dev, 0, sizeof(struct test_device_t));
//给硬件设备的成员变量(hw_device_t common)赋值
dev->common.tag = HARDWARE_DEVICE_TAG; //硬件设备标签
dev->common.version = 0; //版本号
dev->common.module = (hw_module_t*)module; //该硬件设备对应的模块
dev->common.close = test_device_close; //设备的关闭函数
//******原来硬件的接口函数在此指明*****//
dev->set_val = test_set_val;
dev->get_val = test_get_val;
//硬件设备的文件描述符在此获取,同设备文件/dev/testdev在此关联在一起
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1)
{
LOGE("Test Stub: failed to open /dev/testdev-- %s.", strerror(errno));free(dev);
return -EFAULT;
}
//自定义设备test_device_t里的hw_device_t common的指针传给参数
*device = &(dev->common);
LOGI("Test Stub: open /dev/testdev successfully.");
return 0;
}
HAL到底是怎么将test_device_t中定义的接口提供到上层去的呢?
回答:是经过模块初始化函数test_device_open!!!
那么下面,我们只要实现test_device_close、test_set_val、test_set_val,就完成了硬件抽象层模块的开发!
//关闭设备函数
static int test_device_close(struct hw_device_t* device)
{
struct test_device_t* test_device = (struct test_device_t*)device;
if(test_device)
{
close(test_device->fd);//关闭设备文件,对应打开设备文件
free(test_device); //释放给硬件设备分配的空间
}
return 0;
}
//写入接口函数
static int test_set_val(struct test_device_t* dev, int val)
{
LOGI("Test Stub: set value %d to device.", val);
write(dev->fd, &val, sizeof(val));
return 0;
}
//读取接口函数
static int test_get_val(struct test_device_t* dev, int* val)
{
if(!val)
{
LOGE("Test Stub: error val pointer");
return -EFAULT;
}
read(dev->fd, val, sizeof(*val));
LOGI("Test Stub: get value %d from device", *val);
return 0;
}
四. 在test目录下新建Android.mk文件:
同前面的C语言测试程序的Android.mk文件类似,那个编译类型是可执行文件,这个是动态链接库文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := test.c
LOCAL_MODULE := test.default
include $(BUILD_SHARED_LIBRARY)
注意,LOCAL_MODULE的定义规则,test后面跟有default,test.default能够保证我们的模块总能被硬象抽象层加载到。
五. 编译:
//执行shell命令,获取mmm编译工具
~/Android$. ./build/envsetup.sh
//用mmm编译工具编译
~/Android$ mmm hardware/libhardware/modules/test
编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到test.default.so文件了。
六. 重新打包Android系统镜像system.img:
USER-NAME@MACHINE-NAME:~/Android$ make snod
重新打包后,system.img就包含我们定义的硬件抽象层模块test.default了。