基于 riscv32 的 OS 设计:外部设备中断(uart)
RISC-V 中断(Interrupt)的分类
一共有三种中断:
- 本地软件中断;
- 本地时钟中断;
- 外部中断;
中断的控制很复杂,具有多个级别的开关,以确保系统能够灵活、高效地处理各种中断:
1. 设备级别:
- 这是中断控制的最底层。每个外围设备(例如,UART、SPI、GPIO)都有自己的中断使能位。
- 通过设置这些位,可以控制设备是否能够产生中断请求。
- 如果设备级别的中断被禁用,即使设备产生了中断事件,也不会向上一级的中断控制器(PLIC)发送中断请求。
2. PLIC 级别:
- PLIC(Platform-Level Interrupt Controller,平台级中断控制器)是 RISC-V 系统中负责管理和分发外部中断的核心组件。
- PLIC 具有以下几个关键的控制级别:
- 中断使能位:
- PLIC 允许为每个外部中断源独立设置使能位。
- 只有当 PLIC 中的相应中断使能位被设置时,来自该中断源的中断请求才会被传递给 CPU。
- 中断优先级:
- PLIC 允许为每个中断源分配优先级。
- 当多个中断同时发生时,PLIC 根据优先级决定哪个中断先被处理。
- 中断目标:
- PLIC 允许将中断路由到特定的 CPU 核心(hart)。
- 这使得系统能够灵活地将中断分配给不同的 CPU 核心进行处理。
- 中断使能位:
3. CPU 全局级别:
- RISC-V CPU 具有全局中断使能位,通常位于
mstatus
寄存器中。 - 当全局中断使能位被禁用时,CPU 将忽略所有来自 PLIC 的中断请求。
- 全局中断使能位通常用于在执行关键代码段时临时禁用中断,以确保代码的原子性。
4. CPU 局部级别:
- 除了全局中断使能位之外,RISC-V CPU 还具有局部中断使能位,用于控制特定类型的中断。
- 例如,
mie
寄存器用于控制机器模式下的中断使能,sie
寄存器用于控制超级用户模式下的中断使能。 - 通过设置这些局部中断使能位,可以更细粒度地控制 CPU 响应哪些类型的中断。
RISC-V 中断编程中涉及的寄存器
mstatus(Machine Status)
用于跟踪和控制 hart 的当前操作状态(特别地,包括关闭和打开全局中断)。
mie、mip
mie(Machine Interrupt Enable) :打开(1)或者关闭(0)M/S/U 模式下对应的 External/Timer/Software 中断。
mip(Machine Interrupt Pending) :获取当前 M/S/U 模式下对应的 External/Timer/Software 中断是否发生。
RISC-V 中断处理流程
中断发生时 hart 自动执行如下状态转换:
- 把 mstatus 的 MIE 值复制到 MPIE 中,清除 mstatus 中的 MIE 标
志位,效果是中断被禁止。 - 当前的 PC 的下一条指令地址被复制到 mepc 中,同时 PC 被设置为
mtvec。注意如果我们设置 mtvec.MODE = vetcored,PC =
mtvec.BASE + 4 × exception-code。 - 根据 interrupt 的种类设置 mcause,并根据需要为 mtval 设置附加
信息。 - 将 trap 发生之前的权限模式保存在 mstatus 的 MPP 域中,再把
hart 权限模式更改为 M。
退出中断 :编程调用 MRET 指令。
PLIC 介绍
PLIC 编程接口 - 寄存器
RISC-V 规范规定,PLIC 的寄存器编址采用内存映射(memory map)方式。每个寄存器的宽度为 32-bit。
具体寄存器编址采用 base + offset 的格式,且 base 由各个特定platform 自己定义。针对 QEMU-virt,其 PLIC 的设计参考了 FU540-C000,base 为 0x0c000000。
每个 PLIC 中断源对应一个寄存器,用于配置该中断源的优先级。
QEMU-virt 支持 7 个优先级。 0 表示对该中断源禁用中断。其余优先级,1 最低,7 最高。
如果两个中断源优先级相同,则根据中断源的 ID 值进一步区分优先级,ID 值越小的优先级越高。
每个 PLIC 包含 2 个 32 位的 Pending 寄存器,每一个 bit 对应一个中断源,如果为 1 表示该中断源上发生了中断(进入 Pending 状态),有待 hart 处理,否则表示该中断源上当前无中断发生。
Pending 寄存器中断的 Pending 状态可以通过 claim 方式清除。
第一个 Pending 寄存器的第 0 位对应不存在的 0 号中断源,其值永远为 0。
每个 Hart 有 2 个 Enable 寄存器 (Enable1 和 Enable2)用于针对该 Hart 启动或者关闭某路中断源。
每个中断源对应 Enable 寄存器的一个 bit,其中 Enable1 负责控制 1 ~ 31 号中断源;Enable2 负责控制 32 ~ 53 号中断源。 将对应的 bit 位设置为 1 表示使能该中断源,否则表示关闭该中断源。
每个 Hart 有 1 个 Threshold 寄存器用于设置中断优先级的阈值。
所有小于或者等于(<=)该阈值的中断源即使发生了也会被 PLIC 丢弃。特别地,当阈值为 0 时允许所有中断源上发生的中断;当阈值为 7 时丢弃所有中断源上发生的中断。
Claim 和 Complete 是同一个寄存器,每个 Hart 一个。
对该寄存器执行读操作称之为 Claim,即获取当前发生的最高优先级的中断源 ID。Claim 成功后会清除对应的 Pending 位。
对该寄存器执行写操作称之为 Complete。所谓 Complete 指的是通知 PLIC 对该路中断的处理已经结束。
采用中断方式从 UART 实现输入
本实验尝试使用 uart 进行字符输入,这是一种常见的中断源。
首先我们要打开 uart 的中断写入(以 CPU 为参照):
1 |
|
这样,我们在键盘输入字符的时候,uart 接受到字符后,设备就会以中断的方式通知 CPU。
接着,我们设置 PLIC:
1 |
|
当 uart 接受到字符后,会发起中断:
1 |
|
CPU 收到中断后,会进入中断处理程序,也就是 trap。由于 uart 是 外部中断,因此 cause 为 11。之后进入到 external_interrupt_handler:
1 |
|
这个函数专门处理外部中断,如果是 uart 中断,则:
1 |
|
因此,它接受到字符后,又将字符打印到终端了。
运行程序:
1 |
|
在 RISC-V 架构中,UART(通用异步收发器)通过 PLIC(平台级中断控制器)向 CPU 发起中断的流程如下:
1. UART 中断请求:
- 当 UART 接收到数据(例如,键盘输入的字符)或发生其他中断事件(例如,发送缓冲区为空)时,它会产生一个中断请求信号。
- 这个中断请求信号会连接到 PLIC 的一个外部中断源输入引脚。
2. PLIC 中断处理:
- PLIC 接收到来自 UART 的中断请求信号。
- PLIC 根据配置的中断优先级和目标 CPU 核心(hart),决定是否将中断请求传递给 CPU。
- 如果 PLIC 允许该中断(即,中断使能位已设置,且优先级足够高),它会将中断请求信号发送给目标 CPU 核心。
3. CPU 中断处理:
- CPU 接收到来自 PLIC 的中断请求信号。
- CPU 根据当前的中断使能状态(例如,
mstatus
寄存器中的全局中断使能位)和局部中断使能状态(例如,mie
寄存器中的机器模式中断使能位),决定是否响应中断。 - 如果 CPU 允许该中断,它会执行以下操作:
- 保存当前 CPU 的上下文(例如,寄存器状态、程序计数器)。
- 跳转到中断处理程序(中断向量表中的相应条目)。
- 执行中断处理程序,处理 UART 中断事件。
- 恢复之前保存的 CPU 上下文。
- 返回到中断发生前的程序执行位置。
关键组件和配置:
- UART 中断使能:
- UART 内部的寄存器(例如,IER 寄存器)用于启用或禁用特定的 UART 中断事件。
- PLIC 中断使能和优先级:
- PLIC 的寄存器用于配置每个外部中断源的使能状态和优先级。
- PLIC 还允许将中断路由到特定的 CPU 核心。
- CPU 中断使能:
mstatus
寄存器中的全局中断使能位用于控制 CPU 是否响应所有中断。mie
寄存器中的局部中断使能位用于控制 CPU 响应哪些类型的中断。
- 中断向量表:
- 中断向量表是一个存储中断处理程序地址的表格。
- CPU 根据中断类型,从中断向量表中查找相应的中断处理程序地址。