QEMU OpenSBI 裸机开发之定时器中断
1. 中断处理准备
在s模式下的中断设置和处理逻辑和原来的基本一致,首先在entry.S中添加中断处理汇编入口,如下所示:
# # supervisor trap entry. # .globl supervisor_trap_entry .align 4 supervisor_trap_entry: # sscratch holds smode trap stack address, # swap current sp and smode trap stack address csrrw sp, sscratch, sp // call the C trap handler in trap.c call supervisor_trap # restore old sp value csrrw sp, sscratch, sp sret
这里就是设置好中断栈,然后跳转到c函数处理,supervisor_trap和之前的也一样,定义在trap.c中,如下所示。
#include "csr.h" #include "sbi.h" #include "timer.h" #include "printf.h" #define S_SOFT_INT (1) #define M_SOFT_INT (3) #define S_TIMER_INT (5) #define M_TIMER_INT (7) #define S_EXT_INT (9) #define M_EXT_INT (11) #define INSTRUCTION_ADDR_MISALIGNED (0) #define INSTRUCTION_ACCESS_FAULT (1) #define ILLEGAL_INSTRUCTION (2) #define BREAK_POINT (3) #define LOAD_ADDR_MISALIGNED (4) #define LOAD_ACCESS_FAULT (5) #define STORE_ADDR_MISALIGNED (6) #define STORE_ACCESS_FAULT (7) #define ECALL_FROM_UMODE (8) #define ECALL_FROM_SMODE (9) #define ECALL_FROM_MMODE (11) #define INSTRUCTION_PAGE_FAULT (12) #define LOAD_PAGE_FAULT (13) #define STORE_PAGE_FAULT (15) static char *interrupt_cause[] = { "Reserved", "Supervisor software interrupt", "Reserved", "Machine software interrupt", "Reserved", "Supervisor timer interrupt", "Reserved", "Machine timer interrupt", "Reserved", "Supervisor external interrupt", "Reserved", "Machine external interrupt", "Reserved", "Reserved", "Reserved", "Reserved" }; static char *exception_cause[] = { "Instruction address misaligned", "Instruction access fault", "Illegal instruction", "Breakpoint", "Load address misaligned", "Load access fault", "Store/AMO address misaligned", "Store/AMO access fault", "Environment call from U-mode", "Environment call from S-mode", "Reserved", "Environment call from M-mode", "Instruction page fault", "Load page fault", "Reserved", "Store/AMO page fault" }; void supervisor_trap(void) { unsigned long tval = stval_get(); unsigned long sepc = sepc_get(); unsigned long cause = scause_get(); int is_int = (cause & (1l << 63l)) ? 1 : 0; int scode = cause & 0xff; if (scode >= 16) { printf("%s : %s.\r\n", is_int ? "Interrupt" : "Exception", "Unknown code"); return; } if (is_int) { printf("Interrupt : %s.\r\n", interrupt_cause[scode]); switch (scode) { case S_SOFT_INT: // acknowledge the software interrupt by clearing // the SSIP bit in sip. sip_set(sip_get() & (~SIP_SSIP)); break; case S_TIMER_INT: // acknowledge the timer interrupt by set new val. sbi_set_timer(mtime_get() + TIMER_CLK_RATE); break; } } else { printf("Exception : %s.\r\n", exception_cause[scode]); switch (scode) { case ILLEGAL_INSTRUCTION: printf("tval = %p\r\n", tval); printf("sepc = %p\r\n", sepc); break; } sepc_set(sepc + 4); } return; }
在使用中断之前需要设置好中断相关寄存器,这是在start.c中处理的,逻辑和原来的基本一样,如下所示。
static void supervisor_trap_init(void) { //this stack is used for supervisor_trap_entry in entry.S sscratch_set((unsigned long)(__stack_start + 4096 * 2)); // set the supervisor trap handler. stvec_set((unsigned long)supervisor_trap_entry); // enable supervisor interrupts. sstatus_set(sstatus_get() | SSTATUS_SIE); // enable supervisor timer and soft interrupts. sie_set(sie_get() | SIE_STIE | SIE_SSIE); }
2. 设置定时器
在start.c中,通过SBI接口来设置定时器比较器的值,如下所示。
void start(void) { printf("%s %d.\r\n", __func__, __LINE__); supervisor_trap_init(); sbi_set_timer(mtime_get() + TIMER_CLK_RATE); }
这里我们读取定时器的当前值用了一个新的接口mtime_get,定时器的值可以通过MMIO映射的寄存器来访问,也可以通过csr指令来访问,用csr指令访问的汇编代码如下。
static inline unsigned long mtime_get(void) { unsigned long val; asm volatile("csrr %0, time" : "=r" (val) ); return val; }
sbi_set_timer这个函数的参数是要设置的比较器新值,所以需要将当前值先读取出来然后加上一个间隔值,这里加上的是定时器工作频率值,也就是设置1s产生1个中断。
在定时器中断处理中我们需要再次调用sbi_set_timer来设置新的值,这样定时器就可以每隔1s产生一个中断,如下所示。
case S_TIMER_INT: // acknowledge the timer interrupt by set new val. sbi_set_timer(mtime_get() + TIMER_CLK_RATE); break;
在RISCV中,定时器的中断是通过设置新的值来清除的,如果不想下次产生中断,可以设置一个比当前定时器值小的值来实现。
3. 测试
在命令行执行“make qemu”,可以每隔1s打印一条s模式定时器中断信息,如下所示。
gewenbin@gewenbin-virtual-machine:~/Desktop/qemu_test/lesson14$ make qemu riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o trap.o trap.c riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel.ld -o kernelimage entry.o start.o printf.o trap.o riscv64-unknown-elf-objdump -S kernelimage > kernel.asm riscv64-unknown-elf-objdump -t kernelimage | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel.sym qemu-system-riscv64 -machine virt -bios fw_jump.elf -kernel kernelimage -m 128M -smp 1 -nographic OpenSBI v0.8-81-g7dcb1e1 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemu Platform Features : timer,mfdeleg Platform HART Count : 1 Firmware Base : 0x80000000 Firmware Size : 112 KB Runtime SBI Version : 0.2 Domain0 Name : root Domain0 Boot HART : 0 Domain0 HARTs : 0* Domain0 Region00 : 0x0000000080000000-0x000000008001ffff () Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X) Domain0 Next Address : 0x0000000080200000 Domain0 Next Arg1 : 0x0000000082200000 Domain0 Next Mode : S-mode Domain0 SysReset : yes Boot HART ID : 0 Boot HART Domain : root Boot HART ISA : rv64imafdcsu Boot HART Features : scounteren,mcounteren,time Boot HART PMP Count : 16 Boot HART PMP Granularity : 4 Boot HART PMP Address Bits: 54 Boot HART MHPM Count : 0 Boot HART MHPM Count : 0 Boot HART MIDELEG : 0x0000000000000222 Boot HART MEDELEG : 0x000000000000b109 start 29. Interrupt : Supervisor timer interrupt. Interrupt : Supervisor timer interrupt. Interrupt : Supervisor timer interrupt. Interrupt : Supervisor timer interrupt. Interrupt : Supervisor timer interrupt.
4. 工程源码
工程源码:链接:https://pan.baidu.com/s/1TnTYr7mywdKj5bxpdmWnyA,提取码:q772,见lesson14。
评论