中断基本知识

中断过程:

芯片设计固化了能产生哪些中断,那么每个中断对应的处理地址(这个可以由用户自己指定,一般放在代码的最前面,意思是该中断一旦产生,程序无条件pc指针直接跳转到该处理地址执行,那么就需要解决一个问题,如何跳转回来?(后面有介绍))。

Cortex-A7 内核有 8 个异常中断

arm cortex-A中断基本知识和通用汇编中断服务函数模板(必读!!!)_寄存器

因此对应的中断向量映射表如下:

1 .global _start /* 全局标号 */

2

3 _start:

4         ldr pc, =Reset_Handler /* 复位中断 */

5         ldr pc, =Undefined_Handler /* 未定义指令中断 */

6         ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */

7         ldr pc, =PrefAbort_Handler /* 预取终止中断 */

8         ldr pc, =DataAbort_Handler /* 数据终止中断 */

9         ldr pc, =NotUsed_Handler /* 未使用中断 */

10         ldr pc, =IRQ_Handler /* IRQ 中断 */

11         ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */

12

13 /* 复位中断 */

14 Reset_Handler:

15 /* 复位中断具体处理过程 *

一般用到的是复位中断(板子复位按下会首先执行的),还有IRQ外部中断,即我们在单片机开发时候会接触到的中断,比如外部IO中断,定时器中断(定时器也属于外部中断)等等。

从上面的映射表可以看出,各种中断来了,通知(通过GIC中断控制器(如何访问到GIC,通过的 CP15 协处理器)进行控制,比如中断使能中断优先级,多核的话,通知所有核还是某个核,等等控制)CPU就行跳转处理。IRQ中断,只对应一个处理地址,那么那么多外部中断怎么办呢,那么就由IRQ_Handler这个地址统一处理,将那些外部中断用ID进行来源的标识与判断,和处理。

arm cortex-A中断基本知识和通用汇编中断服务函数模板(必读!!!)_中断处理_02

现在讲解外部IO中断来了,当前正在执行的程序是如何跳转过去中断处理地址,以及如何跳转回来的。

跳转过去

// 对于MX6ULL,程序链接到0x87800000地址,这里执行流程如下:

// 芯片发生    复位,                中断控制器(GIC)会自动让pc跳转到 0x87800000 来执行,这个是芯片固化的
// 芯片发生    未执行出现定义,      中断控制器(GIC)会自动让pc跳转到 0x87800004 来执行,这个是芯片固化的
// ......
// 芯片发生    IRQ中断,            中断控制器(GIC)会自动让pc跳转到 0x87800018 来执行,这个是芯片固化的
// 芯片发生    FIQ(快速中断),         中断控制器(GIC)会自动让pc跳转到 0x8780001c 来执行,这个是芯片固化的

// 即这几个是整个系统程序运行的同等入口,因此也叫做中断向量映射表,这些都是芯片固定的执行流程。

// Reset_Handler程序内,正是跳转到了 main函数

_start:

    ldr pc, =Reset_Handler        // 复位中断                     本条指令地址 0x87800000

    ldr pc, =Undefined_Handler    // 未定义中断                    本条指令地址 0x87800004

    ldr pc, =SVC_Handler        // SVC(Supervisor)中断            本条指令地址 0x87800008

    ldr pc, =PrefAbort_Handler    // 预取终止中断                 本条指令地址 0x8780000c

    ldr pc, =DataAbort_Handler    // 数据终止中断                 本条指令地址 0x87800010

    ldr    pc, =NotUsed_Handler    // 未使用中断                    本条指令地址 0x87800014

    ldr pc, =IRQ_Handler        // IRQ中断                        本条指令地址 0x87800018

    ldr pc, =FIQ_Handler        // FIQ(快速中断)                   本条指令地址 0x8780001c

复位中断的代码如下,最后一行跳转进了main函数

/* 复位中断 */    
Reset_Handler:

    cpsid i                        /* 关闭全局中断 */

    /* 关闭I,DCache和MMU
     * 采取读-改-写的方式。
     */
    mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中                           */
    bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache                */
    bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache                    */
    bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐                        */
    bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测                    */
    bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU                           */
    mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中                     */

    
