Android系统对硬件设备的支持是分两层的。一层实现在内核空间中(只有内核空间才有特权操作硬件设备),另一层实现在用户空间中。用户空间中的这一层就是HAL层,也就是Hardware Abstract Layer层。而传统的Linux系统中,对硬件的支持是完全实现在内核空间中的,即把对硬件的支持完全实现在硬件驱动模块中。
之所以这么设计,是为了保护硬件厂商的利益。因为Linux内核源码是遵循GPL协议的,如果硬件厂商把对硬件的支持完全实现在硬件驱动模块中,那么就必须将硬件驱动模块的源代码全部公开,这相当于暴露了硬件的实现细节和参数。
因此Android的做法是在内核空间以硬件驱动模块的形式来仅仅提供简单的硬件访问通道,而用户空间以硬件抽象模块的形式封装硬件的实现细节和参数。我的理解是,驱动层仅仅提供硬件寄存器的读写操作,如getValue()和setValue()。而究竟按什么顺序读写,读写什么内容,则是放在硬件抽象层的。
除了上述的作用外,硬件抽象层的模块接口还定义了统一的规范。使得上层系统能够以一致的方式获取到硬件抽象模块的句柄hw_module_t以及虚拟硬件设备句柄hw_device_t。
举个例子
我们以一个具有一个4字节大小寄存器的虚拟字符硬件设备为例,观察下虚拟字符设备定义,内核驱动编写,硬件抽象层模块编写到实际的加载与使用的整个过程。我们将该设备命名为fake_reg,对应的驱动程序名为freg。
1、开发内核空间驱动层
freg.h
...
struct fake_reg_dev {
int val; //虚拟寄存器
struct semaphore sem; //信号量,用来同步访问虚拟寄存器val
struct cdev dev; //标准的Linux字符设备结构体变量
}
...
freg.c
...
//设备文件操作方法
static int freg_open(struct inode* inode, struct file* filp);
static int reg_release(struct inode* inode, struct file* filp);
static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loft_t * f_pos);
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loft_t * f_pos);
//设备文件操作方法表
static struct file_operations freg_fops = {
.owner = THIS_MODULE,
.open = freg_open,
.release = freg_release,
.read = freg_read,
.write = freg_write,
};
//freg_open的主要作用就是自定义设备结构体保存在文件指针的私有数据域中,方便之后访问设备时可
//以直接拿来用。而文件句柄fd和filp是绑定的。因此获取打开的文件句柄,就可以访问到fake_reg_dev
static int freg_open(struct inode* inode, struct file* filp) {
struct fake_reg_dev* dev;
dev = container_of(inode->i_cdev, struct fake_reg_dev, dev);
filp->private_data = dev;
return 0;
}
static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loft_t* f_pos) {
...
struct fake_reg_dev* dev = filp->private-data;
copy_to_user(buf, &(dev->val), sizeof(dev->val));
...
}
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loft_t* f_pos) {
...
struct fake_reg_dev* dev = filp->private-data;
copy_from_user(&(dev->val), buf, count)
...
}
...
在初始化硬件设备的时候,将freg_fops赋值给fake_reg_dev的cdev dev中的ops变量。那么之后在用户空间调用
fd = open("/dev/freg", O_RDWR);
read(fd, &val, sizeof(val));
write(fd, &val, sizeof(val));
时,则会触发内核空间中的freg_open, freg_read以及freg_write方法被调用。从而完成硬件设备的访问。
硬件设备初始化
static int __freg_setup_dev(struct fake_reg_dev* dev) {
...
memeset(dev, 0, sizeof(struct fake_reg_dev));
cdev_init(&(dev->dev), &freg_fops);
dev->dev.owner = THIS_MODULE;
//将设备文件操作方法表赋给标准字符设备结构体的ops变量
dev->dev.ops = &freg_fops;
init_MUTEXT($(dev->sem));
dev->val = 0;
...
}
忽略掉各种细节以后,以上就是一个虚拟硬件设备的完整定义和驱动。将代码编译打包进Android内核镜像文件后就可以使用了。
2、开发硬件抽象层模块
在第一节中我们定义了一个虚拟字符设备fake_reg,以及相应的驱动程序freg。我们已经可以通过read和write指令去读写其虚拟寄存器val的值。但是用这个寄存器存取什么值,这个值用来做什么,我们还没有定义。这就是我们在硬件抽象层模块中需要做的。硬件抽象层模块最终被会编译成.so文件,通过加载该文件,我们就能够获取到硬件抽象层模块的句柄,也就是后文提到的hw_module_t的地址。
硬件抽象层模块名称与其对应的.so文件位置有对应的关系,因此我们仅需要知道名称就能打开一个设备。
2.1 硬件抽象层模块编写规范
硬件抽象层规范有两个重要的结构体:hw_module_t以及hw_device_t,分别对应硬件抽象层模块和其包含的硬件设备。一个hw_module_t可以包含多个hw_device_t。
#define MAKE_TAG_CONSTANT(A,B,C,D) ((A) << 24) | ((B) << 16) | ((C) << 8) | (D))
#define HARDWARE_MODULE_TAG MAKE_TAG_CONSTANT('H', 'W', 'M', 'T')
//每个硬件抽象层模块的定义都必须包含一个名为HAL_MODULE_INFO_SYM的数据结构,并且该数据结构第一个成员变量的类型必须是hw_module_t。在打开模块对应的.so文件后就可以调用dlsym函数通过HAL_MODULE_INFO_SYM的字面值找到该结构体的地址。由于该结构体的第一个元素必须是hw_module_t,于是我们其实也就拿到了hw_module_t的地址。
#define HAL_MODULE_INFO_SYM HMI
typedef struct hw_modult_t {
...
uint32 tag; //必须被初始化为HARDWARE_MODULE_TAG
const char *id; //硬件抽象层模块的id
const char *name; //硬件抽象层模块的名字
struct hw_module_methods_t *methods; //模块提供的方法
void* dso; //调用dlopen函数打开此模块对应的.so文件时获得的句柄,关闭的时候使用
...
} hw_module_t;
typedf struct hw_module_methods_t {
//打开module模块中对应id值的硬件设备,device为输出参数,用来描述一个已经打开的硬件设备
int (*open) (const struct hw_module_t* module, const char* id, struct hw_device_t** device);
} hw_module_methods_t;
#define HARDWARE_DEVICE_TAG MAKE_TAG_CONSTANT('H', 'W', 'D', 'T')
//每个硬件设备都必须定义一个硬件设备结构体,且第一个成员变量的类型必须为hw_device_t
typedef struct hw_device_t {
...
uint32_t tag; //必须被初始化为HARDWARE_DEVICE_TAG
struct hw_module_t* module; //硬件设备所属于的硬件抽象层模块
int (*close) (struct hw_device_t* device); //硬件设备的打开由hw_module_methods_t的open完成,而关闭是由硬件设备自身完成的。
...
} hw_device_t;
以上就是硬件抽象层模块以及硬件设备的编写规范。总结一下就是必须定义两个自定义结构体以及一个hw_module_methods_t结构体。
一个自定义结构体名字固定为HAL_MODULE_INFO_SYM,且其第一个元素的类型必须是hw_module_t类型。
一个自定义结构体名字不指定,但是其第一个元素的类型必须是hw_device_t类型。
接下来我们按照规范编写之前定义的fake_reg设备的硬件抽象层模块接口。
2.2 编写硬件抽象层模块接口
freg_module.h
...
#define FREG_HARDWARE_MODULE_ID "freg" //模块id
#define FREG_HARDWARE_DEVICE_ID "freg" //设备id
//声明自定义模块结构体,后面会看到freg_module_t的实例名字为HAL_MODULE_INFO_SYM
struct freg_module_t {
struct hw_module_t common; //第一个元素必须为hw_module_t类型
}
//自定义设备结构体
struct freg_device_t {
struct hw_device_t common;
int fd; //虚拟硬件设备的文件描述符
int (*set_val) (struct freg_device_t* dev, int val); //初始化时会被赋值为下文中定义的freg_set_val
int (*get_val) (struct freg_device_t* dev, int* val); //初始化时会被赋值为下文中定义的freg_get_val
}
...
freg_module.cpp
...
#define DEVICE_NAME "/dev/freg/"
#define MODULE_NAME "Freg"
static int freg_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
static int freg_device_close(struct hw_device_t* device);
static int freg_get_val(struct freg_device_t* dev, int* val);
static int freg_set_val(struct freg_device_t* dev, int val);
static struct hw_module_methods_t freg_module_methods = {
open:freg_device_open
}
//自定义模块结构体,且名字必须为HAL_MODULE_INFO_SYM
struct freg_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG, //固定值
version_major: 1, //主版本号
version_minor: 0, //次版本号
id: FREG_HARDWARE_MODULE_ID, //值为"freg"
name: MODULE_NAME; //值为"Freg"
methods: &freg_module_methods;
}
};
...
以上定义了一个名为HAL_MODULE_INFO,类型为自定义freg_module_t类型的结构体,其第一个元素common为hw_module_t类型,hw_module_methods_t成员变量定义了freg_device_open方法。
定义了一个freg_device_t结构体,其第一个元素common为hw_device_t类型。fd是该设备对应的文件句柄。set_val以及get_val函数指针对应着freg_get_val以及freg_set_val方法。
这样,当我们通过dlopen函数和dlsym函数获取到hw_module_t指针后,就可以调用其open函数打开对应的freg_device_t设备,从而调用set_val和get_val函数。完成对硬件设备的操作。
接下来,我们看下freg_device_open, freg_set_val, freg_get_val的具体实现。
static int freg_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
...
struct freg_device_t* dev;
dev->common.tag = HARDWARE_DEVICE_TAG; //给hw_device_t类型的common的成员变量赋值
dev->common.version = 0;
dev->common.module = (hw_module_t*) module;
dev->common.close = freg_device_close;
dev->set_val = freg_set_val;
dev->get_val = freg_get_val;
dev->fd = open(DEVICE_NAME, O_RDWR); //这里会调用第1节中定义在内核空间的驱动层的freg_fops的freg_open函数
*device = &(dev->common); //将赋好值的freg_device_t的common成员赋给输出参数device.
return 0;
...
}
static int freg_get_val(struct freg_device_t* dev, int* val) {
...
read(dev->fd, val, sizeof(*val)); //会触发第1节中定义在内核空间的驱动层的freg_fops的freg_read函数
...
}
static int freg_set_val(struct freg_device_t* dev, int val) {\
...
write(dev->fd, &val, sizeof(val)); //会触发第1节中定义在内核空间的驱动层的freg_fops的freg_write函数
...
}
由上可见,获取到hw_module_t句柄后,便可以通过其成员hw_module_methods_t中定义的freg_device_open打开对应的硬件设备,获得freg_device_t句柄。从而通过其set_val和get_val来触发之前在内核空间中编写的驱动程序来访问硬件设备。
而其实质还是通过硬件设备文件描述符dev->fd进行read和write操作。freg_device_t的主要作用就是持有这个句柄。
那么接下来,关键就是如何加载硬件驱动层模块并获取到这个hw_module_t句柄。
2.3 硬件抽象层模块的加载
由于硬件抽象层模块对应的.so文件的路径是固定的。因此只需要提供模块ID,就能够找到对应的.so文件。其函数原型为
int hw_get_module(const char* id, const struct hw_module_t **module) {
...
load(id, path, module); //path是根据id按照一定的规则生成的路径。一个典型值为/system/lib/hw/freg.goldfish.so
....
}
static int load(const char* id, const char* path, const struct hw_module_t **pHmi) {
...
void *handle;
struct hw_module_t *hmi;
handle = dlopen(path, RTLD_NOW);
const char *sym = HAL_MODULE_INFO_SYM_AS_STR; //在2.1节硬件抽象层模块规范中定义了宏HAL_MODULE_INFO_SYM HMI;
hmi = (struct hw_module_t *)dlsym(handle, sym); //HAL_MODULE_INFO结构体的第一个成员必须是hw_module_t类型,因此可以直接转型
hmi->dso = handle;
*pHmi = hmi;
...
}
于是通过hw_get_module函数,我们就成功的获取到了指定硬件抽象层模块的hw_module_t的句柄,从而可以调用freg_device_open方法打开指定的硬件设备获得freg_device_t句柄,从而可以以freg_device_t为参数调用set_val以及get_val函数对硬件设备进行访问。、
总结
1、Android对于硬件设备的支持分为两部分。
一是内核空间层的驱动部分,封装了对硬件设备的简单存取逻辑。其中驱动的编写的步骤为
(1) 为fake_reg_dev结构体的cdev标准字符设备结构体的ops变量赋值。其类型为file_operations类型。
(2) 实现file_operations中的open, release, read, write指向的函数体,其中read, write的实现主要依靠copy_from_user以及copy_to_user两个函数,来实现用户空间和内核空间之间的数值传递。
(3)调用文件操作函数open, read以及write时,则会触发我们在(2)中指向的函数。
二是硬件抽象层模块部分,封装了对于硬件设备的业务操作逻辑部分。其关键步骤为
(1)按照规范自定义硬件抽象层模块结构体,其名字必须为HAL_MODULE_INFO_SYM,其第一个成员变量必须为hw_module_t类型。
(2)实现hw_module_t中hw_module_methods_t成员变量的open函数。打开指定id的硬件设备。
(3)按照规范自定义硬件设备结构体,其第一个成员变量必须是hw_device_t类型。紧跟着硬件设备操作的函数
(4)调用hw_get_module函数获取到指定id的硬件抽象层模块的hw_module_t句柄,调用open打开指定id的硬件设备,获得freg_device_t句柄。调用freg_device_t中定义的业务逻辑方法。
至此,整个硬件加载和访问流程就结束了。
这里有个需要注意的点是:freg_device_t和hw_device_t的地址,以及freg_module_t和hw_module_t的地址是一样的。所以freg_device_t和hw_device_t是可以互相强制转型的,freg_module_t和hw_module_t也是如此。
本文的代码示例都来源于老罗的《Android系统源代码情景分析》第二章硬件抽象层,只截取了关键代码,初始化以及错误处理部分进行了省略。如需进一步了解详情可以查看该书相关章节。