1、中断优先级
1.1、中断优先级表
NucleusPlus中断优先级类似一个数组,数组的索引代表优先级,数组元素的值代表该优先级对应的中断号,0的优先级最高,优先级汇编语言表如下(仅是按中断号排序,移动中断号的位置即可调整中断的优先级):
INT_IRQ_Priority:
.word IRQ_EINT0
.word IRQ_EINT1
.word IRQ_EINT2
.word IRQ_EINT3
.word IRQ_EINT4_7
.word IRQ_EINT8_23
.word IRQ_INT_CAM
.word IRQ_nBATT_FLT
.word IRQ_INT_TICK
.word IRQ_INT_WDT_AC97
.word IRQ_INT_TIMER0
.word IRQ_INT_TIMER1
.word IRQ_INT_TIMER2
.word IRQ_INT_TIMER3
.word IRQ_INT_TIMER4
.word IRQ_INT_UART2
.word IRQ_INT_LCD
IRQ_PRIORITY_END:
1.2、中断向量表
中断向量表按中断号排序,依次存放中断处理函数的入口地址,汇编语言定义如下(s3c2440):
INT_IRQ_Vectors:
.long INT_Interrupt_Shell // Vector 0
.long INT_Interrupt_Shell // Vector 1
.long INT_Interrupt_Shell // Vector 2
.long INT_Interrupt_Shell // Vector 3
.long INT_Interrupt_Shell // Vector 4
.long INT_Interrupt_Shell // Vector 5
.long INT_Interrupt_Shell // Vector 6
.long INT_Interrupt_Shell // Vector 7
.long INT_Interrupt_Shell // Vector 8
.long INT_Interrupt_Shell // Vector 9
.long INT_Interrupt_Shell // Vector 10
.long INT_Interrupt_Shell // Vector 11
.long INT_Interrupt_Shell // Vector 12
.long INT_Interrupt_Shell // Vector 13
.long INT_Timer_Interrupt // Vector 14
.long INT_Interrupt_Shell // Vector 15
.long INT_Interrupt_Shell // Vector 16
.long INT_Interrupt_Shell // Vector 17
.long INT_Interrupt_Shell // Vector 18
.long INT_Interrupt_Shell // Vector 19
.long INT_Interrupt_Shell // Vector 20
.long INT_Interrupt_Shell // Vector 21
.long INT_Interrupt_Shell // Vector 22
.long INT_Interrupt_Shell // Vector 23
.long INT_Interrupt_Shell // Vector 24
.long INT_Interrupt_Shell // Vector 25
.long INT_Interrupt_Shell // Vector 26
.long INT_Interrupt_Shell // Vector 27
.long INT_Interrupt_Shell // Vector 28
.long INT_Interrupt_Shell // Vector 29
.long INT_Interrupt_Shell // Vector 30
.long INT_Interrupt_Shell // Vector 31
.long INT_Interrupt_Shell // Vector 32
在此代码中仅用了INT_Timer_Interrupt用来实现操作系统调度定时器,其他中断默认调用INT_Interrupt_Shell。
2、中断入口查找
2.1、中断向量
硬件中断向量如下,IRQ处理函数为INT_Interrupt。
.global _start
_start:
ldr pc, ResetAddr
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq // IRQ中断向量地址
ldr pc, _fiq
ResetAddr:
.word reset
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word INT_Interrupt // IRQ中断处理函数
_fiq:
.word fiq
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
2.2、查找中断处理函数
.globl INT_Interrupt
INT_Interrupt:
/* This Code is used to correctly handle interrupts and
is necessary due to the nature of the ARM7 architecture */
STMDB sp!, {r0-r4}
SUB lr,lr,#4
LDR r3, =0x4a000010 // INTPND(Interrupt request status)
LDR r2, [r3, #0]
LDR r3, =INT_IRQ_Priority
IRQ_VECTOR_LOOP:
LDR r0, [r3,#0] // Load first vector to be checked from priority table
MOV r1, #1 // Build mask
MOV r1, r1, LSL r0 // Use vector number to set mask to correct bit position
TST r1, r2 // Test if pending bit is set
BNE IRQ_VECTOR_FOUND // If bit is set, branch to found section...
ADD r3, r3, #4 // Move to next word in the priority table
LDR r0, =IRQ_PRIORITY_END // Load the end address for the priority table
CMP r0, r3 // Make sure not at the end of the table (shouldn't happen!)
BNE IRQ_VECTOR_LOOP // Continue to loop if not at the end of the table
// No bits in pending register set, restore context and exit interrupt servicing
ADD sp,sp,#4 // Adjust sp above IRQ enable value
LDMIA sp!,{r0-r4} // Restore r0-r4
STMDB sp!,{lr}
LDMIA sp!,{pc}^
MOV pc,lr // return to the point of the exception
IRQ_VECTOR_FOUND:
LDR r3,=INT_IRQ_Vectors // Get IRQ vector table address
MOV r2, r0, LSL #2 // Multiply vector by 4 to get offset into table
ADD r3, r3, r2 // Adjust vector table address to correct offset
LDR r2, [r3,#0] // Load branch address from vector table
MOV PC, r2 // Jump to correct branch location based on vector table
/* END: INT_Interrupt */
第5行 保存通用寄存器r0-r4到中断栈里面;
第7行 修正中断返回地址;
第9~10行 读取s3c2440中断挂起寄存器的值(正等待处理的中断,每bit代表一个中断,bit0代表中断号0,bit1代表中断号1),r2 = INTPND;
第11行 获取中断优先级表INT_IRQ_Priority的地址,r3 = &INT_IRQ_Priority;
第14行 获取r3指向的优先级的中断号(r3用于从前往后遍历中断优先级表),r0 = 当前优先级的中断号;
第15~16行 计算当前优先级中断号对应的INTPND bit位,例如中断3发生了中断请求,那么INTPND[3]应为1(1<<3);
第18行 判断当前优先级中断对应的INTPND bit位是否设置(是否有中断请求),r1 & r2并且设置标记位;
第19行 根据前一结果进行跳转,r1 & r2结果为0,表示中断对应的bit位为0(没有中断请求),否则为1(有中断请求),跳转到IRQ_VECTOR_FOUND处理中断;
第21~24行 r3指针移动一个优先级,并判断r3是否已经移动到中断优先级表的末尾,没有的话,跳转到IRQ_VECTOR_LOOP接着判断下一优先级的中断是否产生;
第27~30行 恢复中断,"STMDB sp!,{lr}"主要是用于下一条指令"LDMIA sp!,{pc}^","LDMIA sp!,{pc}^"带有pc寄存器,执行时将恢复spsr到cpsr;
第34~37行 获取中断号r0对应的中断函数地址,注释比较详细就不介绍每一行代码。
第39行 跳转到中断号对应的中断处理函数,即调用中断处理函数。
3、中断处理
3.1、定时器中断处理函数(INT_Timer_Interrupt)
.globl INT_Timer_Interrupt
INT_Timer_Interrupt:
ldr r1, =0x4a000000 // Interrupt Controller Address
ldr r2, [r1, #0x14] // r2 = INTOFFSET
mov r3, #1
lsl r2, r3, r2 // r2 = (1 << INTOFFSET)
ldr r3, [r1, #0] // r3 = SRCPND
orr r3, r3, r2
str r3, [r1, #0] // SRCPND |= (1 << INTOFFSET)
str r3, [r1, #0x10] // INTPND |= (1 << INTOFFSET)
mov r4,lr // Put IRQ return address into r4
bl TCT_Interrupt_Context_Save
bl TMT_Timer_Interrupt // Call the timer interrupt
// processing.
b TCT_Interrupt_Context_Restore
/* End of INT_Timer_Interrupt */
第3~10行 用于清除s3c2440定时器中断,清除中断方式参考s3c2440手册;
第12行 保存中断返回地址lr(pc),后面的bl指令会修改lr,需要先保存,另外函数内部默认不保存r0-r3的值,函数内部直接修改r0-r3,因此lr不能保存在r0-r3的寄存器里面;
第14行 保存中断上下文;
第16行 调用操作系统定时器处理函数;(任务时间片计数、timer计时...)
第18行 恢复中断上下文。
3.2、中断上下文保存(TCT_Interrupt_Context_Save)
//VOID TCT_Interrupt_Context_Save(INT vector)
//{
.globl TCT_Interrupt_Context_Save
TCT_Interrupt_Context_Save:
/* This routine is designed to handle ARM60/THUMB IRQ interrupts. The IRQ
stack is used as a temporary area. Actual context is saved either
on the interrupted thread's stack or the system stack- both of which
are in the Supervisor (SVC) mode. Note: upon entry to this routine
r0-r3 are saved on the current stack and r3 contains the original
(interrupt return address) lr value. The current lr contains the
ISR return address. */
/* Determine if this is a nested interrupt. */
LDR r1,Int_Count // Pickup address of interrupt count
LDR r2,[r1, #0] // Pickup interrupt counter
ADD r2,r2,#1 // Add 1 to interrupt counter
STR r2,[r1, #0] // Store new interrupt counter value
CMP r2,#1 // Is it nested?
BEQ TCT_Not_Nested_Save // No
/* Nested interrupt. Save complete context on the current stack. */
TCT_Nested_Save:
/* 1. Save another register on the exception stack so we have enough to work with */
STMDB sp!,{r5}
// STMFD sp!,{r0-r1}
// LDR R0,=NestX
// ldr r1,[r0]
// add r1,r1,#1
// str r1,[r0]
// LDMFD sp!,{r0-r1}
/* 2. Save the necessary exception registers into r1-r3 */
MOV r1,sp // Put the exception sp into r1
MOV r2,lr // Move the return address for the caller
// of this function into r2
MRS r3,spsr // Put the exception spsr into r3
/* 3. Adjust the exception stack pointer for future exceptions */
ADD sp,sp,#24 // sp will point to enable reg value when done
/* 4. Switch CPU modes to save context on system stack */
MRS r5,CPSR // Pickup the current CPSR
BIC r5,r5,#MODE_MASK // Clear the mode bits
ORR r5,r5,#SUP_MODE // Change to supervisor mode (SVD)
MSR CPSR_cxsf,r5 // Switch modes (IRQ->SVC)
/* 5. Store the SVC sp into r5 so the sp can be saved as is. */
MOV r5,sp
/* 6. Save the exception return address on the stack (PC). */
STMDB r5!,{r4}
/* 7. Save r6-r14 on stack */
STMDB r5!,{r6-r14}
/* 8. Switch back to using sp now that the original sp has been saved. */
MOV sp,r5
/* 9. Get r5 and exception enable registers off of exception stack and
save r5 (stored in r4) back to the system stack. */
// LDMIA r1!,{r4-r5}
LDMIA r1!,{r4}
STMDB sp!,{r4}
// MOV r4,r5 ; Put exception enable value into r4
/* 10. Get the rest of the registers off the exception stack and
save them onto the system stack. */
LDMIA r1!,{r5-r8,r11} // Get r0-r4 off exception stack
STMDB sp!,{r5-r8,r11} // Put r0-r4 on system stack
/* 11. Store the exception enable value back on the exception stack. */
// STMDB r1!,{r4}
/* 12. Save the SPSR on the system stack (CPSR) */
STMDB sp!,{r3}
/* 13. Re-enable interrupts */
MRS r1,CPSR
BIC r1,r1,#(IBIT|FBIT)
MSR CPSR_cxsf,r1
BX r2 // Return to calling ISR
// }
// else
// {
TCT_Not_Nested_Save:
/* Determine if a thread was interrupted. */
// if (TCD_Current_Thread)
// {
LDR r1,Current_Thread // Pickup current thread ptr address
LDR r1,[r1, #0] // Pickup the current thread pointer
CMP r1,#0 // Is it NU_NULL?
BEQ TCT_Idle_Context_Save // If no, no real save is necessary
/* Yes, a thread was interrupted. Save complete context on the
thread's stack. */
/* 1. Save another register on the exception stack so we have enough to work with */
STMDB sp!,{r5}
/* 2. Save the necessary exception registers into r1-r3 */
MOV r1,sp // Put the exception sp into r1
MOV r2,lr // Move the return address for the caller
// of this function into r2
MRS r3,spsr // Put the exception spsr into r3
/* 3. Adjust the exception stack pointer for future exceptions */
ADD sp,sp,#24 // sp will point to enable reg value when done
/* 4. Switch CPU modes to save context on system stack */
MRS r5,CPSR // Pickup the current CPSR
BIC r5,r5,#MODE_MASK // Clear the mode bits
ORR r5,r5,#SUP_MODE // Change to supervisor mode (SVD)
MSR CPSR_cxsf,r5 // Switch modes (IRQ->SVC)
/* 5. Store the SVC sp into r5 so the sp can be saved as is. */
MOV r5,sp
/* 6. Save the exception return address on the stack (PC). */
STMDB r5!,{r4}
/* 7. Save r6-r14 on stack */
STMDB r5!,{r6-r14}
/* 8. Switch back to using sp now that the original sp has been saved. */
MOV sp,r5
/* 9. Get r5 and exception enable registers off of exception stack and
save r5 (stored in r4) back to the system stack. */
// LDMIA r1!,{r4-r5}
LDMIA r1!,{r4}
STMDB sp!,{r4}
// MOV r4,r5 ; Put exception enable value into r4
/* 10. Get the rest of the registers off the exception stack and
save them onto the system stack. */
LDMIA r1!,{r5-r8,r11} // Get r0-r4 off exception stack
STMDB sp!,{r5-r8,r11} // Put r0-r4 on system stack
/* 11. Store the exception enable value back on the exception stack. */
// STMDB r1!,{r4}
/* 12. Save the SPSR on the system stack (CPSR) */
STMDB sp!,{r3}
/* 13. Save stack type to the task stack (1=interrupt stack) */
MOV r1,#1 // Interrupt stack type
STMDB sp!,{r1}
LDR r1,Current_Thread // Pickup current thread ptr address
LDR r3,[r1, #0] // Pickup current thread pointer
STR sp,[r3, #0x2c] // Save stack pointer
/* Switch to the system stack. */
// REG_Stack_Ptr = TCD_System_Stack;
LDR r1,System_Stack // Pickup address of stack pointer
LDR r3,System_Limit // Pickup address of stack limit ptr
LDR sp,[r1, #0] // Switch to system stack
LDR r10,[r3, #0] // Setup system stack limit
/* Re-enable interrupts */
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MRS r1,CPSR
BIC r1,r1,#(IBIT|FBIT) //;;;;;;;now don't open the interrupt
MSR CPSR_cxsf,r1
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/* Return to caller ISR. */
BX r2 // Return to caller ISR
// }
第15~20行 主要对中断嵌套计数器TCD_Interrupt_Count加1,并判断中断嵌套是否发生(TCD_Interrupt_Count不为1表示此次是中断嵌套,即之前正在执行中断处理函数过程中,发生了新的中断并且中断允许,之前的中断处理被打断),中断没有嵌套则跳转到TCT_Not_Nested_Save执行;
第27行 r5入栈(INT_Interrupt已经保存了r0-r4),此时栈里面的内容为{r5, r0-r4} (低地址到高地址);
第37~40行 IRQ栈保存到r1寄存器,TCT_Interrupt_Context_Save返回地址lr保存到r2寄存器,中断前的cpsr保存到r3(这几个寄存器都是专用的,模式切换后就不能直接访问了);
第43行 恢复IRQ的栈指针(中断上下文的r5,r0-r4保存在IRQ栈里面,共4 * 6 = 24个字节空间),至此IRQ栈已经恢复到中断前;
第46~49行 切换到SVC模式
第52行 SVC的栈指针保存到r5寄存器(中断处理程序最终是在SVC模式下执行的),对于中断嵌套,此处的sp是前一中断处理函数的sp,紧跟的代码不直接修改sp,通过r5间接操作SVC栈,第58行会把中断前的sp入栈;
第55行 INT_Timer_Interrupt函数保存的r4(lr中断返回地址)入栈,SVC栈里面的内容为{lr(pc)};
第58行 r6-r14入栈,SVC栈里面的内容为{r6-r14, lr(pc)} (低地址到高地址)
第61行 更新栈sp指针(sp已经保存了,此后可以通过sp直接操作SVC栈);
第66~68行 r5出IRQ栈入SVC栈(r1指向IRQ保存中断上下文栈的地址),SVC栈里面的内容为{r5,r6-r14, lr(pc)} (低地址到高地址);
第73~74 r0-r4出IRQ栈入SVC栈,SVC栈里面的内容为{r0-r4,r5,r6-r14, lr(pc)} (低地址到高地址);
第80行 第40行保存的cpsr入栈,SVC栈里面的内容为{cpsr,r0-r4,r5,r6-r14, lr(pc)} (低地址到高地址);
第84~89行 使能中断并返回TCT_Interrupt_Context_Save上一级函数,至此已经保存完了嵌套中断的上下文,第89行之后的为非中断嵌套上下文保存;
第99~102行 判断中断前允许的任务是否为Idle任务,是的话跳转到TCT_Idle_Context_Save保存Idle任务的上下文,第108行起保存被中断的任务的上下文;
第109行 r5入IRQ栈,SVC栈里面的内容为{r5,r0-r4} (低地址到高地址);
第112~154行 模式切换,中断上下文保存到SVC栈(此时的栈正好是被中断任务的栈),出入栈代码与中断嵌套出入栈代码一样;
第157~158行 1入任务栈栈顶,任务栈里面的内容为{1,cpsr,r0-r4,r5,r6-r14, lr(pc)} (低地址到高地址);
第161~163行 任务栈保存到TCD_Current_Thread->tc_stack_pointer里面(与中断嵌套不一样,中断恢复是一层层恢复栈,任务栈是下次调度时恢复,中断处理之后,当前任务可能被换出cpu,需要知道任务的上下文保存在哪里,因此将任务栈保存在任务控制块的一个变量里面);
第168~171行 设置栈、栈帧指针(保存完中断上下文后的代码运行在System_Stack里面,这有个好处,中断进入时都设置一下,中断退出的时候不用记得恢复栈,下次进入又会重新设置,因此不会造成栈内存泄漏);
第176~184行 使能中断并返回TCT_Interrupt_Context_Save上一级函数接着调用中断处理函数;
3.3、Idle任务上下文保存
Idle任务其实并不存在的,以单任务为例,任务进入睡眠状态,首先任务保存自己的上下文,然后调用TCT_Control_To_System,使用新的栈不断检测是否有任务需要运行,中断返回时仍没有任务需要运行,又会通过其他路径重新设置任务栈,每次Idle都重新执行,因此Idle任务栈的保存TCT_Idle_Context_Save仅仅恢复IRQ栈及使能中断。
TCT_Idle_Context_Save:
MOV r2,lr // Save lr in r2
// LDR r3,[sp] ; Get exception enable value from stack
ADD sp,sp,#20 // Adjust exception sp for future interrupts
// STR r3,[sp] ; Put exception enable value back on stack
MRS r1,CPSR // Pickup current CPSR
BIC r1,r1,#MODE_MASK // Clear the current mode
BIC r1,r1,#(IBIT|FBIT) // Re-enable interrupts
ORR r1,r1,#SUP_MODE // Prepare to switch to supervisor
// mode (SVC)
MSR CPSR_cxsf,r1 // Switch to supervisor mode (SVC)
BX r2 // Return to caller ISR
// }
//}
3.4、中断上下文恢复
//VOID TCT_Interrupt_Context_Restore(void)
//{
.globl TCT_Interrupt_Context_Restore
TCT_Interrupt_Context_Restore:
/* It is assumed that anything pushed on the stack by ISRs has been
removed upon entry into this routine. */
/* Decrement and check for nested interrupt conditions. */
// if (--TCD_Interrupt_Count)
// {
LDR r1,Int_Count // Pickup address of interrupt count
LDR r2,[r1, #0] // Pickup interrupt counter
SUB r2,r2,#1 // Decrement interrupt counter
STR r2,[r1, #0] // Store interrupt counter
CMP r2,#0
BEQ TCT_Not_Nested_Restore
/* Restore previous context. */
LDR r1,[sp], #4 // Pickup the saved CPSR
MSR SPSR_cxsf,r1 // Place into saved SPSR
LDMIA sp,{r0-r15}^ // Return to the point of interrupt
// }
// else
// {
TCT_Not_Nested_Restore:
/* Determine if a thread is active. */
// if (TCD_Current_Thread)
// {
LDR r1,Current_Thread // Pickup current thread ptr address
LDR r0,[r1, #0] // Pickup current thread pointer
CMP r0,#0 // Determine if a thread is active
BEQ TCT_Idle_Context_Restore // If not, idle system restore
/* Clear the current thread pointer. */
// TCD_Current_Thread = NU_NULL;
MOV r2,#0 // Build NU_NULL value
STR r2,[r1, #0] // Set current thread ptr to NU_NULL
/* Determine if a time slice is active. If so, the remaining
time left on the time slice must be saved in the task's
control block. */
// if (TMD_Time_Slice_State == 0)
// {
LDR r3,Slice_State // Pickup time slice state address
LDR r1,[r3, #0] // Pickup time slice state
CMP r1,#0 // Determine if time slice active
BNE TCT_Idle_Context_Restore // If not, skip time slice reset
/* Pickup the remaining portion of the time slice and save it
in the task's control block. */
// REG_Thread_Ptr -> tc_cur_time_slice = TMD_Time_Slice;
// TMD_Time_Slice_State = 1;
LDR r2,Time_Slice // Pickup address of time slice left
MOV r1,#1 // Build disable time slice value
LDR r2,[r2, #0] // Pickup remaining time slice
STR r1,[r3, #0] // Disable time slice
STR r2,[r0, #0x20] // Store remaining time slice
// }
// }
TCT_Idle_Context_Restore:
/* Reset the system stack pointer. */
LDR r1,System_Stack // Pickup address of stack pointer
LDR r2,System_Limit // Pickup address of stack limit ptr
LDR sp,[r1, #0] // Switch to system stack
LDR r10,[r2, #0] // Setup system stack limit
/* Return to scheduler. */
B TCT_Schedule // Return to scheduling loop
// }
//}
第13~18行 中断嵌套计数器TCD_Interrupt_Count减1并判断是否是中断嵌套,非中断嵌套跳转到TCT_Not_Nested_Restore恢复任务上下文(此次被中断的是任务);
第22~24行 取出中断前的cpsr,恢复被嵌套的中断上下文,接着处理未处理完的中断代码;
第36~39行 获取TCD_Current_Thread,判断中断时是否有任务在运行,没有任务在运行则跳转到TCT_Idle_Context_Restore;
第44~45行 设置TCD_Current_Thread = NULL;
第53~56行 检查时间片状态是否激活,激活情况,每个时钟中断会对任务时间片减1,没有激活,那么当前任务的时间片不用处理;
第63~67行 去激活时间片状态,保存当前任务已经运行的时间片,下次再次调度该任务时,时间片继续按当前时间执行,否则如果每次都从一个完整时间片运行,那么经常被中断的任务拥有的执行时间可能就会很长;(去激活时间片后到保存时间片前可能被中断,去激活时间片后,任务的时间片不再更新)
第73~81行 设置System栈、栈帧指针,跳转到TCT_Schedule执行任务调度。