[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
/*
* 用于链式处理器的入口/出口函数,其中主IRQ芯片可能实现
* fasteoi或电平触发流控。
*/
/*
* 这是一个静态内联函数,作为链式中断处理器的标准入口点。
* @chip: 指向顶层(父)中断的中断控制器芯片结构体。
* @desc: 指向顶层(父)中断的中断描述符。
*/
static inline void chained_irq_enter(struct irq_chip *chip,
struct irq_desc *desc)
{
/*
* 注释:FastEOI控制器在入口处无需任何操作。
*
* 原理:检查irq_chip是否定义了irq_eoi回调。如果定义了,
* 表明这是一个支持FastEOI模式的控制器。在这种模式下,
* 中断屏蔽和确认被推迟到处理流程的最后(在chained_irq_exit中)。
*/
if (chip->irq_eoi)
return;

/*
* 检查irq_chip是否定义了一个原子的“屏蔽并确认”回调。
*/
if (chip->irq_mask_ack) {
/* 如果定义了,则直接调用该函数,在硬件上原子地屏蔽并确认中断。*/
chip->irq_mask_ack(&desc->irq_data);
} else {
/*
* 如果没有原子的mask_ack,则执行分离的操作。
* 首先,调用irq_mask回调,在硬件上屏蔽该中断线,这是防止重入的关键。
*/
chip->irq_mask(&desc->irq_data);
/*
* 然后,检查是否定义了irq_ack回调。
*/
if (chip->irq_ack)
/* 如果定义了,则调用它,向硬件确认该中断。*/
chip->irq_ack(&desc->irq_data);
}
}

/*
* 这是一个静态内联函数,作为链式中断处理器的标准出口点。
* @chip: 指向顶层(父)中断的中断控制器芯片结构体。
* @desc: 指向顶层(父)中断的中断描述符。
*/
static inline void chained_irq_exit(struct irq_chip *chip,
struct irq_desc *desc)
{
/*
* 检查irq_chip是否定义了irq_eoi回调,以判断是否为FastEOI模式。
*/
if (chip->irq_eoi)
/*
* 如果是FastEOI模式,则调用irq_eoi回调。
* 这个函数会向中断控制器发送一个“中断结束”信号,
* 完成对本次中断处理的确认和状态复位。
*/
chip->irq_eoi(&desc->irq_data);
else
/*
* 如果是常规的mask/unmask模式,则调用irq_unmask回调。
* 这个函数会在硬件层面重新使能(解除屏蔽)该中断线,
* 使其可以响应下一次中断事件。
*/
chip->irq_unmask(&desc->irq_data);
}

include/linux/irqchip.h

IRQCHIP_DECLARE DT 兼容字符串与初始化函数之间的关联

1
2
3
4
5
6
7
8
9
/*
* 不同的 irqchip 驱动程序必须使用此宏来声明其 DT 兼容字符串与其初始化函数之间的关联。
*
* @name: name 中,该名称在同一文件的所有IRQCHIP_DECLARE中必须是唯一的。
* @compat: IRQCip 驱动的兼容字符串
* @fn: 初始化函数
*/
#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/)的演进紧密相连。

  1. struct irq_chip的标准化:首先,内核定义了struct irq_chip,这是一个包含了一系列函数指针(如.irq_mask, .irq_unmask, .irq_ack等)的结构体。这为所有中断控制器驱动提供了一个标准的回调接口。
  2. 通用函数的出现:开发者们意识到,许多驱动在填充irq_chip结构时,填入的函数逻辑是重复的。于是,generic-chip.c应运而生,它提供了一系列现成的、可直接赋值给irq_chip函数指针的通用函数,如irq_gc_mask_set_bit
  3. 参数化与灵活性:为了让这些通用函数能够适用于不同的硬件(寄存器地址和位布局不同),通用芯片框架被设计为参数化的。驱动程序在设置IRQ域(IRQ domain)时,会将每个中断号(IRQ number)对应的硬件信息(如寄存器偏移量、位掩码等)存储在per-IRQ的数据结构中。通用函数在执行时,会从这些数据结构中获取参数,从而操作正确的硬件。
  4. 支持多种寄存器布局:框架不断演进,以支持更广泛的硬件实现,例如:
    • 不同的寄存器宽度(8-bit, 16-bit, 32-bit, 64-bit)。
    • 支持“读-改-写”和“写1清零/置位”等不同的寄存器操作模式。
    • 支持屏蔽和解除屏蔽使用同一个寄存器(通过读写不同的值)或使用两个独立的寄存器。

目前该技术的社区活跃度和主流应用情况如何?

generic-chip.c是Linux内核IRQ子系统中一个极其稳定和核心的部分。

  • 标准实现方式:对于绝大多数嵌入式系统和片上系统(SoC)中的中断控制器(特别是GPIO控制器、I2C/SPI控制器等),使用generic-chip.c编写驱动程序的标准和首选方式
  • 代码稳定性:其核心逻辑非常成熟,很少发生大的变动。社区的活跃度主要体现在为新的硬件特性或寄存器布局增加新的通用辅助函数。

核心原理与设计

它的核心工作原理是什么?

generic-chip.c的核心原理是**“模板方法”设计模式**的应用。它提供了一套通用的算法框架(如如何屏蔽一个中断),而将算法中易变的部分(具体的寄存器地址和位)推迟到子类(具体的设备驱动)中去定义。

其工作流程如下:

  1. 驱动定义硬件布局:一个中断控制器驱动程序首先定义其硬件寄存器的布局信息。
  2. 选择并填充irq_chip:驱动程序创建一个struct irq_chip的实例,并将其函数指针(如.irq_mask, .irq_unmask)指向generic-chip.c中提供的通用函数,例如irq_gc_mask_set_bitgc代表”generic chip”。
  3. 设置IRQ域和per-IRQ数据:驱动程序在初始化IRQ域时,会为它所管理的每一个中断号,设置一个irq_data结构。驱动会将这个中断号对应的具体硬件参数(例如,控制这个中断的寄存器相对于基地址的偏移量、它在这个寄存器中的是第几位)存储到这个irq_data的私有字段中。
  4. VFS/上层触发操作:当内核需要屏蔽一个中断时(例如,mask_irq(nr)),通用的IRQ核心代码会找到这个中断号nr对应的irq_chip,并调用其.irq_mask函数指针。
  5. 通用函数执行:此时,执行权交给了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
/*
* irq_init_generic_chip - 初始化一个通用中断芯片结构体。
* @gc: 指向需要被初始化的irq_chip_generic结构体。
* @name: 中断芯片的名称字符串。
* @num_ct: 该通用芯片管理的芯片类型(chip_types)的数量。通常为1。
* @irq_base: 该芯片管理的第一个中断所对应的Linux IRQ编号。
* @reg_base: 该中断控制器硬件寄存器的内存映射基地址。
* @handler: 一个irq_flow_handler_t函数指针,作为该芯片所有中断的默认流处理器。
*/
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)
{
/* ct: 指向gc内部的chip_types数组的第一个元素。*/
struct irq_chip_type *ct = gc->chip_types;
/* i: 用于循环计数。*/
int i;

/* 初始化保护该通用芯片寄存器访问的原始自旋锁。*/
raw_spin_lock_init(&gc->lock);
/* 设置该通用芯片管理的芯片类型数量。*/
gc->num_ct = num_ct;
/* 设置该芯片管理的第一个中断的IRQ编号。*/
gc->irq_base = irq_base;
/* 设置硬件寄存器的虚拟内存基地址。*/
gc->reg_base = reg_base;

/* 循环遍历所有的芯片类型(通常只有一个)。*/
for (i = 0; i < num_ct; i++)
/* 为每个芯片类型的irq_chip结构体设置名称。*/
ct[i].chip.name = name;

/*
* 将传入的handler函数指针,赋给第一个芯片类型(chip_types[0])的handler字段。
* 这是关键的赋值操作。
*/
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
/**
* irq_domain_alloc_generic_chips - Allocate generic chips for an irq domain
* @d: irq domain for which to allocate chips
* @info: Generic chip information
*
* Return: 0 on success, negative error code on failure
*/
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;
/* 分配内存 */
/* Allocate a pointer, generic chip and chiptypes for each chip */
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;

/* Calc pointer to the first generic chip */
tmp += dgc_sz;
/* 初始化每个通用中断芯片 */
for (i = 0; i < numchips; i++) {
/* Store the pointer to the generic chip */
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);
/* Calc pointer to the next generic chip */
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);

