Cortex-M与RISC-V区别

环境

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中

assembly 复制代码
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在启动文件中会初始化一个中断向量表,所有的异常和中断根据这个表的地址跳转

assembly 复制代码
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp                      ; Top of Stack
                DCD     Reset_Handler                     ; Reset Handler
                DCD     NMI_Handler                       ; NMI Handler
                DCD     HardFault_Handler                 ; Hard Fault Handler
                DCD     MemManage_Handler                 ; MPU Fault Handler
                DCD     BusFault_Handler                  ; Bus Fault Handler
                DCD     UsageFault_Handler                ; Usage Fault Handler
                DCD     0                                 ; Reserved
                DCD     0                                 ; Reserved
                DCD     0                                 ; Reserved
                DCD     0                                 ; Reserved
                DCD     SVC_Handler                       ; SVCall Handler
                DCD     DebugMon_Handler                  ; Debug Monitor Handler
                DCD     0                                 ; Reserved
                DCD     PendSV_Handler                    ; PendSV Handler
                DCD     SysTick_Handler                   ; SysTick Handler

                ; External Interrupts
                DCD     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()

c 复制代码
    /*
     * Set Exception Entry MTVEC to exc_entry
     * Due to settings above, Exception and NMI
     * will share common entry.
     */
    la t0, exc_entry
    csrw CSR_MTVEC, t0

ECLIC 的每个中断源均可以设置成向量或者非向量处理(通过寄存器 clicintattr[i]的 shv 域),其要点如下:

当 mtvec.MODE != 6'b000011 时,处理器使用默认中断模式

mtvec.MODE = 6'b000011 时,处理器使用ECLIC 中断模式,推荐使用此模式。

assembly 复制代码
    /* Set the interrupt processing mode to ECLIC mode */
    la t0, 0x3f
    csrc CSR_MTVEC, t0
    csrs CSR_MTVEC, 0x3
  • 如果被配置成为向量处理模式,则该中断被处理器内核响应后,处理器直接跳入该中断的向量入口(Vector Table Entry)存储的目标地址

  • 如果被配置成为非向量处理模式,则该中断被处理器内核响应后,处理器直接跳入所有中断共享的入口地址

    默认情况下异常和所有非向量中断共享入口地址(不推荐),推荐将 CSR 寄存器 mtvt2 的最低位设置为 1,则所有非向量中断共享的入口地址由 CSR 寄

    存器 mtvt2 的值(忽略最低 2 位的值)指定

    assembly 复制代码
    /*
         * 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_entry
        csrw CSR_MTVT2, t0
        csrs CSR_MTVT2, 0x1

进入irq_entry后会自动关闭中断MIE

保存完上下文之后会调用对应中断服务程序,在跳入中断服务程序的同时,硬件也会同时打开中断的全局使能

中断服务程序执行完成之后需要将中断全局使能再次关闭,保证恢复上下文的原子性

MPIE会记录异常发生前得MIE值,以便异常结束时恢复到原来的值

assembly 复制代码
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_CONTEXTRESTORE_CONTEXT来保存上下文和恢复上下文

assembly 复制代码
.macro SAVE_CONTEXT
    csrrw sp, CSR_MSCRATCHCSWL, sp
    /* Allocate stack space for context saving */

    addi sp, sp, -20*REGBYTES

    STORE 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_CONTEXT
	LOAD 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*REGBYTES

    csrrw sp, CSR_MSCRATCHCSWL, sp
.endm

还有SAVE_CSR_CONTEXTRESTORE_CSR_CONTEXT来保存和恢复这三个MCAUSE MEPC MSUBMCSR寄存器

例如下面的第一条命令表示把MCAUSE存到SP+11*4的位置,正好上面留了三个位置给CSR寄存器

assembly 复制代码
/**
 * \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_CONTEXT
    LOAD x5,  13*REGBYTES(sp)
    csrw CSR_MSUBM, x5
    LOAD x5,  12*REGBYTES(sp)
    csrw CSR_MEPC, x5
    LOAD x5,  11*REGBYTES(sp)
    csrw CSR_MCAUSE, x5
.endm

栈指针的切换

Cortex-M

Cortex-M有主栈MSP和线程栈PSP自动切换,默认是使用的MSP,那么疑问来了,怎么使用线程栈呢?

assembly 复制代码
PendSV_Handler   PROC
switch_to_thread
    LDR     r1, =rt_interrupt_to_thread
    LDR     r1, [r1]
    LDR     r1, [r1]                ; load thread stack pointer

    LDMFD   r1!, {r4 - r11}         ; pop r4 - r11 register
    MSR     psp, r1                 ; update stack pointer

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

在线程切换的时候,会把线程的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

在保存上下文和恢复上下文中都有如下语句:

assembly 复制代码
csrrw sp, CSR_MSCRATCHCSWL, sp

mscratchcswl 寄存器用于在多个中断 level 间切换时,交换目的寄存器与 mscratch 的值来加速中断处理

使用带读操作的 CSR 指令访问 mscratchcsw,当特权模式不变,在出现中断程序和应用程序的切换时,有以下伪指令所示的寄存器操作:

mcause.mpil表示前一个中断级别

mintstatus.mil表示Machine Mode 的有效中断级别

c 复制代码
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

assembly 复制代码
rt_hw_context_switch_to:
    /* Setup Interrupt Stack using
       The stack that was used by main()
       before the scheduler is started is
       no longer required after the scheduler is started.
       Interrupt stack pointer is stored in CSR_MSCRATCH */
    la t0, _sp
    csrw CSR_MSCRATCH, t0
    LOAD sp, 0x0(a0)                /* Read sp from first TCB member(a0) */

在进入中断时,mepc 寄存器被同时更新,以反映当时遇到中断时的 PC 值。软件必须使用 mret指令退出中断,执行 mret 指令后处理器将从 mepc 定义的 pc 地址重新开始执行。通过这个机制,意味着 mret 指令执行后处理器回到了当时遇到中断时的 PC 地址,从而可以继续执行之前被中止的程序流。

相关推荐
jjyangyou1 天前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
憧憬一下2 天前
Pinctrl子系统中Pincontroller和client驱动程序的编写
arm开发·嵌入式·c/c++·linux驱动开发
蓝天居士2 天前
ES8388 —— 带耳机放大器的低功耗立体声音频编解码器(4)
嵌入式·音频·es8388
田三番2 天前
使用 vscode 简单配置 ESP32 连接 Wi-Fi 每日定时发送 HTTP 和 HTTPS 请求
单片机·物联网·http·https·嵌入式·esp32·sntp
启明智显2 天前
AI笔筒操作说明及应用场景
人工智能·嵌入式硬件·嵌入式·ai大模型·启明智显·esp32-s3
FreakStudio2 天前
全网最适合入门的面向对象编程教程:58 Python字符串与序列化-序列化Web对象的定义与实现
python·单片机·嵌入式·面向对象·电子diy
PegasusYu4 天前
STM32CUBEIDE FreeRTOS操作教程(八):queues多队列
freertos·rtos·队列·stm32cubeide·free-rtos·queues·多队列
Projectsauron6 天前
【STM32】通过 DWT 实现毫秒级延时
stm32·嵌入式·dwt
云中双月7 天前
如何使用Ida Pro和Core Dump文件定位崩溃位置(Linux下无调试符号的进程专享)
linux·嵌入式·gdb·调试·gcc·崩溃·ida pro·ulimit·core dump·cross compile
L_Z_J_I8 天前
超子物联网HAL库笔记:准备篇
笔记·物联网·嵌入式