看了一些網(wǎng)絡(luò)上關(guān)于linux中斷實現(xiàn)的文章,感覺有一些寫的非常好,在這里首先感謝他們的無私付出,然后也想再補充自己對一些問題的理解。先從函數(shù)注冊引出問題吧。 一、中斷注冊方法 在linux內(nèi)核中用于申請中斷的函數(shù)是request_irq(),函數(shù)原型在Kernel/irq/manage.c中定義: int request_irq(unsigned int irq, irq_handler_t handler, irq是要申請的硬件中斷號。 handler是向系統(tǒng)注冊的中斷處理函數(shù),是一個回調(diào)函數(shù),中斷發(fā)生時,系統(tǒng)調(diào)用這個函數(shù),dev_id參數(shù)將被傳遞給它。 irqflags是中斷處理的屬性,若設(shè)置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已經(jīng)不支持了),則表示中斷處理程序是快速處理程序,快速處理程序被調(diào)用時屏蔽所有中斷,慢速處理程序不屏蔽;若設(shè)置了IRQF_SHARED (老版本中的SA_SHIRQ),則表示多個設(shè)備共享中斷,若設(shè)置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示對系統(tǒng)熵有貢獻,對系統(tǒng)獲取隨機數(shù)有好處。(這幾個flag是可以通過或的方式同時使用的) dev_id在中斷共享時會用到,一般設(shè)置為這個設(shè)備的設(shè)備結(jié)構(gòu)體或者NULL。 devname設(shè)置中斷名稱,在cat /proc/interrupts中可以看到此名稱。 request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數(shù)指針為NULL,返回-EBUSY表示中斷已經(jīng)被占用且不能共享。 關(guān)于中斷注冊的例子,大家可在內(nèi)核中搜索下request_irq。 在編寫驅(qū)動的過程中,比較容易產(chǎn)生疑惑的地方是: 1、中斷向量表在什么位置?是如何建立的? 本文以2.6.26內(nèi)核和S3C2410處理器為例,為大家講解這幾個問題。 二、異常向量表的建立 在ARM V4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0000??梢酝ㄟ^CP15協(xié)處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應(yīng)關(guān)系如下: V=0 ~ 0x00000000~0x0000001C arch/arm/mm/proc-arm920.S中 .section ".text.init", #alloc, #execinstr //bit13=1 中斷向量表基址為0xFFFF0000。R0的值將被付給CP15的C1. 在linux中,向量表建立的函數(shù)為: init/main.c->start_kernel()->trap_init() void __init trap_init(void) 在2.6.26內(nèi)核中CONFIG_VECTORS_BASE最初是在各個平臺的配置文件中設(shè)定的,如: arch/arm/configs/s3c2410_defconfig中 CONFIG_VECTORS_BASE=0xffff0000 __vectors_end 至 __vectors_start之間為異常向量表。 位于arch/arm/kernel/entry-armv.S .globl __vectors_start __stubs_end 至 __stubs_start之間是異常處理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它們中間。 stubs_offset值如下: .equ stubs_offset, __vectors_start + 0x200 - __stubs_start stubs_offset是如何確定的呢?(引用網(wǎng)絡(luò)上的一段比較詳細的解釋) 當(dāng)匯編器看到B指令后會把要跳轉(zhuǎn)的標(biāo)簽轉(zhuǎn)化為相對于當(dāng)前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發(fā)生了代碼搬移,所以如果中斷向量表中仍然寫成b vector_irq,那么實際執(zhí)行的時候就無法跳轉(zhuǎn)到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。我們把搬移前的中斷向量表中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后 vectors_start在0xffff0000處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷 向量中的中斷入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再減去中斷入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,對于括號內(nèi)的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的 vectors_start+200-stubs_start就應(yīng)該是stubs_offset,實際上在entry-armv.S中也是這樣定義的.
三、中斷處理過程 這一節(jié)將以S3C2410為例,描述linux-2.6.26內(nèi)核中,從中斷開始,中斷是如何一步一步執(zhí)行到我們注冊函數(shù)的。 3.1 中斷向量表 arch/arm/kernel/entry-armv.S __vectors_start: 中斷發(fā)生后,跳轉(zhuǎn)到b vector_irq + stubs_offset的位置執(zhí)行。注意現(xiàn)在的向量表的初始位置是0xffff0000。 3.2 中斷跳轉(zhuǎn)的入口位置 arch/arm/kernel/entry-armv.S .globl __stubs_start 上面代碼中vector_stub宏的定義為: .macro vector_stub, name, mode, correction=0 @ 用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我們中斷處理的入口位置為vector_irq(宏里面的vector_/name)。 3.3 __irq_usr的實現(xiàn) arch/arm/kernel/entry-armv.S __irq_usr: irq_handler @中斷處理,我們最關(guān)心的地方,3.4節(jié)有實現(xiàn)過程。 mov why, #0 b ret_to_user @中斷處理完成,返回中斷產(chǎn)生的位置,3.7節(jié)有實現(xiàn)過程 上面代碼中的usr_entry是一個宏,主要實現(xiàn)了將usr模式下的寄存器、中斷返回地址保存到堆棧中。 .macro usr_entry stmib sp, {r1 - r12} str r1, [sp] @ save the "real" r0 copied @ @ @ 上面的這段代碼主要在填充結(jié)構(gòu)體pt_regs ,這里提到的struct pt_regs,在include/asm/ptrace.h中定義。此時sp指向struct pt_regs。 struct pt_regs { 3.4 irq_handler的實現(xiàn)過程,arch/arm/kernel/entry-armv.S .macro irq_handler 3.5 get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s .macro get_irqnr_and_base, irqnr, irqstat, base, tmp adds /irqnr, /irqnr, #IRQ_EINT0 @加上中斷號的基準(zhǔn)數(shù)值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況,(子中斷的問題后面會有講解)。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關(guān)的代碼決定的,和硬件中斷掛起寄存器中的中斷號是不等的。 1002: 3.6 asm_do_IRQ實現(xiàn)過程,arch/arm/kernel/irq.c asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc) 上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標(biāo)識。那么這個標(biāo)識的含義如何理解呢? #include <asm/linkage.h>//各個具體處理器在此文件中定義asmlinkage #ifndef asmlinkage//如果以前沒有定義asmlinkage 對于ARM處理器的<asm/linkage.h>,沒有定義asmlinkage,所以沒有意義(不要以為參數(shù)是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調(diào)用標(biāo)準(zhǔn),通過寄存器傳遞的)。 但對于X86處理器的<asm/linkage.h>中是這樣定義的: #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) 表示函數(shù)的參數(shù)傳遞是通過堆棧完成的。 3.7 描述3.3節(jié)中的ret_to_user 中斷返回過程,/arch/arm/kernel/entry-common.S ENTRY(ret_to_user) @ slow_restore_user_regs
ldr r1, [sp, #S_PSR] @ get calling cpsr ldr lr, [sp, #S_PC]! @ get pc msr spsr_cxsf, r1 @ save in spsr_svc ldmdb sp, {r0 - lr}^ @ get calling r0 - lr mov r0, r0 add sp, sp, #S_FRAME_SIZE - S_PC movs pc, lr @ return & move spsr_svc into cpsr 四、中斷處理模型 要想弄清楚desc->handle_irq(irq, desc)和我們注冊的中斷有什么關(guān)聯(lián),就要了解中斷處理模型了。 4.1 中斷處理模型結(jié)構(gòu) 中斷處理模型如下圖所示, 其中NR_IRQS表示最大的中斷號,在include/asm/arch/irq.h中定義。 irq_desc[]是一個指向irq_desc_t結(jié)構(gòu)的數(shù)組, irq_desc_t結(jié)構(gòu)是各個設(shè)備中斷服務(wù)例程的描述符。Irq_desc_t結(jié)構(gòu)體中的成員action指向該中斷號對應(yīng)的irqaction結(jié)構(gòu)體鏈表。Irqaction結(jié)構(gòu)體定義在include/linux/interrupt.h中,如下: truct irqaction { 在注冊中斷號為irq的中斷服務(wù)程序時,系統(tǒng)會根據(jù)注冊參數(shù)封裝相應(yīng)的irqaction結(jié)構(gòu)體。并把中斷號為irq的irqaction結(jié)構(gòu)體寫入irq_desc [irq]->action。這樣就把設(shè)備的中斷請求號與該設(shè)備的中斷服務(wù)例程irqaction聯(lián)系在一起了。樣當(dāng)CPU接收到中斷請求后,就可以根據(jù)中斷號通過irq_desc []找到該設(shè)備的中斷服務(wù)程序。 4.2 中斷共享的處理模型 共享中斷的不同設(shè)備的iqraction結(jié)構(gòu)體都會添加進該中斷號對應(yīng)的irq_desc結(jié)構(gòu)體的action成員所指向的irqaction鏈表內(nèi)。當(dāng)內(nèi)核發(fā)生中斷時,它會依次調(diào)用該鏈表內(nèi)所有的handler函數(shù)。因此,若驅(qū)動程序需要使用共享中斷機制,其中斷處理函數(shù)必須有能力識別是否是自己的硬件產(chǎn)生了中斷。通常是通過讀取該硬件設(shè)備提供的中斷flag標(biāo)志位進行判斷。也就是說不是任何設(shè)備都可以做為中斷共享源的,它必須能夠通過的它的中斷flag判斷出是否發(fā)生了中斷。 int request_irq(unsigned int irq, irq_handler_t handler, 很多權(quán)威資料中都提到,中斷共享注冊時的注冊函數(shù)中的dev_id參數(shù)是必不可少的,并且dev_id的值必須唯一。那么這里提供唯一的dev_id值的究竟是做什么用的? 根據(jù)我們前面中斷模型的知識,可以看出發(fā)生中斷時,內(nèi)核并不判斷究竟是共享中斷線上的哪個設(shè)備產(chǎn)生了中斷,它會循環(huán)執(zhí)行所有該中斷線上注冊的中斷處理函數(shù)(即irqaction->handler函數(shù))。因此irqaction->handler函數(shù)有責(zé)任識別出是否是自己的硬件設(shè)備產(chǎn)生了中斷,然后再執(zhí)行該中斷處理函數(shù)。通常是通過讀取該硬件設(shè)備提供的中斷flag標(biāo)志位進行判斷。那既然kernel循環(huán)執(zhí)行該中斷線上注冊的所有irqaction->handler函數(shù),把識別究竟是哪個硬件設(shè)備產(chǎn)生了中斷這件事交給中斷處理函數(shù)本身去做,那request_irq的dev_id參數(shù)究竟是做什么用的? 很多資料中都建議將設(shè)備結(jié)構(gòu)指針作為dev_id參數(shù)。在中斷到來時,迅速地根據(jù)硬件寄存器中的信息比照傳入的dev_id參數(shù)判斷是否是本設(shè)備的中斷,若不是,應(yīng)迅速返回。這樣的說法沒有問題,也是我們編程時都遵循的方法。但事實上并不能夠說明為什么中斷共享必須要設(shè)置dev_id。 下面解釋一下dev_id參數(shù)為什么必須的,而且是必須唯一的。 當(dāng)調(diào)用free_irq注銷中斷處理函數(shù)時(通常卸載驅(qū)動時其中斷處理函數(shù)也會被注銷掉),因為dev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多個中斷處理程序中刪除指定的一個。如果沒有這個參數(shù),那么kernel不可能知道給定的中斷線上到底要刪除哪一個處理程序。 注銷函數(shù)定義在Kernel/irq/manage.c中定義: 五、S3C2410子中斷的注冊的實現(xiàn) 5.1 S3C2410子中斷注冊問題的提出 參看3.5節(jié)中判斷中斷號的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等帶有子中斷的向量,INTOFFSET無法判斷出具體的中斷號。平臺留給我們的注冊方法如下: 在include/asm/arch/irqs.h中有類似如下定義: /* interrupts generated from the external interrupts sources */ 可以看到平臺為每種子中斷都定義了中斷號,如果你想實現(xiàn)EINT10的中斷注冊,直接按照IRQ_EINT10這個中斷號注冊都可以了。那么平臺代碼是如何實現(xiàn)這部分中斷注冊的呢? 5.2 S3C2410子中斷注冊問題的解決 /*arch/arm/plat-s3c24xx/irq.c*/ …… 平臺在初始化時會調(diào)用到s3c24xx_init_irq,在此函數(shù)中實現(xiàn)了對EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的注冊。下面看看這些帶有子中斷的中斷號對應(yīng)的處理函數(shù)的內(nèi)容。以IRQ_EINT4t7為例,其它情況類似。 /*arch/arm/plat-s3c24xx/irq.c*/ /* eintpnd中可以有多個位同時置1,這一點和intpnd的只能有1個位置1是不一樣的 */ 從上面的函數(shù)可以看出子中斷是如何注冊及被調(diào)用到的。有人可能會問為何不在include/asm/arch-s3c2410/entry-macro.s 文件中g(shù)et_irqnr_and_base函數(shù)判斷中斷號時,直接算出對應(yīng)的子中斷號,就可以直接找到子中斷處理了呢?
原因是: get_irqnr_and_base是平臺給系統(tǒng)提供的函數(shù),對于多個子中斷同時置位的情況無法通過一個值返回(因為子中斷中,如eintpnd是可以多個位同時置位的))。而intpnd則沒有這個問題。 (作者:劉洪濤,華清遠見嵌入式學(xué)院金牌講師) |
|