linux设备驱动归纳总结(五):4.写个简单的LED驱动
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在上面的章节的知识,已经能够实现个简单的LED驱动。居于前面操作LED的函数(5th_mm_2/3rd/test.c),我一步一步来修改。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、实现硬件操作函数
一般的,我写驱动的时候,我会先确定一些基本的硬件操作函数能够使用。如LED驱动我要实现三个操作:配置、开灯和关灯,所以我先要实现这几个硬件操作函数。
其实这些在我介绍IO内存时已经实现了(5th_mm_2/3rd/test.c),我只是稍作了一点修改,改了一下内存的数据类型,其实没什么大出入。
/*5th_mm_4/1st/test.c*/
1
#include
2
#include
3
4
#include
5
#include
6
7
unsigned long virt, phys;
8
unsigned long gpecon, gpedat, gpeup; //其实我就改了这里的数据类型,其实都是用来存放地址
9
unsigned long reg;//没有多大的影响。
10
struct resource *led_resource;
11
12
void s3c_led_config(void) //还将函数的名字改成好听点
13
{
14
reg = ioread32(gpecon);
15
reg &= ~(3 << 24);
16
reg |= (1 << 24);
17
iowrite32(reg, gpecon);
18
19
reg = ioread32(gpeup);
20
reg &= ~(3 << 12);
21
iowrite32(reg, gpeup);
22
}
23
24
void s3c_led_on(void)
25
{
26
reg = ioread32(gpedat);
27
reg &= ~(1 << 12);
28
iowrite32(reg, gpedat);
29
}
30
31
void s3c_led_off(void)
32
{
33
reg = ioread32(gpedat);
34
reg |= (1 << 12);
35
iowrite32(reg, gpedat);
36
}
37
38
void init_led_device(void)
39
{
40
phys = 0x56000000;
41
virt = (unsigned long)ioremap(phys, 0x0c);
42
43
gpecon = virt + 0x40;
44
gpedat = virt + 0x44;
45
gpeup = virt + 0x48;
46
}
47
48
static int __init test_init(void) //模块初始化函数
49
{
50
init_led_device();
51
52
led_resource = request_mem_region(phys, 0x0c, "LED_MEM");
53
if(NULL == led_resource){
54
printk("request mem error!\n");
55
return - ENOMEM;
56
}
57
58
s3c_led_config();
59
s3c_led_on();
60
printk("hello led!\n");
61
return 0;
62
}
63
64
static void __exit test_exit(void) //模块卸载函数
65
{
66
if(NULL != led_resource){
67
s3c_led_off();
68
iounmap((void *)virt);
69
release_mem_region(phys, 0x0c);
70
}
71
printk("bye\n");
72
}
73
74
module_init(test_init);
75
module_exit(test_exit);
76
77
MODULE_LICENSE("GPL");
78
MODULE_AUTHOR("xoao bai");
79
MODULE_VERSION("v0.1");
至于验证我就不做了,效果还是一样,加载模块灯亮,卸载模块灯灭。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、面向对象思想——定义一个LED的结构体
上面的函数中,一大堆的全局变量实在让人看起来不舒服。我在第三章字符设备的文中介绍过,把这些变量定义在一个结构体中,方便以后引用,如函数传参。
/*5th_mm_4/2nd/test.c*/
7
struct _led_t{
8
//hardware obb
9
unsigned long virt, phys;
10
unsigned long gpecon, gpedat, gpeup;
11
unsigned long reg;
12
struct resource *led_resource;
13
14
void (*config)(struct _led_t *);//这里把LED驱动的三个操作函数指针也放进去
15
void (*on)(struct _led_t *);
16
void (*off)(struct _led_t *);
17
};
根据上面定义的数据结构,我再修改一下1st目录的程序,就成了2nd目录中的函数。现在函数做了两步:
1)实现硬件的基本操作。
2)定义了一个面向对象数据类型。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、实现硬件设备初始化函数和注销函数
在对硬件进程操作(配置,开灯、关灯)之前,需要先进行IO内存映射等操作,前面的函数写得很零散,这里我整理了一下:
1)当插入模块时,需要进行一些内存映射等设备初始化操作,使用函数init_led_device。
2)当卸载模块时,需要进行一些硬件注销操作,使用函数eixt_led_device。
接下来就要封装这两个函数:
/*5th_mm_4/3rd/test.c
*/
45
int init_led_device(struct _led_t *led)
46
{
47
led->phys = 0x56000000;//1指定物理地址
48
49
led->led_resource = request_mem_region(led->phys, 0x0c,
"LED_MEM");
50
if(NULL == led->led_resource){//2申请内存区域
51
return - 1;
52
}
53
54
led->virt = (unsigned long)ioremap(led->phys,
0x0c);//3内存映射
55
56
led->gpecon = led->virt + 0x40;//4指定寄存器地址
57
led->gpedat = led->virt + 0x44;
58
led->gpeup = led->virt + 0x48;
59
60
led->config = s3c_led_config;//5将操作函数也放进结构体成员
61
led->on = s3c_led_on;
62
led->off = s3c_led_off;
63
64
return 0;
65
}
66
67
void exit_led_device(struct _led_t *led)
68
{
69
if(NULL != led->led_resource){
70iounmap((void
*)led->virt);
71release_mem_region(led->phys,
0x0c);
72
}
73
}
74
75
struct _led_t my_led;
76
77
static int __init test_init(void) //模块初始化函数
78
{
79
if (-1 == init_led_device(&my_led)){
//加载模块时就调用init_led_device
80
printk("request mem error!\n");
81
return - ENOMEM;
82
}
83
84my_led.config(&my_led); //这里调用操作函数是多于了,我迟点会放在ioctl中
85my_led.on(&my_led);
//这里只不过加载时候灯亮一下,让我知道加载成功
86
printk("hello led!\n");
87
return 0;
88
}
89
90
static void __exit test_exit(void) //模块卸载函数
91
{
92
my_led.off(&my_led);
93
exit_led_device(&my_led); //卸载时调用exit_led_device
94
printk("bye\n");
95
}
至于验证我就不做了,效果还是一样,加载模块灯亮,卸载模块灯灭。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、实现字符设备的申请,即模块与内核的接口
需要实现ioctl功能,首先要这个设备需要先注册,使用字符设备注册的知识:
字符设备注册三步曲:
/*5th_mm_4/4th/test.c*/
18
struct _led_t{
19
//hardware obb
20
unsigned long virt, phys;
21
unsigned long gpecon, gpedat, gpeup;
22
unsigned long reg;
23
struct resource *led_resource;
24
25
void (*config)(struct _led_t *);
26
void (*on)(struct _led_t *);
27
void (*off)(struct _led_t *);
28
29
//kernel oob
30
dev_t devno; //往结构体添加了两个成员
31
struct cdev led_cdev;
32
};
。。。。。。
90
struct _led_t my_led;
91
struct file_operations s3c_led_fops = {
92
//暂时还是空的
93
};
94
95
static int __init led_driver__init(void) //模块初始化函数
96
{
97
int ret;
98
99
ret = init_led_device(&my_led);
100
if (ret){
101
P_DEBUG("request mem error!\n");
102
ret = - ENOMEM;
103
goto err0;
104
}
105
106ret
= alloc_chrdev_region(&my_led.devno, 0, 1, "s3c_led_driver");
//1申请cdev
107
if (ret){
108
P_DEBUG("alloc chrdev failed!\n");
109
goto err1;
110
}
111
P_DEBUG("major[%d], minor[%d]\n", MAJOR(my_led.devno),
MINOR(my_led.devno));
112
113
cdev_init(&my_led.led_cdev, &s3c_led_fops); //2初始化cdev
114
115
ret = cdev_add(&my_led.led_cdev, my_led.devno, 1);
//3添加cdev
116
if (ret){
117
P_DEBUG("cdev_add failed!\n");
118
goto err2;
119
}
120
121
my_led.config(&my_led);
122
my_led.on(&my_led);
123
P_DEBUG("hello led!\n");
124
return 0;
125
126
err2:
127
unregister_chrdev_region(my_led.devno, 1);
128
err1:
129
exit_led_device(&my_led);
130
err0:
131
return ret;
132
}
133
134
static void __exit led_driver__exit(void) //模块卸载函数
135
{
136
my_led.off(&my_led);
137
138
unregister_chrdev_region(my_led.devno, 1); //卸载是注销cdev结构
139
exit_led_device(&my_led);
140
P_DEBUG("bye\n");
141
}
这里就可以验证一下了:
[root:
4th]# insmod test.ko
[led_driver__init]major[253],
minor[0]//申请成功的设备号
[led_driver__init]hello
led!
[root:
4th]# rmmod test
[led_driver__exit]bye
既然设备申请成功,接下来就是要实现系统调用接口了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、实现系统调用对应的函数ioctl
在这里,我需要实现的内容是,在应用层使用ioctl系统调用,可以操作LED配置、打开和关闭。接下来实现文件操作结构体中的ioctl。
1、首先要定义命令:
/*5th_mm_4/5th/led_ioctl.h*/
1
#ifndef _LED_H
2
#define _LED_H
3
4
#define LED_MAGIC 'x'
5
#define LED_CONF _IO(LED_MAGIC, 0)
6
#define LED_ON _IO(LED_MAGIC, 1)
7
#define LED_OFF _IO(LED_MAGIC, 2)
8
9
#endif /* _LED_H */
2、接着实现文件操作结构体中的ioctl:
/*5th_mm_4/5th/led_driver.c
*///这里我把文件的名字改了
92
int s3c_led_ioctl(struct inode *node, struct file *filp, unsigned int
cmd, unsign ed long args)
93
{
94
int ret;
95
struct _led_t *dev = container_of(node->i_cdev, struct _led_t,
led_cdev);
96
switch(cmd){
97
case LED_CONF:
98
dev->config(dev);
99
break;
100
case LED_ON:
101
dev->on(dev);
102
break;
103
case LED_OFF:
104
dev->off(dev);
105
break;
106
default:
107
P_DEBUG("unknow cmd!\n");
108
ret = - EINVAL;
109
goto err0;
110
}
111
return 0;
112
113
err0:
114
return ret;
115
}
116
117
struct _led_t my_led;
118
struct file_operations s3c_led_fops = {
119
.ioctl = s3c_led_ioctl,
//一定要加上。打开和关闭操作我不实现,使用默认的
120
};
3、接着实现应用层函数:
1
#include
2
#include
3
#include
4
#include
5
#include
6
#include
7
8
#include "led_ioctl.h"
9
10
int main(int argc, char *argv[])
11
{
12
int fd;
13
fd = open("/dev/led_driver", O_RDWR);
14
if(fd < 0){
15
perror("open");
16
return -1;
17
}
18
19
ioctl(fd, LED_CONF);
20
21
if(!strncasecmp("on", argv[1], 3))
22
ioctl(fd, LED_ON);
23
24
if(!strncasecmp("off", argv[1], 3))
25
ioctl(fd, LED_OFF);
26
27
28
return 0;
29
}
验证一下:
[root:
5th]# insmod led_driver.ko
[led_driver__init]major[253],
minor[0]
[led_driver__init]hello
led!
[root:
5th]# mknod /dev/led_driver c 253 0
[root:
5th]# ./app on
//亮灯
[root:
5th]# ./app off
//灭灯
[root:
5th]# rmmod led_driver
[led_driver__exit]bye
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
六、使用信号量
其实在单处理器非抢占内核下,是没有必要使用到内核同步机制的,这里使用信号量来限制只能同时一个进程打开并操作led设备文件。实现的方法就是在打开的时候使用信号量:
/*5th_mm_4/6th/led_driver.c*/
20
struct _led_t{
21
//hardware obb
22
unsigned long virt, phys;
23
unsigned long gpecon, gpedat, gpeup;
24
unsigned long reg;
25
struct resource *led_resource;
26
27
void (*config)(struct _led_t *);
28
void (*on)(struct _led_t *);
29
void (*off)(struct _led_t *);
30
31
//kernel oob
32
dev_t devno;
33
struct cdev led_cdev;
34
struct semaphore led_sem;
//非抢占下,其实单纯使用一个标志flag来实现也行,
35
};//文件打开减一,关闭加一,flag不为零时可打开。
。。。。。。。。
63
int init_led_device(struct _led_t *led)
64
{
65
led->phys = 0x56000000;
66
67
led->led_resource = request_mem_region(led->phys, 0x0c,
"LED_MEM");
68
if(NULL == led->led_resource){
69
return - 1;
70
}
71
72
led->virt = (unsigned long)ioremap(led->phys, 0x0c);
73
74
led->gpecon = led->virt + 0x40;
75
led->gpedat = led->virt + 0x44;
76
led->gpeup = led->virt + 0x48;
77
78
led->config = s3c_led_config;
79
led->on = s3c_led_on;
80
led->off = s3c_led_off;
81
82
sema_init(&led->led_sem, 1);
83
84
return 0;
85
}
。。。。。。。
120
int s3c_led_open (struct inode *node, struct file *filp)
121
{
122
struct _led_t *dev = container_of(node->i_cdev, struct _led_t,
led_cdev);
123
filp->private_data = dev;
124
125
if (down_trylock(&dev->led_sem)){
//获得锁
126
P_DEBUG("led busy!\n");
127
return - EBUSY;
128
}
129
130
return 0;
131
}
132
133
int s3c_led_release (struct inode *node, struct file *filp)
134
{
135
struct _led_t *dev = filp->private_data;
136
up(&dev->led_sem);
//释放锁
137
return 0;
138
}
139
140
141
struct _led_t my_led;
142
struct file_operations s3c_led_fops = {
143
.ioctl = s3c_led_ioctl,
144
.open = s3c_led_open,
145
.release = s3c_led_release,
146
};
为了验证,修改一下应用程序,使程序陷入死循环不退出:
/*5th_mm_4/6th/app.c*/
2
#include
3
#include
4
#include
5
#include
6
#include
7
8
#include "led_ioctl.h"
9
10
int main(int argc, char *argv[])
11
{
12
int fd;
13
fd = open("/dev/led_driver", O_RDWR);
14
if(fd < 0){
15
perror("open");
16
return -1;
17
}
18
19
ioctl(fd, LED_CONF);
20
21
if(!strncasecmp("on", argv[1], 3))
22
ioctl(fd, LED_ON);
23
24
if(!strncasecmp("off", argv[1], 3))
25
ioctl(fd, LED_OFF);
26
27
while(1)
28
{
29
;
30
}
31
32
close(fd);
33
return 0;
34
}
也来验证一下:
[root: 6th]# insmod led_driver.ko
[led_driver__init]major[253],
minor[0]
[led_driver__init]hello
led!
[root: 6th]# mknod /dev/led_driver c
253 0
[root: 6th]#
./app on &
//后台开灯
[root: 6th]#
./app off
//在灭灯
[s3c_led_open]led
busy!
//灭灯进程无法打开设备文件,返回错误
open: Device or resource busy
[root: 6th]# rmmod led_driver
[led_driver__exit]bye
这样,一个简单的LED驱动就实现了,大家也可以尝试将my_led结构体通过kmalloc来申请,我只是觉得这个结构体占用的空间不多,就把这个步骤免了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
七、总结
上面的驱动我是按以下顺序写的:
1)实现硬件操作config,on.off
2)定义面向对象数据结构
3)定义硬件初始化操作
4)实现字符设备注册
5)实现ioctl等字符设备操作
6)实现信号量限制打开文件个数
上面介绍了我写驱动函数的步骤,其实最先的步骤应该是定义面向对象的数据结构,在开始实现其他的函数操作,只不过我之前已经将部分的硬件操作函数写好了,所以就稍稍改了前三步的步骤。接下来总结一下:
顺序不是一成不变的,但无论怎么写,也要按照从底层到上层,逐个逐个往上封装。
当然,这个驱动只是我结合了之前学的知识写的,内核中的驱动不可能这么简单,
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx