6.分析request_irq和free_irq函數如何注冊注銷中斷(詳解)


上一節講了如何實現運行中斷,這些都是系統給做好的,當我們想自己寫個中斷處理程序,去執行自己的代碼,就需要寫irq_desc->action->handler,然后通過request_irq()來向內核申請注冊中斷

本節目標:

     分析request_irq()如何申請注冊中斷,free_irq()如何注銷中斷

 

1.request_irq()位於kernel/irq/ manage .c,函數原型如下:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

參數說明:

unsigned int  irq:為要注冊中斷服務函數的中斷號,比如外部中斷0就是16,定義在mach/irqs.h

irq_handler_t  handler:為要注冊的中斷服務函數,就是(irq_desc+ irq )->action->handler

unsigned long  irqflags: 觸發中斷的參數,比如邊沿觸發, 定義在linux/interrupt.h。         

const char  *devname:中斷程序的名字,使用cat /proc/interrupt 可以查看中斷程序名字

void  *dev_id:傳入中斷處理程序的參數,注冊共享中斷時不能為NULL,因為卸載時需要這個做參數,避免卸載其它中斷服務函數

1.1request_irq代碼如下:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
{
       struct irqaction *action;
       ... ...
       action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);  //注冊irqaction結構體類型的action
       if (!action)
                   return -ENOMEM;

/* 將帶進來的參數賦給action   */
         action->handler = handler;     
         action->flags = irqflags;
         cpus_clear(action->mask);
         action->name = devname;
         action->next = NULL;
         action->dev_id = dev_id;

         select_smp_affinity(irq);
     ... ...
         retval = setup_irq(irq, action);   // 進入setup_irq(irq, action),設置irq_ desc[irq]->action
   
if (retval) kfree(action); return retval; }

從上面分析,request_irq()函數主要注冊了一個irqaction型action,然后把參數都賦給這個action,最后進入setup_irq(irq, action)設置irq_ desc[irq]->action 

1.2我們來看看setup_irq(irq, action)如何設置irq_ desc[irq]->action的:

int setup_irq(unsigned int irq, struct irqaction *new)
{
        struct irq_desc *desc = irq_desc + irq;   //根據中斷號找到irq_ desc[irq]
        ... ...
        p = &desc->action;                 //指向desc->action
        old = *p;
         if (old) {                 //判斷action是否為空
                    /*判斷這個中斷是否支持共享 (IRQF_SHARED)*/
                   if (!((old->flags & new->flags) & IRQF_SHARED) ||
                       ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
                            old_name = old->name;
                            goto mismatch;                  //不支持,則跳轉
                   }

#if defined(CONFIG_IRQ_PER_CPU)
                   /* All handlers must agree on per-cpuness */
                   if ((old->flags & IRQF_PERCPU) !=
                       (new->flags & IRQF_PERCPU))
                            goto mismatch;
#endif

                   /*找到action鏈表尾處,后面用於添加 新的中斷服務函數(*new) */
                   do {
                            p = &old->next;
                            old = *p;
                   } while (old);
                   shared = 1;        //表示該中斷支持共享,添加新的action,否則直接賦值新的action
         }

         *p = new;             //指向新的action

 ... ...

         if (!shared) {                  //若該中斷不支持共享
                   irq_chip_set_defaults(desc->chip);    //更新desc->chip,將為空的成員設置默認值       

#if defined(CONFIG_IRQ_PER_CPU)
                     if (new->flags & IRQF_PERCPU)
                              desc->status |= IRQ_PER_CPU;
#endif

                   /* Setup the type (level, edge polarity) if configured: */
                   if (new->flags & IRQF_TRIGGER_MASK) {
                     if (desc->chip && desc->chip->set_type)        // desc->chip->set_type設置為中斷引腳
                          desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
                            else
                                     printk(KERN_WARNING "No IRQF_TRIGGER set_type "

                                            "function for IRQ %d (%s)\n", irq,

                                            desc->chip ? desc->chip->name :

                                            "unknown");
                   } else
                            compat_irq_chip_set_default_handler(desc);

                   desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
                                       IRQ_INPROGRESS);

 
                   if (!(desc->status & IRQ_NOAUTOEN)) {
                            desc->depth = 0;
                            desc->status &= ~IRQ_DISABLED;
                            if (desc->chip->startup)
                                     desc->chip->startup(irq);     //開啟中斷
                            else
                                     desc->chip->enable(irq);     //使能中斷

                   } else

                           /* Undo nested disables: */
                            desc->depth = 1;
         }

從上面可以看出setup_irq(irq, action)主要是將action中斷服務函數放在irq_ desc[irq]->action中,

然后設置中斷引腳:   

desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);

最后[開啟/使能]中斷:

 desc->chip->[startup(irq) /enable(irq)];     //[開啟/使能]中斷

我們以外部中斷0的desc[16]->chip->set_type為例,來看看它是如何初始化中斷引腳的:

s3c_irqext_type(unsigned int irq, unsigned int type)
{
         void __iomem *extint_reg;
         void __iomem *gpcon_reg;
         unsigned long gpcon_offset, extint_offset;
         unsigned long newvalue = 0, value;
       if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))    //找到寄存器
         {
                   gpcon_reg = S3C2410_GPFCON;      
                   extint_reg = S3C24XX_EXTINT0;      // EXTINT0對應中斷0~中斷7
                   gpcon_offset = (irq - IRQ_EINT0) * 2;    //找到gpcon寄存器的相應位偏移量
                   extint_offset = (irq - IRQ_EINT0) * 4;    //找到extint寄存器的相應位偏移量
         }
    else if(... ...)                    //找到其它的EINT4~23的寄存器

