From 38662a860a23218e790409dc9daa42f6782e85f1 Mon Sep 17 00:00:00 2001 From: Junwen Wu Date: Sun, 12 Jun 2022 09:08:15 +0000 Subject: [PATCH 1/2] add riscv-irq-implementation --- articles/20220612-riscv-irq-implementation.md | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 articles/20220612-riscv-irq-implementation.md diff --git a/articles/20220612-riscv-irq-implementation.md b/articles/20220612-riscv-irq-implementation.md new file mode 100644 index 0000000..252105f --- /dev/null +++ b/articles/20220612-riscv-irq-implementation.md @@ -0,0 +1,246 @@ + +> Author: wudaemon
+> Date: 2022/06/12
+> Revisor: lzufalcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux) + + +## linux 内核对 riscv 中断处理过程 +### 中断控制器的初始化 + +中断控制器的初始化在start_kernel---->init_IRQ--->irqchip_init中实现的,包含中断控制器的解析,irq_domain初始化,以及很重要的中断handler的设置。 +先来看解析设备树的中断控制器信息__irqchip_of_table位置 在vmlinux.lds定义的一个段,在of_irq_init查找这个段 扫描初始化dts中断控制器信息。 + + +``` +void __init irqchip_init(void) +{ + of_irq_init(__irqchip_of_table); + acpi_probe_device_table(irqchip); +} + +of_irq_init + for_each_matching_node_and_match(np, __irqchip_of_table, &match) +``` + +irqchip的driver定位如下 在启动的时候被lds文件链接到__irqchip_of_table位置 +``` +IRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init); + +展开后: +static const struct of_device_id __of_table_riscv,cpu +         __used __section("__irqchip_of_table")       +         __aligned(__alignof__(struct of_device_id))      +          = { .compatible = "riscv,cpu-intc",               +              .data = riscv_intc_init} +``` +我们给定的根中断控制器设备树信息如下,通过compatible属性匹配到上述定义的IRQCHIP_DECLARE,匹配之后就会调用的driver的handler也就是riscv_intc_init +``` +interrupt-controller { + #interrupt-cells = <0x1>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + phandle = <0x8>; +}; + +      +0.000000] OF: of_irq_init: init /cpus/cpu@0/interrupt-controller ((____ptrval____)), parent 0000000000000000 +[    0.000000] riscv-intc: 64 local interrupts mapped +[    0.000000] OF: of_irq_init: init /cpus/cpu@1/interrupt-controller ((____ptrval____)), parent 0000000000000000 +[    0.000000] OF: of_irq_init: init /cpus/cpu@2/interrupt-controller ((____ptrval____)), parent 0000000000000000 +[    0.000000] OF: of_irq_init: init /cpus/cpu@3/interrupt-controller ((____ptrval____)), parent 0000000000000000 +[    0.000000] OF: of_irq_init: init /soc/interrupt-controller@c000000 ((____ptrval____)), parent 0000000000000000 + + +``` +riscv_intc_init 主要作用是设置irq_domain,因为riscv目前中断控制器较简单,支持的中断并不多,就使用简单的线性映射(也就是硬件中断号与软件中断号的转换成线性的),没有使用radix tree去做映射。其次就是设置handle_arch_irq , 这里指向了riscv_intc_irq 在中断处理的时候在riscv_intc_irq 中处理。 + +``` +static int __init riscv_intc_init(struct device_node *node, +   struct device_node *parent) +{ + int rc, hartid; + +    .... + intc_domain = irq_domain_add_linear(node, BITS_PER_LONG, +     &riscv_intc_domain_ops, NULL); //线性的irq_domain  处理的比较简单 对64个中断进行映射 + if (!intc_domain) { + pr_err("unable to add IRQ domain\n"); + return -ENXIO; + } + + rc = set_handle_irq(&riscv_intc_irq); //将全局函数指针handle_arch_irq指向了riscv_intc_irq,而处理器在进入中断异常时,会跳转到handle_arch_irq + if (rc) { + pr_err("failed to set irq handler\n"); + return rc; + } + + cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_STARTING, +   "irqchip/riscv/intc:starting", +   riscv_intc_cpu_starting, +   riscv_intc_cpu_dying); + + pr_info("%d local interrupts mapped\n", BITS_PER_LONG); + + return 0; +} +``` + + + +#riscv对中断的处理 + +arch/riscv/kernel/head.S 中的setup_trap_vector设置了异常向量表,这样在中断来临时在arch/riscv/kernel/entry.S的handle_exception跳转generic_handle_arch_irq 在c语言中处理中断 + +前面提及到irq的handler是riscv_intc_irq ,对于riscv处理器来讲 这分为普通中断和IPI(主要做core间通信),我们看对普通中断的处理, + +``` +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; + } +} +``` +对于普通中断handler是generic_handle_domain_irq,处理中断的时候,需要根据所属的irq_domain通过硬件中断号hwirq转换成软件中断号 + +``` + +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_desc结构进而继续拿到每个具体中断设置的handler来继续处理,关于这个处理流程的话对于各个体系架构而言应该是一样的,便不再详述。 + +我们可以打开irq的traceevent得到一些调用栈信息,比如重要的timer中断如下表示,根据上述表示其irq_desc的handler指向handle_percpu_devid_irq,这是timer中断定义的。 + +``` +cat-76      [003] d.h2.   947.920627: irq_handler_entry: irq=5 name=riscv-timer +cat-76      [003] d.h2.   947.920650:  + => trace_event_raw_event_irq_handler_entry + => __traceiter_irq_handler_entry + => handle_percpu_devid_irq + => generic_handle_domain_irq + => riscv_intc_irq + => generic_handle_arch_irq + => ret_from_exception + => _raw_spin_unlock_irqrestore + => 0 +``` +对于串口中断 ,这属于片上外设中断,它和timer中断的处理流程并不是完全相同,这就涉及到了中断控制器的级联 + + +``` +          -0       [000] d.h2.   947.925284: irq_handler_entry: irq=2 name=ttyS0 +          -0       [000] d.h2.   947.925292:  + => trace_event_raw_event_irq_handler_entry + => __traceiter_irq_handler_entry + => __handle_irq_event_percpu + => handle_irq_event + => handle_fasteoi_irq + => generic_handle_domain_irq + => plic_handle_irq + => generic_handle_domain_irq + => riscv_intc_irq + => generic_handle_arch_irq + => ret_from_exception + => rcu_idle_enter + => 0x7172695f72657469 + +``` +串口中断的dts信息如下,uart的中断直接相连的不是root intc,而是plic中断控制器,riscv处理器的外设中断都是通过PLIC中断控制器管理的 + +``` +uart@10000000 { + interrupts = <0xa>; + interrupt-parent = <0x9>; + clock-frequency = <0x384000>; + reg = <0x0 0x10000000 0x0 0x100>; + compatible = "ns16550a"; +}; +interrupt-controller@c000000 { + phandle = <0x9>; + riscv,ndev = <0x35>; + reg = <0x0 0xc000000 0x0 0x4000000>; + interrupts-extended = <0x8 0xb 0x8 0x9 0x6 0xb 0x6 0x9 0x4 0xb 0x4 0x9 0x2 0xb 0x2 0x9>; + interrupt-controller; + compatible = "riscv,plic0"; + #interrupt-cells = <0x1>; + #address-cells = <0x0>; +}; +``` +在of_irq_init会匹配这个irqchip_driver,在这个中断控制器的初始化plic_init 中根据plic_parent_irq(root irq_domain转换而来) 这个控制器的irq号设置plic中断控制器的handle为plic_handle_irq +``` +IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */ +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); + } + +} + +static inline void +irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle) +{ + __irq_set_handler(irq, handle, 1, NULL); +} +void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, + const char *name) +{ + unsigned long flags; + struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);//plic_parent_irq 对应的irq_desc + + if (!desc) + return; + + __irq_do_set_handler(desc, handle, is_chained, name); + irq_put_desc_busunlock(desc, flags); +} + +static void +__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle, + int is_chained, const char *name) +{ + ... + desc->handle_irq = handle; //设置plic_parent_irq 对应的irq_desc + desc->name = name; + + ... +} + +``` +也就是说uart这个片山外设中断会根据hwirq通过intc_domain找到plic_parent_irq,然后根据plic_parent_irq通过plic_domain找到这个外设的swirq,对应的irq_desc 就是真正要处理的handle_fasteoi_irq。 + + + + + + + + + + -- Gitee From cdba8533b6b9b1a8248aeb5795169ee0c09bd3bf Mon Sep 17 00:00:00 2001 From: Junwen Wu Date: Sun, 12 Jun 2022 14:28:05 +0000 Subject: [PATCH 2/2] add riscv ipi implemantation --- articles/20220612-riscv-ipi-implementation.md | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 articles/20220612-riscv-ipi-implementation.md diff --git a/articles/20220612-riscv-ipi-implementation.md b/articles/20220612-riscv-ipi-implementation.md new file mode 100644 index 0000000..cd7ec65 --- /dev/null +++ b/articles/20220612-riscv-ipi-implementation.md @@ -0,0 +1,214 @@ +1## linux 内核对 riscv IPI中断处理过程 + + +### IPI中断处理 + + + +前面提及到irq的handler是riscv_intc_irq ,对于riscv处理器来讲 这分为普通中断和IPI(主要做core间通信),我们接着看对IPI中断的处理,IPI中断主要是做核间通信,对于IPI中断而言其hwirq为RV_IRQ_SOFT + 本文分析基于linux 5.17。 +``` +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; + } +} +``` +对于普通中断handler是generic_handle_domain_irq,处理中断的时候,需要根据所属的irq_domain通过硬件中断号hwirq转换成软件中断号,根据每个cpu上的pending_ipis处理不同的IPI中断 + +``` +void handle_IPI(struct pt_regs *regs) +{ + unsigned long *pending_ipis = &ipi_data[smp_processor_id()].bits; + unsigned long *stats = ipi_data[smp_processor_id()].stats; + + riscv_clear_ipi(); + + while (true) { + unsigned long ops; + + /* Order bit clearing and data access. */ + mb(); + + ops = xchg(pending_ipis, 0); + if (ops == 0) + return; + + if (ops & (1 << IPI_RESCHEDULE)) { + stats[IPI_RESCHEDULE]++; + scheduler_ipi(); + } + + if (ops & (1 << IPI_CALL_FUNC)) { + stats[IPI_CALL_FUNC]++; + generic_smp_call_function_interrupt(); + } + + if (ops & (1 << IPI_CPU_STOP)) { + stats[IPI_CPU_STOP]++; + ipi_stop(); + } + + if (ops & (1 << IPI_IRQ_WORK)) { + stats[IPI_IRQ_WORK]++; + irq_work_run(); + } + +#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST + if (ops & (1 << IPI_TIMER)) { + stats[IPI_TIMER]++; + tick_receive_broadcast(); + } +#endif + BUG_ON((ops >> IPI_MAX) != 0); + + /* Order data access and bit testing. */ + mb(); + } +} +``` +riscv处理器的IPI中断种类有下面几种 + +``` +static const char * const ipi_names[] = { + [IPI_RESCHEDULE] = "Rescheduling interrupts", + [IPI_CALL_FUNC] = "Function call interrupts", + [IPI_CPU_STOP] = "CPU stop interrupts", + [IPI_IRQ_WORK] = "IRQ work interrupts", + [IPI_TIMER] = "Timer broadcast interrupts", +}; +``` + + +比如对于 IPI_CALL_FUNC 类型的IPI中断而言,希望在某个target cpu上调用一个函数,源cpu可以通过 smp_call_function 触发 IPI_CALL_FUNC 这种类型的软件中断,最后经过generic_smp_call_function_interrupt-》generic_smp_call_function_single_interrupt-》flush_smp_call_function_queue中处理同步和非同步回调。 + +``` +#define generic_smp_call_function_interrupt \ + generic_smp_call_function_single_interrupt + +void generic_smp_call_function_single_interrupt(void) +{ + cfd_seq_store(this_cpu_ptr(&cfd_seq_local)->gotipi, CFD_SEQ_NOCPU, + smp_processor_id(), CFD_SEQ_GOTIPI); + flush_smp_call_function_queue(true); +} + +static void flush_smp_call_function_queue(bool warn_cpu_offline) +{ + call_single_data_t *csd, *csd_next; + struct llist_node *entry, *prev; + struct llist_head *head; + static bool warned; + + lockdep_assert_irqs_disabled(); + + head = this_cpu_ptr(&call_single_queue); + cfd_seq_store(this_cpu_ptr(&cfd_seq_local)->handle, CFD_SEQ_NOCPU, + ... ... + /* + * First; run all SYNC callbacks, people are waiting for us. + */ + prev = NULL; + llist_for_each_entry_safe(csd, csd_next, entry, node.llist) { + /* Do we wait until *after* callback? */ + if (CSD_TYPE(csd) == CSD_TYPE_SYNC) { + smp_call_func_t func = csd->func; //拿到同步回调的function + void *info = csd->info; + + if (prev) { + prev->next = &csd_next->node.llist; + } else { + entry = &csd_next->node.llist; + } + + csd_lock_record(csd); + func(info); //执行func + csd_unlock(csd); + csd_lock_record(NULL); + } else { + prev = &csd->node.llist; + } + } + + ... + /* + * Second; run all !SYNC callbacks. + */ + prev = NULL; + llist_for_each_entry_safe(csd, csd_next, entry, node.llist) { + int type = CSD_TYPE(csd); + + if (type != CSD_TYPE_TTWU) { + if (prev) { + prev->next = &csd_next->node.llist; + } else { + entry = &csd_next->node.llist; + } + + if (type == CSD_TYPE_ASYNC) { + smp_call_func_t func = csd->func; //拿到非同步回调的function + void *info = csd->info; + + csd_lock_record(csd); + csd_unlock(csd); + func(info); + csd_lock_record(NULL); + } else if (type == CSD_TYPE_IRQ_WORK) { + irq_work_single(csd); + } + + } else { + prev = &csd->node.llist; + } + } + + /* + * Third; only CSD_TYPE_TTWU is left, issue those. + */ + if (entry) + sched_ttwu_pending(entry); + + cfd_seq_store(this_cpu_ptr(&cfd_seq_local)->hdlend, CFD_SEQ_NOCPU, + smp_processor_id(), CFD_SEQ_HDLEND); +} + +``` + +### IPI中断触发流程 + +前面说到对于IPI_CALL_FUNC的IPI中断通过smp_call_function 来触发,nohz idlebalance +就用IPI_CALL_FUNC 类型的中断来实现源cpu想在target cpu调用nohz_csd_func 来实现nohz的负载均衡,在nohz_balancer_kick 中的smp_call_function_single_async注册一个异步的回调nohz_csd_func,最后通过send_ipi_single 来触发一个IPI_CALL_FUNC 类型的中断 + +``` +INIT_CSD(&rq->nohz_csd, nohz_csd_func, rq); +smp_call_function_single_async(ilb_cpu, &cpu_rq(ilb_cpu)->nohz_csd); + +void arch_send_call_function_single_ipi(int cpu) +{ + send_ipi_single(cpu, IPI_CALL_FUNC); +} + +``` + + + + + + -- Gitee