【ARM】-IRQ 和 FIQ 异常中断处理程序的返回

处理流程

通常处理器执行完当前指令后,查询 IRQ 中断引脚及 FIQ 中断引脚,并且查看系统是否允许 IRQ 中断及 FIQ中断。
如果有中断引脚有效,并且系统允许该中断产生,处理器将产生 IRQ 异常中断或 FIQ 异常中断。
当 IRQ 和 FIQ 异常中断产生时,程序计数器 PC 的值已经更新,它指向当前指令后面第 3 条指令(对于 ARM 系统来说它指向当前指令地址加 12 个字节的位置,对于 Thumb 指令来说,它指向当前指令加 6 个字节的位置)。
当 IRQ 和 FIQ 异常中断发生时,处理器将 PC-4 的值保存到异常模式下的寄存器 LR_mode 中,这时 LR_mode 中的值即为PC-4 ,即指向当前指令后的第二条指令。因此返回操作需要将 LR_mode - 4(指向当前指令的下一条指令) 赋给 PC,可以通过下面的指令来实现:

SUBS PC, LR, #4  // 注意 S ,指定了 S 意味着同时将SPSR 拷贝到 CPSR

该指令将寄存器 LR 中的值减 4 后,复制到程序计数器 PC 中,实现程序返回,同时将 SPSR_mode 寄存器内容复制到 CPSR 中。
当异常中断处理程序中使用了数据栈时,可以通过下面的指令在异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断处理程序中使用的数据栈由用户提供。

subs lr, lr, #4
stmdb sp!, {r0-r12, lr}   // 保存现场 r0-r12 - reg_list
// user code
ldmia sp!, {r0-r12, pc}^  // 恢复现场 r0-r12 - reg_list

在上述指令中,reg_list 是异常中断处理程序中使用的寄存器列表。标识符 ^ 指示将 SPSR_mode 寄存器内容复制到 CPSR 寄存器中,该指令只能在特权模式下使用。

示例

在这里插入图片描述
1、假设在 instruction+0 指令执行完成后发生 irq/frq 异常
2、此时 PC 值 已经更新,指向 instruction+3
3、进入 irq/frq 异常后,LR_mode = PC -4,所以此时 LR 指向 instruction+2
4、irq/frq 异常处理完成之后,程序需要返回到 instruction+1 处执行,所以将此时的 LR_mode 寄存器的值 -4 赋给 PC。

代码实现

IRQ_Handler 汇编函数中,需要做下面

  • 上文保存
  • 获取中断号
  • 中断服务函数跳转到 C 中,并将中断号作为参数 0 传递
  • 中断完成
  • 下文恢复

不支持中断嵌套

不支持中断嵌套,在 IRQ 模式下处理中断

    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function

