環境
Cortex-M以STM32H750為代表,RISC-V以芯來為代表
RTOS版本為RT-Thread 4.1.1
寄存器
RISC-V
常用匯編
RISC-V
關于STORE
x4, 4(sp)這種寄存器前面帶數字的寫法,其意思為將x4的值存入sp+4這個地址,即前面的數字表示偏移的意思
反之LOAD
表示從內存里面取值
la
地址加載 (Load Address). 偽指令(Pseudoinstruction), RV32I and RV64I.
la rd, symbol x[rd] = &symbol
例如將函數irq_entry()的地址放入t0中
la t0, irq_entry
mv
移動(Move). 偽指令(Pseudoinstruction), RV32I and RV64I.
mv rd, rs1 x[rd] = x[rs1]
把寄存器 x[rs1]復制到 x[rd]中。實際被擴展為 addi rd, rs1, 0
addi
加立即數(Add Immediate). I-type, RV32I and RV64I.
把符號位擴展的立即數加到寄存器 x[rs1]上,結果寫入 x[rd]。忽略算術溢出。
addi rd, rs1, immediate x[rd] = x[rs1] + sext(immediate)
add
加 (Add). R-type, RV32I and RV64I.
把寄存器 x[rs2]加到寄存器 x[rs1]上,結果寫入 x[rd]。忽略算術溢出。
add rd, rs1, rs2 x[rd] = x[rs1] + x[rs2]
壓縮形式:c.add rd, rs2; c.mv rd, rs2
Cortex-M
中斷與異常處理
中斷與異常入口
Cortex-M
Cortex-M
在啟動文件中會初始化一個中斷向量表,所有的異常和中斷根據這個表的地址跳轉
; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLYEXPORT __VectorsEXPORT __Vectors_EndEXPORT __Vectors_Size__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault HandlerDCD BusFault_Handler ; Bus Fault HandlerDCD UsageFault_Handler ; Usage Fault HandlerDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCall HandlerDCD DebugMon_Handler ; Debug Monitor HandlerDCD 0 ; ReservedDCD PendSV_Handler ; PendSV HandlerDCD SysTick_Handler ; SysTick Handler; External InterruptsDCD WWDG_IRQHandler ; Window WatchDog interrupt ( wwdg1_it) DCD PVD_AVD_IRQHandler ; PVD/AVD through EXTI Line detection
RISC-V
RISC-V
的異常會跳入所有異常共享的異常處理程序入口mtvec,在啟動文件中的初始化如下:
exc_entry由匯編編寫,其中又會調用core_exception_handler()
/** Set Exception Entry MTVEC to exc_entry* Due to settings above, Exception and NMI* will share common entry.*/la t0, exc_entrycsrw CSR_MTVEC, t0
ECLIC 的每個中斷源均可以設置成向量或者非向量處理(通過寄存器 clicintattr[i]的 shv 域),其要點如下:
當 mtvec.MODE != 6’b000011 時,處理器使用默認中斷模式
mtvec.MODE = 6’b000011 時,處理器使用ECLIC 中斷模式
,推薦使用此模式。
/* Set the interrupt processing mode to ECLIC mode */la t0, 0x3fcsrc CSR_MTVEC, t0csrs CSR_MTVEC, 0x3
-
如果被配置成為向量處理模式,則該中斷被處理器內核響應后,處理器直接跳入該中斷的向量入口(Vector Table Entry)存儲的目標地址
-
如果被配置成為非向量處理模式,則該中斷被處理器內核響應后,處理器直接跳入所有中斷共享的入口地址
默認情況下異常和所有非向量中斷共享入口地址(不推薦),推薦將 CSR 寄存器 mtvt2 的最低位設置為 1,則所有非向量中斷共享的入口地址由 CSR 寄
存器 mtvt2 的值(忽略最低 2 位的值)指定/** Set ECLIC non-vector entry to be controlled* by mtvt2 CSR register.* Intialize ECLIC non-vector interrupt* base address mtvt2 to irq_entry.*/la t0, irq_entrycsrw CSR_MTVT2, t0csrs CSR_MTVT2, 0x1
進入irq_entry
后會自動關閉中斷MIE
保存完上下文之后會調用對應中斷服務程序,在跳入中斷服務程序的同時,硬件也會同時打開中斷的全局使能
中斷服務程序執行完成之后需要將中斷全局使能再次關閉,保證恢復上下文的原子性
MPIE會記錄異常發生前得MIE值,以便異常結束時恢復到原來的值
csrrw ra, CSR_JALMNXTI, ra
在跳入中斷服務程序的同時,“csrrw ra, CSR_JALMNXTI, ra”指令還會達到 JAL(Jump and Link)的效果,硬件同時更新 Link 寄存器的值為該指令的 PC 自身作為函數調用的返回地址。因此,從中斷服務程序函數返回后會回到該“csrrw ra,CSR_JALMNXTI, ra”指令重新執行,重新判斷是否還有中斷在等待(Pending),從而達到中斷咬尾的效果。如果沒有中斷在等待(Pending),則該指令相當于是個 Nop 指令不做任何操作。
對于中斷嵌套,會重新從irq_entry
進入
保存上下文
Cortex-M
當Cortex-M開始響應一個中斷時,會自動完成如下操作:
-
入棧: 自動把8個寄存器的值壓入棧
-
取向量:從向量表中找出對應的服務程序入口地址
-
選擇堆棧指針MSP/PSP,更新堆棧指針SP,更新連接寄存器LR,更新程序計數器PC
響應異常的第一個行動,就是自動保存現場的必要部分:依次把xPSR, PC, LR, R12以及R3‐R0由硬件自動壓入適當的堆棧中:如果當響應異常時,當前的代碼正在使用PSP,則壓入PSP,即使用線程堆棧;否則壓入MSP,使用主堆棧。一旦進入了服務例程,就將一直使用主堆棧。
壓棧順序如下:
地址(設原SP為 N-0) | 寄存器 | 被保存的順序 |
---|---|---|
N-4 | xPSR | 2 |
N-8 | PC | 1 |
N-12 | LR | 8 |
N-16 | R12 | 7 |
N-20 | R3 | 6 |
N-24 | R2 | 5 |
N-28 | R1 | 4 |
N-32 (新SP也指向這里) | R0 | 3 |
RISC-V
RISC-V
架構的處理器在進入和退出中斷處理模式時沒有硬件自動保存和恢復上下文(通用寄存器)的操作,因此需要軟件明確地使用(匯編語言編寫的)指令進行上下文的保存和恢復。根據中斷是向量處理模式還是非向量處理模式,上下文的保存和恢復涉及到的內容會有所差異
在上述異常exc_entry和中斷irq_entry中,都有SAVE_CONTEXT
和RESTORE_CONTEXT
來保存上下文和恢復上下文
.macro SAVE_CONTEXTcsrrw sp, CSR_MSCRATCHCSWL, sp/* Allocate stack space for context saving */addi sp, sp, -20*REGBYTESSTORE x1, 0*REGBYTES(sp)STORE x4, 1*REGBYTES(sp)STORE x5, 2*REGBYTES(sp)STORE x6, 3*REGBYTES(sp)STORE x7, 4*REGBYTES(sp)STORE x10, 5*REGBYTES(sp)STORE x11, 6*REGBYTES(sp)STORE x12, 7*REGBYTES(sp)STORE x13, 8*REGBYTES(sp)STORE x14, 9*REGBYTES(sp)STORE x15, 10*REGBYTES(sp)STORE x16, 14*REGBYTES(sp)STORE x17, 15*REGBYTES(sp)STORE x28, 16*REGBYTES(sp)STORE x29, 17*REGBYTES(sp)STORE x30, 18*REGBYTES(sp)STORE x31, 19*REGBYTES(sp)
.endm.macro RESTORE_CONTEXTLOAD x1, 0*REGBYTES(sp)LOAD x4, 1*REGBYTES(sp)LOAD x5, 2*REGBYTES(sp)LOAD x6, 3*REGBYTES(sp)LOAD x7, 4*REGBYTES(sp)LOAD x10, 5*REGBYTES(sp)LOAD x11, 6*REGBYTES(sp)LOAD x12, 7*REGBYTES(sp)LOAD x13, 8*REGBYTES(sp)LOAD x14, 9*REGBYTES(sp)LOAD x15, 10*REGBYTES(sp)LOAD x16, 14*REGBYTES(sp)LOAD x17, 15*REGBYTES(sp)LOAD x28, 16*REGBYTES(sp)LOAD x29, 17*REGBYTES(sp)LOAD x30, 18*REGBYTES(sp)LOAD x31, 19*REGBYTES(sp)addi sp, sp, 20*REGBYTEScsrrw sp, CSR_MSCRATCHCSWL, sp
.endm
還有SAVE_CSR_CONTEXT
和RESTORE_CSR_CONTEXT
來保存和恢復這三個MCAUSE
MEPC
MSUBM
CSR寄存器
例如下面的第一條命令表示把MCAUSE
存到SP+11*4的位置,正好上面留了三個位置給CSR寄存器
/*** \brief Macro for save necessary CSRs to stack* \details* This macro store MCAUSE, MEPC, MSUBM to stack.*/
.macro SAVE_CSR_CONTEXT/* Store CSR mcause to stack using pushmcause */csrrwi x5, CSR_PUSHMCAUSE, 11/* Store CSR mepc to stack using pushmepc */csrrwi x5, CSR_PUSHMEPC, 12/* Store CSR msub to stack using pushmsub */csrrwi x5, CSR_PUSHMSUBM, 13
.endm.macro RESTORE_CSR_CONTEXTLOAD x5, 13*REGBYTES(sp)csrw CSR_MSUBM, x5LOAD x5, 12*REGBYTES(sp)csrw CSR_MEPC, x5LOAD x5, 11*REGBYTES(sp)csrw CSR_MCAUSE, x5
.endm
棧指針的切換
Cortex-M
Cortex-M有主棧MSP和線程棧PSP自動切換,默認是使用的MSP,那么疑問來了,怎么使用線程棧呢?
PendSV_Handler PROC
switch_to_threadLDR r1, =rt_interrupt_to_threadLDR r1, [r1]LDR r1, [r1] ; load thread stack pointerLDMFD r1!, {r4 - r11} ; pop r4 - r11 registerMSR psp, r1 ; update stack pointerpendsv_exit; restore interruptMSR PRIMASK, r2ORR lr, lr, #0x04BX lrENDP
在線程切換的時候,會把線程的sp——rt_interrupt_to_thread
賦給psp
在進入異常服務程序后,LR的值被自動更新為特殊的EXC_RETURN,所以只需要在異常中將LR的bit2置1就可以切換PSP了
EXC_RETURN會根據進入異常前的模式和SP使用情況生成(保持進入異常前的值),理論上只用第一次線程切換時手動把MSP改成PSP
EXC_RETURN位段 | 含義 |
---|---|
[31:4] | EXC_RETURN的標識:必須全為1 |
3 | 0=返回后進入Handler模式 1=返回后進入線程模式 |
2 | 0=從主堆棧中做出棧操作,返回后使用MSP, 1=從進程堆棧中做出棧操作,返回后使用PSP |
1 | 保留,必須為0 |
0 | 0=返回ARM狀態。 1=返回Thumb狀態。在CM3中必須為1 |
LR在函數調用時會自動更新,對于函數的返回,將LR出棧給PC即可
在異常退出時,也會將LR賦給PC,但是很顯然這不是代碼空間的地址,系統會根據標識檢測到這是一條EXC_RETURN命令,進一步根據進入中斷時入棧的PC進行返回
RISC-V
在保存上下文和恢復上下文中都有如下語句:
csrrw sp, CSR_MSCRATCHCSWL, sp
mscratchcswl 寄存器用于在多個中斷 level 間切換時,交換目的寄存器與 mscratch 的值來加速中斷處理
使用帶讀操作的 CSR 指令訪問 mscratchcsw,當特權模式不變,在出現中斷程序和應用程序的切換時,有以下偽指令所示的寄存器操作:
mcause.mpil
表示前一個中斷級別
mintstatus.mil
表示Machine Mode 的有效中斷級別
csrrw rd, mscratchcswl, rs1// Pseudocode operation.
// 棧指針的切換只在中斷中操作,mintstatus.mil肯定不為0
// 如果mcause.mpil==0表示從線程中進入的中斷(待驗證),即判斷成立,使用mscratch和SP交換來加載主棧
// RESTORE_CONTEXT中再次調用即再次交互,把主棧存入mscratch并把線程棧交換到SP中
if ( (mcause.mpil==0) != (mintstatus.mil == 0) )
{t = rs1; rd = mscratch; mscratch = t;
}
else
{ // 中斷嵌套使用同一個棧,不需要改變rd = rs1; // mscratch unchanged.
}
// Usual use: csrrw sp, mscratchcswl, sp
看到這里,就會有一個疑問,第一次進中斷時,mscratch中的內容從哪里來呢?
rt_hw_context_switch_to
表示沒有來源即第一次切換線程(在開始OS調度時調用,不是在中斷切換)
所以第一次切換線程時會將主棧存入mscratch,之后就不需要再管了
之后便線程棧賦值給sp
rt_hw_context_switch_to:/* Setup Interrupt Stack usingThe stack that was used by main()before the scheduler is started isno longer required after the scheduler is started.Interrupt stack pointer is stored in CSR_MSCRATCH */la t0, _spcsrw CSR_MSCRATCH, t0LOAD sp, 0x0(a0) /* Read sp from first TCB member(a0) */
在進入中斷時,mepc 寄存器被同時更新,以反映當時遇到中斷時的 PC 值。軟件必須使用 mret指令退出中斷,執行 mret 指令后處理器將從 mepc 定義的 pc 地址重新開始執行。通過這個機制,意味著 mret 指令執行后處理器回到了當時遇到中斷時的 PC 地址,從而可以繼續執行之前被中止的程序流。