linux驱动之中断处理过程汇编部分

      linux系统下驱动中,中断异常的处理过程,与裸机开发中断处理过程非常类似。通过简单的回顾裸机开发中断处理部分,来参考学习linux系统下中断处理流程。

一、ARM裸机开发中断处理过程

      以S3C2440的裸机开发启动文件中,有关irq中断部分代码为例进行说明:

.extern     main
.text 
.global _start 
_start:      
    b   Reset
HandleUndef:
    b   HandleUndef 
HandleSWI:
    b   HandleSWI
HandlePrefetchAbort:
    b   HandlePrefetchAbort
HandleDataAbort:
    b   HandleDataAbort
HandleNotUsed:
    b   HandleNotUsed
    b   HandleIRQ
HandleFIQ:
    b   HandleFIQ

Reset:                  
    ldr sp, =4096           @ 设置栈指针,以下都是C函数,调用前需要设好栈
    bl  disable_watch_dog   @ 关闭WATCHDOG,否则CPU会不断重启
    
    msr cpsr_c, #0xd2       @ 进入中断模式
    ldr sp, =3072           @ 设置中断模式栈指针

    msr cpsr_c, #0xdf       @ 进入系统模式
    ldr sp, =4096           @ 设置系统模式栈指针

    bl  init_led            @ 初始化LED的GPIO管脚
    bl  init_irq            @ 调用中断初始化函数,在init.c中
    msr cpsr_c, #0x5f       @ 设置I-bit=0,开IRQ中断
    
    ldr lr, =halt_loop      @ 设置返回地址
    ldr pc, =main           @ 调用main函数
halt_loop:
    b   halt_loop

HandleIRQ:
    sub lr, lr, #4                  @ 计算返回地址
    stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器
                                    @ 注意,此时的sp是中断模式的sp,初始值是上面设置的3072   
    ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  
    ldr pc, =EINT_Handle            @ 调用中断服务函数,在interrupt.c中
int_return:
    ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr

      当irq中断发生时,一些列的处理流程如下:

1、硬件自动令PC置为irq的中断向量,从而执行跳转指令“b HandleIRQ”。

     其实,之前还伴随着保存中断断点地址到lr(还要换算);CPSR的值到SPSR;将CPSR切换到异常模式。

2、保存中断现场

sub lr, lr, #4                  @ 计算返回地址
stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器

3、执行中断服务程序

ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  
ldr pc, =EINT_Handle            @ 调用中断服务函数,在interrupt.c中

4、从中断异常工作模式返回

int_return:
ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr

二、linux系统中断处理流程

      具体的代码细节没有分析,主要是为了理清中断处理的整体脉络。

1、ARM异常向量表

      arch/arm/kernel/entry-armv.S

    .globl    __vectors_start
__vectors_start:
    swi    SYS_ERROR0                            /* 复位时,执行这条指令 */
    b    vector_und + stubs_offset               /* 未定义异常 */
    ldr    pc, .LCvswi + stubs_offset            /* swi异常 */
    b    vector_pabt + stubs_offset              /* 指令预取异常 */
    b    vector_dabt + stubs_offset              /* 数据访问终止 */
    b    vector_addrexcptn + stubs_offset        /* 没有用 */
    b    vector_irq + stubs_offset               /* irq异常 */
    b    vector_fiq + stubs_offset               /* fiq异常 */

    .globl    __vectors_end
__vectors_end:

      异常向量表,无非还是一些跳转指令。当发生irq中断,执行指令“b vector_irq + stubs_offset”,也就是跳转到vector_irq代码段继续执行。

      在linux内核初始化阶段,start_kernel函数(init/main.c)会调用trap_init、init_IRQ两个函数来初始化异常向量相关处理函数。简要说明就是,将异常向量表拷贝到地址0xffff0000处(ARM体系协处理器寄存器c1能设置异常向量的基地址为0xffff0000),再把异常向量表中异常处理的进一步函数代码段拷贝到0xffff0200位置(vector_und、vector_irq等)。

2、异常处理进一步函数----vector_irq

      arch/arm/kernel/entry-armv.S

 1     .globl    __stubs_start
 2 __stubs_start:
 3     vector_stub    irq, IRQ_MODE, 4
 4     .long    __irq_usr            @  0  (USR_26 / USR_32)
 5     .long    __irq_invalid            @  1  (FIQ_26 / FIQ_32)
 6     .long    __irq_invalid            @  2  (IRQ_26 / IRQ_32)
 7     .long    __irq_svc            @  3  (SVC_26 / SVC_32)
 8     .long    __irq_invalid            @  4
 9     .long    __irq_invalid            @  5
