• <li id="00i08"><input id="00i08"></input></li>
  • <sup id="00i08"><tbody id="00i08"></tbody></sup>
    <abbr id="00i08"></abbr>
  • 新聞中心

    EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > linux2.6.26內(nèi)核中ARM中斷實(shí)現(xiàn)詳解

    linux2.6.26內(nèi)核中ARM中斷實(shí)現(xiàn)詳解

    作者: 時(shí)間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
    看了一些網(wǎng)絡(luò)上關(guān)于linux中斷實(shí)現(xiàn)的文章,感覺有一些寫的非常好,在這里首先感謝他們的無(wú)私付出,然后也想再補(bǔ)充自己對(duì)一些問題的理解。先從函數(shù)注冊(cè)引出問題吧。

    一、中斷注冊(cè)方法

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

    在linux內(nèi)核中用于申請(qǐng)中斷的函數(shù)是request_irq(),函數(shù)原型在Kernel/irq/manage.c中定義:

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

    irq是要申請(qǐng)的硬件中斷號(hào)。

    handler是向系統(tǒng)注冊(cè)的中斷處理函數(shù),是一個(gè)回調(diào)函數(shù),中斷發(fā)生時(shí),系統(tǒng)調(diào)用這個(gè)函數(shù),dev_id參數(shù)將被傳遞給它。

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

    dev_id在中斷共享時(shí)會(huì)用到,一般設(shè)置為這個(gè)設(shè)備的設(shè)備結(jié)構(gòu)體或者NULL。

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

    request_irq()返回0表示成功,返回-INVAL表示中斷號(hào)無(wú)效或處理函數(shù)指針為NULL,返回-EBUSY表示中斷已經(jīng)被占用且不能共享。

    關(guān)于中斷注冊(cè)的例子,大家可在內(nèi)核中搜索下request_irq。

    在編寫驅(qū)動(dòng)的過程中,比較容易產(chǎn)生疑惑的地方是:

    1、中斷向量表在什么位置?是如何建立的?
    2、從中斷開始,系統(tǒng)是怎樣執(zhí)行到我自己注冊(cè)的函數(shù)的?
    3、中斷號(hào)是如何確定的?對(duì)于硬件上有子中斷的中斷號(hào)如何確定?
    4、中斷共享是怎么回事,dev_id的作用是?

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

    二、異常向量表的建立

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

    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中,向量表建立的函數(shù)為:

    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內(nèi)核中CONFIG_VECTORS_BASE最初是在各個(gè)平臺(tái)的配置文件中設(shè)定的,如:

    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 //復(fù)位異常:
    ldr pc, .LCvswi + stubs_offset //未定義指令異常:
    b vector_pabt + stubs_offset //軟件中斷異常:
    b vector_dabt + stubs_offset //數(shù)據(jù)異常:
    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是如何確定的呢?(引用網(wǎng)絡(luò)上的一段比較詳細(xì)的解釋)

    當(dāng)匯編器看到B指令后會(huì)把要跳轉(zhuǎn)的標(biāo)簽轉(zhuǎn)化為相對(duì)于當(dāng)前PC的偏移量(±32M)寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發(fā)生了代碼搬移,所以如果中斷向量表中仍然寫成b vector_irq,那么實(shí)際執(zhí)行的時(shí)候就無(wú)法跳轉(zhuǎn)到搬移后的vector_irq處,因?yàn)橹噶畲a里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。我們把搬移前的中斷向量表(__vectors_start 到 __vectors_end之間的區(qū)域)中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個(gè)偏移量在搬移前后是不變的。搬移后 vectors_start在0xffff0處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對(duì)于中斷向量中的中斷入口地址的偏移量就是,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,對(duì)于括號(hào)內(nèi)的值實(shí)際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的 vectors_start+200-stubs_start就應(yīng)該是stubs_offset,實(shí)際上在entry-armv.S中也是這樣定義的。

    下面是圖解:

    圖中的標(biāo)號(hào)表示的地址在編譯內(nèi)核是就已經(jīng)確定了,可以在System.map中進(jìn)行搜索。

    搬移前:

    此時(shí),編譯器在處理B vector_irq的時(shí)候,會(huì)計(jì)算vector_irq與當(dāng)前PC指針的偏差,然后將這個(gè)偏差加到PC上,就實(shí)現(xiàn)了跳轉(zhuǎn)到vector_irq執(zhí)行。

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

    搬移后:

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

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

    為了在執(zhí)行B vector_irq時(shí)可以成功,需要重新計(jì)算偏移量:

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

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

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

    linux-2.6.26內(nèi)核中ARM中斷實(shí)現(xiàn)詳解(2)

    作者:劉洪濤,華清遠(yuǎn)見嵌入式學(xué)院金牌講師,ARM公司ATC授權(quán)培訓(xùn)講師。

    三、中斷處理過程

    這一節(jié)將以S3C2410為例,描述linux-2.6.26內(nèi)核中,從中斷開始,中斷是如何一步一步執(zhí)行到我們注冊(cè)函數(shù)的。

    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:

    中斷發(fā)生后,跳轉(zhuǎn)到b vector_irq + stubs_offset的位置執(zhí)行。注意現(xiàn)在的向量表的初始位置是0xffff0。

    3.2 中斷跳轉(zhuǎn)的入口位置 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 @為后面進(jìn)入svc模式做準(zhǔn)備

    @
    @ the branch table must immediately follow this code
    @
    and lr, lr, #0x0f @進(jìn)入中斷前的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] @如果進(jìn)入中斷前是usr,則取出PC+4*0的內(nèi)容,即__irq_usr @如果進(jìn)入中斷前是svc,則取出PC+4*3的內(nèi)容,即__irq_svc
    movs pc, lr @ 當(dāng)指令的目標(biāo)寄存器是PC,且指令以S結(jié)束,則它會(huì)把@ spsr的值恢復(fù)給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)。
    從上面代碼中的注釋可以看出,根據(jù)進(jìn)入中斷前的工作模式不同,程序下一步將跳轉(zhuǎn)到_irq_usr 、或__irq_svc等位置。我們先選擇__irq_usr作為下一步跟蹤的目標(biāo)。

    3.3 __irq_usr的實(shí)現(xiàn) archarmkernelentry-armv.S

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

    irq_handler @中斷處理,我們最關(guān)心的地方,3.4節(jié)有實(shí)現(xiàn)過程。
    #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 @中斷處理完成,返回中斷產(chǎn)生的位置,3.7節(jié)有實(shí)現(xiàn)過程

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

    .macro usr_entry
    sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE的值在archarmkernelasm-offsets.c
    @ 中定義 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));實(shí)際上等于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

    上面的這段代碼主要在填充結(jié)構(gòu)體pt_regs ,這里提到的struct pt_regs,在include/asm/ptrace.h中定義。此時(shí)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的實(shí)現(xiàn)過程,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 @判斷中斷號(hào),通過R0返回,3.5節(jié)有實(shí)現(xiàn)過程
    movne r1, sp
    @
    @ routine called with r0 = irq number, r1 = struct pt_regs *
    @
    adrne lr, 1b
    bne asm_do_IRQ @進(jìn)入中斷處理。
    ……
    .endm

    3.5 get_irqnr_and_base中斷號(hào)判斷過程,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 @加上中斷號(hào)的基準(zhǔn)數(shù)值,得到最終的中斷號(hào),注意:此時(shí)沒有考慮子中斷的具體情況,(子中斷的問題后面會(huì)有講解)。IRQ_EINT0在 include/asm/arch-s3c2410/irqs.h中定義.從這里可以看出,中斷號(hào)的具體值是有平臺(tái)相關(guān)的代碼決定的,和硬件中斷掛起寄存器中的中斷號(hào)是不等的。

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

    3.6 asm_do_IRQ實(shí)現(xiàn)過程,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;//根據(jù)中斷號(hào)找到對(duì)應(yīng)的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);// 根據(jù)中斷號(hào)和desc進(jìn)入中斷處理
    /* 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標(biāo)識(shí)。那么這個(gè)標(biāo)識(shí)的含義如何理解呢?
    該符號(hào)定義在kernel/include/linux/linkage.h中,如下所示:

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

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

    對(duì)于ARM處理器的,沒有定義asmlinkage,所以沒有意義(不要以為參數(shù)是從堆棧傳遞的,對(duì)于ARM平臺(tái)來說還是符合ATPCS過程調(diào)用標(biāo)準(zhǔn),通過寄存器傳遞的)。

    但對(duì)于X86處理器的中是這樣定義的:

    #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)
    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

    第三章主要跟蹤了從中斷發(fā)生到調(diào)用到對(duì)應(yīng)中斷號(hào)的desc->handle_irq(irq, desc)中斷函數(shù)的過程。后面的章節(jié)還會(huì)繼續(xù)講解后面的內(nèi)容。

    linux-2.6.26內(nèi)核中ARM中斷實(shí)現(xiàn)詳解(3)

    作者:劉洪濤,華清遠(yuǎn)見嵌入式學(xué)院金牌講師。

    四、中斷處理模型

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

    4.1 中斷處理模型結(jié)構(gòu)

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

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

    irq_desc[]是一個(gè)指向irq_desc_t結(jié)構(gòu)的數(shù)組, irq_desc_t結(jié)構(gòu)是各個(gè)設(shè)備中斷服務(wù)例程的描述符。Irq_desc_t結(jié)構(gòu)體中的成員action指向該中斷號(hào)對(duì)應(yīng)的irqaction結(jié)構(gòu)體鏈表。Irqaction結(jié)構(gòu)體定義在include/linux/interrupt.h中,如下:

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

    在注冊(cè)中斷號(hào)為irq的中斷服務(wù)程序時(shí),系統(tǒng)會(huì)根據(jù)注冊(cè)參數(shù)封裝相應(yīng)的irqaction結(jié)構(gòu)體。并把中斷號(hào)為irq的irqaction結(jié)構(gòu)體寫入 irq_desc [irq]->action。這樣就把設(shè)備的中斷請(qǐng)求號(hào)與該設(shè)備的中斷服務(wù)例程irqaction聯(lián)系在一起了。樣當(dāng)CPU接收到中斷請(qǐng)求后,就可以根據(jù)中斷號(hào)通過irq_desc []找到該設(shè)備的中斷服務(wù)程序。

    4.2 中斷共享的處理模型

    共享中斷的不同設(shè)備的 iqraction結(jié)構(gòu)體都會(huì)添加進(jìn)該中斷號(hào)對(duì)應(yīng)的irq_desc結(jié)構(gòu)體的action成員所指向的irqaction鏈表內(nèi)。當(dāng)內(nèi)核發(fā)生中斷時(shí),它會(huì)依次調(diào)用該鏈表內(nèi)所有的handler函數(shù)。因此,若驅(qū)動(dòng)程序需要使用共享中斷機(jī)制,其中斷處理函數(shù)必須有能力識(shí)別是否是自己的硬件產(chǎn)生了中斷。通常是通過讀取該硬件設(shè)備提供的中斷flag標(biāo)志位進(jìn)行判斷。也就是說不是任何設(shè)備都可以做為中斷共享源的,它必須能夠通過的它的中斷flag判斷出是否發(fā)生了中斷。
    中斷共享的注冊(cè)方法是:

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

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

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

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

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

    當(dāng)調(diào)用 free_irq注銷中斷處理函數(shù)時(shí)(通常卸載驅(qū)動(dòng)時(shí)其中斷處理函數(shù)也會(huì)被注銷掉),因?yàn)閐ev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多個(gè)中斷處理程序中刪除指定的一個(gè)。如果沒有這個(gè)參數(shù),那么kernel不可能知道給定的中斷線上到底要?jiǎng)h除哪一個(gè)處理程序。

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

    五、S3C2410子中斷的注冊(cè)的實(shí)現(xiàn)

    5.1 S3C2410子中斷注冊(cè)問題的提出

    參看3.5節(jié)中判斷中斷號(hào)的方法,可以看到只是通過S3C2410中斷控制器中的INTOFFSET寄存器來判斷的。對(duì)于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等帶有子中斷的向量,INTOFFSET無(wú)法判斷出具體的中斷號(hào)。平臺(tái)留給我們的注冊(cè)方法如下:

    在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)

    可以看到平臺(tái)為每種子中斷都定義了中斷號(hào),如果你想實(shí)現(xiàn)EINT10的中斷注冊(cè),直接按照IRQ_EINT10這個(gè)中斷號(hào)注冊(cè)都可以了。那么平臺(tái)代碼是如何實(shí)現(xiàn)這部分中斷注冊(cè)的呢?

    5.2 S3C2410子中斷注冊(cè)問題的解決

    /*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);

    ……
    }

    平臺(tái)在初始化時(shí)會(huì)調(diào)用到s3c24xx_init_irq,在此函數(shù)中實(shí)現(xiàn)了對(duì)EINT4_7、EINT8_23、INT_UART0、INT_ADC等中斷的注冊(cè)。下面看看這些帶有子中斷的中斷號(hào)對(duì)應(yīng)的處理函數(shù)的內(nèi)容。以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中可以有多個(gè)位同時(shí)置1,這一點(diǎn)和intpnd的只能有1個(gè)位置1是不一樣的 */
    while (eintpnd) { //循環(huán)執(zhí)行所有置位的子中斷
    irq = __ffs(eintpnd); //算出第一個(gè)不為0的位,類似arm v5后的clz前導(dǎo)0的作用
    eintpnd &= ~(1<irq += (IRQ_EINT4 - 4);//算出對(duì)應(yīng)的中斷號(hào)
    desc_handle_irq(irq, irq_desc + irq);//執(zhí)行對(duì)應(yīng)子中斷的注冊(cè)函數(shù)
    }
    }

    從上面的函數(shù)可以看出子中斷是如何注冊(cè)及被調(diào)用到的。有人可能會(huì)問為何不在include/asm/arch-s3c2410/entry-macro.s 文件中g(shù)et_irqnr_and_base函數(shù)判斷中斷號(hào)時(shí),直接算出對(duì)應(yīng)的子中斷號(hào),就可以直接找到子中斷處理了呢?

    原因是: get_irqnr_and_base是平臺(tái)給系統(tǒng)提供的函數(shù),對(duì)于多個(gè)子中斷同時(shí)置位的情況無(wú)法通過一個(gè)值返回(因?yàn)樽又袛嘀校鏴intpnd是可以多個(gè)位同時(shí)置位的))。而intpnd則沒有這個(gè)問題。



    評(píng)論


    相關(guān)推薦

    技術(shù)專區(qū)

    關(guān)閉
    主站蜘蛛池模板: 兰州市| 深泽县| 龙泉市| 铜鼓县| 清远市| 仲巴县| 盘锦市| 灵寿县| 武义县| 白朗县| 和田县| 吴江市| 临朐县| 白朗县| 维西| 公主岭市| 清苑县| 滨州市| 池州市| 响水县| 彭阳县| 洱源县| 宝清县| 花垣县| 浦东新区| 平远县| 梁山县| 扶风县| 太保市| 塘沽区| 隆林| 贞丰县| 阜新市| 渭源县| 澎湖县| 富平县| 金秀| 肇庆市| 阿克苏市| 大庆市| 黎川县|