中断的硬件实现

中断的硬件逻辑

中断的硬件实现(寄存器级描述)

硬件分为3个部分:

  1. CSR寄存器部分(保证处理器核在硬件上支持中断)
  2. CLINT(终断控制、终断优先级等部分)
  3. PLIC(用于控制外部中断)

中断介绍

  1. 中断源、中断请求
  2. 中断服务程序(Interrupt Service Routine, ISR)
  3. 保存现场、恢复现场
  4. 中断优先级(每个中断源配备级别寄存器优先级寄存器)、中断仲裁 TODO: 看6.2.9节关于优先级定义的部分

基础介绍

外部信号进入芯片后,通过中断控制器中的“使能+信号+优先级+仲裁”等逻辑,最终输出到 MCU

  1. 在riscv中一共定义了三种状态中断,对于hart层面,hart包含local中断源和global中断源。 而local中断只有Timer和Software中断两种,而global中断则称为external interrupts。
  2. 只有global中断源可以被PLIC(Platform-Level Interrupt Controller) core响应,通常为I/O device; 一般来说,timer和software是通过CLINT(CORE LOCAL INTERRUPT),而外部中断通过PLIC处理; PLIC的实现是独立于hart的IP设计

  1. 由于PLIC的使用是针对外部中断的,所以可以单独设置每个中断。可以设置如下的值:

    • 中断的优先级priotity
    • 中断挂起位pending
    • 中断使能enables
    • 中断阈值priority Thresholds
  2. 常见的同步错误(异常)有如下四种:

    • 访问错误:访问了不该访问的地址空间,例如尝试写入到ROM
    • 执行到了ecall, ebreak指令
    • 在ID发现指令非法
    • 地址不对齐: RISC-V其实没有强制支持非对齐访存,因此不支持非对齐访存的处理器,存在该异常

    Notes: 所有 RISC-V 系统的共同问题是如何处理异常和屏蔽中断

  3. 根据RISC-V的架构定义,处理器当前的Machine Mode或者User Mode并没有反映在任何软件可见的寄存器中(处理器内核会维护一个对软件不可见的硬件寄存器), 因此软件程序无法通过读取任何寄存器而查看当前自己所处的Machine

时钟中断

  1. 触发条件:mtime>=mtimecmp
  2. mtime每个cycle自增1,当满足触发条件的时候,由CLINT产生timer中断
  3. mie寄存器的MTIE字段使能该中断、由mip寄存器的MTIP来指示timer中断是否在pending
  4. timer 的寄存器在 timer 设备里,不在 CPU 中,是通过 MMIO 的方式映射到内存中的;都可以按照内存映射的方式被读写
  5. 满足时钟中断触发条件之后,对应的处理函数会将mtimecmp+=inerval,这样触发条件又不满足了 TODO: ask 我们系统是不是没有实现时钟中断?我们是不是不需要时钟中断

软件中断

  1. 定义:软件触发的中断、主要指核间中断IPI(internal processor Interrupt)
  2. 通过写入mip寄存器的MSIP字段来触发

外部中断

中断处理操作

硬件操作

  1. 更新pc,改变指令流
    1
    2
    mepc= mcause[31] ? pc_next : pc; // 0->中断,1->异常
    pc=mtvec[1:0] ? Base+4*Cause : Base;
  2. 设置中断原因到mcause寄存器
  3. 更改中断权限
    1
    2
    mPIE=mIE; # 存储trap发生之前的中断使能位
    mIE=0 # 关闭中断
  4. 保存处理器特权级: 只有m特权级就不用管这个字段

软件操作(中断处理程序)

  1. 保存32个通用寄存器到堆栈: mscratch寄存器通常被用作指向附加临时内存空 间的指针,通过该指针就可以知道通用寄存器该被压栈的位置

  2. 执行中断处理程序

  3. 从栈上恢复通用寄存器的值

