LINUX-核心-中斷分析-中斷向量表(2)-mips
mips中斷概念
在《MIPS體系結構透視》的第5章說到,在MIPS中,中斷、陷阱、系統呼叫和任何可以中斷程式正常執行流的情況全被都被稱為異常。
以上這種統一到“異常”的概念及其邏輯當然會體現在MIPS的異常入口點的設計中,特別如MIPS中斷入口點的引出。
MIPS的異常入口點(中斷入口點)及異常向量概念的引出
非向量化中斷
根據《MIPS體系結構透視》第5章介紹,類似x86這樣的CISC處理器根據所發生的異常(用x86的話來說就是同步中斷+非同步中斷)的種類把CPU指向不同的入口,具體到非同步中斷,也即是根據啟用的中斷輸入訊號在不同的入口點處理這些中斷,這種做法叫做“向量化中斷”,而MIPS則不這樣做,理由有3:
- CISC需要花費時間指向不同的中斷入口,然後作業系統軟體再從入口處獲得一個序號,再花些時間調到通用中斷處理程式。MIPS認為這是一件繁瑣的事情;
- 即使是用硬體來分析這麼多種中斷,這個硬體會異常複雜,MIPS認為可以用軟體以其他的邏輯,更高的效率來應對這個問題;
- 在類似MIPS這樣的RISC架構中,CPU比外設快得實在太多。考慮到中斷服務程式一般都要和外設打交道,外設相對“慢”的速度導致這種和外設的互動要花費大量的時間,此時,MIPS自己實現的中斷入口分配程式碼的耗時相對而言就不算是什麼效能瓶頸了,不需要去用所謂“向量化中斷”;
- 對於應用於嵌入式系統的RISC體系來說,要面對的外設多種多樣,且各種形式的嵌入式硬體的外設都變化多端,如果給每一種可能的外設的中斷都像x86那樣使用“向量化中斷”的話,無法避免會很浪費向量資源。
綜合以上理由,除了個別特殊中斷外,MIPS將幾乎所有的外部中斷都看作是同一種異常,同時也就為幾乎所有的外部中斷都只准備了一個所謂異常入口。而在x86系統中,32~238的中斷向量全部供給了外部中斷使用。
向量化異常–異常向量
雖然將所有外部中斷都看作是同一種異常,但是MIPS體系結構中異常也是分很多種的。因此,雖然外部中斷沒有被向量化,但是MIPS中還是將異常給向量化了。根據《MIPS體系結構透視》第3章表3-2的介紹,通過Cause暫存器中的Excode值,MIPS定義並可以識別32種異常。MIPS為這32種異常準備了一個所謂異常向量表,這個異常向量表體現到核心程式碼裡面本質上就是一個叫做exception_handlers[32]的陣列。該陣列的每一項都是一個函式指標,其中0號異常即為所有外部中斷的統一入口點。
當異常發生後,位於地址0x80000180處的函式except_vec3_generic()會根據Cause暫存器中的ExcCode位的值,呼叫exception_handlers[32]中相應成員的那個函式指標執行相關的處理函式。這個 exception_handlers[32]是traps.c下面定義的一個全域性變數。
---------------------------------- arch\mips\kernel\traps.c
unsigned long exception_handlers[32];
MIPS體系異常向量表的初始化
雖然中斷沒有被向量化,但是異常被向量化了,所以MIPS體系結構依然存在一個對異常向量表的初始化,而且同樣是在trap_init()中進行。
在中我們說到x86的trap_init()本質上是在給idt_table賦值,而MIPS體系中不存在門的概念,取而代之的是上一節提到的exception_handlers[]陣列。
在x86的trap_init()中呼叫了一系列的set_intr_gate()、set_system_intr_gate()、set_task_gate()函式來設定各種門。而在MIPS的trap_init()中則是全部使用set_except_vector(int n, void *addr)函式來為32個數組成員賦值。而賦值的方式就是直接給exception_handlers[]這個全域性變數的各個成員賦值—-實在是夠直接!!!
初始化大藍圖
trap_init()函式設定異常處理函式
這個trap_init()本身一直都是一個體系相關函式,在MIPS體系中,該函式有兩個主要工作:
- 將統一異常處理入口函式excep_vec3_generic()載入到地址BASE+0X180;
- 為exception_handlers[]中的32種異常設定各自的處理函式;
early_irq_init()函式對IRQ子系統的前期初始化
函式本身是體系平臺無關的,主要任務也是為所有irq_desc設定預設的irq_chip為no_irq_chip,預設的電源處理函式為handle_bad_irq()等,這些內容的程式碼各個體系都是一樣的。
------kernel/irq/irqdesc.c------->
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
struct module *owner)
{
int cpu;
desc->irq_data.irq = irq;
desc->irq_data.chip = &no_irq_chip;
desc->irq_data.chip_data = NULL;
desc->irq_data.handler_data = NULL;
desc->irq_data.msi_desc = NULL;
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
desc->handle_irq = handle_bad_irq;
desc->depth = 1;
desc->irq_count = 0;
desc->irqs_unhandled = 0;
desc->name = NULL;
desc->owner = owner;
for_each_possible_cpu(cpu)
*per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
desc_smp_init(desc, node);
}
第二步就與硬體平臺相關了,呼叫arch_early_irq_init()進行一些平臺相關的irq系統前期初始化。在x86體系中,arch_early_irq_init()主要是處理的8259和IOAPC的差異相關,這導致對前16個固定IRQ的處理會有些區別,但是這個函式基本上僅用於x86和ppc體系,包括MIPS在內的其他體系基本上arch_early_irq_init()函式就直接返回了,乾脆直接取名叫做x86_arch_early_irq_init()。
init_IRQ()對外設中斷進行初始化
init_IRQ()是一個體系相關函式,在MIPS中,除了將所有的NR_IRQS個irq_desc中設定noprobe外,就是呼叫一個MIPS專用的arch_init_irq()函式,這個函式是體系且平臺相關的,裡面主要是設定一堆IRQ的irq_chip和電流處理函式,在arch_init_irq()內部,僅有mips_cpu_irq_init()是僅體系相關(體系相關但平臺無關)。
下面以QCA的WIFI晶片AR955X為例進行簡要說明。
mips_cpu_irq_init()函式固定設定IP2~IP8這6個外部中斷引腳的IRQ
mips_cpu_irq_init()函式基本上在所有的MIPS體系中都應該是一樣的,與不同廠家不同平臺無關,這從該函式位於檔案arch/mips/kernel/irq_cpu.c中這一點也可以得到證實。從程式碼來看,該函式一股腦地將IP2~IP8這6個外部中斷的irq_chip都設定為叫做“MIPS”的mips_cpu_irq_controller型中斷控制器。
ath_misc_irq_init(ATH_MISC_IRQ_BASE)在AR955X中設定MISC相關IRQ
ath_misc_irq_init(ATH_MISC_IRQ_BASE) 在AR955X中設定從IRQ16開始的MISC相關的27個IRQ。將這27個IRQ的irq_chip都設定為叫做“ATH MISC”的ath_misc_irq_controller型中斷控制器,將電流處理函式設定為handle_percpu_irq()。
ath_gpio_irq_init(ATH_GPIO_IRQ_BASE)在AR955X中設定GPIO相關IRQ
ath_gpio_irq_init(ATH_GPIO_IRQ_BASE)在AR955X中負責設定從43開始的GPIO相關的32個IRQ。將這32個IRQ的irq_chip都設定為叫做“ATH GPIO”的ath_gpio_intr_controller型中斷控制器,將電流處理函式設定為handle_percpu_irq()。
ath_arch_init_irq()在AR955X中負責無線外界晶片的IRQ
955x中5G無線外接晶片使用IRQ2號,因此事實上該函式覆蓋了mips_cpu_irq_init()對IRQ2的設定,對IRQ2進行了重新設定,將IRQ2的irq_chip設定為叫做”dummy”的dummy_irq_chip型中斷控制器,將電流處理函式設定為handle_percpu_irq()。