本章主要讲解在Android的内核中编译硬件驱动程序,由于没有明确的GPIO口控制,因此这里不涉及硬件部分,我们使用一个虚拟硬件设备,并实现读写接口。
由于我现在内核的知识点还比较浅,因此内核的部分只是作为参考,主要目的,是为了尝试从APK到内核的一条通路实现过程。详细的内核需要等以后把Framework相关的知识补充完整后,再进一步学习。
这里创建一个虚拟硬件设备,命名为hello,即内核驱动的名称也为hello,用来进行读写寄存器的值。
1.进入到kernel/driers,并新建hello文件夹
Tianger:~ getianger$ cd /Developer/Android/kernel/drivers/
Tianger:drivers getianger$ mkdir hello
2.进入到hello目录,并创建hello.h,在hello.h中编写如下代码:
1 | #ifndef _HELLO_ANDROID_H_ |
这里定义了四个名称,分别为设备节点名称、设备文件名—通过传统的设备文件来访问、设备proc文件名–通过proc文件系统来访问、设备目录名称,这里都称为hello.
同时创建了一个字符设备结构体,包括val-设备寄存器;sem—信号量,用于同步访问寄存器val,dev—字符设备。
3.在hello目录中创建hello.c,用来实现驱动程序.
这里主要是为了向上层提供寄存器的读写接口,便于进行寄存器值得读与写。我们常用的读写设备方法是传统的设备访问方法—open、release、read、write这四个打开、释放、读取、写入文件的方法,这里我们还要提供另外两种方法,一种是通过proc文件系统来访问,另一种是通过devfs文件系统来访问。下面进入正题。
先定义三种设备的访问方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "hello.h"
/*主设备和从设备号变量*/
static int hello_major = 0;
static int hello_minor = 0;
/*设备类别和设备变量*/
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;
/*1.传统的设备文件操作方法*/
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, \
size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, \
size_t count, loff_t* f_pos);
/*2.devfs文件系统访问--设备文件操作方法表*/
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
/*2.devfs文件系统访问--访问设置属性方法-*/
static ssize_t hello_val_show(struct device* dev, \
struct device_attribute* attr, char* buf);
static ssize_t hello_val_store(struct device* dev, \
struct device_attribute* attr, const char* buf, size_t count);
/*定义设备属性*/
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
/*3.Proc文件系统访问操作方法*/
static ssize_t hello_proc_read(char* page, char** start, off_t off, int count,\
int* eof, void* data);
static ssize_t hello_proc_write(struct file* filp, const char __user *buff, \
unsigned long len, void* data);
static void hello_create_proc(void);
static void hello_remove_proc(void) ;
1)接下来开始先使用第一种访问方法—传统的设备文件访问方法。
通过hello_open \ hello_release \ hello_read \ hello_write 来进行文件的打开、释放、读取、写入操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80/*******************************************
* 1.传统设备访问方法 *
********************************************/
/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp) {
struct hello_android_dev* dev;
/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;
return 0;
}
/*设备文件释放时调用,我们这里使用虚拟是被,因此无需真正实发,只要返回空即可*/
static int hello_release(struct inode* inode, struct file* filp) {
return 0;
}
/*读取设备的寄存器val的值*/
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count,
loff_t* f_pos) {
ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;
/*同步访问*/
/*用来获取信号量,如果信号量大于或等于0,获取信号量,否则进入睡眠状态,等待信号量被释放后,激活该程*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count < sizeof(dev->val)) {
goto out;
}
/*将寄存器val的值拷贝到用户提供的缓冲区*/
/*从内核空间读取val数值到用户空间*/
if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
/*释放信号量*/
up(&(dev->sem));
return err;
}
/*写设备的寄存器值val*/
static ssize_t hello_write(struct file* filp, const char __user *buf,
size_t count, loff_t* f_pos) {
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;
/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count != sizeof(dev->val)) {
goto out;
}
/*将用户提供的缓冲区的值写到设备寄存器去*/
/*将用户空间的数据val,写入到内核空间中*/
if(copy_from_user(&(dev->val), buf, count)) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
/*释放信号量*/
up(&(dev->sem));
return err;
}
2)通过devfs文件系统访问方法
这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法hello_get_val和hello_set_val:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/*******************************************
* 2.devfs文件系统访问方法 *
********************************************/
/*读取寄存器val的值到缓冲区buf中,内部使用*/
static ssize_t __hello_get_val(struct hello_android_dev* dev, char* buf) {
int val = 0;
/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
val = dev->val;
/*释放信号量*/
up(&(dev->sem));
/*把val的值写入到buf中*/
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
/*把缓冲区buf的值写到设备寄存器val中去,内部使用*/
static ssize_t __hello_set_val(struct hello_android_dev* dev,
const char* buf, size_t count) {
int val = 0;
/*将字符串转换成数字*/
val = simple_strtol(buf, NULL, 10);
/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
/*把需要写入的val的值,传入到dev->val中*/
dev->val = val;
up(&(dev->sem));
return count;
}
/*读取设备属性val*/
static ssize_t hello_val_show(struct device* dev,
struct device_attribute* attr, char* buf) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_get_val(hdev, buf);
}
/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev,
struct device_attribute* attr, const char* buf, size_t count) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_set_val(hdev, buf, count);
}
3)通过proc文件系统访问方法
主要实现了hello_proc_read和hello_proc_write两个方法,同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/*******************************************
* 3.proc文件系统访问方法 *
********************************************/
/*读取设备寄存器val的值,保存在page缓冲区中*/
static ssize_t hello_proc_read(char* page, char** start, off_t off,
int count, int* eof, void* data) {
if(off > 0) {
*eof = 1;
return 0;
}
return __hello_get_val(hello_dev, page);
}
/*把缓冲区的值buff保存到设备寄存器val中去*/
static ssize_t hello_proc_write(struct file* filp, const char __user *buff,
unsigned long len, void* data) {
int err = 0;
char* page = NULL;
if(len > PAGE_SIZE) {
printk(KERN_ALERT"The buff is too large: %lu.\n", len);
return -EFAULT;
}
page = (char*)__get_free_page(GFP_KERNEL);
if(!page) {
printk(KERN_ALERT"Failed to alloc page.\n");
return -ENOMEM;
}
/*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/
if(copy_from_user(page, buff, len)) {
printk(KERN_ALERT"Failed to copy buff from user.\n");
err = -EFAULT;
goto out;
}
err = __hello_set_val(hello_dev, page, len);
out:
free_page((unsigned long)page);
return err;
}
/*创建/proc/hello文件*/
static void hello_create_proc(void) {
struct proc_dir_entry* entry;
entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
if(entry) {
entry->owner = THIS_MODULE;
entry->read_proc = hello_proc_read;
entry->write_proc = hello_proc_write;
}
}
/*删除/proc/hello文件*/
static void hello_remove_proc(void) {
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}
4)最后,实现模块加载和卸载方法,这里只要是执行设备注册和初始化操作:
1 | /******************************************* |
4.驱动程序写完成了,接下来我们进入到编译的流程:
1)在hello目录中新建两个文件Kconfig\Makefile
2)在Makefile中假如下列两行
obj-$(CONFIG_HELLO) += hello.o
obj-$(CONFIG_HELLO) += hello/3)kernel/drivers目录和目录kernel\arch\arm中修改Kconfig
在menu “Device Drivers”和endmenu之间添加一行:
source “drivers/hello/Kconfig”4)修改arch/arm/Kconfig
在menu “Device Drivers”和endmenu之间添加一行:
source “drivers/hello/Kconfig”5)进入到kernel目录中,输入命令make menuconfig
在弹出的界面中选择”Device Drivers”,进入到二级界面,选择”First Android Drivers”,输入y,并选择save,现在exit,一直退出menuconfig的界面6)cd kernel目录,输入make进行编译
编译成功后,我们可以在hello目录中看到我们的hello.o文件,并且产生的image中已经包含了hello驱动,把这个image下载到手机或者使用模拟器打开这个image就可以进行验证,至此,我们的hello驱动程序编写完成。