QEMU裸机开发之M模式定时器中断
定时器相关的寄存器也是定义在clint中的,再来看下寄存器映射,如下所示。
同样的我们只关心第一个核的寄存器,和定时器相关的总共就两个寄存器,“mtime”寄存器相当于计数器,从系统上电开始就一直增加,“mtimecmp”是比较寄存器,当mtime的值增加到和其一样时就可以产生中断。我们在“clint.c”中添加两个接口操作定时器,如下所示。
#include "address.h" // core local interruptor (CLINT), which contains the timer. #define CLINT_MSIP (CLINT_REG_BASE + 0x0) #define CLINT_MTIMECMP (CLINT_REG_BASE + 0x4000) #define CLINT_MTIME (CLINT_REG_BASE + 0xBFF8) // cycles since boot. void msoftint_make(void) { *(volatile unsigned int*)CLINT_MSIP = 1; } void msoftint_clear(void) { *(volatile unsigned int*)CLINT_MSIP = 0; } unsigned long timer_get(void) { return (*(volatile unsigned long*)CLINT_MTIME); } void timer_set(unsigned long interval) { *(volatile unsigned long*)CLINT_MTIMECMP = interval; }
对定时器操作需要知道其输入时钟频率,QEMU中定时器的工作频率大概是10MHz,所以我们在“clint.h”中定义这个频率宏,如下所示。
#define TIMER_CLK_RATE (10 * 1000 * 1000) //10MHz
在“start.c”中添加设置比较器的代码,如下所示。
void start(void) { printf("%s %d.\r\n", __func__, __LINE__); machine_trap_init(); //msoftint_make(); timer_set(timer_get() + TIMER_CLK_RATE); }
将当前时间加上定时器的时钟频率,也就是在1s后产生中断的意思,同样的,在中断处理中,我们需要更新比较寄存器,以让定时器持续每隔1s产生一个中断,如下所示。
void machine_trap(void) { unsigned long cause = mcause_get(); unsigned long mepc = mepc_get(); unsigned long tval = mtval_get(); int is_int = (cause & (1l << 63l)) ? 1 : 0; int mcode = cause & 0xff; if (mcode >= 16) { printf("%s : %s.\r\n", is_int ? "Interrupt" : "Exception", "Unknown code"); return; } if (is_int) { printf("Interrupt : %s.\r\n", interrupt_cause[mcode]); switch (mcode) { case M_SOFT_INT: msoftint_clear(); break; case M_TIMER_INT: timer_set(timer_get() + TIMER_CLK_RATE); break; } } else { printf("Exception : %s.\r\n", exception_cause[mcode]); switch (mcode) { case ILLEGAL_INSTRUCTION: printf("tval = %p\r\n", tval); printf("mepc = %p\r\n", mepc); break; case ECALL_FROM_SMODE: break; } mepc_set(mepc + 4); } return; }
在命令行执行“make qemu”,可以看到每隔1s打印一条定时器中断信息,如下所示。
gewenbin@gewenbin-virtual-machine:~/Desktop/qemu_test/lesson6$ make qemu riscv64-unknown-elf-gcc -c -o entry.o entry.S 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 start.o start.c 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 uart.o uart.c 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-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 clint.o clint.c riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel.ld -o kernelimage entry.o start.o uart.o trap.o clint.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 none -kernel kernelimage -m 128M -smp 1 -nographic start 28. Interrupt : Machine timer interrupt. Interrupt : Machine timer interrupt. Interrupt : Machine timer interrupt. Interrupt : Machine timer interrupt. Interrupt : Machine timer interrupt.
工程源码:链接:https://pan.baidu.com/s/1TnTYr7mywdKj5bxpdmWnyA,提取码:q772,见lesson6。
评论