/**
* __irq_alloc_domain_generic_chips - 为 irq 域分配通用芯片
* @d: irq domain for which to allocate chips
* @irqs_per_chip: Number of interrupts each chip handles (max 32)
* @num_ct: Number of irq_chip_type instances associated with this
* @name: Name of the irq chip
* @handler: Default flow handler associated with these chips
* @clr: IRQ_* bits to clear in the mapping function
* @set: IRQ_* bits to set in the mapping function
* @gcflags: Generic chip specific setup flags
*/
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];
}

/**
* irq_get_domain_generic_chip - Get a pointer to the generic chip of a hw_irq
* @d: irq domain pointer
* @hw_irq: Hardware interrupt number
*/
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

  1. syscore_ops 框架:

    • syscore_ops(System Core Operations)是为那些不属于标准设备模型但又需要在系统范围内进行电源管理的核心子系统设计的。标准设备(如一个USB设备)的电源管理由其驱动在设备模型框架内处理。但像中断控制器这样的核心部件,它的挂起操作必须在所有设备之前执行,恢复操作必须在所有设备之后执行。syscore_ops正是为此提供了在合适时机执行回调的机制。
  2. 通用中断芯片 (irq_chip_generic):

    • 为了避免为每一种中断控制器都重写大量相似的代码,内核提供了一个“通用中断芯片”框架。各种具体的中断控制器驱动(如ARM GIC, STM32 NVIC)可以注册为irq_chip_generic,并提供一组符合标准接口的回调函数。
    • gc_list是一个全局链表,它串联了系统中所有已注册的irq_chip_generic实例。
  3. 注册与回调流程:

    • 初始化: 在内核启动期间,irq_gc_init_ops函数被调用。它唯一的任务就是调用register_syscore_ops,将一个包含了suspend, resume, shutdown函数指针的irq_gc_syscore_ops结构体注册到内核的syscore处理链表中。
    • 执行: 当系统管理员执行poweroffsystemctl 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 // 如果内核编译时配置了支持睡眠(suspend-to-ram)功能...

