【嵌入式Linux】i.MX6ULL IRQ中断服务函数的编写


本文章结合了正点原子的 i.mx6u嵌入式Linux开发指南和笔者的理解。

IRQ中断服务函数流程解释

IRQ Interrupt Request 外部中断

0. 基本流程步骤

整个IRQ中断服务函数的流程可以简洁地概括为以下几个关键步骤:

  1. 保存环境:

    • 保存当前任务的关键寄存器(r0-r3, r12, lr)和程序状态寄存器(SPSR)以保护执行环境。
  2. 获取中断源:

    • 从Generic Interrupt Controller (GIC)读取当前触发的中断号,这是识别和处理特定中断的关键步骤。
  3. 模式切换:

    • 切换到SVC(Supervisor Call)模式以允许中断嵌套,即在处理当前中断时允许其他中断的处理。
  4. 执行中断处理:

    • 调用C语言编写的中断处理函数system_irqhandler,处理具体的中断逻辑。
  5. 中断结束处理:

    • 将处理完成的中断号写入GICC_EOIR寄存器,通知GIC中断已被处理。
  6. 恢复环境并退出:

    • 恢复之前保存的各寄存器和程序状态,通过更新程序计数器完成从中断返回到主程序的流程。

这些步骤确保了系统在响应中断时能够保持稳定和可预测,同时保护了任务的执行状态不受中断影响。

1. 入口部分

  1. 保存寄存器状态:
    push {lr}                   /* 保存链接寄存器(lr)的值,即返回地址 */
    push {r0-r3, r12}           /* 保存通用寄存器r0到r3和r12 */
    
    这些步骤是中断服务例程的标准做法,用于保护执行环境,使得中断处理过程不会影响当前执行的任务。
  • R0-R3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。
  • R4-R11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。
  • R12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复
    r12。
  • R13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。
  • R14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复
  • R15 是程序计数器 PC。它不能用于任何其它用途。
  • 注意:在中断程序中,所有的寄存器都必须保护,编译器会自动保护R4~R11
  1. 保存程序状态寄存器:
    mrs r0, spsr                /* 读取保存程序状态寄存器(SPSR)到r0 */
    push {r0}                   /* 将SPSR的值压栈保存 */
    
    SPSR保存了中断发生时的处理器状态,需要在中断处理后恢复。
  • CPSR: Current Program Status Register 当前程序状态寄存器
  • SPSR: Saved Program Status Register 程序状态保存寄存器 当特定的异常中断发生时,SPSR用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后,可以用 SPSR 中保存的值来恢复 CPSR。

