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。

在这里插入图片描述
特殊用途寄存器:

  1. 寄存器 r13, 通常用作堆栈指针(sp) , 保存当前处理器模式的堆栈的栈顶;
  2. 寄存器 r14, 通常称作连接寄存器(lr) , 保存调用子程序的返回地址;
  3. 寄存器 r15, 通常称作程序计数器(pc) , 保存的是处理器要取的下一条指令地址;
  4. 寄存器 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异常会使处理器硬件经过以下的一个标准流程:

  1. 处理器切换到一个特定中断请求模式,标明产生了中断;
  2. 响应中断,将前一个模式的cpsr被保存到新的中断请求模式的spsr,pc被保存到新的中断请求模式的lr中;
  3. 关中断,在cpsr中禁止IRQ中断,禁止相同类型的中断请求被响应;
  4. 处理器跳转到向量表中的IRQ入口;
  5. 寄存器上下文保存;
  6. 根据中断向量号,执行用户中断服务函数;
  7. 中断寄存器上下文恢复,执行返回。

在这里插入图片描述
图 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驱动开发指南,上面已经进行了很好介绍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值