(本文原创,转载注明出处,谢谢)
最近比较忙,博客没有更新。今天特别想起来朋友问我的一个问题ARM7、ARM9支持嵌套中断吗?这个问题当时我不假思索的回答支持。
实际上,这个问题并不像我想象的那么简单,是非常复杂的。在RTOS系统里,如果想支持ARM的嵌套中断,也需要对RTOS针对ARM做特殊的处理。
首先我们来看一个问题,ARM的中断过程。ARM 有两种中断,一个是FIQ;一个是IRQ。FIQ异常中断为快速异常中断,它比IRQ异常中断优先级高。体现在:
1.当FIQ和IRQ异常中断同时产生时,CPU先处理FIQ异常中断;
2.在FIQ异常中断处理程序中,IRQ异常中断被禁止。
同时,与其他的异常模式相比,FIQ异常向量还有额外的5个物理寄存器,在进入FIQ处理程序时,可以不用保存这5个寄存器,从而也提高了FIQ异常中断的执行速度。
当中断来临时,ARM硬件响应IRQ异常中断的伪代码如下所示:
R14_irq = address of next instruction to be executed + 4
SPSR_irq = CPSR
/*进入IRQ异常中断*/
CPSR[4:0]=0b10010
/*切换到ARM状态*/
CPSR[5]=0
/*CPSR[6] is unchanged*/
/*禁止IRQ异常中断*/
CPSR[7] =1
if high vectors configured then
PC = 0xFFFF0018
else
PC = 0x00000018
硬件响应FIQ异常中断的伪代码如下:
R14_fiq = address of next instruction to be executed + 4
SPSR_fiq = CPSR
/*进入FIQ异常中断模式*/
CPSR[4:0]=0b10001
/*切换到ARM状态*/
CPSR[5]=0
/*禁止FIQ异常中断*/
CPSR[6]=1
/*禁止IRQ异常中断*/
CPSR[5]=1
if high vectors configured then
PC=0xFFFF001C
else
PC=0x0000001C
看到这里我们可能已经明白了,ARM在IRQ状态下,是不允许IRQ中断的,允许FIQ中断嵌套;而在FIQ中断下,既不允许IRQ中断,也不允许FIQ中断。这是不是意味着ARM不支持中断嵌套呢?在RTOS中又如何设计呢?
先看看ARM支持不支持嵌套中断。进入IRQ时,CPSR[5]=1;那么也意味着,IRQ产生中断后,就不能再中断了。如果我们手动的将CPSR[5]=0,这样,在IRQ状态下即可允许产生第二个IRQ中断。但第二中断来了,根据上面的伪代码很显然会破坏 R14_irq、SPSR_irq。
杜春雷《ARM体系结构与编程》一文中给出的办法是:
书中也给了现实的代码例子,这个处理过程是通用处理过程。一般的RTOS系统,比如说uC/OS-II和RTEMS,从bootloader接手CPU的控制权后,就停留在SVC模式下执行。用户态和系统态根本就没有用到。FIQ模式也没有用到。只用到:SVC模式、IRQ模式、UND和ABT模式。
我从uC/OS-II的官网上下载了uC/OS-II 2.86 在 AT91SAM9260 上的移植代码。相关代码罗列如下:
;******************************************************************************************************** ; INTERRUPT REQUEST EXCEPTION HANDLER ; ; Register Usage: R0 Exception Type ; R1 ; R2 ; R3 Return PC ;******************************************************************************************************** OS_CPU_ARM_ExceptIrqHndlr SUB LR, LR, #4 ; LR offset to return from this exception: -4. STMFD SP!, {R0-R12, LR} ; Push working registers. MOV R3, LR ; Save link register. MOV R0, #OS_CPU_ARM_EXCEPT_IRQ ; Set exception ID to OS_CPU_ARM_EXCEPT_IRQ. B OS_CPU_ARM_ExceptHndlr ; Branch to global exception handler.
进入中断的向量的代码从384行的,OS_CPU_ARM_ExceptIrqHndlr开始。
;******************************************************************************************************** ; GLOBAL EXCEPTION HANDLER ; ; Register Usage: R0 Exception Type ; R1 Exception's SPSR ; R2 Old CPU mode ; R3 Return PC ;******************************************************************************************************** OS_CPU_ARM_ExceptHndlr MRS R1, SPSR ; Save CPSR (i.e. exception's SPSR). ; DETERMINE IF WE INTERRUPTED A TASK OR ANOTHER LOWER PRIORITY EXCEPTION: ; SPSR.Mode = SVC : task, ; SPSR.Mode = FIQ, IRQ, ABT, UND : other exceptions, ; SPSR.Mode = USR : *unsupported state*. AND R2, R1, #OS_CPU_ARM_MODE_MASK CMP R2, #OS_CPU_ARM_MODE_SVC BNE OS_CPU_ARM_ExceptHndlr_BreakExcept ;******************************************************************************************************** ; EXCEPTION HANDLER: TASK INTERRUPTED ; ; Register Usage: R0 Exception Type ; R1 Exception's SPSR ; R2 Exception's CPSR ; R3 Return PC ; R4 Exception's SP ;******************************************************************************************************** OS_CPU_ARM_ExceptHndlr_BreakTask MRS R2, CPSR ; Save exception's CPSR. MOV R4, SP ; Save exception's stack pointer. ; Change to SVC mode & disable interruptions. MSR CPSR_c, #(OS_CPU_ARM_CONTROL_INT_DIS | OS_CPU_ARM_MODE_SVC) ; SAVE TASK'S CONTEXT ONTO TASK'S STACK: STMFD SP!, {R3} ; Push task's PC, STMFD SP!, {LR} ; Push task's LR, STMFD SP!, {R5-R12} ; Push task's R12-R5, LDMFD R4!, {R5-R9} ; Move task's R4-R0 from exception stack to task's stack. STMFD SP!, {R5-R9} STMFD SP!, {R1} ; Push task's CPSR (i.e. exception SPSR). ; if (OSRunning == 1) LDR R1, ?OS_Running LDRB R1, [R1] CMP R1, #1 BNE OS_CPU_ARM_ExceptHndlr_BreakTask_1 ; HANDLE NESTING COUNTER: LDR R3, ?OS_IntNesting ; OSIntNesting++; LDRB R4, [R3] ADD R4, R4, #1 STRB R4, [R3] LDR R3, ?OS_TCBCur ; OSTCBCur->OSTCBStkPtr = SP; LDR R4, [R3] STR SP, [R4] OS_CPU_ARM_ExceptHndlr_BreakTask_1 MSR CPSR_cxsf, R2 ; RESTORE INTERRUPTED MODE. ; EXECUTE EXCEPTION HANDLER: LDR R1, ?OS_CPU_ExceptHndlr ; OS_CPU_ExceptHndlr(except_type = R0); MOV LR, PC BX R1 ; Adjust exception stack pointer. This is needed because ; exception stack is not used when restoring task context. ADD SP, SP, #(14 * 4) ; Change to SVC mode & disable interruptions. MSR CPSR_c, #(OS_CPU_ARM_CONTROL_INT_DIS | OS_CPU_ARM_MODE_SVC) ; Call OSIntExit(). This call MAY never return if a ready ; task with higher priority than the interrupted one is ; found. LDR R0, ?OS_IntExit MOV LR, PC BX R0 ; RESTORE NEW TASK'S CONTEXT: LDMFD SP!, {R0} ; Pop new task's CPSR, MSR SPSR_cxsf, R0 LDMFD SP!, {R0-R12, LR, PC}^ ; Pop new task's context. ;******************************************************************************************************** ; EXCEPTION HANDLER: EXCEPTION INTERRUPTED ; ; Register Usage: R0 Exception Type ; R1 ; R2 ; R3 ;******************************************************************************************************** OS_CPU_ARM_ExceptHndlr_BreakExcept MRS R2, CPSR ; Save exception's CPSR. ; Change to SVC mode & disable interruptions. MSR CPSR_c, #(OS_CPU_ARM_CONTROL_INT_DIS | OS_CPU_ARM_MODE_SVC) ; HANDLE NESTING COUNTER: LDR R3, ?OS_IntNesting ; OSIntNesting++; LDRB R4, [R3] ADD R4, R4, #1 STRB R4, [R3] MSR CPSR_cxsf, R2 ; RESTORE INTERRUPTED MODE. ; EXECUTE EXCEPTION HANDLER: LDR R3, ?OS_CPU_ExceptHndlr ; OS_CPU_ExceptHndlr(except_type = R0); MOV LR, PC BX R3 ; Change to SVC mode & disable interruptions. MSR CPSR_c, #(OS_CPU_ARM_CONTROL_INT_DIS | OS_CPU_ARM_MODE_SVC) ; HANDLE NESTING COUNTER: LDR R3, ?OS_IntNesting ; OSIntNesting--; LDRB R4, [R3] SUB R4, R4, #1 STRB R4, [R3] MSR CPSR_cxsf, R2 ; RESTORE INTERRUPTED MODE. ; RESTORE OLD CONTEXT: LDMFD SP!, {R0-R12, PC}^ ; Pull working registers and return from exception.
将相关寄存器保护后进入OS_CPU_ARM_ExceptHndlr处执行,分为两种情况,一种是从SVC模式中断;另外是从其它模式中断而来。我们从上面的IRQ进入的伪代码可知,在不支持嵌套的情况下,必然是从SVC模式而来。所以,必然从标号OS_CPU_ARM_ExceptHndlr_BreakTask 处执行。贴出的两段代码英文注释写得非常清楚了,在这里不做细细的介绍了。我只大致说一下这段代码的问题:
我们上面贴出的ARM进入IRQ模式的伪代码是硬件过程,硬件自动完成的。这段代码并未考虑这一点,441行和445行还禁止了IRQ中断。471和472行又恢复了中断。 这是多余的一个工作。 中断本身就是禁止的,恢复后依然是禁止的。从整段代码来看,这个代码是支持嵌套中断的代码,但却没有考虑到ARM中断的硬件动作。致使嵌套中断的代码变成了不支持嵌套中断的代码。
仔细看一下这段代码,uC/OS-II系统中断栈的处理,基本上是中断栈使用中断硬件独立的栈,而任务使用任务的栈。这样可以避免任务栈的浪费,有些CPU不支持中断的独立栈,只能是浪费被中断了的任务的栈。uC/OS-II这个移植做得还不错。充分考虑了栈这里的特点,使用了ARM的硬件栈,避免了任务栈的浪费。
如果将uC/OS-II的代码改为支持嵌套中断的代码也非常简单,在第461和462行插入伪代码
R2 &= ~(OS_CPU_ARM_CONTROL_IRQ_DIS);
在第499和500行也插入同样的伪代码。这样,就将屏蔽的中断位打开了。即可允许嵌套了。RTEMS的移植包中,其实也有同样的问题,但我还没有细细的阅读相关的代码,回头读完一并将代码分析贴出来。
写得不好,希望大家宽宥,如有错误,希望多多指教。
(本文原创,转载注明出处,谢谢)