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》已经注册到该中断线上的其他任何所有中断都被标识为贡献的;

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