opensbi下的riscv64裸机编程2(中断与异常)
1.本文说明
2.riscv特权模式下的异常
2.1 CSPs
2.2 异常开关的寄存器
2.3 与中断相关的指令
3.中断测试
3.1 设置中断向量表
3.2 开启中断设置
3.3 初始化timer
3.4 开启中断
3.5 中断处理
4.测试及校验
5.总结
1.本文说明
任何时候,中断和异常的产生都是十分值得关注的,这些将破坏程序原有的执行逻辑。按照芯片的设计来说,中断和异常大致上可以分为三类异常(Exception)、陷入(Trap)、外部中断(Interrupt)。
异常(Exception)
在一条指令执行的过程中发生了错误,可以通过异常处理函数进行处理,最常见的异常包括无效的内存地址访问、非法指令异常、缺页异常等等。当发生这些异常后可以进行处理。
陷入(Trap)
主动的让其进入异常处理函数,常见的是系统调用syscall。而在riscv上为ecall或者进入断点的ebreak。
外部中断(Interrupt)
一般由外部事件触发,比如定时器中断、GPIO中断等。这些异常是不可预知的。
对于一般的中断处理流程,进入中断后需要进行上下文的保存与恢复。
2.riscv特权模式下的异常
涉及到中断和异常,RISCV的特权模式是不能绕开的。在RISCV中,无论在任何模式发生的异常,硬件线程都会将控制权交给M-Mode的异常处理程序。然而对于类Unix的操作系统来说,异常都是由操作系统来处理。而操着系统运行的模式是S-Mode,所以RISCV也可以选择将异常重新导向到S-Mode,也支持异常委托机制(Machine Interrupt Delegaintion)将异常直接通过S-Mode进行处理,这样可以大大的增加操作系统的灵活性。

一般来说M-Mode是必须实现的,S-Mode也一般会有,而U-Mode是选择性扩展的。目前的RISCV芯片中例如蜂鸟的E203与K210都只支持了RISCV架构中的Machine Mode。
2.1 CSPs
实际上RISCV在实现系统指令集的时候,是支持多种模式的扩展的,这一系列的指令集通过Control and Status Registers (CSRs)来进行控制。
CSR地址是扩展了12位,也就是可以设计最大4096个指令。

通过下面的网站可以看到当前CSRs的实现状态。
http://www.five-embeddev.com/quickref/csrs.html
这里只针对S-Mode下的异常处理进行分析,M-Mode下的异常处理类似。
Name |
Number |
Feature/Extensions |
Description |
---|
sepc |
0x0141 |
supervisor |
Supervisor Exception Program Counter |
scause |
0x0142 |
supervisor |
Supervisor Exception Cause |
stval |
0x0143 |
supervisor |
Supervisor bad address or instruction. |
stvec |
0x0105 |
supervisor |
Supervisor Trap Vector Base Address |
sstatus |
0x0100 |
supervisor |
Supervisor Status |
Supervisor Exception Program Counter (sepc)
当中断发生时,存放需要跳转的PC值。这里需要利用stvec提供中断向量表的基地址。

该寄存器的值是在32位下是4字节对齐的。
Supervisor Cause Register (scause)
该寄存器表示中断发生的原因。下面的表格中表述了中断的发生原因:
Interrupt |
Exception Code |
Description |
---|
1 |
0 |
Reserved |
1 |
1 |
Supervisor software interrupt |
1 |
2–4 |
Reserved |
1 |
5 |
Supervisor timer interrupt |
1 |
6–8 |
Reserved |
1 |
9 |
Supervisor external interrupt |
1 |
10–15 |
Reserved |
1 |
≥16 |
Designated for platform use |
0 |
0 |
Instruction address misaligned |
0 |
1 |
Instruction access fault |
0 |
2 |
Illegal instruction |
0 |
3 |
Breakpoint |
0 |
4 |
Load address misaligned |
0 |
5 |
Load access fault |
0 |
6 |
Store/AMO address misaligned |
0 |
7 |
Store/AMO access fault |
0 |
8 |
Environment call from U-mode |
0 |
9 |
Environment call from S-mode |
0 |
10–11 |
Reserved |
0 |
12 |
Instruction page fault |
0 |
13 |
Load page fault |
0 |
14 |
Reserved |
0 |
15 |
Store/AMO page fault |
0 |
16–23 |
Reserved |
0 |
24–31 |
Designated for custom use |
0 |
32–47 |
Reserved |
0 |
48–63 |
Designated for custom use |
0 |
≥64 |
Reserved |
Supervisor Trap Value (stval) Register
由于scause不足以表示异常发生的所有信息,比如发生了缺页异常,就会将stavl设置成需要访问但是不在内存中的地址。以便于操作系统将这个地址加载进来。
Supervisor Trap Vector Base Address Register (stvec)
设置中断处理的基地址,同时设置模式