10     .long    __irq_invalid            @  6
11     .long    __irq_invalid            @  7
12     .long    __irq_invalid            @  8
13     .long    __irq_invalid            @  9
14     .long    __irq_invalid            @  a
15     .long    __irq_invalid            @  b
16     .long    __irq_invalid            @  c
17     .long    __irq_invalid            @  d
18     .long    __irq_invalid            @  e
19     .long    __irq_invalid            @  f

      从第4行到第19行,记录了(代码链接阶段填入的地址数据)在各个模式下遇到irq中断时,发生异常的处理分支。比如第4行__irq_usr表示用户模式下发生irq中断时,由__irq_usr对应的代码段来处理这种情况。

      vector_stub是一个宏,将宏展开内容如下:

vector_irq:
    sub    lr, lr, #4
    stmia    sp, {r0, lr}    @ save r0, lr
    
    mrs    lr, spsr
    str    lr, [sp, #8]        @ save spsr
    
    mrs    r0, cpsr
    eor    r0, r0, #(IRQ_MODE ^ SVC_MODE)
    msr    spsr_cxsf, r0

    and    lr, lr, #0x0f
    mov    r0, sp
    ldr    lr, [pc, lr, lsl #2]
    movs    pc, lr            @ branch to handler in SVC mode
    .endm

      这个宏的目的就是,根据进入irq中断前处理器所处的模式,将紧接着其下边的16个地址池中对应位置的处理向量,取出来赋给PC,完成进一步跳转。这里我们选择让程序跳转到__irq_usr代码段继续执行。

3、异常处理进一步函数----__irq_usr

      arch/arm/kernel/entry-armv.S

__irq_usr:
    usr_entry             @将usr模式下的寄存器、中断返回地址保存到堆栈中

    get_thread_info tsk  @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9

    irq_handler          @中断处理

    mov    why, #0       
    b    ret_to_user     @中断处理完成,返回中断产生的位置

4、irq_handler

      irq_handler是一个宏,将其内容展开如下:

      arch/arm/kernel/entry-armv.S

    .macro    irq_handler
    get_irqnr_preamble r5, lr
1:    get_irqnr_and_base r0, r6, r5, lr
    movne    r1, sp
    adrne    lr, 1b
    bne    asm_do_IRQ
.endm

      由此可见,进入asm_do_IRQ函数开始具体的中断处理。需要指出的是,asm_do_IRQ是中断的C语言总入口函数。asm_do_IRQ函数原型为:

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

      在汇编处理阶段,会为asm_do_IRQ传入两个参数irq(中断号)和regs,中断号对应着发生了什么样的中断事件,于是可以采取什么样的中断服务程序进行处理。

5、get_irqnr_and_base

      include/asm-arm/arch-s3c2410/entry-macro.s

    .macro    get_irqnr_and_base, irqnr, irqstat, base, tmp

        mov    ase, #S3C24XX_VA_IRQ

        @@ try the interrupt offset register, since it is there

        ldr    irqstat, [ ase, #INTPND ]
        teq    irqstat, #0
        beq    1002f
        ldr    irqnr, [ ase, #INTOFFSET ]
        mov    	mp, #1
        tst    irqstat, 	mp, 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    	mp, 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    @加上中断号的基准数值,得到最终的中断号
1002:
        @@ exit here, Z flag unset if IRQ

    .endm

      linux系统中断号判断过程,是与硬件平台相关的。例如S3C2410的中断号判断过程,是根据INTOFFSET来判断的。但是,需要注意的是,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。

#define S3C2410_CPUIRQ_OFFSET     (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)

/* main cpu interrupts */
#define IRQ_EINT0      S3C2410_IRQ(0)        /* 16 */
#define IRQ_EINT1      S3C2410_IRQ(1)        /* 17 */
#define IRQ_EINT2      S3C2410_IRQ(2)        /* 18 */
#define IRQ_EINT3      S3C2410_IRQ(3)        /* 19 */
      ...............

参考资料:linux-2.6.26内核中ARM中断实现详解(转)

原文地址:https://www.cnblogs.com/amanlikethis/p/6921826.html