2. 读取中断号

  1. 获取中断号:
    mrc p15, 4, r1, c15, c0, 0  /* 从CP15协处理器中获取GIC基地址 */
    add r1, r1, #0x2000         /* 计算GIC的CPU接口端基地址 */
    ldr r0, [r1, #0xC]          /* 从GICC_IAR寄存器读取当前的中断号 */
    push {r0, r1}               /* 保存中断号和GIC基地址 */
    
    这部分代码是从GIC(Generic Interrupt Controller)中获取当前触发的中断号,这是处理中断的关键步骤,用于确定具体的中断处理函数。
  • c15寄存器
    Cortex-A7 Technical ReferenceManua.pdf P68
    在这里插入图片描述
  • mrc p15, 4, r1, c15, c0, 0 通过c15寄存器读取了CBAR寄存器
    Cortex-A7 Technical ReferenceManua.pdf P138
    在这里插入图片描述
    在这里插入图片描述
  • 获取GIC块在基地址上的偏移大小
    Cortex-A7 Technical ReferenceManua.pdf P138
    在这里插入图片描述
  • 获取GICC_IAR寄存器的在CPU interface上的偏移地址(记录了中断号)为0x000C
    Cortex-A7 Technical ReferenceManua.pdfP188
    在这里插入图片描述

3. 切换模式并调用C语言处理函数

  1. 切换到SVC模式以允许中断嵌套:
    cps #0x13                   /* 切换到SVC(Supervisor Call)模式 */
    push {lr}                   /* 保存SVC模式的链接寄存器 */
    
    这允许在处理当前中断的过程中再次接受其他中断,实现中断嵌套。
  • CPS 是 Change Processor State 的缩写 [6],它可以改变 CPSR 中的 A、I、F 中断屏蔽位以及 M 模式位,而不会改变其他 CPSR 位 [6]。
  • cps #0x13 就是改变了M模式位(处理器模式控制位)
    【正点原子】LMX6U嵌入式Linux驱动开发指南V1.81.pdfP294
    在这里插入图片描述
    ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdfP1139
    在这里插入图片描述
  1. 调用C语言中断处理函数:
    ldr r2, =system_irqhandler  /* 将C语言编写的中断处理函数地址加载到r2 */
    blx r2                      /* 使用链接寄存器跳转到该函数执行 */
    
    这里,system_irqhandler 是实际处理中断的C语言函数,可以根据r0中的中断号执行相应的操作。
  • blx 的全称是 Branch and Link with eXchange。它是一个 ARM 指令,用于跳转到一个新的地址并保存当前的 PC 值到 LR 寄存器。
  • blx 指令可以改变处理器状态,从 ARM 状态切换到 Thumb 状态,或者从 Thumb 状态切换到 ARM 状态。这取决于目标地址的最低位。
  • 如果目标地址的最低位为 0,则处理器状态将切换到 ARM 状态。如果目标地址的最低位为 1,则处理器状态将切换到 Thumb 状态。

4. 清理和恢复环境

  1. 恢复IRQ模式并完成中断处理:
    pop {lr}                    /* 恢复SVC模式的lr寄存器 */
    cps #0x12                   /* 切回IRQ模式 */
    pop {r0, r1}                /* 恢复r0, r1寄存器值 */
    str r0, [r1, #0x10]         /* 向GICC_EOIR寄存器写入中断结束信号 */
    
    这些步骤标志着中断处理的结束,通过向GICC_EOIR寄存器写入中断号来通知GIC中断已被处理。
  • pop {lr} 指令用于从堆栈中取出(pop)一个值并将其存入到链接寄存器(Link Register, 简称 LR)中

  • 为什么中断发生时,要进入SVC模式后进行中断函数的执行,执行完成后又进入IRQ模式

    1. 中断函数执行完后需要从 SVC 模式变回 IRQ 模式。 这是因为 SVC 模式用于处理系统调用,而 IRQ 模式用于处理外部中断。
    2. 中断发生时,处理器会从当前模式切换到 IRQ 模式,并执行相应的中断服务函数。
    3. 中断服务函数可能需要调用系统调用来完成某些操作。 例如读取设备寄存器或发送数据。
    4. 为了执行系统调用,处理器需要切换到 SVC 模式。
    5. 系统调用执行完毕后,处理器需要返回到 IRQ 模式,继续处理中断。
    6. 因此,中断函数执行完后需要从 SVC 模式变回 IRQ 模式,以确保中断处理流程的完整性。
  • 写结束信号

    • 获取GICC_EOIR寄存器的在CPU interface上的偏移地址(记录了中断号)为0x00010
      Cortex-A7 Technical ReferenceManua.pdfP189
      在这里插入图片描述
    • GICC_EOIR寄存器结构
      ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdfP214
      在这里插入图片描述
  1. 恢复处理器状态并退出中断:
    pop {r0}                    /* 恢复保存的SPSR */
    msr spsr_cxsf, r0           /* 将SPSR的值恢复到处理器状态寄存器 */
    pop {r0-r3, r12}            /* 恢复通用寄存器 */
    pop {lr}                    /* 恢复链接寄存器 */
    subs pc, lr, #4             /* 通过链接寄存器设置程序计数器,完成中断返回 */
    
    这最后的步骤将处理器状态恢复到中断发生前的状态,并通过subs pc, lr, #4指令安全地返回到中断点。
  • 为什么这里要将lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?

核心原因: ARM 处理器采用流水线执行指令,当前执行的指令地址和下一条将要执行的指令地址并不一致。
具体解释:

  1. ARM 处理器流水线: ARM 处理器采用三级流水线,分别为取指、译指和执行。这意味着处理器会同时进行取指、译指和执行三个步骤,而不是顺序执行。
  2. PC 指针: PC 寄存器指向的是正在取值的指令地址,而不是正在执行的指令地址。
  3. 中断发生时: 当中断发生时,处理器会保存当前 PC 指针的值到 LR 寄存器。此时,LR 寄存器保存的是下一条将要执行的指令地址,而不是当前正在执行的指令地址。
  4. 中断返回: 中断处理完成后,需要返回到中断发生的位置继续执行。如果直接将 LR 赋值给 PC,那么处理器会跳过当前正在执行的指令,导致程序执行错误。
  5. lr - 4 赋值给 pc 为了确保程序正常执行,需要将 LR 寄存器的值减去 4,然后赋值给 PC 寄存器。这样,PC 指针就会指向中断发生时正在执行的指令的下一条指令,从而保证程序能够正常执行。

示例:

0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC
  • 中断发生时,LR 寄存器保存的是 0X2008,因为 PC 指针指向的是下一条将要执行的指令地址。
  • 如果直接将 LR 赋值给 PC,处理器会跳过 0X2004 处的指令 MOV R2, R3,导致程序执行错误。
  • lr - 4 赋值给 PC,即 PC = 0X2004,处理器会从 0X2004 处的指令 MOV R2, R3 开始执行,保证程序正常执行。

总结:

lr - 4 赋值给 pc 是为了解决 ARM 处理器流水线执行指令带来的问题,确保中断处理完成后能够正常返回到中断发生的位置继续执行程序。

5. 完整代码

@ IRQ中断服务函数
IRQ_Handler:
  push {lr}				    /* 保存lr地址,Link Register, R14 寄存器*/
  push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */

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

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

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__Witheart__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值