Linux的通用中斷子系統(tǒng)的一個(gè)設(shè)計(jì)原則就是把底層的硬件實(shí)現(xiàn)盡可能地隱藏起來(lái),使得驅(qū)動(dòng)程序的開發(fā)人員不用關(guān)注底層的實(shí)現(xiàn),要實(shí)現(xiàn)這個(gè)目標(biāo),內(nèi)核的開發(fā)者們必須把硬件相關(guān)的內(nèi)容剝離出來(lái),然后定義一些列標(biāo)準(zhǔn)的接口供上層訪問(wèn),上層的開發(fā)人員只要知道這些接口即可完成對(duì)中斷的進(jìn)一步處理和控制。對(duì)底層的封裝主要包括兩部分:
- 實(shí)現(xiàn)不同體系結(jié)構(gòu)中斷入口,這部分代碼通常用asm實(shí)現(xiàn);
- 中斷控制器進(jìn)行封裝和實(shí)現(xiàn);
本文的內(nèi)容正是要討論硬件封裝層的實(shí)現(xiàn)細(xì)節(jié)。我將以ARM體系進(jìn)行介紹,大部分的代碼位于內(nèi)核代碼樹的arch/arm/目錄內(nèi)。
/*****************************************************************************************************/
聲明:本博內(nèi)容均由http://blog.csdn.net/droidphone原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處,謝謝!
/*****************************************************************************************************/
1. CPU的中斷入口
我們知道,arm的異常和復(fù)位向量表有兩種選擇,一種是低端向量,向量地址位于0x00000000,另一種是高端向量,向量地址位于0xffff0000,Linux選擇使用高端向量模式,也就是說(shuō),當(dāng)異常發(fā)生時(shí),CPU會(huì)把PC指針自動(dòng)跳轉(zhuǎn)到始于0xffff0000開始的某一個(gè)地址上:
ARM的異常向量表
地址 |
異常種類 |
FFFF0000 |
復(fù)位 |
FFFF0004 |
未定義指令 |
FFFF0008 |
軟中斷(swi) |
FFFF000C |
Prefetch abort |
FFFF0010 |
Data abort |
FFFF0014 |
保留 |
FFFF0018 |
IRQ |
FFFF001C |
FIQ |
中斷向量表在arch/arm/kernel/entry_armv.S中定義,為了方便討論,下面只列出部分關(guān)鍵的代碼:
- .globl __stubs_start
- __stubs_start:
-
- vector_stub irq, IRQ_MODE, 4
-
- .long __irq_usr @ 0 (USR_26 / USR_32)
- .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
- .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
- .long __irq_svc @ 3 (SVC_26 / SVC_32)
-
- vector_stub dabt, ABT_MODE, 8
-
- .long __dabt_usr @ 0 (USR_26 / USR_32)
- .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
- .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
- .long __dabt_svc @ 3 (SVC_26 / SVC_32)
-
- vector_fiq:
- disable_fiq
- subs pc, lr, #4
- ......
- .globl __stubs_end
- __stubs_end:
-
-
-
- .equ stubs_offset, __vectors_start + 0x200 - __stubs_start
- .globl __vectors_start
- __vectors_start:
- ARM( swi SYS_ERROR0 )
- THUMB( svc #0 )
- THUMB( nop )
- W(b) vector_und + stubs_offset
- W(ldr) pc, .LCvswi + stubs_offset
- W(b) vector_pabt + stubs_offset
- W(b) vector_dabt + stubs_offset
- W(b) vector_addrexcptn + stubs_offset
- W(b) vector_irq + stubs_offset
- W(b) vector_fiq + stubs_offset
-
- .globl __vectors_end
- __vectors_end:
代碼被分為兩部分:
- 第一部分是真正的向量跳轉(zhuǎn)表,位于__vectors_start和__vectors_end之間;
- 第二部分是處理跳轉(zhuǎn)的部分,位于__stubs_start和__stubs_end之間;
- vector_stub irq, IRQ_MODE, 4
以上這一句把宏展開后實(shí)際上就是定義了vector_irq,根據(jù)進(jìn)入中斷前的cpu模式,分別跳轉(zhuǎn)到__irq_usr或__irq_svc。
- vector_stub dabt, ABT_MODE, 8
以上這一句把宏展開后實(shí)際上就是定義了vector_dabt,根據(jù)進(jìn)入中斷前的cpu模式,分別跳轉(zhuǎn)到__dabt_usr或__dabt_svc。
系統(tǒng)啟動(dòng)階段,位于arch/arm/kernel/traps.c中的early_trap_init()被調(diào)用:
- void __init early_trap_init(void)
- {
- ......
- /*
- * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
- * into the vector page, mapped at 0xffff0000, and ensure these
- * are visible to the instruction stream.
- */
- memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
- memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
- ......
- }
以上兩個(gè)memcpy會(huì)把__vectors_start開始的代碼拷貝到0xffff0000處,把__stubs_start開始的代碼拷貝到0xFFFF0000+0x200處,這樣,異常中斷到來(lái)時(shí),CPU就可以正確地跳轉(zhuǎn)到相應(yīng)中斷向量入口并執(zhí)行他們。
圖1.1 Linux中ARM體系的中斷向量拷貝過(guò)程
對(duì)于系統(tǒng)的外部設(shè)備來(lái)說(shuō),通常都是使用IRQ中斷,所以我們只關(guān)注__irq_usr和__irq_svc,兩者的區(qū)別是進(jìn)入和退出中斷時(shí)是否進(jìn)行用戶棧和內(nèi)核棧之間的切換,還有進(jìn)程調(diào)度和搶占的處理等,這些細(xì)節(jié)不在這里討論。兩個(gè)函數(shù)最終都會(huì)進(jìn)入irq_handler這個(gè)宏:
- .macro irq_handler
- #ifdef CONFIG_MULTI_IRQ_HANDLER
- ldr r1, =handle_arch_irq
- mov r0, sp
- adr lr, BSYM(9997f)
- ldr pc, [r1]
- #else
- arch_irq_handler_default
- #endif
- 9997:
- .endm
如果選擇了MULTI_IRQ_HANDLER配置項(xiàng),則意味著允許平臺(tái)的代碼可以動(dòng)態(tài)設(shè)置irq處理程序,平臺(tái)代碼可以修改全局變量:handle_arch_irq,從而可以修改irq的處理程序。這里我們討論默認(rèn)的實(shí)現(xiàn):arch_irq_handler_default,它位于arch/arm/include/asm/entry_macro_multi.S中:
- .macro arch_irq_handler_default
- get_irqnr_preamble r6, lr
- 1: get_irqnr_and_base r0, r2, r6, lr
- movne r1, sp
- @
- @ routine called with r0 = irq number, r1 = struct pt_regs *
- @
- adrne lr, BSYM(1b)
- bne asm_do_IRQ
- ......
get_irqnr_preamble和get_irqnr_and_base兩個(gè)宏由machine級(jí)的代碼定義,目的就是從中斷控制器中獲得IRQ編號(hào),緊接著就調(diào)用asm_do_IRQ,從這個(gè)函數(shù)開始,中斷程序進(jìn)入C代碼中,傳入的參數(shù)是IRQ編號(hào)和寄存器結(jié)構(gòu)指針,這個(gè)函數(shù)在arch/arm/kernel/irq.c中實(shí)現(xiàn):
- /*
- * asm_do_IRQ is the interface to be used from assembly code.
- */
- asmlinkage void __exception_irq_entry
- asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
- {
- handle_IRQ(irq, regs);
- }
到這里,中斷程序完成了從asm代碼到C代碼的傳遞,并且獲得了引起中斷的IRQ編號(hào)。
2. 初始化
與通用中斷子系統(tǒng)相關(guān)的初始化由start_kernel()函數(shù)發(fā)起,調(diào)用流程如下圖所視:
圖2.1 通用中斷子系統(tǒng)的初始化
- 首先,在setup_arch函數(shù)中,early_trap_init被調(diào)用,其中完成了第1節(jié)所說(shuō)的中斷向量的拷貝和重定位工作。
- 然后,start_kernel發(fā)出early_irq_init調(diào)用,early_irq_init屬于與硬件和平臺(tái)無(wú)關(guān)的通用邏輯層,它完成irq_desc結(jié)構(gòu)的內(nèi)存申請(qǐng),為它們其中某些字段填充默認(rèn)值,完成后調(diào)用體系相關(guān)的arch_early_irq_init函數(shù)完成進(jìn)一步的初始化工作,不過(guò)ARM體系沒(méi)有實(shí)現(xiàn)arch_early_irq_init。
- 接著,start_kernel發(fā)出init_IRQ調(diào)用,它會(huì)直接調(diào)用所屬板子machine_desc結(jié)構(gòu)體中的init_irq回調(diào)。machine_desc通常在板子的特定代碼中,使用MACHINE_START和MACHINE_END宏進(jìn)行定義。
- machine_desc->init_irq()完成對(duì)中斷控制器的初始化,為每個(gè)irq_desc結(jié)構(gòu)安裝合適的流控handler,為每個(gè)irq_desc結(jié)構(gòu)安裝irq_chip指針,使他指向正確的中斷控制器所對(duì)應(yīng)的irq_chip結(jié)構(gòu)的實(shí)例,同時(shí),如果該平臺(tái)中的中斷線有多路復(fù)用(多個(gè)中斷公用一個(gè)irq中斷線)的情況,還應(yīng)該初始化irq_desc中相應(yīng)的字段和標(biāo)志,以便實(shí)現(xiàn)中斷控制器的級(jí)聯(lián)。
3. 中斷控制器的軟件抽象:struct irq_chip
正如上一篇文章Linux中斷(interrupt)子系統(tǒng)之一:中斷系統(tǒng)基本原理所述,所有的硬件中斷在到達(dá)CPU之前,都要先經(jīng)過(guò)中斷控制器進(jìn)行匯集,合乎要求的中斷請(qǐng)求才會(huì)通知cpu進(jìn)行處理,中斷控制器主要完成以下這些功能:
- 對(duì)各個(gè)irq的優(yōu)先級(jí)進(jìn)行控制;
- 向CPU發(fā)出中斷請(qǐng)求后,提供某種機(jī)制讓CPU獲得實(shí)際的中斷源(irq編號(hào));
- 控制各個(gè)irq的電氣觸發(fā)條件,例如邊緣觸發(fā)或者是電平觸發(fā);
- 使能(enable)或者屏蔽(mask)某一個(gè)irq;
- 提供嵌套中斷請(qǐng)求的能力;
- 提供清除中斷請(qǐng)求的機(jī)制(ack);
- 有些控制器還需要CPU在處理完irq后對(duì)控制器發(fā)出eoi指令(end of interrupt);
- 在smp系統(tǒng)中,控制各個(gè)irq與cpu之間的親緣關(guān)系(affinity);
通用中斷子系統(tǒng)把中斷控制器抽象為一個(gè)數(shù)據(jù)結(jié)構(gòu):struct irq_chip,其中定義了一系列的操作函數(shù),大部分多對(duì)應(yīng)于上面所列的某個(gè)功能:
- struct irq_chip {
- const char *name;
- unsigned int (*irq_startup)(struct irq_data *data);
- void (*irq_shutdown)(struct irq_data *data);
- void (*irq_enable)(struct irq_data *data);
- void (*irq_disable)(struct irq_data *data);
-
- void (*irq_ack)(struct irq_data *data);
- void (*irq_mask)(struct irq_data *data);
- void (*irq_mask_ack)(struct irq_data *data);
- void (*irq_unmask)(struct irq_data *data);
- void (*irq_eoi)(struct irq_data *data);
-
- int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
- int (*irq_retrigger)(struct irq_data *data);
- int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
- int (*irq_set_wake)(struct irq_data *data, unsigned int on);
-
- void (*irq_bus_lock)(struct irq_data *data);
- void (*irq_bus_sync_unlock)(struct irq_data *data);
-
- void (*irq_cpu_online)(struct irq_data *data);
- void (*irq_cpu_offline)(struct irq_data *data);
-
- void (*irq_suspend)(struct irq_data *data);
- void (*irq_resume)(struct irq_data *data);
- void (*irq_pm_shutdown)(struct irq_data *data);
-
- void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
-
- unsigned long flags;
-
- /* Currently used only by UML, might disappear one day.*/
- #ifdef CONFIG_IRQ_RELEASE_METHOD
- void (*release)(unsigned int irq, void *dev_id);
- #endif
- };
各個(gè)字段解釋如下:
name 中斷控制器的名字,會(huì)出現(xiàn)在 /proc/interrupts中。
irq_startup 第一次開啟一個(gè)irq時(shí)使用。
irq_shutdown 與irq_starup相對(duì)應(yīng)。
irq_enable 使能該irq,通常是直接調(diào)用irq_unmask()。
irq_disable 禁止該irq,通常是直接調(diào)用irq_mask,嚴(yán)格意義上,他倆其實(shí)代表不同的意義,disable表示中斷控制器根本就不響應(yīng)該irq,而mask時(shí),中斷控制器可能響應(yīng)該irq,只是不通知CPU,這時(shí),該irq處于pending狀態(tài)。類似的區(qū)別也適用于enable和unmask。
irq_ack 用于CPU對(duì)該irq的回應(yīng),通常表示cpu希望要清除該irq的pending狀態(tài),準(zhǔn)備接受下一個(gè)irq請(qǐng)求。
irq_mask 屏蔽該irq。
irq_unmask 取消屏蔽該irq。
irq_mask_ack 相當(dāng)于irq_mask + irq_ack。
irq_eoi 有些中斷控制器需要在cpu處理完該irq后發(fā)出eoi信號(hào),該回調(diào)就是用于這個(gè)目的。
irq_set_affinity 用于設(shè)置該irq和cpu之間的親緣關(guān)系,就是通知中斷控制器,該irq發(fā)生時(shí),那些cpu有權(quán)響應(yīng)該irq。當(dāng)然,中斷控制器會(huì)在軟件的配合下,最終只會(huì)讓一個(gè)cpu處理本次請(qǐng)求。
irq_set_type 設(shè)置irq的電氣觸發(fā)條件,例如IRQ_TYPE_LEVEL_HIGH或IRQ_TYPE_EDGE_RISING。
irq_set_wake 通知電源管理子系統(tǒng),該irq是否可以用作系統(tǒng)的喚醒源。
以上大部分的函數(shù)接口的參數(shù)都是irq_data結(jié)構(gòu)指針,irq_data結(jié)構(gòu)的由來(lái)在上一篇文章已經(jīng)說(shuō)過(guò),這里僅貼出它的定義,各字段的意義請(qǐng)參考注釋:
- /**
- * struct irq_data - per irq and irq chip data passed down to chip functions
- * @irq: interrupt number
- * @hwirq: hardware interrupt number, local to the interrupt domain
- * @node: node index useful for balancing
- * @state_use_accessors: status information for irq chip functions.
- * Use accessor functions to deal with it
- * @chip: low level interrupt hardware access
- * @domain: Interrupt translation domain; responsible for mapping
- * between hwirq number and linux irq number.
- * @handler_data: per-IRQ data for the irq_chip methods
- * @chip_data: platform-specific per-chip private data for the chip
- * methods, to allow shared chip implementations
- * @msi_desc: MSI descriptor
- * @affinity: IRQ affinity on SMP
- *
- * The fields here need to overlay the ones in irq_desc until we
- * cleaned up the direct references and switched everything over to
- * irq_data.
- */
- struct irq_data {
- unsigned int irq;
- unsigned long hwirq;
- unsigned int node;
- unsigned int state_use_accessors;
- struct irq_chip *chip;
- struct irq_domain *domain;
- void *handler_data;
- void *chip_data;
- struct msi_desc *msi_desc;
- #ifdef CONFIG_SMP
- cpumask_var_t affinity;
- #endif
- };
根據(jù)設(shè)備使用的中斷控制器的類型,體系架構(gòu)的底層的開發(fā)只要實(shí)現(xiàn)上述接口中的各個(gè)回調(diào)函數(shù),然后把它們填充到irq_chip結(jié)構(gòu)的實(shí)例中,最終把該irq_chip實(shí)例注冊(cè)到irq_desc.irq_data.chip字段中,這樣各個(gè)irq和中斷控制器就進(jìn)行了關(guān)聯(lián),只要知道irq編號(hào),即可得到對(duì)應(yīng)到irq_desc結(jié)構(gòu),進(jìn)而可以通過(guò)chip指針訪問(wèn)中斷控制器。
4. 進(jìn)入流控處理層
進(jìn)入C代碼的第一個(gè)函數(shù)是asm_do_IRQ,在ARM體系中,這個(gè)函數(shù)只是簡(jiǎn)單地調(diào)用handle_IRQ:
- /*
- * asm_do_IRQ is the interface to be used from assembly code.
- */
- asmlinkage void __exception_irq_entry
- asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
- {
- handle_IRQ(irq, regs);
- }
handle_IRQ本身也不是很復(fù)雜:
- void handle_IRQ(unsigned int irq, struct pt_regs *regs)
- {
- struct pt_regs *old_regs = set_irq_regs(regs);
-
- irq_enter();
-
- /*
- * Some hardware gives randomly wrong interrupts. Rather
- * than crashing, do something sensible.
- */
- if (unlikely(irq >= nr_irqs)) {
- if (printk_ratelimit())
- printk(KERN_WARNING "Bad IRQ%u\n", irq);
- ack_bad_irq(irq);
- } else {
- generic_handle_irq(irq);
- }
-
- /* AT91 specific workaround */
- irq_finish(irq);
-
- irq_exit();
- set_irq_regs(old_regs);
- }
irq_enter主要是更新一些系統(tǒng)的統(tǒng)計(jì)信息,同時(shí)在__irq_enter宏中禁止了進(jìn)程的搶占:
- #define __irq_enter() \
- do { \
- account_system_vtime(current); \
- add_preempt_count(HARDIRQ_OFFSET); \
- trace_hardirq_enter(); \
- } while (0)
CPU一旦響應(yīng)IRQ中斷后,ARM會(huì)自動(dòng)把CPSR中的I位置位,表明禁止新的IRQ請(qǐng)求,直到中斷控制轉(zhuǎn)到相應(yīng)的流控層后才通過(guò)local_irq_enable()打開。你可能會(huì)奇怪,既然此時(shí)的irq中斷都是都是被禁止的,為何還要禁止搶占?這是因?yàn)橐紤]中斷嵌套的問(wèn)題,一旦流控層或驅(qū)動(dòng)程序主動(dòng)通過(guò)local_irq_enable打開了IRQ,而此時(shí)該中斷還沒(méi)處理完成,新的irq請(qǐng)求到達(dá),這時(shí)代碼會(huì)再次進(jìn)入irq_enter,在本次嵌套中斷返回時(shí),內(nèi)核不希望進(jìn)行搶占調(diào)度,而是要等到最外層的中斷處理完成后才做出調(diào)度動(dòng)作,所以才有了禁止搶占這一處理。
下一步,generic_handle_irq被調(diào)用,generic_handle_irq是通用邏輯層提供的API,通過(guò)該API,中斷的控制被傳遞到了與體系結(jié)構(gòu)無(wú)關(guān)的中斷流控層:
- int generic_handle_irq(unsigned int irq)
- {
- struct irq_desc *desc = irq_to_desc(irq);
-
- if (!desc)
- return -EINVAL;
- generic_handle_irq_desc(irq, desc);
- return 0;
- }
最終會(huì)進(jìn)入該irq注冊(cè)的流控處理回調(diào)中:
- static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
- {
- desc->handle_irq(irq, desc);
- }
5. 中斷控制器的級(jí)聯(lián)
在實(shí)際的設(shè)備中,經(jīng)常存在多個(gè)中斷控制器,有時(shí)多個(gè)中斷控制器還會(huì)進(jìn)行所謂的級(jí)聯(lián)。為了方便討論,我們把直接和CPU相連的中斷控制器叫做根控制器,另外一些和跟控制器相連的叫子控制器。根據(jù)子控制器的位置,我們把它們分為兩種類型:
- 機(jī)器級(jí)別的級(jí)聯(lián) 子控制器位于SOC內(nèi)部,或者子控制器在SOC的外部,但是是某個(gè)板子系列的標(biāo)準(zhǔn)配置,如圖5.1的左邊所示;
- 設(shè)備級(jí)別的級(jí)聯(lián) 子控制器位于某個(gè)外部設(shè)備中,用于匯集該設(shè)備發(fā)出的多個(gè)中斷,如圖5.1的右邊所示;
圖5.1 中斷控制器的級(jí)聯(lián)類型
對(duì)于機(jī)器級(jí)別的級(jí)聯(lián),級(jí)聯(lián)的初始化代碼理所當(dāng)然地位于板子的初始化代碼中(arch/xxx/mach-xxx),因?yàn)橹灰鞘褂眠@個(gè)板子或SOC的設(shè)備,必然要使用這個(gè)子控制器。而對(duì)于設(shè)備級(jí)別的級(jí)聯(lián),因?yàn)樵撛O(shè)備并不一定是系統(tǒng)的標(biāo)配設(shè)備,所以中斷控制器的級(jí)聯(lián)操作應(yīng)該在該設(shè)備的驅(qū)動(dòng)程序中實(shí)現(xiàn)。機(jī)器設(shè)備的級(jí)聯(lián),因?yàn)榈靡嬗谑孪纫呀?jīng)知道子控制器的硬件連接信息,內(nèi)核可以方便地為子控制器保留相應(yīng)的irq_desc結(jié)構(gòu)和irq編號(hào),處理起來(lái)相對(duì)簡(jiǎn)單。設(shè)備級(jí)別的級(jí)聯(lián)則不一樣,驅(qū)動(dòng)程序必須動(dòng)態(tài)地決定組合設(shè)備中各個(gè)子設(shè)備的irq編號(hào)和irq_desc結(jié)構(gòu)。本章我只討論機(jī)器級(jí)別的級(jí)聯(lián),設(shè)備級(jí)別的關(guān)聯(lián)可以使用同樣的原理,也可以實(shí)現(xiàn)為共享中斷,我會(huì)在本系列接下來(lái)的文章中討論。
要實(shí)現(xiàn)中斷控制器的級(jí)聯(lián),要使用以下幾個(gè)的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)字段和通用中斷邏輯層的API:
irq_desc.handle_irq irq的流控處理回調(diào)函數(shù),子控制器在把多個(gè)irq匯集起來(lái)后,輸出端連接到根控制器的其中一個(gè)irq中斷線輸入腳,這意味著,每個(gè)子控制器的中斷發(fā)生時(shí),CPU一開始只會(huì)得到根控制器的irq編號(hào),然后進(jìn)入該irq編號(hào)對(duì)應(yīng)的irq_desc.handle_irq回調(diào),該回調(diào)我們不能使用流控層定義好的幾個(gè)流控函數(shù),而是要自己實(shí)現(xiàn)一個(gè)函數(shù),該函數(shù)負(fù)責(zé)從子控制器中獲得irq的中斷源,并計(jì)算出對(duì)應(yīng)新的irq編號(hào),然后調(diào)用新irq所對(duì)應(yīng)的irq_desc.handle_irq回調(diào),這個(gè)回調(diào)使用流控層的標(biāo)準(zhǔn)實(shí)現(xiàn)。
irq_set_chained_handler() 該API用于設(shè)置根控制器與子控制器相連的irq所對(duì)應(yīng)的irq_desc.handle_irq回調(diào)函數(shù),并且設(shè)置IRQ_NOPROBE和IRQ_NOTHREAD以及IRQ_NOREQUEST標(biāo)志,這幾個(gè)標(biāo)志保證驅(qū)動(dòng)程序不會(huì)錯(cuò)誤地申請(qǐng)?jiān)搃rq,因?yàn)樵搃rq已經(jīng)被作為級(jí)聯(lián)irq使用。
irq_set_chip_and_handler() 該API同時(shí)設(shè)置irq_desc中的handle_irq回調(diào)和irq_chip指針。
以下例子代碼位于:/arch/arm/plat-s5p/irq-eint.c:
- int __init s5p_init_irq_eint(void)
- {
- int irq;
-
- for (irq = IRQ_EINT(0); irq <= IRQ_EINT(15); irq++)
- irq_set_chip(irq, &s5p_irq_vic_eint);
-
- for (irq = IRQ_EINT(16); irq <= IRQ_EINT(31); irq++) {
- irq_set_chip_and_handler(irq, &s5p_irq_eint, handle_level_irq);
- set_irq_flags(irq, IRQF_VALID);
- }
-
- irq_set_chained_handler(IRQ_EINT16_31, s5p_irq_demux_eint16_31);
- return 0;
- }
該SOC芯片的外部中斷:IRQ_EINT(0)到IRQ_EINT(15),每個(gè)引腳對(duì)應(yīng)一個(gè)根控制器的irq中斷線,它們是正常的irq,無(wú)需級(jí)聯(lián)。IRQ_EINT(16)到IRQ_EINT(31)經(jīng)過(guò)子控制器匯集后,統(tǒng)一連接到根控制器編號(hào)為IRQ_EINT16_31這個(gè)中斷線上??梢钥吹剑涌刂破鲗?duì)應(yīng)的irq_chip是s5p_irq_eint,子控制器的irq默認(rèn)設(shè)置為電平中斷的流控處理函數(shù)handle_level_irq,它們通過(guò)API:irq_set_chained_handler進(jìn)行設(shè)置。如果根控制器有128個(gè)中斷線,IRQ_EINT0--IRQ_EINT15通常占據(jù)128內(nèi)的某段連續(xù)范圍,這取決于實(shí)際的物理連接。IRQ_EINT16_31因?yàn)橐矊儆诟刂破?,所以它的值也?huì)位于128以內(nèi),但是IRQ_EINT16--IRQ_EINT31通常會(huì)在128以外的某段范圍,這時(shí),代表irq數(shù)量的常量NR_IRQS,必須考慮這種情況,定義出超過(guò)128的某個(gè)足夠的數(shù)值。級(jí)聯(lián)的實(shí)現(xiàn)主要依靠編號(hào)為IRQ_EINT16_31的流控處理程序:s5p_irq_demux_eint16_31,它的最終實(shí)現(xiàn)類似于以下代碼:
- static inline void s5p_irq_demux_eint(unsigned int start)
- {
- u32 status = __raw_readl(S5P_EINT_PEND(EINT_REG_NR(start)));
- u32 mask = __raw_readl(S5P_EINT_MASK(EINT_REG_NR(start)));
- unsigned int irq;
-
- status &= ~mask;
- status &= 0xff;
-
- while (status) {
- irq = fls(status) - 1;
- generic_handle_irq(irq + start);
- status &= ~(1 << irq);
- }
- }
在獲得新的irq編號(hào)后,它的最關(guān)鍵的一句是調(diào)用了通用中斷邏輯層的API:generic_handle_irq,這時(shí)它才真正地把中斷控制權(quán)傳遞到中斷流控層中來(lái)。
|