linux-2.6.26核心中ARM中斷實現詳解(3)
作者:劉洪濤,華清遠見嵌入式學院金牌講師。
四、中斷處理模型
要想弄清楚desc->handle_irq(irq, desc)和我們註冊的中斷有什麼關聯,就要瞭解中斷處理模型了。
4.1 中斷處理模型結構
中斷處理模型如所示,
其中NR_IRQS表示最大的中斷號,在include/asm/arch/irq.h中定義。
irq_desc[]是一個指向irq_desc_t結構的數組, irq_desc_t結構是各個裝置插斷服務常式的描述符。Irq_desc_t結構體中的成員action指向該中斷號對應的irqaction結構體鏈表。Irqaction結構體定義在include/linux/interrupt.h中,如下:
truct irqaction {
irq_handler_t handler; //中斷處理函數,註冊時提供
unsigned long flags; //中斷標誌,註冊時提供
cpumask_t mask; //中斷掩碼
const char *name; //中斷名稱
void *dev_id; //裝置id,本文後面部分介紹中斷共用時會詳細說明這個參數的作用
struct irqaction *next; //如果有中斷共用,則繼續執行,
int irq; //中斷號,註冊時提供
struct proc_dir_entry *dir; //指向IRQn相關的/proc/irq/n目錄的描述符
};
在註冊中斷號為irq的中斷服務程式時,系統會根據註冊參數封裝相應的irqaction結構體。並把中斷號為irq的irqaction結構體寫入irq_desc [irq]->action。這樣就把裝置的插斷要求號與該裝置的插斷服務常式irqaction聯絡在一起了。樣當CPU接收到插斷要求後,就可以根據中斷號通過irq_desc []找到該裝置的中斷服務程式。
4.2 中斷共用的處理模型
共用中斷的不同裝置的iqraction結構體都會添加進該中斷號對應的irq_desc結構體的action成員所指向的irqaction鏈表內。當核心發生中斷時,它會依次調用該鏈表內所有的handler函數。因此,若驅動程式需要使用共用中斷機制,其中斷處理函數必須有能力識別是否是自己的硬體產生了中斷。通常是通過讀取該硬體裝置提供的中斷flag標誌位進行判斷。也就是說不是任何裝置都可以做為中斷共用源的,它必須能夠通過的它的中斷flag判斷出是否發生了中斷。
中斷共用的註冊方法是:
int request_irq(unsigned int irq, irq_handler_t handler,
IRQF_SHARED, const char *devname, void *dev_id)
很多權威資料中都提到,中斷共用註冊時的註冊函數中的dev_id參數是必不可少的,並且dev_id的值必須唯一。那麼這裡提供唯一的dev_id值的究竟是做什麼用的?
根據我們前面中斷模型的知識,可以看出發生中斷時,核心並不判斷究竟是共用中斷線上的哪個裝置產生了中斷,它會迴圈執行所有該中斷線上註冊的中斷處理函數(即irqaction->handler函數)。因此irqaction->handler函數有責任識別出是否是自己的硬體裝置產生了中斷,然後再執行該中斷處理函數。通常是通過讀取該硬體裝置提供的中斷flag標誌位進行判斷。那既然kernel迴圈執行該中斷線上註冊的所有irqaction->handler函數,把識別究竟是哪個硬體裝置產生了中斷這件事交給中斷處理函數本身去做,那request_irq的dev_id參數究竟是做什麼用的?
很多資料中都建議將裝置結構指標作為dev_id參數。在中斷到來時,迅速地根據硬體寄存器中的資訊比照傳入的dev_id參數判斷是否是本裝置的中斷,若不是,應迅速返回。這樣的說法沒有問題,也是我們編程時都遵循的方法。但事實上並不能夠說明為什麼中斷共用必須要設定dev_id。
下面解釋一下dev_id參數為什麼必須的,而且是必須唯一的。
當調用free_irq登出中斷處理函數時(通常卸載驅動時其中斷處理函數也會被登出掉),因為dev_id是唯一的,所以可以通過它來判斷從共用中斷線上的多個中斷處理常式中刪除指定的一個。如果沒有這個參數,那麼kernel不可能知道給定的中斷線上到底要刪除哪一個處理常式。
登出函數定義在Kernel/irq/manage.c中定義:
void free_irq(unsigned int irq, void *dev_id)
五、S3C2410子中斷的註冊的實現
5.1 S3C2410子中斷註冊問題的提出
參看3.5節中判斷中斷號的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對於INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等帶有子中斷的向量,INTOFFSET無法判斷出具體的中斷號。平台留給我們的註冊方法如下:
在include/asm/arch/irqs.h中有類似如下定義:
/* interrupts generated from the external interrupts sources */
#define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
#define IRQ_EINT5 S3C2410_IRQ(33)
#define IRQ_EINT6 S3C2410_IRQ(34)
#define IRQ_EINT7 S3C2410_IRQ(35)
#define IRQ_EINT8 S3C2410_IRQ(36)
#define IRQ_EINT9 S3C2410_IRQ(37)
#define IRQ_EINT10 S3C2410_IRQ(38)
#define IRQ_EINT11 S3C2410_IRQ(39)
#define IRQ_EINT12 S3C2410_IRQ(40)
#define IRQ_EINT13 S3C2410_IRQ(41)
#define IRQ_EINT14 S3C2410_IRQ(42)
#define IRQ_EINT15 S3C2410_IRQ(43)
#define IRQ_EINT16 S3C2410_IRQ(44)
#define IRQ_EINT17 S3C2410_IRQ(45)
#define IRQ_EINT18 S3C2410_IRQ(46)
#define IRQ_EINT19 S3C2410_IRQ(47)
#define IRQ_EINT20 S3C2410_IRQ(48) /* 64 */
#define IRQ_EINT21 S3C2410_IRQ(49)
#define IRQ_EINT22 S3C2410_IRQ(50)
#define IRQ_EINT23 S3C2410_IRQ(51)
可以看到平台為每種子中斷都定義了中斷號,如果你想實現EINT10的中斷註冊,直接按照IRQ_EINT10這個中斷號註冊都可以了。那麼平台代碼是如何?這部分中斷註冊的呢?
5.2 S3C2410子中斷註冊問題的解決
/*arch/arm/plat-s3c24xx/irq.c*/
void __init s3c24xx_init_irq(void)
{……
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
……
}
平台在初始化時會調用到s3c24xx_init_irq,在此函數中實現了對EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的註冊。下面看看這些帶有子中斷的中斷號對應的處理函數的內容。以IRQ_EINT4t7為例,其它情況類似。
/*arch/arm/plat-s3c24xx/irq.c*/
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */
/* eintpnd中可以有多個位同時置1,這一點和intpnd的只能有1個位置1是不一樣的 */
while (eintpnd) { //迴圈執行所有置位的子中斷
irq = __ffs(eintpnd); //算出第一個不為0的位,類似arm v5後的clz前置0的作用
eintpnd &= ~(1<<irq);//清除相應的位
irq += (IRQ_EINT4 - 4);//算出對應的中斷號
desc_handle_irq(irq, irq_desc + irq);//執行對應子中斷的註冊函數
}
}
從上面的函數可以看出子中斷是如何註冊及被調用到的。有人可能會問為何不在include/asm/arch-s3c2410/entry-macro.s 檔案中get_irqnr_and_base函數判斷中斷號時,直接算出對應的子中斷號,就可以直接找到子中斷處理了呢?
原因是: get_irqnr_and_base是平台給系統提供的函數,對於多個子中斷同時置位的情況無法通過一個值返回(因為子中斷中,如eintpnd是可以多個位同時置位的))。而intpnd則沒有這個問題。
本文來自CSDN部落格:http://blog.csdn.net/farsight2009/archive/2009/07/02/4315273.aspx