分类: LINUX
2009-09-22 19:50:24
15:08 2009-9-22
今天分析了一把4020的IRQ进入和退出过程,发现不少东东,按照常理进IRQ的过程是硬件完成的,而出中断的过程则需要根据具体过程进行加减步骤了。
正常的中断进入过程是:
(1)拷贝CPSR到SPSR_
(2)设置正确的CPSR位,arm自动硬件完成下面三项工作:切换到ARM状态,切换到异常模式,禁止中断。
(3)保存返回地址在LR_
(4)设置PC到异常向量地址
以上这四个操作是硬件完成的,另外我发现当模式换到IRQ模式下,此时的堆栈指针sp已经在中断栈指针(提前指定好的)上加8了,为啥呢??
而我们在真正操作中还需要将其他寄存器入栈,这个过程是我们自己写的。
(5)将r0-r12压栈
这样实现的是不能中断嵌套的过程,如果想实现中断嵌套,还需要将r14,spsr,也压栈。
出中断时,我们自己需要做以下操作:
(1)r0-r12出栈
(2)从SPSR_
(3)从LR_
一般(2)(3)过程是一下完成的,用 LDMFD sp!,{pc} ^ 即可完成,用它代码实现是比较麻烦的。
原来在SEP4020的中断处理代码如下,可以看出是相当麻烦的。
IRQ_DO
stmfd sp!, {r0,r1}
ldr r0, =IRQ_R1
str r1, [r0]
ldmfd sp!, {r0}
ldr r1, =IRQ_R0
str r0, [r1] ;保存R0和R1寄存器(因为这两个寄存器再后面要用到)
add r13, r13, #4 ;恢复中断栈针
sub r14, r14, #4 ;使r14指向中断返回的下条语句
mov r0, r14 ;LR_irq(R14)减4并保存在R0
mrs r1, spsr
orr r1, r1, #0x80 ;将SPSR_irq的中断屏蔽位置‘1’(屏蔽中断),并保存再R1 中
msr cpsr_cxsf, r1 ;将模式切换到中断前的模式,即这个时候就又回到了SVC模式了,但此时是不允许中断的。
;---------------现在又进入SVC模式了------------------------------------------------------------------------------
bic r1, r1, #0x80 ;将原先保存的SPSR_irq的R1的中断屏蔽位清零(允许中断)
stmfd sp!, {r0}
stmfd sp!, {r14}
stmfd sp!, {r1} ;依次将R0,R14,R1的值压入中断前模式下的堆栈(当前R0,R14,R1中存放的分别是LR_irq-4,中断前模式下的LR,SPSR_irq)
ldr r0, =IRQ_R1
ldr r1, [r0]
stmfd sp!, {r1}
ldr r1, =IRQ_R0
ldr r0, [r1]
stmfd sp!, {r0}
ldmfd sp!, {r0,r1} ;恢复原先保存的R0和R1
stmfd sp!, {r0-r12} ;将r0--r12全部压入中断以前模式即SVC模式下的堆栈
;-----------------------------
IMPORT int_vector_handler
bl int_vector_handler ;跳转到中断源判断和中断处理程序
;-----------------中断返回------------ ;restore the register
ldmfd sp!, {r0-r12} ;恢复原先保存的R0-R12
ldmfd sp!, {r14}
msr cpsr_cxsf, r14 ;恢复SVC下的cpsr,并且允许中断
ldmfd sp!, {r14} ;将原先保存的SPSR_irq恢复到CPSR中
ldmfd sp!, {pc}
我后来自己改的一个流程,这个是中断允许嵌套的做法
IRQ_DO
stmfd sp!, {r0,r1}
ldr r0, =IRQ_R1 ;加载 常数IRQ_R1的地址到r0
str r1, [r0]
ldmfd sp!, {r0}
ldr r1, =IRQ_R0
str r0, [r1] ;保存R0和R1寄存器(因为这两个寄存器再后面要用到)
;add r13, r13, #4 ;restore the sp_irq top to original irq top
sub r14, r14, #4
mov r0, r14 ;LR_irq(R14)减4并保存在R0
mrs r1, spsr ;保存SVC模式下的cpsr到r1中,准备压栈保存,以便返回时用
stmfd sp!, {r0} ;lr-4
stmfd sp!, {r14} ;r14
stmfd sp!, {r1} ;spsr_irq
ldr r0, =IRQ_R1
ldr r1, [r0]
stmfd sp!, {r1}
ldr r1, =IRQ_R0
ldr r0, [r1]
stmfd sp!, {r0}
ldmfd sp!, {r0,r1} ;恢复原先保存的R0和R1
stmfd sp!, {r0-r12} ;将r0--r12全部压入中断以前模式下的堆栈
;mrs r5,cpsr
;bic r5,r5,#0x80
;msr cpsr_cxsf,r5 ;使中断能够嵌套,但不能在此用,因为中断源还没别清,这是开中断又会进入中断了
;-----------------------------
IMPORT int_vector_handler
bl int_vector_handler ;跳转到中断源判断和中断处理程序
;----------------------------- ;restore the register
ldmfd sp!, {r0-r12} ;恢复原先保存的R0-R12
ldmfd sp!, {r14}
msr spsr_cxsf, r14
ldmfd sp!, {r14} ;将原先保存的SPSR_irq恢复到CPSR中
ldmfd sp!, {pc}^
EXPORT ENABLE_IRQ
;----------下面是函数栈,可以对比下中断栈于函数栈的区别-----------------------------
ENABLE_IRQ
stmfd sp!,{r5,r14} ;函数进入时,函数压栈
mrs r5,cpsr
bic r5,r5,#0x80
msr cpsr_cxsf,r5 ;使中断能够嵌套,
ldmfd sp!,{r5,pc} ;函数返回
;可以发现中断栈与函数栈的区别在于,中断栈要保存cpsr,而函数栈不用管cpsr了,因此返回时也不用切换模式了。
在中断函数中将irq打开即可再次引起中断,用ENABLE_IRQ即可,而由于这时候上次的中断现场都已经保存所以再次进中断是可以的,但前提是上次中断已经清除了。