SylixOS中断系统分析
1. 适用范围
本文将会基于ARM架构介绍SylixOS的中断系统,详细的将中断过程进行了分析,对于想了解SylixOS中断有一定帮助。
2. 原理概述
中断就是硬件或软件产生的一个信号,处理器会根据当前中断的状态,中止正常指令执行,转而响应中断请求。中断是嵌入式系统中一个非常重要的概念,深入了解一个架构或者系统,中断是必须要理解的核心概念之一。
ARM架构中外部中断分为普通中断(IRQ)和快速中断(FIQ)两种,本文后续主要以IRQ为例,介绍SylixOS中断的处理过程。在介绍SylixOS中断处理过程之前,需要对中断控制器、异常模式、寄存器组、中断服务流程进行简单的介绍和了解。接下来章节会通过中断控制器了解中断的信号流,以及中断发生时CPU进行的模式切换和自动执行的一些动作,对其中涉及到的模式和寄存器进行讲解,最后概况中断的通用处理流程。
2.1 中断控制器
在数字逻辑层面,ARM架构的外部设备和CPU之间有一条专门的中断信号线,用于连接外设和CPU中断的引脚。当外部设备状态发生改变时,可以通过中断信号线通知CPU。为了接受和处理多设备同时发送中断,ARM引入了中断控制器概念,ARM的中断控制器的的框架如图2.1所示,设备生成中断以信号或者消息的方式发送给中断控制器(GIC),GIC则以IRQ或FIQ方式通知相关GPU,也就是ARM处理器会将每个物理信号线映射为两个中断源IRQ和FIQ。
2.2 异常与模式
对于ARM架构总计有7种异常对应了其中5种处理器模式。异常发生时,会导致内核进入到特定的模式。IRQ也是异常的一种,当CPU响应中断后,会进行相应模式切换。如当IRQ中断发生时,内核则进入到IRQ模式。不同的异常和对应的模式如表2.1所示。
当一个异常导致处理器模式改变时,内核会自动执行一系列动作:
- 把cpsr保存到相应异常模式下的spsr;
- 把pc保存到相应异常模式下的lr;
- 设置cpsr为相应的异常模式;
- 设置pc为相应的异常处理程序的入口地址。
这里面要重点介绍一下当前程序状态寄存器(cpsr)如图2.2所示,ARM内核使用它监视和控制内部操作。后面的处理器模式切换也会反复用到这个寄存器。
cpsr的访问控制权是由处理器的模式决定的,只有特权模式才能对cpsr完全的读写访问,非特权模式只允许对Flags域进行读写访问,支持的7种处理器模式中,仅用户模式是非特权级,其他都是特权级。
2.3 分组寄存器
处理器响应 IRQ 中断切换模式后, 寄存器组也会有部分变化。 先介绍一下通用寄存器,
它是使用字母 r 前缀加寄存器序号进行表示, 如 r0, 可以用来保存数据和地址。 ARM 分组
寄存器共有 37 个, 在不同处理器模式下活动寄存器会有所区别, 最多可以有 18 个活动寄存
器, 16 个数据寄存器和 2 个程序状态寄存器。
如图 2.3 所示, 展示了不同模式下 ARM 寄存器的分组情况。 其中灰色部分寄存器只有
处理器为对应模式时, 才会对编程者可见。 例如当处理器从用户模式切换到中断模式时, 程
序访问的 r13 和 r14 其实是 r13_irq 和 r14_irq, 用户模式下的 r13 和 r14 不会受到任何影响。
由于下面章节主要介绍 IRQ 模式, 所以重点了解一下与之相关的几个特殊用途寄存器, r13、
r14、 r15、 cpsr、 spsr。
特殊用途寄存器:
- 寄存器 r13, 通常用作堆栈指针(sp) , 保存当前处理器模式的堆栈的栈顶;
- 寄存器 r14, 通常称作连接寄存器(lr) , 保存调用子程序的返回地址;
- 寄存器 r15, 通常称作程序计数器(pc) , 保存的是处理器要取的下一条指令地址;
- 寄存器 cpsr 和 spsr, 分别是当前和备份程序状态寄存器。 通过更改 cpsr 可切换改
处理器模式, 异常发生时自动拷贝 cpsr 到 spsr, 手动切换模式不会拷贝 cpsr。
2.4 中断向量表
当一个异常或中断发生时, 处理器会把 PC 设定为一个指定的地址, 这个地址就是向量
表的地址, 向量表的入口是一些跳转指令, 跳转到专门处理异常或者中断的子程序。 图 2.4
展示了异常和中断的种类以及对应的偏移地址。
SylixOS 的中断向量表如程序清单 2.1 所示, IRQ 的入口地址使用 LDR 装载 pc, 以实现
间接地址跳转。
程 序 清 单 2.1 S y l i x O S 中 断 向 量 表 程序清单2.1 SylixOS中断向量表 程序清单2.1SylixOS中断向量表
;/*******************************************************************************************
; 异常向量表
;*******************************************************************************************/
SECTION(.vector)
FUNC_DEF(vector)
LDR PC, resetEntry
LDR PC, undefineEntry
LDR PC, swiEntry
LDR PC, prefetchEntry
LDR PC, abortEntry
LDR PC, reserveEntry
LDR PC, irqEntry
LDR PC, fiqEntry
FUNC_END()
FUNC_LABEL(resetEntry)
.word reset
FUNC_LABEL(undefineEntry)
.word archUndEntry
FUNC_LABEL(swiEntry)
.word archSwiEntry
FUNC_LABEL(prefetchEntry)
.word archPreEntry
FUNC_LABEL(abortEntry)
.word archAbtEntry
FUNC_LABEL(reserveEntry)
.word 0
FUNC_LABEL(irqEntry)
.word archIntEntry
FUNC_LABEL(fiqEntry)
.word 0
2.5 流水线与PC
使用流水线可以加快执行速度,在取下一条指令的同时译码和执行其他指令。一个经典的三级流水线分为取指、译码、执行三个阶段,分别介绍如下:
- 取指(fetch):从存储器装载一条指令;
- 译码(decode):识别将被执行的指令;
- 执行(excute):处理指令并把结果写回寄存器。
在中断服务函数中会看到开始有程序清单2.2所示代码,主要进行返回地址调整,这里面就是涉及到了流水线知识。
程 序 清 单 2.2 调 整 返 回 地 址 程序清单2.2 调整返回地址 程序清单2.2调整返回地址
SUB LR , LR, #4
以一段代码为例介绍流水线和pc的使用情况,如图2.5所示,当指令在执行阶段,pc总是执行该指令地址加8的地址,换句话说,pc总是指向当前正在执行的指令地址在加两条指令的地址。当异常发生时cpsr会总是切换到ARM状态,在ARM状态下每条指令占用4个字节,所以增加两条指令的地址,自然是pc+8。
图
2.5
p
c
=
a
d
d
r
e
s
s
+
8
图2.5 pc=address + 8
图2.5pc=address+8
当一个中断产生时,一条处于执行的指令会继续执行完成的,之后才会响应中断,处理中断时CPU会自动将当前pc会保存到lr中,而此时处于译码阶段的指令实际并未执行,因此在IRQ中断中要调整到正确的返回地址,即LR=LR-4。中断执行完成返回后就会从之前译码阶段指令继续执行。上面是以3级流水线为例,5级或7级流水线对也是一样,就不进行展开说明。
2.6 中断服务流程
一个IRQ异常会使处理器硬件经过以下的一个标准流程:
- 处理器切换到一个特定中断请求模式,标明产生了中断;
- 响应中断,将前一个模式的cpsr被保存到新的中断请求模式的spsr,pc被保存到新的中断请求模式的lr中;
- 关中断,在cpsr中禁止IRQ中断,禁止相同类型的中断请求被响应;
- 处理器跳转到向量表中的IRQ入口;
- 寄存器上下文保存;
- 根据中断向量号,执行用户中断服务函数;
- 中断寄存器上下文恢复,执行返回。
图
2.6
中
断
执
行
流
程
图2.6 中断执行流程
图2.6中断执行流程
3. 准备工作
3.1 环境准备
本文介绍是以mini2440平台为例,需要事先部署SylixOS开发环境,安装好RealEvo-IDE以便于代码查看和测试。
3.2 资源准备
本文涉及到的代码为SylixOS 的Base工程和mini2440的BSP工程,都可以从翼辉官网进行获取。
4. 技术实现
SylixOS中断处理流程如图4.1所示,整个中断处理过程中,涉及到的相关函数调用。从图4.1中还可以了解到,所有IRQ入口函数都为archIntEntry()函数,archIntEntry()内部经过一些函数调用,最终会找到驱动层注册的中断服务函数,完成整个中断过程。
图
4.1
S
y
l
i
x
O
S
中
断
处
理
流
程
图4.1 SylixOS中断处理流程
图4.1SylixOS中断处理流程
SylixOS中断处理过程主要涉及到系统的三个层次,分别为Bsp层、Arch层、Kernel层:
- Bsp层主要通过向量表跳转到中断入口和获取中断向量号;
- Arch层主要向Bsp层提供统一的IRQ入口以及体系架构相关的上下文保存,之后调用Kernel层API进行中断处理,同时也会调用部分Bsp层函数进行中断操作;
- Kernel层主要通过链表查找当前中断的服务函数,完成中断调用。
图
4.2
S
y
l
i
x
O
S
中
断
层
次
关
系
图4.2 SylixOS中断层次关系
图4.2SylixOS中断层次关系
SylixOS中断处理过程中关键函数的介绍如下:
- archIntEntry函数,执行中断过程中的上下文保存和恢复,同时进行中断嵌套处理;
- API_InterEnter 函数,第一次中断进入时,拷贝当前寄存器上下文到任务TCB;
- bspIntHandler函数,底层中断入口函数,主要获取当前中断的向量号;
- archIntHandler函数,对向量号进行合法性检查,同时判断是否需要开启中断嵌套;
- API_InterVectorIsr函数,向量中断服务总函数,根据中断号得到对应的中断服务链表,找到具体的中断服务函数;
- API_InterExit函数,中断结束时寄存器上下文恢复。
经过以上介绍已经了解了SylixOS中断函数调用过程,接下来章节将会从中断上下文保存恢复、中断服务函数查找、bsp中断支持、中断相关API四部分进行详细介绍。
4.1 上下文处理
中断上下文处理的主要函数是archIntEntry,程序源码可以直接查看文件“libsylixos\SylixOS\arch\arm\common\armExcAsm.S”中的archIntEntry。接下来章节会以代码执行涉及到中断栈变化为路线,逐步分解archIntEntry整个操作过程。
4.1.1 构建寄存器上下文
程序执行到archIntEntry说明已经发生中断并且通过中断向量表正确跳转到了中断入口函数。如图4.3所示,此部分代码主要构建寄存器上下文进行保存,如果不进行保存,后续中断重入和其他函数调用会破坏掉相关寄存器。
图
4.3
构
建
寄
存
器
上
下
文
图4.3 构建寄存器上下文
图4.3构建寄存器上下文
代码第62行,通过SUB指令调整LR找到正确的中断返回地址,即PC值,前面介绍流水线和PC关系章节已经介绍过,LR=LR-4就是正确的返回地址。
代码第6364行,通过STMFD指令将LR,R0R12保存到IRQ栈中。CPU进入中断时会自动将sp指向中断栈栈顶,中断栈大小和配置是在bsp中startup.S中进行初始化,如程序清单4.1所示。
程 序 清 单 4.1 初 始 化 堆 栈 程序清单4.1 初始化堆栈 程序清单4.1初始化堆栈
;/*******************************************************************************************
; 初始化堆栈
;*******************************************************************************************/
LDR R0 , =__stack_end ;/* 栈区顶端地址 */
MSR CPSR_c, #(SVC32_MODE | DIS_INT)
MOV SP , R0
SUB R0 , R0, #SVC_STACK_SIZE
MSR CPSR_c, #(SYS32_MODE | DIS_INT)
MOV SP , R0
SUB R0 , R0, #SYS_STACK_SIZE
MSR CPSR_c, #(FIQ32_MODE | DIS_INT)
MOV SP , R0
SUB R0 , R0, #FIQ_STACK_SIZE
MSR CPSR_c, #(IRQ32_MODE | DIS_INT)
MOV SP , R0
SUB R0 , R0, #IRQ_STACK_SIZE
...
代码第65-72行,通过MSR保存系统模式下的sp和lr到当前中断栈,以及通过MRS将IRQ模式下的spsr继续保存到IRQ中断栈。
至此就已经构建完成了寄存器上下文,最后IRQ中断栈的效果如图4.4所示。
图
4.4
中
断
上
下
文
栈
帧
图4.4 中断上下文栈帧
图4.4中断上下文栈帧
4.1.2 保存寄存器上下文
第一次进入中断需要将当前IRQ模式下构建的寄存器上下文拷贝到当前任务TCB的ARCH_REG_CTX中,如图4.5所示。
图
4.5
寄
存
器
上
下
文
保
存
图4.5 寄存器上下文保存
图4.5寄存器上下文保存
代码第78~81行,就是通过R0寄存器将SP作为参数传递到API_InterEnter函数内部,之后通过BX执行函数调用。
API_InterEnter函数通过调用archIntCtxSaveReg函数判断是否属于第一次进入中断,如果是则进行中断寄存器上下文拷贝,如程序清单4.2所示,archTaskCtxCopy函数的参数TCB_archRegCtx指向目的地址,是ARCH_REG_CTX结构体类型。reg0是原地址,即前面构建的中断寄存器上下文,这里将reg0通过ARCH_REG_CTX结构体进行了强制类型转换,之后才进行拷贝。
程 序 清 单 4.2 a r c h I n t C t x S a v e R e g 函 数 程序清单4.2 \ archIntCtxSaveReg函数 程序清单4.2 archIntCtxSaveReg函数
/*******************************************************************************************
** 函数名称: archIntCtxSaveReg
** 功能描述: 中断保存寄存器
** 输 入 : pcpu CPU 结构
** reg0 寄存器 0
** reg1 寄存器 1
** reg2 寄存器 2
** reg3 寄存器 3
** 输 出 : NONE
** 全局变量:
** 调用模块:
*******************************************************************************************/
VOID archIntCtxSaveReg (PLW_CLASS_CPU pcpu,
ARCH_REG_T reg0,
ARCH_REG_T reg1,
ARCH_REG_T reg2,
ARCH_REG_T reg3)
{
if (pcpu->CPU_ulInterNesting == 1) {
archTaskCtxCopy(&pcpu->CPU_ptcbTCBCur->TCB_archRegCtx, (ARCH_REG_CTX *)reg0);
}
}
最终执行拷贝的函数是archTaskCtxCopy,如图4.6所示,是一段汇编指令,就是分两步将R1源地址拷贝到R0目的地址。实现了将中断上下文保存的寄存器拷贝到当前任任务TCB指向的寄存器上下文结构。
图
4.6
a
r
c
h
T
a
s
k
C
t
x
C
o
p
y
函
数
图4.6 archTaskCtxCopy函数
图4.6archTaskCtxCopy函数
ARCH_REG_CTX结构体定义如程序清单4.3所示,它是任务控制块结构体LW_CLASS_TCB中的第一个成员,作为第一个成员后面上下文恢复会非常方便,同时也可以看到它的成员和archIntEntry构建的寄存器上下文是能够一一对应的。
程 序 清 单 4.3 A R C H _ R E G _ C T X 结 构 体 程序清单4.3 \ ARCH\_REG\_CTX结构体 程序清单4.3 ARCH_REG_CTX结构体
typedef UINT32 ARCH_REG_T;
typedef struct {
ARCH_REG_T REG_uiCpsr;
ARCH_REG_T REG_uiR14;
ARCH_REG_T REG_uiR13;
ARCH_REG_T REG_uiR0;
ARCH_REG_T REG_uiR1;
ARCH_REG_T REG_uiR2;
ARCH_REG_T REG_uiR3;
ARCH_REG_T REG_uiR4;
ARCH_REG_T REG_uiR5;
ARCH_REG_T REG_uiR6;
ARCH_REG_T REG_uiR7;
ARCH_REG_T REG_uiR8;
ARCH_REG_T REG_uiR9;
ARCH_REG_T REG_uiR10;
ARCH_REG_T REG_uiR11;
ARCH_REG_T REG_uiR12;
ARCH_REG_T REG_uiR15;
#define REG_uiFp REG_uiR11
#define REG_uiIp REG_uiR12
#define REG_uiSp REG_uiR13
#define REG_uiLr REG_uiR14
#define REG_uiPc REG_uiR15
} ARCH_REG_CTX;
总结一下寄存器上下文保存的过程如图4.7所示,总体来说就是将构建好的中断寄存器上下文,拷贝到了当前任务控制块中的寄存器上下文进行保存。
图
4.7
寄
存
器
上
下
文
保
存
图4.7 寄存器上下文保存
图4.7寄存器上下文保存
4.1.3 首次进入中断处理
当第一次进入中断时,还需进行一些其他配置工作,如图4.8所示。
图
4.8
首
次
中
断
配
置
工
作
图4.8 首次中断配置工作
图4.8首次中断配置工作
代码第93行,由于之前操作已经将IRQ模式栈空间的寄存器上下文保存到了当前任务TCB的寄存器上下文,因此IRQ栈里的寄存器上下文就可以不必保存,这里通过调整SP将指针回退,回退大小为ARCH_REG_CTX_SIZE大小,最终中断栈结果如图4.9所示。
代码第98~103行,通过API_InterStackBaseGet获取当前CPU的中断栈,之后切换到系统模式,至此后面的函数调用将会使用操作系统定义的中断栈。这便于后面的中断栈管理和使能中断优先级抢占。
4.1.4 执行中断服务函数
中断处理最终就是找到与中断号对应的中断服务函数,之后进行处理。如图4.10所示,代码第111行,调用bspIntHandle获取本次中断号,最终调用API_InterVectorIsr函数完成中断服务。
API_InterVectorIsr函数如程序清单4.4所示。更详细的处理过程会在后续章节专门进行分析。
程
序
清
单
4.4
A
P
I
_
I
n
t
e
r
V
e
c
t
o
r
I
s
r
函
数
程序清单4.4 \ API\_InterVectorIsr函数
程序清单4.4 API_InterVectorIsr函数
/*******************************************************************************************
** 函数名称: API_InterVectorIsr
** 功能描述: 向量中断总服务
** 输 入 : ulVector 中断向量号 (arch 层函数需要保证此参数正确)
** 输 出 : 中断返回值
** 全局变量:
** 调用模块:
** 注 意 : 这里并不处理中断嵌套, 他需要 arch 层移植函数支持.
API 函数
*******************************************************************************************/
LW_API
irqreturn_t API_InterVectorIsr (ULONG ulVector)
{
PLW_CLASS_CPU pcpu;
PLW_LIST_LINE plineTemp;
PLW_CLASS_INTDESC pidesc;
PLW_CLASS_INTACT piaction;
irqreturn_t irqret = LW_IRQ_NONE;
#if LW_CFG_INTER_MEASURE_HOOK_EN > 0
struct timespec tv;
#endif
pcpu = LW_CPU_GET_CUR(); /* 中断处理程序中, 不会改变 CPU*/
#if LW_CFG_CPU_INT_HOOK_EN > 0
__LW_CPU_INT_ENTER_HOOK(ulVector, pcpu->CPU_ulInterNesting);
#endif /* LW_CFG_CPU_INT_HOOK_EN > 0 */
#if LW_CFG_SMP_EN > 0
if (pcpu->CPU_ulIPIVector == ulVector) { /* 核间中断 */
_SmpProcIpi(pcpu);
if (pcpu->CPU_pfuncIPIClear) {
pcpu->CPU_pfuncIPIClear(pcpu->CPU_pvIPIArg, ulVector); /* 清除核间中断 */
}
} else
#endif /* LW_CFG_SMP_EN */
{
pidesc = LW_IVEC_GET_IDESC(ulVector);
if (pidesc->IDESC_ulFlag & LW_IRQ_FLAG_QUEUE) {
#if LW_CFG_SMP_EN > 0
LW_SPIN_LOCK(&pidesc->IDESC_slLock); /* 锁住 spinlock */
#endif /* LW_CFG_SMP_EN > 0 */
for (plineTemp = pidesc->IDESC_plineAction;
plineTemp != LW_NULL;
plineTemp = _list_line_get_next(plineTemp)) {
piaction = (PLW_CLASS_INTACT)plineTemp;
INTER_VECTOR_SVC(break;);
}
#if LW_CFG_SMP_EN > 0
LW_SPIN_UNLOCK(&pidesc->IDESC_slLock); /* 解锁 spinlock */
#endif /* LW_CFG_SMP_EN > 0 */
} else {
piaction = (PLW_CLASS_INTACT)pidesc->IDESC_plineAction;
if (piaction) {
INTER_VECTOR_SVC(;);
} else {
_DebugFormat(__ERRORMESSAGE_LEVEL, "interrupt vector: %ld no service.\r\n", ulVector);
}
}
}
#if LW_CFG_CPU_INT_HOOK_EN > 0
__LW_CPU_INT_EXIT_HOOK(ulVector, pcpu->CPU_ulInterNesting);
#endif /* LW_CFG_CPU_INT_HOOK_EN > 0 */
return (irqret);
}
4.1.5 恢复寄存器上下文
执行完成中断会恢复寄存器上下文,这里会分为两种情况,无中断嵌套和有中断嵌套。
如果没有发生中断嵌套,如图4.11所示,会调用API_InterExi函数,它根据CPU_ulInterNesting自减为0之后,调用archIntCtxLoad将之前保存寄存器上下文恢复到当前任务TCB。
无中断嵌套寄存器上下文恢复的调用关系如图 4.12 所示。
其中archTaskCtxStart和archTaskCtxLoad代码最为重要,它们会在SVC模式下将之前保存到任务TCB的寄存器恢复到的寄存器组里面。
恢复过程如图4.14所示,archTaskCtxLoad首先读取TCB寄存器上下文的REG_uiCpsr,REG_uiR14,REG_uiR13到R2~R4寄存器,之后通过R2~R4将REG_uiR13恢复到SYS模式下的r13,将REG_uiR14恢复到r14。完成后切换到SVC模式,这时将REG_uiCpsr恢复到spsr_svc,之后执行LDMIA命令将REG_uiR0~REG_uiR12以及REG_uiR15分别恢复到r0~r12和r15(pc),同时将spsr_svc更新到cpsr,最终完成上下文恢复。
图
4.14
恢
复
过
程
和
结
果
图4.14 恢复过程和结果
图4.14恢复过程和结果
如果发生了中断嵌套,如图4.15所示,恢复过程和非中断嵌套类似,区别是将IRQ中断栈里的寄存器进行恢复,这里就不进行详细介绍,可以参考非中断嵌套。
最后附上完整的archIntEntry代码,如程序清单4.5所示,到这里整个archIntEntry处理过程分析完成。
程
序
清
单
4.5
a
r
c
h
I
n
t
E
n
t
r
y
完
整
代
码
程序清单4.5 archIntEntry完整代码
程序清单4.5archIntEntry完整代码
;/*******************************************************************************************
; 中断入口
;*******************************************************************************************/
FUNC_DEF(archIntEntry)
;/*
; * 保存 REG 到 IRQ 模式栈空间(这里做了个必须成立的假设, 之前必须工作在 SYS 或 USR 模式)
; */
SUB LR , LR, #4 ;/* 调整用于中断返回的 PC 值 */
STMFD SP!, {LR} ;/* 保存返回地址 */
STMFD SP!, {R0-R12} ;/* 保存寄存器 */
MOV R1 , SP
MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式 */
STMFD R1!, {SP} ;/* 保存 SP_sys */
STMFD R1 , {LR} ;/* 保存 LR_sys */
MSR CPSR_c, #(DIS_INT | IRQ32_MODE) ;/* 回到 IRQ 模式 */
SUB SP , SP , #(2 * 4) ;/* 调整 SP_irq */
MRS R2 , SPSR
STMFD SP!, {R2} ;/* 保存 CPSR_sys */
;/*
; * API_InterEnter(SP_irq), 如果是第一次中断, 会将 IRQ 模式栈空间的 ARCH_REG_CTX
; * 拷贝到当前任务 TCB 的 ARCH_REG_CTX 里
; */
MOV R0 , SP
LDR R1 , =API_InterEnter
MOV LR , PC
BX R1
;/*
; * 如果不是第一次进入中断, 那么上一次中断(工作在 SYS 模式)已经设置 SP_sys, 只需要回到
; * SYS 模式
; */
CMP R0 , #1
BNE 1f
;/*
; * 第一次进入中断: 因为已经将 IRQ 模式栈空间的 ARCH_REG_CTX 拷贝到当前任务 TCB 的
; * ARCH_REG_CTX 里调整 SP_irq
; */
ADD SP , SP , #(ARCH_REG_CTX_SIZE)
;/*
; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并回到 SYS 模式, 并设置 SP_sys
; */
LDR R0 , =API_InterStackBaseGet
MOV LR , PC
BX R0
MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式 */
MOV SP , R0 ;/* 设置 SP_sys */
1:
MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式(不是多余的) */
;/*
; * bspIntHandle()
; */
LDR R1 , =bspIntHandle
MOV LR , PC
BX R1
;/*
; * API_InterExit()
; * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数, SP_irq 在上面已经调整好
; */
LDR R1 , =API_InterExit
MOV LR , PC
BX R1
;/*
; * 来到这里, 说明发生了中断嵌套
; */
MSR CPSR_c, #(DIS_INT | IRQ32_MODE) ;/* 回到 IRQ 模式 */
MOV R0 , SP
LDMIA R0!, {R2-R4} ;/* 读取 CPSR LR SP */
ADD SP , SP , #(ARCH_REG_CTX_SIZE) ;/* 调整 SP_irq */
MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式 */
MOV SP , R4 ;/* 恢复 SP_sys */
MOV LR , R3 ;/* 恢复 LR_sys */
MSR CPSR_c, #(DIS_INT | IRQ32_MODE) ;/* 回到 IRQ 模式 */
MSR SPSR_cxsf , R2
LDMIA R0 , {R0-R12, PC}^ ;/* 恢复包括 PC 的所有寄存器, */
;/* 同时更新 CPSR */
FUNC_END()
4.2 中断服务查找
中断发生,进入总中断archIntEntry后,会调用bspIntHandle获取响应的中断号,之后通过调用API_InterVectorIsr遍历中断服务列表,完成中断处理,调用关系如图4.16所示。
SylixOS系统使用一张表描述所支持的中断数量,由kernel_cfg.h中的宏进行配置,如程序清单4.6所示,默认支持256个中断。
程
序
清
单
4.6
L
W
_
C
F
G
_
M
A
X
_
I
N
T
E
R
_
S
R
C
程序清单4.6 LW\_CFG\_MAX\_INTER\_SRC
程序清单4.6LW_CFG_MAX_INTER_SRC
#define LW_CFG_MAX_INTER_SRC 256 /* 系统使用中断向量表大小,中断源数量 < 9999 */
SylixOS在k_globalvar.h中定义了系统中断向量表,如程序清单4.7所示。
程
序
清
单
4.7
中
断
描
述
表
程序清单4.7 中断描述表
程序清单4.7中断描述表
/*******************************************************************************************
系统中断向量表
*******************************************************************************************/
__KERNEL_EXT LW_CLASS_INTDESC _K_idescTable[LW_CFG_MAX_INTER_SRC];
#ifdef __KERNEL_MAIN_FILE
LW_SPINLOCK_CA_DEFINE_CACHE_ALIGN (_K_slcaVectorTable);
#else
__KERNEL_EXT LW_SPINLOCK_CA_DECLARE (_K_slcaVectorTable);
#endif /* __KERNEL_MAIN_FILE */
API_InterVectorIsr中就是通过LW_IVEC_GET_IDESC快速获取与之对应的中断描述,具体代码如图4.17第124行所示,最后调用INTER_VECTOR_SVC完成中断调用。
5. 总结
本文基于ARM架构介绍了SylixOS的IRQ中断系统处理过程,主要针对archIntEntry和API_InterVectorIsr处理细节进行了分解和介绍,对其中涉及到的堆栈变化进行了详细介绍,希望本文能够对学习SylixOS中断系统的同学有所帮助。
针对中断编程和bsp支持可以参考SylixOS驱动开发指南,上面已经进行了很好介绍。