[toc]
include/linux/irqchip/chained_irq.h chained_irq_enter 链式中断进入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 static inline void chained_irq_enter (struct irq_chip *chip, struct irq_desc *desc) { if (chip->irq_eoi) return ; if (chip->irq_mask_ack) { chip->irq_mask_ack(&desc->irq_data); } else { chip->irq_mask(&desc->irq_data); if (chip->irq_ack) chip->irq_ack(&desc->irq_data); } } static inline void chained_irq_exit (struct irq_chip *chip, struct irq_desc *desc) { if (chip->irq_eoi) chip->irq_eoi(&desc->irq_data); else chip->irq_unmask(&desc->irq_data); }
include/linux/irqchip.h IRQCHIP_DECLARE DT 兼容字符串与初始化函数之间的关联 1 2 3 4 5 6 7 8 9 #define IRQCHIP_DECLARE(name, compat, fn) \ OF_DECLARE_2(irqchip, name, compat, typecheck_irq_init_cb(fn))
1 2 3 4 5 6 7 0x00000000c02a1820 __irqchip_of_table = . *(__irqchip_of_table) __irqchip_of_table 0x00000000c02a1820 0xc4 drivers/irqchip/irq-nvic.o __irqchip_of_table 0x00000000c02a18e4 0x188 drivers/irqchip/irq-stm32-exti.o *(__irqchip_of_table_end)
kernel/irq/generic-chip.c 通用 IRQ 芯片(通用中断控制器) 用于实现中断控制器驱动程序的可重用工具包 历史与背景 这项技术是为了解决什么特定问题而诞生的? 这项技术是为了解决在Linux内核中,中断控制器(Interrupt Controller)驱动程序之间存在大量重复代码 的问题。
中断控制器是硬件芯片,负责接收来自外围设备的中断信号,并将其通知给CPU。几乎所有的中断控制器都提供一组相似的基础功能:
**屏蔽(Mask)**一个中断源。
**解除屏蔽(Unmask)**一个中断源。
**应答(Acknowledge)**一个中断。
设置中断类型 (如边沿触发 vs. 电平触发)。
在generic-chip.c框架出现之前,每个新的中断控制器驱动程序都需要从头开始为这些基础功能编写自己的实现函数。然而,绝大多数简单的中断控制器,其实现方式都惊人地相似:无非就是对某个内存映射的寄存器(MMIO)进行读、改、写的位操作 。例如,屏蔽一个中断可能仅仅是将某个寄存器的特定位置1。
这种模式导致了大量的代码冗余,不仅增加了内核的体积,也使得驱动开发变得繁琐且容易出错。generic-chip.c的诞生,就是为了将这些通用的、基于位操作的逻辑 抽象出来,形成一个可重用的“工具箱”,从而极大地简化和标准化中断控制器驱动的编写。
它的发展经历了哪些重要的里程碑或版本迭代? generic-chip.c的发展与内核通用IRQ子系统(kernel/irq/)的演进紧密相连。
struct irq_chip的标准化 :首先,内核定义了struct irq_chip,这是一个包含了一系列函数指针(如.irq_mask, .irq_unmask, .irq_ack等)的结构体。这为所有中断控制器驱动提供了一个标准的回调接口。
通用函数的出现 :开发者们意识到,许多驱动在填充irq_chip结构时,填入的函数逻辑是重复的。于是,generic-chip.c应运而生,它提供了一系列现成的、可直接赋值给irq_chip函数指针 的通用函数,如irq_gc_mask_set_bit。
参数化与灵活性 :为了让这些通用函数能够适用于不同的硬件(寄存器地址和位布局不同),通用芯片框架被设计为参数化 的。驱动程序在设置IRQ域(IRQ domain)时,会将每个中断号(IRQ number)对应的硬件信息(如寄存器偏移量、位掩码等)存储在per-IRQ的数据结构中。通用函数在执行时,会从这些数据结构中获取参数,从而操作正确的硬件。
支持多种寄存器布局 :框架不断演进,以支持更广泛的硬件实现,例如:
不同的寄存器宽度(8-bit, 16-bit, 32-bit, 64-bit)。
支持“读-改-写”和“写1清零/置位”等不同的寄存器操作模式。
支持屏蔽和解除屏蔽使用同一个寄存器(通过读写不同的值)或使用两个独立的寄存器。
目前该技术的社区活跃度和主流应用情况如何? generic-chip.c是Linux内核IRQ子系统中一个极其稳定和核心的部分。
标准实现方式 :对于绝大多数嵌入式系统和片上系统(SoC)中的中断控制器(特别是GPIO控制器、I2C/SPI控制器等),使用generic-chip.c是编写驱动程序的标准和首选方式 。
代码稳定性 :其核心逻辑非常成熟,很少发生大的变动。社区的活跃度主要体现在为新的硬件特性或寄存器布局增加新的通用辅助函数。
核心原理与设计 它的核心工作原理是什么? generic-chip.c的核心原理是**“模板方法”设计模式**的应用。它提供了一套通用的算法框架(如如何屏蔽一个中断),而将算法中易变的部分(具体的寄存器地址和位)推迟到子类(具体的设备驱动)中去定义。
其工作流程如下:
驱动定义硬件布局 :一个中断控制器驱动程序首先定义其硬件寄存器的布局信息。
选择并填充irq_chip :驱动程序创建一个struct irq_chip的实例,并将其函数指针(如.irq_mask, .irq_unmask)指向generic-chip.c中提供的通用函数 ,例如irq_gc_mask_set_bit。gc代表”generic chip”。
设置IRQ域和per-IRQ数据 :驱动程序在初始化IRQ域时,会为它所管理的每一个中断号,设置一个irq_data结构。驱动会将这个中断号对应的具体硬件参数 (例如,控制这个中断的寄存器相对于基地址的偏移量、它在这个寄存器中的是第几位)存储到这个irq_data的私有字段中。
VFS/上层触发操作 :当内核需要屏蔽一个中断时(例如,mask_irq(nr)),通用的IRQ核心代码会找到这个中断号nr对应的irq_chip,并调用其.irq_mask函数指针。
通用函数执行 :此时,执行权交给了generic-chip.c中的irq_gc_mask_set_bit函数。这个函数是通用的,它不知道任何具体的硬件信息。它会: a. 使用当前中断号nr,获取其对应的irq_data。 b. 从irq_data中提取 出之前驱动存入的硬件参数(寄存器偏移量、位掩码)。 c. 使用这些参数,执行一个标准的、与硬件无关的内存映射I/O(MMIO)位操作 ,从而完成对硬件的控制。
它的主要优势体現在哪些方面?
极高的代码复用率 :驱动开发者无需再编写任何低级的位操作代码,只需填充数据结构和选择正确的通用函数即可。
降低开发难度和bug率 :使用经过内核社区充分测试和验证的通用代码,远比每个开发者都编写一套自己的实现要安全可靠。
驱动代码更简洁清晰 :驱动程序的核心逻辑变得更加清晰,因为它只关注于描述硬件的“是什么”,而不是“如何操作”的细节。
它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?
不适用于非标准硬件 :generic-chip.c主要针对那些遵循简单“寄存器-位”模型的控制器。对于一些功能极其复杂或设计非常独特的架构级中断控制器(如x86的APIC、ARM的GIC),它们有自己专属的、高度优化的驱动程序,generic-chip.c无法覆盖其全部功能(如中断路由、处理器间中断IPI等)。
轻微的性能开销 :相比于一个完全内联、硬编码的驱动函数,通用函数需要从irq_data中查找参数,存在一层额外的间接性。但在绝大多数情况下,这种开销是完全可以忽略不计的,并且被代码复用带来的巨大好处所抵消。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? 它是为任何使用MMIO寄存器来控制中断的、行为标准的 中断控制器编写驱动的首选解决方案。
GPIO控制器驱动 :这是最经典、最普遍的应用场景。几乎所有SoC上的GPIO控制器,其每个引脚的中断使能、屏蔽、状态都由寄存器中的特定位控制,它们的驱动几乎完全由generic-chip.c的函数构建。
SoC内部外设的中断控制器 :许多片上外设(如定时器、DMA控制器、I2C/SPI接口)的中断线会汇集到一个次级的中断控制器上,这些控制器的驱动通常也使用generic-chip.c。
FPGA中的软核中断控制器 :在FPGA设计中,自定义的中断控制器通常也采用简单的寄存器接口,为其编写Linux驱动时,generic-chip.c是理想的选择。
是否有不推荐使用该技术的场景?为什么?
架构级中断控制器 :如上所述,x86的APIC/IOAPIC、ARM的GIC(通用中断控制器)、RISC-V的PLIC等,这些是CPU架构的一部分,功能复杂,有自己专门的驱动。
消息信号中断(MSI/MSI-X) :这是PCI/PCIe总线规范的一部分,它不通过中断线,而是通过向特定的内存地址写入一个“消息”来触发中断。其处理机制完全不同,由PCI子系统专门处理,不使用irq_chip模型。
对比分析 请将其 与 其他相似技术 进行详细对比。 使用generic-chip.c vs. 编写完全自定义的irq_chip实现
特性
使用generic-chip.c
编写完全自定义的irq_chip实现
实现方式
驱动程序填充irq_chip结构时,将其函数指针指向generic-chip.c中已有的函数 。
驱动程序需要自己编写 irq_mask_my_chip(), irq_unmask_my_chip()等所有回调函数的完整实现。
开发工作量
低 。开发者主要工作是描述硬件布局和填充数据结构。
高 。需要编写、调试和维护大量底层的、重复的寄存器操作代码。
代码体积
小 。驱动代码非常紧凑。
大 。驱动代码包含大量实现细节。
健壮性
高 。依赖于内核核心的、经过广泛测试的通用代码。
依赖于开发者 。更容易引入因位操作错误、锁处理不当等导致的bug。
灵活性
中等 。能够覆盖绝大多数标准硬件模型。
最高 。可以实现任何非标准的、独特的硬件行为。
适用场景
绝大多数SoC外设和简单的中断控制器。
功能复杂、非标准的架构级中断控制器,或者有特殊性能优化需求的场景。
irq_init_generic_chip 初始化通用中断芯片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 void irq_init_generic_chip (struct irq_chip_generic *gc, const char *name, int num_ct, unsigned int irq_base, void __iomem *reg_base, irq_flow_handler_t handler) { struct irq_chip_type *ct = gc->chip_types; int i; raw_spin_lock_init(&gc->lock); gc->num_ct = num_ct; gc->irq_base = irq_base; gc->reg_base = reg_base; for (i = 0 ; i < num_ct; i++) ct[i].chip.name = name; gc->chip_types->handler = handler; }
__irq_alloc_domain_generic_chips 为 irq 域分配通用芯片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 int irq_domain_alloc_generic_chips (struct irq_domain *d, const struct irq_domain_chip_generic_info *info) { struct irq_domain_chip_generic *dgc ; struct irq_chip_generic *gc ; unsigned long flags; int numchips, i; size_t dgc_sz; size_t gc_sz; size_t sz; void *tmp; int ret; if (d->gc) return -EBUSY; numchips = DIV_ROUND_UP(d->revmap_size, info->irqs_per_chip); if (!numchips) return -EINVAL; gc_sz = struct_size(gc, chip_types, info->num_ct); dgc_sz = struct_size(dgc, gc, numchips); sz = dgc_sz + numchips * gc_sz; tmp = dgc = kzalloc(sz, GFP_KERNEL); if (!dgc) return -ENOMEM; dgc->irqs_per_chip = info->irqs_per_chip; dgc->num_chips = numchips; dgc->irq_flags_to_set = info->irq_flags_to_set; dgc->irq_flags_to_clear = info->irq_flags_to_clear; dgc->gc_flags = info->gc_flags; dgc->exit = info->exit ; d->gc = dgc; tmp += dgc_sz; for (i = 0 ; i < numchips; i++) { dgc->gc[i] = gc = tmp; irq_init_generic_chip(gc, info->name, info->num_ct, i * dgc->irqs_per_chip, NULL , info->handler); gc->domain = d; if (dgc->gc_flags & IRQ_GC_BE_IO) { gc->reg_readl = &irq_readl_be; gc->reg_writel = &irq_writel_be; } if (info->init) { ret = info->init(gc); if (ret) goto err; } raw_spin_lock_irqsave(&gc_lock, flags); list_add_tail(&gc->list , &gc_list); raw_spin_unlock_irqrestore(&gc_lock, flags); tmp += gc_sz; } return 0 ; err: while (i--) { if (dgc->exit ) dgc->exit (dgc->gc[i]); irq_remove_generic_chip(dgc->gc[i], ~0U , 0 , 0 ); } d->gc = NULL ; kfree(dgc); return ret; } EXPORT_SYMBOL_GPL(irq_domain_alloc_generic_chips); int __irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, int num_ct, const char *name, irq_flow_handler_t handler, unsigned int clr, unsigned int set , enum irq_gc_flags gcflags) { struct irq_domain_chip_generic_info info = { .irqs_per_chip = irqs_per_chip, .num_ct = num_ct, .name = name, .handler = handler, .irq_flags_to_clear = clr, .irq_flags_to_set = set , .gc_flags = gcflags, }; return irq_domain_alloc_generic_chips(d, &info); } EXPORT_SYMBOL_GPL(__irq_alloc_domain_generic_chips);
irq_get_domain_generic_chip 获取中断域的通用芯片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static struct irq_chip_generic *__irq_get_domain_generic_chip (struct irq_domain *d , unsigned int hw_irq ) { struct irq_domain_chip_generic *dgc = d->gc; int idx; if (!dgc) return ERR_PTR(-ENODEV); idx = hw_irq / dgc->irqs_per_chip; if (idx >= dgc->num_chips) return ERR_PTR(-EINVAL); return dgc->gc[idx]; } struct irq_chip_generic *irq_get_domain_generic_chip (struct irq_domain *d, unsigned int hw_irq) { struct irq_chip_generic *gc = __irq_get_domain_generic_chip(d, hw_irq); return !IS_ERR(gc) ? gc : NULL ; } EXPORT_SYMBOL_GPL(irq_get_domain_generic_chip);
IRQ通用芯片的系统核心电源管理操作 本代码片段定义了Linux内核中“通用中断芯片”(Generic IRQ Chip)框架与系统核心电源管理(syscore_ops)之间的接口。其核心功能是,在系统进入挂起(suspend)、恢复(resume)或关机(shutdown)等关键状态转换时,能够正确地通知并调用每一个通用中断控制器驱动提供的相应电源管理回调函数。这确保了作为系统核心部件的中断控制器能够在电源状态变化时,正确地保存、恢复其状态或执行清理操作。
实现原理分析 此功能的实现依赖于Linux内核的一个基础电源管理框架——syscore_ops。
syscore_ops 框架 :
syscore_ops(System Core Operations)是为那些不属于标准设备模型 但又需要在系统范围内进行电源管理的核心子系统 设计的。标准设备(如一个USB设备)的电源管理由其驱动在设备模型框架内处理。但像中断控制器这样的核心部件,它的挂起操作必须在所有设备之前执行,恢复操作必须在所有设备之后执行。syscore_ops正是为此提供了在合适时机执行回调的机制。
通用中断芯片 (irq_chip_generic) :
为了避免为每一种中断控制器都重写大量相似的代码,内核提供了一个“通用中断芯片”框架。各种具体的中断控制器驱动(如ARM GIC, STM32 NVIC)可以注册为irq_chip_generic,并提供一组符合标准接口的回调函数。
gc_list是一个全局链表,它串联了系统中所有已注册的irq_chip_generic实例。
注册与回调流程 :
初始化 : 在内核启动期间,irq_gc_init_ops函数被调用。它唯一的任务就是调用register_syscore_ops,将一个包含了suspend, resume, shutdown函数指针的irq_gc_syscore_ops结构体注册到内核的syscore处理链表中。
执行 : 当系统管理员执行poweroff或systemctl suspend等命令时,内核的电源管理核心会遍历所有已注册的syscore_ops。当遍历到irq_gc_syscore_ops时,它会根据当前的操作(关机或挂起)调用相应的函数指针。
分发 : 被调用的函数(例如irq_gc_shutdown)会遍历gc_list全局链表,从而找到系统中的每一个通用中断控制器。然后,它会检查该控制器是否提供了特定的电源管理回调函数(如chip.irq_pm_shutdown)。如果提供了,就执行它。这个过程将一个系统级的电源事件,成功地分发到了每一个具体的中断控制器驱动中。
代码分析 宏定义与条件编译 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #ifdef CONFIG_PM_SLEEP static int irq_gc_suspend (void ) { struct irq_chip_generic *gc ; list_for_each_entry_reverse(gc, &gc_list, list ) { struct irq_chip_type *ct = gc->chip_types; if (ct->chip.irq_pm_suspend) { struct irq_data *data = irq_gc_get_irq_data(gc); if (data) ct->chip.irq_pm_suspend(data); } } return 0 ; } static void irq_gc_resume (void ) { struct irq_chip_generic *gc ; list_for_each_entry(gc, &gc_list, list ) { struct irq_chip_type *ct = gc->chip_types; if (ct->chip.irq_pm_resume) { struct irq_data *data = irq_gc_get_irq_data(gc); if (data) ct->chip.irq_pm_resume(data); } } } #else #define irq_gc_suspend NULL #define irq_gc_resume NULL #endif
irq_gc_shutdown 函数在系统关机或重启时被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void irq_gc_shutdown (void ) { struct irq_chip_generic *gc ; list_for_each_entry(gc, &gc_list, list ) { struct irq_chip_type *ct = gc->chip_types; if (ct->chip.irq_pm_shutdown) { struct irq_data *data = irq_gc_get_irq_data(gc); if (data) ct->chip.irq_pm_shutdown(data); } } }
syscore_ops 结构体与初始化函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static struct syscore_ops irq_gc_syscore_ops = { .suspend = irq_gc_suspend, .resume = irq_gc_resume, .shutdown = irq_gc_shutdown, }; static int __init irq_gc_init_ops (void ) { register_syscore_ops(&irq_gc_syscore_ops); return 0 ; }
kernel/irq/chip.c irq_set_handler_data 为 IRQ 设置 IRQ 处理程序数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int irq_set_handler_data (unsigned int irq, void *data) { scoped_irqdesc_get_and_lock(irq, 0 ) { scoped_irqdesc->irq_common_data.handler_data = data; return 0 ; } return -EINVAL; } EXPORT_SYMBOL(irq_set_handler_data);
__irq_do_set_handler 设置中断处理程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 static void __irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle, int is_chained, const char *name) { if (!handle) { handle = handle_bad_irq; } else { struct irq_data *irq_data = &desc->irq_data; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY while (irq_data) { if (irq_data->chip != &no_irq_chip) break ; if (WARN_ON(is_chained)) return ; irq_data = irq_data->parent_data; } #endif if (WARN_ON(!irq_data || irq_data->chip == &no_ir_chip)) return ; } if (handle == handle_bad_irq) { if (desc->irq_data.chip != &no_irq_chip) mask_ack_irq(desc); irq_state_set_disabled(desc); if (is_chained) { desc->action = NULL ; WARN_ON(irq_chip_pm_put(irq_desc_get_irq_data(desc))); } desc->depth = 1 ; } desc->handle_irq = handle; desc->name = name; if (handle != handle_bad_irq && is_chained) { unsigned int type = irqd_get_trigger_type(&desc->irq_data); if (type != IRQ_TYPE_NONE) { __irq_set_trigger(desc, type); desc->handle_irq = handle; } irq_settings_set_noprobe(desc); irq_settings_set_norequest(desc); irq_settings_set_nothread(desc); desc->action = &chained_action; WARN_ON(irq_chip_pm_get(irq_desc_get_irq_data(desc))); irq_activate_and_startup(desc, IRQ_RESEND); } }
__irq_set_handler 设置中断处理程序 1 2 3 4 5 6 7 void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, const char *name) { scoped_irqdesc_get_and_lock(irq, 0 ) __irq_do_set_handler(scoped_irqdesc, handle, is_chained, name); } EXPORT_SYMBOL_GPL(__irq_set_handler);
handle_fasteoi_irq 用于透明中断控制器的irq处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 void handle_fasteoi_irq (struct irq_desc *desc) { struct irq_chip *chip = desc->irq_data.chip; guard(raw_spinlock)(&desc->lock); if (!irq_can_handle_pm(desc)) { if (irqd_needs_resend_when_in_progress(&desc->irq_data)) desc->istate |= IRQS_PENDING; cond_eoi_irq(chip, &desc->irq_data); return ; } if (!irq_can_handle_actions(desc)) { mask_irq(desc); cond_eoi_irq(chip, &desc->irq_data); return ; } kstat_incr_irqs_this_cpu(desc); if (desc->istate & IRQS_ONESHOT) mask_irq(desc); handle_irq_event(desc); cond_unmask_eoi_irq(desc, chip); if (unlikely(desc->istate & IRQS_PENDING)) check_irq_resend(desc, false ); } EXPORT_SYMBOL_GPL(handle_fasteoi_irq);
handle_edge_irq 边沿类型IRQ处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 void handle_edge_irq (struct irq_desc *desc) { guard(raw_spinlock)(&desc->lock); if (!irq_can_handle(desc)) { desc->istate |= IRQS_PENDING; mask_ack_irq(desc); return ; } kstat_incr_irqs_this_cpu(desc); desc->irq_data.chip->irq_ack(&desc->irq_data); do { if (unlikely(!desc->action)) { mask_irq(desc); return ; } if (unlikely(desc->istate & IRQS_PENDING)) { if (!irqd_irq_disabled(&desc->irq_data) && irqd_irq_masked(&desc->irq_data)) unmask_irq(desc); } handle_irq_event(desc); } while ((desc->istate & IRQS_PENDING) && !irqd_irq_disabled(&desc->irq_data)); } EXPORT_SYMBOL(handle_edge_irq);
irq_get_irq_data 获取中断描述符结构体 1 2 3 4 5 6 7 struct irq_data *irq_get_irq_data (unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); return desc ? &desc->irq_data : NULL ; } EXPORT_SYMBOL_GPL(irq_get_irq_data);
irq_startup 函数族: 中断线的首次启动与使能这一组函数是Linux内核中断子系统的核心组件, 负责执行中断线的首次启动 (startup) 。这不仅仅是简单地”使能”一个中断, 而是一个更深层次的、一次性的初始化过程, 它确保中断控制器(irq_chip)完成了所有必要的硬件设置, 使中断线从一个未配置的状态转换到一个完全准备就绪、可以随时被使能或屏蔽的”实时”状态。
其核心原理是分层委托与状态管理 :
顶层编排 (irq_startup) : 这是暴露给request_irq等上层API调用的主函数。它是一个智能的编排器:
它首先检查一个started状态标志。如果该中断线之前已经被”启动”过, 那么它就不再执行完整、耗时的启动流程, 而是简单地调用irq_enable来重新使能它。这是一个关键的优化。
如果这是第一次启动, 它会重置depth计数器(强制清除所有软件层面的禁用请求), 处理CPU亲和性(affinity)设置, 然后调用底层的__irq_startup来执行核心工作。
它还提供了一个resend选项, 可以在启动后重新探测一次硬件, 以捕获在禁用期间可能错过的中断信号。
核心启动实现 (__irq_startup) : 这是实际执行一次性启动操作的函数。它体现了硬件抽象 :
它会优先检查irq_chip驱动是否提供了一个专门的irq_startup回调函数。如果提供了, 它就调用这个函数。这允许那些需要复杂上电/初始化序列的硬件(例如, 需要配置内部路由或时钟)执行其特定的操作。
如果irq_chip没有提供irq_startup, 它会优雅地回退 到调用通用的irq_enable函数。
最关键的是, 在成功执行后, 它会设置started状态标志, 确保这个一次性的启动过程不会被重复执行。
智能使能逻辑 (irq_enable) : 这个函数负责”使能”一个中断, 但它比简单的硬件解屏蔽(unmask)更智能:
它会区分两种”禁用”状态: 一种是临时的屏蔽 (masked) , 另一种是更深层次的禁用 (disabled) 。
如果中断只是被屏蔽了, 它就只调用unmask_irq。
如果中断是被”禁用”了, 它会先清除软件禁用标志, 然后优先调用irq_chip提供的irq_enable回调(如果存在)。这个回调用于处理那些除了屏蔽位之外还有独立使能位的硬件。如果不存在, 它再回退到unmask_irq。
在STM32H750上, irq_chip驱动对应的是NVIC(嵌套向量中断控制器)。irq_startup和irq_enable的调用最终会转化为对NVIC寄存器的操作, 例如写入ISER(Interrupt Set-Enable Registers)来使能某个中断通道。虽然NVIC的使能操作相对简单, 但这个分层框架确保了驱动代码可以无缝地运行在具有更复杂中断控制器的其他平台上。
irq_enable (智能使能)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void irq_enable (struct irq_desc *desc) { if (!irqd_irq_disabled(&desc->irq_data)) { unmask_irq(desc); } else { irq_state_clr_disabled(desc); if (desc->irq_data.chip->irq_enable) { desc->irq_data.chip->irq_enable(&desc->irq_data); irq_state_clr_masked(desc); } else { unmask_irq(desc); } } }
__irq_startup (核心启动)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static int __irq_startup(struct irq_desc *desc){ struct irq_data *d = irq_desc_get_irq_data(desc); int ret = 0 ; WARN_ON_ONCE(!irqd_is_activated(d)); if (d->chip->irq_startup) { ret = d->chip->irq_startup(d); irq_state_clr_disabled(desc); irq_state_clr_masked(desc); } else { irq_enable(desc); } irq_state_set_started(desc); return ret; }
irq_startup (顶层编排)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int irq_startup (struct irq_desc *desc, bool resend, bool force) { struct irq_data *d = irq_desc_get_irq_data(desc); const struct cpumask *aff = irq_data_get_affinity_mask(d); int ret = 0 ; desc->depth = 0 ; if (irqd_is_started(d)) { irq_enable(desc); } else { switch (__irq_startup_managed(desc, aff, force)) { case IRQ_STARTUP_NORMAL: if (d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP) irq_setup_affinity(desc); ret = __irq_startup(desc); if (!(d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP)) irq_setup_affinity(desc); break ; } } if (resend) check_irq_resend(desc, false ); return ret; }
好的,我将对您提供的这段关于Linux内核中断子系统irq_startup核心实现的代码进行分析。
irq_startup & irq_enable: 中断的首次启动与使能本代码片段展示了Linux中断子系统中一个中断线(IRQ)从“休眠”状态被首次激活并启动 的底层核心逻辑。irq_startup是这个过程的高级入口,它负责处理中断的启动状态、电源管理状态和CPU亲和性(affinity),并最终调用__irq_startup。__irq_startup则会根据中断控制器(irq_chip)的能力,选择调用专用的.irq_startup回调,或者回退到通用的irq_enable函数。irq_enable是更基础的操作,负责解除对一个已经启动 的中断的屏蔽。
实现原理分析 此机制的核心原理是状态机 和分层的回调抽象 ,它严格区分了中断的“首次启动”(Startup)和后续的“使能/取消屏蔽”(Enable/Unmask)。
状态机 : 内核为每个中断维护了多个状态标志,其中最重要的是IRQD_IRQ_STARTED。
一个中断在被request_irq请求后,并不会立即在硬件层面被使能,它可能处于“已激活但未启动”的状态。
irq_startup是第一个将中断状态从“未启动”转换为“已启动”的函数。
一旦IRQD_IRQ_STARTED标志被设置,后续对irq_startup的调用将走入一个快速路径,仅仅调用irq_enable来取消屏蔽,而不会重复执行昂贵的首次启动流程。
分层回调 (.irq_startup vs .irq_enable vs .irq_unmask) :
.irq_startup (可选,最高级) : __irq_startup函数会优先检查irq_chip是否提供了这个回调。这个回调用于那些需要复杂的一次性初始化 的硬件。例如,一个中断控制器在首次使用某个中断线之前,可能需要配置其触发类型、优先级、路由,甚至开启相关的时钟或电源域。.irq_startup就是为这种复杂场景设计的。
.irq_enable (可选,中级) : 如果.irq_startup不存在,__irq_startup会调用irq_enable。irq_enable函数会检查irq_chip是否提供了.irq_enable回调。这个回调用于那些“使能”操作比简单的“取消屏蔽”更复杂的硬件。
.irq_unmask (必需,最低级) : 如果.irq_enable也不存在,irq_enable会回退到调用unmask_irq,而unmask_irq最终会调用irq_chip中必须提供 的.irq_unmask回调。这是最基础的操作,它只负责在中断控制器中取消对该中断线的屏蔽。
irq_startup 的完整流程 :
它是__enable_irq(在上一段分析中)的最终执行者。
它首先重置depth计数器,意在强制启动中断,无论之前被禁用了多少次。
它检查started状态,决定是走“重使能”路径还是“首次启动”路径。
在“首次启动”路径中,它会处理CPU亲和性 (affinity)。某些硬件需要在启动中断前设置好它应该在哪个CPU上触发(IRQCHIP_AFFINITY_PRE_STARTUP),而另一些则相反。irq_startup处理了这两种情况。
它还会处理电源管理 (__irq_startup_managed),确保在启动中断前,相关的硬件已经被唤醒。
最后,check_irq_resend用于处理在中断被禁用期间可能“丢失”的中断信号,确保它们能被重新处理。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 static void irq_enable (struct irq_desc *desc) { if (!irqd_irq_disabled(&desc->irq_data)) { unmask_irq(desc); } else { irq_state_clr_disabled(desc); if (desc->irq_data.chip->irq_enable) { desc->irq_data.chip->irq_enable(&desc->irq_data); irq_state_clr_masked(desc); } else { unmask_irq(desc); } } } static int __irq_startup(struct irq_desc *desc){ struct irq_data *d = irq_desc_get_irq_data(desc); int ret = 0 ; if (d->chip->irq_startup) { ret = d->chip->irq_startup(d); irq_state_clr_disabled(desc); irq_state_clr_masked(desc); } else { irq_enable(desc); } irq_state_set_started(desc); return ret; } int irq_startup (struct irq_desc *desc, bool resend, bool force) { struct irq_data *d = irq_desc_get_irq_data(desc); const struct cpumask *aff = irq_data_get_affinity_mask(d); int ret = 0 ; desc->depth = 0 ; if (irqd_is_started(d)) { irq_enable(desc); } else { switch (__irq_startup_managed(desc, aff, force)) { case IRQ_STARTUP_NORMAL: if (d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP) irq_setup_affinity(desc); ret = __irq_startup(desc); if (!(d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP)) irq_setup_affinity(desc); break ; } } if (resend) check_irq_resend(desc, false ); return ret; }
synchronize_irq & synchronize_hardirq: 中断处理程序的同步等待本代码片段展示了Linux中断子系统中用于同步 的关键API——synchronize_irq及其变体synchronize_hardirq。其核心功能是阻塞当前代码的执行,直到指定中断(IRQ)的所有处理程序都已完全执行完毕 。这是在需要安全地释放被中断处理程序使用的资源(例如,在设备移除或驱动卸载时)之前,必须调用的一个至关重要的同步函数。synchronize_hardirq只等待硬件中断处理部分,而synchronize_irq则会等待硬件和任何关联的中断线程 。
实现原理分析 此机制的原理是通过一个状态标志 (IRQD_IRQ_INPROGRESS)和一个等待队列 ,来协调调用者与可能在任何CPU上并发执行的中断处理程序。
硬中断同步 (__synchronize_hardirq) :
职责 : 等待一个IRQ的硬中断处理函数 (即request_irq注册的handler)执行完毕。
状态标志 : 内核的底层中断入口代码会在调用驱动的handler之前,为该IRQ设置IRQD_IRQ_INPROGRESS标志;在handler返回后清除该标志。
自旋-锁定-再检查 (Spin-Lock-Recheck) :
while (irqd_irq_inprogress(...)) cpu_relax();: 这是一个无锁的自旋等待 。它快速地、反复地检查inprogress标志。cpu_relax()是一个提示,告诉CPU它正在自旋,可以进入一个低功耗状态或让给超线程的另一半。这是一个优化 ,用于快速处理“中断没有在运行”的常见情况。
guard(raw_spinlock_irqsave)(&desc->lock);: 在无锁自旋结束后,它会获取中断描述符的自旋锁 。这个锁提供了必要的内存屏障 ,确保能看到所有CPU对inprogress标志的最新、最准确的状态。
inprogress = irqd_irq_inprogress(...): 在锁内再次检查 inprogress标志。这次检查是绝对可靠的。
__irq_get_irqchip_state(...): 这是一个可选的、更深层次的硬件检查。它会直接查询中断控制器芯片,看该中断是否处于“硬件层面活动”(in-flight)的状态。这可以捕获中断已在硬件层面触发、但尚未被CPU响应的罕见竞态。
while (inprogress);: 如果在锁内的检查仍然发现中断在进行中,整个do-while循环会重复。这保证了当函数最终退出时,硬中断处理绝对已经完成。
完整中断同步 (__synchronize_irq) :
职责 : 等待硬中断处理和所有关联的中断线程 执行完毕。
实现 : 这是一个两步过程。
__synchronize_hardirq(desc, true): 首先,它调用硬中断同步,并传递true来启用硬件芯片状态检查,提供最强的同步保证。
wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active)): 这是处理中断线程的关键 。
desc->threads_active是一个原子计数器,当中断处理程序唤醒一个中断线程时,它会递增此计数器;当中断线程完成其工作后,会递减此计数器。
wait_event是一个可睡眠的 等待原语。它会使当前任务进入睡眠状态,直到!atomic_read(&desc->threads_active)这个条件为真(即所有线程都已完成)。
睡眠特性 : 因为wait_event会睡眠,所以synchronize_irq只能 在可以安全睡眠的进程上下文中调用,绝不能 在原子上下文(如中断或持有自旋锁时)中调用。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip){ struct irq_data *irqd = irq_desc_get_irq_data(desc); bool inprogress; do { while (irqd_irq_inprogress(&desc->irq_data)) cpu_relax(); guard(raw_spinlock_irqsave)(&desc->lock); inprogress = irqd_irq_inprogress(&desc->irq_data); if (!inprogress && sync_chip) { __irq_get_irqchip_state(irqd, IRQCHIP_STATE_ACTIVE, &inprogress); } } while (inprogress); } bool synchronize_hardirq (unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); if (desc) { __synchronize_hardirq(desc, false ); return !atomic_read (&desc->threads_active); } return true ; } EXPORT_SYMBOL(synchronize_hardirq); static void __synchronize_irq(struct irq_desc *desc){ __synchronize_hardirq(desc, true ); wait_event(desc->wait_for_threads, !atomic_read (&desc->threads_active)); } void synchronize_irq (unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); if (desc) __synchronize_irq(desc); } EXPORT_SYMBOL(synchronize_irq);