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