From 59fa0f0746d612084ee5a76e32c4044b099289b2 Mon Sep 17 00:00:00 2001 From: unknown <985400330@qq.com> Date: Wed, 29 Jun 2022 22:06:01 +0800 Subject: [PATCH] add 20220626-riscv-irq-analysis-part2-Interrupt-handling-plic.md --- ...-analysis-part2-Interrupt-handling-plic.md | 600 ++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 articles/20220626-riscv-irq-analysis-part2-Interrupt-handling-plic.md diff --git a/articles/20220626-riscv-irq-analysis-part2-Interrupt-handling-plic.md b/articles/20220626-riscv-irq-analysis-part2-Interrupt-handling-plic.md new file mode 100644 index 0000000..5744086 --- /dev/null +++ b/articles/20220626-riscv-irq-analysis-part2-Interrupt-handling-plic.md @@ -0,0 +1,600 @@ +> Author: 通天塔 985400330@qq.com
+> Date: 2022/05/19
+> Revisor:
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux) + +# RISC-V 中断子系统分析——PLIC 中断处理 + +## 前言 + + [RISC-V 中断子系统分析——硬件及其初始化](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20220519-riscv-irq-analysis.md) 上一篇文章对中断的硬件实现以及硬件初始化进行了分析,本篇文章将对中断的申请、产生、处理进行分析。 + +本文代码分析基于 Linux-5.17 + +## RISC-V Linux 中断申请 + +中断的注册分析,我们首先找到一个 Linux 驱动,从设备驱动入手进行分析。 + +``` +rtc@101000 { + interrupts = <0x0b>;//表明连接到 0x0b 中断线上 + interrupt-parent = <0x09>;//表明归属于 0x09 中断控制器,经过查找是 plic@c000000。 + reg = <0x00 0x101000 0x00 0x1000>; + compatible = "google,goldfish-rtc"; +}; +``` + +从设备树中可以看到,该 RTC 驱动连接到了中断控制器上,该驱动的文件名为:`rtc-goldfish.c` + +驱动与设备匹配成功后,调用以下代码: + +```c +//drivers/rtc/rtc-goldfish.c +static int goldfish_rtc_probe(struct platform_device *pdev) +{ +··· + err = devm_request_irq(&pdev->dev, rtcdrv->irq, + goldfish_rtc_interrupt, + 0, pdev->name, rtcdrv);//申请 irq +··· +} +``` + +其中申请 irq 的最终函数调用为 `request_threaded_irq`。 + +```c +devm_request_irq(&pdev->dev, rtcdrv->irq, goldfish_rtc_interrupt,0, pdev->name, rtcdrv); + +——>devm_request_threaded_irq(&pdev->dev, rtcdrv->irq, goldfish_rtc_interrupt, NULL, 0,pdev->name, dev_id); + +————>request_threaded_irq( rtcdrv->irq, goldfish_rtc_interrupt, NULL, 0, pdev->name, dev_id); +``` + +参数分析如下: + +* `rtcdrv->irq`: 要分配的中断 +* `goldfish_rtc_interrupt`:中断产生时要调用的函数 +* `NULL`:中断处理线程要调用的函数 +* `0`:中断类型标志位,更多可选设定解释如下 + * `IRQF_SHARED`:共享中断 + * `IRQF_TRIGGER_*`:触发边界或等级 + * `IRQF_ONESHOT`:只触发 1 次 +* `pdev->name`:声明设备的 ascii 名称 +* `dev_id`:全局唯一的。因为这个值由处理程序接收,所以设备数据结构的地址常用作 dev_id。 + +`request_threaded_irq` 做的事情: + +* 获取 `irq_desc` +* 创建 `irqaction` +* 通过 `__setup_irq` 将 `irqaction` 添加到 IRQ 链表当中 + +`__setup_irq` 函数有将近 400 行,此处暂不分析,在中断函数处理的时候,我们再分析该函数的作用。 + +至此,完成中断的申请及注册。 + +## RISC-V Linux 中断产生 + +本文以 RTC 为例进行中断处理的分析,现在我们创建一个中断,让系统在运行过程中发出中断,以便后续分析中断的处理流程。 + +首先通过 menuconfig 配置,将 goldfish-rtc 的驱动编译进内核,将 rtc 设备驱动起来。 + +应用创建中断的代码如下: + +```c +#include +#include +#include +#include +#include +#include +struct rtc_time time; //保存时间值 +struct rtc_wkalrm alrm; //保存闹钟值 +int main(int argc,char **argv) +{ + int fd=open("/dev/rtc0",O_RDWR); // 2==O_RDWR + if(fd<0){ + printf("驱动设备文件打开失败!\r\n"); + return 0; + } + alrm.enabled=1; + alrm.pending=0; + ioctl(fd,RTC_RD_TIME,&time); + time.tm_sec+=5;//5 秒后闹钟产生 + alrm.time=time; + ioctl(fd,RTC_WKALM_SET,&alrm); + ioctl(fd,RTC_ALM_READ,&time); + printf("ALM %d-%d-%d %d:%d:%d\r\n",time.tm_year+1900,time.tm_mon+1,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec); + while(1){ + ioctl(fd,RTC_RD_TIME,&time); + printf("now time=%d-%d-%d %d:%d:%d\r\n",time.tm_year+1900,time.tm_mon+1,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec); + sleep(1); + } +} + +``` + +编译命令如下: + +``` +$ riscv64-linux-gnu-gcc rtc_alarm.c -o rtc_alarm +``` + +运行命令: + +``` +$ cp /lib/ld-linux-riscv64-lp64.so.1 /lib/ld-linux-riscv64-lp64d.so.1 +$ ./rtc_alarm +``` + +运行环境:Linux Lab,riscv64 虚拟开发板,内核版本 Linux 5.17。 + +关于 ld 库为什么要复制一个 lp64d 的库的原因是通过命令 `riscv64-linux-gnu-readelf rtc_alarm -a` 读取到 `[Requesting program interpreter: /lib/ld-linux-riscv64-lp64d.so.1]` 说明要使用的库名称不同,故单独进行复制,否则程序将不能正确运行。 + +程序运行起来之后,代码追踪打印信息如下: + +``` +[nfk test] RTC_WKALM_SET +[nfk test] drivers/rtc/dev.c-rtc_dev_ioctl-375 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-464 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-469 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-473 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-477 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-483 +[nfk test] drivers/rtc/interface.c-rtc_timer_enqueue-834 +[nfk test] drivers/rtc/interface.c-rtc_timer_enqueue-837 +[nfk test] drivers/rtc/interface.c-rtc_timer_enqueue-841 +[nfk test] drivers/rtc/interface.c-__rtc_set_alarm-416 +[nfk test] drivers/rtc/interface.c-__rtc_set_alarm-427 +[nfk test] drivers/rtc/interface.c-__rtc_set_alarm-432 +[nfk test] goldfish_rtc_set_alarm +[nfk test] goldfish_rtc_set_alarm +[nfk test] drivers/rtc/interface.c-__rtc_set_alarm-454 +[nfk test] drivers/rtc/interface.c-rtc_timer_enqueue-845 +[nfk test] drivers/rtc/interface.c-rtc_set_alarm-501 +[nfk test] get RTC_ALM_READ +ALM 2022-6-24 15:54:35 +now time=2022-6-24 15:54:30 +... +now time=2022-6-24 15:54:34 +[nfk test] goldfish_rtc_interrupt +[nfk test] goldfish_rtc_alarm_irq_enable +now time=2022-6-24 15:54:35 +... +now time=2022-6-24 15:54:40 + +``` + +代码执行路径: + +```c +用户空间: + main + open + ioctl +内核空间: + rtc_dev_ioctl + rtc_set_alarm + rtc_timer_enqueue + __rtc_set_alarm + goldfish_rtc_set_alarm +``` + +通过分析可以知道,闹钟成功地触发了中断,RTC 驱动中的中断函数得到了运行,后面我们进行深入分析,看中断如何从触发到最终执行中断函数的完整流程。 + +## RISC-V Linux 中断处理 + +### 中断处理流程 + +首先在 RTC 驱动的中断处理函数 `goldfish_rtc_interrupt` 中添加函数 `dump_stack` 进行调用栈的回溯,以便找到函数的调用关系。 + +``` +[nfk test] goldfish_rtc_interrupt +CPU: 0 PID: 0 Comm: swapper/0 Not tainted 5.17.0-dirty #85 +Hardware name: riscv-virtio,qemu (DT) +Call Trace: +[] dump_backtrace+0x1c/0x24 +[] show_stack+0x2c/0x38 +[] dump_stack_lvl+0x40/0x58 +[] dump_stack+0x14/0x1c +[] goldfish_rtc_interrupt+0x22/0x74 +[] __handle_irq_event_percpu+0x52/0xe0 +[] handle_irq_event_percpu+0x12/0x4e +[] handle_irq_event+0x5e/0x94 +[] handle_fasteoi_irq+0xac/0x18e +[] generic_handle_domain_irq+0x28/0x3a +[] plic_handle_irq+0x8a/0xec +[] generic_handle_domain_irq+0x28/0x3a +[] riscv_intc_irq+0x34/0x5c +[] generic_handle_arch_irq+0x4a/0x74 +[] ret_from_exception+0x0/0xc +[] rcu_idle_enter+0x10/0x18 +``` + +`ret_from_exception` 函数表明 CPU 收到了一个异常,并且已经完成了异常的处理。 + +架构设计时,就考虑了 CPU 要进行各种各样的异常处理,于是提前定义好了一些异常向量表,在 CPU 执行特殊的指令时,就会触发异常。 + +本文不对 CPU 如何接收异常和处理异常进行深入分析,这**涉及到 RISC-V 架构相关的汇编指令**的分析,这里先给出参考文档 [Linux异常处理体系结构](https://www.cnblogs.com/gulan-zmc/p/11604437.html) ,在下一篇文章中进行重点分析。 + +本文暂时只对 **CPU 中断控制器 -> PLIC 中断控制器 -> 驱动中的中断处理函数** 这一层面进行分析。 + +经过查找代码,在 RISC-V 的 entry.S 中的异常处理汇编代码中调用了 `generic_handle_arch_irq` 函数。 + +汇编代码如下: + +``` +/*arch/riscv/kernel/entry.S: 113*/ +#ifdef CONFIG_CONTEXT_TRACKING + /* If previous state is in user mode, call context_tracking_user_exit. */ + li a0, SR_PP + and a0, s1, a0 + bnez a0, skip_context_tracking + call context_tracking_user_exit +skip_context_tracking: +#endif + + /* + * MSB of cause differentiates between + * interrupts and exceptions + */ + bge s4, zero, 1f + + la ra, ret_from_exception + + /* Handle interrupts */ + move a0, sp /* pt_regs */ + la a1, generic_handle_arch_irq + jr a1 +``` + +汇编代码通过寄存器进行传参,将参数传给了函数 `generic_handle_arch_irq`。 + +```c +//kernel/irq/handle.c +/** + * generic_handle_arch_irq - root irq handler for architectures which do no + * entry accounting themselves + * @regs: Register file coming from the low-level handling code + */ +asmlinkage void noinstr generic_handle_arch_irq(struct pt_regs *regs) +{ + struct pt_regs *old_regs; + + irq_enter(); + old_regs = set_irq_regs(regs); + handle_arch_irq(regs); + set_irq_regs(old_regs); + irq_exit(); +} +``` + +可以看到这个通用接口中并未调用 `riscv_intc_irq` 中断处理函数,但是 dump 出来的 stack 中却看到了中断处理函数。 + +以下就是原因,可以看到在以下函数中已经配置了对应架构的中断处理函数,该函数被 `riscv_intc_init` 函数调用。 + +```c +//kernel/irq/handle.c +int __init set_handle_irq(void (*handle_irq)(struct pt_regs *)) +{ + if (handle_arch_irq) + return -EBUSY; + handle_arch_irq = handle_irq; + return 0; +} +``` + +下面开始分析 RISC-V 架构下的中断处理函数是如何调用到 PLIC 中断控制器中的。 + +```c +//drivers/irqchip/irq-riscv-intc.c +static asmlinkage void riscv_intc_irq(struct pt_regs *regs) +{ + unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG; + + if (unlikely(cause >= BITS_PER_LONG)) + panic("unexpected interrupt cause"); + + switch (cause) { +#ifdef CONFIG_SMP + case RV_IRQ_SOFT: + /* + * We only use software interrupts to pass IPIs, so if a + * non-SMP system gets one, then we don't know what to do. + */ + handle_IPI(regs);//软中断接口 + break; +#endif + default://硬中断接口 + generic_handle_domain_irq(intc_domain, cause); + break; + } +} +``` + +`generic_handle_domain_irq(intc_domain, cause)` 函数的 `intc_domain` 参数是通过 `irq-riscv-intc.c` 驱动的初始化建立的,`cause` 参数是汇编代码中传进来的,`cause` 参数就是中断号,方便进一步的追踪具体调用哪个中断处理函数。 + +`generic_handle_domain_irq` 代码如下: + +```c +//kernel/irq/irqdesc.c +int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq) +{ + WARN_ON_ONCE(!in_irq()); + return handle_irq_desc(irq_resolve_mapping(domain, hwirq)); +} +``` + +可以看到通过两个函数的调用完成进一步的中断处理,第一个是 `irq_resolve_mapping` ,通过中断域和硬件中断号得出中断描述符。这到底是如何实现的?下一小节我们将清楚 `domain` 与 `hwirq` 的来龙去脉。 + +### domain 的来龙去脉 + +#### 中断描述符(irq_desc)获取 + +中断描述符通过硬件中断号和中断域获取,中断描述符中存储着一个中断最全的信息,要处理该中断必须获取到这个中断的信息。 + +通过设备树可知,RTC 的 `hwirq = 0xb`,以下分析都是基于已知 RTC 的 `hwirq` 的情况下添加打印所进行的追踪分析。 + +```c +//kernel/irq/irqdesc.c +int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq) +{ + WARN_ON_ONCE(!in_irq()); + struct irq_desc *my= irq_resolve_mapping(domain, hwirq); + if(hwirq==11) + { + printk("[nfk test]hwirq=%u\n",hwirq); + printk("[nfk test] irq =%d,hwirq=%d",my->irq_data.irq,my->irq_data.hwirq); + printk("[nfk test] name =%s",my->dev_name); + } + return handle_irq_desc(irq_resolve_mapping(domain, hwirq)); +} +``` + +追踪 `irq_desc` 的获取函数, + +```c +//kernel/irq/irqdomain.c +struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain, + irq_hw_number_t hwirq, + unsigned int *irq) +{ + struct irq_desc *desc = NULL; + struct irq_data *data; +...//查找 desc 不是用的以上代码,故省略 + rcu_read_lock(); + /* Check if the hwirq is in the linear revmap. */ + if (hwirq < domain->revmap_size){ + if(hwirq==11) + printk("hwirq < domain->revmap_size\n");//经验证,走的这条路获取到了 irq_data + data = rcu_dereference(domain->revmap[hwirq]);//在 RCU 保护机制下,通过 domain->revmap[hwirq] 找到了 irq_data, + } + else + data = radix_tree_lookup(&domain->revmap_tree, hwirq); + if (likely(data)) { + if(hwirq==11) + printk("hwirq is in the linear revmap\n"); + desc = irq_data_to_desc(data);//container_of,已知成员变量地址,求出结构体起始地址 + if (irq) + *irq = data->irq; + } + rcu_read_unlock(); + return desc; +} +``` + +通过以上函数,我们确认到 `irq_data` 就存在 `domain->revmap` 中,但我们什么时候才将 `irq_data` 放入到 `domain` 中的呢? + +#### domain 中的 irq_data 来源 + +通过查找数组 `domain->revmap` 找到以下函数,可以确认 `irq_data` 就是在这里被添加进去的。 + +```c +//kernel/irq/irqdomain.c +static void irq_domain_set_mapping(struct irq_domain *domain, + irq_hw_number_t hwirq, + struct irq_data *irq_data) +{ + if (irq_domain_is_nomap(domain)) + return; + if(hwirq==11) + { + printk("[nfk test] irq_domain_set_mapping,hwirq=%d\n",hwirq); + dump_stack(); + } + + mutex_lock(&domain->revmap_mutex); + if (hwirq < domain->revmap_size) + rcu_assign_pointer(domain->revmap[hwirq], irq_data); + else + radix_tree_insert(&domain->revmap_tree, hwirq, irq_data); + mutex_unlock(&domain->revmap_mutex); +} +``` + +通过栈回溯得到以下信息: + +``` +[nfk test] irq_domain_set_mapping,hwirq=11 +[] dump_backtrace+0x1c/0x24 +[] show_stack+0x2c/0x38 +[] dump_stack_lvl+0x40/0x58 +[] dump_stack+0x14/0x1c //栈回溯 +[] irq_domain_set_mapping+0x84/0x86 //设置映射 +[] __irq_domain_alloc_irqs+0x1cc/0x25a //申请中断内存 +[] irq_create_fwspec_mapping+0xe6/0x248 +[] irq_create_of_mapping+0x50/0x6e +[] of_irq_get+0x4c/0x5e +[] of_irq_to_resource+0x2c/0xcc //解析节点的中断,并为其申请资源。 +[] of_irq_to_resource_table+0x32/0x4c +[] of_device_alloc+0x128/0x292 +[] of_platform_device_create_pdata+0x36/0x9a //平台设备总线创建设备 +[] of_platform_bus_create+0x120/0x172 +[] of_platform_bus_create+0x14a/0x172 //平台总线创建 +[] of_platform_populate+0x36/0x86 +[] of_platform_default_populate_init+0xbe/0xcc +[] do_one_initcall+0x3e/0x168 +[] kernel_init_freeable+0x19e/0x202 +[] kernel_init+0x1e/0x10a +[] ret_from_exception+0x0/0xc +``` + +以上的整个流程回溯起来篇幅有些大,这里不再深入分析,可以确定的是 `irq_data` 和 `hwirq` 的对应是在平台总线初始化时,进行设备节点的扫描,然后为每个设备都进行了 irq 的映射。 + +根据代码的追踪,最终确认到 `irq_domain_insert_irq` 调用的 `irq_domain_set_mapping(struct irq_domain *domain,irq_hw_number_t hwirq,struct irq_data *irq_data)` ,这时 `irq_data` 在获取时,采用的就是 `irq_desc` 结构体中获取的数据。相关代码如下: + +```c +//kernel/irq/irqdomain.c +static void irq_domain_insert_irq(int virq) +{ + struct irq_data *data; + + for (data = irq_get_irq_data(virq); data; data = data->parent_data) {//获取 irq_data +... + } + + irq_clear_status_flags(virq, IRQ_NOREQUEST); +} +``` + +那么现在问题就变成了 `irq_desc` 的来源在哪里? + +#### irq_desc 的来源 + +通过以下代码进行栈回溯,可以找到何时进行 `irq_desc` 的创建。 + +```c +//kernel/irq/irqdesc.c +static void irq_insert_desc(unsigned int irq, struct irq_desc *desc) +{ + if(irq==1) + { + printk("irq_insert_desc irq=1\n"); + dump_stack(); + } + + radix_tree_insert(&irq_desc_tree, irq, desc); +} +``` + +发现流程与 `irq_data` 的来源基本一致,经过确认是在为中断申请资源时,在 `__irq_alloc_descs` 函数中创建的 `irq_desc`。栈回溯打印如下: + +``` +irq_insert_desc irq=1 +... +[] dump_backtrace+0x1c/0x24 +[] show_stack+0x2c/0x38 +[] dump_stack_lvl+0x40/0x58 +[] dump_stack+0x14/0x1c +[] __irq_alloc_descs+0x1f2/0x1f4 +[] irq_domain_alloc_descs+0x60/0x80 +[] __irq_domain_alloc_irqs+0x156/0x25a +[] irq_create_fwspec_mapping+0xe6/0x248 +[] irq_create_of_mapping+0x50/0x6e +... + +``` + +通过以上小节的分析,可以确认的是,整个中断的信息添加到 `domain` 中的节点,是在平台驱动注册时完成的。 + +#### 回讲第一节——中断申请 + +到现在为止,我们搞清楚了 `domain` 的来龙去脉,那么我们现在再思考中断申请到底干了啥? + +```c +//drivers/rtc/rtc-goldfish.c +static int goldfish_rtc_probe(struct platform_device *pdev) +{ +··· + err = devm_request_irq(&pdev->dev, rtcdrv->irq, + goldfish_rtc_interrupt, + 0, pdev->name, rtcdrv);//申请 irq +··· +} +``` + +可以看出是为已创建好的 `irq_desc` 添加处理函数,以实现中断触发时,调用对应的中断处理函数。 + +### 流程续讲 + +上一小节讲清楚 `domain` 之后,我们继续讲整个中断的处理流程。 + +``` +[] dump_backtrace+0x1c/0x24 +[] show_stack+0x2c/0x38 +[] dump_stack_lvl+0x40/0x58 +[] dump_stack+0x14/0x1c +[] goldfish_rtc_interrupt+0x22/0x74 +[] __handle_irq_event_percpu+0x52/0xe0 +[] handle_irq_event_percpu+0x12/0x4e +[] handle_irq_event+0x5e/0x94 +[] handle_fasteoi_irq+0xac/0x18e +[] generic_handle_domain_irq+0x28/0x3a //通过 hwirq = 11,进行查找 rtc 的中断处理程序 +[] plic_handle_irq+0x8a/0xec //PLIC 中断处理程序 +[] generic_handle_domain_irq+0x28/0x3a //通过 hwirq = 9,进行查找 plic 的中断处理程序 +[] riscv_intc_irq+0x34/0x5c //RISC-V 架构下的中断处理器 +[] generic_handle_arch_irq+0x4a/0x74 //根据架构查找 RISC-V 架构下的中断处理程序 +[] ret_from_exception+0x0/0xc +[] rcu_idle_enter+0x10/0x18 +``` + +可以看到栈回溯过程中执行了两次 `generic_handle_domain_irq` ,这两次的 `domain` 参数肯定是不同的,一次的 `domain` 是 RISC-V 的 `domain`,另一次是 PLIC 的 `domain`,通过这种级联的结构,使得 CPU 可以扩展更多的中断,并且通过中断控制器完成更加复杂的功能。这种级联结构可以在上一篇的硬件分析中看到图示,[ RISC-V 中断子系统分析——硬件及其初始化](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20220519-riscv-irq-analysis.md) + +通过 PLIC 初始化的相关代码也可以看到,PLIC 的 `domain` 是有 `parent` 的,代码如下: + +```c +//drivers/irqchip/irq-sifive-plic.c +static int __init plic_init(struct device_node *node, + struct device_node *parent) +{ + ... + /* 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);//为 parent 设置中断处理函数 + printk("[nfk test]set chained handler\n"); + } + ... +} +``` + +也就是说,本质上 PLIC 也会有一个 `irq_desc`,也有自己的中断处理函数。通过设备树和打印信息,确认到 PLIC 的 `hwirq` 和 `irq` 都为 9。 + +通过命令,我们可以看到 RTC 的中断号为 11,在 CPU0 上触发了中断。 + +```c +# cat /proc/interrupts + CPU0 CPU1 CPU2 CPU3 + 1: 1 0 0 0 SiFive PLIC 11 Edge 101000.rtc + 2: 315 0 0 0 SiFive PLIC 10 Edge ttyS0 + 3: 11 0 0 0 SiFive PLIC 8 Edge virtio0 + 4: 103 0 0 0 SiFive PLIC 7 Edge virtio1 + 5: 20719 20725 20725 20728 RISC-V INTC 5 Edge riscv-timer +IPI0: 35 23 26 31 Rescheduling interrupts +IPI1: 763 247 211 683 Function call interrupts +IPI2: 0 0 0 0 CPU stop interrupts +IPI3: 0 0 0 0 IRQ work interrupts +IPI4: 0 0 0 0 Timer broadcast interrupts +``` + + + +## 小结 + +本文讲述了从**中断申请 -> 中断产生 -> 中断处理**的流程。 + +在中断申请时,并没有对中断申请进行深入的分析,而是在中断处理小节中,再返回来看中断申请,这样就感觉豁然开朗,中断申请的作用就显得非常简单了。 + +在中断处理小节中,先看了中断的处理流程,但是对于中断处理流程中的 `domain` 又不明白为什么通过 `hwirq` 就可以找到中断处理函数,又针对性的补上了 `domain` 的分析。 + +至此,已经能够搞清楚一个 IRQ 从产生到处理的 C 语言部分的全部流程。下一篇文章中,将重点针对 IRQ 产生后 CPU 是如何在汇编层面调用到 C 语言这一部分代码进行分析。 + +## 参考资料 + +* [articles/20220519-riscv-irq-analysis.md · 泰晓科技/RISCV-Linux - Gitee.com](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20220519-riscv-irq-analysis.md) +* [如何分析 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) +* [Linux Kernel 的中断子系统之(七):GIC 代码分析 (wowotech.net)](http://www.wowotech.net/irq_subsystem/gic_driver.html) +* [Linux 中断子系统 - GIC 驱动源码分析 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/363134084) \ No newline at end of file -- Gitee