/*將GPIO引腳設為中斷引腳*/
value = __raw_readl(gpcon_reg);  
value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);  //相應位設置0x02
switch (type)          //設置EXTINT0中斷模式
{
case IRQT_NOEDGE:            //未指定的中斷模式
                    printk(KERN_WARNING "No edge setting!\n");
                    break; 

           case IRQT_RISING:           //上升沿觸發,設置EXTINT0相應位為0x04
                    newvalue = S3C2410_EXTINT_RISEEDGE;
                    break;

           case IRQT_FALLING:     //下降沿觸發,設置EXTINT0相應位為0x02
                    newvalue = S3C2410_EXTINT_FALLEDGE;
                    break;
 
           case IRQT_BOTHEDGE:  //雙邊沿觸發,設置EXTINT0相應位為0x06
                    newvalue = S3C2410_EXTINT_BOTHEDGE;
                    break;

           case IRQT_LOW:                   //低電平觸發,設置EXTINT0相應位為0x00
                    newvalue = S3C2410_EXTINT_LOWLEV;
                    break;

           case IRQT_HIGH:                 //高電平觸發,設置EXTINT0相應位為0x01
                    newvalue = S3C2410_EXTINT_HILEV;
                    break;
           default:          
}

/*更新EXTINT0相應位*/
value = __raw_readl(extint_reg);
value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);  //相應位設置
__raw_writel(value, extint_reg);    //向extint_reg寫入value值
return 0;
}

通過上面分析,就是將action->flags帶入到desc[16]->chip->set_type里面,根據不同的中斷來設置寄存器模式

 

2.request_irq()是注冊中斷,同樣的卸載中斷的函數是free_irq()

free_irq()也位於kernel/irq/ manage .c,函數原型如下:

free_irq(unsigned int irq, void *dev_id);

參數說明:

unsigned int  irq:要卸載的中斷號

void  *dev_id:這個是要卸載的中斷action下的哪個服務函數,

2.1 free_irq()代碼如下:

void free_irq(unsigned int irq, void *dev_id)
{
   struct irq_desc *desc;
   struct irqaction **p;
   unsigned long flags;
   irqreturn_t (*handler)(int, void *) = NULL;
 
   WARN_ON(in_interrupt());
   if (irq >= NR_IRQS)
          return;

   desc = irq_desc + irq;                //根據中斷號,找到數組
   spin_lock_irqsave(&desc->lock, flags);
   p = &desc->action;          //p指向中斷里的action鏈表

   for (;;) {
        struct irqaction *action = *p;

          if (action) {        //在action鏈表中找到與參數dev_id相等的中斷服務函數
                struct irqaction **pp = p;
                p = &action->next;       
                if (action->dev_id != dev_id)    //直到找dev_id才執行下面,進行卸載
                  continue;          
                *pp = action->next;      //指向下個action成員,將當前的action釋放掉
                #ifdef CONFIG_IRQ_RELEASE_METHOD
                   if (desc->chip->release)   //執行chip->release釋放中斷服務函數相關的東西
                         desc->chip->release(irq, dev_id);
                #endif
          if (!desc->action) {   //判斷當前action成員是否為空,表示沒有中斷服務函數
                       desc->status |= IRQ_DISABLED;
                       if (desc->chip->shutdown)       //執行chip->shutdown關閉中斷
              desc->chip->shutdown(irq);
                 else                          //執行chip-> disable禁止中斷
               desc->chip->disable(irq);
                                    }

                  spin_unlock_irqrestore(&desc->lock, flags);
                  unregister_handler_proc(irq, action);
            synchronize_irq(irq);
            if (action->flags & IRQF_SHARED)
              handler = action->handler;
                kfree(action);
                 return;

               }

   printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);//沒有找到要卸載的action成員

   spin_unlock_irqrestore(&desc->lock, flags);

   return;

       }

#ifdef CONFIG_DEBUG_SHIRQ
         if (handler) {

                   /*

                    * It's a shared IRQ -- the driver ought to be prepared for it

                    * to happen even now it's being freed, so let's make sure....

                    * We do this after actually deregistering it, to make sure that

                    * a 'real' IRQ doesn't run in parallel with our fake

                    */
                   handler(irq, dev_id);

         }
#endif
}

從上面分析,free_irq()函數主要通過irq和dev_id來找要釋放的中斷action

若釋放的中斷action不是共享的中斷(為空),則執行:

 

*pp = action->next;      //指向下個action成員,將當前的action釋放掉
desc->chip->release(irq, dev_id);    //執行chip->release釋放中斷服務函數相關的東西

desc->status |= IRQ_DISABLED;            //設置desc[irq]->status標志位
desc->chip->[shutdown(irq)/ desible(irq)];    //關閉/禁止中斷

 

若釋放的中斷action是共享的中斷(還有其它中斷服務函數)的話就只執行:

*pp = action->next;      //指向下個action成員,將當前的action釋放掉
desc->chip->release(irq, dev_id);    //執行chip->release釋放中斷服務函數相關的東西

 

request_irq()和free_irq()分析完畢后,接下來開始編寫中斷方式的按鍵驅動

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM