linux按键驱动中断函数参数分析,linux按键中断驱动分析

上次用了查询方式来读出按键状态,这种方法虽然也能读取按键是否被按下了,但是占用了99%的CPU资源,下面来说一下用中断方式实现方法,ARM架构的linux中常见异常有未定义指令,指令预取中止,数据访问中止,中断异常,SWI异常,中断也是一种异常,下面重点来说中断异常

1中断处理体系结构

linux里面对所用中断统一编号,使用一个irq-desc结构数组来描述这些中断,它在include/linux/irq.h中定义

struct irq_desc {

irq_flow_handler_t handle_irq;

//当前中断处理函数入口,是一个函数指针。

struct

irq_chip *chip; //底层硬件初始化

struct

msi_desc *msi_desc;

void *handler_data;

void *chip_data;

struct

irqaction *action; //用户提供的中断处理函数链表,我们写的中断处理

//函数会通过这个链表链头连起来

unsigned

int status;

....

}当发生中断时handle_irq是这个或这组中断处理函数入口,这是总中断入口函数asm_do_IRQ根据中断号来调用irg_desc数组中的handle_irq。(在arch/arm/kernel/irq.c中定义,其实可以直接在Source

Insight中打入asm_do_IRQ,然后按住ctrl点一下鼠标就可以跳到,其它的也一样)

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct pt_regs *old_regs =

set_irq_regs(regs);

irq_entFer();

if (irq >= NR_IRQS)

handle_bad_irq(irq,

&bad_irq_desc);//根据中断号调用相应bad_irq_desc

else

generic_handle_irq(irq);

....

irq就是传进来中断号,

下面来看一下irq_chip这个结构体一些成员(在include/linux/irq.h中)

