1.RISC-V 中断(Interrupt)的分类
- 本地中断 Local
- 软中断 software-interrupt
- 定时器中断 timer-imterrupt
- 全局中断 Global
- 外设中断 externle interrupt
2.RISC-V 中断编程中涉及的寄存器
上节课,我们知道了:
- mtvec
- mepc
- mcause
- mtval
- mstatus
- mscratch
只有mie
和mip
没有讲
-
mie
寄存器:之前mstatus的mie是全局中断的使能位,如果全局中断关了,那么所有的关了。如果全局中断是开着的,由mie
寄存器进行更精细的中断管理. -
mip
寄存器:记录当前这种中断是否发生了,1表示发生了
3.RISC-V 中断处理流程
4.PLIC 介绍
- 外部中断,也就是外设的中断——如USB(鼠标、键盘),磁盘管理 等
- 如果所有的外设都用芯片的一个单独的引脚去处理,会非常麻烦,所以引入了PLIC
- PLIC:平台级别的中断控制器
- 左边有很多很多外设接到PLIC,右边针对一个hart,只有一个引脚,所以功能就是,只要有一个中断,那么就触发CPU的中断,而且如果有多个中断,当前时刻只会让一个中断进来,对左边的中断进行协调.
中断源:Interrupt Source (串口设备UART也是一个中断源,这里定义的是10)
- PLIC本身也是一个外设,我们也是用寄存器去操纵这个外设
优先级 Priority
:
记录某路中断源是否发生:Pending
记录某路中断源是否开了中断:Enable
设置中断发生的阈值,如果该中断源的优先级<=阈值,就会屏蔽:Threshold
获取当前处理的中断/消除这个中断 : Cliam/Complete
:
5.采用中断方式从 UART 实现输入
如何在控制台上敲击键盘,然后屏幕上显示出来呢?
当敲击键盘时,触发外部中断(externel trap),然后去执行那个外部中断的处理函数,在处理函数中会执行uart_put
去将这个字符写入到串口中,串口我们前面知道是用轮询的方式去检查是否有字符待处理,所以这样就可以去将字符展示在控制台上.
总的中断处理函数
:
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
reg_save t6
csrw mscratch, t6
# call the C trap handler in trap.c
csrr a0, mepc
csrr a1, mcause
call trap_handler
# trap_handler will return the return address via a0.
csrw mepc, a0
# restore context(registers).
csrr t6, mscratch
reg_restore t6
# return to whatever we were doing before trap.
mret
复制代码
在这里可以看到,中断发生时,去调用trap_handler
函数,如下:
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
if (cause & 0x80000000) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
uart_puts("software interruption!\n");
break;
case 7:
uart_puts("timer interruption!\n");
break;
case 11:
uart_puts("external interruption!\n");
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}
return return_pc;
}
复制代码
11是外部中断,所以当我们敲击键盘时,执行case11,所以执行external_interrupt_handler
,如下:
void external_interrupt_handler()
{
int irq = plic_claim(); // 获取一个中断源的编号
if (irq == UART0_IRQ){ // 如果是uart产生的中断,那么就去写到uart中
uart_isr();
} else if (irq) {
printf("unexpected interrupt irq = %d\n", irq);
}
if (irq) { // 如果是一个中断源,那么就通知这个中断结束了,解决了
plic_complete(irq);
}
}
复制代码
最后,我们来看看uart_isr
是如何将字符写到uart的.
/*
* handle a uart interrupt, raised because input has arrived, called from trap.c.
*/
void uart_isr(void)
{
while (1) {
int c = uart_getc(); // 从uart中得到从键盘敲入的字符
if (c == -1) {
break;
} else {
uart_putc((char)c); // 输出这个字符
uart_putc('\n');
}
}
}
复制代码
所以,在这里我们将敲入的字符输出去了.