一,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》已經註冊到該中斷線上的其他任何所有中斷都被標識爲貢獻的;