上一節講了如何實現運行中斷,這些都是系統給做好的,當我們想自己寫個中斷處理程序,去執行自己的代碼,就需要寫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()分析完畢后,接下來開始編寫中斷方式的按鍵驅動