opensbi下的riscv64裸机编程:中断与异常

描述

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进行处理,这样可以大大的增加操作系统的灵活性。

RISC-V

一般来说M-Mode是必须实现的,S-Mode也一般会有,而U-Mode是选择性扩展的。目前的RISCV芯片中例如蜂鸟的E203与K210都只支持了RISCV架构中的Machine Mode。

2.1 CSPs

实际上RISCV在实现系统指令集的时候,是支持多种模式的扩展的,这一系列的指令集通过Control and Status Registers (CSRs)来进行控制。

CSR地址是扩展了12位,也就是可以设计最大4096个指令。

RISC-V

通过下面的网站可以看到当前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提供中断向量表的基地址。

RISC-V

该寄存器的值是在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)

设置中断处理的基地址,同时设置模式

RISC-V

对于基地址的模式有如下两种:

 

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)

控制中断的状态等等,也可以控制全局中断的时能等等。

RISC-V

SIE域表示全局中断使能。当该MIE域值为1时,表示所有中断的全局开关打开,当MIE域的值为0时候,表示全局关闭所有中断。

SPIE用于保存进入异常之前MIE域的值。

2.2 异常开关的寄存器

对于S-Mode中断的Enable与Pending,还需要关注两个寄存器。sie与sip。

Supervisor Interrupt Enable(sie)

RISC-V

Supervisor Interrupt Pending(sip)

RISC-V

可以看到有三种类型的中断,由芯片厂家进行自定义设计。

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的寄存器开启。

RISC-V

所以只需要设置即可。

 

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全局的状态寄存器设置。

RISC-V

通过设置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阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分