[toc]

在这里插入图片描述

kernel/irq/manage.c 中断请求管理(Interrupt Request Management) 内核中断线路的生命周期控制

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/irq/manage.c 内的代码是为了解决操作系统中一个基础且关键的硬件资源管理问题:如何对数量有限的硬件中断线(IRQ Lines)进行仲裁、抽象和生命周期管理

在没有一个集中化管理机制的情况下,会面临诸多问题:

  • 资源冲突:两个不同的设备驱动程序可能尝试使用同一个硬件中断号,导致中断信号无法被正确地分发,引发不可预测的系统行为。
  • 缺乏抽象:驱动程序需要编写与具体中断控制器(如x86的APIC,ARM的GIC)高度耦合的代码来使能(enable)、禁用(disable)、屏蔽(mask)中断。这使得驱动代码变得不可移植。
  • 动态性差:在不支持可加载模块和热插拔设备的早期系统中,中断的分配是静态的。但在现代系统中,当中断的属主(驱动模块)被加载和卸载时,系统必须有能力动态地分配和回收中断资源。

manage.c提供了一个中央管理器,它强制所有驱动程序必须通过一套标准的API(request_irq, free_irq, enable_irq, disable_irq等)来与中断子系统交互,从而解决了上述所有问题。

它的发展经历了哪些重要的里程碑或版本迭代?

Linux中断管理子系统的演进是其走向平台无关和高性能的关键一步。

  • irq_desc 结构体的引入:这是一个决定性的里程碑。内核不再将中断看作一个简单的数字,而是为每一个中断线(IRQ number)创建了一个struct irq_desc(中断描述符)。这个结构体成为描述一个中断所有属性(状态、锁、中断控制器、处理函数链表等)的中央对象。
  • 通用IRQ子系统 (generic_irq):为了将驱动与具体的中断控制器硬件解耦,内核开发了通用IRQ子系统。manage.c是其上层,而下层则通过struct irq_chip回调函数集来抽象中断控制器的具体硬件操作(如mask, unmask, ack, eoi)。这使得驱动代码可以完全平台无关。
  • 中断域(IRQ Domains):在复杂的SoC中,可能有多个级联的中断控制器,硬件中断号可能在局部是唯一的,但在全局不是。IRQ Domain提供了一个强大的映射机制,能将任意硬件中断号映射到一个全局唯一的、线性的Linux IRQ号空间中。
  • 线程化中断 (Threaded IRQs):为了进一步降低硬中断处理程序(上半部)的延迟,manage.c中加入了request_threaded_irq()接口。它允许将中断处理分为一个极快的上半部和一个运行在专用内核线程中的下半部,使得耗时的工作可以在一个可睡眠的、可抢占的上下文中完成,极大地提升了系统的实时响应能力。

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

kernel/irq/manage.c是内核中最核心、最稳定的组件之一。它的代码不会频繁地进行大规模重构,但会持续进行性能优化(如减少irq_desc的锁竞争)和功能增强以支持新的硬件特性(如MSI中断的虚拟化)。
它是每一个需要处理硬件中断的设备驱动程序都必须使用的基础框架,是整个Linux驱动生态的基石。

核心原理与设计

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

manage.c的核心是围绕**struct irq_desc(中断描述符)**进行的一系列生命周期管理操作。

  1. 中断描述符 (irq_desc):内核中有一个全局的irq_desc数组或基数树,为系统中每一个可能的Linux IRQ号都准备了一个描述符。这个描述符是管理该中断线路的“控制中心”。

  2. 申请中断 (request_irq / request_threaded_irq)

    • 一个设备驱动调用此API来声明它要使用某个IRQ号。
    • manage.c中的代码会找到对应的irq_desc
    • 它会分配一个struct irq_action结构体,用于保存驱动提供的中断处理函数(handler)、中断标志、驱动名和私有数据。
    • 这个irq_action会被添加到irq_desc的一个链表中。一个链表可以有多个irq_action,这正是实现**共享中断(Shared Interrupts)**的基础。
    • 最后,它会调用底层irq_chip的回调函数(如irq_startupirq_enable)来在硬件层面真正地使能这个中断。
  3. 禁用/使能中断 (disable_irq / enable_irq)

    • 这些函数通常用于驱动需要在一段代码中临时屏蔽中断的场景。
    • 它们会操作irq_desc中的一个深度计数器(depth),以支持嵌套调用。
    • 当计数器从0变为1时,disable_irq会调用底层irq_chip->irq_mask来屏蔽硬件中断。当计数器从1变回0时,enable_irq会调用irq_chip->irq_unmask来取消屏蔽。
  4. 释放中断 (free_irq)

    • 当驱动模块被卸载时,必须调用此函数。
    • 它会在irq_desc的链表中找到并移除属于该驱动的irq_action
    • 如果这是该中断线上的最后一个irq_actionmanage.c会调用irq_chip->irq_shutdown来彻底禁用该硬件中断,并清理相关状态。

它的主要优势体现在哪些方面?

  • 安全性与稳定性:通过集中的仲裁机制,从根本上防止了驱动间的IRQ资源冲突。
  • 抽象与可移植性:驱动程序面向标准的API编程,无需关心底层中断控制器的型号和实现细节。
  • 灵活性与功能性:原生支持中断共享、线程化中断、动态开关等高级功能,简化了复杂驱动的编写。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 合作式模型manage.c的正确运行依赖于所有驱动都“遵守规则”。一个有缺陷的驱动(例如,在中断处理函数中长时间占用CPU,或忘记调用free_irq)仍然会影响整个系统的稳定性。
  • 抽象的开销:通用IRQ层的存在,相较于一个为特定硬件写死的、高度优化的中断处理路径,会带来微小的性能开销。但在现代复杂的系统中,这种为可移植性和稳定性付出的代价是完全值得的。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

