能弄清楚下面這個圖,對linux中斷系統的掌握也基本到位了。
最核心的結構體是irq_desc,之前為了易于理解,我們說在Linux內核中有一個中斷數組,對于每一個硬件中斷,都有一個數組項,這個數組就是irq_desc數組。
注意:
如果內核配置了CONFIG_SPARSE_IRQ,那么它就會用基數樹(radix tree)來代替irq_desc數組。SPARSE的意思是“稀疏”,假設大小為1000的數組中只用到2個數組項,那不是浪費嘛?所以在中斷比較“稀疏”的情況下可以用基數樹來代替數組。
1.irq_desc數組
irq_desc結構體在include/linux/irqdesc.h中定義,主要內容如下圖:
每一個irq_desc數組項中都有一個函數:handle_irq,還有一個action鏈表。要理解它們,需要先看中斷結構圖:
外部設備1、外部設備n共享一個GPIO中斷B,多個GPIO中斷匯聚到GIC(通用中斷控制器)的A號中斷,GIC再去中斷CPU。那么軟件處理時就是反過來,先讀取GIC獲得中斷號A,再細分出GPIO中斷B,最后判斷是哪一個外部芯片發生了中斷。
所以,中斷的處理函數來源有三:
① GIC的處理函數:
假設irq_desc[A].handle_irq是XXX_gpio_irq_handler(XXX指廠家),這個函數需要讀取芯片的GPIO控制器,細分發生的是哪一個GPIO中斷(假設是B),再去調用irq_desc[B]. handle_irq。
注意:
irq_desc[A].handle_irq細分出中斷后B,調用對應的irq_desc[B].handle_irq。
顯然中斷A是CPU感受到的頂層的中斷,GIC中斷CPU時,CPU讀取GIC狀態得到中斷A。
② 模塊的中斷處理函數:
比如對于GPIO模塊向GIC發出的中斷B,它的處理函數是irq_desc[B].handle_irq。
BSP開發人員會設置對應的處理函數,一般是handle_level_irq或handle_edge_irq,從名字上看是用來處理電平觸發的中斷、邊沿觸發的中斷。
注意:
導致GPIO中斷B發生的原因很多,可能是外部設備1,可能是外部設備n,可能只是某一個設備,也可能是多個設備。所以irq_desc[B].handle_irq會調用某個鏈表里的函數,這些函數由外部設備提供。這些函數自行判斷該中斷是否自己產生,若是則處理。
③ 外部設備提供的處理函數:
這里說的“外部設備”可能是芯片,也可能只是簡單的按鍵。它們的處理函數由自己驅動程序提供,這是最熟悉這個設備的“人”:它知道如何判斷設備是否發生了中斷,如何處理中斷。
對于共享中斷,比如GPIO中斷B,它的中斷來源可能有多個,每個中斷源對應一個中斷處理函數。所以irq_desc[B]中應該有一個鏈表,存放著多個中斷源的處理函數。
一旦程序確定發生了GPIO中斷B,那么就會從鏈表里把那些函數取出來,一一執行。
這個鏈表就是action鏈表。
對于我們舉的這個例子來說,irq_desc數組如下:
2.irqaction結構體
irqaction結構體在include/linux/interrupt.h中定義,主要內容如下圖:
當調用request_irq、request_threaded_irq注冊中斷處理函數時,內核就會構造一個irqaction結構體。在里面保存name、dev_id等,最重要的是handler、thread_fn、thread。
handler是中斷處理的上半部函數,用來處理緊急的事情。
thread_fn對應一個內核線程thread,當handler執行完畢,Linux內核會喚醒對應的內核線程。在內核線程里,會調用thread_fn函數。
可以提供handler而不提供thread_fn,就退化為一般的request_irq函數。
可以不提供handler只提供thread_fn,完全由內核線程來處理中斷。
也可以既提供handler也提供thread_fn,這就是中斷上半部、下半部。
里面還有一個名為sedondary的irqaction結構體,它的作用以后再分析。
在reqeust_irq時可以傳入dev_id,為何需要dev_id?作用有二:
① 中斷處理函數執行時,可以使用dev_id
② 卸載中斷時要傳入dev_id,這樣才能在action鏈表中根據dev_id找到對應項
所以在共享中斷中必須提供dev_id,非共享中斷可以不提供。
3. irq_data結構體
irq_data結構體在include/linux/irq.h中定義,主要內容如下圖:
它就是個中轉站,里面有irq_chip指針 irq_domain指針,都是指向別的結構體。
比較有意思的是irq、hwirq,irq是軟件中斷號,hwirq是硬件中斷號。比如上面我們舉的例子,在GPIO中斷B是軟件中斷號,可以找到irq_desc[B]這個數組項;GPIO里的第x號中斷,這就是hwirq。
誰來建立irq、hwirq之間的聯系呢?由irq_domain來建立。irq_domain會把本地的hwirq映射為全局的irq,什么意思?比如GPIO控制器里有第1號中斷,UART模塊里也有第1號中斷,這兩個“第1號中斷”是不一樣的,它們屬于不同的“域”──irq_domain。
4.irq_domain結構體
irq_domain結構體在include/linux/irqdomain.h中定義,主要內容如下圖:
當我們后面從設備樹講起,如何在設備樹中指定中斷,設備樹的中斷如何被轉換為irq時,irq_domain將會起到極大的作用。
這里基于入門的解讀簡單講講,在設備樹中你會看到這樣的屬性:
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
它表示要使用gpio1里的第5號中斷,hwirq就是5。
但是我們在驅動中會使用request_irq(irq, handler)這樣的函數來注冊中斷,irq是什么?它是軟件中斷號,它應該從“gpio1的第5號中斷”轉換得來。
誰把hwirq轉換為irq?由gpio1的相關數據結構,就是gpio1對應的irq_domain結構體。
irq_domain結構體中有一個irq_domain_ops結構體,里面有各種操作函數,主要是:
① xlate
用來解析設備樹的中斷屬性,提取出hwirq、type等信息。
② map
把hwirq轉換為irq。
5.irq_chip結構體
irq_chip結構體在include/linux/irq.h中定義,主要內容如下圖:
這個結構體跟“chip”即芯片相關,里面各成員的作用在頭文件中也列得很清楚,摘錄部分如下:
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
我們在request_irq后,并不需要手工去使能中斷,原因就是系統調用對應的irq_chip里的函數幫我們使能了中斷。
我們提供的中斷處理函數中,也不需要執行主芯片相關的清中斷操作,也是系統幫我們調用irq_chip中的相關函數。
但是對于外部設備相關的清中斷操作,還是需要我們自己做的。
就像上面圖里的“外部設備1“、“外部設備n”,外設備千變萬化,內核里可沒有對應的清除中斷操作。






