diff --git a/articles/200220519-riscv-irq-analysis.md b/articles/200220519-riscv-irq-analysis.md
new file mode 100644
index 0000000000000000000000000000000000000000..f68bab557932b62f3658cda9da511266a813c41e
--- /dev/null
+++ b/articles/200220519-riscv-irq-analysis.md
@@ -0,0 +1,401 @@
+> Author: 通天塔 985400330@qq.com
+> Date: 2022/05/19
+> Revisor:
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+
+# RISC-V 中断子系统分析——硬件及其初始化
+
+## 前言
+
+中断子系统是 CPU 对设备进行管理的一种有效手段,设备可以通过中断来向 CPU 发出请求,进行数据的处理。这些设备有的在片上例如 SPI 控制器、I2C 控制器、UART 等内部设备,外部设备例如键盘、鼠标等。RISC-V 架构下的 CPU 是如何对外部中断进行响应的,本文将进行分析。
+
+## RISC-V 中断硬件实现
+
+### CM32M433R MCU实现
+
+ 参考手册:[Nuclei_N级别指令架构手册](https://www.rvmcu.com/quickstart-show-id-1.html#38)
+
+这是 RISC-V 中文社区中,CM32M433R 芯片中的中断实现,外部信号进入芯片后,通过中断控制器中的“使能+信号+优先级+仲裁”等逻辑,最终输出到 MCU。
+
+中断控制器内部逻辑如下图所示:
+
+
+
+ECLIC 中断控制器内部逻辑有一些可以通过寄存器进行配置,从而实现中断是否接入 MCU 的控制,uCORE 通过接收中断控制器的信号,确定是否产生中断,从而触发中断处理函数。
+
+
+
+### 蜂鸟E203 MCU实现
+
+[sifive中断处理流程](https://www.elecfans.com/d/1575408.html)
+
+
+
+蜂鸟 203 实现方法
+
+
+
+### 全志 D1 芯片实现
+
+全志 D1 芯片使用 PLIC 进行中断的管理。
+
+
+
+## RISC-V Linux 中断子系统软件实现
+
+### 中断处理流程
+
+
+
+### Risc-V CPU 中断控制器初始化
+
+上一小节是从网上找到的一款 RISC-V MCU 的中断控制器的硬件实现,我们要分析的是 RISC-V 架构下的 Linux 的软件实现。
+
+首先从设备树入手,设备树下共 4 个 CPU 核,每个节点下边有一个中断控制器,中断控制器匹配的中断代码是:
+
+```c
+ cpu@0 {
+ phandle = <0x07>;
+ device_type = "cpu";
+ reg = <0x00>;
+ status = "okay";
+ compatible = "riscv";
+ riscv,isa = "rv64imafdcsu";
+ mmu-type = "riscv,sv48";
+
+ interrupt-controller {
+ #interrupt-cells = <0x01>;
+ interrupt-controller;
+ compatible = "riscv,cpu-intc";
+ phandle = <0x08>;
+ };
+ };
+
+```
+
+在 [ Linux 启动时序 ](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20220515-riscv-linux-startup-process-analysis.md) 这篇文章中 `init_IRQ` 函数会进行设备树的匹配与遍历,从而触发对应驱动的 `init` 函数。
+
+根据设备树找到对应的驱动 `irq-riscv-intc.c`,驱动首先调用 `init` 函数,`init` 函数省略部分代码如下:
+
+```c
+static int __init riscv_intc_init(struct device_node *node,
+ struct device_node *parent)
+{
+...
+ intc_domain = irq_domain_add_linear(node, BITS_PER_LONG,
+ &riscv_intc_domain_ops, NULL);
+...
+ rc = set_handle_irq(&riscv_intc_irq);
+ if (rc) {
+ pr_err("failed to set irq handler\n");
+ return rc;
+ }
+...
+ pr_info("%d local interrupts mapped\n", BITS_PER_LONG);
+...
+}
+
+```
+
+比较重要的函数 `irq_domain_add_linear`,线性映射 ,HW interrupt ID 作为 index,通过查表可以获取对应的 IRQ number,也就是说这是一个架构无关的代码。经过查找,没有找到使用该中断的设备。
+
+### PLIC 中断控制器初始化
+
+以 RTC 为例,确定 RTC 的中断是如何触发的。设备节点匹配到的驱动为:`rtc-goldfish.c`
+
+```c
+ rtc@101000 {
+ interrupts = <0x0b>;//表明连接到0x0b中断线上
+ interrupt-parent = <0x09>;//表明归属于0x09中断控制器,经过查找是plic@c000000。
+ reg = <0x00 0x101000 0x00 0x1000>;
+ compatible = "google,goldfish-rtc";
+ };
+
+
+```
+
+rtc 设备中断连接到了 `plic@c000000` 设备上。
+
+该设备的设备节点如下:
+
+```c
+ plic@c000000 {
+ phandle = <0x09>;
+ riscv,ndev = <0x35>;
+ reg = <0x00 0xc000000 0x00 0x210000>;
+ interrupts-extended = <0x08 0x0b 0x08 0x09 0x06 0x0b 0x06 0x09 0x04 0x0b 0x04 0x09 0x02 0x0b 0x02 0x09>;
+ interrupt-controller;
+ compatible = "riscv,plic0";
+ #interrupt-cells = <0x01>;
+ #address-cells = <0x00>;
+ };
+
+```
+
+该设备节点的匹配到的驱动为:`irqchip/irq-sifive-plic.c`
+
+通过以下宏定义可知
+
+```c
+IRQCHIP_DECLARE(sifive_plic, "sifive,plic-1.0.0", plic_init);
+IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */
+IRQCHIP_DECLARE(thead_c900_plic, "thead,c900-plic", plic_init); /* for firmware driver */
+```
+
+调用设备初始化函数:`static int __init plic_init(struct device_node *node,struct device_node *parent)`
+
+```c
+static int __init plic_init(struct device_node *node,
+ struct device_node *parent)
+{
+ int error = 0, nr_contexts, nr_handlers = 0, i;
+ u32 nr_irqs;
+ struct plic_priv *priv;
+ struct plic_handler *handler;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regs = of_iomap(node, 0);//对PLIC寄存器映射
+ if (WARN_ON(!priv->regs)) {
+ error = -EIO;
+ goto out_free_priv;
+ }
+
+ error = -EINVAL;
+ of_property_read_u32(node, "riscv,ndev", &nr_irqs);//读取属性
+ if (WARN_ON(!nr_irqs))
+ goto out_iounmap;
+
+ nr_contexts = of_irq_count(node);//统计该节点的irq数量
+ printk("[nfk test] nr_contexts=%d\n",nr_contexts);//得到数量8个
+ if (WARN_ON(!nr_contexts))
+ goto out_iounmap;
+
+ error = -ENOMEM;
+ priv->irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
+ &plic_irqdomain_ops, priv);//创建IRQ域,建立线性映射
+ if (WARN_ON(!priv->irqdomain))
+ goto out_iounmap;
+...
+
+```
+
+其中 nr_contexts = 8 的由来如下:
+
+```c
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=0
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@0/interrupt-controller:ffffffff
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=1
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@0/interrupt-controller:00000009
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=2
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@1/interrupt-controller:ffffffff
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=3
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@1/interrupt-controller:00000009
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=4
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@2/interrupt-controller:ffffffff
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=5
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@2/interrupt-controller:00000009
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=6
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@3/interrupt-controller:ffffffff
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=7
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@3/interrupt-controller:00000009
+[ 0.000000] irq.np->fullname=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=8
+[ 0.000000] [nfk test] nr_contexts=8
+
+```
+
+通过分析 `of_irq_count` 函数,可知,这个函数通过 `#interrupt-cells` 获取一个中断描述所需要的参数,可以得出 `interrupts-extended` 的 16 个参数描述了 8 个中断。
+
+```
+interrupts-extended = <0x08 0x0b 0x08 0x09 0x06 0x0b 0x06 0x09 0x04 0x0b 0x04 0x09 0x02 0x0b 0x02 0x09>;
+```
+
+由于反编译的设备树,丢失了一些信息,正常的话,应该是长这个样子:
+
+```
+interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;...
+```
+
+以上函数追踪过程中尝试把挂载在 plic 下的设备节点名称打出来,尝试了很多种方法未能打出,打出来的名称一直是 `interrupt-controller`,有点疑惑,没搞懂。
+
+函数的前半部分,主要进行了一系列的初始化,读取数据,以及中断域的建立。后半部分则针对于中断进行解析和配置。
+
+```c
+ for (i = 0; i < nr_contexts; i++) {
+ struct of_phandle_args parent;
+ irq_hw_number_t hwirq;
+ int cpu, hartid;
+
+ if (of_irq_parse_one(node, i, &parent)) {//解析irq
+ pr_err("failed to parse parent for context %d.\n", i);
+ continue;
+ }
+ else
+ {
+ printk("parent.np->full_name=%s\n",parent.np->full_name);
+ }
+
+ /*
+ * Skip contexts other than external interrupts for our
+ * privilege level.
+ */
+ if (parent.args[0] != RV_IRQ_EXT)//判断是不是RV_IRQ_EXT
+ continue;
+ else
+ printk("is RV_IRQ_EXT\n");//共4个nr_handlers
+
+
+ hartid = riscv_of_parent_hartid(parent.np);//获取hartid
+ if (hartid < 0) {
+ pr_warn("failed to parse hart ID for context %d.\n", i);
+ continue;
+ }
+
+ cpu = riscv_hartid_to_cpuid(hartid);//获取cpu id
+ if (cpu < 0) {
+ pr_warn("Invalid cpuid for context %d\n", i);
+ continue;
+ }
+
+ /* Find parent domain and register chained handler */
+ if (!plic_parent_irq && irq_find_host(parent.np)) {//注册处理函数
+ plic_parent_irq = irq_of_parse_and_map(node, i);
+ if (plic_parent_irq)
+ irq_set_chained_handler(plic_parent_irq,
+ plic_handle_irq);
+ printk("[nfk test]set chained handler\n");
+ }
+
+ /*
+ * When running in M-mode we need to ignore the S-mode handler.
+ * Here we assume it always comes later, but that might be a
+ * little fragile.
+ */
+ handler = per_cpu_ptr(&plic_handlers, cpu);
+ if (handler->present) {
+ pr_warn("handler already present for context %d.\n", i);
+ plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD);
+ goto done;
+ }//处理函数已经设置则直接done
+
+ cpumask_set_cpu(cpu, &priv->lmask);
+ handler->present = true;
+ handler->hart_base =
+ priv->regs + CONTEXT_BASE + i * CONTEXT_PER_HART;
+ raw_spin_lock_init(&handler->enable_lock);
+ handler->enable_base =
+ priv->regs + ENABLE_BASE + i * ENABLE_PER_HART;
+ handler->priv = priv;
+done:
+ for (hwirq = 1; hwirq <= nr_irqs; hwirq++)
+ plic_toggle(handler, hwirq, 0);//写寄存器,清零掩码寄存器
+ nr_handlers++;
+ }
+
+ /*
+ * We can have multiple PLIC instances so setup cpuhp state only
+ * when context handler for current/boot CPU is present.
+ */
+ handler = this_cpu_ptr(&plic_handlers);//热插拔相关设置
+ if (handler->present && !plic_cpuhp_setup_done) {
+ cpuhp_setup_state(CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING,
+ "irqchip/sifive/plic:starting",
+ plic_starting_cpu, plic_dying_cpu);
+ plic_cpuhp_setup_done = true;
+ }
+
+ pr_info("%pOFP: mapped %d interrupts with %d handlers for"
+ " %d contexts.\n", node, nr_irqs, nr_handlers, nr_contexts);
+ return 0;
+
+out_iounmap:
+ iounmap(priv->regs);
+out_free_priv:
+ kfree(priv);
+ return error;
+}
+```
+
+一共 4 个 handler,打印如下:
+
+```c
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=0
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@0/interrupt-controller:ffffffff
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=1
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@0/interrupt-controller:00000009
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] is RV_IRQ_EXT
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=1
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@0/interrupt-controller:00000009
+[ 0.000000] [nfk test]set chained handler
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=2
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@1/interrupt-controller:ffffffff
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=3
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@1/interrupt-controller:00000009
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] is RV_IRQ_EXT
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=4
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@2/interrupt-controller:ffffffff
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=5
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@2/interrupt-controller:00000009
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] is RV_IRQ_EXT
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=6
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@3/interrupt-controller:ffffffff
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] of_irq_parse_one: dev=/soc/plic@c000000, index=7
+[ 0.000000] of_irq_parse_raw: /cpus/cpu@3/interrupt-controller:00000009
+[ 0.000000] parent.np->full_name=interrupt-controller
+[ 0.000000] is RV_IRQ_EXT
+```
+
+### CLINT 中断控制器初始化
+
+clint 也是 RISC-V 架构下的中断控制器,clint 只负责软件中断和定时器中断,属于内部中断。
+
+```
+clint@2000000 {
+interrupts-extended = <0x08 0x03 0x08 0x07 0x06 0x03 0x06 0x07 0x04 0x03 0x04 0x07 0x02 0x03 0x02 0x07>;
+reg = <0x00 0x2000000 0x00 0x10000>;
+compatible = "riscv,clint0";
+};
+```
+
+驱动匹配到 `clocksource/timer-clint.c`
+
+初始化函数为 `clint_timer_init_dt`
+
+函数所做的工作基本与 PLIC 中断控制器一样,主要做的工作有:
+
+1、统计中断数量
+
+2、进行 IO 映射
+
+3、注册中断函数
+
+4、热插拔配置
+
+## 小结
+
+本文进行了 RISC-V 架构下的中断控制器进行了简单的分析,从硬件的角度和软件初始化的角度进行了分析,后续会在其他驱动如何触发中断,以及触发中断之后的流程进行进一步的分析。
+
+参考资料:
+
+[如何分析 Linux 内核 RISC-V 架构相关代码](https://tinylab.org/riscv-linux-quickstart/)
+
+[Nuclei_N级别指令架构手册](https://www.rvmcu.com/quickstart-show-id-1.html#38)
+
+[sifive中断处理流程](https://www.elecfans.com/d/1575408.html)
\ No newline at end of file
diff --git a/articles/images/riscv-irq-analysis/image-20220519234513284.png b/articles/images/riscv-irq-analysis/image-20220519234513284.png
new file mode 100644
index 0000000000000000000000000000000000000000..c44e9da27febea41b99c540bafda84d522a51a7d
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220519234513284.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220519234540201.png b/articles/images/riscv-irq-analysis/image-20220519234540201.png
new file mode 100644
index 0000000000000000000000000000000000000000..c35b483bdb4d49b5abca2b31515c8b534b80b308
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220519234540201.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220526234058899.png b/articles/images/riscv-irq-analysis/image-20220526234058899.png
new file mode 100644
index 0000000000000000000000000000000000000000..36b0a64fb7886387194f0ac134c43a28ea40433c
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220526234058899.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220526234133423.png b/articles/images/riscv-irq-analysis/image-20220526234133423.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0081fa9bc2d79bf7af39edccaefa2ce7cba3acc
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220526234133423.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220610000250720.png b/articles/images/riscv-irq-analysis/image-20220610000250720.png
new file mode 100644
index 0000000000000000000000000000000000000000..edcc370aa60ecdee818e083865d2c4bbabb24ae2
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220610000250720.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220612175448894.png b/articles/images/riscv-irq-analysis/image-20220612175448894.png
new file mode 100644
index 0000000000000000000000000000000000000000..851768531aa2446ad06ad8bc42b11298c63255bd
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220612175448894.png differ
diff --git a/articles/images/riscv-irq-analysis/image-20220612180146138.png b/articles/images/riscv-irq-analysis/image-20220612180146138.png
new file mode 100644
index 0000000000000000000000000000000000000000..c515bea5918416e4ea25293ff0ff9831fe6c1990
Binary files /dev/null and b/articles/images/riscv-irq-analysis/image-20220612180146138.png differ