它是Linux内核中管理和注册硬件中断处理函数的唯一且标准的解决方案。

  • 任何物理设备驱动
    • 网卡驱动:在其.probe函数中,会从PCI配置空间读取分配到的IRQ号,然后调用request_threaded_irq()来注册一个能快速处理硬件状态的上半部和一个能处理网络包的下半部线程。
    • 键盘/鼠标驱动:会申请相应的IRQ,中断处理函数被触发时,它会读取按键或位移数据,并将其上报给输入子系统。
    • 磁盘控制器驱动:当一次DMA传输完成后,磁盘控制器会产生中断,驱动的中断处理函数会被调用,以通知上层I/O操作已完成。
  • GPIO中断:当一个GPIO引脚被配置为中断模式时,GPIO驱动框架内部会使用request_irq来将这个GPIO中断与一个处理函数关联起来。

是否有不推荐使用该技术的场景?为什么?

该技术高度特化,只用于物理硬件中断

  • 不用于软件触发的事件:对于由软件内部逻辑触发的、需要延迟执行的任务,应该使用softirq, taskletworkqueue,而不是request_irq。这些是纯软件的异步执行机制。
  • 轮询式驱动:对于一些没有中断能力的简单硬件,或者在某些特殊场景下(如启动早期),驱动只能通过循环**轮询(Polling)**设备的状态寄存器来判断事件是否发生。这种方式完全绕过了中断管理子系统。

对比分析

请将其 与 其他内核异步事件处理机制进行详细对比。

manage.c所管理的硬中断与内核的软中断进行对比,可以清晰地看出它们在内核事件处理层次结构中的不同角色。

| 特性 | IRQ管理 (Hard IRQ / Top-Half) | 软中断 (Softirq / Bottom-Half) |
| :— | :— | :— | :— |
| 事件来源 | 外部硬件。由物理设备产生的电信号触发。 | 内部软件。通常由硬中断处理程序在内部通过raise_softirq()调用来触发。 |
| 执行上下文 | 硬中断上下文。这是一个非常受限的环境,会屏蔽当前CPU上的至少同级中断。 | 软中断上下文。在这个环境中,硬件中断是打开的,但它仍然是原子上下文(不可睡眠)。 |
| 响应优先级 | 最高。CPU必须立即响应硬件中断。 | 。但低于硬中断,在硬中断返回后等时机执行。 |
| 注册/管理API | 动态。通过request_irq()在运行时注册和注销。由manage.c管理。 | 静态。软中断的类型在编译时就已固定,通过一个静态数组注册处理函数。 |
| 并发模型 | 可通过IRQF_SHARED标志允许多个处理函数共享同一中断线。 | 可并发。同一种类型的软中断(如NET_RX_SOFTIRQ)可以在多个CPU上同时并行执行。 |
| 核心目的 | 对硬件进行最快速的响应和交互(如ACK中断、从FIFO读数据),并将耗时工作推迟。 | 执行由硬中断推迟的耗时工作。它是硬中断的“下半部”,是处理任务的主体。 |
| 关系 | 生产者-消费者关系。硬中断处理程序是软中断任务的生产者。 | 软中断处理程序是硬中断任务的消费者。 |

irq_setup_forced_threading: 强制将中断处理线程化

此函数是Linux内核实时补丁集(PREEMPT_RT)中的一项关键机制, 其核心作用是根据一个全局的内核配置(通常是threadirqs内核启动参数), 强行将一个传统的、在硬中断上下文(hardirq context)中运行的中断处理程序(handler), 转换为在专门的内核线程(kthread)中运行

这个机制的根本原理和目标是为了提高系统的实时性(real-time performance)。在标准的Linux内核中, 中断处理程序在运行时会禁用中断, 这会增加系统中其他中断的延迟。对于一个需要确定性、低延迟响应的实时系统, 任何长时间运行在硬中断上下文的代码都是不可接受的。irq_setup_forced_threading就是一把”大锤”, 它将那些可能编写得不够”实时友好”的驱动程序中断处理, 强制迁移到可抢占、可调度的内核线程中, 从而将禁用中断的时间缩减到极致。

