ESP32上移植Rt-thread
一、ESP-IDF结构分析
能够清楚的认识IDF框架的结构对之后移植RT-thread具有很大的帮助
以下结构均为本文作者通过查询手册、网上搜索和查看源码自行分析而来,不保证权威性与正确性,如有问题欢迎联系修改
总体结构:
- components:各种组件目录
- esp32:esp32启动前代码
- freertos:freertos组件
- make:整个系统总的Makefile
- project.mk:整个系统工具链配置(CFLAGS等参数)
- tools:构建工具及脚本
- cmake:cmake配置
- build.cmake:整个系统工具链配置(CFLAGS等参数)
- cmake:cmake配置
- docs:帮助文档
重点关注components/freertos
组件,其目录为:
-
include/freertos:头文件
-
CMakeLists.txt:组件cmake
-
component.mk:组件Makefile
-
portasm.S:freertos移植汇编文件(该文件需重点关注)
- port_IntStack:中断栈底
- port_IntStackTop:中断栈顶
- port_switch_flag:调度标志
- _frxt_setup_switch()
- _frxt_int_enter():XT_RTOS_INT_ENTER宏的具体实现,进入中断,用来保存其余的中断上下文并进入RTOS,设置C环境
- _frxt_int_exit():XT_RTOS_INT_EXIT宏的具体实现,退出中断,开始调度(此时仍在中断中,调度器将返回到XT_STK_EXIT/XT_SOL_EXIT处保存在中断栈帧中的退出点)
- _frxt_timer_int():systick中断
- _frxt_tick_timer_init():systick初始化
- _frxt_dispatch():上下文切换(核心调度函数,真正的上下文切换在这里)
- vPortYield():调度函数
- vPortYieldFromInt():从中断中调度
- _frxt_task_coproc_state():协处理器状态
-
xtensa_context.S:该文件中是一些上下文保存和恢复(包括协处理器)相关的函数
- _xt_context_save()
- _xt_context_restore()
- _xt_coproc_init()
- _xt_coproc_release()
- _xt_coproc_savecs()
- _xt_coproc_restorecs()
-
xtensa_init.c:freertos中xtensa初始化相关函数
- _xt_tick_divisor_init()
- xt_clock_freq()
-
xtensa_intr.c:主要用于注册中断
- xt_set_exception_handler()
- xt_set_interrupt_handler()
-
xtensa_intr_asm.S:上述文件的汇编部分
- _xt_intdata:下面两个变量的起始地址
- _xt_intenable
- _xt_vpri_mask
- _xt_interrupt_table ([0]=xt_unhandled_interrupt):中断向量表
- _xt_exception_table ([0]=xt_unhandled_exception):异常向量表
- xt_ints_on()
- xt_ints_off()
-
xtensa_overlay_os_hook.c:
- xt_overlay_init_os()
- xt_overlay_lock()
- xt_overlay_unlock()
-
xtensa_vector_defaults.S:xtensa中默认的中断
- _xt_debugexception():调试异常中断
- _xt_highint2()
- _xt_highint3()
- _xt_highint4()
- _xt_highint5()
- _xt_highint6()
- _xt_nmi()
-
xtensa_vectors.S:xtensa中的中断向量(重要)
- get_percpu_entry_for reg scratch 宏
- extract_msb aout ain 宏
- dispatch_c_isr level mask 宏:当中断的C语言环境设置好后通过该宏来调用注册的具体的C语言函数
- .L_xt_user_int_&level&
- .L_xt_user_int_timer_&level&
- _xt_panic():panic异常中断
- _xt_intexc_hooks
- _DebugExceptionVector():调用xt_debugexception()
- _DoubleExceptionVector():最终调用_xt_panic()
- _KernelExceptionVector():-> _xt_kernel_exc() -> _xt_panic()
- _UserExceptionVector(): -> _xt_user_exc():
- 有可能调用_xt_lowint1()
- 保存一部分上文
- _xt_context_save()
- 调用_xt_intexc_hooks中的钩子函数(需要XT_INTEXC_HOOKS宏)
- 调用_xt_exception_table表中注册的中断
- _xt_context_restore()
- 恢复一部分上文
- _xt_user_exit():调度退出时,在这里修改最后的ps、pc、sp(stack pointer)值,修改完毕后立马返回,之后就执行新的线程
- _xt_coproc_sa_offset
- _xt_coproc_owner_sa
- _xt_lowint1():私有函数(1级中断函数)
- 保存一部分上文(设置出口点为_xt_user_exit())
- 调用XT_RTOS_INT_ENTER宏
- dispatch_c_isr 1 XCHAL_INTLEVEL1_MASK
- 调用XT_RTOS_INT_EXIT宏
- _Level2Vector(): -> _xt_medint2():
- 保存一部分上文(设置出口点为_xt_medint2_exit())
- 调用XT_RTOS_INT_ENTER宏
- dispatch_c_isr 2 XCHAL_INTLEVEL2_MASK
- 调用XT_RTOS_INT_EXIT宏
- _xt_medint2_exit()
- _Level3Vector(): -> _xt_medint3()
- _xt_medint3_exit()
- _Level4Vector(): -> _xt_medint4()
- _xt_medint4_exit()
- _Level5Vector(): -> _xt_medint5()
- _xt_medint5_exit()
- _Level6Vector(): -> _xt_medint6()
- _xt_medint6_exit()
- _Level2Vector(): -> xt_highint2()
- _Level3Vector(): -> xt_highint3()
- _Level4Vector(): -> xt_highint4()
- _Level5Vector(): -> xt_highint5()
- _Level6Vector(): -> xt_highint6()
- _NMIExceptionVector(): -> xt_nmi()
- _WindowOverflow4
- _WindowUnderflow4
- _WindowOverflow8
- _WindowUnderflow8
- _WindowOverflow12
- _WindowUnderflow12
- call_user_start():空函数
二、CPU架构移植
参考RT-thread官方手册进行cpu架构移植,其中需要移植以下函数:
函数和变量 | 描述 |
---|---|
rt_base_t rt_hw_interrupt_disable(void); | 关闭全局中断 |
void rt_hw_interrupt_enable(rt_base_t level); | 打开全局中断 |
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit); | 线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数 |
void rt_hw_context_switch_to(rt_uint32 to); | 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用 |
void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于线程和线程之间的切换 |
void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用 |
rt_uint32_t rt_thread_switch_interrupt_flag; | 表示需要在中断里进行切换的标志 |
rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; | 在线程进行上下文切换时候,用来保存 from 和 to 线程 |
1.1 中断相关
rtt中中断相关函数为rt_base_t rt_hw_interrupt_disable(void);
和void rt_hw_interrupt_enable(rt_base_t level);
,即关闭和打开全局中断,rtt内核要求:
在 rt_hw_interrupt_disable() 函数里面需要依序完成的功能是:
- 保存当前的全局中断状态,并把状态作为函数的返回值
- 关闭全局中断
在 rt_hw_interrupt_enable(rt_base_t level) 里:
- 将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态
- 恢复全局中断
在IDF框架中已经有如下函数能够参考:
不带返回值:
-
include\freertos\portmacro.h
中的portDISABLE_INTERRUPTS
宏(关闭中断,禁用所有可屏蔽中断):1
-
idf中
XCHAL_EXCM_LEVEL
为3(PS.EXCM
屏蔽的级别) -
其中
portbenchmarkINTERRUPT_RESTORE
宏为freertos的插件,idf中未启用,为空 -
XTOS_SET_INTLEVEL
的具体实现(设置PS.INTLEVEL
):1
2
3
4
-
-
include\freertos\portmacro.h
中的portENABLE_INTERRUPTS
宏(使能中断):1
带返回值:
-
include\freertos\portmacro.h
中的portSET_INTERRUPT_MASK_FROM_ISR
宏(关闭中断):1
2
3
4
5
6
7static inline unsigned portENTER_CRITICAL_NESTED() {
unsigned state = XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL);
portbenchmarkINTERRUPT_DISABLE();
return state;
} -
include\freertos\portmacro.h
中的portCLEAR_INTERRUPT_MASK_FROM_ISR
宏(使能中断):1
2
3
1.2 线程栈初始化
在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init()
,_rt_thread_init()
函数会调用栈初始化函数rt_hw_stack_init()
,在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。上下文在栈里的排布如下图所示(Cortex-M架构):
xtensa架构中栈也是自顶向下增长,堆为自底向上增长
xtensa架构中初始化第一个线程时默认的线程栈如下所示(包括协处理器栈):
其中最后一部分为任务的上文。关于这块xtensa又分为两部分:
-
STK帧:如下图所示为STK栈帧,初始化的线程栈中也是STK栈帧。该栈帧用于线程被中断打断时保存上文使用,会将全部的寄存器压入栈中
-
SOL帧:如下所以为SOL栈帧,该栈帧仅压入最关键的寄存器到栈中,一般为线程间切换时使用
上述两种栈的类型可以通过栈帧中的exit成员判断。STK中exit成员用于保存函数退出后的函数,而SOL中的exit为0。
1.2.1 源码分析
rtt中cortex-m3架构的代码:
1 | truct exception_stack_frame |
xtensa架构中的rtt移植函数(cpuport.c
):
1 | rt_uint8_t *rt_hw_stack_init(void *tentry, |
-
受限于xtensa中STK栈帧的影响,线程退出函数exit只能存放
_xt_user_exit
函数,所以这里暂不支持线程退出。可以通过全局修改调用exit函数的地方固定为_xt_user_exit
函数 -
_thread_local_start
,_thread_local_end
,_rodata_start
为链接脚本components/esp32/ld/esp32.project.ld.in
中定义的三个地址 -
rtt中断
stack_frame
就是idf中的XtExcFrame
(components/freertos/include/freertos/xtensa_context.h
中定义),对应上述代码中的frame
,其结构如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//结构体如下
STRUCT_BEGIN
STRUCT_FIELD (long, 4, XT_STK_EXIT, exit) /* exit point for dispatch */
STRUCT_FIELD (long, 4, XT_STK_PC, pc) /* return PC */
STRUCT_FIELD (long, 4, XT_STK_PS, ps) /* return PS */
STRUCT_FIELD (long, 4, XT_STK_A0, a0)
STRUCT_FIELD (long, 4, XT_STK_A1, a1) /* stack pointer before interrupt */
STRUCT_FIELD (long, 4, XT_STK_A2, a2)
STRUCT_FIELD (long, 4, XT_STK_A3, a3)
STRUCT_FIELD (long, 4, XT_STK_A4, a4)
STRUCT_FIELD (long, 4, XT_STK_A5, a5)
STRUCT_FIELD (long, 4, XT_STK_A6, a6)
STRUCT_FIELD (long, 4, XT_STK_A7, a7)
STRUCT_FIELD (long, 4, XT_STK_A8, a8)
STRUCT_FIELD (long, 4, XT_STK_A9, a9)
STRUCT_FIELD (long, 4, XT_STK_A10, a10)
STRUCT_FIELD (long, 4, XT_STK_A11, a11)
STRUCT_FIELD (long, 4, XT_STK_A12, a12)
STRUCT_FIELD (long, 4, XT_STK_A13, a13)
STRUCT_FIELD (long, 4, XT_STK_A14, a14)
STRUCT_FIELD (long, 4, XT_STK_A15, a15)
STRUCT_FIELD (long, 4, XT_STK_SAR, sar)
STRUCT_FIELD (long, 4, XT_STK_EXCCAUSE, exccause)
STRUCT_FIELD (long, 4, XT_STK_EXCVADDR, excvaddr)
STRUCT_FIELD (long, 4, XT_STK_LBEG, lbeg)
STRUCT_FIELD (long, 4, XT_STK_LEND, lend)
STRUCT_FIELD (long, 4, XT_STK_LCOUNT, lcount)
/* Temporary space for saving stuff during window spill */
STRUCT_FIELD (long, 4, XT_STK_TMP0, tmp0)
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
/* Storage for virtual priority mask */
STRUCT_FIELD (long, 4, XT_STK_VPRI, vpri)
/* Storage for overlay state */
STRUCT_FIELD (long, 4, XT_STK_OVLY, ovly)
STRUCT_END(XtExcFrame)
1.3 上下文切换
RT-Thread的libcpu抽象层需要实现三个线程切换相关的函数:
rt_hw_context_switch_to()
:没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用rt_hw_context_switch()
:在线程环境下,从当前线程切换到目标线程rt_hw_context_switch_interrupt ()
:在中断环境下,从当前线程切换到目标线程
线程环境下,如果调用rt_hw_context_switch()
函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换。
在中断处理程序里如果触发了线程的调度,调度函数里会调用 rt_hw_context_switch_interrupt() 触发上下文切换。中断处理程序里处理完中断事务之后,中断退出之前,检查 rt_thread_switch_interrupt_flag
变量,如果该变量的值为 1,就根据 rt_interrupt_from_thread
变量和 rt_interrupt_to_thread
变量,完成线程的上下文切换。
在IDF框架中,整个上下文切换是通过汇编实现的(portasm.S
中的_frxt_dispatch
函数),充当所有上下文切换功能(包括vPortYield()
和vPortYieldFromInt()
(均为汇编),其中vPortYieldFromInt()
通过_frxt_int_exit()
间接调用)的共享退出路径
portasm.S文件相关函数即变量:
变量:
- port_switch_flag:类似rtt中的
rt_thread_switch_interrupt_flag
变量,表示中断返回时需要切换任务函数:
_frxt_setup_switch
:设置port_switch_flag=1_frxt_int_enter
:实现freertos中的XT_RTOS_INT_ENTER
宏(include\freertos\xtensa_rtos.h
),通过_xt_context_save
保存其他尚未保存的中断上下文,只能通过call0从汇编调用,并且需要关闭中断
port_xSchedulerRunning
:调度启动时置1,每个核对应一个port_interruptNesting
:中断嵌套级别,每个核对应一个_frxt_int_exit
:实现freertos中的XT_RTOS_INT_EXIT
宏(include\freertos\xtensa_rtos.h
),如果需要,请调用vPortYieldFromInt()以执行任务上下文切换,还原(可能是)新任务的上下文,然后返回保存在任务堆栈框架中XT_STK_EXIT的出口调度程序。只能通过call0从汇编调用,不会返回到调用者。内部会调用以下函数:
- vPortYieldFromInt:
- _frxt_dispatch:下面
_frxt_timer_int
:实现freertos中的XT_RTOS_TIMER_INT
宏。调用每个计时器中断。管理滴答计时器,每个滴答都调用xPortSysTickHandler()
_frxt_tick_timer_init
:初始化计时器和计时器中断处理程序(已调用_xt_tick_divisor_init()
)_frxt_dispatch
:将上下文切换到优先级最高的就绪任务,还原其状态并向其调度控制。公共调度程序,充当所有上下文切换功能(包括vPortYield()
和vPortYieldFromInt()
)的共享退出路径,所有这些功能都会最终调用此调度程序vPortYield
:执行请求的上下文切换(来自任务),保存挂起任务所需的最小状态,清除CPENABLE,最终调用_frxt_dispatch
执行上下文切换,不会返回vPortYieldFromInt
:执行未经请求的上下文切换(来自中断),保存并清除CPENABLE,最终调用_frxt_dispatch
执行上下文切换,不会返回_frxt_task_coproc_state
:实现freertos中的XT_RTOS_CP_STATE
宏。只能在任务正在运行时调用,而不是在中断处理程序内调用(在这种情况下返回0),只能从汇编调用相关宏:
XT_RTOS_INT_ENTER
宏(_frxt_int_enter
):通知RTOS进入中断处理程序。允许RTOS管理切换到任何系统堆栈并统计嵌套级别
XT_RTOS_INT_EXIT
宏(_frxt_int_exit
):通知RTOS中断处理程序的完成,并将控制权交给RTOS以执行线程/任务调度,从任何系统堆栈切换回并恢复上下文,然后返回保存在堆栈帧中XT_STK_EXIT
的出口调度程序。RTOS端口可以调用_xt_context_restore
来恢复在XT_RTOS_INT_ENTER
中通过_xt_context_save
保存的上下文,而仅剩下一小部分上下文由出口调度程序恢复。此函数不会返回到调用它的位置
XT_RTOS_CP_STATE
宏(_frxt_task_coproc_state
):在a15中返回触发协处理器异常的线程的协处理器状态保存区的基地址,如果没有线程在运行,则返回0。
1.3.1 xtensa中freertos的调度
支持两种主流场景的任务调度:
- 任务主动调度:主要用于当前工作任务结束、等待,或者需要别的事件输入时,任务将主动放弃当前调度并通过FreeRTOS的
TaskYield
处理实现任务调度- vTaskDelay
- 基于中断的调度
- SysTick调度:ESP32保持了一个1000Hz的SysTick中断,除了实现CPU运转Tick计数和基于Tick的某些定时功能外,还可以用来触发产生任务调度。这样即便没有外围其它中断触发,高优先级任务也能再合适的时间得到调度
- 各种中断事件调度:所有中断处理的最后(包括SysTick中断)都会通过
XT_RTOS_INT_ENTER()
和XT_RTOS_INT_EXIT()
函数实现当前任务堆栈的保存和调度切换(根据优先级和当前任务执行时间),这样任何外部事件或者中断触发都可以得到一次任务调度机会
1.3.2 xtensa架构的中断相关知识
xtensa的中断向量:
- _DebugExceptionVector
- xt_debugexception
- _DoubleExceptionVector
- _xt_panic
- _KernelExceptionVector
- _xt_panic
- _UserExceptionVector
- _Level2Vector
- _Level3Vector
- _Level4Vector
- _Level5Vector
- _Level6Vector
- _NMIExceptionVector
注意事项:
- 用户可以通过调用
xt_set_interrupt_handler()
为低级和中级中断安装特定于应用程序的中断处理程序(蓝牙中有使用)(安装到数组_xt_interrupt_table中)- 最后会在汇编宏
dispatch_c_isr
中调用注册的中断 - 汇编宏
dispatch_c_isr
会在1-6级(ESP32中只有3级)中断中调用,会通通过XT_RTOS_INT_ENTER()
和XT_RTOS_INT_EXIT()
函数保护
- 最后会在汇编宏
- 用户还可以通过调用
xt_set_exception_handler()
以相同的方式安装特定于应用程序的异常处理程序(目前貌似还没看到使用)(安装到数组_xt_exception_table中)- 每个处理程序都将指向异常帧的指针(xt_exc_handler)作为其单个参数传递给它。异常帧是在堆栈上创建的,并保存了发生异常的线程上下文。如果处理程序返回,将还原上下文,并重试导致异常的指令
- 最后会在
_xt_user_exc
函数中调用(User exception handler)注册的函数
- 程序跳转时使用
call0
而不是j
指令
1.3.3 rtt中的xtensa架构上下文切换的实现
rtt中xtensa架构的上下文切换的实现主要在libcpu/xtensa
文件夹下,具体来说主要由以下三个函数:
- rt_hw_context_switch:线程间切换
- rt_hw_context_switch_interrupt:中断中切换
- rt_hw_context_switch_to:切换到第一个线程
rt_hw_context_switch:
1 | .global rt_hw_context_switch |
rt_hw_context_switch_interrupt:
1 | .global rt_hw_context_switch_interrupt |
rt_hw_context_switch_to:
1 | .global rt_hw_context_switch_to |
三、其他事项
3.1 控制台移植
控制台只需要实现一个rt_hw_console_output
接口即可,将传入的参数通过串口输出到控制台上。这里比较简单,直接调用IDF中提供的接口函数ets_printf
即可
1 | void rt_hw_console_output(const char *str) |
3.2 内核中的修改部分
- 由于需要兼容IDF框架以及各种组件,尤其是其中的
newlib
组件,需要每个线程中提供一个struct _reent
结构体,故在struct rt_thread
中增加了该成员,并在_rt_thread_init
中对该成员进行了初始化,在rt_thread_detach
和rt_thread_delete
中对该成员进行了销毁。 - 还是newlib库中带来的问题。由于newlib库中需要在整个系统运行前申请一些互斥量或信号量,而此时正由于系统没有起来所以会导致宕机。解决方法是将
rt_system_timer_init
、rt_system_scheduler_init
提前到初始化前,并将rtthread_startup
中相关函数进行注释
附一:xtensa架构上的汇编指令
MOVE指令
- MOVI:12bit常量赋值到寄存器中
- MOVEQZ:为零则赋值
- MOVNEZ:非零则赋值
- MOVLTZ:小于零则赋值
- MOVGEZ:大于等于则赋值
算术指令
- ADD ar, as, at:ar=as+at,无溢出检测,32bit加法
- ADDX2 ar, as, at:ar=(as<<1)+at,无溢出检测,32bit加法
- ADDX4 ar, as, at:ar=(as<<2)+at,无溢出检测,32bit加法
- ADDX8 ar, as, at:ar=(as<<3)+at,无溢出检测,32bit加法
- ADDI at, as, -128-127:at=as+( -128-127),无溢出检测,32bit加法
存储指令
如果启用了区域转换选项(Region Translation Option,Xtensa® Instruction Set Architecture(ISA) Reference Manual第156页)或MMU选项(Xtensa® Instruction Set Architecture(ISA) Reference Manual第158页),则虚拟地址将转换为物理地址。 如果不是,则物理地址与虚拟地址相同。 如果转换或内存引用遇到错误(例如,违反保护或不存在内存),则处理器会引发几种异常之一(Xtensa® Instruction Set Architecture(ISA) Reference Manual第89页的4.4.1.5节)
- S8I at, as, 0-255:将数据从寄存器at存储到虚拟地址中(as+偏移0-255),只存储at中的低8bit
- S16I at, as, 0-510:将数据从寄存器at存储到虚拟地址中(as+偏移0-510),只存储at中的低16bit
- S32I at, as, 0-1020:将数据从寄存器at存储到虚拟地址中(as+偏移0-1020),只存储at中的低32bit
加载指令
加载指令通过添加一个基本寄存器和一个8位无符号偏移形成一个虚拟地址。必要时,这个虚拟地址被翻译成物理地址。然后,该物理地址被用来访问内存系统(通常通过高速缓存)。存储器系统返回一个数据项(根据配置,可以是32、64或128位)。然后,加载指令从该内存项中提取引用的数据,并将结果零扩展或符号扩展写入寄存器中。除非启用不对齐异常选项,否则处理器不会处理不对齐的数据。或当使用了一个错误对齐的地址时,会出现陷阱;相反,它只是简单地加载对齐的数据项。含有计算出的虚拟地址。这就允许漏斗移位器与一个包含计算出的虚拟地址的 对的负载来引用任意字节地址上的数据。
只有loads L32I、L32I.N和L32R可以访问InstRAM和InstROM位置。
- L8UI at, as, 0-255:从虚拟地址as+(0-255)处加载一个无符号8bit数据到at中
- L16SI at, as, 0…510:从虚拟地址as+((0-510)<<1)处加载一个有符号16bit数据到at中
- L16UI at, as, 0…510:从虚拟地址as+((0-510)<<1)处加载一个无符号16bit数据到at中
- L32I at, as, 0-1020:从虚拟地址as+((0-1020)<<2)处加载一个32bit数据到at中
- L32R at, label:
处理器控制指令
RSR:读特殊寄存器
RSR是RSR.*的汇编器宏,它提供了与包含特殊寄存器名称或编号的旧版本指令的兼容性。
- RSR.* at:
- RSR at, *:
- RSR at, 0-255:
由指令字的8位sr字段指定的特殊寄存器的内容被写入地址寄存器’at’。在上述汇编器语法中用特殊寄存器的名称代替’*',并由汇编器翻译到8位sr字段。
WSR:写特殊寄存器
- WSR.* at:
- WSR at, *:
- WSR at, 0-255:
地址寄存器at的内容被写入指令字的8位sr字段指定的特殊寄存器中。在上述汇编器语法中用特殊寄存器的名称代替’*',由汇编器翻译到8位sr字段。
XSR:交换特殊寄存器(RSR+WSR)
其他指令
ENTRY as, 0-32760:
ENTRY是用CALL4,CALL8,CALL12,CALLX4,CALLX8或CALLX12调用的所有子例程的第一条指令。 该指令不能由CALL0或CALLX0调用的例程使用。主要用途:
- 按调用者要求的数量递增寄存器窗口指针(WindowBase)(记录在PS.CALLINC字段中)
- 它将堆栈指针从调用者复制到被调用者,并分配被调用者的堆栈帧。操作数’as’指定了堆栈指针寄存器;它必须指定a0…a3中的一个,否则ENTRY的操作就没有定义。在窗口移动之前,先读取它,减去堆栈框架的大小,然后写入被移动窗口中的as寄存器。
堆栈帧大小被指定为8位字节的12位无符号imm12字段。大小为零扩展,向左移动3,然后从调用者的堆栈指针中减去以获取被调用者的堆栈指针。因此,最多可以指定32760字节的堆栈帧。初始堆栈帧大小必须为常数,但随后可以使用MOVSP指令在堆栈上分配动态大小的对象,或进一步扩展大于32760字节的恒定堆栈帧。
RSIL at, 0-15:
首先读取PS寄存器(程序状态寄存器),将参数0-15写到寄存器at中,然后设置PS.INTLEVEL为该值,在PS.INTLEVEL级别及以下的中断将被禁用。
附二:xtensa架构上相关寄存器说明
PS寄存器
PS(Miscellaneous Program State Register)寄存器,杂项程序状态寄存器,复位默认PS.INTLEVEL为15,PS.EXCM为1,其他字段为零(Region Translation Option,Xtensa® Instruction Set Architecture(ISA) Reference Manual第5.3.5节“处理器状态特殊寄存器”详细描述了该寄存器的字段)
- INTLEVEL:中断等级禁用选项,用于设置处理器当前中断级别(中断选项),在PS.INTLEVEL级别及以下的中断将被禁用。
- EXCM:异常模式(异常选项)
- 0:正常运行
- 1:异常模式
- UM:用户向量模式(异常选项)
- 0:内核向量模式——异常不需要切换堆栈
- 1:用户向量模式——异常情况下需要切换堆栈
- RING:特权级别(MMU选项)
- OWB:Old window base(窗口寄存器选项),窗口溢出或下溢前的WindowBase值。
- CALLINC:调用增量(窗口寄存器选项),由CALL指令设置窗口增量。由ENTRY用于旋转窗口。
- WOE:窗口溢出检测启用(窗口寄存器选项),用于计算当前窗口溢出的启用情况(4.4.1.4节)