对于基地址的模式有如下两种:
Value |
Name |
Description |
---|
0 |
Direct |
All exceptions set pc to BASE. |
1 |
Vectored |
Asynchronous interrupts set pc to BASE+4×cause. |
≥2 |
— |
Reserved |
Direct:顾名思义,当异常发生的时候,每次都会跳转到这个地址,然后通过这个地址的中断处理程序去判断哪种中断。
Vectored:在这种模式下,会跳转到BASE + 4 * cause 进行处理流程。每种异常的cause都不一样。
Supervisor Status Register (sstatus)
控制中断的状态等等,也可以控制全局中断的时能等等。

SIE域表示全局中断使能。当该MIE域值为1时,表示所有中断的全局开关打开,当MIE域的值为0时候,表示全局关闭所有中断。
SPIE用于保存进入异常之前MIE域的值。
2.2 异常开关的寄存器
对于S-Mode中断的Enable与Pending,还需要关注两个寄存器。sie与sip。
Supervisor Interrupt Enable(sie)

Supervisor Interrupt Pending(sip)

可以看到有三种类型的中断,由芯片厂家进行自定义设计。
Supervisor software interrupt
Supervisor timer interrupt
Supervisor external interrupt
2.3 与中断相关的指令
CSR Read Write(csrrw)
csrrw dst, csr, src:将指定的CSR寄存器写入dst,同时将src的值写入CSR。
CSR Read(csrr)
csrr dst,csr:读一个CSR寄存器到dst。
CSR Clear(csrc)
csrc(i) csr, rs1:将指定的位清零。
CSR Set(csrs)
csrs(i) csr, rs1:将指定的位置一。
3.中断测试
由于在qemu上,中断的产生可以通过定时器来发生,所以需要理解riscv上对timer的使用。timer又需要通过sbi的接口进行访问。
相关的代码文件可以参考:
https://github.com/bigmagic123/riscv64_opensbi_baremetal/tree/master/03_interrupt
已经实现了timer中断的产生过程。
3.1 设置中断向量表
本程序需要设置中断向量表,前面提到过,中断向量的跳转有两种模式:Direct与Vectored。Direct可以直接转到固定的pc地址,然后由统一的入口进行处理,这种比较容易实现,所以设置为这种模式。
.global table_val_set table_val_set: la t0, trap_entry csrw stvec, t0 jr ra
直接将trap_entry函数的入口写到stvec的寄存器中。由于函数地址4字节对其,所以设置后模式为Direct。
3.2 开启中断设置
要开启时钟中断,这样才能产生时钟,而根据手册,开启时钟中断实际上是设置Supervisor Interrupt Enable(sie),也就是设置SIE的寄存器开启。

所以只需要设置即可。
void enable_timer_interrupt(void) { w_sie(r_sie() | SIE_STIE); }
3.3 初始化timer
对于timer的填充,其实就是设置中断的值。当timer达到设定的值后会产生中断。
void set_timer(uint64 stime_value) { SBI_TIMER(stime_value); } // get current time uint64 get_cycle() { return r_time(); } void clock_set_next_event() { set_timer(get_cycle() + TIMEBASE); }
函数填充了下一个tick的值。
3.4 开启中断
中断的开启通过sstatus全局的状态寄存器设置。

通过设置SIE位就可以达到使能或者关闭中断的作用。
void interrupt_enable(void) { w_sstatus(r_sstatus() | SSTATUS_SIE); }
3.5 中断处理
中断处理需要保存当前的上下文寄存器(寄存器压栈操作),然后跳转到中断处理函数去处理具体的中断。当处理完成之后返回现场(寄存器出栈)。
这里先不做这么复杂的工作,中断产生后直接跳转到中断处理函数中,只执行一次。
.global trap_entry trap_entry: csrr a0, scause csrrc a1, stval, zero csrr a2, sepc mv a3, s0 /* scause, stval, sepc, sp */ call handle_trap
其中a0为第一个参数,保存中断发生的原因。
a1是中断发生的具体信息。
a2表示了中断异常返回值。
然后进入hande_trap。
uintptr_t handle_trap(uintptr_t scause, uintptr_t stval, uintptr_t sepc, uintptr_t sp) { tfp_printf("handle_trap %08lx:%08lx:%08lx:%08lx ", scause, stval, sepc, sp); while (1); return 0; }
4.测试及校验
因为工程文件的增加,所以使用了Makefile进行工程的构建工作。
%.o: %.c %.s $(CC) $(CFLAGS) -c $< -o $@
Makefile的语法规则基本
TARGET … : DEPENDENCIES … COMMAND
这里也不过多的涉及了。
输入make后,在fw_bin目录下执行run.sh脚本即可。

最后可以看到中断的原因

最高位是8,相应的中断描述为Supervisor timer interrupt。
5.总结
riscv的异常和中断的处理模式在M-Mode或者S-Mode下都可以设计,具体要看芯片的设计方式,如果设计在M-Mode,对于操作系统来说,可以通过转发或者代理给S-Mode的操作系统,如果S-Mode存在中断处理,那么处于S-Mode的系统可以直接处理,这样比较简洁。
责任编辑:xj
原文标题:opensbi下的riscv64裸机编程2(中断与异常)
文章出处:【微信公众号:嵌入式IoT】欢迎添加关注!文章转载请注明出处。
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