附录:必须实现的8个ÇSR寄存器

  1. mstatus:指令处理器核的状态信息
    • MIE是对应特权级下全局中断使能位
    • xPIE, xPP, xIE举例:从s特权级产生中断进入到m特权级时
      1
      2
      3
      mPIE=mIE; # 存储trap发生之前的中断使能位
      mPP=s # trap发生之前,处理器所处的特权级
      mIE=0 # 关闭中断
  2. mtvec:
    • 可以配置向量模式跟非向量模式,向量模式下中断响应最快
    • 非向量模式下(参考该文章5.13):
      • mtvt2[0]==0: 中断跟异常都通过mtvec指定地址
      • mtvt2[0]==1: 中断通过mtvt2指定地址
  3. mepc
  4. mcause:指令trap的原因;mcause[31]==1表示是外部中断、否则是异常; mcause代码参考该文章的3.4.2节 20231012172130.png
    trap发生的时候, mcause会被硬件写入,记录trap发生的原因
  5. mie: 针对各种类型的指令,指明当前处理器会处理哪些中断、忽视哪些中断
  6. mip: 列出目前正准备处理的中断
  7. mtval: 保存了陷入(trap)的附加信息,当触发硬件断点、地址未对齐、access fault、page fault 时,mtval 记录的是引发这些问题的虚拟地址;如果是由非法指令造成的异常,则将该指令的指令编码更新到mtval寄存器中
  8. mscratch: 暂时存放一个字大小的数据 > 将所有三个控制状态寄存器 虑,如果 mstatus.MIE = 1,mie[7] = 1,且 mip[7] = 1,则可以处理机器的时钟中断

PLIC(Platform-Level Interrupt Controller)

CLIC(Core-Local Interrupt Controller)

  1. 作用:用于多个内部中断、外部中断进行仲裁;支持嵌套中断;向core发送中断信号
  2. 中断目标: 生成一根中短线发送给Core,这根线就称作为中断目标
  3. 中断源:
    • RISC-V默认支持4096个中断源
    • 每个中断源有如下参数:
      • 编号(ID): 0~18被预留为内核使用,其余的中断ID从19开始
      • 使能位(IE)
      • Pending位(IP)
      • 级别跟优先级(Level and Priority)
      • 向量或非向量处理(Vector or Non-Vector Mode)
  4. 阈值:仲裁出的中断源,其Level必须大于阈值,才会最终被发送给Core
  5. 仲裁原理:
    • 中断源参与仲裁的条件: enable & pending ==1
    • 仲裁原理:先比较Level,再比较Priority,在比较ID(数字都是越大越好)

我们的实现

CORE的硬件支持

IMAGE-20231013212119766

PS: 目前CORE可以顺序的执行I-MEMORY里的指令,并且可以对BRANCH、JUMP指令正确地跳转到对应PC,

现在需要增加对中断的支持;中断=中断(INTERRUPT)+异常(EXCEPTION)

CORE对中断进行处理,需要增加如下的功能:

  1. 接受中断发生的信号
  2. 中断发生时,跳转到对应的处理程序的能力
  3. 辨别不同类型的中断的能力

PS: 对中断处理的主要动作是当中断发生的时候->停止当前的工作流->按照中断的类型完成对应的处理->恢复之前的工作流

CORE需要提供的硬件支持

为了实现上述3种功能,CORE需要在原来五级流水线的基础上增加如下的硬件支持

接受中断信号

  1. CORE需要接受一个中断发生的信号,以通知CORE当前发生了中断

    具体指ID STAGE需要接受信号、知道中断发生了,因为ID STAGE负责PC的更新

IMAGE-20231013220122658
  1. RISC-V规定了3种类型的中断,分别是:外部中断、软件中断、定时器中断,其来源会在下文MCAUSE部分介绍