此函数通过修改irqaction结构体来巧妙地实现这一转换:

  1. 检查与豁免: 函数首先会进行一系列检查, 以确定是否应该进行强制转换:

    • !force_irqthreads(): 检查全局的threadirqs开关是否打开。如果没打开, 函数直接返回, 不做任何事。
    • IRQF_NO_THREAD, IRQF_PERCPU: 驱动程序可以通过这些标志明确表示其中断处理不应被线程化(例如, 高频率的定时器中断或每CPU中断), 函数会尊重这些请求。
    • handler == irq_default_primary_handler: 如果驱动程序已经将handler设置为默认的占位符, 说明它本来就是一个纯线程化的中断, 无需强制转换。
  2. 安全保障 (IRQF_ONESHOT): 这是最关键的安全步骤。函数会给irqaction强制添加IRQF_ONESHOT标志。这个标志告诉内核中断核心: 在硬中断处理程序返回后, 必须保持硬件中断线被屏蔽(masked), 直到对应的中断线程执行完毕才能重新使能。这可以完美地防止中断风暴, 尤其是对于电平触发的中断, 如果不这样做, 在线程被调度运行之前, 中断会立即再次触发。

  3. 处理程序“搬家”:

    • 简单情况 (只有handler): 这是最常见的转换。
      • new->thread_fn = new->handler;: 将驱动程序原本的硬中断处理函数(handler)的指针, “搬到”线程处理函数(thread_fn)的槽位里。
      • new->handler = irq_default_primary_handler;: 将原来的硬中断处理函数槽位用一个内核预设的、极简的irq_default_primary_handler来填充。这个默认处理程序的唯一工作就是返回IRQ_WAKE_THREAD, 以唤醒中断线程。
    • 复杂情况 (同时有handlerthread_fn): 如果驱动本身就是一个”分离式”处理模型。函数会更进一步:
      • 它会分配一个次级的(secondary) irqaction结构。
      • 将驱动原本的thread_fn“搬到”这个次级irqactionthread_fn槽位里。
      • 然后, 按照简单情况的逻辑, 将驱动原本的handler“搬到”主irqactionthread_fn槽位里。
      • 最终, 原始的一个中断请求被转换成了两个串联的纯线程化中断请求。
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
static int irq_setup_forced_threading(struct irqaction *new)
{
// 检查全局的 "threadirqs" 内核参数是否设置. 如果没有, 则不进行任何强制操作.
if (!force_irqthreads())
return 0;
// 如果驱动明确请求了 IRQF_NO_THREAD(不线程化), IRQF_PERCPU(每CPU中断),
// 或已经是 IRQF_ONESHOT, 则尊重驱动的设置, 不进行强制转换.
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
return 0;

/*
* 如果 handler 已经是 irq_default_primary_handler,
* 说明这个中断请求原本就是一个纯线程化的中断, 无需再强制.
*/
if (new->handler == irq_default_primary_handler)
return 0;

// *** 关键安全措施 ***: 强制设置 IRQF_ONESHOT 标志.
// 这确保了在线程处理函数完成前, 该中断线在硬件层面保持被屏蔽,
// 以防止中断风暴, 特别是对于电平触发的中断.
new->flags |= IRQF_ONESHOT;

/*
* 处理复杂情况: 驱动已经提供了一个主处理函数(handler)和一个线程处理函数(thread_fn).
* 我们通过创建一个次级 action 来强制将两者都线程化.
*/
if (new->handler && new->thread_fn) {
/* 分配一个次级的 irqaction 结构体 */
new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!new->secondary)
return -ENOMEM;
// 将次级 action 的主处理函数设置为一个占位符
new->secondary->handler = irq_forced_secondary_handler;
// 将驱动原本的 thread_fn "搬到" 次级 action 的 thread_fn
new->secondary->thread_fn = new->thread_fn;
// 复制其他必要信息
new->secondary->dev_id = new->dev_id;
new->secondary->irq = new->irq;
new->secondary->name = new->name;
}
// --- 执行核心的“搬家”操作 ---
// 在 action 的线程标志中设置一个位, 标记它是一个被强制线程化的中断.
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
// 将驱动原本的主处理函数 handler "搬到" 线程处理函数 thread_fn 的位置.
new->thread_fn = new->handler;
// 将主处理函数 handler 替换为内核的默认占位符.
// 这个默认函数唯一的职责就是返回 IRQ_WAKE_THREAD 来唤醒线程.
new->handler = irq_default_primary_handler;
return 0;
}

__irq_set_trigger: 设置中断线的硬件触发模式

这是Linux内核中断子系统中一个至关重要的底层函数, 负责将一个抽象的中断触发类型请求(如边沿触发、电平触发)转换为对具体中断控制器(irq_chip)硬件寄存器的编程操作。当request_irq被调用时, 或者当驱动程序需要动态改变中断触发方式时, 最终都会通过这个函数来与硬件交互。

此函数的核心原理是充当通用中断核心与**特定硬件驱动(irq_chip)**之间的桥梁, 并实施一套关键的安全规程:

  1. 委托给irq_chip: 函数首先会检查与该中断线关联的irq_chip驱动是否实现了irq_set_type这个回调函数。如果没有实现, 意味着该硬件不支持动态配置触发模式, 函数会直接返回。如果实现了, 实际的硬件编程工作将完全委托给这个函数来完成。

  2. “先屏蔽, 再配置, 后恢复”的安全序列: 这是此函数最重要的安全机制。某些中断控制器硬件(如STM32上的EXTI)要求在修改其触发模式配置时, 对应的中断线必须先被屏蔽(masked/disabled)。如果在中断使能的状态下修改配置, 可能会导致不确定的行为或产生伪中断(spurious IRQ)。

    • 函数会检查irq_chip的标志位, 看它是否声明了IRQCHIP_SET_TYPE_MASKED
    • 如果声明了, 函数会在调用irq_set_type之前, 先调用mask_irq在硬件上屏蔽该中断。
    • irq_set_type调用完成之后, 如果之前屏蔽了中断, 它会再调用unmask_irq来恢复中断线之前的使能状态。
    • 这个”屏蔽-配置-恢复”的序列确保了硬件配置更改的原子性和安全性。
  3. 内核状态同步: 在成功配置硬件后, 函数会更新内核内部维护的、与该中断线相关的多个状态描述符(irq_datairq_settings), 确保内核的软件状态与硬件的实际状态保持严格一致。特别是, 它会明确地设置或清除IRQD_LEVEL标志, 这个标志对于内核后续应该使用哪种中断流处理程序(flow handler)至关重要。

