分类: BSD
2012-12-25 15:21:04
ARM中断发生时,ARM核会做下面一些工作:
1、下一条将要执行的指令地址+4赋值给 r14(lr)
2、CPSR复制到SPSR
3、CPSR的模式更改为IRQ模式
4、如果原来是thumb状态,则CPSR更改为ARM状态
5、CPSR的bit7设置为0,即关闭IRQ
6、PC指针跳转到IRQ的中断向量地址(大多数情况为0x00000018)
因此如果从中断服务中返回,只要将lr减4复制给PC,同时恢复SPSR到CPSR即可。这样被中断的代码可以继续运行下去。
另外在大多数的ARM实现中,线程运行的模式大都为SVC模式,下文以此为假设。
好了,这样就从 hal/arm/arch/current/src/vectors.S 开始IRQ之旅。
784 IRQ:
785 // Note: I use this exception stack while saving the context because
786 // the current SP does not seem to be always valid in this CPU mode.
中断发生时,首先需要保存现场,所谓保存现场,就是将一大堆寄存器保存在一个地方,当中断返回时,重新恢复这些寄存器,以恢复原来的CPU运行状态。
eCos将现场定义为一个数据结构,并将该数据结构保存在中断发生时的上下文的堆栈。比如说中断发生在thread的上下文,则该数据结构保存在thread的堆栈里。
在“hal/arm/arch/current/include/hal_arch.h” 定义了该数据结构。
103 typedef struct
104 {
105 // These are common to all saved states
106 cyg_uint32 d[HAL_NUM_THREAD_CONTEXT_REGS] ; // Data regs (r0..r10)
107 cyg_uint32 fp; // (r11) Frame. pointer
108 cyg_uint32 ip; // (r12)
109 cyg_uint32 sp; // (r13) Stack pointer
110 cyg_uint32 lr; // (r14) Link Reg
111 cyg_uint32 pc; // (r15) PC place holder
112 // (never used)
113 cyg_uint32 cpsr; // Condition Reg
114 // These are only saved for exceptions and interrupts
115 cyg_uint32 vector; // Vector number
116 cyg_uint32 svc_lr; // saved svc mode lr
117 cyg_uint32 svc_sp; // saved svc mode sp
118
119 } HAL_SavedRegisters;
后面的汇编马上开始保存该数据结构的寄存器。
787 ldr sp,.__exception_stack // get good stack
788 stmfd sp!,{r0-r5} // save some supervisor regs
代码已经进入IRQ模式,这里需要注意不同模式下的堆栈sp寄存器不是同一个寄存器。因此787行的sp并不是被中断的thread上下文的sp,而是中断模式的堆栈寄存器sp_irq 。因为后面的代码需要使用r0-r5,因此需要把被中断的上下文的r0-r5临时保存在堆栈中,堆栈为exception的堆栈:__exception_stack
789 sub r0,lr,#4 // PC at time of interrupt
790 mrs r1,spsr
791 mov r2,#CYGNUM_HAL_VECTOR_IRQ
792 mov r3,sp
793
r0为中断发生时thread的PC位置,对应数据结构中的pc (为什么减4在文章最开始已经说明)
r1为中断发生时thread的CPSR,对应数据结构中的cpsr
r2为中断向量,对应数据结构中的vector
r3为临时保存下sp _irq,后面要用
794 mrs r4,cpsr // switch to Supervisor Mode
795 bic r4,r4,#CPSR_MODE_BITS
796 // When handling an IRQ we must disable FIQ unless the current
797 // mode in CPSR is IRQ. If we were to get a FIQ while in another
798 // mode, the FIQ handling code would transform. the FIQ into an
799 // IRQ and call the non-reentrant IRQ handler again. As a result,
800 // for example, the stack pointer would be set to the beginning
801 // of the exception_stack clobbering the registers we have just
802 // saved.
803 orr r4,r4,#CPSR_SUPERVISOR_MODE|CPSR_FIQ_DISABLE
804 msr cpsr,r4
805
把当前的IRQ模式切换到SVC模式下,并关FIQ中断(IRQ在中断发生时,cpsr的IRQ位已经被关掉) 。当前已经不是IRQ模式,如果用户thread的模式为SVC模式,那么当前的模式已经恢复到thread的模式下。
806 mov r5,sp // save original svc sp
807 mov r4,lr // save original svc lr
808 stmfd sp!,{r0-r2,r4,r5} // push svc_sp, svc_lr, vector, psr, pc
809
r5为sp_svc,对应数据结构中的svc_sp
r4为lr_svc,对应数据结构中的svc_lr
将svc_sp, svc_lr, vector, cpsr, pc压栈
这里注意:因为已经是SVC模式,因此这里的堆栈sp已经是线程的堆栈了。
810 #ifdef CYGOPT_HAL_ARM_WITH_USER_MODE
811 // did exception occur in user mode ?
812 and r2, r1, #CPSR_MODE_BITS
813 cmp r2, #CPSR_USER_MODE
814 bne 1f
815 stmfd sp, {r8-r12, sp, lr}^ // get user mode regs
816 nop
817 sub sp, sp, #4*7
818 bal 2f
819 1:
820 #endif
这里对于用户线程为user模式的处理,一般我们都采用svc模式,这段代码暂不关心
821 // switch to pre-exception mode to get banked regs
822 mov r0,sp // r0 survives mode switch
823 mrs r2,cpsr // Save current psr for return
824 orr r1,r1,#CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE
825 bic r1,r1,#CPSR_THUMB_ENABLE
826 msr cpsr,r1
827 stmfd r0!,{r8-r12,sp,lr}
828 msr cpsr,r2 // back to svc mode
829 mov sp,r0 // update stack pointer
830
恢复到中断发生前的模式,并将当前模式设置为IRQ/FIQ关闭,使能thumb模式,以获取寄存器的值 。继续将lr,sp,r12,r11,r10,r9,r8 压栈。
831 2:
832 // now save pre-exception r0-r7 on current stack
833 ldmfd r3,{r0-r5}
834 stmfd sp!,{r0-r7}
835
从__exception_stack中获取保存的r0-r5,连同r6,r7压入堆栈。
836 // sp needs fixing if exception occured in SVC mode.
837 ldr r1,[sp,#armreg_cpsr]
838 and r1,r1,#CPSR_MODE_BITS
839 cmp r1,#CPSR_SUPERVISOR_MODE
840 ldreq r1,[sp,#armreg_svcsp]
841 streq r1,[sp,#armreg_sp]
842
取出数据结构HAL_SavedRegisters中的cpsr,判断是否为svc模式,如果是则说明被中断的上下文为SVC模式,因此将svc模式的sp_svc复制 到sp。我们如果在这里恢复现场,那么把r0-r14出栈,则sp的值正好是svc_sp,也就是中断发生时的sp的值。
注意,上面我们没有分析用户线程运行在USER模式的代码,因此对于SVC模式来说,数据结构HAL_SavedRegisters中的lr,sp,lr_svc,sp_svc是重复的。
至此,数据结构 HAL_SavedRegisters已保存完整,即现场保存工作已经完成。
下面要进行中断ISR的调用。
843 mov v6,sp // Save pointer to register frame.
844
845 // mov r0,sp
846 // bl _show_frame_in
847
把sp保存在v6寄存器中(v1为r4, v6为r9) ,这里的v6就是数据结构 HAL_SavedRegisters的指针
848 #ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
849 // Switch to interrupt stack
850 ldr r2,.irq_level // current number of nested interrupts
851 ldr r0,[r2]
852 add r1,r0,#1
853 str r1,[r2] // if was zero, switch stacks
宏CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK表明为中断处理函数使用中断堆栈, 而不是线程堆栈。通常都会使用这个宏,最大的好处在于不用为每个线程的堆栈预留出中断的堆栈开销。
eCos支持中断嵌套,irq_level为全局变量,初始值为0,表明中断的嵌套层数。这里加1。
854 cmp r0,#0
855 moveq r1,sp // save old stack pointer
856 ldreq sp,.__interrupt_stack
857 stmeqfd sp!,{r1}
858 10:
859 #endif
860
如果irq_level在加1前的值为0(即表明中断未嵌套),把sp设置为__interrupt_stack,并把旧的sp值压栈保存,从当前开始,堆栈从线程堆栈切换到中断堆栈。如果中断嵌套,则表明当前sp已经是中断堆栈了。
861 // The entire CPU state is now stashed on the stack,
862 // increment the scheduler lock and handle the interrupt
863
864 #ifdef CYGFUN_HAL_COMMON_KERNEL_SUPPORT
865 .extern cyg_scheduler_sched_lock
866 ldr r3,.cyg_scheduler_sched_lock
867 ldr r4,[r3]
868 add r4,r4,#1
869 str r4,[r3]
870 #endif
871
cyg_scheduler_sched_lock即是类Cyg_Scheduler_SchedLock的成员sched_lock,表示进程调度锁的计数
kernel/current/include/smp.hxx
257 class Cyg_Scheduler_SchedLock
258 {
259 static volatile cyg_ucount32 sched_lock // lock counter
260 CYGBLD_ATTRIB_ASM_ALIAS( cyg_scheduler_sched_lock )
261 CYGBLD_ANNOTATE_VARIABLE_SCHED
262 ;
..........
..........
..........
306 };
这里将cyg_scheduler_sched_lock加1,即表明需要锁住调度
872 THUMB_MODE(r3,10)
873
THUMB_MODE宏在非thumb模式下为空,暂不关心
874 mov r0,v6
875 bl hal_IRQ_handler // determine interrupt source
876 mov v1,r0 // returned vector #
877
调用hal_IRQ_handler(),获取发生的中断号,返回值存放在v1
hal/arm/br104h/current/src/br104h_misc.c
241 //
242 // This routine is called to respond to a hardware interrupt (IRQ). It
243 // should interrogate the hardware and return the IRQ vector number.
244
245 int hal_IRQ_handler(void)
246 {
247 // Do hardware-level IRQ handling
248 int irq_status, vector;
249
250 HAL_READ_UINT32(CYG_DEVICE_IRQ_Status, irq_status);
251 //diag_init(); diag_printf("%s, status: %x\n", __PRETTY_FUNCTION__, irq_status);
252
253 for (vector = 0; vector <= 16; vector++) {
254 if (irq_status & (1<
255 }
256 return CYGNUM_HAL_INTERRUPT_NONE; // This shouldn't happen!
257 }
hal_IRQ_handler()和具体的芯片实现相关的,这里我把br104h的的实现列出来。
878 #if defined(CYGPKG_KERNEL_INSTRUMENT) && \
879 defined(CYGDBG_KERNEL_INSTRUMENT_INTR)
880 ldr r0,=RAISE_INTR // arg0 = type = INTR,RAISE
881 mov r1,v1 // arg1 = vector
882 mov r2,#0 // arg2 = 0
883 bl cyg_instrument // call instrument function
884 #endif
INSTRUMENT宏用于内核调试,暂不关心
886 ARM_MODE(r0,10)
ARM_MODE宏在非thumb模式下为空
888 mov r0,v1 // vector #
r0保存中断向量
889
890 #if defined(CYGDBG_HAL_DEBUG_GDB_CTRLC_SUPPORT) \
891 || defined(CYGDBG_HAL_DEBUG_GDB_BREAK_SUPPORT)
892 // If we are supporting Ctrl-C interrupts from GDB, we must squirrel
893 // away a pointer to the save interrupt state here so that we can
894 // plant a breakpoint at some later time.
895
896 .extern hal_saved_interrupt_state
897 ldr r2,=hal_saved_interrupt_state
898 str v6,[r2]
899 #endif
GDB STUB对ctrl-c的处理,暂不关心
901 cmp r0,#CYGNUM_HAL_INTERRUPT_NONE // spurious interrupt
902 bne 10f
正常流程跳转到后面的标号10。
904 #ifdef CYGIMP_HAL_COMMON_INTERRUPTS_IGNORE_SPURIOUS
905 // Acknowledge the interrupt
906 THUMB_CALL(r1,12,hal_interrupt_acknowledge)
907 #else
908 mov r0,v6 // register frame.
909 THUMB_CALL(r1,12,hal_spurious_IRQ)
910 #endif // CYGIMP_HAL_COMMON_INTERRUPTS_IGNORE_SPURIOUS
911 b spurious_IRQ
异常的情况为:有中断,但无法获取中断号,则处理处理spurious_IRQ,这种异常情况一般不会发生,这里暂不关心
后面的代码需要处理ISR,hal_interrupt_data和hal_interrupt_handlers定义在hal/arm/arch/current/src/vectors.S 如下所示:
1144 .globl hal_interrupt_handlers
1145 hal_interrupt_handlers:
1146 .rept CYGNUM_HAL_ISR_COUNT
1147 .long hal_default_isr
1148 .endr
1149
1150 .globl hal_interrupt_data
1151 hal_interrupt_data:
1152 .rept CYGNUM_HAL_ISR_COUNT
1153 .long 0
1154 .endr
1155
1156 .globl hal_interrupt_objects
1157 hal_interrupt_objects:
1158 .rept CYGNUM_HAL_ISR_COUNT
1159 .long 0
1160 .endr
缺省的isr为hal_default_isr,缺省的data和objects为0。这是没有安装中断的数值,如果已经安装中断,即调用宏HAL_INTERRUPT_ATTACH的宏安装,这isr, data, object会被修改成需要处理的中断变量和函数。
913 10: ldr r1,.hal_interrupt_data
914 ldr r1,[r1,v1,lsl #2] // handler data
915 ldr r2,.hal_interrupt_handlers
916 ldr v3,[r2,v1,lsl #2] // handler (indexed by vector #)
917 mov r2,v6 // register frame. (this is necessary
918 // for the ISR too, for ^C detection)
需要调用的isr的函数原型为 xxxx_isr(cyg_vector_t vector, cyg_addrword_t data,
HAL_SavedRegisters *regs), 因此需要计算出r0/r1/r2三个参数:
r0和v1为vecotor,这里计算出r1为对应的data,v3为isr的handler, r2为寄存器frame的地址,即HAL_SavedRegisters的指针
920 #ifdef __thumb__
921 ldr lr,=10f
922 bx v3 // invoke handler (thumb mode)
923 .pool
924 .code 16
925 .thumb_func
926 IRQ_10T:
927 10: ldr r2,=15f
928 bx r2 // switch back to ARM mode
929 .pool
930 .code 32
931 15:
932 IRQ_15A:
这里对于thumb模式处理,暂不关心
933 #else
934 mov lr,pc // invoke handler (call indirect
因为ARM的指令为流水线处理,因此这是pc已经是加8,因此lr的值为spurious_IRQ。
因此ISR处理完毕后,返回处理spurious_IRQ
935 mov pc,v3 // thru v3)
936 #endif
调用isr函数。
938 spurious_IRQ:
939
940 #ifdef CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
941 // If we are returning from the last nested interrupt, move back
942 // to the thread stack. interrupt_end() must be called on the
943 // thread stack since it potentially causes a context switch.
944 ldr r2,.irq_level
945 ldr r3,[r2]
946 subs r1,r3,#1
947 str r1,[r2]
948 ldreq sp,[sp] // This should be the saved stack pointer
949 #endif
这里将irq_level减1,如果减1后变为0(说明没有中断嵌套),则恢复线程上下文的sp(即寄存器frame指针)。因此可以看到,处理ISR的堆栈是中断的堆栈(如果CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK宏设能的话),而后面处理的DSR的堆栈是线程的堆栈。
至此,ISR调用完毕,后面开始处理DSR的工作。
950 // The return value from the handler (in r0) will indicate whether a
951 // DSR is to be posted. Pass this together with a pointer to the
952 // interrupt object we have just used to the interrupt tidy up routine.
953
954 // don't run this for spurious interrupts!
955 cmp v1,#CYGNUM_HAL_INTERRUPT_NONE
956 beq 17f
如果vector等于-1(中断异常流程:有中断,但无法获取中断号)转到后面的标号17
957 ldr r1,.hal_interrupt_objects
958 ldr r1,[r1,v1,lsl #2]
959 mov r2,v6 // register frame.
960
961 THUMB_MODE(r3,10)
962
963 bl interrupt_end // post any bottom layer handler
964 // threads and call scheduler
965 ARM_MODE(r1,10)
interrupt_end()的函数原型为:interrupt_end(cyg_uint32 isr_ret,Cyg_Interrupt *intr,HAL_SavedRegisters *regs)
取中断的object,这里r0=vector, r1=object, r2=HAL_SavedRegisters指针
调用interrupt_end()函数,处理post_dsr, dsr处理, 线程切换等,这个函数比较复杂,我们放到以后分析
966 17:
967
968 // mov r0,sp
969 // bl show_frame_out
970
971 // return from IRQ is same as return from exception
972 b return_from_exception
处理中断返回,恢复现场
670 return_from_exception:
671
672 ldr r0,[sp,#armreg_cpsr]
673
674 // return to supervisor mode is simple
675 and r1,r0,#CPSR_MODE_BITS
676 cmp r1,#CPSR_SUPERVISOR_MODE
677
678 #ifndef CYGOPT_HAL_ARM_PRESERVE_SVC_SPSR
CYGOPT_HAL_ARM_PRESERVE_SVC_SPSR宏在cdl的定义为
cdl_option CYGOPT_HAL_ARM_PRESERVE_SVC_SPSR {
display "Preserve svc spsr before returning to svc mode"
default_value 0
description "
This option secures exception and breakpoint processing
triggered during execution of application specific SWI
handlers."
}
看的不是很确切,反正缺省为0,暂不关心
679 msr spsr,r0
680 ldmeqfd sp,{r0-r14,pc}^
线程为SVC模式非常简单:取出保存的cpsr值给r0,如果cpsr为SVC模式,则将r0赋值给spsr。
680行这句代码非常关键,由于有“^ ”这个很特别的符号,汇编指令ldmeqfd将所有寄存器恢复,同时将spsr拷贝到cpsr。至此中断现场恢复,被中断的线程从中断的位置继续运行。
681 #else
.........
.......
剩余代码为非SVC模式,暂不关心