🌟CSR寄存器及其操作

  1. 记录中断的发生:

    IMAGE-20231013211942338

    中断使能位在CSR.MIE寄存器中配置

    • CSR.MIP寄存器用于记录各种PENDING的中断 IMAGE-20231013131939115
    • PLIC检测到外部中断之后,会发送一个NOTIFY信号到CSR单元,表明外部中断发生; 导致CSR.MIP[11]=1;
    • TIMER触发时钟中断之后,也会发送一个NOTIFY信号到CSR单元,表明时钟中断发送; 导致CSR.MIP[7]=1;
    • 软件中断(通常用于向另一个核发送),此时可以通过MMIO的方式往另一个核的CSR单元写入; 导致CSR.MIP[3]=1;
  2. 判断中断发生 IMAGE-20231013212029071

    如上所述:3种中断发生之后,都会通过硬件在1个CYCLE内记录到CSR.MIP寄存器中,现在可以对中断进行处理了

    • 中断判断在TRAP_HANDLER单用中完成(为了代码结构清晰),它通过输入CSR.MIP, CSR.MIE信号,判断中断是否发生; 若发生中断,则会将TRAP信号拉高,该信号会被ID STAGE使用, TRAP=MIP[X] & MIE[X], X=3,7,11
    • TRAP信号会发送给CORE跟CSR单元,完成相应的硬件操作以支撑中断
  3. 记录中断的类型&更改处理器状态

    IMAGE-20231013213455624
    • 在中断发生的时候,硬件会自动更新CSR.MCAUSE寄存器,保存中断的原因
    • 例如“外部中断发生”之后,CSR.MCAUSE寄存器的EXCEPTION CODE字段会被写入0X80000800
    • 中断处理程序(INTERRUPT SERVICE ROUTINE, ISR)通过查询CSR.MCAUSE,就可以知道中断的原因,进而做想要的操作
    • 中断判定之后,需要修改CSR.MSTATUS寄存器以更改处理器状态, 硬件默认会将CSR.MSTATUS.MPIE=CSR.MSTATUS.MIE, CSR.MSTATUS.MIE=0,默认不支持中断嵌套
    • 中断嵌套: 进入ISR后,可以通过CSR指令(如CSRRW)将CSR.MSTATUS.MIE置1从而再次打开中断,实现中断嵌套
  4. 跳转到ISR执行

    中断处理核心的工作就是更改PC,从而跳转到ISR并且从ISR返回

    IMAGE-20231013214933645
    • CSR.MTVEC存放ISR的地址: 上面的TRAP信号被拉高之后,ID会将PC_INSTR更新为MTVEC的值,起到跳转到ISR的功能; CSR.MTVEC会在系统初始化的时候被CSR指令写入
    • 记录ISR返回的PC到CSR.MEPC中: MEPC = INTERRUPT ? PC_NEXT : PC;
    • 在ISR中,需要通过指令来保存上下文到D-MEMORY(栈区),栈指针有CSR.MSCRATCH寄存器给出
  5. 从ISR返回

    中断处理结束之后,ID需要将PC更换为MEPC的值,从而继续之前的任务

    IMAGE-20231013222648570
    • 从ISR返回的条件是执行到了MRET指令(在此之前,ISR已经通过指令完成了上下文的恢复才会调用MRET指令)
    • ID STAGE会将PC替换为CSR.MEPC的值,并且CSR相关的STATUS, MIP寄存器会被硬件恢复为中断之前的状态
    • PLIC同样需要接受MRET信号,从而判定当前中断已经处理完成、去仲裁新的外部中断

中断发生时,硬件自动完成的操作

有了上述硬件支持之后,中断发生之后,下述操作会有硬件自动完成:

  • 异常指令的 PC 被保存在 MEPC 中,PC 被设置为 MTVEC
  • 根据异常来源设置 MCAUSE
  • 把控制状态寄存器 MSTATUS 中的 MIE 位置零以禁用中断,并把先前的 MIE 值保 留到 MPIE 中
  • 发生异常之前的权限模式保留在 MSTATUS 的 MPP 域中,再把权限模式更改为 M(咱们只实现了M模式,故这一步可以省略)

参考文献

  1. RISC-V 手册 10.3
  2. 详解RISC v中断
  3. Nuclei_N级别指令架构手册
  4. RISC-V 中断子系统分析——CPU 中断处理
  5. RISC-V 中断子系统分析——硬件及其初始化