third_drv中断方式的按键驱动
15年4月30日17:05:52
这一段是从网上下载的,分析中断的流程:
要想用中断方式编写应用程序,首先需要理解中断的流程。
内核在start_kernel函数中调用trap_init、init_IRQ两个函数来设置异常的处理函数,首先我们想来看看trap_init函数,部分代码如下:
void __init trap_init(void)
{
.......................................................
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
.......................................................
}
分析:ARM构架CPU的异常向量基址可以是0x00000000,也可以是 0xffff0000,linux内核使用后者。而trap_init函数就是用于将异常向量拷贝到0xffff0000处。上面的代码 里,vectors为0xffff0000,__vectors_start和__vector_end之间存放的就是异常向量,他们被拷贝到地址0xffff0000处,但那只是一些简单的跳转指令,更加复杂的指令存放在__stubs_start和__stubs_end之间,他们被拷贝到0xffff0000+200处。
下面我们把目光转到__vectors_start处(arch/arm/kernel文件里),看一下代码:
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
其中:.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
分析:当相应的某个异常发生的时候,他会根据上面对应的异常向量跳转指令跳转到相应的位置进行处理。以b vector_irq + stubs_offset为例,当发生中断时,会执行它,然后跳转到vector_irq处(sutbs_offset用于从定位跳转的位置,暂时忽略也没有关系),可是我们搜遍代码页没有发现vector_irq这个标号。不过我们发现了如下代码:
vector_stub irq, IRQ_MODE, 4
经分析我们发现这是一条宏定义,相关代码如下:
.macro vector_stub, name, mode, correction=0 @vector_stub irq, IRQ_MODE, 4
.align 5
vector_\name: @vector_irq
.if \correction
sub lr, lr, #\correction
.endif
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
我们进行一下置换就得到了:vector_irq这个标号,其中最后一段代码是跳转指令,用于跳转到具体的中断入口。我们可以看看这个标号下面的内容:
vector_irq:
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
我们以.long __irq_usr为例进一步分析:
我们找到__irq_usr 这个标号,看一下其下面的代码:
__irq_usr:
usr_entry @保存寄存器
................................................................................
irq_handler@这也是一个宏,它会最终调用函数asm_do_IRQ,这是个c函数
...............................................................................
接下来是c语言了,我们列出主要框架:
asm_do_IRQ
struct irq_desc *desc = irq_desc + irq;//以中断号为下标得到中断描述结构体数组中的一项
desc_handle_irq(irq, desc);//进入中断处理
desc->handle_irq(irq, desc);//要想知道这个函数是怎么回事,我们可以反推回去,不过为了便于分析,我们采取正叙的方法
void __init s3c24xx_init_irq(void)//初始化中断
set_irq_chip(irqno, &s3c_irqext_chip);
desc = irq_desc + irq;
desc->chip = chip;//设置一些底层硬件操作函数
set_irq_handler(irqno, handle_edge_irq);
__set_irq_handler(irq, handle, 0, NULL);
desc = irq_desc + irq;
desc->handle_irq = handle;
到这一步我们将desc->handle_irq=handle_edge_irq,在结合上面的desc->handle_irq(irq, desc),我们知道上面就是执行handle_edge_irq这条指令,那么handle_edge_irq有做了什么呢?我们接着看代码,搭建框架:
handle_edge_irq
handle_IRQ_event(irq, action);
do {
ret = action->handler(irq, action->dev_id);//这就是中断处理函数
action = action->next;//这是中断链表
} while (action);
由此我们看出发生中断后会跳转到入口函数asm_do_IRQ,之后会根据中断号找到中断处理函数来执行。关于上面提到的三个结构体,我们还要在来分析一下:
struct irq_desc {
irq_flow_handler_t handle_irq;//当前中断的处理入口函数,我们会将中断处理函数赋给它
struct irq_chip *chip;//底层的硬件访问
.................................
struct irqaction *action;//中断处理函数列表
unsigned int status; //中断状态
................................
const char *name;//中断名称
}
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);//启动中断,默认是enable
void (*shutdown)(unsigned int irq);//关闭中断,默认是disable
void (*enable)(unsigned int irq);//使能中断,默认是unmask
void (*disable)(unsigned int irq);//禁止中断,默认是mask
void (*ack)(unsigned int irq);//响应中断,通常是清除当前中断使得可以接收下一个中断
void (*mask)(unsigned int irq);//屏蔽中断源
void (*mask_ack)(unsigned int irq);//屏蔽和响应中断
void (*unmask)(unsigned int irq);//开启中断
..................................................
};
struct irqaction {
irq_handler_t handler;//用户注册的中断处理函数
unsigned long flags;//中断标志
cpumask_t mask;//用于SMP(对称多处理器系统)
const char *name;//用户注册的中断名称
void *dev_id;//用户上面传过来的handler参数,还可以用来区分共享中断
struct irqaction *next;
int irq; //中断号
struct proc_dir_entry *dir;
};
通过上面的分析我们知道了中断发生后最终调用到desc->action->handler,于是我们就知道注册中断的话必须根据中断号将中断处理函数赋给
desc->action->handler,那么到底是不是呢?我们来查看代码:
以一个中断注册函数为例:
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
action->handler = handler;//中断处理函数放在这里了
retval = setup_irq(irq, action);//设置中断
struct irq_desc *desc = irq_desc + irq;//以中断号为下标构造中断描述结构体数组中的一项,发生中断的时候我们会根据中断号取出相应 //的结构体
到此为止,中断的大体流程我们就分析明白了,下面我们就以中断方式来编写按键驱动。
********************************************************************
(一)先写出来中断方式按键驱动的框架,直接在第二个驱动程序上面修改:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include //添加了这个头文件
7 #include
8 #include
9 #include
10 #include
11 #include
12
13
14 static struct class *third_drv_class;
15 static struct class_device *third_drv_class_dev;
16
17 volatile unsigned long *gpfcon;
18 volatile unsigned long *gpfdat;
19
20 volatile unsigned long *gpgcon;
21 volatile unsigned long *gpgdat;
22
23 static irqreturn_t buttons_irq(int irq, void *dev_id) //发生中断以后的处理函数
24 {
25 printk("irq = %d\n", irq);
26 return IRQ_RETVAL(IRQ_HANDLED);
27 }
28
29 static int third_drv_open(struct inode *inode, struct file *file)
30 {
31 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", 1);
//向内核注册中断,在后面再具体分析这个函数。
32 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", 1);
33 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", 1);
34 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", 1);
35
36 return 0;
37 }
38
39 ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
40 {
41 unsigned char key_vals[4];
42 int regval;
43
44 copy_to_user(buf, key_vals, sizeof(key_vals));
45
46 return sizeof(key_vals);
47 }
48
49
50 int third_drv_close(struct inode *inode, struct file *file)
51 {
52 free_irq(IRQ_EINT0, 1); //释放中断,与前面open函数对应。
53 free_irq(IRQ_EINT2, 1);
54 free_irq(IRQ_EINT11, 1);
55 free_irq(IRQ_EINT19, 1);
56 return 0;
57 }
58
59
60 static struct file_operations third_drv_fops = {
61 .owner = THIS_MODULE,
62 .open = third_drv_open,
63 .read = third_drv_read,
64 .release = third_drv_close,
65 };
66
67
68 int major;
69 static int third_drv_init(void)
70 {
71 major = register_chrdev(0, "third_drv", &third_drv_fops);
72
73 third_drv_class = class_create(THIS_MODULE, "third_drv");
74
75 third_drv_class_dev = class_device_create(third_drv_class, NULL, MKDEV(major, 0), NULL, "buttons");
76 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
77 gpfdat = gpfcon + 1;
78
79 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
80 gpgdat = gpgcon + 1;
81
82 return 0;
83 }
84
85 static void third_drv_exit(void)
86 {
87 unregister_chrdev(major, "third_drv");
88 class_device_unregister(third_drv_class_dev);
89 class_destroy(third_drv_class);
90 iounmap(gpfcon);
91 iounmap(gpgcon);
92 return 0;
93 }
94
95
96 module_init(third_drv_init);
97
98 module_exit(third_drv_exit);
99
100 MODULE_LICENSE("GPL");
(1)注册中断程序的时候用 request_irq,下面分析request_irq 这个函数:
int request_irq (unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
unsigned int irq:中断号
irq_handler_t handler:发生中断时的处理函数
unsigned long irqflags:触发方式,比如说上升沿触发,下降沿触发还是双边沿触发。
char *devname:中断名字
void *dev_id:设备id
request_irq函数做了以下几个任务:
1)分配一个irqaction结构
2)把这个结构放入irq_desc[irq]数组里面,action链表。
3)设置引脚,将引脚设置成中断引脚,使能中断
(2)释放中断的时候用free_irq:
free_irq(irq, dev_id)
1)出链
2)禁止中断
构建中断方式的按键驱动框架步骤总结:
(一)出入口函数
third_drv_init:注册设备,创建类,地址映射
third_drv_exit:注销设备,注销类,取消地址映射
(二)构建file_operations结构体,字符驱动的核心。
1)open函数
在open函数里面注册中断,需要用到request_irq函数。其中request_irq函数中有一个中断处理函数buttons_irq,就是发生中断以后需要做什么事情,把这个程序写出来。
2)read函数,核心就是一个copy_to_user函数。
3)open函数中打开了中断,释放中断就用到了close函数,close函数在file_operations结构体中对应 .release = third_drv_close,close函数中就用到了free_irq这个内核函数。
这样就构建出来了一个中断方式的按键驱动框架。
这时候把程序在开发板上运行:
提示下图所示的错误:
发现在buttons_irq中,用的是printf这个打印函数,而不是printk这个内核函数,所以出现上图错误。
安装上驱动以后,用exec 5
注销中断用exec 5<&-命令,如下所示:
继续深化程序:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12
13 static struct class *third_drv_class;
14 static struct class_device *third_drv_class_dev;
15
16 volatile unsigned long *gpfcon;
17 volatile unsigned long *gpfdat;
18
19 volatile unsigned long *gpgcon;
20 volatile unsigned long *gpgdat;
21
22 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
23 static volatile int ev_press = 0;
24
25 struct pin_desc{
26 unsigned int pin;
27 unsigned int key_val;
28 };
29
30 static unsigned char key_val;
31 /*自己定义键值,按下时为0x01 0x02 0x03 0x04 松开时为0x81 0x82 0x83 0x84*/
32 static struct pin_desc pins_desc[4] = {
33 {S3C2410_GPF0, 0X01},
34 {S3C2410_GPF2, 0X02},
35 {S3C2410_GPG3, 0X03},
36 {S3C2410_GPG11, 0X04},
37 };
38
39 static irqreturn_t buttons_irq(int irq, void *dev_id) //确定按键值
40 {
41 struct pin_desc * pindesc = (struct pin_desc *)dev_id;
42 unsigned int pinval;
43
44 pinval = s3c2410_gpio_getpin(pindesc->pin);
45
46 if(pinval) /*松开*/
47 {
48 key_val = 0x80 | pindesc->key_val;
49 }
50 else /*按下*/
51 {
52 key_val = pindesc->key_val;
53 }
54
55 ev_press = 1; //表示中断发生了
56 wake_up_interruptible(&button_waitq); //唤醒休眠的进程
57
58 return IRQ_RETVAL(IRQ_HANDLED);
59 }
60
61 static int third_drv_open(struct inode *inode, struct file *file){
62
63 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
64 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
65 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
66 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
67 return 0;
68 }
69
70 ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
71
72 if(size != 1)
73 return -EINVAL;
74 /* 如果没有按键动作,休眠 */
75 wait_event_interruptible(button_waitq, ev_press);
76
77 copy_to_user(buf, &key_val, 1);
78
79 ev_press = 0; /*把数据发给用户以后,记得把 ev_press置0,不然下次执行到这的时候, ev_press为1,直接把上次的数据发给用户了*/
80
81 return 1;
82 }
83
84 int third_drv_close(struct inode *inode, struct file *file)
85 {
86 free_irq(IRQ_EINT0, &pins_desc[0]);
87 free_irq(IRQ_EINT2, &pins_desc[1]);
88 free_irq(IRQ_EINT11, &pins_desc[2]);
89 free_irq(IRQ_EINT19, &pins_desc[3]);
90 }
91
92 static struct file_operations third_drv_fops = {
93 .owner = THIS_MODULE,
94 .open = third_drv_open,
95 .read = third_drv_read,
96 .release = third_drv_close,
97 };
98
99 int major;
100 static int __init third_drv_init(void){
101 major = register_chrdev(0, "third_drv", &third_drv_fops);
102
103 third_drv_class = class_create(THIS_MODULE, "third_drv");
104 third_drv_class_dev = class_device_create(third_drv_class, NULL, MKDEV(major, 0), NULL, "buttons");
105
106 gpfcon = (unsigned long *)ioremap(0x56000050, 16);
107 gpfdat = gpfcon + 1;
108
109 gpgcon = (unsigned long *)ioremap(0x56000060, 16);
110 gpgdat = gpgcon + 1;
111 return 0;
112 }
113
114 static void __exit third_drv_exit(void){
115 unregister_chrdev(major, "third_drv");
116
117 class_device_unregister(third_drv_class_dev);
118 class_destroy(third_drv_class);
119
120 iounmap(gpfcon);
121 iounmap(gpgcon);
122
123 return 0;
124 }
125
126
127
128 module_init(third_drv_init);
129 module_exit(third_drv_exit);
130
131 MODULE_LICENSE("GPL");
测试程序如下:
1 #include
2 #include
3 #include
4 #include
5
6 int main (int argc, char **argv){
7 int fd;
8 unsigned char key_val;
9
10 fd = open("/dev/buttons",O_RDWR);
11
12 if (fd < 0)
13 printf("cannot open!\n");
14
15 while (1)
16 {
17 read(fd, &key_val, 1);
18 printf("key_val = 0x%x\n", key_val);
19 }
20 return 0;
21 }
深化按键驱动程序,就是在中断处理函数里面加一些东西,比如说确定按键值。因为有四个开关,我们假设他们的按键值按下时为0x01 0x02 0x03 0x04,松开时为0x81 0x82 0x83 0x84,这时候可以用一个swich语句来实现,但是太麻烦,我们先建立一个结构体数组,static struct pin_desc pins_desc[4] ,把这几个按键值填充进去,然后通过struct pin_desc * pindesc = (struct pin_desc *)dev_id; pinval = s3c2410_gpio_getpin(pindesc->pin);这两句话来确定按键值等于多少。 同时,在request_irq函数中把dev_id也与这个结构体数组相关,用下面的函数实现:request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]); 。在open函数中修改了request_irq函数,同样在close函数中,记得修改free_irq函数。
在第二个驱动程序中可以看到,采用轮询机制的话,cpu占有率为99%,这个程序中,我们让进程在没有按键动作的时候进入休眠状态,测试程序中,主程序是一个while(1)循环,在循环里面是read函数,所以在驱动程序中的read函数中,我们用wait_event_interruptible(button_waitq, ev_press); 这个函数让进程在没有按键动作时进入休眠状态,函数的两个参数分别用 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static volatile int ev_press = 0;来声明,其中ev_press为中断事件标志,wait_event_interruptible函数根据它判断是否进入休眠状态,当ev_press为0时,调用wait_event_interruptible函数可以使进程进入休眠状态,当它为1时不能。
如果发生了中断,系统进入中断处理函数,先将ev_press = 1; 置为1,表明发生了中断,然后调用wake_up_interruptible(&button_waitq); 函数唤醒进程。同样,在read函数中,把数据发给用户以后,记得把 ev_press置0,不然下次执行到这的时候, ev_press为1,直接把上次的数据发给用户了。
好好理解这个中断的过程。