在STM32H750上, 这个函数是配置GPIO外部中断(EXTI)触发方式(上升沿、下降沿、双边沿)的核心。当驱动请求一个GPIO中断时, __irq_set_trigger会被调用, 它的irq_chip就是STM32的EXTI控制器驱动, chip->irq_set_type最终会去修改EXTI控制器的RTSR(上升沿触发选择寄存器)和FTSR(下降沿触发选择寄存器)等硬件寄存器。

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
/*
* __irq_set_trigger - 设置一个中断描述符(desc)的触发模式
* @desc: 要配置的中断线的描述符
* @flags: 包含了所需触发模式的标志位 (例如 IRQ_TYPE_EDGE_RISING)
*/
int __irq_set_trigger(struct irq_desc *desc, unsigned long flags)
{
// 获取与此中断线关联的 irq_chip (中断控制器驱动) 的操作函数表
struct irq_chip *chip = desc->irq_data.chip;
int ret, unmask = 0; // ret 用于保存返回值, unmask 用于标记是否需要在最后解除屏蔽

// 检查 irq_chip 是否存在, 以及它是否实现了 irq_set_type 回调函数
if (!chip || !chip->irq_set_type) {
/*
* 请求了 IRQF_TRIGGER_* 但中断控制器不支持多种触发类型?
*/
// 打印一条调试信息, 然后直接返回成功. 意味着无法配置, 但不认为是一个错误.
pr_debug("No set_type function for IRQ %d (%s)\n",
irq_desc_get_irq(desc),
chip ? (chip->name ? : "unknown") : "unknown");
return 0;
}

// *** 关键的安全检查 ***
// 检查 irq_chip 是否要求在设置类型前必须先屏蔽中断
if (chip->flags & IRQCHIP_SET_TYPE_MASKED) {
// 如果中断当前未被屏蔽, 则调用 mask_irq() 在硬件上屏蔽它
if (!irqd_irq_masked(&desc->irq_data))
mask_irq(desc);
// 如果中断在调用此函数前不是被完全禁用的状态, 则设置 unmask 标志,
// 以便在函数结束时恢复其中断使能状态.
if (!irqd_irq_disabled(&desc->irq_data))
unmask = 1;
}

// 清理 flags, 只保留与触发模式相关的位 (例如: 上升沿/下降沿/高电平/低电平)
flags &= IRQ_TYPE_SENSE_MASK;
// *** 核心调用 ***: 调用特定硬件驱动的回调函数来实际配置硬件寄存器
ret = chip->irq_set_type(&desc->irq_data, flags);

// 根据 irq_chip 回调函数的返回值进行处理
switch (ret) {
case IRQ_SET_MASK_OK:
case IRQ_SET_MASK_OK_DONE:
// 硬件配置成功. 现在更新内核的软件状态以保持同步.
// 清除 irq_data 中旧的触发模式标志
irqd_clear(&desc->irq_data, IRQD_TRIGGER_MASK);
// 设置 irq_data 中新的触发模式标志
irqd_set(&desc->irq_data, flags);
fallthrough; // 顺势执行下一个 case 的代码

case IRQ_SET_MASK_OK_NOCOPY:
// 同样是成功的情况.
// 从 irq_data 中重新获取最终确定的触发类型 (可能与请求的略有不同)
flags = irqd_get_trigger_type(&desc->irq_data);
// 在中断设置(settings)中也更新触发类型掩码
irq_settings_set_trigger_mask(desc, flags);
// 清除旧的电平触发状态
irqd_clear(&desc->irq_data, IRQD_LEVEL);
irq_settings_clr_level(desc);
// 如果新的触发模式是电平触发
if (flags & IRQ_TYPE_LEVEL_MASK) {
// 则设置相应的电平标志. 这个标志对中断流控至关重要.
irq_settings_set_level(desc);
irqd_set(&desc->irq_data, IRQD_LEVEL);
}

ret = 0; // 将返回值标准化为 0 (成功)
break;
default:
// irq_set_type 返回了一个错误码.
pr_err("Setting trigger mode %lu for irq %u failed (%pS)\n",
flags, irq_desc_get_irq(desc), chip->irq_set_type);
}
// 如果在函数开始时屏蔽了中断, 现在就解除屏蔽
if (unmask)
unmask_irq(desc);

return ret; // 返回最终的操作结果
}

__setup_irq: 中断处理程序安装的核心引擎

这是Linux内核中断子系统中的一个底层核心函数, 是request_threaded_irq等上层API的最终执行者。它的核心原理是以一种高度同步化和原子化的方式, 将一个代表中断处理请求的irqaction结构体, 安全地安装到指定中断线(irq_desc)的动作链表上, 并根据请求的标志和硬件的能力, 对中断控制器(irqchip)进行必要的配置

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;

if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
}

if (IS_ERR(t))
return PTR_ERR(t);

/*
* We keep the reference to the task struct even if
* the thread dies to avoid that the interrupt code
* references an already freed task_struct.
*/
new->thread = get_task_struct(t);
/*
* Tell the thread to set its affinity. This is
* important for shared interrupt handlers as we do
* not invoke setup_affinity() for the secondary
* handlers as everything is already set up. Even for
* interrupts marked with IRQF_NO_BALANCE this is
* correct as we want the thread to move to the cpu(s)
* on which the requesting code placed the interrupt.
*/
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}

/*
* Primary handler for nested threaded interrupts. Should never be
* called.
*/
static irqreturn_t irq_nested_primary_handler(int irq, void *dev_id)
{
WARN(1, "Primary handler called for nested irq %d\n", irq);
return IRQ_NONE;
}

static int irq_request_resources(struct irq_desc *desc)
{
struct irq_data *d = &desc->irq_data;
struct irq_chip *c = d->chip;

return c->irq_request_resources ? c->irq_request_resources(d) : 0;
}

/* __setup_irq: 安装一个 irqaction 的内部核心函数 */
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;

if (!desc)
return -EINVAL;

// 如果中断线没有关联到有效的 irq_chip, 则无法处理
if (desc->irq_data.chip == &no_irq_chip)
return -ENOSYS;
// 尝试增加 irqchip 驱动模块的引用计数, 防止其在使用中被卸载
if (!try_module_get(desc->owner))
return -ENODEV;

new->irq = irq;

// 如果调用者未指定触发类型, 则使用该中断的默认类型
if (!(new->flags & IRQF_TRIGGER_MASK))
new->flags |= irqd_get_trigger_type(&desc->irq_data);

