[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; }