正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.4讲 GPIO中断实验-IRQ中断服务函数详解

前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档

正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第15.4 讲” 的读书笔记。第15讲主要是介绍I.MX6U处理器GPIO中断控制实验。本节将参考正点原子的视频教程第15讲和配套的正点原子开发指南文档进行学习。在第15.4讲视频教程中,正点原子会讲解如何实现自己的 IRQ中断服务函数。

0. 概述

1. GIC中断控制器的基地址

1.1 GIC 中断寄存器内存映射基地址

在文档《Cortex-A7 Technical ReferenceManua.pdf》章节“Chapter 8Generic Interrupt Controller”中介绍了Cortex-A7 GIC 中断控制器的内存映射起始基地址,GIC分发器端寄存器描述和偏移,GIC CPU接口端寄存器描述和偏移。

在GIC中断控制器寄存器的内存映射如下表所示,从表中可以看到GIC中断控制器的寄存器内存映射中 GIC 的 CPU Interface(内核接口端)寄存地址范围从 0x2000(4KB)地址偏移开始,到 0x3ffff结束,地址范围为 0x2000~0x3fff。

1.2 GIC的CPU接口端寄存器 GICC_IAR 

在GIC中断控制器CPU Interface(内核接口端)寄存器中本节教程我们需要关注的是 GICC_IAR(Interrupt Acknowledge Register) 寄存器:

1.3 GIC的CPU接口端寄存器 ICC_EOIR 寄存器

当在 IRQ 中断服务函数里中断ID处理完成之后,需要向 GICC_EIOR (End Of Interrupt Register)寄存器写回之前从 GICC_IAR 中读取到的中断ID号,通知 GIC 控制器该中断ID已经处理完成。

2. 正点原子第15.4讲视频教程“IRQ中断服务函数”B站视频弹幕问题的分析

2.1. 问题1:IRQ中断服务函数入口为什么要对 lr, r0-r3, r12, spsr 寄存器入栈?在IRQ组队服务函数的入口不入栈lr寄存器可以么?

为什么要在IRQ中断服务函数入口,对 lr,r1-r3, r12, spsr 寄存器入栈保存 ?
在IRQ中断函数入口不把 lr 入栈保存可以么?在下面的SVC模式下使用的是SVC模式自己的lr_svc寄存器,并没有用到 IRQ 模式下的 lr_irq 寄存器,在IRQ中断服务函数入口是不是就不用将 lr 寄存器入栈?

问题解答(我的理解):

在IRQ中断服务程序入口需要对 lr(IRQ模式下的 lr_irq)寄存器入栈保存,原因是可能存在中断嵌套。在IRQ中断服务程序中通过 'cps #0x13' 处理器进入SVC模式,处理器运行在SVC模式下进入通用中断驱动出来函数 “system_irqhandler()” 执行,此时允许IRQ中断再次发生,此时就发生了IRQ中断嵌套。第一个IRQ中断服务函数函数还没有执行完成(第一个中断处理函数执行到 system_irqhandler()里面),此时发生了第二个IRQ中断,第一个IRQ中断嵌套在通用中断处理函数中执行,如果在IRQ中断服务函数入口不对 'lr'寄存器入栈保存,当发生第二次中断嵌套的时候处理器会自动的将第二次中断的返回地址更新到 'lr_irq' 寄存器,当第二次中断执行完成返回后,第一次中断服务函数的现场里的 'lr_irq' 寄存器里的值就已经被破坏了,第一个IRQ中断服务函数丢失了它自己的'lr' 寄存器的返回地址。

这样当发生中断嵌套的时候,如果不再IRQ中断服务程序入口保存 'lr', 'r1-r3, r12', 'cpsr' 寄存器,发生中断嵌套的时候,前一次还没处理完的中断服务函数的执行现场里的寄存器值 'lr' 就别破坏了,'lr' 寄存器里保存的中断返回地址丢失了。

同样的道理,也可以分析可知,IRQ中断服务函数的入口处需要保存 ‘spsr’ 寄存器,应为IRQ中断服务函数在进行 'cps #0x13' , 'cps #0x12' 切换处理器运行模式的时候,处理器会自动更新 ‘spsr’寄存器,如果不再IRQ中断服务函数的入口处保存 ‘spsr’ 寄存器,被中断程序的'spsr'寄存器返回值就丢失了。

2.2. 问题2:r0,r1不是已经入栈了么?为什么不能直接使用软?

为什么不能直接使用源码第121行里保存入栈r0,r1寄存器,在调用函数system_irqhandler()的时候,r0,r1不是已经入栈了么?为什么不能直接使用栈里面的r0和r1?

我的理解:
1. 因为在IRQ中断服务函数的起始为止入栈的时候处理器是IRQ模式,此时是将 r0, r1 入栈到了IRQ(sp_irq)模式下的栈中保存。此时在通过 'bl system_irqhandler()' 调用C函数的时处理器是SVC模式,在SVC模式下不能访问到 sp_irq 栈里保存的r0, iq,这个只是原因之一。
2. 另一个原因是ARM的 ATPCS(ARM-Thumb Procedure Call Standard) C语言函数调用约定标准,ARM C语言函数如果形参小于等于4个,使用寄存器 r0,r1,r2,r3 来传递C函数形参。我们这里调用 system_irqhandler() 的时候就使用 r0 来传递参数。

