之前一直在学习基于Linux内核的一些字符型驱动的编程,对Linux内核驱动也算有了一些基本的了解吧,后来也做过一些基于Linux内核的驱动开发,像基于Android的CC1101高频模块的驱动开发,以及基于V4L2的USB摄像头开发。但是还是一直都没有用到过Android的HAL模块,这一次刚好在暑假,就好好系统学习一下Android的HAL了。
说到HAL,我想目前市面上关于这方面的书应该也有不少,或者随便到网上一搜,都是一大把。但是作为一个只了解了一点Linux驱动方面的知识,懂一点初级的C语言,要完全了解Android的HAL还是有一定困难的,下面我也就将我在这一块理解的一些心得同大家分享。
HAL(Hardware Abstraction Layer),中文是硬件抽象层,也就是说是对硬件的一种抽象。在我们之前所开发的Linux驱动程序当中,在编写好驱动程序之后会在/dev的目录下生成相应的设备文件,然后如果该驱动程序是应用在Android系统里面的话,可能还要编写相应的NDK(jni)部分,使之生成动态链接库(.so文件),以方便上层的java程序调用。整个模型如下图所示。
这个模型图的结构比较简单,也很清晰。我想大家也很清楚能够看懂。既然这样,为什么还要加入HAL呢,个人认为主要有以下几点原因:
协议方面的原因,因为我们知道所有的Linux程序都必须要遵循GPL协议,也就是全部开源协议。而对于有些企业和个人其并不想完全将自己的劳动成果或者是知识产权吧完全公开,所以就在Linux驱动程序的基础之上,在弄了一个HAL层。由于对于一个驱动程序来说,其主要包括两个部分,访问寄存器的代码和业务逻辑代码。而对于访问硬件寄存器的代码,并没有什么秘密可言,无非就是一些Linux内核向寄存器发号施令的标准函数(如ioread32、iowrite32等),所以一个驱动的核心部分应该是在业务逻辑代码上,而开发者将这些业务逻辑的代码放在HAL层,由于HAL层属于用户空间部分,所以并不要遵循Linux的GPL协议,因而HAL便得以广泛应用。
统一调用接口,我们知道Linux驱动程序的调用接口复杂,不统一,而HAL提供了标准的调用接口,这样很方便
针对一些特殊要求,例如有些硬件,可能需要访问用户空间的资源,而由于Linux驱动程序是放在内核空间的,所以加入存放用户空间的HAL有利于对用户空间资源的访问。
通过上面的一些分析,我想应该也大概对HAL有一些了解了,现在我给出整个HAL模型图。
看到这个图,大家一定会想,怎么又多了一个Service程序库过来呀,确实在HAL模型刚刚出来的那会儿,的确没有这么一个Service程序库部分。而没有Service程序的HAL构架虽然已经将访问寄存器的代码和业务逻辑代码区分开了,但是其仍然有很多的问题,就是其还是一个孤立与Android系统之外的一个部分,没有与Android系统本身融为一体,根本就没有发挥出HAL的强大优势。所以用于调用HAL程序库的Service层便出现了。
下面我就以一个非常常见的led灯的例子,来讲解HAL框架。
第一部分
Linux内核层(Linux内核驱动程序),相对于没有HAL框架的Linux内核驱动程序,有HAL框架的Linux驱动程序就显得结构比较简单了,无非就是对寄存器的一些简单操作。
#include "s3c6410_leds_hal.h"
#include "leds_hal_define.h"
static unsigned char mem[5]; // 第1个字节:GPM寄存器类型,后面4个字节保存GPM寄存器的值
static int major = S3C6410_LEDS_MAJOR;
static int minor = S3C6410_LEDS_MINOR;
static dev_t dev_number; // 设备号
static struct class *leds_class = NULL;
//将四个字节转换成int类型的数据,因为从用户空间传递过来的,都是以char数组形式传递的,而如果要在Linux内核使用int类型数据的话,就必须要有这么
//一步
// 只处理从start开始的4个字节,第start个字节为int的最高位
static int bytes_to_int(unsigned char buf[], int start)
{
int n = 0;
n = ((int) buf[start]) << 24 | ((int) buf[start + 1]) << 16
| ((int) buf[start + 2]) << 8 | ((int) buf[start + 3]);
return n;
}
//同样,在处理完用户空间传过来的参数之后,有需要将其转换为byte类型的数据,由于Buf为char类型数组,所以每一次只取低8位数据。
static void int_to_bytes(int n, unsigned char buf[], int start)
{
buf[start] = n >> 24;
buf[start + 1] = n >> 16;
buf[start + 2] = n >> 8;
buf[start + 3] = n;
}
// 向GPM寄存器写数据
static ssize_t s3c6410_leds_hal_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
if (copy_from_user(mem, buf, 5))
{
return -EFAULT;
}
else
{
int gpm_type = mem[0]; // 获取GPM寄存器类型,这里寄存器的类型放在了buf的第0位。
switch (gpm_type)
{
case S3C6410_LEDS_HAI_WRITE_GPMCON:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMCON); //iowrite32是Linux内核标准的库函数,用于向寄存器写数据
break;
case S3C6410_LEDS_HAI_WRITE_GPMPUD:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMPUD);
break;
case S3C6410_LEDS_HAI_WRITE_GPMDAT:
iowrite32(bytes_to_int(mem, 1), S3C64XX_GPMDAT);
break;
}
}
return 5;
}
// 向GPM寄存器写数据
static ssize_t s3c6410_leds_hal_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int gpm_type = mem[0]; // 获取GPM寄存器类型
int gpm_value = 0;
//关于S3C6410_LEDS_HAI_READ_GPMCON这些宏,定义在leds_hal_define.h头文件中
switch (gpm_type)
{
case S3C6410_LEDS_HAI_READ_GPMCON:
gpm_value = ioread32(S3C64XX_GPMCON);
break;
case S3C6410_LEDS_HAI_READ_GPMPUD:
gpm_value = ioread32(S3C64XX_GPMPUD);
break;
case S3C6410_LEDS_HAI_READ_GPMDAT:
gpm_value = ioread32(S3C64XX_GPMDAT);
break;
}
int_to_bytes(gpm_value, mem, 1);
if (copy_to_user(buf, (