// 检查是否为嵌套中断, 并进行相应处理
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) { // 嵌套中断必须有线程处理函数
ret = -EINVAL;
goto out_mput;
}
// 将主处理函数替换为警告函数, 因为嵌套中断不应执行主处理
new->handler = irq_nested_primary_handler;
} else {
// 检查是否可以强制线程化, 以提高实时性
if (irq_settings_can_thread(desc)) {
ret = irq_setup_forced_threading(new);
if (ret)
goto out_mput;
}
}

// 如果提供了线程处理函数且不是嵌套中断, 则创建内核线程
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}

// 如果 irqchip 硬件本身是 oneshot 安全的, 则移除软件 ONESHOT 标志以优化
if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
new->flags &= ~IRQF_ONESHOT;

// 1. 获取最外层锁: request_mutex, 串行化 request_irq/free_irq
mutex_lock(&desc->request_mutex);

// 2. 获取总线锁, 用于保护慢速总线上的 irqchip 操作
chip_bus_lock(desc);

// 如果这是该中断线上的第一个 action, 需要请求硬件资源
if (!desc->action) {
ret = irq_request_resources(desc);
if (ret) {
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
new->name, irq, desc->irq_data.chip->name);
goto out_bus_unlock;
}
}

// 3. 获取最内层锁: desc->lock, 禁用本地中断, 保护 action 链表
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
if (old) { // --- 处理共享中断 ---
unsigned int oldtype;

// NMI (不可屏蔽中断) 不能被共享
if (irq_is_nmi(desc)) {
ret = -EINVAL;
goto out_unlock;
}

// 如果之前未设置触发类型, 则继承第一个请求者的类型
if (irqd_trigger_type_was_set(&desc->irq_data)) {
oldtype = irqd_get_trigger_type(&desc->irq_data);
} else {
oldtype = new->flags & IRQF_TRIGGER_MASK;
irqd_set_trigger_type(&desc->irq_data, oldtype);
}

// 检查共享兼容性: 必须都声明共享, 且触发类型一致
if (!((old->flags & new->flags) & IRQF_SHARED) ||
(oldtype != (new->flags & IRQF_TRIGGER_MASK)))
goto mismatch;

// 检查 ONESHOT 标志兼容性
if ((old->flags & IRQF_ONESHOT) &&
(new->flags & IRQF_COND_ONESHOT))
new->flags |= IRQF_ONESHOT;
else if ((old->flags ^ new->flags) & IRQF_ONESHOT)
goto mismatch;

// 检查 PERCPU 标志兼容性
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;

// 遍历到链表末尾, 并计算所有已存在 action 的 thread_mask
do {
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}

// 为 ONESHOT 中断设置 thread_mask
if (new->flags & IRQF_ONESHOT) {
if (thread_mask == ~0UL) { // 位掩码已用完
ret = -EBUSY;
goto out_unlock;
}
// 分配第一个可用的比特位给新的 action
new->thread_mask = 1UL << ffz(thread_mask);

} else if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
// 危险组合: handler=NULL, 且 !ONESHOT. 对于电平中断可能导致中断风暴. 拒绝.
ret = -EINVAL;
goto out_unlock;
}

if (!shared) { // --- 处理第一个(非共享)中断 ---
// 设置硬件触发类型
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc,
new->flags & IRQF_TRIGGER_MASK);

if (ret)
goto out_unlock;
}

// 激活中断(关联资源)
ret = irq_activate(desc);
if (ret)
goto out_unlock;

// 清理/设置各种状态位
desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
IRQS_ONESHOT | IRQS_WAITING);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

if (new->flags & IRQF_ONESHOT)
desc->istate |= IRQS_ONESHOT;

// 如果不要求手动使能(NO_AUTOEN), 则启动并使能中断
if (!(new->flags & IRQF_NO_AUTOEN) &&
irq_settings_can_autoenable(desc)) {
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
} else {
desc->depth = 1; // 否则, 标记为已禁用
}

} else if (new->flags & IRQF_TRIGGER_MASK) {
// 对于共享中断, 检查新请求的触发类型是否与已配置的匹配
//... (代码省略)
}

// *** 关键步骤: 将新的 action 链接到链表中 ***
*old_ptr = new;

// ... (清理和状态更新)

// 释放最内层锁
raw_spin_unlock_irqrestore(&desc->lock, flags);
// 释放总线锁
chip_bus_sync_unlock(desc);
// 释放最外层锁
mutex_unlock(&desc->request_mutex);

// ... (注册 procfs 条目等收尾工作)

return 0;

mismatch: // 标志不匹配的错误处理
ret = -EBUSY;

out_unlock: // 清理路径1: 释放内层锁
raw_spin_unlock_irqrestore(&desc->lock, flags);
if (!desc->action)
irq_release_resources(desc);
out_bus_unlock: // 清理路径2: 释放总线锁
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);

out_thread: // 清理路径3: 停止已创建的线程
//... (代码省略)
out_mput: // 清理路径4: 释放模块引用
module_put(desc->owner);
return ret;
}

request_threaded_irq: 注册中断处理程序 (核心函数)

这是Linux内核中用于为一个驱动程序申请和注册中断处理程序的根本性函数。当一个硬件设备(如STM32上的DMA、UART或GPIO)需要通过中断信号通知CPU有事件发生时, 其驱动程序必须调用此函数来建立硬件中断信号与特定软件处理函数之间的连接。

