Chinaunix首页 | 论坛 | 博客
  • 博客访问: 299543
  • 博文数量: 76
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 715
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-20 20:38
文章分类
文章存档

2016年(20)

2015年(56)

分类: 嵌入式

2015-05-25 20:23:32

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,直接把上次的数据发给用户了。

好好理解这个中断的过程。
阅读(1394) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~