本文闡述eCos在Cortex-M架構中的中斷響應過程。eCos是開源免版稅的搶占式實時作業系統。其最大亮點是可配置,與其配套的圖形化配置工具提供元件管理、選項配置、自動化單元測試等。
使能和屏蔽中斷
Cortex-M有三個特殊功能寄存器與中斷使能和屏蔽有關,分别是primask、faultmask、basepri,設定primask可以屏蔽除了硬ault異常和NMI異常之外的所有異常和中斷,設定faultmask可以屏蔽除了NMI異常之外的所有異常和中斷,設定basepri可以屏蔽優先級等于或低于目前basepri值的所有異常和中斷,但basepri為0時表示使能所有異常和中斷。
eCos使用basepri控制中斷使能和屏蔽。在整個eCos運作期間,包括初始化、中斷處理過程,primask和faultmask寄存器都處于預設狀态,即清除狀态,在整個運作期間硬fault異常總是使能的。進入初始化例程後,用法fault、總線fault、存儲器管理fault被使能且優先級設定為0。eCos在初始化早期将basepri設定成優先級低于0(優先級數值大于0)的最高有效優先級,最高有效優先級定義為宏CYGNUM_HAL_CORTEXM_PRIORITY_MAX,
CYGNUM_HAL_CORTEXM_PRIORITY_MAX = 1<<(8-CYGNUM_HAL_CORTEXM_PRIORITY_LEVEL_BITS)
CYGNUM_HAL_CORTEXM_PRIORITY_LEVEL_BITS與Cortex-M核心的配置有關,不同的Cortex-M變種使用不同的數值,例如Kinetis和STM32為4,LPC17xx為5。
屏蔽中斷
eCos将basepri設定為CYGNUM_HAL_CORTEXM_PRIORITY_MAX來屏蔽中斷。
// hal/cortexm/arch/<version>/include/hal_intr.h:239
# define HAL_DISABLE_INTERRUPTS(__old) \
__asm__ volatile ( \
"mrs %0, basepri \n" \
"mov r1,%1 \n" \
"msr basepri,r1 \n" \
: "=&r" (__old) \
: "r" (CYGNUM_HAL_CORTEXM_PRIORITY_MAX)\
: "r1" \
);
#endif
(4)将原來的basepri複制到__old,供下次恢複basepri值。
(6)将CYGNUM_HAL_CORTEXM_PRIORITY_MAX複制給basepri,屏蔽所有優先級等于或低于CYGNUM_HAL_CORTEXM_PRIORITY_MAX的中斷。
使能中斷
eCos将basepri設定為0來使能中斷。
// hal/cortexm/arch/<version>/include/hal_intr.h:260
# define HAL_ENABLE_INTERRUPTS() \
__asm__ volatile ( \
"mov r1,#0 \n" \
"msr basepri,r1 \n" \
: \
: \
: "r1" \
);
#endif
(5)将pasepri設定為0,使能所有中斷。
中斷優先級
eCos将basepri設定為CYGNUM_HAL_CORTEXM_PRIORITY_MAX來屏蔽中斷,這意味着所有中斷優先級都不能高于CYGNUM_HAL_CORTEXM_PRIORITY_MAX,否則HAL_DISABLE_INTERRUPTS宏将不能正常工作,為了将中斷優先級等于低于CYGNUM_HAL_CORTEXM_PRIORITY_MAX,在設定中斷優先級時,eCos對優先級數值做了修改。
// hal/cortexm/arch/<version>/src/hal_misc.c:502
__externC void hal_interrupt_set_level( cyg_uint32 vector, cyg_uint32 level )
{
cyg_uint32 l = (level)+CYGNUM_HAL_CORTEXM_PRIORITY_MAX;
if( l > 0xFF ) l = 0xFF; /* clamp to 0xFF */
......
}
(3)将優先級數值加上CYGNUM_HAL_CORTEXM_PRIORITY_MAX,這将保證中斷優先級等于或低于CYGNUM_HAL_CORTEXM_PRIORITY_MAX。
(4)如果超過0xFF,那麼設定為0xFF,避免高位元組截斷引起優先級紊亂,0xFF是Cortex-M最低優先級。
中斷響應
eCos将中斷處理分成兩部分:ISR和DSR。中斷響應的過程也就分成了兩種,一種是ISR沒有要求執行DSR或者排程器被加鎖的情況,這種情況下執行ISR後立即傳回被中斷線程;另一種是ISR要求執行DSR并且排程器未加鎖的情況,這種情況下執行ISR後接着執行DSR,在DSR中可能将某些線程從等待狀态切換到就緒狀态并執行線程排程,DSR執行完成後可能傳回目前線程,也可能傳回剛剛被列入就緒隊列的更高優先級的線程。
eCos在Cortex-M架構下的中斷響應過程如下圖所示,下圖的響應過程是要求執行DSR的過程。
圖中S為Cyg_Scheduler的縮寫,I為Cyg_Interrupt的縮寫,S::unlock()意為Cyg_Scheduler::unlock()。
中斷響應過程涉及4個運作狀态Thread、ISR、PendSV、SVC,Thread使用PSP堆棧,ISR、PendSV、SVC均使用MSP堆棧,包括7個步驟,1次中斷向量響應,1次PendSV響應,2次SVC響應。
{1}中斷産生,目前線程執行過程被打斷,CPU進入中斷異常向量開始中斷響應,eCos将所有中斷包括SysTick的中斷向量設定為hal_default_interrupt_vsr,是以首先進入hal_default_interrupt_vsr函數,該函數讀取中斷号後調用hal_deliver_interrupt對中斷進行分發,hal_deliver_interrupt函數調用對應的ISR,并根據需要挂起一個PendSV請求。
{2}中斷向量傳回後通過Cortex-M的咬尾中斷機制直接進入PendSV向量,PendSV構造一個假的異常堆棧,通過這個假的異常堆棧線上程環境下調用hal_interrupt_end函數,從PendSV傳回後,因為假的異常堆棧的存在并不傳回目前線程被中斷的代碼處繼續執行,而是傳回到hal_interrupt_end函數,此外假的異常堆棧将hal_interrupt_end函數的傳回點設定為hal_interrupt_end_done。看起來就像是在目前線程被中斷處調用了hal_interrupt_end_done,然後hal_interrupt_end_done調用了hal_interrupt_end函數一樣。
{3}傳回到線程環境後開始執行hal_interrupt_end,依次調用interrupt_end、Cyg_Scheduler::unlock、Cyg_Scheduler::unlock_inner、Cyg_Scheduler::call_pending_DSRs、Cyg_Interrupt::call_pending_DSRs,eCos希望在專用的中斷堆棧環境下執行DSR,是以通過swi指令進入SVC系統調用向量,在SVC環境下調用DSR,SVC使用的MSP堆棧,eCos将MSP用作專用的中斷和異常堆棧。
{4}CPU執行到swi指令後進入SVC系統調用向量,這次系統調用将調用hal_call_dsrs_vsr函數,hal_call_dsrs_vsr直接跳轉到cyg_interrupt_call_pending_DSRs,cyg_interrupt_call_pending_DSRs調用Cyg_Interrupt::call_pending_DSRs_inner來調用DSR。
{5}SVC系統調用向量傳回目前線程後,最終傳回到hal_interrupt_end_done。前面是通過假的異常堆棧幀在目前線程中斷點插入對hal_interrupt_end的調用,hal_interrupt_end_done的作用是撤銷假堆棧幀恢複目前線程的被中斷點運作環境,通過swi指令進入SVC系統調用來恢複線程堆棧。
{6}CPU執行到swi指令後進入sVC系統調用向量,這次系統調用将調用hal_interrupt_end_vsr,hal_interrupt_end_vsr的作用是撤銷假堆棧恢複目前線程堆棧指針,然後傳回。
{7}終于,中斷響應完成,傳回到線程被中斷點繼續執行,需要注意的是{5}和{6}之間會進行排程,如果排程了更高優先級的線程,當傳回到這裡的時候已經過去比較長的時間了。
接下來依據中斷響應過程的調用次序,詳細講述各函數。
hal_default_interrupt_vsr
在HAL的初始化過程,所有中斷向量服務例程包括SysTick的向量服務例程均被設定成hal_default_interrupt_vsr,是以産生中斷後首先進入的就是向量服務例程hal_default_interrupt_vsr。
hal/cortexm/arch/<version>/src/vectors.S:212
.type hal_default_interrupt_vsr, %function
hal_default_interrupt_vsr:
push {lr} // Save return link
sub sp,#4 // Realign SP to 8 bytes
mrs r0,ipsr // R0 = arg0 = vector number
sub r0,#15 // Adjust to interrupt range
bl hal_deliver_interrupt
add sp,#4 // pop alignment padding
pop {pc} // Pop LR and return
(5)向量服務例程首先儲存目前的lr寄存器值到堆棧,因為後面馬上要使用bl指令調用函數,bl指令将會修改lr寄存器。
(6)堆棧按8位元組對齊。
(8)讀取目前中斷号。
(9)對中斷号進行修正,ipsr中存儲的實際上是向量編号,這裡實際需要的是中斷編号,減掉15而不是16的原因是eCos将SysTick也作為中斷來處理,SysTick的異常編号為15,對應中斷編号0。
(11)調用hal_deliver_interrupt函數分發中斷服務例程。
(13)重新修正堆棧指針。
(14)将lr寄存器彈出到pc,等價于mov pc, lr,從中斷傳回,如果沒有DSR需要執行,立即傳回被中斷線程,如果需要執行DSR,那麼執行咬尾中斷,進入PendSVC異常服務例程。
hal_deliver_interrupt
VSR讀取目前中斷号後調用中斷分發函數,中斷分發函數根據中斷号調用對應的ISR。
// hal/cortexm/arch/<version>/src/hal_misc.c:371
void hal_deliver_interrupt( cyg_uint32 vector )
{
register cyg_uint32 isr_result;
register cyg_isr *isr;
cyg_bool pendsvc = false;
isr = (cyg_isr *)hal_interrupt_handlers[vector];
// Call the ISR
isr_result = isr( vector, hal_interrupt_data[vector] );
// If the ISR has returned the CALL_DSR bit, post the DSR and set
// the pendable SVC exception pending.
if( isr_result & CYG_ISR_CALL_DSR )
{
cyg_interrupt_post_dsr( hal_interrupt_objects[vector] );
// Post the pendable SVC to call interrupt_end(). But only if
// the scheduler lock is currently zero. If it is non zero
// then interrupt_end will do nothing useful, so avoid calling
// it.
if( cyg_scheduler_sched_lock == 0 )
pendsvc = true;
}
// Post the pendable SVC if required.
if( pendsvc )
{
cyg_uint32 icsr;
HAL_READ_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_ICSR, icsr );
icsr |= CYGARC_REG_NVIC_ICSR_PENDSVSET;
HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_ICSR, icsr );
}
}
(2)中斷号作為中斷分發函數的入參傳入。
(8)以中斷号作為索引讀取ISR函數指針,ISR函數指針是在驅動程式通過調用cyg_drv_interrupt_attach來設定的。
(11)調用ISR,ISR入參為中斷向量号以及使用者資料,多個中斷源可能會使用相同的ISR,根據中斷向量号或使用者資料可以判斷到底是哪一個中斷。
(15)判斷ISR傳回值,看是否需要調用DSR。
(17)如果需要調用DSR,那麼調用cyg_interrupt_post_dsr将目前中斷對應的DSR追加到DSR清單。
(23)判斷目前線程是否對排程器進行加鎖,如果未加鎖,那麼允許執行DSR。
(28-34)如果需要執行DSR,那麼将ICSR寄存器PENDSVSET置位,這将挂起一個PendSV請求,因為PendSV的優先級被初始化為最低優先級,是以不會對目前中斷處理進行搶占,隻有中斷向量服務函數傳回後才會執行PendSV。
疑問:(31-33)讀取ICSR寄存器修改後寫回,這個過程沒有進行關中斷保護,如果在(31)讀取ICSR後産生了更高優先級的中斷,那麼将會搶占目前中斷處理進入嵌套中斷處理流程執行更高優先級的中斷服務例程,當高優先級中斷服務例程傳回後,ICSR寄存器的值與icsr臨時變量存儲的值還保持一緻嗎?
hal_pendable_svc_vsr
eCos中ISR的優先級要絕對高于DSR且不可配置,DSR僅在ISR執行完成後執行,如果産生了嵌套中斷,那麼要等所有ISR全部執行完成後再執行DSR。Cortex-M的中斷控制器與核心緊密結合,隻有執行了hal_default_interrupt_vsr最後一條指令(pop {pc})後,中斷處理才算完成。目前中斷處理完成後,要麼傳回被中斷線程,要麼開始進入低優先級的中斷服務例程。eCos為了保證ISR的優先級絕對高于DSR,且在ISR完成後有機會調用DSR,使用了Cortex-M的PendSV機制,在PendSV處理DSR相關請求。PendSV優先級被設定成最低優先級,如果目前有ISR正在執行,由于PendSV的優先級最低,不可能搶占ISR,是以可以保證ISR優先級絕對高于DSR,即使目前中斷同樣是最低優先級,由于Cortex-M不支援同優先級的搶占,是以也會保證在ISR執行完後再處理DSR。
中斷向量服務例程完成後,通過Cortex-M的咬尾中斷機制進入PendSV向量服務例程。
// hal/cortexm/arch/<v>/src/vectors.S:257
.type hal_pendable_svc_vsr, %function
hal_pendable_svc_vsr:
mrs r12,psp // R12 = thread's PSP
sub r0,r12,#HAL_SAVEDREG_AUTO_FRAME_SIZE // Make space for frame
msr psp,r0 // Put it back
ldr r3,=0x01000000 // R3 = PSR = thumb bit set
ldr r2,=hal_interrupt_end // R2 = PC = interrupt end entry point
ldr r1,=hal_interrupt_end_done // R1 = LR = restore code
stmfd r12!,{r0-r3} // Save fake R12, LR, PC, PSR
stmfd r12!,{r0-r3} // Save fake R0-R3
bx lr // Return to hal_interrupt_end
(5)讀取線程堆棧寄存器,eCos中線程使用PSP為堆棧,PendSV使用MSP最為堆棧,是以通過MRS指令讀取PSP寄存器。
(6)為後面的假異常堆棧幀預留白間。
(7)将修改後的堆棧寫回PSP寄存器。
(9-13)構造假異常堆棧幀,假異常堆棧幀的PSR=0x01000000,PC=hal_interrupt_end,LR=hal_interrupt_end_done,其他寄存器值的内容無關緊要,但是必須按照Cortex-M異常響應時自動壓棧的次序壓入。
(15)從PendSV傳回,Cortex-M執行到這條語句時,将自動從PSP堆棧中恢複R0-R3,R12,LR,PC,PSR,是以當傳回後,PC=hal_interrupt_end,LR=hal_interrupt_end_done,也就是說并不是傳回目前線程的被中斷點,而是傳回到了hal_interrupt_end函數,而且從hal_interrupt_end傳回後還不是線程被中斷點,而是傳回hal_interrupt_end_done。eCos通過構造假異常堆棧幀的手段線上程環境環境下調用部分中斷處理相關代碼。
hal_interrupt_end
hal_interrupt_end的主要作用是調用interrupt_end函數,interrupt_end屬于eCos核心的中斷處理内容。
// hal/cortexm/arch/<v>/src/hal_misc.c:430
__externC void hal_interrupt_end( void )
{
#ifdef CYGFUN_HAL_COMMON_KERNEL_SUPPORT
cyg_scheduler_sched_lock++;
#endif
interrupt_end(0,0,0);
}
(5)排程器鎖進行加鎖,在interrupt_end将對排程器解鎖,這裡的加鎖是為了與interrupt_end中的解鎖配套。cyg_scheduler_sched_lock是Cyg_Scheduler_SchedLock::sched_lock的符号别名,在kernel/<version>/include/smp.hxx:414定義二者的别名關系。
(8)調用interrupt_end,interrupt_end是屬于核心中斷處理的内容,interrupt_end函數的關鍵是調用Cyg_Scheduler::unlock函數,Cyg_Scheduler::unlock調用Cyg_Scheduler::unlock_inner,Cyg_Scheduler::unlock_inner調用Cyg_Interrupt::call_pending_DSRs,Cyg_Interrupt::call_pending_DSRs最終調用DSR。
Cyg_Interrupt::call_pending_DSRs是通過HAL宏來調用DSR的,因為eCos需要在中斷堆棧中調用DSR,而堆棧切換是與硬體息息相關的,是以必須通過HAL宏來實作,該宏為HAL_INTERRUPT_STACK_CALL_PENDING_DSRS,在Cortex-M架構中,該宏定義如下
// hal/cortexm/arch/<v>/include/hal_intr.h:303
#define HAL_INTERRUPT_STACK_CALL_PENDING_DSRS() \
{ \
__asm__ volatile ( \
"ldr r3,=hal_call_dsrs_vsr \n" \
"swi 0 \n" \
: \
: \
: "r3" \
); \
}
(5)将SVC系統調用服務需要調用的函數指針通過R3傳入,R0-R2參數不使用。
(6)使用swi指令觸發系統調用服務。
hal_default_svc_vsr
eCos中斷響應過程包括了2次SVC系統調用服務,是以eCos定義了一個通用的SVC向量服務例程來根據傳入的參數調用響應的函數,當執行到swi指令時将進入SVC向量服務例程響應SVC系統調用服務。
// hal/cortexm/arch/<v>/src/vectors.S:348
.type hal_default_svc_vsr, %function
hal_default_svc_vsr:
mrs r12,psp
ldmfd r12,{r0-r3}
bx r3 // Jump to routine in R3
(5-6)從PSP堆棧取參數。R0-R3是在進入SVC向量服務例程時由Cortex-M自動壓入PSP堆棧的。
(7)R3存儲的是被調用函數指針,跳轉到該函數。
hal_call_dsrs_vsr
// hal/cortexm/arch/<v>/src/vectors.S:326
.type hal_call_dsrs_vsr, %function
hal_call_dsrs_vsr:
.extern cyg_interrupt_call_pending_DSRs
b cyg_interrupt_call_pending_DSRs
(5)直接跳轉到cyg_interrupt_call_pending_DSRs,cyg_interrupt_call_pending_DSRs在kernel/<version>/src/intr/intr.cxx:233中定義,cyg_interrupt_call_pending_DSRs的作用是調用DSR。
hal_interrupt_end_done
DSR調用完成後,從SVC向量服務例程傳回到線程環境,然後沿着函數調用路徑退回到hal_interrupt_end,從hal_interrupt_end傳回時傳回到hal_interrupt_end_done,這個傳回過程是在構造假異常堆棧幀時設定的。
// hal/cortexm/arch/<v>/src/vectors.S:289
.type hal_interrupt_end_done, %function
hal_interrupt_end_done:
ldr r3,=hal_interrupt_end_vsr
swi 0
(4)将hal_interrupt_end_vsr函數位址儲存到R3,SVC系統調用将通過R3調用hal_interrupt_end_vsr。
(5)觸發SVC系統調用。
hal_interrupt_end_vsr
hal_interrupt_end_vsr的作用是撤銷假異常堆棧幀,恢複線程在中斷自動壓棧後的狀态。
// hal/cortexm/arch/<v>/src/vectors.S:306
.type hal_interrupt_end_vsr, %function
hal_interrupt_end_vsr:
mrs r12,psp // R12 = thread's PSP
add r12,#HAL_SAVEDREG_AUTO_FRAME_SIZE // Skip our saved state
msr psp,r12 // Restore thread's PSP
bx lr // And return
(4)讀取線程堆棧PSP。