新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > linux2.6.26內核中ARM中斷實現詳解

        linux2.6.26內核中ARM中斷實現詳解

        作者: 時間:2016-11-09 來源:網絡 收藏
        看了一些網絡上關于linux中斷實現的文章,感覺有一些寫的非常好,在這里首先感謝他們的無私付出,然后也想再補充自己對一些問題的理解。先從函數注冊引出問題吧。

        一、中斷注冊方法

        本文引用地址:http://www.104case.com/article/201611/317938.htm

        在linux內核中用于申請中斷的函數是request_irq(),函數原型在Kernel/irq/manage.c中定義:

        int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)

        irq是要申請的硬件中斷號。

        handler是向系統注冊的中斷處理函數,是一個回調函數,中斷發生時,系統調用這個函數,dev_id參數將被傳遞給它。

        irqflags是中斷處理的屬性,若設置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已經不支持了),則表示中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程序不屏蔽;若設置了IRQF_SHARED (老版本中的SA_SHIRQ),則表示多個設備共享中斷,若設置了IRQF_SAMPLE_RANDOM(老版本中的 SA_SAMPLE_RANDOM),表示對系統熵有貢獻,對系統獲取隨機數有好處。(這幾個flag是可以通過或的方式同時使用的)

        dev_id在中斷共享時會用到,一般設置為這個設備的設備結構體或者NULL。

        devname設置中斷名稱,在cat /proc/interrupts中可以看到此名稱。

        request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數指針為NULL,返回-EBUSY表示中斷已經被占用且不能共享。

        關于中斷注冊的例子,大家可在內核中搜索下request_irq。

        在編寫驅動的過程中,比較容易產生疑惑的地方是:

        1、中斷向量表在什么位置?是如何建立的?
        2、從中斷開始,系統是怎樣執行到我自己注冊的函數的?
        3、中斷號是如何確定的?對于硬件上有子中斷的中斷號如何確定?
        4、中斷共享是怎么回事,dev_id的作用是?

        本文以2.6.26內核和S3C2410處理器為例,為大家講解這幾個問題。

        二、異常向量表的建立

        ARM V4及V4T以后的大部分處理器中,中斷向量表的位置可以有兩個位置:一個是0,另一個是0xffff0。可以通過CP15協處理器c1寄存器中V位(bit[13])控制。V和中斷向量表的對應關系如下:

        V=0 ~ 0x00~0x1C
        V=1 ~ 0xffff0~0xffff001C

        arch/arm/mm/proc-arm920.S中

        .section ".text.init", #alloc, #execinstr
        __arm920_setup:
        …… orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1

        //bit13=1 中斷向量表基址為0xFFFF0。R0的值將被付給CP15的C1.

        在linux中,向量表建立的函數為:

        init/main.c->start_kernel()->trap_init()

        void __init trap_init(void)
        {
        unsigned long vectors = CONFIG_VECTORS_BASE;
        ……
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
        memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
        ....
        }

        在2.6.26內核中CONFIG_VECTORS_BASE最初是在各個平臺的配置文件中設定的,如:

        arch/arm/configs/s3c2410_defconfig中

        CONFIG_VECTORS_BASE=0xffff0

        __vectors_end 至 __vectors_start之間為異常向量表。

        位于arch/arm/kernel/entry-armv.S

        .globl __vectors_start
        __vectors_start:
        swi SYS_ERROR0:
        b vector_und + stubs_offset //復位異常:
        ldr pc, .LCvswi + stubs_offset //未定義指令異常:
        b vector_pabt + stubs_offset //軟件中斷異常:
        b vector_dabt + stubs_offset //數據異常:
        b vector_addrexcptn + stubs_offset //保留:
        b vector_irq + stubs_offset //普通中斷異常:
        b vector_fiq + stubs_offset //快速中斷異常:
        .globl __vectors_end:
        __vectors_end:

        __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是如何確定的呢?(引用網絡上的一段比較詳細的解釋)

        當匯編器看到B指令后會把要跳轉的標簽轉化為相對于當前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發生了代碼搬移,所以如果中斷向量表中仍然寫成b vector_irq,那么實際執行的時候就無法跳轉到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。我們把搬移前的中斷向量表(__vectors_start 到 __vectors_end之間的區域)中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后 vectors_start在0xffff0處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷向量中的中斷入口地址的偏移量就是,200+vector_irq在stubs中的偏移量-中斷入口vector_irq在中斷向量表(vectors)中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,對于括號內的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的 vectors_start+200-stubs_start就應該是stubs_offset,實際上在entry-armv.S中也是這樣定義的。

        下面是圖解:

        圖中的標號表示的地址在編譯內核是就已經確定了,可以在System.map中進行搜索。

        搬移前:

        此時,編譯器在處理B vector_irq的時候,會計算vector_irq與當前PC指針的偏差,然后將這個偏差加到PC上,就實現了跳轉到vector_irq執行。

        即: 偏移量就是vector_irq - (irq_PC+8) //ARM指令,3級流水,編譯器自動處理

        搬移后:

        此時,可以看到,中斷向量表中B vector_irq+x 的地址變成了0xffff0+(irq_PC - __vectors_start)

        中斷處理函數vector_irq的地址變成了0xffff0200+(vector_irq-__stubs_start)

        為了在執行B vector_irq時可以成功,需要重新計算偏移量:

        即: 0xffff0200+(vector_irq-__stubs_start) - [ 0xffff0+(irq_PC - __vectors_start) + 8]

        = [vector_irq - (irq_PC + 8)] + (__vectors_start+0x200+__stubs_start)

        跟之前的對比可以得出差異就是(__vectors_start+0x200+__stubs_start),即stubs_offset,即 B vectors_irq + stubs_offset。

        linux-2.6.26內核中ARM中斷實現詳解(2)

        作者:劉洪濤,華清遠見嵌入式學院金牌講師,ARM公司ATC授權培訓講師。

        三、中斷處理過程

        這一節將以S3C2410為例,描述linux-2.6.26內核中,從中斷開始,中斷是如何一步一步執行到我們注冊函數的。

        3.1 中斷向量表 archarmkernelentry-armv.S

        __vectors_start:
        swi SYS_ERROR0
        b vector_und + stubs_offset
        ldr pc, .LCvswi + stubs_offset
        b vector_pabt + stubs_offset
        b vector_dabt + stubs_offset
        b vector_addrexcptn + stubs_offset
        b vector_irq + stubs_offset
        b vector_fiq + stubs_offset
        .globl __vectors_end
        __vectors_end:

        中斷發生后,跳轉到b vector_irq + stubs_offset的位置執行。注意現在的向量表的初始位置是0xffff0。

        3.2 中斷跳轉的入口位置 archarmkernelentry-armv.S

        .globl __stubs_start
        __stubs_start:
        /*
        * Interrupt dispatcher
        */
        vector_stub irq, IRQ_MODE, 4 @IRQ_MODE在includeasmptrace.h中定義:0x12
        .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)
        .long __irq_invalid @ 4
        .long __irq_invalid @ 5
        .long __irq_invalid @ 6
        .long __irq_invalid @ 7
        .long __irq_invalid @ 8
        .long __irq_invalid @ 9
        .long __irq_invalid @ a
        .long __irq_invalid @ b
        .long __irq_invalid @ c
        .long __irq_invalid @ d
        .long __irq_invalid @ e
        .long __irq_invalid @ f

        上面代碼中vector_stub宏的定義為:

        .macro vector_stub, name, mode, correction=0
        .align 5
        vector_name:
        .if correction
        sub lr, lr, #correction
        .endif
        @
        @ Save r0, lr_ (parent PC) and spsr_
        @ (parent CPSR)
        @
        stmia sp, {r0, lr} @ save r0, lr
        mrs lr, spsr
        str lr, [sp, #8] @ save spsr
        @
        @ Prepare for SVC32 mode. IRQs remain disabled.
        @
        mrs r0, cpsr
        eor r0, r0, #(mode ^ SVC_MODE)
        msr spsr_cxsf, r0 @為后面進入svc模式做準備

        @
        @ the branch table must immediately follow this code
        @
        and lr, lr, #0x0f @進入中斷前的mode的后4位
        @#define USR_MODE 0x10
        @#define FIQ_MODE 0x11
        @#define IRQ_MODE 0x12
        @#define SVC_MODE 0x13
        @#define ABT_MODE 0x17
        @#define UND_MODE 0x1b
        @#define SYSTEM_MODE 0x1f
        mov r0, sp
        ldr lr, [pc, lr, lsl #2] @如果進入中斷前是usr,則取出PC+4*0的內容,即__irq_usr @如果進入中斷前是svc,則取出PC+4*3的內容,即__irq_svc
        movs pc, lr @ 當指令的目標寄存器是PC,且指令以S結束,則它會把@ spsr的值恢復給cpsr branch to handler in SVC mode
        .endm
        .globl __stubs_start
        __stubs_start:
        /*
        * Interrupt dispatcher
        */
        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)

        用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我們中斷處理的入口位置為vector_irq(宏里面的vector_name)。
        從上面代碼中的注釋可以看出,根據進入中斷前的工作模式不同,程序下一步將跳轉到_irq_usr 、或__irq_svc等位置。我們先選擇__irq_usr作為下一步跟蹤的目標。

        3.3 __irq_usr的實現 archarmkernelentry-armv.S

        __irq_usr:
        usr_entry @后面有解釋
        kuser_cmpxchg_check
        #ifdef CONFIG_TRACE_IRQFLAGS
        bl trace_hardirqs_off
        #endif
        get_thread_info tsk @獲取當前進程的進程描述符中的成員變量thread_info的地址,并將該地址保存到寄存器tsk等于r9(在entry-header.S中定義)
        #ifdef CONFIG_PREEMPT//如果定義了搶占,增加搶占數值
        ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
        add r7, r8, #1 @ increment it
        str r7, [tsk, #TI_PREEMPT]
        #endif

        irq_handler @中斷處理,我們最關心的地方,3.4節有實現過程。
        #ifdef CONFIG_PREEMPT
        ldr r0, [tsk, #TI_PREEMPT]
        str r8, [tsk, #TI_PREEMPT]
        teq r0, r7
        strne r0, [r0, -r0]
        #endif
        #ifdef CONFIG_TRACE_IRQFLAGS
        bl trace_hardirqs_on
        #endif

        mov why, #0

        b ret_to_user @中斷處理完成,返回中斷產生的位置,3.7節有實現過程

        上面代碼中的usr_entry是一個宏,主要實現了將usr模式下的寄存器、中斷返回地址保存到堆棧中。

        .macro usr_entry
        sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在archarmkernelasm-offsets.c
        @ 中定義 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));實際上等于72

        stmib sp, {r1 - r12}
        ldmia r0, {r1 - r3}
        add r0, sp, #S_PC @ here for interlock avoidance
        mov r4, #-1 @ "" "" "" ""

        str r1, [sp] @ save the "real" r0 copied
        @ from the exception stack

        @
        @ We are now ready to fill in the remaining blanks on the stack:
        @
        @ r2 - lr_, already fixed up for correct return/restart
        @ r3 - spsr_
        @ r4 - orig_r0 (see pt_regs definition in ptrace.h)
        @
        @ Also, separately save sp_usr and lr_usr
        @
        stmia r0, {r2 - r4}
        stmdb r0, {sp, lr}^

        @
        @ Enable the alignment trap while in kernel mode
        @
        alignment_trap r0

        @
        @ Clear FP to mark the first stack frame
        @
        zero_fp
        .endm

        上面的這段代碼主要在填充結構體pt_regs ,這里提到的struct pt_regs,在include/asm/ptrace.h中定義。此時sp指向struct pt_regs。

        struct pt_regs {
        long uregs[18];
        };
        #define ARM_cpsr uregs[16]
        #define ARM_pc uregs[15]
        #define ARM_lr uregs[14]
        #define ARM_sp uregs[13]
        #define ARM_ip uregs[12]
        #define ARM_fp uregs[11]
        #define ARM_r10 uregs[10]
        #define ARM_r9 uregs[9]
        #define ARM_r8 uregs[8]
        #define ARM_r7 uregs[7]
        #define ARM_r6 uregs[6]
        #define ARM_r5 uregs[5]
        #define ARM_r4 uregs[4]
        #define ARM_r3 uregs[3]
        #define ARM_r2 uregs[2]
        #define ARM_r1 uregs[1]
        #define ARM_r0 uregs[0]
        #define ARM_ORIG_r0 uregs[17]

        3.4 irq_handler的實現過程,archarmkernelentry-armv.S

        .macro irq_handler
        get_irqnr_preamble r5, lr
        @在include/asm/arch-s3c2410/entry-macro.s中定義了宏get_irqnr_preamble為空操作,什么都不做
        1: get_irqnr_and_base r0, r6, r5, lr @判斷中斷號,通過R0返回,3.5節有實現過程
        movne r1, sp
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        adrne lr, 1b
        bne asm_do_IRQ @進入中斷處理。
        ……
        .endm

        3.5 get_irqnr_and_base中斷號判斷過程,include/asm/arch-s3c2410/entry-macro.s

        .macro get_irqnr_and_base, irqnr, irqstat, base, tmp
        mov base, #S3C24XX_VA_IRQ
        @@ try the interrupt offset register, since it is there
        ldr irqstat, [ base, #INTPND ]
        teq irqstat, #0
        beq 1002f
        ldr irqnr, [ base, #INTOFFSET ] @通過判斷INTOFFSET寄存器得到中斷位置
        mov tmp, #1
        tst irqstat, tmp, lsl irqnr
        bne 1001f
        @@ the number specified is not a valid irq, so try
        @@ and work it out for ourselves
        mov irqnr, #0 @@ start here
        @@ work out which irq (if any) we got
        movs tmp, irqstat, lsl#16
        addeq irqnr, irqnr, #16
        moveq irqstat, irqstat, lsr#16
        tst irqstat, #0xff
        addeq irqnr, irqnr, #8
        moveq irqstat, irqstat, lsr#8
        tst irqstat, #0xf
        addeq irqnr, irqnr, #4
        moveq irqstat, irqstat, lsr#4
        tst irqstat, #0x3
        addeq irqnr, irqnr, #2
        moveq irqstat, irqstat, lsr#2
        tst irqstat, #0x1
        addeq irqnr, irqnr, #1
        @@ we have the value
        1001:

        adds irqnr, irqnr, #IRQ_EINT0 @加上中斷號的基準數值,得到最終的中斷號,注意:此時沒有考慮子中斷的具體情況,(子中斷的問題后面會有講解)。IRQ_EINT0在 include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號的具體值是有平臺相關的代碼決定的,和硬件中斷掛起寄存器中的中斷號是不等的。

        1002:
        @@ exit here, Z flag unset if IRQ
        .endm

        3.6 asm_do_IRQ實現過程,arch/arm/kernel/irq.c

        asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
        {
        struct pt_regs *old_regs = set_irq_regs(regs);
        struct irq_desc *desc = irq_desc + irq;//根據中斷號找到對應的irq_desc
        /*
        * Some hardware gives randomly wrong interrupts. Rather
        * than crashing, do something sensible.
        */
        if (irq >= NR_IRQS)
        desc = &bad_irq_desc;
        irq_enter();//沒做什么特別的工作,可以跳過不看
        desc_handle_irq(irq, desc);// 根據中斷號和desc進入中斷處理
        /* AT91 specific workaround */
        irq_finish(irq);
        irq_exit();
        set_irq_regs(old_regs);
        }

        static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
        {
        desc->handle_irq(irq, desc);//中斷處理
        }

        上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage標識。那么這個標識的含義如何理解呢?
        該符號定義在kernel/include/linux/linkage.h中,如下所示:

        #include //各個具體處理器在此文件中定義asmlinkage
        #ifdef __cplusplus
        #define CPP_ASMLINKAGE extern "C"
        #else
        #define CPP_ASMLINKAGE
        #endif

        #ifndef asmlinkage//如果以前沒有定義asmlinkage
        #define asmlinkage CPP_ASMLINKAGE
        #endif

        對于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數是從堆棧傳遞的,對于ARM平臺來說還是符合ATPCS過程調用標準,通過寄存器傳遞的)。

        但對于X86處理器的中是這樣定義的:

        #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

        表示函數的參數傳遞是通過堆棧完成的。

        3.7 描述3.3節中的ret_to_user 中斷返回過程,/arch/arm/kernel/entry-common.S

        ENTRY(ret_to_user)
        ret_slow_syscall:
        disable_irq @ disable interrupts
        ldr r1, [tsk, #TI_FLAGS]
        tst r1, #_TIF_WORK_MASK
        bne work_pending
        no_work_pending:
        /* perform architecture specific actions before user return */
        arch_ret_to_user r1, lr

        @ 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)中斷函數的過程。后面的章節還會繼續講解后面的內容。

        linux-2.6.26內核中ARM中斷實現詳解(3)

        作者:劉洪濤,華清遠見嵌入式學院金牌講師。

        四、中斷處理模型

        要想弄清楚desc->handle_irq(irq, desc)和我們注冊的中斷有什么關聯,就要了解中斷處理模型了。

        4.1 中斷處理模型結構

        中斷處理模型如下圖所示,

        其中NR_IRQS表示最大的中斷號,在include/asm/arch/irq.h中定義。

        irq_desc[]是一個指向irq_desc_t結構的數組, irq_desc_t結構是各個設備中斷服務例程的描述符。Irq_desc_t結構體中的成員action指向該中斷號對應的irqaction結構體鏈表。Irqaction結構體定義在include/linux/interrupt.h中,如下:

        truct irqaction {
        irq_handler_t handler; //中斷處理函數,注冊時提供
        unsigned long flags; //中斷標志,注冊時提供
        cpumask_t mask; //中斷掩碼
        const char *name; //中斷名稱
        void *dev_id; //設備id,本文后面部分介紹中斷共享時會詳細說明這個參數的作用
        struct irqaction *next; //如果有中斷共享,則繼續執行,
        int irq; //中斷號,注冊時提供
        struct proc_dir_entry *dir; //指向IRQn相關的/proc/irq/n目錄的描述符
        };

        在注冊中斷號為irq的中斷服務程序時,系統會根據注冊參數封裝相應的irqaction結構體。并把中斷號為irq的irqaction結構體寫入 irq_desc [irq]->action。這樣就把設備的中斷請求號與該設備的中斷服務例程irqaction聯系在一起了。樣當CPU接收到中斷請求后,就可以根據中斷號通過irq_desc []找到該設備的中斷服務程序。

        4.2 中斷共享的處理模型

        共享中斷的不同設備的 iqraction結構體都會添加進該中斷號對應的irq_desc結構體的action成員所指向的irqaction鏈表內。當內核發生中斷時,它會依次調用該鏈表內所有的handler函數。因此,若驅動程序需要使用共享中斷機制,其中斷處理函數必須有能力識別是否是自己的硬件產生了中斷。通常是通過讀取該硬件設備提供的中斷flag標志位進行判斷。也就是說不是任何設備都可以做為中斷共享源的,它必須能夠通過的它的中斷flag判斷出是否發生了中斷。
        中斷共享的注冊方法是:

        int request_irq(unsigned int irq, irq_handler_t handler,
        IRQF_SHARED, const char *devname, void *dev_id)

        很多權威資料中都提到,中斷共享注冊時的注冊函數中的dev_id參數是必不可少的,并且dev_id的值必須唯一。那么這里提供唯一的dev_id值的究竟是做什么用的?

        根據我們前面中斷模型的知識,可以看出發生中斷時,內核并不判斷究竟是共享中斷線上的哪個設備產生了中斷,它會循環執行所有該中斷線上注冊的中斷處理函數(即irqaction->handler函數)。因此irqaction->handler函數有責任識別出是否是自己的硬件設備產生了中斷,然后再執行該中斷處理函數。通常是通過讀取該硬件設備提供的中斷flag標志位進行判斷。那既然kernel循環執行該中斷線上注冊的所有 irqaction->handler函數,把識別究竟是哪個硬件設備產生了中斷這件事交給中斷處理函數本身去做,那request_irq的 dev_id參數究竟是做什么用的?

        很多資料中都建議將設備結構指針作為dev_id參數。在中斷到來時,迅速地根據硬件寄存器中的信息比照傳入的dev_id參數判斷是否是本設備的中斷,若不是,應迅速返回。這樣的說法沒有問題,也是我們編程時都遵循的方法。但事實上并不能夠說明為什么中斷共享必須要設置dev_id。

        下面解釋一下dev_id參數為什么必須的,而且是必須唯一的。

        當調用 free_irq注銷中斷處理函數時(通常卸載驅動時其中斷處理函數也會被注銷掉),因為dev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多個中斷處理程序中刪除指定的一個。如果沒有這個參數,那么kernel不可能知道給定的中斷線上到底要刪除哪一個處理程序。

        注銷函數定義在Kernel/irq/manage.c中定義:
        void free_irq(unsigned int irq, void *dev_id)

        五、S3C2410子中斷的注冊的實現

        5.1 S3C2410子中斷注冊問題的提出

        參看3.5節中判斷中斷號的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等帶有子中斷的向量,INTOFFSET無法判斷出具體的中斷號。平臺留給我們的注冊方法如下:

        在include/asm/arch/irqs.h中有類似如下定義:

        /* interrupts generated from the external interrupts sources */
        #define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
        #define IRQ_EINT5 S3C2410_IRQ(33)
        #define IRQ_EINT6 S3C2410_IRQ(34)
        #define IRQ_EINT7 S3C2410_IRQ(35)
        #define IRQ_EINT8 S3C2410_IRQ(36)
        #define IRQ_EINT9 S3C2410_IRQ(37)
        #define IRQ_EINT10 S3C2410_IRQ(38)
        #define IRQ_EINT11 S3C2410_IRQ(39)
        #define IRQ_EINT12 S3C2410_IRQ(40)
        #define IRQ_EINT13 S3C2410_IRQ(41)
        #define IRQ_EINT14 S3C2410_IRQ(42)
        #define IRQ_EINT15 S3C2410_IRQ(43)
        #define IRQ_EINT16 S3C2410_IRQ(44)
        #define IRQ_EINT17 S3C2410_IRQ(45)
        #define IRQ_EINT18 S3C2410_IRQ(46)
        #define IRQ_EINT19 S3C2410_IRQ(47)
        #define IRQ_EINT20 S3C2410_IRQ(48) /* 64 */
        #define IRQ_EINT21 S3C2410_IRQ(49)
        #define IRQ_EINT22 S3C2410_IRQ(50)
        #define IRQ_EINT23 S3C2410_IRQ(51)

        可以看到平臺為每種子中斷都定義了中斷號,如果你想實現EINT10的中斷注冊,直接按照IRQ_EINT10這個中斷號注冊都可以了。那么平臺代碼是如何實現這部分中斷注冊的呢?

        5.2 S3C2410子中斷注冊問題的解決

        /*arch/arm/plat-s3c24xx/irq.c*/
        void __init s3c24xx_init_irq(void)
        {……
        set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
        set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
        set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
        set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
        set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
        set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

        ……
        }

        平臺在初始化時會調用到s3c24xx_init_irq,在此函數中實現了對EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的注冊。下面看看這些帶有子中斷的中斷號對應的處理函數的內容。以IRQ_EINT4t7為例,其它情況類似。

        /*arch/arm/plat-s3c24xx/irq.c*/
        s3c_irq_demux_extint4t7(unsigned int irq,
        struct irq_desc *desc)
        {
        unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
        unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
        eintpnd &= ~eintmsk;
        eintpnd &= 0xff; /* only lower irqs */

        /* eintpnd中可以有多個位同時置1,這一點和intpnd的只能有1個位置1是不一樣的 */
        while (eintpnd) { //循環執行所有置位的子中斷
        irq = __ffs(eintpnd); //算出第一個不為0的位,類似arm v5后的clz前導0的作用
        eintpnd &= ~(1<irq += (IRQ_EINT4 - 4);//算出對應的中斷號
        desc_handle_irq(irq, irq_desc + irq);//執行對應子中斷的注冊函數
        }
        }

        從上面的函數可以看出子中斷是如何注冊及被調用到的。有人可能會問為何不在include/asm/arch-s3c2410/entry-macro.s 文件中get_irqnr_and_base函數判斷中斷號時,直接算出對應的子中斷號,就可以直接找到子中斷處理了呢?

        原因是: get_irqnr_and_base是平臺給系統提供的函數,對于多個子中斷同時置位的情況無法通過一個值返回(因為子中斷中,如eintpnd是可以多個位同時置位的))。而intpnd則沒有這個問題。



        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 齐齐哈尔市| 寻乌县| 五指山市| 措美县| 南阳市| 顺平县| 樟树市| 德阳市| 宝坻区| 郯城县| 布尔津县| 伊川县| 大悟县| 琼海市| 兴仁县| 宜良县| 宣化县| 土默特左旗| 樟树市| 天门市| 建湖县| 河东区| 黄龙县| 靖安县| 营口市| 寻甸| 开远市| 云霄县| 文水县| 石渠县| 武川县| 佛冈县| 西贡区| 湖北省| 九龙县| 鄯善县| 邵阳市| 札达县| 科尔| 乌鲁木齐市| 武陟县|