// irq_gc_suspend: 在系统进入挂起状态时被调用。
static int irq_gc_suspend(void)
{
struct irq_chip_generic *gc;

// 从后向前遍历gc_list链表。挂起操作通常与初始化顺序相反。
list_for_each_entry_reverse(gc, &gc_list, list) {
struct irq_chip_type *ct = gc->chip_types; // 获取芯片类型。

// 检查芯片是否提供了 irq_pm_suspend 回调函数。
if (ct->chip.irq_pm_suspend) {
// 获取与此通用芯片关联的irq_data,它包含了中断上下文信息。
struct irq_data *data = irq_gc_get_irq_data(gc);

if (data)
// 调用具体驱动提供的挂起函数。
ct->chip.irq_pm_suspend(data);
}
}
return 0;
}

// irq_gc_resume: 在系统从挂起状态恢复时被调用。
static void irq_gc_resume(void)
{
struct irq_chip_generic *gc;

// 从前向后遍历gc_list链表。恢复操作通常与初始化顺序相同。
list_for_each_entry(gc, &gc_list, list) {
struct irq_chip_type *ct = gc->chip_types;

// 检查芯片是否提供了 irq_pm_resume 回调函数。
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 // 如果内核编译时没有配置支持睡眠功能...

// 使用宏将suspend和resume定义为NULL。
// 这是一种编译时优化,任何对这些函数指针的引用都会在编译时被优化掉,
// 不会产生任何运行时开销。
#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;

// 从前向后遍历gc_list链表,找到每一个通用中断控制器。
list_for_each_entry(gc, &gc_list, list) {
struct irq_chip_type *ct = gc->chip_types; // 获取芯片类型。

// 检查该芯片驱动是否实现了 irq_pm_shutdown 回调。
if (ct->chip.irq_pm_shutdown) {
// 获取中断上下文数据。
struct irq_data *data = irq_gc_get_irq_data(gc);

if (data)
// 调用具体驱动提供的关机函数,例如,让NVIC驱动禁用所有中断。
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
// 声明并初始化一个syscore_ops结构体实例。
static struct syscore_ops irq_gc_syscore_ops = {
// 将 .suspend 成员指向我们上面定义的 irq_gc_suspend 函数 (或NULL)。
.suspend = irq_gc_suspend,
// 将 .resume 成员指向我们上面定义的 irq_gc_resume 函数 (或NULL)。
.resume = irq_gc_resume,
// 将 .shutdown 成员指向 irq_gc_shutdown 函数。
.shutdown = irq_gc_shutdown,
};

// irq_gc_init_ops: 初始化函数,在内核启动时被调用。
// __init 标记告诉编译器,这个函数及其相关数据在初始化完成后就可以被丢弃,以节省内存。
static int __init irq_gc_init_ops(void)
{
// 核心操作:将我们定义好的 irq_gc_syscore_ops 结构体注册到
// 内核的系统核心电源管理框架中。
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
/**
* irq_set_handler_data - 为 IRQ 设置 IRQ 处理程序数据
* @irq:中断编号
* @data:中断特定数据的指针
*
* 设置 irq 的硬件 irq 控制器数据
*/
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
/*
* 这是一个静态内部函数,负责为一个中断描述符设置其流处理函数。
* @desc: 指向目标中断描述符。
* @handle: 要设置的irq_flow_handler_t函数指针。
* @is_chained: 布尔值,如果为true,表示这是一个链式中断。
* @name: 与此处理器关联的名称字符串。
*/
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
int is_chained, const char *name)
{
/* 如果传入的handle为NULL,则强制将其设置为默认的“坏中断”处理器。*/
if (!handle) {
handle = handle_bad_irq;
} else {
/* irq_data: 指向中断描述符中包含硬件信息的irq_data结构体。*/
struct irq_data *irq_data = &desc->irq_data;
/* 如果内核配置了层级中断域支持。*/
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/*
* 在层级域中,我们可能会遇到这样的情况:最外层的芯片尚未设置,
* 但内层的芯片已经就绪。与其在这里失败退出,我们选择安装处理器,
* 但很明显,我们此时不能使能/启动这个中断。
*/
/* 向上遍历父中断域,直到找到一个已正确设置irq_chip的域。*/
while (irq_data) {
/* 如果找到了一个不是no_irq_chip的有效芯片,则跳出循环。*/
if (irq_data->chip != &no_irq_chip)
break;
/*
* 如果外层芯片未设置,并且中断被要求立即启动,
* 则发出警告并退出。
*/
if (WARN_ON(is_chained))
return;
/* 尝试其父域的数据。*/
irq_data = irq_data->parent_data;
}
#endif
/* 这是一个健壮性检查,如果最终还是没有找到有效的irq_chip,则发出警告并退出。*/
if (WARN_ON(!irq_data || irq_data->chip == &no_ir_chip))
return;
}

/* 是卸载操作吗?*/
/* 如果要设置的处理器是handle_bad_irq,则视为一次“卸载”或禁用操作。*/
if (handle == handle_bad_irq) {
/* 如果有有效的irq_chip,则调用mask_ack_irq在硬件上屏蔽并确认该中断。*/
if (desc->irq_data.chip != &no_irq_chip)
mask_ack_irq(desc);
/* 在软件上将中断状态设置为已禁用。*/
irq_state_set_disabled(desc);
/* 如果是链式中断,则清理其action链表。*/
if (is_chained) {
desc->action = NULL;
WARN_ON(irq_chip_pm_put(irq_desc_get_irq_data(desc)));
}
/* 重置中断嵌套深度。*/
desc->depth = 1;
}

/* 核心赋值:将中断描述符的handle_irq函数指针设置为传入的handle。*/
desc->handle_irq = handle;
/* 设置中断的名称。*/
desc->name = name;

/* 如果设置的是一个有效的处理器,并且这是一个链式中断。*/
if (handle != handle_bad_irq && is_chained) {
/* 获取该中断当前的触发类型(如边沿、电平)。*/
unsigned int type = irqd_get_trigger_type(&desc->irq_data);

/*
* 我们即将立即启动这个中断,因此需要设置触发配置。
* 但是.set_type回调可能会覆盖掉流处理器,因为它不知道
* 我们正在处理一个链式中断。我们立即重置它,因为我们
* 更清楚情况。
*/
if (type != IRQ_TYPE_NONE) {
/* 调用__irq_set_trigger来实际配置硬件的触发方式。*/
__irq_set_trigger(desc, type);
/* 再次强制设置handle_irq,防止被__irq_set_trigger意外修改。*/
desc->handle_irq = handle;
}

/* 设置一系列标志,表明这是一个内核内部的链式中断,不应再进行探测或请求等。*/
irq_settings_set_noprobe(desc);
irq_settings_set_norequest(desc);
irq_settings_set_nothread(desc);
/* 将中断的action链表指向一个特殊的chained_action。*/
desc->action = &chained_action;
WARN_ON(irq_chip_pm_get(irq_desc_get_irq_data(desc)));
/*
* 调用irq_activate_and_startup,它会激活中断并最终调用irq_chip的
* .irq_unmask或.irq_startup回调,在硬件上使能这个中断。
*/
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
/*
* handle_fasteoi_irq - 用于透明中断控制器的irq处理器
* @desc: 该irq的中断描述符结构体
*
* 只会对芯片发出一个回调:当中断被服务完毕后,调用一次 ->eoi()。
* 这使得内核能支持那些在硬件中透明地处理流程细节的现代中断处理器。
*/
void handle_fasteoi_irq(struct irq_desc *desc)
{
/* 获取与此中断关联的irq_chip结构体,里面包含了操作硬件的函数指针。*/
struct irq_chip *chip = desc->irq_data.chip;

/*
* 使用guard宏来获取并自动释放自旋锁。这是为了保护desc结构体在多核环境下的并发访问,
* 确保在处理期间,其状态不会被其他CPU弄乱。
*/
guard(raw_spinlock)(&desc->lock);

/*
* 当CPU亲和性(affinity)的变更与中断处理发生竞争时,下一个中断可能在新CPU上到达,
* 而此时原来的CPU还未完成前一个中断的处理 —— 这时可能需要重新发送中断。
* irq_can_handle_pm检查设备是否处于可处理中断的电源状态。如果设备已休眠,则不能处理。
*/
if (!irq_can_handle_pm(desc)) {
/*
* 检查这个中断是否需要在处理过程中被重新触发。如果是,就设置IRQS_PENDING标志,
* 以便稍后由check_irq_resend处理。
*/
if (irqd_needs_resend_when_in_progress(&desc->irq_data))
desc->istate |= IRQS_PENDING;
/*
* 有条件地发送EOI。因为我们没有处理中断,所以只发送EOI,不进行unmask。
* 通知硬件可以发送下一个中断了。
*/
cond_eoi_irq(chip, &desc->irq_data);
return;
}

/*
* 检查这个中断是否有注册的中断服务例程(ISR)。如果没有(desc->action == NULL),
* 说明这是一个未被任何驱动申请的中断(可能是杂散中断)。
*/
if (!irq_can_handle_actions(desc)) {
/* 在软件层面屏蔽此中断,防止它继续风暴式地产生。*/
mask_irq(desc);
/* 发送EOI,结束本次中断。*/
cond_eoi_irq(chip, &desc->irq_data);
return;
}

/* 增加本CPU处理的中断计数,用于/proc/interrupts等处的统计。*/
kstat_incr_irqs_this_cpu(desc);
/*
* 检查是否为“一次性触发”(ONESHOT)模式。这种模式下,中断在处理前必须被屏蔽,
* 且只能由驱动程序在处理完毕后显式地重新使能。
*/
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);

/*
* **核心调用**:处理中断事件。这个函数会遍历desc->action链表,
* 并调用由驱动程序通过request_irq()注册的那个最终的ISR函数。
* 这是真正执行设备相关工作的步骤。
*/
handle_irq_event(desc);

/*
* 有条件地取消屏蔽并发送EOI。
* - 如果不是ONESHOT模式,它会调用chip->irq_unmask()和chip->irq_eoi()。
* - 如果是ONESHOT模式,它只会调用chip->irq_eoi(),unmask操作留给驱动去做。
* 对于Fast EOI控制器,硬件收到EOI后会自动unmask。
*/
cond_unmask_eoi_irq(desc, chip);

/*
* 当上面注释中描述的竞争发生时,istate中会带有IRQS_PENDING标志。
* 这里的check_irq_resend会检查该标志,并将中断重新投递到正确的CPU上,
* 从而确保没有中断会丢失。unlikely()是编译器优化提示。
*/
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
/**
* handle_edge_irq - 边沿类型IRQ处理器
* @desc: 该irq的中断描述符结构体
*
* 中断在硬件信号的下降沿和/或上升沿发生。这个事件被锁存在IRQ控制器
* 硬件中,并且必须被应答(ack)才能被重新使能。在应答之后,即使第一个
* 中断事件还未被其关联的事件处理器处理完,同一个中断源上也可能发生
* 另一次中断。如果发生这种情况,根据控制器硬件的不同,可能需要禁用(mask)
* 该中断。这就要求在处理循环内部重新使能中断,该循环用于处理在处理器
* 运行时到达的中断。如果所有挂起的中断都被处理完毕,循环就会退出。
*/
void handle_edge_irq(struct irq_desc *desc)
{
/* 获取并自动释放描述符锁,保护并发访问。*/
guard(raw_spinlock)(&desc->lock);

/* 检查中断是否能被处理(如电源状态是否正常)。*/
if (!irq_can_handle(desc)) {
/*
* 如果不能处理,我们不能就这么算了。因为边沿中断已经被硬件锁存,
* 我们必须记录下这个事实。设置IRQS_PENDING标志,表示“有一个中断
* 已经发生,但被搁置了”。
*/
desc->istate |= IRQS_PENDING;
/*
* 屏蔽并应答此中断。即使不能处理,也必须应答,以告知硬件可以
* 准备接收下一个边沿。同时屏蔽它,以防它再次触发。
*/
mask_ack_irq(desc);
return;
}

/* 增加中断统计计数。*/
kstat_incr_irqs_this_cpu(desc);

/*
* **关键步骤1:先应答**
* 在处理实际的设备逻辑之前,立即向中断控制器发送ACK信号。
* 这一步至关重要,它“解锁”了中断控制器,使其能够立即开始监测并
* 锁存下一个新的中断边沿。这个新边沿可能会在我们调用handle_irq_event
* 的过程中到达。
*/
desc->irq_data.chip->irq_ack(&desc->irq_data);

/*
* **关键步骤2:do-while循环**
* 使用do-while循环,因为我们知道至少有一次中断需要处理。
* 这个循环的目的是处理掉所有“积压”的中断。
*/
do {
/* 安全检查:如果没有驱动程序注册ISR,就屏蔽此中断并返回,防止中断风暴。*/
if (unlikely(!desc->action)) {
mask_irq(desc);
return;
}

/*
* 检查IRQS_PENDING标志。如果这个标志被设置了,说明在我们处理
* 期间,有新的中断事件被其他代码路径(如另一个CPU)注意到并标记了。
*/
if (unlikely(desc->istate & IRQS_PENDING)) {
/*
* 如果中断被标记为待处理,并且它当前被软件屏蔽了(但没有被永久禁用),
* 我们就在这里取消屏蔽它。这是为了确保硬件线路是畅通的,
* 以便在循环的下一次迭代中可以正确地处理中断。
*/
if (!irqd_irq_disabled(&desc->irq_data) &&
irqd_irq_masked(&desc->irq_data))
unmask_irq(desc);
}

/*
* 调用我们熟悉的分发器。它会释放锁,并调用驱动的ISR。
* 正是在它释放锁的这段时间里,另一个CPU可能处理同一个中断,
* 发现它正在被处理(通过IRQS_INPROGRESS标志),然后设置IRQS_PENDING
* 标志,并退出。这个标志就是我们循环的驱动力。
*/
handle_irq_event(desc);

/*
* **循环条件**:只要IRQS_PENDING标志被设置(意味着有新的中断待处理)
* 并且该中断没有被永久禁用,就继续循环。
* 当handle_irq_event返回后,我们再次检查IRQS_PENDING。如果它没被设置,
* 说明在我们处理期间没有新的中断进来,循环结束。
*/
} 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)完成了所有必要的硬件设置, 使中断线从一个未配置的状态转换到一个完全准备就绪、可以随时被使能或屏蔽的”实时”状态。

其核心原理是分层委托与状态管理:

  1. 顶层编排 (irq_startup): 这是暴露给request_irq等上层API调用的主函数。它是一个智能的编排器:

    • 它首先检查一个started状态标志。如果该中断线之前已经被”启动”过, 那么它就不再执行完整、耗时的启动流程, 而是简单地调用irq_enable来重新使能它。这是一个关键的优化。
    • 如果这是第一次启动, 它会重置depth计数器(强制清除所有软件层面的禁用请求), 处理CPU亲和性(affinity)设置, 然后调用底层的__irq_startup来执行核心工作。
    • 它还提供了一个resend选项, 可以在启动后重新探测一次硬件, 以捕获在禁用期间可能错过的中断信号。
  2. 核心启动实现 (__irq_startup): 这是实际执行一次性启动操作的函数。它体现了硬件抽象:

    • 它会优先检查irq_chip驱动是否提供了一个专门的irq_startup回调函数。如果提供了, 它就调用这个函数。这允许那些需要复杂上电/初始化序列的硬件(例如, 需要配置内部路由或时钟)执行其特定的操作。
    • 如果irq_chip没有提供irq_startup, 它会优雅地回退到调用通用的irq_enable函数。
    • 最关键的是, 在成功执行后, 它会设置started状态标志, 确保这个一次性的启动过程不会被重复执行。
  3. 智能使能逻辑 (irq_enable): 这个函数负责”使能”一个中断, 但它比简单的硬件解屏蔽(unmask)更智能:

    • 它会区分两种”禁用”状态: 一种是临时的屏蔽 (masked), 另一种是更深层次的禁用 (disabled)
    • 如果中断只是被屏蔽了, 它就只调用unmask_irq
    • 如果中断是被”禁用”了, 它会先清除软件禁用标志, 然后优先调用irq_chip提供的irq_enable回调(如果存在)。这个回调用于处理那些除了屏蔽位之外还有独立使能位的硬件。如果不存在, 它再回退到unmask_irq

在STM32H750上, irq_chip驱动对应的是NVIC(嵌套向量中断控制器)。irq_startupirq_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)
{
// 检查中断是否处于"软件禁用"状态. irqd_irq_disabled() 检查 desc->istate 中的 IRQS_DISABLED 位.
if (!irqd_irq_disabled(&desc->irq_data)) {
// 如果不是"软件禁用"状态 (可能只是被屏蔽 masked), 则直接调用 unmask_irq.
// unmask_irq 最终会调用 chip->irq_unmask() 来操作硬件.
unmask_irq(desc);
} else {
// 如果是"软件禁用"状态, 需要执行更完整的使能流程.
// 首先清除软件中的"禁用"标志位.
irq_state_clr_disabled(desc);
// 检查 irq_chip 是否提供了专门的 irq_enable 回调函数.
// 某些硬件的"使能"和"解屏蔽"是两个独立的操作.
if (desc->irq_data.chip->irq_enable) {
// 如果提供了, 调用它来执行硬件使能操作.
desc->irq_data.chip->irq_enable(&desc->irq_data);
// 成功后, 也一并清除软件中的"屏蔽"标志位.
irq_state_clr_masked(desc);
} else {
// 如果没有提供专门的 irq_enable, 则回退到使用 unmask_irq.
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;

// 警告: 正常情况下, 中断在启动前应该已经被"激活"(activated)了.
WARN_ON_ONCE(!irqd_is_activated(d));

// 检查 irq_chip 是否提供了专门的 irq_startup 回调.
if (d->chip->irq_startup) {
// 如果提供了, 调用它来执行一次性的硬件启动/初始化序列.
ret = d->chip->irq_startup(d);
// 成功后, 清除软件中的"禁用"和"屏蔽"标志.
irq_state_clr_disabled(desc);
irq_state_clr_masked(desc);
} else {
// 如果没有提供, 则回退到调用通用的 irq_enable 函数.
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;

// 重置中断禁用深度计数器. 这会强制性地清除所有软件层面的 disable_irq() 调用效果.
desc->depth = 0;

// 检查"已启动"标志.
if (irqd_is_started(d)) {
// 如果已经启动过了, 只需要简单地重新使能它.
irq_enable(desc);
} else {
// 如果是第一次启动:
// 调用 __irq_startup_managed, 这是一个用于高级电源管理的钩子.
// 在此代码片段中, 它总是返回 IRQ_STARTUP_NORMAL.
switch (__irq_startup_managed(desc, aff, force)) {
case IRQ_STARTUP_NORMAL:
// 检查 irq_chip 是否要求在硬件启动前设置CPU亲和性.
if (d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP)
irq_setup_affinity(desc);
// 调用核心启动函数.
ret = __irq_startup(desc);
// 如果 irq_chip 要求在硬件启动后设置CPU亲和性.
if (!(d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP))
irq_setup_affinity(desc);
break;
// ... 其他 case 用于处理高级电源管理 ...
}
}
// 如果调用者请求, 则在使能后检查是否有待处理的中断信号.
if (resend)
check_irq_resend(desc, false);

return ret;
}