一,Linux 中断实现:
<linux/interrupt.h>
中断注册:int request_irq(int irq,irqreturn_t (*irq_handle)(int irq,void *dev_id),int flags,char *name,void *dev_id);
irq:中断号即中断线;
irq_handle:中断处理例程;
flags:中断标志;
name:与中断相关联的名称;
dev_id:中断处理例程的参数;
二,中断号的检测:
1,通过模块参数制定(需要事先知道或跳线)
2,自动探测:
自动探测分为三种策略:设置默认值(对可能的端口地址 按照规范设置中断号),读取设备配置(例如:读取PCI设备的配置空间),动态探测中断号(动态尝试每一个中断线);
重点介绍第三个:
动态尝试每一个中断线流程:驱动程序通知设备产生中断,然后探测每一个中断结果,如果中断处理例程顺利执行,则该中断号为将要注册中断处理例程的中断号;
动态尝试分为两种实现方案:Linux内核提供的辅助探测函数,DIY探测(自己实现的探测方案)
1》Linux提供的辅助探测函数——只能用于非共享模式:
unsigned long probe_irq_on();
打开中断探测,返回未被注册的中断位掩码,驱动程序要保存该位掩码;然后驱动程序通知设备产生至少一次中断;
int probe_irq_off(unsigned long mask);
参数是probe_irq_on返回的未被注册的中断位掩码;返回值是将要注册中断处理例程的中断号;
如果返回的中断号小于0,则表示发生了多次中断,产生了二义性;
如果返回值为0 ,则未发生中断;
示例:
int count=0;
int irq=0;
do{
unsigned long mask=probe_irq_on();
outb_p(0x10,0x378);//打开中断;
outb_p(0x00,0x376);
outb_p(0x80,0x376);//产生中断;
udelay(5); //延时5ms等待中断被探测到;
outb_p(0x00,0x378);
udelay(5); //延时5ms等待中断被探测到;
irq=probe_irq_off(mask);
if(irq<=0){
printk(KERN_INFO "no irq reported by probe.\n");
}
count++;
}while(irq<=0&&count<5);
2》DIY探测:
int probe_irq=0;
irqreturn_t irq_handle(int irq,void *dev_id){
if(probe_irq==0){
probe_irq=irq;
} else if(probe_irq>0){
probe_irq=-irq;
}
return IRQ_HANDLED;
}
//可能的中断号,一般列举4~5中断号;
int trials[]={3,5,7,9};
//注册结果;
int tried[]={0,0,0,0};
//注册中断;
int i;
for(i=0;i<4;i++){
tried[i]=request_irq(trials[i],irq_handle,IRQF_DISABLED,"probe_irq",NULL);
}
//产生中断;
int count=0,irq=0;
do{
outb_p(0x10,0x378);
outb_p(0x00,0x376);
outb_p(0x80,0x376);
outb_p(0x00,0x378);
udelay(5);//等待中断被探测到;
if(probe<=0){
printk(KERN_INFO "no irq reported by probe.\n");
}
count++;
}while(probe_irq<=0 && count<5);
if(probe_irq<=0){
for(i=0;i<4;i++){
if(tried[i]){
free_irq(trials[i],NULL);
}
}
}
三,中断处理例程;
<linux/interrupt.h>
Linux 内核函数指针:irqreturn_t (*irq_handle)(int irq,void *dev_id);
返回值:
IRQ_NONE:没有要处理的中断;
IRQ_HANDLED:中断已经被处理完毕;
IRQ_RETVAL(handled); //有中断被处理handle大于0;
中断处理例程满足的要求:
1,快速执行,不能有延时;2,能够处理耗时任务;
为了同时满足这两个需求,中断处理例程被设计成顶半部和低半部;
顶半部:
顶半部由中断处理例程来完成,处理快速不耗时部分,调用底半部;
示例:
struct timeval tvl;
irqreturn_t irq_handle(int irq,void *dev_irq){
do_gettimeofday(&tvl);
//调用底半部;
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}
底半部,底半部处理耗时任务,由tasklet或workqueue来实现(tasklet或workqueue由处理器在合适的时刻(系统认为安全的时刻)调用):
void do_tasklet(unsigned long data){
int written=sprintf(buffer,"%8d.%6d\n",tvl.t_sec,tvl.t_usec);
wake_up_interruptible(&wait_queue);
}
DECLARE_TASKLET(tasklet);
四,中断共享;
由于系统的中断线数量有限,为了满足所有设备的中断需求,系统允许设备共享中断线;
1,共享中断注册条件
1》request_irq中的标志位flags必须设置为IRQF_SHARED;
2》为了标识在共享中断线上注册的每个设备的中断处理例程,dev_id参数必须唯一且不能为NULL,如果为NULL卸载某一个中断的时候就会出现问题进而引起内核oops;
2,中断共享注册成功的条件,以下两个条件满足之一即可:
1》将要注册的中断线是空闲的;
2》已经注册到该中断线上的其他任何所有中断都被标识为贡献的;