struct irq_chip {

const char *name;

unsigned int (*startup)(unsigned

int irq);//启动中断

void (*shutdown)(unsigned

int irq);//关闭中断

void (*enable)(unsigned

int irq);//使能中断

void (*disable)(unsigned

int irq);

void (*ack)(unsigned

int irq);

void (*mask)(unsigned

int irq);//屏蔽中断源

void (*mask_ack)(unsigned

int irq);

void (*unmask)(unsigned

int irq);//开启中断源

void (*eoi)(unsigned

int irq);

void (*end)(unsigned

int irq);

void (*set_affinity)(unsigned

int irq,

const struct cpumask

*dest);

最后来看一下irq_desc结构中的irqaction结构,这是我们用户注册的中断处理函数,一个中断可以有多个处理函数,他们的irqaction结构连接成一个链表,以action为表头,irqaction结构定义如下(在include/linux/interrupt.h中)

struct irqaction {

irq_handler_t

handler;//用户注册的中断处理函数,由我们自己来写的

unsigned long flags;//触发中断方式

cpumask_t mask;

const char *name;

void *dev_id;//中断处理函数和卸载中断处理函数是用到

struct irqaction *next;

int irq;//中断号

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};

总结一下中断处理流程:

(1)发生中断时,cpu执行异常向量vector_irq的代码地址0xffff0000(已近从0x0中复制过来);

(2)在vector_irq里面会用到一些宏等,还会进入管理模式(其实就是保存了中断现场)最终就是调用中断总入口函数asm_do_IRQ;

(3)asm_do_IRQ会根据中断号来调用 irq_desc数组中的handle_irq;

(4)handle_irq会使用chip成员来设置硬件,比如清中断等等;

(5)最后就是逐个调用用户在action链表中注册的中断处理函数;

所以,中断体系结构初始化就是构造这些数据结构,比如irq_desc数组中的handle_irq,chip等成员,还有就是用户注册中断处理函数;

2中断处理体系结构初始化

init_IRQ函数就是用来初始化中断处理体系结构的。代码在arch/arm/kernel/irq.c中

void __init init_IRQ(void)

{

int irq;

for (irq = 0; irq < NR_IRQS;

irq++)

irq_desc[irq].status |=

IRQ_NOREQUEST | IRQ_NOPROBE;//初始化irq_desc中每一项中断状态

#ifdef CONFIG_SMP

cpumask_setall(bad_irq_desc.affinity);

bad_irq_desc.cpu = smp_processor_id();

#endif

init_arch_irq();//调用构架相关的中断初始化函数

}

init_arch_irq();//调用构架相关的中断初始化函数,对已2440调用的就是s3c24xx_init_irq这个函数它在arch/arm/plat-s3c24xx/irq.c中,他为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq),下面以IRQ_EINT0到IRQ_EINT3为例如下

for (irqno = IRQ_EINT0; irqno

<= IRQ_EINT3; irqno++) {

irqdbf("registering irq %d (ext

int)\n", irqno);

set_irq_chip(irqno,

&s3c_irq_eint0t4);//irq_desc[irq].chip=handle_edge_irq,设置了chip处理函数

//处理中断触发方式,清中断等

set_irq_handler(irqno,

handle_edge_irq);//irq_desc[irq].handle_irq=handle_edge_irq

//发生中断是就是调用这个函数(handle_edge_irq);

set_irq_flags(irqno,

IRQF_VALID);

}

对以TQ2440开发板当调用完init_IRQ之后,chip,handle_irq成员就被设置好了。handle_edge_irq函数功能是

(1)清中断它会调用desc_chip_ack(irq)来清中断,代码在kernel/irq/chip.c中

handle_edge_irq(unsigned int irq, struct irq_desc *desc)

{

spin_lock(&desc->lock);

desc->status &=

~(IRQ_REPLAY | IRQ_WAITING);

if (unlikely((desc->status

& (IRQ_INPROGRESS | IRQ_DISABLED)) ||

!desc->action)) {

desc->status |=

(IRQ_PENDING | IRQ_MASKED);

mask_ack_irq(desc, irq);

desc = irq_remap_to_desc(irq,

desc);

goto out_unlock;

}

kstat_incr_irqs_this_cpu(irq,

desc);//根据中断号找到desc结构

if

(desc->chip->ack)

desc->chip->ack(irq);//清中断 desc = irq_remap_to_desc(irq, desc);

(2)调用handle_IRQ_event来处理中断,具体是取出action链表中action->handler中断处理函数来处理中断

代码在kernel/irq/handle.c中

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction

*action)//传入中断号irq

{

irqreturn_t ret, retval = IRQ_NONE;

unsigned int status = 0;

if (!(action->flags

& IRQF_DISABLED))

local_irq_enable_in_hardirq();

do {

trace_irq_handler_entry(irq,

action);//根据irq找到action

ret =

action->handler(irq,

action->dev_id);//根据irq,dev_id执行用户注册中断处理函数

trace_irq_handler_exit(irq,

action, ret);

.....

对以TQ2440执行完init_IRQ后,irq_desc数组项中的chip,handle_irq成员就被设置好了,下面来说我们用户注册中断处理函数,它是通过request_irq函数向内核注册中断处理函数的,也就是说我们写的中断处理函数要先注册,它在kernel/irq/manage.c中定义

request_irq(unsigned int irq, irq_handler_t

handler, unsigned long flags,

const char *name, void

*dev)

{

return request_threaded_irq(irq, handler, NULL, flags, name,

dev);

}

request_threaded_irq(中断号,中断处理函数,触发方式,中断名字,id)

首先request_threaded_irq会利用这些参数构造一个irqaction结构,然后调用 __setup_irq(irq,

desc, action)函数将它连入链表中去

request_threaded_irq这个函数构造irqaction结构

int request_threaded_irq(unsigned int irq, irq_handler_t

handler,

irq_handler_t thread_fn, unsigned long irqflags,

const char *devname, void *dev_id)

{

struct irqaction *action;

struct irq_desc *desc;

int retval;

....

if (!action)

return -ENOMEM;

action->handler = handler;

action->thread_fn = thread_fn;

action->flags = irqflags;

action->name = devname;

action->dev_id = dev_id;

retval =

__setup_irq(irq, desc, action);//根据中断号找到desc数组的action

__setup_irq(irq, desc,

action)在kernel/irq/manage.c中定义,他完成功能有

a)将新建的irqaction结构链入irq_desc[irq].action中

b )设置irq_desc[irq]结构中chip成员没有设置的指针

c)设置中断触发方式,把相应引脚设为中断引脚

d)使能中断,一般来说执行完 request_irq这个中断就使能了

