PLIC内存布局
#define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
#define PLIC_PENDING (PLIC + 0x1000)
#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart) * 0x100)
#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart) * 0x2000)
#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart) * 0x2000)
Xv6外部中断
外部中断通常是指来自处理器外部设备的中断。
Xv6只支持两个外设:
- uart0:中断号为10,映射到物理内存0x10000000
- virtio disk:中断号为1,映射到物理内存0x10001000
// qemu puts UART registers here in physical memory.
#define UART0 0x10000000L
#define UART0_IRQ 10
// virtio mmio interface
#define VIRTIO0 0x10001000
#define VIRTIO0_IRQ 1
PLIC驱动
plicinit函数
同虚拟内存初始化,PLIC的初始化也是两阶段初始化。第一阶段的初始化由cpu0执行,也就是说该函数只执行一次。
该函数的任务是设置中断优先级。Xv6只支持两个外设:UART和virtio mmio disk,该函数把这两个外设的中断优先级都设置为1。此外,RISC-V规定:如果两个相同优先级的中断同时触发,那么编号小的中断具有较高的优先级。
void plicinit(void)
{
// set desired IRQ priorities non-zero (otherwise disabled).
*(uint32 *)(PLIC + UART0_IRQ * 4) = 1;
*(uint32 *)(PLIC + VIRTIO0_IRQ * 4) = 1;
}
plicinithart函数
该函数是PLIC的第二阶段初始化,每个核心初始化时都要执行这个函数。
该函数会使能S模式下的uart中断和virtio disk中断,并且设置S模式下中断阈值为0
这就说明了:
- 如果发生外部中断,那么每个核心都会收到外部中断的信号
- 只有一个核心会处理这次外部中断
void plicinithart(void)
{
int hart = cpuid();
// set enable bits for this hart's S-mode
// for the uart and virtio disk.
*(uint32 *)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ);
// set this hart's S-mode priority threshold to 0.
*(uint32 *)PLIC_SPRIORITY(hart) = 0;
}
plic_claim函数
该函数由异常处理程序调用,用来返回异常号。如果同时有多个中断发生,PLIC硬件会根据之前的配置返回当前待定中断源中优先级最高的那个。其中只有一个核心会得到异常号,其他的核心的返回值是0
int plic_claim(void)
{
int hart = cpuid();
int irq = *(uint32 *)PLIC_SCLAIM(hart);
return irq;
}
plic_complete函数
中断请求寄存器和中断完成寄存器是同一个寄存器。中断处理程序处理完中断后,会调用该函数,同时将中断号写入该寄存器中
void plic_complete(int irq)
{
int hart = cpuid();
*(uint32 *)PLIC_SCLAIM(hart) = irq;
}
参考资料
The xv6 Kernel-27 PLIC_Platform Level Interrupt Controller_哔哩哔哩_bilibili
Lecture 9 - Interrupts 中文版_哔哩哔哩_bilibili