ARM处理器工作模式
ARM处理器工作模式一共有 7 种:
- 
    USR 模式 正常用户模式,程序正常执行模式 
- 
    FIQ模式(Fast Interrupt Request) 处理快速中断,支持高速数据传送或通道处理 
- 
    IRQ模式 处理普通中断 
- 
    SVC模式(Supervisor) 操作系统保护模式,处理软件中断swi reset 
- 
    ABT中止(Abort mode){数据、指令} 处理存储器故障、实现虚拟存储器和存储器保护 
- 
    UND 未定义(Undefined 处理未定义的指令陷阱,支持硬件协处理器的软件仿真 
- 
    SYS 系统模式(基本上=USR)(System 运行特权操作系统任务 
用户模式和特权模式
除了用户模式之外的其他6种处理器模式称为特权模式
特权模式下,程序可以访问所有的系统资源,也可以任意地进行处理器模式的切换。
特权模式中,除系统模式外,其他5种模式又称为异常模式。
大多数的用户程序运行在用户模式下,此时,应用程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换。
用户模式下,当需要进行处理器模式切换时,应用程序可以产生异常处理,在异常处理中进行处理器模式的切换。
ARM异常中断处理概述
- 中断的概念
中断是一个过程,是CPU在执行当前程序的过程中因硬件或软件的原因插入了另一段程序运行的过程。因硬件原因引起的中断过程的出现是不可预测的,即随机的,而软中断是事先安排的。
- 中断源的概念
我们把可以引起中断的信号源称之为中断源
- 中断优先级的概念
ARM处理器中有7种类型的异常,按优先级从高到低的排列如下:
- 复位异常(Reset)
- 数据异常(Data Abort)
- 快速中断异常(FIQ)
- 外部中断异常(IRQ)
- 预取异常(Prefetch Abort)
- 软件中断(SWI)
- 未定义指令异常(Undefined instruction)
ARM体系异常种类细说
1.复位异常
当处理器的复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行。 复位异常中断通常用在下面两种情况下。
- 系统上电
- 统复位
当复位异常时,系统执行下列伪操作:
   R14_svc = UNPREDICTABLE value    //任意值
   SPSR_svc = UNPREDICTABLE value  //任意值
   CPSR[4∶0] = 0b10011  /*进入管理模式*/ 
   CPSR[5] = 0    /*处理器进入ARM状态*/ 
   CPSR[6] = 1    /*禁止快速中断*/ 
   CPSR[7] = 1    /*禁止外设中断*/ 
   If high vectors configured then 
     PC = 0xffff0000 
       Else 
     PC = 0x00000000
复位异常中断处理程序的主要功能:
- 设置异常中断向量表。
- 初始化数据栈和寄存器。
- 初始化存储系统,如系统中的MMU等。
- 初始化关键的I/O设备。
- 使能中断。
- 处理器切换到合适的模式。
- 初始化C变量,跳转到应用程序执行。
2.未定义指令异常
当ARM处理器执行协处理器指令时,它必须等待一个外部协处理器应答后,才能真正执行这条指令。若协处理器没有响应,则发生未定义指令异常。
3.软中断SWI
软中断异常发生时,处理器进入特权模式,执行一些特权模式下的操作系统功能。
4.预取指令异常
预取指令异常是由系统存储器报告的。当处理器试图去取一条被标记为预取无效的指令时,发生预取异常。
5.数据访问中止异常
数据访问中止异常是由存储器发出数据中止信号,它由存储器访问指令Load/Store产生。当数据访问指令的目标地址不存在或者该地址不允许当前指令访问时,处理器产生数据访问中止异常。
6.外部中断IRQ
当处理器的外部中断请求引脚有效,而且CPSR寄存器的I控制位被清除时,处理器产生外部中断IRQ异常。系统中各外部设备通常通过该异常中断请求处理器服务。
7.快速中断FIQ
当处理器的快速中断请求引脚有效且CPSR寄存器的F控制位被清除时,处理器产生快速中断请求FIQ异常。
当异常发生时,处理器会把PC设置为一个特定的存储器地址。这一地址放在被称为向量表(vector table)的特定地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序。
进入异常
在异常发生后,ARM内核会作以下工作:
- 在LR中保存下一条指令的地址(即返回地址);
- 将CPSR复制到适当的SPSR中;
- 将CPSR模式位强制设置为与异常类型相对应的值;
- 原来无论是ARM状态或THUMB状态,都进入ARM状态;
- 屏蔽快速中断和外部中断。
- 强制PC从相关的异常向量处取指。
进入异常过程
- 
    程序在系统模式下运行用户程序,假定当前处理器状态为Thumb状态、允许IRQ中断; 
- 
    用户程序运行时发生IRQ中断,硬件完成以下动作: 
- 置位I位(禁止IRQ中断)
- 清零T位(进入ARM状态)
- 设置MOD位,切换处理器模式至IRQ模式
- 将下一条指令的地址存入IRQ模式的LR寄存器
- 将CPSR寄存器内容存入IRQ模式的SPSR寄存器
- 将跳转地址存入PC,实现跳转
中断响应步骤
- 保护断点。
- 寻找中断入口,根据不同的中断源所产生的中断,查找不同的入口地址。
- 执行中断处理程序。
- 中断返回。
具体实现如下:
1.判断处理器状态
当异常发生时,处理器自动切换到ARM状态,所以在异常处理函数中要判断在异常发生前处理器是ARM状态还是Thumb状态。这可以通过检测SPSR的T位来判断。
2.向量表
每一个异常发生时总是从异常向量表开始跳转。最简单的一种情况是向量表里面的每一条指令直接跳向对应的异常处理函数。
异常处理向量表如下:

利用跳转指令B建立异常向量表
  ENTRY			;汇编程序开始 
  b reset		;跳入reset处理程序 
  b HandleUndef		;跳到未定义处理程序          
  b HandSWI		;跳到软中断处理程序       
  b HandPrefetchAbt 	;跳到预取指令处理程序       
  b HandDataAbt		;跳回数据访问中止处理程序      
  b HandNoUsed		;保留 
  b HandleIRQ		;跳入中断处理程序  
  b HandleFIQ		;跳回快速中断处理标签 
注意: 跳转指令B的跳转范围为±32MB,但很多情况下不能保证所有的异常处理函数都定位在向量的32MB范围内,需要更大范围的跳转,而且由于向量表空间的限制,只能由一条指令完成。
具体实现方法有下面两种:
- 
    MOV PC,#imme_value 这种办法将目标地址直接赋值给PC。但这种方法受格式限制不能处理任意立即数。这个立即数由一个8位数值循环右移偶数位得到。 
- 
    LDR PC,[PC+offset] 把目标地址先存储在某一个合适的地址空间,然后把这个存储器单元的32位数据传送给PC来实现跳转。这种方法对目标地址值没有要求。但是存储目标地址的存储器单元必须在当前指令的±4KB空间范围内。 
退出异常
当异常结束时,异常处理程序必须:
- 将LR中的值减去偏移量后存入PC,偏移量根据异常的类型而有所不同;
- 将SPSR的值复制回CPSR;
- 清零在入口置位的中断禁止标志。
注: 恢复CPSR的动作会将T、F和I位自动恢复为异常发生前的值。
在异常处理结束后,异常处理程序完成以下动作:
- 将SPSR寄存器的值复制回CPSR寄存器;
- 将LR寄存的值减去一个常量后复制到PC寄存器,跳转到被中断的用户程序。
从异常处理程序中返回
当一个ARM异常处理返回时,一共有3件事情需要处理
- 通用寄存器的恢复
- 状态寄存器的恢复
- PC指针的恢复
具体操作如下:
1.恢复被中断程序的处理器状态
PC和CPSR的恢复可以通过一条指令来实现,下面是3个例子。
  MOVS  PC,LR 
  SUBS  PC,LR,#4 
  LDMFD  SP!,{PC}^ 
这几条指令是普通的数据处理指令,特殊之处在于它们把程序计数器寄存器PC作为目标寄存器,并且带了特殊的后缀S或^。其中S或^的作用就是使指令在执行时,同时完成从SPSR到CPSR的拷贝,达到恢复状态寄存器的目的。
2.异常的返回地址
注意: 异常返回时,另一个非常重要的问题就是返回地址的确定。前面提到过,处理器进入异常时会有一个保存LR的动作,但是该保持值并不一定是正确中断的返回地址。最简单的方法使用关键字__irq封装,ARM会自动给你处理这些事情。
总结IRQ中断处理过程

测试实例:
/******************************************************************************
 * File:head.S
 * 功能:初始化,设置中断模式、管理模式的栈,设置好中断处理函数
*****************************************************************************/ 
.extern        main
.text
.global        _start
_start:
/****************************************************************************** 
* 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
******************************************************************************/
    b    Reset
; 0x04: 未定义指令中止模式的向量地址
HandleUndef:
    b    HandleUndef    
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
handleSWI:
    b    handleSWI
; 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
    b    HandlePrefetchAbort  
; 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
    b    HandleDataAbort
; 0x14: 保留
HandleNotUsed:
    b    HandleNotUsed  
; 0x18: 中断模式的向量地址
    b    HandleIRQ  
; 0x1C: 快中断模式的向量地址
HandleFIQ:
    b    HandleFIQ
; CPU刚上电或是复位之后,系统处于 arm 状态,管理(SVC)模式。
Reset:
    ldr    sp,        =4096    ;设置栈指针,以下都是C函数,调用前需要设好栈
    bl    disable_watch_dog    ;关闭 WATCHDOG,否则CPU会不断重启
    
    /****************************************************************
     * 在arm汇编中,唯一能更改cpsr的指令就是msr, cpsr_c是CPSR的低8位, 
     * 0xd2 = (110 10010)b,可参见CPSR各bit的定义,
     * 可知CPSR的低5位是工作模式位,如果定义为10010就是进入IRQ(中断模式), 
     * 于是这条指令的意义就是进入中断模式 
     * **************************************************************/
    msr    cpsr_c,    #0xd2  ; 进入 中断(IRQ) 模式
    ldr    sp,        =3072  ; 设置中断模式的栈指针,中断模式是有自己的堆栈寄存器的,就是r13。
    
    msr    cpsr_c,    #0xd3        ; 重新进入 管理(SVC) 模式。
    ldr    sp,        =4096        ; 设置 管理(SVC) 模式下的栈指针
                            ; 其实复位之后,CPU就处于管理模式,
                            ; 前面的“ldr sp, =4096”完成同样的功能,此句可省略
    bl    init_led            ; 初始化 LED 的GPIO管脚 
    bl    init_irq            ; arm中中断使能前的初始化配置
    
    msr    cpsr_c,    #0x5f        ; 进入 系统(sys)模式,使能 IRQ 中断
    
    ldr    lr,        =halt_loop   ; 设置返回地址
    ldr    pc,        =main        ; 调用 main 函数
halt_loop:
    b    halt_loop
    
HandleIRQ:
    sub    lr,    lr,    #4 ;计算返回地址,irq 模式,lr-4,指向发送异常是正在执行的指令
    stmdb        {r0-r12,lr}        ; 保存使用到的寄存器,现场保护
                            ; 注意,此时的sp是中断模式的sp
                            ; 初始值是上面设置的3072
                                    
    ldr    lr, =int_return         ; 设置调用ISR即EINT_Handle函数后的返回地址 
    ldr    pc,    =EINT_Handle     ; 调用中断服务函数,在interrupt.c中
    
int_return:
    ldmia        {r0-r12,pc}^    ; 中断返回,现场恢复, ^表示将spsr的值复制到cpsr
IRQ初始化函数:
void init_irq(void)
{
    // 设置GPIO为中断
    // S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2
    GPFCON &= ~(GPF0_msk | GPF2_msk);
    GPFCON |= GPF0_eint | GPF2_eint;
    // S4对应的引脚设为中断引脚EINT11
    GPGCON &= ~GPG3_msk;
    GPGCON |= GPG3_eint;
    // 设置外部中断属性,子中断属性
    // 对于EINT11,需要在EINTMASK寄存器中使能它
    EINTMASK &= ~(1<<11);
    // 设置优先级
    // 使能中断
    // EINT0、EINT2、EINT8_23使能
    INTMSK &= (~(1<<0)) & (~(1<<2)) & (~(1<<5));
}
中断处理函数:
void EINT_Handle(void)
{
    unsigned long oft = INTOFFSET;    //获取中断偏移,即那个中断被触发
    switch( oft )
    {
        // S2被按下
    case 0: 
        { 
            GPFDAT |= (0x7<<4); // 所有LED熄灭
            GPFDAT &= ~(1<<4); // LED1点亮
            break;
        }
        // S3被按下
    case 2:
        { 
            GPFDAT |= (0x7<<4); // 所有LED熄灭
            GPFDAT &= ~(1<<5); // LED2点亮
            break;
        }
        // K4被按下
    case 5:
        { 
            GPFDAT |= (0x7<<4); // 所有LED熄灭
            GPFDAT &= ~(1<<6); // LED4点亮 
            break;
        }
    default:
        break;
    }
    //清中断
    if( oft == 5 ) 
        EINTPEND = (1<<11); // EINT8_23合用IRQ5,写“1”清零此位 
    SRCPND = 1<<oft;
    INTPND = 1<<oft;
}
