这一章的中断处理,讲的是处理外部中断。在中断处理中涉及到外设PLIC外部中断控制器。以及用于开启全局中断的CSR状态寄存器mstatus和mie。
一、mstatus和mie状态寄存器
MCU的全局中断开关由mstatus中的第3位MIE控制,为1使能全局中断。
在全局中断开启的情况下,可以通过mie寄存器更加细粒度的控制中断的使能。 中断共分类三种类型,分别是:外部中断、定时器中断、软件中断。分别用E、T、S来表示。mie寄存器如下图所示,想要开启外部中断,需要使第11位MEIE的值为1。
在代码中体现如下:
csrr t0, mstatus ori t0, t0, 0x8 csrw mstatus, t0 csrr t0, mie li t1, 0x800 or t0, t0, t1 csrw mie, t0
二、PLIC平台级中断控制器(外部中断控制器)
PLIC控制中断的大体流程是:
1、PLIC的Gateway根据当下有没有正在处理的中断,决定要不要处理下一个优先级最高的中断请求。
2、响应某个中断请求后,判断中断优先级若为0,则不处理。
3、若判断该类型中断请求未使能,不处理。
4、若中断优先级小于设置阈值,不处理。
5、设置pending位,示意MCU中断的到来。
更具体的流程可查看:PLIC基础知识
PLIC外设需要了解四个寄存器分别是中断优先级控制寄存器(地址:0x0c000000)、中断使能寄存器(地址:0x0c002000)、优先级阈值寄存器(地址:0x0c200000)、中断claim_complete寄存器(地址:0x0c200004)。
我们需要通过基地址(通常为0x0c000000)以及偏移地址设置这些寄存器的值。通常优先级设置为除0外的任何值,中断使能需打开,优先级阈值设置为0。设置代码如下:
void PLIC_SetPriority(uint32_t source, uint32_t priority) { volatile uint32_t *priority_reg = (uint32_t *)((PLIC_BASE) + PLIC_PRIORITY_OFFSET + \ + (source << PLIC_PRIORITY_SHIFT_PER_SOURCE)); *priority_reg = priority; } void PLIC_EnableInterrupt(uint32_t source) { volatile uint32_t *enable_reg = (uint32_t *)((PLIC_BASE) + PLIC_ENABLE_OFFSET + \ + (source >> 5) * 4); uint32_t current = *enable_reg; current = current | (1<<(source&0x1F)); *enable_reg = current; } void PLIC_DisableInterrupt(uint32_t source) { volatile uint32_t *enable_reg = (uint32_t *)((PLIC_BASE) + PLIC_ENABLE_OFFSET + (source >> 5) * 4); uint32_t current = *enable_reg; current = current & (~(1<<(source&0x1F))); *enable_reg = current; } void inline PLIC_SetThreshold(uint32_t thresh) { volatile uint32_t *thresh_reg = (uint32_t *)((PLIC_BASE) + PLIC_THRESHOLD_OFFSET); *thresh_reg = thresh; }
PLIC通知MCU中断到来后,会在claim_complete寄存器指示中断类型。用户需要读取这个寄存器的值表示正在处理中断。处理完成后,需要把对应类型值(中断请求号)写入该寄存器,表示完成中断处理。以便PLIC可以处理下一个中断。对应写入读取代码如下:
uint32_t PLIC_ClaimInterrupt(void) { volatile uint32_t *claim_reg = (uint32_t *)((PLIC_BASE) + PLIC_CLAIM_OFFSET); return (*claim_reg); } void PLIC_CompleteInterrupt(uint32_t source) { volatile uint32_t *complete_reg = (uint32_t *)((PLIC_BASE) + PLIC_CLAIM_OFFSET); *complete_reg = source; }
PLIC寄存器的详细信息请查看:RISC-V SiFive U54内核——PLIC平台级中断控制器
三、中断处理
在中断到来时,程序会跳转到共用的处理函数入口,进行trap处理。我们需要根据claim_complete寄存器中指示的中断类型分别做出不同的响应。最后记得把中断号写回寄存器。
int read_external_irq()
{
return PLIC_ClaimInterrupt();
}
void external_interrupt_handler()
{
int irq=read_external_irq();
if(irq == UART1_IRQ){
printf("UART1 INTERUPPT\n");
uart_isr();
}
else if(irq==GPIO){
printf("GPIO INTERUPPT\n");
}
else{
printf("unexpected interrupt irq = %d\n",irq);
}
PLIC_CompleteInterrupt(irq);
}
reg_t trap_handler(reg_t epc,reg_t cause){
reg_t return_pc = epc;
reg_t cause_code = cause & MCAUSE_MASK_ECODE;
Uart_puts("traps!\n");
if(cause & MCAUSE_MASK_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:
printf("Unknown async exception! Code = %ld\n", cause_code);
break;
}
}else {
/* Synchronous trap - exception */
printf("Sync exceptions! Code = %ld\n", cause_code);
panic("OOPS! What can I do!");
return_pc += 4;
}
return return_pc;
}