上次用了查询方式来读出按键状态,这种方法虽然也能读取按键是否被按下了,但是占用了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
。。。。。。。。。。。。。。。。。。