LDD之中斷

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章