2.3 问题3:调用C函数之前为什么要对lr机型入栈?

第一次进来保存的是IRQ模式下的lr

lr入栈是因为切换模式了,不同的模式都要保存一次不同的现场

lr是中断返回地址一定要保持一致,这里跳转了lr的值会被修改,因此使用栈保存

lr是函数返回地址,调用函数前需要保存当前lr,不然会在执行blx时被覆盖

 我的理解:
1. 此时处理器是SVC模式,使用'bl system_irqhandler()' 调用C函数时,在 'bl' 跳转时会自动修改 lr_svc 寄存器的值,如果不在'bl'跳转之前将 'sp_svc' 寄存器的值入栈保存,那从C函数 system_irqhandler() 调用返回之后之前的 'sp_svc' 寄存器的值就丢失了。所以需要再调用C函数 ‘bl’ 跳转之前,先将 'lr_svc' 入栈,C函数调用结束之后再将 'lr_svc' 出栈。
2. ARM 函数调用通过 'bl' 跳转的一个通用流程就是,先 lr 入栈,bl 跳转到函数(处理器会自动修改lr),函数返回后 lr 出栈。

2.4 问题4:忘记了恢复了CPSR?

问题分析:
截图里第135行的代码,将IRQ中断复程序入口处最后压住栈的 spsr 寄存器出栈到 r0 ,然后使用 'msr spsr, r0' 恢复 spsr 寄存器的值。这里仅仅是IRQ中断返回前恢复了 spsr 寄存器的值;cpsr 寄存器里的值还没有回复到被中断之前的状态,那么cpsr寄存器的值是在什么时候被回复的哪?
 
pop {r0}        @sp_irq栈出栈到r0,这里出栈的是程序入口压入栈的spsr寄存器
msr cpsr, r0    @r0传送到spsr寄存器

问题解答:
百度一下IRQ中断服务程序最后一条指令 'subs pc, lr, #4' ,将 lr 寄存器的值减去4个字节复制给 'pc' 寄存器,写 pc 寄存器之后,IRQ中断服务函数调回被被中断的程序继续执行。这里从IRQ中断服务程序调回之前被中断的程序继续执行时,必须从恢复之前被中断函数执行现场的 CPSR 状态寄存器,这是怎么做的呢?在哪里恢复了 CPSR 寄存器呢?

subs pc, lr, #4

使用搜索引擎百度了一下 "subs pc, lr#4" ,就能找到上面问题的答案,在IRQ中断服务函数的最后使用ARM汇编指令 "subs pc, lr#4" , subs 指令不仅仅会将 'lr - 4' 的值赋值给 pc ,同时也会将 spsr 寄存器的值恢复到cpsr中,这样通过最后的一条 subs 汇编指令,IRQ中断服务返回不仅推到了被中断的现场继续执行,也同时恢复了被中断现场的CPSR寄存器。


参考链接:

ARM汇编:SUBS pc, lr, #4_subs 汇编-CSDN博客

也可以参考ARM手册《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》手册,在手册里搜索 "sub pc, lr' 就可以找到对 'subs pc, lr, #<const>' 的解释。

2.5 问题5:IRQ中断返回时为什么 pc=lr-4?

 参考链接:ARM异常中断返回地址计算:

ARM中断异常处理的返回_为什么预取中止是减4-CSDN博客

ARM异常中断返回的几种情况_fiq中断的返回地址-CSDN博客

ARM处理器异常返回地址_arm下触发软中断返回地址出错-CSDN博客

嵌入式系统Linux内核开发实战指南(ARM平台)_7.4 ARM处理器的中断(IRQ或FIQ)在线阅读-QQ阅读

我的理解:
阅读了如上参考链接里的博文,我的理解是这样子的,ARM 指令集的内核在执行完指令之后,开始执行新指令之前,此时PC已经更新 PC=新指令+8(原因是ARM三级流水线),ARM内核在这个点上检查当前是否有IRQ中断需要处理,如果有就跳转到中断(异常)服务函数,在跳转到异常处理函数时处理器会自动更新 LR=PC-4 也就是 LR=(新指令+8)-4,LR指向新指令的下一条。当IRQ中断服务函数中断ID处理完成,需要返回到被中断的还未执行的新指令处继续执行,此时需要更新 PC 为 LR 的值再减去4,PC = ((新指令+8)-4)-4 ,更新PC寄存器会引起ARM多级流水线重新刷新,这样就返回了被中断的新指令处继续执行了。
这也就是为什么在IRQ中断服务函数返回的时候,PC=LR-4 的原因。

2.6: 问题6: IRQ中断服务函数返回的时候

3. IRQ中断服务函数源码

根据上面的分析和结合正点原子第15.4讲视频教程的讲解,编写我们的I.MX6U Cortex-A7 的 IRQ中断服务程序源码如下:

