这次我们介绍的功能是PLIC中断。
首先我们先看一下指令集《The RISC-V Instruction Set Manual Volume II: Privileged Architecture Privileged Architecture Version 1.10》是如何定义的。
文档链接:https://riscv.org/specifications/privileged-isa/
PLIC是全局的中断单元,用于I/O设备,入口可以很多个,但上报给CORE的只有一个,也就是多个中断来临时,是有优先级限制的。上图的红色箭头是一些使能位置,后面我会一个个进行说明。
协议中的7.5节规定了ID 0是没有中断的含义,且中断ID值从1开始,当多个优先级相同的中断来临时,ID越小的优先级越高,关于优先级的说明后面会详细分析。
更多关于PLIC的协议内容,大家可以直接查看指令集,我就不在这里说了。
现在我们参考SiFive E3的使用文档来使用rocket-chip的PLIC模块。
首先先看一下rocket-chip的PLIC模块的物理地址,如下图。
可以看出是物理地址是对得上的,那我们直接看E3怎么用就是了。
下面看的是E3关于PLIC的寄存器内容。
具体的寄存器说明大家看文档吧。需要注意的是,pending相关的寄存器是只读的,priorities & enables相关的寄存器复位后为x值,priority thresholds寄存器 为零时允许所有具有非零优先级的中断,而值为7则屏蔽所有中断。claim process & completion寄存器是同一个寄存器。读取0表示没有中断悬挂。非零就说明有中断,且记录当前中断产生中优先级最高的中断ID。对该寄存器写入当前产生中断中最高的中断ID,则说明系统已知道该中断发生,且已处理或丢弃该中断。
接下来介绍的是与中断相关的CSR寄存器。
mie CSR寄存器,用于管理各种中断的使能,mip CSR寄存器用于管理各种中断悬挂的情况,mstatus CSR寄存器是中断的总开关,也记录中断来临时系统的特权模式,mcause CSR寄存器是记录异常和中断的内容,还有一个是mtvec CSR寄存器,这里没有贴出来,是用于设置中断处理函数的入口,如果看过我博客的内容,这个入口就是trap_entry函数的起始地址,是在底层汇编*.S中进行配置的。
https://blog.csdn.net/a_weiming/article/details/89006615
从mie中看到,PLIC中断模块使能是MEIE bit,即使PLIC模块内部有很多中断源,但从CORE来看也是只有一个入口。PLIC模块中断悬挂可以查看mip的MEIP bit。mstatus中的MIE是整个CPU的中断总开关,不开的话,全部中断都不会上报,这个开关是最后开的。
看完一些说明内容,下面我们将介绍PLIC的使用流程。
PLIC中断的优先级,PLIC优先级符合以下规律。
- 相同优先级的情况下,ID越小优先级越高,即ID1和ID15的中断源优先级均为7,中断同时来临时,先响应ID1中断。
- 不同优先级的情况下,按优先级来处理,优先级配置为0~7,即ID1中断源优先级为1,ID15中断源优先级为4,两中断同时来时,先响应ID15中断。
- 中断源的优先级要高于priority thresholds设置的值,不然该中断不会上报。
PLIC中断的使用开关。
- 各外设模块的中断使能开关。
- PLIC总的阈值寄存器priority thresholds可以配置为0,但各个ID中断源的优先级(priorities)必须配置优先级大于1,不然不会上报中断,理由是:priority thresholds寄存器为零时允许所有具有非零优先级的中断上报,如果中断源的优先级为0,就不会上报,所以优先级是第一季使能开关,对应下图Priority的红色箭头。
- PLIC的enables寄存器,使能各ID中断源,允许中断进入PLIC模块,对应下图IE的红色箭头。
- PLIC各ID中断源优先级高于priority
thresholds的值,允许中断传到CORE中,对应下图Threshold的箭头。 - 出了PLIC后是CORE里面的CSR寄存器,首先是mie寄存器,使能MEIE bit,允许PLIC上报模块中断。
- 最后是mstatus中的MIE bit,允许CORE产生中断,对应下图最右边的箭头。
PLIC中断配置的建议。
- 复位各ID中断源优先级,复位各ID中源使能信号。
- 配置PLIC模块的优先级阈值。
- 配置各ID中断源的优先级。
- 配置各ID中断源的使能。
- 配置mie.MEIE使能。
- 配置mstatus.MIE使能。
- 等待中断。
- 读取PLIC当前中断中最有优先级的ID源。
- 进入中断处理函数,关闭外设模块中断信号。
- 中断函数处理完后,请出PLIC中断源的ID。
PLIC中断的特殊说明。
- 中断源可以使电平,也可以是脉冲,只要hold timing符合要求即刻。
- 中断源ID 0虽然不产生中断,但它对应的寄存器位置是存在的,所以配置时要注意。
- 当读了claim process & completion寄存器后,pending对应位置的位会被拉低,也就是说PLIC认为你读claim process & completion时已经知道该中断源被悬挂过。
下面看的是我写的测试代码。
#include "encoding.h"
#define U32 *(volatile unsigned int *)
#define DEBUG_SIG 0x70000000
#define DEBUG_VAL 0x70000004
#define PLIC_BASE 0x0C000000
void handle_trap();
void csr_cfg();
void PLIC_init_cfg(unsigned int num, unsigned int threshold);
void PLIC_id_clr(unsigned int id);
void PLIC_sourceX_cfg(unsigned int num, unsigned int priority, unsigned int enable);
unsigned int PLIC_id_read();
unsigned int PLIC_read_pending();
//--------------------------------------------------------------------------
// handle_trap function
void handle_trap()
{
unsigned int id_temp;
//enter handle_trap()
U32(DEBUG_SIG) = 0x2;
//print PLIC interrupt id
id_temp = PLIC_id_read();
U32(DEBUG_VAL) = id_temp;
//clear the PLIC interrupt id
PLIC_id_clr(id_temp);
//if PLIC pending is not 0x0, some other interrupts is pending
while(PLIC_read_pending() != 0x0)
{
//print PLIC interrupt id
id_temp = PLIC_id_read();
U32(DEBUG_VAL) = id_temp;
//clear the PLIC interrupt id
PLIC_id_clr(id_temp);
}
U32(DEBUG_SIG) = 0xFF;
}
//--------------------------------------------------------------------------
// CSR interrupt configuration function
void csr_cfg()
{
unsigned int csr_tmp;
//mie.MEIE
csr_tmp = read_csr(mie);
U32(DEBUG_VAL) = csr_tmp;
//write_csr(mie,0x0);
write_csr(mie,(csr_tmp | 0xFFFF0888));
//mstatus.MIE
csr_tmp = read_csr(mstatus);
U32(DEBUG_VAL) = csr_tmp;
//write_csr(mstatus,0x0);
write_csr(mstatus,(csr_tmp | 0x8));
}
//--------------------------------------------------------------------------
// PLIC initial configuration function
void PLIC_init_cfg(unsigned int num, unsigned int threshold)
{
unsigned int i;
//source x priority
for(i=1; i<=num; i++)
{
U32(PLIC_BASE + 0x0000 + 4*i) = 0x0;
}
//PLIC interrupt enable0
U32(PLIC_BASE + 0x2000) = 0x0;
//PLIC interrupt enable1
if(num >= 32)
{
U32(PLIC_BASE + 0x2004) = 0x0;
}
//set PLIC interrupt threshold
U32(PLIC_BASE + 0x200000) = threshold;
}
//--------------------------------------------------------------------------
// PLIC ID clear function
unsigned int PLIC_id_read()
{
//read PIIL interrupt id
unsigned int id;
id = U32(PLIC_BASE + 0x200004);
return id;
}
void PLIC_id_clr(unsigned int id)
{
//clear PIIL interrupt id
U32(PLIC_BASE + 0x200004) = id;
}
//--------------------------------------------------------------------------
// PLIC read pending function
unsigned int PLIC_read_pending()
{
//read PIIL interrupt pending
unsigned int pending;
pending = U32(PLIC_BASE + 0x1000);
return pending;
}
//--------------------------------------------------------------------------
// PLIC sourceX configuration function
void PLIC_sourceX_cfg(unsigned int num, unsigned int priority, unsigned int enable)
{
//set source X priority
U32(PLIC_BASE + 0x0000 + 4*num) = priority;
//set source X enable
U32(PLIC_BASE + 0x2000) = U32(PLIC_BASE + 0x2000) | (enable<<num);
}
//--------------------------------------------------------------------------
// Main
void main()
{
PLIC_init_cfg(0x2,0x1);
PLIC_sourceX_cfg(0x1,0x2,0x1);
PLIC_sourceX_cfg(0x2,0x3,0x1);
csr_cfg();
U32(DEBUG_SIG) = 0x1;
while(1) {asm volatile ("wfi");}
}
接下来就是仿真的波形。
从上图中可以看到,是同时将PLIC ID1中断源和ID2中断源拉高的,结合上面的C语言代码可以知道,ID2中断源的优先级更高的,所以先处理ID2中断。在中断函数中不断判断pending的值,只要有悬挂的就一并处理了,所以看到蓝色箭头和绿色箭头是相继被拉低的。拉低的动作是通过axi4协议的mmio端口完成的。当中断来临的时候mcasue是会变化的,具体的变化值可以参考SiFive E3的文档。
放大波形看一下,可以看到io_pc在中断来临的时候是变为0x8000_0005C,也就是我们设定的trap_enrty函数入口,在trap_enrty函数中再跳到handle_trap函数中。
中断进入和退出的说明可以参考以下说明。
到这里,关于PLIC的内容已经介绍完毕了,如果觉得还是有用的那就给个赞吧,谢谢。