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執行任務調度。