本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。
RVOS是本课程基于RISC-V搭建的简易操作系统名称。
课程代码和环境搭建教程参考github仓库: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md
前置知识:
mstatus寄存器中的MIE位用于控制全局中断是否开启,而mie作为次级中断控制寄存器,用于在全局中断打开的情况下,控制某个具体的中断类型是否开启:
mip寄存器用于告诉我们是否发生了某类中断。
我们的外设想要和CPU进行通信,就需要通过中断的方式进行通信,那么考虑到外设的可插拔性,我们需要做好中断信号的转换和汇聚处理:
中断信号转换和汇聚的工作由PLIC完成,也就是中断平台控制器:
PLIC 是 “Platform-Level Interrupt Controller” 的缩写,它是一种用于处理中断的硬件模块,常用于处理器系统中。PLIC 负责管理和分发各种中断信号,并将它们传递给适当的处理器核心或其他设备。
PLIC 的主要功能包括:
PLIC 在大型多核处理器系统中特别有用,因为它可以协调多个处理器核心之间的中断处理。每个处理器核心可以向 PLIC 注册其中断处理程序,并通过 PLIC 获取适当的中断。
PLIC 的具体实现和配置取决于具体的处理器架构和系统设计。在 RISC-V 架构中,PLIC 是标准的中断控制器,用于处理中断请求和分发。在某些 SOC 中,例如 FU540-C000,PLIC 是其中的一部分,用于管理系统中的中断。
PLIC(Platform-Level Interrupt Controller)中的中断源是指可以触发中断请求的硬件设备或其他事件。每个中断源都有一个唯一的标识符或中断号,用于在 PLIC 中进行识别和管理。
PLIC 中的中断源可以是各种外设或模块,例如:
在 PLIC 中,每个中断源都被分配一个唯一的中断号,这些中断号用于识别和区分不同的中断源。当某个中断源产生中断请求时,PLIC 根据中断号和优先级确定中断的处理顺序,并将中断请求发送给适当的处理器核心。
具体的 PLIC 中断源数量和配置取决于处理器架构和 SOC 设计。在 FU540-C000 等特定 SOC 中,具体的中断源和中断号分配可能会有所不同。
上图显示的串口设备的中断源为10
中断源–>中断号—>中断向量–>中断服务程序(ISR)–>中断返回
在PLIC(Platform-Level Interrupt Controller)中,"claim"寄存器和"complete"寄存器是用于处理中断请求和中断完成的寄存器。
这样,其他处理器核心可以通过读取认领寄存器来获取待处理的中断,并开始处理这些中断。当一个处理器核心正在处理一个中断时,其他处理器核心可以认领并处理其他已经就绪的中断,从而实现并行处理多个中断。
因此,PLIC在将中断标记为已完成后,会继续处理其他已经就绪的中断,并允许其他处理器核心去处理这些中断。这样可以提高系统的并发性和响应性。
大家可以对照下图,看看各个寄存器在PILC电路图中的位置,以及其作用域范围:
实现思路:
代码实现:
void plic_init(void)
{
//获取hartId
int hart = r_tp();
/*
* Set priority for UART0.
*
* Each PLIC interrupt source can be assigned a priority by writing
* to its 32-bit memory-mapped priority register.
* The QEMU-virt (the same as FU540-C000) supports 7 levels of priority.
* A priority value of 0 is reserved to mean "never interrupt" and
* effectively disables the interrupt.
* Priority 1 is the lowest active priority, and priority 7 is the highest.
* Ties between global interrupts of the same priority are broken by
* the Interrupt ID; interrupts with the lowest ID have the highest
* effective priority.
*/
//设置UART中断源的优先级
*(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1;
/*
* Enable UART0
*
* Each global interrupt can be enabled by setting the corresponding
* bit in the enables registers.
*/
//设置当前hart对应的enable寄存器中UART位为1,即针对当前hart开启UART中断源
*(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ);
/*
* Set priority threshold for UART0.
*
* PLIC will mask all interrupts of a priority less than or equal to threshold.
* Maximum threshold is 7.
* For example, a threshold value of zero permits all interrupts with
* non-zero priority, whereas a value of 7 masks all interrupts.
* Notice, the threshold is global for PLIC, not for each interrupt source.
*/
//针对当前hart设置中断源优先级阈值为0
*(uint32_t*)PLIC_MTHRESHOLD(hart) = 0;
/* enable machine-mode external interrupts. */
//打开mie次级中断控制器中外部中断使能
w_mie(r_mie() | MIE_MEIE);
/* enable machine-mode global interrupts. */
//开启全局中断
w_mstatus(r_mstatus() | MSTATUS_MIE);
}
/*
* DESCRIPTION:
* Query the PLIC what interrupt we should serve.
* Perform an interrupt claim by reading the claim register, which
* returns the ID of the highest-priority pending interrupt or zero if there
* is no pending interrupt.
* A successful claim also atomically clears the corresponding pending bit
* on the interrupt source.
* RETURN VALUE:
* the ID of the highest-priority pending interrupt or zero if there
* is no pending interrupt.
*/
int plic_claim(void)
{
//获取当前hart id
int hart = r_tp();
//返回待处理的中断源ID
int irq = *(uint32_t*)PLIC_MCLAIM(hart);
return irq;
}
/*
* DESCRIPTION:
* Writing the interrupt ID it received from the claim (irq) to the
* complete register would signal the PLIC we've served this IRQ.
* The PLIC does not check whether the completion ID is the same as the
* last claim ID for that target. If the completion ID does not match an
* interrupt source that is currently enabled for the target, the completion
* is silently ignored.
* RETURN VALUE: none
*/
void plic_complete(int irq)
{
int hart = r_tp();
//将处理完毕的中断源id写入complete寄存器
*(uint32_t*)PLIC_MCOMPLETE(hart) = irq;
}
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;
}
void external_interrupt_handler()
{
//获取中断源ID
int irq = plic_claim();
//处理UART中断源
if (irq == UART0_IRQ){
uart_isr();
} else if (irq) {
//其他中断源不进行处理
printf("unexpected interrupt irq = %d\n", irq);
}
//中断源合法,告知PLIC中断源处理完毕
if (irq) {
plic_complete(irq);
}
}
/*
* handle a uart interrupt, raised because input has arrived, called from trap.c.
*/
void uart_isr(void)
{
while (1) {
//获取uart接收到字符
int c = uart_getc();
//将字符写出
if (c == -1) {
break;
} else {
uart_puts("uart revice word: ");
uart_putc((char)c);
uart_putc('\n');
}
}
}
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
page_init();
trap_init();
//新增plic模块初始化
plic_init();
sched_init();
os_main();
schedule();
uart_puts("Would not go here!\n");
while (1) {}; // stop here!
}