__setup_irq(unsigned int irq, struct irq_desc *desc, struct

irqaction *new)

{

struct irqaction *old, **old_ptr;

const char *old_name = NULL;

unsigned long flags;

int shared = 0;

int ret;

....

spin_lock_irqsave(&desc->lock,

flags);

old_ptr =

&desc->action;//找到irq_desc[irq].action

old = *old_ptr;

.....

总结一下 request_irq函数注册之后的“成果”

(1)irq_desc[irq]结构中的action链表中链入了用户注册的中断处理函数;

(2)中断触发方式已近设置好,引脚也设为了中断引脚;

(3)中断已使能;

##################################################################################################

下面来总结一下中断处理流程:

(1)中断向量调用总入口函数asm_do_IRQ;

(2)asm_do_IRQ根据传入中断号irq调用相应的irq_desc[irq].handle_irq,它是这个中断处理函数入口,对以电平触发这个函数为handle_level_irq,对以边沿触发这个处理函数是handle_edge_irq(上面是以边沿触发为例的);

(3)入口函数首先是清中断,对以handle_level_irq还要屏蔽中断(handle_level_irq就不说了);

(4)逐个调用irq_desc[irq].action注册的用户中断处理函数,对以handle_level_irq入口函数还要重启中断;

上面分析如果有错请各位大帝指点。。。

最后来说一下卸载中断处理函数的实现

free_irq函数是用来卸载中断处理函数的在kernel/irq/manage.c中定义

void free_irq(unsigned int irq, void *dev_id)

{

kfree(__free_irq(irq, dev_id));

}

它要irq和dev_id,就会根据irq找到中断项然好再根据dev_id确定是哪个中断处理函数,把它干掉就可以了,如果里面没有了中断处理函数还要关中断。。。。

说了这么多,其实有很打一部分都是内核帮我们做好的了,我们要做的无非就是注册用户中断处理函数,写中断处理函数;

下面的是代码,用的是linux-2.6.30.4内核,开发板是TQ2440,不同内核可能用到的函数有所不同,不同开发板所用寄存器GPIO接法不同

#include

 #include

#include

#include

#include

#include

#include

#include

#include

#include

 #include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

static struct class *thdrv_clas//创立自动设备节点时会用到这个结构体

//static struct class_device *thdrv_class_devs;

volatile unsigned long *gpfcon = NULL;//这里没用到,这是我在上一个驱动里写的

volatile unsigned long *gpfdat = NULL;;//这里没用到

static

DECLARE_WAIT_QUEUE_HEAD(button_waitq);//定义button_waitq用它来挂载休眠进程

static volatile int ev_press = 0;//ev_press =

0时进程将休眠(这里指的是应用程序休眠)

static unsigned char key;//获取按键状态

struct pin_desc{

unsigned int

pin; unsigned

char keyl;

};

struct pin_desc pins_desc[4] = {

{S3C2410_GPF0, 0x01},//定义一些按键返回值

{S3C2410_GPF1, 0x02},

{S3C2410_GPF2, 0x03},

{S3C2410_GPF4, 0x04},

};

static irqreturn_t buttons_irq(int irq, void *dev_id)

{

struct pin_desc * pindesc = (struct pin_desc

*)dev_id;//构造struct pin_desc指针

unsigned int pinval;

pinval =

s3c2410_gpio_getpin(pindesc->pin);//s3c2410_gpio_getpin函数用来获取按键状态

//没按下时为高电平,按下为低电平

if (pinval)

{

//松开时返回0x81

key = 0x80 |

pindesc->keyl;

}

else

{

//按下时返回0x01

key = pindesc->keyl;

}

ev_press =

1; //ev_press

= 1用来唤醒休眠中进程

wake_up_interruptible(&button_waitq); //唤醒休眠中进程

return

IRQ_RETVAL(IRQ_HANDLED);

}

static int tq2440_myzd_open(struct inode *inode, struct file

*file)

{

//注册用户中断处理函数

request_irq(IRQ_EINT0,buttons_irq,IRQ_TYPE_EDGE_BOTH,

"S0",(void*)&pins_desc[0] );

request_irq(IRQ_EINT1,buttons_irq,IRQ_TYPE_EDGE_BOTH,

"S1",(void*)&pins_desc[1] );

request_irq(IRQ_EINT2,buttons_irq,IRQ_TYPE_EDGE_BOTH, "S2",

(void*)&pins_desc[2]);

request_irq(IRQ_EINT4,buttons_irq,IRQ_TYPE_EDGE_BOTH,

"S4",(void*)&pins_desc[3]);

return 0;

}

static ssize_t tq2440_myzd_read(struct file *file, char __user

*buf,

size_t nbytes, loff_t *ppos)

{

wait_event_interruptible(button_waitq,

ev_press);//按键没有按下(没有发生中断)时让应有程序休眠

ev_press =

0;//中断处理函数把它设为1了这里重新把它设为0,让它执行完之后再休眠

copy_to_user(buf, &key,

1);//读出按键状态传到用户空间

return

0;

}

static int tq2440_drv_close(struct inode * inode, struct file *

file)

{

//释放中断

free_irq(IRQ_EINT0,(void*)&pins_desc[0]);

free_irq(IRQ_EINT1,(void*)&pins_desc[1]);

free_irq(IRQ_EINT2,(void*)&pins_desc[2]);

free_irq(IRQ_EINT4,(void*)&pins_desc[3]);

return 0;

}

static struct file_operations tq2440_myzd_fops = {

.owner = THIS_MODULE,

.open = tq2440_myzd_open,

.read = tq2440_myzd_read,

.release = tq2440_drv_close,//关闭文件时执行

};

static int major;

static int __init tq2440_myzd_init(void)

{

major=register_chrdev(0, "zhzd",

&tq2440_myzd_fops);

thdrv_class = class_create(THIS_MODULE,

"thdrv");//自动创建设备节点时用到

device_create(thdrv_class,NULL,MKDEV(major,0),NULL,"ccc");

gpfcon = (volatile unsigned long

*)ioremap(0x56000050,0x100000);

gpfdat = gpfcon + 1;

return 0;

}

static void __exit tq2440_myzd_exit(void)

{

unregister_chrdev(major, "zhzd");

iounmap(gpfcon);

device_destroy(thdrv_class,MKDEV(major,0));//卸载时把自动创建设备节点野释放掉

class_destroy(thdrv_class);

}

module_init(tq2440_myzd_init);

module_exit(tq2440_myzd_exit);

MODULE_LICENSE("GPL");

下面给出应用程序

#include

#include

#include

#include

int main()

{

int fd=-1;

unsigned char key;

fd = open("/dev/ccc",O_RDWR);

if(fd<0)

{

printf("can't open /dev/ccc\n");

return -1;

}

while(1)

{

read(fd,&key,1);

printf("key pless :

0x%x\n",key);

}

return 0;

}

下面是这个驱动运行情况

[root@cgyl2010 ~]#insmod thirdzd.ko

[root@cgyl2010 ~]#./myzd &

[root@cgyl2010 ~]#key pless : 0x1

key pless : 0x81

key pless : 0x2

key pless : 0x2

key pless : 0x82

key pless : 0x2

key pless : 0x2

key pless : 0x82

key pless : 0x2

key pless : 0x82

key pless : 0x3

key pless : 0x83

key pless : 0x3

key pless : 0x83

key pless : 0x4

key pless : 0x84

[root@cgyl2010 ~]#cat /proc/interrupts

CPU0

16: 2 s3c-ext0 S0

17: 17 s3c-ext0 S1

18: 4 s3c-ext0 S2

30: 46985 s3c S3C2410 Timer Tick

42: 0 s3c ohci_hcd:usb1

43: 0 s3c s3c2440-i2c

48: 2 s3c-ext S4

51: 2980 s3c-ext eth0

70: 59 s3c-uart0 s3c2440-uart

71: 126 s3c-uart0 s3c2440-uart

83: 0 - s3c2410-wdt

Err: 0

。。。。。。。。。。。。。。。。。。

你可能感兴趣的:(linux按键驱动中断函数参数分析,linux按键中断驱动分析)