此函数的核心原理是实现了现代Linux内核的中断处理”两阶段”或”分离式处理”模型, 将中断处理分为两个部分:

  1. 硬中断处理程序 (handler): 这是”上半部”(Top Half)。当硬件中断发生时, CPU会立即跳转到这里执行。此函数的运行环境非常受限:

    • 它在原子上下文中运行, 意味着它不能睡眠(不能调用任何可能导致进程调度的函数, 如kmallocmutex_lock)。
    • 在单核系统(如STM32H750)上, 它运行时本地中断是关闭的, 以防止被其他中断嵌套。
    • 它的职责必须是最小化和快速的: 检查中断是否真的由其设备产生(在共享中断的情况下), 读取/清除中断状态寄存器, 禁用设备的中断源以防中断风暴, 如果有”下半部”, 则唤醒它。
    • 如果它返回IRQ_WAKE_THREAD, 内核就会唤醒对应的”下半部”线程。
  2. 线程化中断处理程序 (thread_fn): 这是”下半部”(Bottom Half)。它在一个普通的内核线程上下文中运行。这意味着:

    • 它可以被抢占, 也可以睡眠。
    • 它可以调用所有标准的内核服务, 如内存分配、加锁、与用户空间交互等。
    • 它负责执行所有耗时较长的中断处理工作, 如数据拷贝、协议处理、唤醒等待队列等。

此函数的工作流程是:

  1. 参数验证: 首先进行一系列严格的健全性检查。最重要的是, 如果中断被声明为共享的 (IRQF_SHARED), 那么dev_id参数必须是一个唯一的非NULL值。这是因为当多个设备共享同一条IRQ线时, 内核需要dev_id来区分应该释放哪一个具体的中断处理程序。
  2. 分配irqaction: 它分配一个irqaction结构体。这个结构体就像一个”中断注册表单”, 包含了驱动程序提供的所有信息: 两个处理函数指针、标志位、名称和dev_id
  3. 安装irqaction: 它调用内部函数__setup_irq, 将这个irqaction结构体添加到内核为该IRQ号维护的irq_desc(中断描述符)的动作链表中。对于非共享中断, 这个链表只有一个节点; 对于共享中断, 则有多个。
  4. 使能中断: __setup_irq最终会通过irqchip(中断控制器驱动)回调, 在硬件层面(如NVIC)解除对该中断的屏蔽(unmask), 使其能够被CPU响应。
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
/*
* 线程中断的默认主中断处理程序。是
* 在调用 request_threaded_irq 时被分配为主处理程序
* 与处理程序 == NULL 一起使用。对于一次性中断很有用。
*/
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}

/**
* request_threaded_irq - 分配一条中断线
* @irq: 要分配的中断线编号
* @handler: 当IRQ发生时调用的函数。对于线程化中断, 这是主处理程序。
* 如果 handler 为 NULL 且 thread_fn 不为 NULL, 则安装默认的主处理程序。
* @thread_fn: 从irq处理线程中调用的函数。如果为NULL, 则不创建irq线程。
* @irqflags: 中断类型标志
* @devname: 声明此中断的设备的ascii名称
* @dev_id: 一个传递回处理函数的 cookie
* ... (其余注释为详细说明)
*/
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action; // 中断动作结构体, 包含了中断处理的所有信息
struct irq_desc *desc; // 内核中代表一条中断线的描述符
int retval; // 返回值

// 检查是否为无效的IRQ号
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;