IRQ_Handler:
    /* 执行到这里之前:
    * 1. lr_irq 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_irq 保存有被中断模式的 CPSR
    * 3. CPSR 中的 M4-M0 被设置为 10010, 进入到 irq 模式
    * 4. 跳到 0x18 的地方执行程序
    */

    /* 保存现场 */
    /* 在 irq 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr-4 是异常处理完后的返回地址, 也要保存 */
    sub lr, lr, #4
    stmfd sp!, {
    
    r0-r12, lr}

    ldr r1, =0x00a00000         /* imx6ull 的 GIC 基地址为 0x00a00000 */
    add r1, r1, #0X2000         /* GIC 基地址加 0X2000,也就是 GIC 的 CPU 接口端基地址 */
    ldr r0, [r1, #0XC]          /* GIC 的 CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器,
                                    * GICC_IAR 寄存器保存这当前发生中断的中断号,我们要根据
                                    * 这个中断号来绝对调用哪个中断服务函数
                                    */

    push {
    
    r0, r1}               /* 保存 r0,r1 */
    bl system_irqhandler        /* 运行 C 语言中断处理函数,带有一个参数,保存在 R0 寄存器中 */
    pop {
    
    r0, r1}
    str r0, [r1, #0X10]         /* r0 存放当前中断号,
                                 * 中断执行完成,将中断号写如 GIC->EOIR(End Of Interrupt Register offset 0x10)
                                 */

    /* 恢复现场 */
    ldmfd sp!, {
    
    r0-r12, pc}^  /* ^会把 spsr_irq 的值恢复到 cpsr 里 */

支持中断嵌套

支持中断嵌套,在 SVC 模式下处理中断

    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function

IRQ_Handler:
    push {
    
    lr}                   /* 保存lr地址 */
    push {
    
    r0-r3, r12}           /* 保存r0-r3,r12寄存器 */

    mrs r0, spsr                /* 读取spsr寄存器 */
    push {
    
    r0}                   /* 保存spsr寄存器 */

.if 0 /* on qemu platform it always return 0, i don't know why */
    mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
                                * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
                                * Cortex-A7 Technical ReferenceManua.pdf P68 P138
                                */
.else
    ldr r1, =0x00a00000
.endif
    add r1, r1, #0X2000         /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
    ldr r0, [r1, #0XC]          /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
                                    * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
                                    * 这个中断号来绝对调用哪个中断服务函数
                                    */
    push {
    
    r0, r1}               /* 保存r0,r1 */

    cps #0x13                   /* 进入SVC模式,允许其他中断再次进去 */

    push {
    
    lr}                   /* 保存SVC模式的lr寄存器 */
    ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
    blx r2                      /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

    pop {
    
    lr}                    /* 执行完C语言中断服务函数,lr出栈 */
    cps #0x12                   /* 进入IRQ模式 */
    pop {
    
    r0, r1}
    str r0, [r1, #0X10]         /* 中断执行完成,写EOIR */

    pop {
    
    r0}
    msr spsr_cxsf, r0           /* 恢复spsr */

    pop {
    
    r0-r3, r12}            /* r0-r3,r12出栈 */
    pop {
    
    lr}                    /* lr出栈 */
    subs pc, lr, #4             /* 将lr-4赋给pc */

C 语言执行中断服务函数

由于在 IRQ_Handler 汇编函数中,对上文保存,下文恢复等都做了处理,所以 C 语言处理中断服务函数时,只需要执行对应的中断回调即可,

void system_irqhandler(unsigned int giccIar)
{
    
    

    uint32_t intNum = giccIar & 0x3FFUL;

    /* 检查中断号是否符合要求 */
    if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
    {
    
    
        return;
    }

    irqNesting++; /* 中断嵌套计数器加一 */

    /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);

    irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */
}

附录源码

.equ Mode_USR,        0x10
.equ Mode_FIQ,        0x11
.equ Mode_IRQ,        0x12
.equ Mode_SVC,        0x13
.equ Mode_MON,        0x16
.equ Mode_ABT,        0x17
.equ Mode_HYP,        0x1A
.equ Mode_UND,        0x1B
.equ Mode_SYS,        0x1F

.equ Stack_size,      0x400
.equ Stack_Start,     0x80200000

.equ Mode_USR_Stack,  Stack_Start + Stack_size
.equ Mode_FIQ_Stack,  Mode_USR_Stack + Stack_size
.equ Mode_IRQ_Stack,  Mode_FIQ_Stack + Stack_size
.equ Mode_SVC_Stack,  Mode_IRQ_Stack + Stack_size
.equ Mode_MON_Stack,  Mode_SVC_Stack + Stack_size
.equ Mode_ABT_Stack,  Mode_MON_Stack + Stack_size
.equ Mode_HYP_Stack,  Mode_ABT_Stack + Stack_size
.equ Mode_UND_Stack,  Mode_HYP_Stack + Stack_size
.equ Mode_SYS_Stack,  Mode_UND_Stack + Stack_size

    /* 定义一个 .isr_vector 段,链接脚本中定义相应的段来存放该段的数据 */
    .section .isr_vector, "a"
    .arm
    .align 2
    .globl __isr_vector
__isr_vector:
    ldr     pc, =Reset_Handler           /* Reset                  */
    ldr     pc, =Undefined_Handler       /* Undefined instructions */
    ldr     pc, =SVC_Handler             /* Supervisor Call        */
    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */
    ldr     pc, =DataAbort_Handler       /* Data abort             */
    .word   0                            /* RESERVED               */
    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */
    ldr     pc, =FIQ_Handler             /* FIQ interrupt          */

    /* 余下的指令的放在 .text 中,所以用 .text 指定段 */
/* Reset Handler */
    .text
    .arm
    .align 2
    .globl   Reset_Handler
    .weak    Reset_Handler
    .type    Reset_Handler, %function
Reset_Handler:

    cpsid i /* 关闭全局中断 */

    /* 关闭I,DCache和MMU
     * 采取读-改-写的方式。
     */
    mrc p15, 0, r0, c1, c0, 0     /* 读取 CP15 的 C1 寄存器到 R0 中 */
    bic r0,  r0, #(0x1 << 12)     /* 清除 C1 寄存器的  bit12 位(I位),关闭 I Cache */
    bic r0,  r0, #(0x1 <<  2)     /* 清除 C1 寄存器的 bit2 (C位),关闭 D Cache */
    bic r0,  r0, #0x2             /* 清除 C1 寄存器的 bit1 (A位),关闭对齐 */
    bic r0,  r0, #(0x1 << 11)     /* 清除 C1 寄存器的 bit11 (Z位),关闭分支预测 */
    bic r0,  r0, #0x1             /* 清除 C1 寄存器的 bit0 (M位),关闭 MMU */
    mcr p15, 0, r0, c1, c0, 0     /* 将 r0 寄存器中的值写入到 CP15 的 C1 寄存器中 */

#if 0
    /* 汇编版本设置中断向量表偏移 */
    ldr r0, =0x80000000

    dsb
    isb
    mcr p15, 0, r0, c12, c0, 0
    dsb
    isb
#endif

    /* 模式切换并设置 sp 地址 */
    cps     #Mode_FIQ
    ldr     sp, =Mode_FIQ_Stack

    cps     #Mode_IRQ
    ldr     sp, =Mode_IRQ_Stack

    cps     #Mode_SVC
    ldr     sp, =Mode_SVC_Stack

    cps     #Mode_MON
    ldr     sp, =Mode_MON_Stack

    cps     #Mode_ABT
    ldr     sp, =Mode_ABT_Stack

    cps     #Mode_HYP
    ldr     sp, =Mode_HYP_Stack

    cps     #Mode_UND
    ldr     sp, =Mode_UND_Stack

    /* sys mode and user have common sp register */
    cps     #Mode_SYS
    ldr     sp, =Mode_SYS_Stack

    cpsie i /* 打开全局中断 */

    /* clear bss */
    ldr r1, =__bss_start__
    ldr r2, =__bss_end__
    mov    r0, #0
bss_loop:
    cmp     r1, r2
    itt     lt
    strlt   r0, [r1], #4
    blt    bss_loop


    /* 使能IRQ中断 */
    mrs r0, cpsr        /* 读取cpsr寄存器值到r0中 */
    bic r0, r0, #0x80   /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
    msr cpsr, r0        /* 将r0重新写入到cpsr中 */

swi_code:
    swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

und_code:
    nop              /* for debug */
    .word 0xeeadc0de /* undefine instruction */
    nop              /* for debug */
    .word 0xFFFFFFFF /* undefine instruction */
    nop              /* for debug */

    ldr   r0, =main
    bx    r0


    .align 2
    .arm
    .weak Undefined_Handler
    .type Undefined_Handler, %function
Undefined_Handler:
    /* 执行到这里之前:
    * 1. lr_und 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_und 保存有被中断模式的CPSR
    * 3. CPSR 中的 M4-M0 被设置为 11011, 进入到 und 模式
    * 4. 跳到 0x4 的地方执行程序
    */

    /* 在 und 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {
    
    r0-r12, lr}

    /* 保存现场 */
    /* 处理 und 异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    /* 恢复现场 */
    ldmia sp!, {
    
    r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

und_string:
    .string "undefined instruction exception"

    .align 2
    .arm
    .weak SVC_Handler
    .type SVC_Handler, %function
SVC_Handler:
    /* 执行到这里之前:
    * 1. lr_svc 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_svc 保存有被中断模式的 CPSR
    * 3. CPSR 中的 M4-M0 被设置为 10011, 进入到 svc 模式
    * 4. 跳到 0x08 的地方执行程序
    */

    /* 保存现场 */
    /* 在 swi 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {
    
    r0-r12, lr}

.if 0
    mov r4, lr /* LR 由硬件自动保存软中断指令下一条指令的地址 */

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    sub r0, r4, #4 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    bl printSWIVal
.else
    ldr r4,[lr, #-4] /* LR 由硬件自动保存软中断指令下一条指令的地址,指令地址存放的是指令的操作码,将操作码取出,低 24 位置,即为软中断号 */
    bic r0, r4, #0xff000000 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    push {
    
    r0}

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    pop {
    
    r0}

    bl printSWIVal
.endif

    /* 恢复现场 */
    ldmia sp!, {
    
    r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

swi_string:
    .string "swi exception"


    .align 2
    .arm
    .weak PrefAbort_Handler
    .type PrefAbort_Handler, %function
PrefAbort_Handler:
    ldr r0, =PrefAbort_Handler
    bx r0

    .align 2
    .arm
    .weak DataAbort_Handler
    .type DataAbort_Handler, %function
DataAbort_Handler:
    ldr r0, =DataAbort_Handler
    bx r0

.if 1
    /* 不支持中断嵌套,在 IRQ 模式下处理中断 */
    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function

IRQ_Handler:
    /* 执行到这里之前:
    * 1. lr_irq 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_irq 保存有被中断模式的 CPSR
    * 3. CPSR 中的 M4-M0 被设置为 10010, 进入到 irq 模式
    * 4. 跳到 0x18 的地方执行程序
    */

    /* 保存现场 */
    /* 在 irq 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr-4 是异常处理完后的返回地址, 也要保存 */
    sub lr, lr, #4
    stmfd sp!, {
    
    r0-r12, lr}

    ldr r1, =0x00a00000         /* imx6ull 的 GIC 基地址为 0x00a00000 */
    add r1, r1, #0X2000         /* GIC 基地址加 0X2000,也就是 GIC 的 CPU 接口端基地址 */
    ldr r0, [r1, #0XC]          /* GIC 的 CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器,
                                    * GICC_IAR 寄存器保存这当前发生中断的中断号,我们要根据
                                    * 这个中断号来绝对调用哪个中断服务函数
                                    */

    push {
    
    r0, r1}               /* 保存 r0,r1 */
    bl system_irqhandler        /* 运行 C 语言中断处理函数,带有一个参数,保存在 R0 寄存器中 */
    pop {
    
    r0, r1}
    str r0, [r1, #0X10]         /* r0 存放当前中断号,
                                 * 中断执行完成,将中断号写如 GIC->EOIR(End Of Interrupt Register offset 0x10)
                                 */

    /* 恢复现场 */
    ldmfd sp!, {
    
    r0-r12, pc}^  /* ^会把 spsr_irq 的值恢复到 cpsr 里 */

.else
    /* 支持中断嵌套,在 SVC 模式下处理中断 */
    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function

IRQ_Handler:
    push {
    
    lr}                   /* 保存lr地址 */
    push {
    
    r0-r3, r12}           /* 保存r0-r3,r12寄存器 */

    mrs r0, spsr                /* 读取 spsr 寄存器 */
    push {
    
    r0}                   /* 保存 spsr 寄存器 */

.if 0 /* it always return 0, i don't know why */
    mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
                                * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
                                * Cortex-A7 Technical ReferenceManua.pdf P68 P138
                                */
.else
    ldr r1, =0x00a00000         /* imx6ull 的 GIC 基地址为 0x00a00000 */
.endif
    add r1, r1, #0X2000         /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
    ldr r0, [r1, #0XC]          /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
                                    * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
                                    * 这个中断号来绝对调用哪个中断服务函数
                                    */
    push {
    
    r0, r1}               /* 保存r0,r1 */

    cps #0x13                   /* 进入 SVC 模式,允许其他中断再次进去 */

    push {
    
    lr}                   /* 保存SVC模式的lr寄存器 */
    ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
    blx r2                      /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

    pop {
    
    lr}                    /* 执行完C语言中断服务函数,lr出栈 */
    cps #0x12                   /* 进入 IRQ 模式 */
    pop {
    
    r0, r1}
    str r0, [r1, #0X10]         /* r0 存放当前中断号,
                                 * 中断执行完成,将中断号写如 GIC->EOIR(End Of Interrupt Register offset 0x10)
                                 */

    pop {
    
    r0}
    msr spsr_cxsf, r0           /* 恢复 spsr */

    pop {
    
    r0-r3, r12}            /* r0-r3,r12出栈 */
    pop {
    
    lr}                    /* lr出栈 */
    subs pc, lr, #4             /* 将lr-4赋给pc */

.endif /* 中断嵌套宏判断 */

    .align 2
    .arm
    .weak FIQ_Handler
    .type FIQ_Handler, %function
FIQ_Handler:
    ldr r0, =FIQ_Handler
    bx r0


    .end

猜你喜欢

转载自blog.csdn.net/tyustli/article/details/131513216