/* IRQ中断服务函数 */
IRQ_Handler:
	@ ldr r0, =IRQ_Handler
	@ bx r0;

	push {lr}					/* lr_irq入栈保存,保存到IRQ栈 */
	push {r0-r3, r12}			/* r0-r3, r12入栈保存,保存到IRQ栈  */
	mrs r0, spsr				/* read cpsr_irq */
	push {r0}					/* spsr_irq 入栈保存,保存到IRQ栈 */
	
	mrc p15, 4, r1, c15, c0, 0	/* 读取cp15协处理器的c15到r1中,也就是 GICC_CBAR 
								 * 的GIC内存映射区的起始基地址 */
	add r1, r1, #0x2000			/* GIC的CPU接口端寄存器基地址 */
	ldr r0, [r1, #0xc]			/* GICC_IAR 寄存器读取到r0,GICC_IAR中可以读取到中断ID号 */
	push {r0,r1}				/* r0,r1入栈保存,保存到IRQ栈,此时r1=GIC内存映射区的基地址,
								 * r0=GICC_IAR */
	
	cps #0x13					/* 进入到 SVC 模式 */
	push {lr}					/* lr_svc入栈保存,保存到SVC栈。因为接下来bl指令会修改lr_svc,
								 * 所以这里在bl指令之前先将 lr_svc 入栈 */
	ldr r2, =system_irqhandler	
	blx r2						/* 调用C函数 system_irqhandler, 通过r0第一个参数给C函数 */
	pop {lr}					/* lr_svc 出栈 */
	cps #0x12					/* 返回到 IRQ 模式*/


	pop {r0,r1}					/* IRQ出栈r0,r1,因为上面在进入SVC模式之前push了r0,r1,所以这里要出栈r0,r1*/
	@中断处理完成必须写 GICC_EOIR(End Of Interrupt Register)寄存器通知GIC控制器中断处理完成
	str r0, [r1, #0x10]			/* r0中保存的是GICC_IAR,r1中保存GIC的CPU接口端寄存器起始基地址,
								 * r1+0x10 是寄存器GICC_EOIR, 写r0到r1+0x10就是 GICC_EOIR=r0,
								 * 也就是写中断ID到GICC_EOIR寄存器 
								 */
	
	@ ldr r0, =30
	@ ldr r2, =beep_times
	@ blx r2

	pop {r0}					/* IRQ栈 cpsr 出栈到r0 */
	msr spsr_cxsf, r0 			/* 恢复cpsr寄存器 */
	pop {r0-r3, r12}			/* IRQ栈 r0-r3,r12 出栈,恢复r0-r3,r12 */
	pop {lr}					/* IRQ栈 lr 出栈,恢复lr */
	@sub pc, lr, #4				/* PC=lr-4 返回到被中断的指令继续执行 */
	subs pc, lr, #4				/* PC=lr-4 返回到被中断的指令继续执行 */

我用Micorsoft PPT 画的上面源码中IRQ中断服务程序中的寄存器CPSR,LR,和处理器各个模式下堆栈的入栈出栈过程,帮助我自己理解IRQ中断服务程序。

  • IRQ中断服务程序汇编源码为什么这样写?
  • 为什么在IRQ入口开始地方需要对lr入栈,对r0-r3,r12入栈?对spsr入栈?
  • 为什么从IRQ模式进入SVC模式之前,再次对r0,r1入栈?
  • 为什么在SVC模式中使用'bl'指令跳转到符号之前,先对lr入栈?
  • 为什么在IRQ中断服务程序返回的地方要对之前保存的spsr出栈?对r0-r3,r12出栈?对lr出栈?
  • 为什么在IRQ中断服务程序最后返回的时候,pc=lr-4?

 上面这些问题都是我们针对IRQ中断程序源码需要思考的问题,只有对IRQ中断服务程序源码里面这十几行汇编源码每一条都搞的非常明白清晰,才算理解了IRQ中断服务程序和Cortex-A7的IRQ中断服务流程。

4. 总结和问题记录分析

4.1 问题1:在IRQ中断服务函数的出口地方使用了 'sub pc, lr, #4' 指令,无法返回到被中断现场继续执行。

问题原因分析:

IRQ中断服务函数返回时,不仅仅需要更新 pc=l4-4 跳回到被中断的指令现场继续执行也需要恢复被中断指令处现场之前的CPSR寄存器。'sub pc, lr, #4' 仅仅更新了pc寄存器的值 pc=lr-4 但是 cpsr 寄存器的值没有被回复到被中断之前现场的CPSR的状态,所以使用'sub pc, lr, #4'返回后程序执行异常。

百度搜索查阅博客,并参考ARMv7的指令手册,在ARMv7指令手册中明确说明在IRQ中断(异常)返回的时候需要使用 'subs pc, lr, #4' 注意这里用的是 'subs' 指令多了一个 's' 。 'subs pc, lr, #4' 不仅仅会跟新pc=lr-4 调到被中断指令现场急促执行同时也会讲 spsr 恢复到cpsr,这样被中断现场的CPSR寄存器就恢复了。

使用 'subs pc, lr, #4' 才能正确的从IRQ中断(异常)返回到被中断现场继续执行。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值