/*
* 健全性检查:
* 1. 共享中断(IRQF_SHARED)必须提供一个唯一的 dev_id, 否则以后无法正确释放.
* 2. 共享中断不能和 IRQF_NO_AUTOEN (不自动使能) 一起使用.
* 3. IRQF_COND_SUSPEND (条件性挂起) 只对共享中断有意义.
* 4. IRQF_NO_SUSPEND (不挂起) 和 IRQF_COND_SUSPEND 互斥.
*/
if (((irqflags & IRQF_SHARED) && !dev_id) ||
((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL; // 返回无效参数错误

// 将中断号转换为内核内部的中断描述符
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;

// 检查此中断是否允许被请求, 以及一些配置警告
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;

// 如果调用者只提供了线程处理函数 thread_fn 而没有提供主处理函数 handler
if (!handler) {
if (!thread_fn)
return -EINVAL; // 两者都为NULL是无效的
// 安装一个默认的主处理函数. 这个函数通常只做最少的工作, 然后返回 IRQ_WAKE_THREAD
handler = irq_default_primary_handler;
}

// 为 irqaction 结构体分配内存
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;

// 填充 action 结构体, 将所有传入的参数保存起来
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

// 获取中断控制器的电源管理引用 (运行时PM相关)
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}

// 调用 __setup_irq, 这是实际安装和使能中断的核心内部函数.
// 它会将 action 添加到 desc 的动作链表中, 并通过 irqchip 回调来操作硬件.
retval = __setup_irq(irq, desc, action);

// 如果 __setup_irq 失败
if (retval) {
// 释放之前获取的PM引用和分配的内存, 进行清理
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}

#ifdef CONFIG_DEBUG_SHIRQ_FIXME
// 这是一个调试特性: 如果是共享中断, 注册成功后立即触发一次伪中断.
// 目的是为了检查驱动的 handler 是否能正确处理并非由自己设备触发的中断.
if (!retval && (irqflags & IRQF_SHARED)) {
unsigned long flags;

disable_irq(irq); // 暂时禁用真正的硬件中断
local_irq_save(flags); // 保存当前中断状态

handler(irq, dev_id); // 直接调用 handler, 模拟一次中断

local_irq_restore(flags); // 恢复中断状态
enable_irq(irq); // 重新使能硬件中断
}
#endif
return retval; // 返回操作结果
}
// 将函数导出, 使其对其他内核模块可用
EXPORT_SYMBOL(request_threaded_irq);

disable_irq & enable_irq: 中断线的使能、禁用与同步

本代码片段展示了Linux内核中断子系统中最核心、最常用的API——disable_irqenable_irq系列函数。其核心功能是为驱动程序提供一个可嵌套的、带同步机制的方法来禁用和重新启用一个中断线(IRQ)。disable_irq_nosync提供了基础的、异步的禁用功能,而disable_irq则在其之上增加了一个关键的同步等待步骤,确保在函数返回时,该中断的处理程序已经完全执行完毕。

实现原理分析

此机制的核心原理是引用计数(嵌套)同步原语的结合,以在保证功能正确性的同时,处理复杂的并发场景。

  1. 嵌套与引用计数 (irq_desc->depth):

    • 问题: 内核中可能有多处代码因为不同的原因需要临时禁用同一个中断。如果简单地用一个布尔标志来开关中断,那么第一个调用enable_irq的代码就会意外地为其他仍然需要禁用状态的代码重新打开中断。
    • 解决方案: 每个中断描述符struct irq_desc中都有一个depth成员,它充当一个引用计数器
      • __disable_irq: 每次调用disable_irq(或其变体),depth都会递增。但只有在depth从0变为1时,才会真正调用底层的irq_disable(desc)来操作硬件中断控制器,屏蔽该中断。后续的调用只会增加计数。
      • __enable_irq: 每次调用enable_irqdepth都会递减。但只有在depth从1变为0时,才会真正调用irq_startup(desc, ...)来操作硬件,重新使能该中断。
    • 这个嵌套机制确保了enable_irqdisable_irq的调用必须是成对的。只有最后一层disableenable抵消后,中断才会被真正地重新开启。
  2. 同步 vs 异步 (disable_irq vs disable_irq_nosync):

    • disable_irq_nosync (异步): 这是最基础的禁用操作。它仅仅是递增depth并在需要时屏蔽硬件中断,然后立即返回。它不保证在它返回时,该中断的中断服务程序(ISR)没有正在其他CPU上运行,或者没有正在当前CPU上被更高优先级的中断抢占。
    • disable_irq (同步): 这是更常用、更安全的版本。它执行两个步骤:
      1. 调用__disable_irq_nosync(irq)来执行基础的禁用操作。
      2. 调用synchronize_irq(irq)这是关键的同步步骤synchronize_irq阻塞(可能睡眠),直到所有当前正在执行的、与irq相关的中断处理程序(包括主处理函数和任何中断线程)全部执行完毕
    • 死锁警告: 注释中明确警告,如果在持有某个锁的同时调用disable_irq,而该中断的处理程序也试图获取同一个锁,那么系统将死锁。这是因为disable_irq会等待ISR,而ISR在等待锁。

代码分析

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
/**
* @brief 递增中断禁用深度,并在第一次禁用时实际操作硬件。
* @param desc 中断描述符。
*/
void __disable_irq(struct irq_desc *desc)
{
/* 如果深度从0变为1,则调用硬件相关的禁用函数。*/
if (!desc->depth++)
irq_disable(desc);
}

/**
* @brief 异步禁用中断的核心实现。
* @param irq 中断号。
* @return int 成功返回0,失败返回-EINVAL。
*/
static int __disable_irq_nosync(unsigned int irq)
{
/* 安全地获取中断描述符并加锁。 */
scoped_irqdesc_get_and_lock(irq, IRQ_GET_DESC_CHECK_GLOBAL) {
__disable_irq(scoped_irqdesc);
return 0;
}
return -EINVAL;
}

/**
* @brief 异步地禁用一个中断,不等待处理程序完成。
* @param irq 要禁用的中断。
* @details 禁用和使能是可嵌套的。此函数可从中断上下文调用。
*/
void disable_irq_nosync(unsigned int irq)
{
__disable_irq_nosync(irq);
}
EXPORT_SYMBOL(disable_irq_nosync);

/**
* @brief 同步地禁用一个中断,并等待其处理程序完成。
* @param irq 要禁用的中断。
* @details 此函数会等待任何挂起的中断处理程序完成后才返回。
* 如果在一个中断线程上调用,可能会睡眠。
*/
void disable_irq(unsigned int irq)
{
might_sleep(); /* 标记此函数可能会睡眠。 */
/* 首先,异步地禁用中断。 */
if (!__disable_irq_nosync(irq))
/* 然后,同步等待所有正在运行的处理程序完成。 */
synchronize_irq(irq);
}
EXPORT_SYMBOL(disable_irq);

static inline void irq_settings_set_noprobe(struct irq_desc *desc)
{
desc->status_use_accessors |= _IRQ_NOPROBE;
}

/**
* @brief 递减中断禁用深度,并在最后一次使能时实际操作硬件。
* @param desc 中断描述符。
*/
void __enable_irq(struct irq_desc *desc)
{
switch (desc->depth) {
case 0: /* 错误:不配对的使能调用。 */
err_out:
WARN(1, KERN_WARNING "不配对的IRQ %d使能\n",
irq_desc_get_irq(desc));
break;
case 1: { /* 最后一次使能调用。 */
if (desc->istate & IRQS_SUSPENDED)
goto err_out; /* 如果中断在挂起状态,则出错。 */
/* Prevent probing on this irq: */
irq_settings_set_noprobe(desc);
/*
* 调用irq_startup()而不是irq_enable(),以处理首次使能
* 和从休眠中恢复的场景。
*/
irq_startup(desc, IRQ_RESEND, IRQ_START_FORCE);
break;
}
default: /* 只是递减深度计数。 */
desc->depth--;
}
}

/**
* @brief 使能一个中断的处理。
* @param irq 要使能的中断。
* @details 抵消一次disable_irq()的调用。如果这是最后一次匹配的
* 禁用,中断处理将被重新使能。
*/
void enable_irq(unsigned int irq)
{
/* 安全地获取中断描述符并加锁。 */
scoped_irqdesc_get_and_lock(irq, IRQ_GET_DESC_CHECK_GLOBAL) {
struct irq_desc *desc = scoped_irqdesc;

/* ... 警告检查 ... */
__enable_irq(desc);
}
}
EXPORT_SYMBOL(enable_irq);

request_irq & request_threaded_irq: 中断处理程序的注册

本代码片段展示了Linux内核中用于申请一个中断线并为其注册一个处理程序的最高级API——request_irq及其更通用的底层实现request_threaded_irq。这是所有设备驱动程序与中断子系统交互的标准入口点。其核心功能是接收驱动程序提供的中断处理函数(handler)和相关信息,然后执行一系列复杂的内部操作,包括分配资源、配置中断控制器、并将该处理函数挂载到内核的中断分发链路上。

实现原理分析

此机制的核心是**中断动作(irqaction的创建与中断描述符(irq_desc)**的配置。它支持普通中断、共享中断和线程化中断等多种模式。

  1. API分层:

    • request_irq: 这是一个简化的、向后兼容的内联函数。它只接收一个handler函数,并自动将thread_fn设置为NULL,然后调用request_threaded_irq。它隐式地添加了IRQF_COND_ONESHOT标志,这是一种对非线程化中断处理的优化。绝大多数简单的中断驱动都使用这个API。
    • request_threaded_irq: 这是功能更全、更底层的核心函数。它允许驱动程序同时提供一个硬中断上下文的处理函数(handler)和一个线程上下文的处理函数(thread_fn)。
  2. 线程化中断 (Threaded IRQs):

    • 问题: 某些中断处理需要执行可能睡眠的操作(如获取互斥锁、进行I/O),或者执行时间较长,长时间禁用中断会损害系统响应性。
    • 解决方案: 线程化中断将中断处理分为两部分:
      1. handler主处理程序):在硬中断上下文(hardirq context)中执行,不可睡眠,必须快速完成。它的主要职责是:识别中断来源、禁用设备的中断(防止中断风暴)、并返回IRQ_WAKE_THREAD
      2. thread_fn线程化处理程序):在一个专门为此中断创建的、高优先级的内核线程中执行。它可以睡眠,可以执行耗时操作。
    • 如果thread_fnNULL,则handler就是唯一且完整的中断处理程序。
  3. 核心注册流程 (request_threaded_irq):

    • 参数校验: 函数首先进行一系列健全性检查,例如,共享中断(IRQF_SHARED)必须提供一个唯一的dev_id,以便在释放时能够精确识别。
    • 资源分配 (kzalloc): 分配一个struct irqaction结构体。这个结构体是中断处理程序的内核抽象,它包含了handler, thread_fn, flags, name, dev_id等所有相关信息。
    • 填充irqaction: 将传入的参数填充到新分配的action结构中。
    • 核心设置 (__setup_irq): 调用__setup_irq(代码未显示)来执行实际的挂载操作。__setup_irq会:
      1. 获取irq_desc的锁。
      2. 如果中断是共享的,则将新的irqaction添加到irq_descaction链表的末尾。
      3. 如果中断是独占的,则将irqaction直接赋给irq_desc->action
      4. 配置中断的触发类型(上升沿、下降沿等),这通常会调用irq_chip.irq_set_type回调来操作硬件。
      5. 如果这是一个线程化中断,则创建一个名为irq/irq_num-devname的内核线程。
      6. 最后,调用irq_startup使能该中断(除非设置了IRQF_NO_AUTOEN)。