#if 0
    /* 汇编版本设置中断向量表偏移 */
    ldr r0, =0X87800000

    dsb
    isb
    mcr p15, 0, r0, c12, c0, 0
    dsb
    isb
#endif
   
    /* 设置各个模式下的栈指针,
     * 注意:IMX6UL的堆栈是向下增长的!
     * 堆栈指针地址一定要是4字节地址对齐的!!!
     * DDR范围:0X80000000~0X9FFFFFFF
     */
    /* 进入IRQ模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x12     /* r0或上0x13,表示使用IRQ模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中,此时进入了IRQ模式,访问的寄存器sp就是指IRQ模式中的sp寄存器了 ,可以看我这篇文章arm中SP,LR,PC寄存器以及其它所有寄存器介绍                    */
    ldr sp, =0x80600000    /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

    /* 进入SYS模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x1f     /* r0或上0x13,表示使用SYS模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                     */
    ldr sp, =0x80400000    /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */

    /* 进入SVC模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x13     /* r0或上0x13,表示使用SVC模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                     */
    ldr sp, =0X80200000    /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

    cpsie i                /* 打开全局中断 */
#if 0
    /* 使能IRQ中断 */
    mrs r0, cpsr        /* 读取cpsr寄存器值到r0中             */
    bic r0, r0, #0x80    /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
    msr cpsr, r0        /* 将r0重新写入到cpsr中             */
#endif

    b main                /* 跳转到main函数                  */

外部中断跳进去以及跳出来

/* IRQ中断!重点!!!!! */
IRQ_Handler:                    /* 此时已经自动进入了IRQ模式 */
    push {lr}                    /* 保存lr地址(防止该模式中又有子程序调用导致lr寄存器被覆盖),lr寄存器会自动保存了还未跳转进来,正取指的指令的地址,因此想跳回去接着执行,最后得写上pop{lr},pc=lr-4 */
    push {r0-r3, r12}            /* 保存r0-r3(子程序调用参数传递寄存器,其实不保存也没关系,因为本程序中没用到),r12寄存器:内部程序调用寄存器(ip寄存器) */

    mrs r0, spsr                /* 读取spsr寄存器,spsr寄存器会自动保存了还未跳转进来cpsr寄存器的内容(程序状态寄存器) */
    push {r0}                    /* 保存spsr寄存器,防止模式切换导致spsr寄存器被覆盖 */

    mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中,是中断控制器GIC的基地址
                                * 参考文档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寄存器保存这当前发生中断的中断号,我们要根据
                                 * 这个中断号来绝对调用哪个中断服务函数,获取了的中断号通过 参数传递寄存器r0 传递给具体的中断服务c语言程序
                                 */
    push {r0, r1}                /* 保存r0,r1 */
    
    cps #0x13                    /* 进入SVC模式,此时访问的寄存器都是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(因为arm三级流水线) ,实现跳出去原来被打断原程序继续执行,相当于该中断return,事实上c语言中的return关键字就是干这事*/

实际的C语言中断处理服务函数system_irqhandler模板

/*
 * @description			: C语言中断服务函数,irq汇编中断服务函数会
 						  调用此函数,此函数通过在中断服务列表中查
 						  找指定中断号所对应的中断处理函数并执行。
 * @param - giccIar		: 中断号
 * @return 				: 无
 */
void system_irqhandler(unsigned int giccIar) 
{

   uint32_t intNum = giccIar & 0x3FFUL;
   
   /* 检查中断号是否符合要求 */
   if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
   {
	 	return;
   }
 
   irqNesting++;	/* 中断嵌套计数器加一 */

   /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
   irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
 
   irqNesting--;	/* 中断执行完成,中断嵌套寄存器减一 */

}


#define NUMBER_OF_INT_VECTORS 160                /**< Number of interrupts in the Vector table */

/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

/* 中断服务函数形式 */ 
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);


/* 中断服务函数结构体*/
typedef struct _sys_irq_handle
{
    system_irq_handler_t irqHandler; /* 中断服务函数 */
    void *userParam;                 /* 中断服务函数参数 */
} sys_irq_handle_t;