diff --git a/articles/20230207-riscv-kvm-int-impl-2.md b/articles/20230207-riscv-kvm-int-impl-2.md
new file mode 100644
index 0000000000000000000000000000000000000000..c880b8e90edb4143de4998fcc9018c89c35a8b90
--- /dev/null
+++ b/articles/20230207-riscv-kvm-int-impl-2.md
@@ -0,0 +1,723 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [tounix spaces tables images urls]
+> Author: XiakaiPan <13212017962@163.com>
+> Date: 20230109
+> 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 中断处理的实现(二)
+
+## 前言
+
+本文对于 kvmtool 和 KVM 中的中断注入与处理,以及 MMIO 设备的注册与使用,结合代码进行了分析和解读,并主要以流程图的方式呈现其代码实现。
+
+## 代码版本
+
+| Software | Version |
+|-------------------|------------------------------------------|
+| [Linux Kernel][1] | 6.0-rc6 |
+| [kvmtool][6] | e17d182ad3f797f01947fc234d95c96c050c534b |
+
+## KVM 异常处理
+
+### RISC-V Trap 类型、编码及其关系
+
+在 RISC-V 中,CSR `mcause` / `scause` / `vscause` 用于记录引发 Trap 的编码,Interrupt 和 Exception 的区分是通过 CSR 最高位作为标志位来实现的,当标志位为 1 时表示当前 Trap 为 Interrupt,为 0 时则是 Exception。
+
+RISC-V 中的中断分为三类:软件中断、计时器中断和外部中断,来自不同特权级的各类中断具有各自的编码。Linux 中对这些中断编码如下:
+
+```cpp
+// arch/riscv/include/asm/csr.h: line 66
+/* Exception cause high bit - is an interrupt if set */
+#define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
+
+/* Interrupt causes (minus the high bit) */
+#define IRQ_S_SOFT 1
+#define IRQ_VS_SOFT 2
+#define IRQ_M_SOFT 3
+#define IRQ_S_TIMER 5
+#define IRQ_VS_TIMER 6
+#define IRQ_M_TIMER 7
+#define IRQ_S_EXT 9
+#define IRQ_VS_EXT 10
+#define IRQ_M_EXT 11
+#define IRQ_PMU_OVF 13
+
+/* Exception causes */
+#define EXC_INST_MISALIGNED 0
+#define EXC_INST_ACCESS 1
+#define EXC_INST_ILLEGAL 2
+#define EXC_BREAKPOINT 3
+#define EXC_LOAD_ACCESS 5
+#define EXC_STORE_ACCESS 7
+#define EXC_SYSCALL 8
+#define EXC_HYPERVISOR_SYSCALL 9
+#define EXC_SUPERVISOR_SYSCALL 10
+#define EXC_INST_PAGE_FAULT 12
+#define EXC_LOAD_PAGE_FAULT 13
+#define EXC_STORE_PAGE_FAULT 15
+#define EXC_INST_GUEST_PAGE_FAULT 20
+#define EXC_LOAD_GUEST_PAGE_FAULT 21
+#define EXC_VIRTUAL_INST_FAULT 22
+#define EXC_STORE_GUEST_PAGE_FAULT 23
+```
+
+中断标记前缀为 `IRQ`(Interrupt ReQuest),异常标记前缀为 `EXC`(EXCeption)。
+
+### KVM 异常处理
+
+KVM 内部处理的是来自于 Guest 的异常,具体来说包括三类:
+
+- 指令异常:对应 Guest 的虚拟指令异常
+- 内存异常:对应 Guest page-fault
+- 环境调用:对应来自于 Guest 在 VS-mode 的 `ecall` 指令
+
+详细代码分析参见 [此文][2]。
+
+## KVM 虚拟化相关的中断处理
+
+在 Linux 内核的 `arch/riscv/kvm` 目录下,实现了对 RISC-V 虚拟化扩展的支持,此节将分析其中有关中断处理的代码实现。据代码可知,KVM 的架构相关的实现中仅包括了 VS-mode 对应的一系列中断的处理,其它中断的处理机制见下一节中断控制器分析。
+
+### 全局中断基准
+
+如果仅支持 M-Mode,那么默认的中断使能(Interrupt Enable)、Trap 向量、中断请求均以 M-Mode 为基准:
+
+- CSR 使用 `mstatus`, `mie`, `mtvec`, `mcause` 等
+- 状态寄存器标志以 `mstatus` 的为准:`mstatus.mie`, `mstatus.mpie`, `mstatus.mpp`
+- 中断编码均对应 M-Mode:`IRQ_M_SOFT/TIMER/EXT`
+
+否则,就以 S-Mode 为基准,如下方代码所示。
+
+```cpp
+// arch/riscv/include/asm/csr.h: line 300
+#ifdef CONFIG_RISCV_M_MODE
+/* CSR */
+# define CSR_STATUS CSR_MSTATUS
+# define CSR_IE CSR_MIE
+# define CSR_TVEC CSR_MTVEC
+# define CSR_SCRATCH CSR_MSCRATCH
+# define CSR_EPC CSR_MEPC
+# define CSR_CAUSE CSR_MCAUSE
+# define CSR_TVAL CSR_MTVAL
+# define CSR_IP CSR_MIP
+
+/* Status Register Flags */
+# define SR_IE SR_MIE
+# define SR_PIE SR_MPIE
+# define SR_PP SR_MPP
+
+/* Interrupt Cause */
+# define RV_IRQ_SOFT IRQ_M_SOFT
+# define RV_IRQ_TIMER IRQ_M_TIMER
+# define RV_IRQ_EXT IRQ_M_EXT
+#else /* CONFIG_RISCV_M_MODE */
+# define CSR_STATUS CSR_SSTATUS
+# define CSR_IE CSR_SIE
+# define CSR_TVEC CSR_STVEC
+# define CSR_SCRATCH CSR_SSCRATCH
+# define CSR_EPC CSR_SEPC
+# define CSR_CAUSE CSR_SCAUSE
+# define CSR_TVAL CSR_STVAL
+# define CSR_IP CSR_SIP
+
+# define SR_IE SR_SIE
+# define SR_PIE SR_SPIE
+# define SR_PP SR_SPP
+
+# define RV_IRQ_SOFT IRQ_S_SOFT
+# define RV_IRQ_TIMER IRQ_S_TIMER
+# define RV_IRQ_EXT IRQ_S_EXT
+# define RV_IRQ_PMU IRQ_PMU_OVF
+# define SIP_LCOFIP (_AC(0x1, UL) << IRQ_PMU_OVF)
+
+#endif /* !CONFIG_RISCV_M_MODE */
+
+/* IE/IP (Supervisor/Machine Interrupt Enable/Pending) flags */
+#define IE_SIE (_AC(0x1, UL) << RV_IRQ_SOFT)
+#define IE_TIE (_AC(0x1, UL) << RV_IRQ_TIMER)
+#define IE_EIE (_AC(0x1, UL) << RV_IRQ_EXT)
+```
+
+M/S-Mode 的中断做统一处理,Guest 内部的 VS-Mode 中断将由 KVM 单独处理。下面将对三类中断的实现分别进行分析。
+
+### VS-Mode 软件中断
+
+所谓软件中断也称为 IPI(Inter-Processor Interrupt),即处理器间中断。对于 KVM 虚拟机来说,VS-mode 的软件中断是通过 SBI 进行处理的,如下图所示。
+
+具体注入过程如下:
+1. 某个发送 vCPU 通过在 VS-mode 调用 ecall,给另外一个接收 vCPU 发送 IPI 中断。
+2. 此时触发发送 vCPU 所在 pCPU 的 HS-mode 异常,退出到 kvm_riscv_vcpu_exit 中,之后处理流程为:`kvm_riscv_vcpu_exit -> kvm_riscv_vcpu_sbi_ecall() -> sbi_ext->handler() -> kvm_sbi_ext_ipi_handler() -> kvm_riscv_vcpu_set_interrupt ()`
+3. 最后 `kvm_riscv_vcpu_set_interrupt()` 函数,把 IPI 注入到接收 vCPU 的标志位上,`vcpu->arch.irqs_pending` 和 `vcpu->arch.irqs_pending_mask`,然后调用 `kvm_vcpu_kick()` 函数,提醒接收 vCPU,处理 IPI。实际上就是向接收 vCPU 所在的 pCPU 发送 HS-mode IPI(通过函数 `smp_send_reschedule()` 发送),让接收 vCPU 退出。
+4. 接收 vCPU 退出后,在重新进入运行前,会运行 `kvm_riscv_vcpu_flush_interrupts()` 函数,把 VS-level software interrupt 写入接收 vCPU 的 `vcpu->arch.guest_csr.hvip` 里,然后 `kvm_riscv_update_hvip()` 函数把 `vcpu->arch.guest_csr.hvip` 写入到 CSR_HVIP,即这个 pCPU 的 HVIP CSR 里。
+5. 接收 vCPU 运行到 VS-Mode 后,VS-level software interrupt 触发,由 VS-Mode 的 Guest OS 处理这个 IPI。
+
+```mermaid
+flowchart
+
+subgraph arch/riscv/include/asm/csr.h
+isft[IRQ_VS_SOFT]
+end
+
+subgraph arch/riscv/kvm/main.c
+hwen[kvm_arch_hardware_enable]
+end
+
+subgraph virt/kvm/kvm_main.c
+startcpu[kvm_starting_cpu]-->hwennl
+hwenall[hardware_enable_all]-->hwennl
+mdl_init[module_kvm_init]-->
+rv_init[riscv_kvm_init]-->
+kvm_init[kvm_init]-->ops
+kvm_exit[kvm_exit]-->ops
+ops[kvm_syscore_ops]-->
+resume[kvm_resume]-->hwennl
+hwennl[hardware_enable_nolock]-->hwen
+
+vcpu[kvm_vcpu_ioctl]-->run
+
+dev_ioctl[kvm_dev_ioctl]-->
+dev_create_vm[kvm_dev_ioctl_create_vm]-->
+cvm[kvm_create_vm]-->hwenall
+kvm_init-->startcpu
+
+kvm_compat[kvm_vcpu_compat_ioctl]-->vcpu
+
+exp_exit[EXPORT_SYMBOL_GPL]-->kvm_exit
+
+vm[kvm_vm_ioctl]-->
+cvcpu[kvm_vm_ioctl_create_vcpu]-->
+vcpu_fd[create_vcpu_fd]-->
+fops[kvm_vcpu_fops]-->kvm_compat
+end
+
+subgraph arch/riscv/kvm/vcpu_sbi_replace.c
+ipi[kvm_sbi_ext_ipi_handler]
+sbi_ipi[vcpu_sbi_ext_ipi]-->ipi
+end
+
+subgraph arch/riscv/kvm/vcpu_sbi.c
+ecall[kvm_riscv_vcpu_sbi_ecall]-->
+sbi[sbi_ext]-->sbi_ipi
+
+sbi-->sbiv01
+end
+
+subgraph arch/riscv/kvm/vcpu.c
+ustint[kvm_riscv_vcpu_unset_interrupt]
+stint[kvm_riscv_vcpu_set_interrupt]
+syncint[kvm_riscv_vcpu_sync_interrupts]-->isft
+run[kvm_arch_vcpu_ioctl_run]-->syncint
+end
+
+subgraph arch/riscv/kvm/vcpu_sbi_v01.c
+sbiv01[vcpu_sbi_ext_v01]-->
+v01[kvm_sbi_ext_v01_handler]-->stint
+v01-->ustint
+end
+
+ipi-->stint
+stint-->isft
+ustint-->isft
+hwen-->isft
+
+subgraph arch/riscv/kvm/vcpu_exit.c
+exit[kvm_riscv_vcpu_exit]-->ecall
+end
+```
+
+([下载由 Mermaid 生成的 PNG 图片][007])
+
+### VS-Mode 计时器中断
+
+与 VS-mode 软件中断类似,vCPU 的计时器中断处理接口在 `arch/riscv/kvm/vcpu_timer.c` 中定义,而这些接口则是通过调用 `vcpu.c` 中统一的中断处理函数实现的(`kvm_riscv_vcpu_has/set/unset_interrupts`)。
+
+```mermaid
+flowchart LR
+
+subgraph arch/riscv/include/asm/csr.h
+itimer[IRQ_VS_TIMER]
+end
+
+subgraph arch/riscv/kvm/vcpu.c
+ustint[kvm_riscv_vcpu_unset_interrupt]-->itimer
+stint[kvm_riscv_vcpu_set_interrupt]-->itimer
+hasint[kvm_riscv_vcpu_has_interrupts]-->itimer
+end
+
+subgraph arch/riscv/kvm/vcpu_timer.c
+expired[kvm_riscv_vcpu_hrtimer_expired]-->stint
+update[kvm_riscv_vcpu_update_hrtimer]-->ustint
+pending[kvm_riscv_vcpu_timer_pending]-->hasint
+
+init[kvm_riscv_vcpu_timer_init]-->expired
+init-->update
+
+init-->
+vstimer_expired[kvm_riscv_vcpu_vstimer_expired]
+
+init-->
+vstimecmp_update[kvm_riscv_vcpu_update_vstimecmp]
+end
+
+subgraph arch/riscv/kvm/vcpu.c
+vcpu_create[kvm_arch_vcpu_create]-->init
+
+vcpu_pending[kvm_cpu_has_pending_timer]-->pending
+end
+
+subgraph virt/kvm/kvm_main.c
+check[kvm_vcpu_check_block]-->vcpu_pending
+end
+
+```
+
+([下载由 Mermaid 生成的 PNG 图片][008])
+
+### VS-Mode 外部中断
+
+#### KVM 中的 ioctl
+
+##### ioctl
+
+从 Kernel 到 VM:调用 `ioctl` 注册 KVM 虚拟机并为其申请资源。具体实现可以参见 [此文][15] 中有关 kvmtool 创建 VM 的部分。
+
+kvmtool 作为用户态程序,对于 VM 的所有访问都是通过 `ioctl` 完成的,例如 `kvm_cpu__arch_init` 初始化 VM、vCPU 和内存:
+
+```cpp
+struct kvm_cpu *kvm_cpu__arch_init(struct kvm *kvm, unsigned long cpu_id)
+{
+ // ...
+ /* 创建 vCPU */
+ vcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, cpu_id);
+
+ // ...
+ /* 获取 VM 的寄存器 */
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+
+ // ...
+}
+```
+
+`ioctl` 函数自身定义如下:
+
+```cpp
+#include
+
+int ioctl(int fd, unsigned long request, ...);
+```
+
+##### kvm_*_ioctl
+
+从 VM 到 Kernel:VM 内部触发 IO 控制,调用 `kvm_*_ioctl` 进行处理
+
+```mermaid
+flowchart
+
+subgraph kvm
+direction LR
+i
+e
+fops
+end
+
+subgraph i[kvm_*_ioctl]
+vcpu/device/vm/dev
+end
+
+i-->e
+
+subgraph fops[kvm_*_fops]
+vcpu/device/vm/chardev
+end
+
+subgraph e[elements_in_fops]
+...
+ui[unlocked_ioctl]
+end
+
+e-->fops-->vfs
+
+vfs[vfs_ioctl]-->
+ept(EXPORT_SYMBOL)
+
+vfs-->dvfs[do_vfs_ioctl]
+
+dvfs-->d3(SYSCALL_DEFINE3)
+dvfs-->cd3(COMPAT_SYSCALL_DEFINE3)
+
+subgraph fs/ioctl
+vfs
+dvfs
+end
+
+subgraph include/linux
+
+subgraph syscalls
+d3
+cd3
+end
+
+subgraph export
+ept
+end
+
+end
+```
+
+([下载由 Mermaid 生成的 PNG 图片][009])
+
+#### 外部中断
+
+`kvm_vcpu_ioctl` 函数作为 `kvm_vcpu_fops.unlocked_ioctl` 在 KVM 初始化之时就已经被注册。当发生对 `/dev/kvm` 的 `ioctl` 调用时,就会通过如上节所述的 `vfs_ioctl` 方法调用 `filp->f_op->unlocked_ioctl` 即 `kvm_vcpu_ioctl` 进行处理。
+
+KVM 内部与 VS-Mode 外部中断相关的调用如下图所示:
+
+```mermaid
+flowchart LR
+
+subgraph arch/riscv/include/asm/csr.h
+ext[IRQ_VS_EXT]
+end
+
+subgraph arch/riscv/kvm/vcpu.c
+
+async[kvm_arch_vcpu_async_ioctl]-->
+int[kvm_riscv_vcpu_set/unset_interrupt]-->ext
+end
+
+subgraph virt/kvm/kvm_main.c
+vcpu[kvm_vcpu_ioctl]-->async
+end
+
+```
+
+([下载由 Mermaid 生成的 PNG 图片][010])
+
+([下载由 Mermaid 生成的 PNG 图片][009])
+
+`kvm_arch_vcpu_async_ioctl` 内部实现依据具体的中断类型采取对应的操作:
+
+```cpp
+// arch/riscv/kvm/vcpu.c: line 569
+
+long kvm_arch_vcpu_async_ioctl(struct file *filp,
+ unsigned int ioctl, unsigned long arg)
+{
+ struct kvm_vcpu *vcpu = filp->private_data;
+ void __user *argp = (void __user *)arg;
+
+ if (ioctl == KVM_INTERRUPT) {
+ struct kvm_interrupt irq;
+
+ // 将用户态的由 argp 所指向的中断信息复制到 irq 中
+ if (copy_from_user(&irq, argp, sizeof(irq)))
+ return -EFAULT;
+
+ // 根据 irq 的中断操作类型,对指定的 vcpu 进行中断操作(set, unset)
+ if (irq.irq == KVM_INTERRUPT_SET)
+ return kvm_riscv_vcpu_set_interrupt(vcpu, IRQ_VS_EXT);
+ else
+ return kvm_riscv_vcpu_unset_interrupt(vcpu, IRQ_VS_EXT);
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+```
+
+## RISC-V 中断在 Linux 中的实现
+
+### Timer 驱动
+
+参考 [此文][3] 对 RISC-V 计时器在 Linux 内核中的实现的分析,Linux Timer 的实现包含两个驱动文件:
+
+- 无 MMU 的 `drivers/clocksource/timer-riscv.c`:运行于 M-mode 下,可直接读取 `mtime` CSR 获取当前时间、通过 `mtimecmp` CSR 设置中断,考虑到虚拟化对于特权级的需求,该实现并不会在虚拟化系统中被调用。
+- 有 MMU 的 `drivers/clocksource/timer-clint.c`:支持 S-mode (S/HS/VS) 下的时钟访问,但因为权限问题,需要借助于 CSR 读写指令达成。在不支持 SSTC 扩展的情况下,需要通过 SBI 写入 `mtimecmp` 实现计时器中断。
+
+在添加了虚拟化扩展之后,VS-mode 的计时器中断操作需要通过 SBI 进入 HS-mode 再进入 M-mode,访问 `htimedelta`,`mtimecmp` 等 CSR,开销较大。后续有望通过添加 [SSTC 扩展][4] 实现对 `vstimecmp` 的直接访问进而简化虚拟情况下的中断开销。
+
+### 中断驱动与 PLIC 控制器
+
+[这篇文章][5] 基于一个 RTC(Real Time Clock)例程分析了 RISC-V 中断的申请、产生、处理流程。
+
+Linux 内核中涉及 RISC-V 中断相关的处理机制如下图所示,从左到右依次为 PLIC、INTC(INTerrupt Controller)和内核中断处理。
+
+```mermaid
+flowchart
+
+e[arch/riscv/kernel/entry.S]-->ghai
+
+subgraph kernel/irq/handle.c
+ghai[generic_handle_arch_irq]
+shi[set_handle_irq]
+end
+
+subgraph kernel/softirq.c
+ghai-->ie[irq_exit]
+ghai-.->so[others]
+end
+
+subgraph other
+end
+
+ghai-.->other
+
+subgraph drivers/irqchip/irq-riscv-intc.c
+direction
+ii[IRQCHIP_DECLARE:riscv_intc_init]-->shi-->
+rii[riscv_intc_irq]
+idm[[intc_domain]]
+end
+
+subgraph kernel/irq/irqdesc.c
+ghdi[generic_handle_domain_irq]
+end
+
+subgraph include/linux/irqdomain.h
+al[irq_domain_add_linear]
+end
+
+ii-->al-.return..->idm
+
+rii-->ghdi
+idm-.arg..->ghdi
+
+subgraph drivers/irqchip/irq-sifive-plic.c
+direction TB
+epid[IRQCHIP_DECLARE: plic_edge_init]-->pei[plic_edge_init]-->pi
+pid[IRQCHIP_DECLARE: plic_init]-->
+tpi[__plic_init]-->pi[plic_init]-->phi[plic_handle_irq]
+end
+```
+
+([下载由 Mermaid 生成的 PNG 图片][011])
+
+([下载由 Mermaid 生成的 PNG 图片][010])
+
+### 小结
+
+结合本节和上一节中有关 Linux 以及 KVM 对 RISC-V 中断的分析可知,KVM 内实现了将虚拟机内部 VS-mode 的中断与外部中断处理控制器的绑定,同时实现了特定于 VS-mode 的中断处理功能,从而完成了对于 RISC-V 虚拟化的支持。
+
+## MMIO 虚拟化
+
+### KVM
+
+通过用户态程序(如 kvmtool)创建了 vCPU 之后,vcpu 内部就包含了 MMIO 相关的项,如下图所示。如此,便实现了虚拟机 MMIO 的管理。所以 Guest 的 MMIO 操作都是基于下图所示的数据结构实现的。
+
+```mermaid
+flowchart BT
+
+subgraph v[kvm_vcpu]
+
+subgraph va[kvm_vcpu_arch]
+md[kvm_mmio_decode]
+vao[other arch states, ...]
+end
+
+subgraph r[kvm_run]
+m[mmio]
+ro[other run states, ...]
+end
+
+end
+```
+
+([下载由 Mermaid 生成的 PNG 图片][012])
+
+([下载由 Mermaid 生成的 PNG 图片][011])
+
+mmio 在 Host 一端的注册与销毁如下图所示:
+
+```mermaid
+flowchart LR
+subgraph kvm_main.c
+cv[kvm_create_vm]
+cd[kvm_destroy_vm]
+...
+end
+
+subgraph coalseced_mmio.c
+mi[kvm_coalesced_mmio_init]
+mf[kvm_coalesced_mmio_free]
+...,
+end
+
+cv-->mi
+cv-->mf
+cd-->mf
+```
+
+([下载由 Mermaid 生成的 PNG 图片][013])
+
+([下载由 Mermaid 生成的 PNG 图片][012])
+
+KVM 中的 MMIO 的访存操作有如下三个对应处理函数:
+
+```cpp
+// arch/riscv/include/asm/kvm_vcpu_insn.h: line 40
+int kvm_riscv_vcpu_mmio_load(struct kvm_vcpu *vcpu, struct kvm_run *run,
+ unsigned long fault_addr,
+ unsigned long htinst);
+int kvm_riscv_vcpu_mmio_store(struct kvm_vcpu *vcpu, struct kvm_run *run,
+ unsigned long fault_addr,
+ unsigned long htinst);
+int kvm_riscv_vcpu_mmio_return(struct kvm_vcpu *vcpu, struct kvm_run *run);
+
+```
+
+下图展示了 MMIO 访存操作的具体实现,可以发现 LAOD/STORE 操作最终是通过调用 IO 设备中注册好的读写函数来实现的:
+
+```mermaid
+flowchart LR
+subgraph vi[arch/riscv/kvm/vcpu_insn.c]
+l[kvm_riscv_vcpu_mmio_load]-->r
+s[kvm_riscv_vcpu_mmio_store]-->r
+r[kvm_riscv_vcpu_mmio_return]
+end
+
+subgraph m[virt/kvm/kvm_main.c]
+rd[kvm_io_bus_read]
+wr[kvm_io_bus_write]
+end
+
+l-->rd
+s-->wr
+
+subgraph dv[include/kvm/iodev.h]
+subgraph iodev
+subgraph ops
+frd[*read]
+fwr[*write]
+end
+
+end
+
+end
+
+rd-.->frd
+wr-.->fwr
+```
+
+([下载由 Mermaid 生成的 PNG 图片][014])
+
+([下载由 Mermaid 生成的 PNG 图片][013])
+
+### kvmtool 中断注入及 MMIO 创建
+
+在 kvmtool 中 MMIO 是作为 VIRTIO 设备之一连带着中断处理函数一起被注册的。整个过程可以分为两个部分:
+
+- PLIC,设备树初始化
+- MMIO/PCI 等设备与 PLIC 以及中断处理函数的绑定
+- Console/Net 等设备与初始化时与 MMIO/PCI 设备的绑定
+
+执行完整个 Console 的创建过程就完成了 Guest 的 PLIC、IRQ 与设备的绑定,即实现了虚拟机的中断注入机制与 MMIO 创建。
+
+下图中左上的 `virtio_dev_init:virtio_console__init` 表示以 KVM 指定的方式初始化设备完成绑定。
+
+右边 RISC-V 模块左下方的 `late_init:setup_fdt` 则表示包含有 PLIC 的设备树的初始化。
+
+```mermaid
+flowchart LR
+
+subgraph riscv
+subgraph irq.c
+il[kvm__irq_line]
+it[kvm__irq_trigger]
+end
+
+subgraph plic.c
+pit[plic__irq_trig]
+pnd[pci__generate_fdt_nodes]
+end
+il-->pit
+it-->pit
+subgraph fdt.c
+li[late_init:setup_fdt]
+end
+li-->pnd
+end
+
+subgraph virtio
+
+subgraph unified_devices
+subgraph console.c
+cdi[virtio_dev_init:virtio_console__init]
+end
+subgraph net.c
+bdi[virtio_dev_init:virtio_net__init]
+end
+udo[other unified_devices, ...]
+end
+
+cdi-->vi
+bdi-->vi
+udo-.->vi
+
+subgraph pci.c
+pvq[virtio_pci__signal_vq]-->it
+pvq-->il
+pcfg[virtio_pci__signal_config]-->it
+po[other functions, ...]
+end
+
+subgraph mmio.c
+vq[virtio_mmio_signal_vq]-->it
+cfg[virtio_mmio_signal_config]-->it
+mo[other functions, ...]
+end
+
+pm[pci-modern.c]-->il
+pl[pci-legacy.c]-->il
+
+subgraph core.c
+vi[virtio_init: case VIRTIO_*]
+cm[mmio]
+cp[pci]
+end
+
+end
+
+cm-.->mmio.c
+cp-.->pci.c
+
+subgraph hw
+i8[i8042.c]-->il
+sr[serial.c]-->il
+end
+
+```
+
+([下载由 Mermaid 生成的 PNG 图片][016])
+
+([下载由 Mermaid 生成的 PNG 图片][014])
+
+## 总结
+
+RISC-V 中断通过 PLIC,CLINT 等驱动和控制器来实现,KVM 模块对于虚拟化的支持体现在两方面,一方面是 KVM 实现了与 Guest 外部的中断控制相关联的 VS-mode 的中断处理,另一方面则是通过为用户态程序如 kvmtool 提供接口,支持了虚拟机内部的设备与中断处理函数的注册与绑定,也实现了虚拟机与内核态的绑定,这使得 Guest 的 MMIO 访存等操作顺利进行。
+
+## 参考资料
+
+- [Linux Kernel][1]
+- [RISC-V 异常处理在 KVM 中的实现][2]
+- [RISC-V timer 在 Linux 中的实现][3]
+- [RISC-V SSTC Extension][4]
+- [RISC-V 中断子系统分析——PLIC 中断处理][5]
+- [kvmtool][6]
+
+[1]: https://www.kernel.org/
+[2]: 20221021-riscv-kvm-excp-impl.md
+[3]: https://tinylab.org/riscv-timer/#kvm-vcpu_timerc
+[4]: https://github.com/riscv/riscv-time-compare/releases/download/v0.5.4/Sstc.pdf
+[5]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20220919-riscv-irq-analysis-part2-interrupt-handling-plic.md
+[6]: https://git.kernel.org/pub/scm/linux/kernel/git/will/kvmtool.git
+[007]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-1.png
+[008]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-2.png
+[009]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-3.png
+[010]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-4.png
+[011]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-5.png
+[012]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-6.png
+[013]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-7.png
+[014]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-8.png
+[15]: 20220802-kvm-user-app.md#kvmtool
+[016]: images/riscv-riscv_kvm_int_impl_2/mermaid-riscv-kvm-int-impl-2-9.png