正点原子[第二期]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: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.2 问题2:调用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.3 问题3:忘记了恢复了CPSR?
2.4 问题4: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 的原因。

3. IRQ中断服务函数源码

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

TypeScript 复制代码
/* 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寄存器 
								 */
	
	pop {r0}					/* IRQ栈 cpsr 出栈到r0 */
	msr spsr, 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 返回到被中断的指令继续执行 */

我用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中断服务流程。

相关推荐
岁岁岁平安21 分钟前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA24 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
qq_5895681029 分钟前
数据可视化echarts学习笔记
学习·信息可视化·echarts
PyAIGCMaster32 分钟前
ubuntu装P104驱动
linux·运维·ubuntu
奈何不吃鱼33 分钟前
【Linux】ubuntu依赖安装的各种问题汇总
linux·运维·服务器
icy、泡芙35 分钟前
T527-----音频调试
linux·驱动开发·音视频
aherhuo38 分钟前
kubevirt网络
linux·云原生·容器·kubernetes
zzzhpzhpzzz1 小时前
Ubuntu如何查看硬件型号
linux·运维·ubuntu
蜜獾云1 小时前
linux firewalld 命令详解
linux·运维·服务器·网络·windows·网络安全·firewalld
兔C1 小时前
微信小程序的轮播图学习报告
学习·微信小程序·小程序