前面学习了一下中断处理流程和linux对中断的管理所用的数据结构,接下来再学习一下linux对外提供的标准接口,几乎所有的接口都是围绕着irq_desc和irq_chip这两个结构体进行的。
驱动程序员最关心的就是linux提供的接口,我们学会使用这些接口,就可以编写我们的驱动程序。下面就列举和解析linux提供的中断操作接口。在linux中,EXPORT_SYMBOL(fun)宏是把fun符号引出给外部使用。linux3.6提供的中断接口定义在kernel\irq\manage.c里面,声明和封装在include/linux/interrupt.h给外部使用。我们常用的有以下几个:
1.request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);
函数参数说明
unsigned int irq:所要申请的硬件中断号,对于ARM体系,irq编号通常在平台级的代码中事先定义好
irq_handler_t handler:中断服务程序的入口地址,中断发生时,系统调用handler这个函数。irq_handler_t为自定义类型,其原型为:
typedef irqreturn_t (*irq_handler_t)(int, void *);
而irqreturn_t的原型为:typedef enum irqreturn irqreturn_t;
enum irqreturn {
IRQ_NONE,/*此设备没有产生中断*/
IRQ_HANDLED,/*中断被处理*/
IRQ_WAKE_THREAD,/*唤醒中断*/
};
在枚举类型irqreturn定义在include/linux/irqreturn.h文件中。
unsigned long flags:中断处理的属性,与中断管理有关的位掩码选项,有一下几组值:
#define IRQF_DISABLED 0x00000020 /*中断禁止*/
#define IRQF_SAMPLE_RANDOM 0x00000040 /*供系统产生随机数使用*/
#define IRQF_SHARED 0x00000080 /*在设备之间可共享*/
#define IRQF_PROBE_SHARED 0x00000100/*探测共享中断*/
#define IRQF_TIMER 0x00000200/*专用于时钟中断*/
#define IRQF_PERCPU 0x00000400/*每CPU周期执行中断*/
#define IRQF_NOBALANCING 0x00000800/*复位中断*/
#define IRQF_IRQPOLL 0x00001000/*共享中断中根据注册时间判断*/
#define IRQF_ON
#define IRQF_TRIGGER_NONE 0x00000000/*无触发中断*/
#define IRQF_TRIGGER_RISING 0x00000001/*指定中断触发类型:上升沿有效*/
#define IRQF_TRIGGER_FALLING 0x00000002/*中断触发类型:下降沿有效*/
#define IRQF_TRIGGER_HIGH 0x00000004/*指定中断触发类型:高电平有效*/
#define IRQF_TRIGGER_LOW 0x00000008/*指定中断触发类型:低电平有效*/
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010/*触发式检测中断*/
const char *dev_name:设备描述,表示那一个设备在使用这个中断。
void *dev_id:用作共享中断线的指针.。一般设置为这个设备的设备结构体或者NULL。它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断 。这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void *dev_id。如果中断没有被共享, dev_id 可以设置为 NULL。
2.void free_irq(unsigned int irq, void *dev_id);
对于驱动,基本就使用这两个函数。其他函数不做列举了。
在注册中断时,最重要的是中断号irq参数,但是linux是如何把irq与硬件的中断多映射的呢?我们使用某个中断引脚是应该传什么中断号呢?
前面我们知道,这个irq对应的其实就是irq_desc数据结构编号,在中断发生时由底层函数传递给linux。所以我们要搞清楚底层传上来的irq是对应硬件的哪一个中断。前面在分析linux中断处理流程的时候知道irq是由“get_irqnr_and_base,irqnr, irqstat, base, tmp“汇编指令取得。这个指令是对硬件的操作,不同平台的实现是不同的。atmel A5的实现在arch\arm\include\asm\hardware\entry-macro-iomd.s
#include <asm/hardware/iomd.h>
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
ldrb \irqstat, [\base, #IOMD_IRQREQB] @ get high priority first
ldr \tmp, =irq_prio_h
teq \irqstat, #0
#ifdef IOMD_BASE
ldreqb \irqstat, [\base, #IOMD_DMAREQ] @ get dma
addeq \tmp, \tmp, #256 @ irq_prio_h table size
teqeq \irqstat, #0
bne 2406f
#endif
ldreqb \irqstat, [\base, #IOMD_IRQREQA] @ get low priority
addeq \tmp, \tmp, #256 @ irq_prio_d table size
teqeq \irqstat, #0
#ifdef IOMD_IRQREQC
ldreqb \irqstat, [\base, #IOMD_IRQREQC]
addeq \tmp, \tmp, #256 @ irq_prio_l table size
teqeq \irqstat, #0
#endif
#ifdef IOMD_IRQREQD
ldreqb \irqstat, [\base, #IOMD_IRQREQD]
addeq \tmp, \tmp, #256 @ irq_prio_lc table size
teqeq \irqstat, #0
#endif
2406: ldrneb \irqnr, [\tmp, \irqstat] @ get IRQ number
.endm
但对s3c2440的实现在arch/arm/mach-s3c24xx/include/mach/entry-macro.s
.macro get_irqnr_preamble, base, tmp
.endm
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp//对应 r0, r6, r5, lr
mov \base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr \irqstat, [ \base, #INTPND ]//获取INTPND寄存器的数据 存入 r6
teq \irqstat, #0//测试irqstat的值,看是否为0
beq 1002f//如果为0 就说明没有中断发生 INTPND寄存器中没有置1的位 跳转到 1002标号处
ldr \irqnr, [ \base, #INTOFFSET ]//如果不为0,说明INTPND有位置1,这里读取INTOFFSET寄存器的内容,到irqnr中
mov \tmp, #1//给tmp赋值1
tst \irqstat, \tmp, lsl \irqnr//测试irqstat和1 << irqnr 进行逻辑按位与操作,如果相等,测Z flag为0 ,不等则Z flag为1
bne 1001f//如果上面为相等,那么就说明找到了中断号,就直接调到1001标号。
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov \irqnr, #0 @@ start here
@@ work out which irq (if any) we got
//这里假设INTPND的第8为被置1
movs \tmp, \irqstat, lsl#16//tmp = irqstat << 16; 更新cpsr
addeq \irqnr, \irqnr, #16//这里的eq条件肯定不成立啊,因为tmp != 0,所以跳过
moveq \irqstat, \irqstat, lsr#16//这里也跳过
tst \irqstat, #0xff//这里测试irqstat的低8位,按位与,结果肯定为0,Z flag 置位
addeq \irqnr, \irqnr, #8//这里的条件成立,irqnr = 0+8;
moveq \irqstat, \irqstat, lsr#8//irqstat右移8位,原先的第8位,变为最低位了
tst \irqstat, #0xf//测试低四位,按位与,结果不为0,Z Flag不会置位
addeq \irqnr, \irqnr, #4
moveq \irqstat, \irqstat, lsr#4
tst \irqstat, #0x3//测试低两位,与上面相同
addeq \irqnr, \irqnr, #2
moveq \irqstat, \irqstat, lsr#2
tst \irqstat, #0x1//测试最低位,也是跳过下面的一条
addeq \irqnr, \irqnr, #1
@@ we have the value
1001:
adds \irqnr, \irqnr, #IRQ_EINT0//这里irqnr= irqnr + IRQ_EINT0,应该是8 + IRQ_EINT0
1002:
@@ exit here, Z flag unset if IRQ
.endm
能看到,最后irqnr = irqnr+IRQ_EINT0.irqnr由INTPND寄存器决定。所以需要了解arm芯片的INTPND对应的位是什么中断就可以了。