代码分析

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
/**
* @brief 注册一个中断处理程序(简化版API)。
* @param irq 要分配的中断线。
* @param handler 中断发生时调用的函数。
* @param flags 处理标志 (IRQF_*)。
* @param name 产生此中断的设备名称。
* @param dev 传递给处理函数的cookie(通常是设备指针)。
* @return int 成功返回0,失败返回错误码。
*/
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
/*
* 这是request_threaded_irq的一个简单封装。
* 它将thread_fn设置为NULL,表示这是一个普通的、非线程化的中断。
* 它隐式地添加了IRQF_COND_ONESHOT标志,用于优化。
*/
return request_threaded_irq(irq, handler, NULL, flags | IRQF_COND_ONESHOT, name, dev);
}

/*
* Default primary interrupt handler for threaded interrupts. Is
* assigned as primary handler when request_threaded_irq is called
* with handler == NULL. Useful for oneshot interrupts.
*/
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}

/**
* @brief 注册一个(可能)线程化的中断处理程序(核心实现)。
* @param irq 中断线。
* @param handler 硬中断上下文处理程序(主处理程序)。
* @param thread_fn 线程上下文处理程序(可为NULL)。
* @param irqflags 中断类型标志。
* @param devname 设备名称。
* @param dev_id 传递给处理程序的唯一cookie。
* @return int 成功返回0,失败返回错误码。
*/
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;

if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;

/*
* 一系列的健全性检查:
* - 共享中断必须提供dev_id。
* - 共享中断不能禁用自动使能。
* - 等等...
*/
if (((irqflags & IRQF_SHARED) && !dev_id) ||
/* ... 其他检查 ... */)
return -EINVAL;

/* 获取该IRQ号对应的中断描述符。 */
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;

/* ... 检查该中断是否允许被请求 ... */

/* 如果没有提供主处理程序,但提供了线程处理程序,则使用默认的主处理程序。 */
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}

/* 为中断动作分配内存。 */
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;

/* 填充irqaction结构。 */
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

/* ... 电源管理相关的引用计数获取 ... */

/*
* 调用核心的__setup_irq函数,该函数会处理链表挂载、
* 中断控制器配置、线程创建和中断使能。
*/
retval = __setup_irq(irq, desc, action);

if (retval) {
/* ... 错误回滚路径 ... */
kfree(action);
}

/* ... 用于调试共享中断的特殊代码块 ... */

return retval;
}
EXPORT_SYMBOL(request_threaded_irq);