diff --git a/articles/20221120-riscv-kvm-int-impl-1.md b/articles/20221120-riscv-kvm-int-impl-1.md new file mode 100644 index 0000000000000000000000000000000000000000..aeef44c63a74a83b5e2ccc5498ffb4d7c7526f57 --- /dev/null +++ b/articles/20221120-riscv-kvm-int-impl-1.md @@ -0,0 +1,664 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [tounix spaces]
+> Author: XiakaiPan <13212017962@163.com>
+> Date: 20221201
+> Revisor: Walimis
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [RISC-V 虚拟化技术调研与分析](https://gitee.com/tinylab/riscv-linux/issues/I5E4VB)
+> Sponsor: PLCT Lab, ISCAS + +# RISC-V 中断处理的实现(一) + +## 前言 + +本文就 Spike 和 QEMU 中与 RISC-V Trap 处理相关的实现细节进行了较为深入的挖掘,包括 PLIC 和 CLINT 等在模拟器中的实现。从模拟器的角度来看,当前它们对于虚拟化扩展的支持,是在其内部中断处理机制的基础上,添加了虚拟化所需的 CSR 以及对应的 Trap 判断与触发逻辑。 + +## 软件版本 + +| Software | Version | +|------------|------------------------------------------| +| [QEMU][5] | 7.1.0 | +| [Spike][6] | 70f8aa01b803f4dbc0461fd7c986c1ca76d4b1d9 | + +## Spike 实现分析 + +### Trap 统一编码与命名习惯 + +RISC-V Trap 包含中断和异常,其编码在 [自动生成][1] 的编码文件中分别以如下名称开头: + +- IRQ:Interrupt ReQuest,用于表示中断编码 +- CAUSE:cause 是 RISC-V 中用于保存 trap 的具体编码的 CSR,包括 `mcause`, `scause`, `vscause` 等,此处专门用于表示异常编码 + +### Trap 处理函数调用 + +#### 中断处理 + +##### 中断定义 + +```cpp +// riscv/encoding.h: line 266 +#define IRQ_U_SOFT 0 +#define IRQ_S_SOFT 1 +#define IRQ_VS_SOFT 2 +#define IRQ_M_SOFT 3 +#define IRQ_U_TIMER 4 +#define IRQ_S_TIMER 5 +#define IRQ_VS_TIMER 6 +#define IRQ_M_TIMER 7 +#define IRQ_U_EXT 8 +#define IRQ_S_EXT 9 +#define IRQ_VS_EXT 10 +#define IRQ_M_EXT 11 +#define IRQ_S_GEXT 12 +#define IRQ_COP 12 +#define IRQ_LCOF 13 +``` + +Spike 定义了 `sim_t` 类作为最外层的模拟管理器,其包含了一个 `processor` vector,Spike 模拟 CPU 的执行即是 `sim` 的各个 `processor` 调用 `step` 函数执行 fetch, decode, execute 等操作。trap 处理操作即在 `step` 函数中实现。 + +具体来说,trap 处理包含了两个步骤: + +##### 判断当前是否需要进行 Interrupt 处理 + +当且仅当 `mie` 和 `mip` 两个 CSR 的值,进行逻辑与不为 0 时,trap 才会被处理: + +```cpp +void take_pending_interrupt() { take_interrupt(state.mip->read() & state.mie->read()); } +``` + +##### 确定 Interrupt 的内容 + +`void take_interrupt(reg_t mask);`:如果 `mask` 为 0 则不执行 trap 处理,否则将判断 trap 具体类型,抛出一个 trap(`trap_t`) + +```cpp +void processor_t::take_interrupt(reg_t pending_interrupts) +{ + // 不执行中断处理 + // Do nothing if no pending interrupts + if (!pending_interrupts) { + return; + } + + // 依照 M, HS, VS 的顺序确定具体要处理哪一个特权级的 trap + // M-ints have higher priority over HS-ints and VS-ints + const reg_t mie = get_field(state.mstatus->read(), MSTATUS_MIE); + const reg_t m_enabled = state.prv < PRV_M || (state.prv == PRV_M && mie); + reg_t enabled_interrupts = pending_interrupts & ~state.mideleg->read() & -m_enabled; + if (enabled_interrupts == 0) { + // HS-ints have higher priority over VS-ints + const reg_t deleg_to_hs = state.mideleg->read() & ~state.hideleg->read(); + const reg_t sie = get_field(state.sstatus->read(), MSTATUS_SIE); + const reg_t hs_enabled = state.v || state.prv < PRV_S || (state.prv == PRV_S && sie); + enabled_interrupts = pending_interrupts & deleg_to_hs & -hs_enabled; + if (state.v && enabled_interrupts == 0) { + // VS-ints have least priority and can only be taken with virt enabled + const reg_t deleg_to_vs = state.hideleg->read(); + const reg_t vs_enabled = state.prv < PRV_S || (state.prv == PRV_S && sie); + enabled_interrupts = pending_interrupts & deleg_to_vs & -vs_enabled; + } + } + + // 按照 MEI, MSI, MTI; SEI, SSI, STI (HS > VS) 的优先级确定 mcause/scause 最高位之外的内容 + if (!state.debug_mode && enabled_interrupts) { + // nonstandard interrupts have highest priority + if (enabled_interrupts >> (IRQ_M_EXT + 1)) + enabled_interrupts = enabled_interrupts >> (IRQ_M_EXT + 1) << (IRQ_M_EXT + 1); + // standard interrupt priority is MEI, MSI, MTI, SEI, SSI, STI + else if (enabled_interrupts & MIP_MEIP) + enabled_interrupts = MIP_MEIP; + else if (enabled_interrupts & MIP_MSIP) + enabled_interrupts = MIP_MSIP; + else if (enabled_interrupts & MIP_MTIP) + enabled_interrupts = MIP_MTIP; + else if (enabled_interrupts & MIP_SEIP) + enabled_interrupts = MIP_SEIP; + else if (enabled_interrupts & MIP_SSIP) + enabled_interrupts = MIP_SSIP; + else if (enabled_interrupts & MIP_STIP) + enabled_interrupts = MIP_STIP; + else if (enabled_interrupts & MIP_LCOFIP) + enabled_interrupts = MIP_LCOFIP; + else if (enabled_interrupts & MIP_VSEIP) + enabled_interrupts = MIP_VSEIP; + else if (enabled_interrupts & MIP_VSSIP) + enabled_interrupts = MIP_VSSIP; + else if (enabled_interrupts & MIP_VSTIP) + enabled_interrupts = MIP_VSTIP; + else + abort(); + + // 抛出异常编码(最高位为 1) + throw trap_t(((reg_t)1 << (isa->get_max_xlen() - 1)) | ctz(enabled_interrupts)); + } +} +``` + +#### 异常处理 + +##### 异常定义 + +如上节所述,异常的指令集编码即宏定义命名由生成的 `encoding.h` 指定,之后在 `trap.h` 通过宏定义为每一类异常定义一个 `trap_t` 的派生类。 + +RISC-V 指令集规定的所有异常编码及其命名如下代码块所示: + +```cpp +// riscv/encoding.h: line 3147 +#define CAUSE_MISALIGNED_FETCH 0x0 +#define CAUSE_FETCH_ACCESS 0x1 +#define CAUSE_ILLEGAL_INSTRUCTION 0x2 +#define CAUSE_BREAKPOINT 0x3 +#define CAUSE_MISALIGNED_LOAD 0x4 +#define CAUSE_LOAD_ACCESS 0x5 +#define CAUSE_MISALIGNED_STORE 0x6 +#define CAUSE_STORE_ACCESS 0x7 +#define CAUSE_USER_ECALL 0x8 +#define CAUSE_SUPERVISOR_ECALL 0x9 +#define CAUSE_VIRTUAL_SUPERVISOR_ECALL 0xa +#define CAUSE_MACHINE_ECALL 0xb +#define CAUSE_FETCH_PAGE_FAULT 0xc +#define CAUSE_LOAD_PAGE_FAULT 0xd +#define CAUSE_STORE_PAGE_FAULT 0xf +#define CAUSE_FETCH_GUEST_PAGE_FAULT 0x14 +#define CAUSE_LOAD_GUEST_PAGE_FAULT 0x15 +#define CAUSE_VIRTUAL_INSTRUCTION 0x16 +#define CAUSE_STORE_GUEST_PAGE_FAULT 0x17 +``` + +将 exception cause 编码与特定类绑定是通过如下代码实现的,共有三类 exception 类型的 trap: + +- `MEM_TRAP`:访存相关的异常,如地址对齐、page-fault +- `TRAP`:系统调用指令 `ecall` 和 中断指令 `ebreak` +- `INSN_TRAP`:指令相关异常,如非法指令、虚拟指令特权级问题 + +```cpp +// riscv/trap.h: line 91 +#define DECLARE_MEM_TRAP(n, x) class trap_##x : public mem_trap_t { \ + public: \ + trap_##x(bool gva, reg_t tval, reg_t tval2, reg_t tinst) : mem_trap_t(n, gva, tval, tval2, tinst) {} \ + const char* name() { return "trap_"#x; } \ +}; +#define DECLARE_TRAP(n, x) class trap_##x : public trap_t { \ + public: \ + trap_##x() : trap_t(n) {} \ + const char* name() { return "trap_"#x; } \ +}; +#define DECLARE_INST_TRAP(n, x) class trap_##x : public insn_trap_t { \ + public: \ + trap_##x(reg_t tval) : insn_trap_t(n, /* gva */false, tval) {} \ + const char* name() { return "trap_"#x; } \ +}; +// ... + +// riscv/trap.h: line 103 +DECLARE_MEM_TRAP(CAUSE_MISALIGNED_FETCH, instruction_address_misaligned) +DECLARE_TRAP(CAUSE_USER_ECALL, user_ecall) +DECLARE_INST_TRAP(CAUSE_ILLEGAL_INSTRUCTION, illegal_instruction) +// ... +``` + +综上,所有 trap(exception,interrupt)在 Spike 代码中的表示方式如下图所示: + +```mermaid +flowchart BT + +subgraph interrupt +int[interrupt] +end + +subgraph riscv/trap.h: abstract trap +direction LR +t[trap_t]---int +it[insn_trap_t]-->t +mt[mem_trap_t]-->t +end + +subgraph riscv/trap.h: all exceptions + +tmf[trap_instruction_address_misaligned] +tii[trap_illegal_instruction] +tec[trap_user_ecall] +oe[other exceptions, ...] + +tmf-->mt +tii-->it +tec-->t + +oe-.->it +oe-.->mt +oe-.->t +end + +subgraph riscv/encoding.h +mf[CAUSE_MISALIGNED_FETCH] +ii[CAUSE_ILLEGAL_INSTRUCTION] +ec[CAUSE_USER_ECALL] +oc[other causes, ...] +end + +mf-->tmf +ii-->tii +ec-->tec +oc-->oe +``` + +([下载由 Mermaid 生成的 PNG 图片][007]) + +##### 异常抛出 + +整个模拟器在执行过程中,通过 `try, catch` 来捕获并处理每个处理器执行中的异常,这些异常是在各个组件以及指令中写定的。例如访存相关的异常都是在 MMU 的实现中规定的: + +```cpp +// riscv/mmu.h: line 364 +// ITLB lookup +inline tlb_entry_t translate_insn_addr(reg_t addr) { + // ... + return fetch_slow_path(addr); +} +// riscv/mmu.cc: line 80 +tlb_entry_t mmu_t::fetch_slow_path(reg_t vaddr) +{ + // ... + if (!mmio_load(paddr, sizeof fetch_temp, (uint8_t*)&fetch_temp)) + throw trap_instruction_access_fault(proc->state.v, vaddr, 0, 0); + result = {(char*)&fetch_temp - vaddr, paddr - vaddr}; + // ... + return result; +} +``` + +单个处理器的执行主题循环如下所示,异常和中断均是在其中的 `try, catch` 中捕获并处理的: + +```cpp +// riscv/execute.cc: line 219 +// fetch/decode/execute loop +void processor_t::step(size_t n) +{ + // ... + + while (n > 0) { + // ... + + try + { + take_pending_interrupt(); + + // 仿真的循环主体:抛出执行过程中的异常,之后进入 catch 中的处理语句 + // Main simulation loop, slow/fast path: throw exceptions if they occur during execution + // ... + } + catch(trap_t& t) + { + take_trap(t, pc); + // ... + } + // Other catch statements, instructions counting. + // ... + } +} + +``` + +#### Trap 处理 + +整个 trap 的处理过程如下图所示: + +虚线表示函数对应的实现,细实线表示函数调用关系,粗实线表示参数的传递关系。 + +```mermaid +flowchart TB + +subgraph sim.h +ss[sim_t] +end + +subgraph execute.c +ps[step] + +subgraph try_catch +try--> +ttpi[take_pending_interrupt] + +try-->mem[mmu]==throw mem_trap_t==>c + +try-->o[others]==throw insn_trap_t==>c +end + +ps-->try_catch +c[catch take_trap] +end + +subgraph processor.cc +s[sim_t::procs->step] +tpi[take_pending_interrupt] +ti[take_interrupt] +tt[take_trap] +end + +subgraph mmu +li[load_insn] +ai[access_icache] +end + +mem-->li +mem-->ai + +ss-->s-.->ps +ttpi-.->tpi-->ti +c-.->tt + +ttpi==throw trap_t==>c +ti==interrupt==>tt +``` + +([下载由 Mermaid 生成的 PNG 图片][008]) + +由图可知,最终的 trap 处理是通过调用 `riscv/process.cc` 的 `take_trap` 函数实现的,其定义及功能分析参见 [此文][2] 的模拟器实现中的 Spike 小节。简单来说,它完成了如下任务: + +- 确定该 trap 的类型(异常或者中断)以及它将在哪个特权级被处理(默认 M,但可以通过 medeleg/mideleg 和 hedeleg/hideleg 委托给 HS 或 VS) +- 写入 CSR(`cause`, `epc`, `tval`)以记录 trap 内容,保存当前执行环境用于处理完成之后的恢复 +- 修改 `status` 寄存器及当前特权级,进入 trap 处理程序运行环境 + +### CLINT + +CLINT(Core-Local INTerrupt)是 RISC-V 核间局部中断控制器,用于管理软件中断和计时器中断的注入和解除。 + +在 Spike 中,CLINT 是 `bus` 上的设备之一 `device`,CPU 对 CLINT 和 `mems` 等的访问通过 `mmio.store/load` 来实现。所有的 mmio 访存均是通过调用 `bus.load/store` 完成的,例如 mmu_t 和 sim_t 的 load, store 操作: + +```mermaid +flowchart + +subgraph riscv/abstract_device.h +ad[abstract_device_t] +end + +subgraph riscv/devices.h +direction TB +cl[clint_t]-->ad +rom[rom_device_t]-->ad +mem[mem_t]-->ad +plic[plic_t]-->ad +ns[ns16550_t]-->ad +plg[mmio_plugin_device_t]-->ad + +subgraph bus_t +d[devices:map] +access[load/store] +end + +cl-.-d +rom-.-d +mem-.-d +plic-.-d +ns-.-d +plg-.-d + +bus_t-->ad +end + +subgraph riscv/sim.cc:sim_t +ldst[mmio_load/store]-->access +end + +subgraph riscv/mmu.cc:mmu_t +direction LR +mldst[mmio_load/store]-->ldst +spi[store_slow_path_intrapage]-->mldst +ssp[store_slow_path]-->spi +store[store, ...]-->ssp +end +``` + +([下载由 Mermaid 生成的 PNG 图片][009]) + +而 CPU 通过 `bus` 实现的访存,是先通过地址所在的范围确定对应的具体设备,进而调用该设备的访存函数来完成的: + +```cpp +bool bus_t::load(reg_t addr, size_t len, uint8_t* bytes) +{ + // Find the device with the base address closest to but + // less than addr (price-is-right search) + auto it = devices.upper_bound(addr); + if (devices.empty() || it == devices.begin()) { + // Either the bus is empty, or there weren't + // any items with a base address <= addr + return false; + } + // Found at least one item with base address <= addr + // The iterator points to the device after this, so + // go back by one item. + it--; + return it->second->load(addr - it->first, len, bytes); +} + +bool bus_t::store(reg_t addr, size_t len, const uint8_t* bytes) +{ + auto it = devices.upper_bound(addr); + if (devices.empty() || it == devices.begin()) { + return false; + } + it--; + return it->second->store(addr - it->first, len, bytes); +} +``` + +对于 CLINT 来说,其所担负的软件中断和计时器中断的职能,就是通过 `clint_t` 的 `load/store` 方法配合对应 CSR 的访问来实现的,参考 [此文][3]: + +```cpp +// riscv/clint.cc: line 48 +bool clint_t::store(reg_t addr, size_t len, const uint8_t* bytes) +{ + // 若地址在 MSIP 内,表示当前中断为软件中断,将 byte 内的内容写入 addr 作为偏移量所指示的位置,即写入软件中断 + if (addr >= MSIP_BASE && addr + len <= MSIP_BASE + procs.size()*sizeof(msip_t)) { + std::vector msip(procs.size()); + std::vector mask(procs.size(), 0); + memcpy((uint8_t*)&msip[0] + addr - MSIP_BASE, bytes, len); + memset((uint8_t*)&mask[0] + addr - MSIP_BASE, 0xff, len); + for (size_t i = 0; i < procs.size(); ++i) { + if (!(mask[i] & 0xFF)) continue; + procs[i]->state.mip->backdoor_write_with_mask(MIP_MSIP, 0); + if (!!(msip[i] & 1)) + procs[i]->state.mip->backdoor_write_with_mask(MIP_MSIP, MIP_MSIP); + } + } else if (addr >= MTIMECMP_BASE && addr + len <= MTIMECMP_BASE + procs.size()*sizeof(mtimecmp_t)) { + // 设置计时器中断:向 mtimecmp 寄存器写入特定值,待到 mtime 达到该值时产生一个中断 + memcpy((uint8_t*)&mtimecmp[0] + addr - MTIMECMP_BASE, bytes, len); + } else if (addr >= MTIME_BASE && addr + len <= MTIME_BASE + sizeof(mtime_t)) { + // 设置 mtime 的值 + memcpy((uint8_t*)&mtime + addr - MTIME_BASE, bytes, len); + } else { + return false; + } + increment(0); + return true; +} +``` + +### PLIC/Interrupt Controller/NS16550 + +PLIC(Platform-Level Interrupt Controller)是 RISC-V 外部中断控制器,而 NS16550 则是一个 [UART(Universal Asynchronous Receiver/Transmitter)软核][4],用于 CPU 和外设之间的通信。CLINT,PLIC 以及 NS16550 都要挂载在设备树(fdt, flatten device tree)上。NS16550 在创建时会以 PLIC 指针为中断控制器,用来向 CPU 发送外部中断。 + +与 CLINT 类似,PLIC 等设备在创建之后也都几乎没有用到除计时之外的功能。 + +## QEMU + +### Trap 相关术语及定义 + +中断统一以 IRQ 作为前缀命名: + +```cpp +// target/riscv/cpu_bits.h: line 609 +/* Interrupt causes */ +#define IRQ_U_SOFT 0 +#define IRQ_S_SOFT 1 +#define IRQ_VS_SOFT 2 +#define IRQ_M_SOFT 3 +#define IRQ_U_TIMER 4 +#define IRQ_S_TIMER 5 +#define IRQ_VS_TIMER 6 +#define IRQ_M_TIMER 7 +#define IRQ_U_EXT 8 +#define IRQ_S_EXT 9 +#define IRQ_VS_EXT 10 +#define IRQ_M_EXT 11 +#define IRQ_S_GEXT 12 +#define IRQ_LOCAL_MAX 16 +#define IRQ_LOCAL_GUEST_MAX (TARGET_LONG_BITS - 1) +``` + +异常定义为枚举变量: + +```cpp +// target/riscv/cpu_bits.h: line 581 +/* Exception causes */ +typedef enum RISCVException { + RISCV_EXCP_NONE = -1, /* sentinel value */ + RISCV_EXCP_INST_ADDR_MIS = 0x0, + RISCV_EXCP_INST_ACCESS_FAULT = 0x1, + RISCV_EXCP_ILLEGAL_INST = 0x2, + RISCV_EXCP_BREAKPOINT = 0x3, + RISCV_EXCP_LOAD_ADDR_MIS = 0x4, + RISCV_EXCP_LOAD_ACCESS_FAULT = 0x5, + RISCV_EXCP_STORE_AMO_ADDR_MIS = 0x6, + RISCV_EXCP_STORE_AMO_ACCESS_FAULT = 0x7, + RISCV_EXCP_U_ECALL = 0x8, + RISCV_EXCP_S_ECALL = 0x9, + RISCV_EXCP_VS_ECALL = 0xa, + RISCV_EXCP_M_ECALL = 0xb, + RISCV_EXCP_INST_PAGE_FAULT = 0xc, /* since: priv-1.10.0 */ + RISCV_EXCP_LOAD_PAGE_FAULT = 0xd, /* since: priv-1.10.0 */ + RISCV_EXCP_STORE_PAGE_FAULT = 0xf, /* since: priv-1.10.0 */ + RISCV_EXCP_SEMIHOST = 0x10, + RISCV_EXCP_INST_GUEST_PAGE_FAULT = 0x14, + RISCV_EXCP_LOAD_GUEST_ACCESS_FAULT = 0x15, + RISCV_EXCP_VIRT_INSTRUCTION_FAULT = 0x16, + RISCV_EXCP_STORE_GUEST_AMO_ACCESS_FAULT = 0x17, +} RISCVException; +``` + +所有的 CSR 操作函数其返回值均为 `RISCVException`,例如: + +```cpp +// target/riscv/csr.c: line 1468 +static RISCVException read_mtvec(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->mtvec; + return RISCV_EXCP_NONE; +} +// target/riscv/csr.c: line 832 +static const target_ulong vs_delegable_excps = DELEGABLE_EXCPS & + ~((1ULL << (RISCV_EXCP_S_ECALL)) | + (1ULL << (RISCV_EXCP_VS_ECALL)) | + (1ULL << (RISCV_EXCP_M_ECALL)) | + (1ULL << (RISCV_EXCP_INST_GUEST_PAGE_FAULT)) | + (1ULL << (RISCV_EXCP_LOAD_GUEST_ACCESS_FAULT)) | + (1ULL << (RISCV_EXCP_VIRT_INSTRUCTION_FAULT)) | + (1ULL << (RISCV_EXCP_STORE_GUEST_AMO_ACCESS_FAULT))); +``` + +### Trap 处理 + +QEMU 内部通过 `DEFINE_TYPES` 注册 RISC-V CPU 的相关信息,其中就包括中断处理相关的操作。内部中断(Software,Timer)通过将中断处理函数 `riscv_cpu_do_interrupt` 注册到 `tcg_ops` 里面来实现,外部中断则通过 CPU 初始化函数中的 GPIO 设备配置来完成。 + +整体结构如下图所示: + +```mermaid +flowchart TB + +subgraph cpu.c + +define[DEFINE_TYPES: riscv_cpu_type_infos]--> +ti[riscv_cpu_type_infos]-->ci[.class_init = riscv_cpu_class_init]-->tcg[riscv_tcg_ops] + +ti-->ii[.instance_init = riscv_cpu_init] +end + +subgraph cpu.c +ii-->igpioi[qdev_init_gpio_in]-->sirq[riscv_cpu_set_irq] +end + +subgraph kvm.c +ksirq[kvm_riscv_set_irq] +end + +ioctl[kvm_vcpu_ioctl: KVM_INTERRUPT] + +sirq-->ksirq-->ioctl + +subgraph cpu_helper.c +eint[riscv_cpu_exec_interrupt] +dint[riscv_cpu_do_interrupt] +end + +tcg-->eint-->dint +tcg-->dint +``` + +([下载由 Mermaid 生成的 PNG 图片][010]) + +用于依据 `type_infos` 进行初始化的函数如下: + +```cpp +// include/qom/object.h: line 849 +/** + * DEFINE_TYPES: + * @type_array: The array containing #TypeInfo structures to register + * + * @type_array should be static constant that exists for the life time + * that the type is registered. + */ +#define DEFINE_TYPES(type_array) \ +static void do_qemu_init_ ## type_array(void) \ +{ \ + type_register_static_array(type_array, ARRAY_SIZE(type_array)); \ +} \ +type_init(do_qemu_init_ ## type_array) + +// include/qemu/module.h: line 56 +#define type_init(function) module_init(function, MODULE_INIT_QOM) +// include/qemu/module.h: line 34 +/* This should not be used directly. Use block_init etc. instead. */ +#define module_init(function, type) \ +static void __attribute__((constructor)) do_qemu_init_ ## function(void) \ +{ \ + register_module_init(function, type); \ +} +#endif +``` + +```cpp +// util/module.c: line 69 +void register_module_init(void (*fn)(void), module_init_type type) +{ + ModuleEntry *e; + ModuleTypeList *l; + + e = g_malloc0(sizeof(*e)); + e->init = fn; + e->type = type; + + l = find_type(type); + + QTAILQ_INSERT_TAIL(l, e, node); +} +``` + +## 总结 + +本文对两个模拟器中 RISC-V 的 Trap 定义和处理过程进行了较为详细地调研,为实际上手进行软硬件实现提供了一定的参考。 + +## 参考资料 + +- [RISC-V CSR 编码生成器][1] +- [RISC-V 架构 H 扩展中的 Trap 处理][2] +- [哪吒 D1 开发板 RISC-V CLINT 编程实践][3] + +[1]: https://github.com/riscv/riscv-opcodes +[2]: 20220905-riscv-kvm-virt-trap.md#spike +[3]: https://cloud.tencent.com/developer/article/1851557 +[4]: https://www.latticesemi.com/zh-CN/Products/DesignSoftwareAndIP/IntellectualProperty/IPCore/DCDCores/D16550 +[5]: https://www.qemu.org/ +[6]: https://github.com/riscv-software-src/riscv-isa-sim +[007]: images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-1.png +[008]: images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-2.png +[009]: images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-3.png +[010]: images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-4.png diff --git a/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-1.png b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..75fb405deb0c175d6923e5fb4339b2a4bf480a54 Binary files /dev/null and b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-1.png differ diff --git a/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-2.png b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-2.png new file mode 100644 index 0000000000000000000000000000000000000000..da018e9babb0340e4bbf1dac896d4e0154e70514 Binary files /dev/null and b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-2.png differ diff --git a/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-3.png b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-3.png new file mode 100644 index 0000000000000000000000000000000000000000..28b67d0de8805e581b57d706726c746843b2d031 Binary files /dev/null and b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-3.png differ diff --git a/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-4.png b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-4.png new file mode 100644 index 0000000000000000000000000000000000000000..c78785fd12cbdd4dc735c0bb149dba81e6386602 Binary files /dev/null and b/articles/images/riscv-riscv_kvm_int_impl_1/mermaid-riscv-kvm-int-impl-1-4.png differ