[TOC]
include/linux/interrupt.h or_softirq_pending 软中断待处理状态 1 2 3 4 5 6 7 8 DEFINE_PER_CPU_ALIGNED(irq_cpustat_t , irq_stat); EXPORT_PER_CPU_SYMBOL(irq_stat); #define local_softirq_pending_ref irq_stat.__softirq_pending #define local_softirq_pending() (__this_cpu_read(local_softirq_pending_ref)) #define set_softirq_pending(x) (__this_cpu_write(local_softirq_pending_ref, (x))) #define or_softirq_pending(x) (__this_cpu_or(local_softirq_pending_ref, (x)))
raise_timer_softirq 定时器软中断 1 2 3 4 5 6 7 8 static inline void raise_timer_softirq (unsigned int nr) { lockdep_assert_in_irq(); if (force_irqthreads()) raise_ktimers_thread(nr); else __raise_softirq_irqoff(nr); }
include/linux/irq.h irq_alloc_domain_generic_chips 分配中断域 1 2 3 4 5 6 7 #define irq_alloc_domain_generic_chips(d, irqs_per_chip, num_ct, name, \ handler, clr, set, flags) \ ({ \ MAYBE_BUILD_BUG_ON(irqs_per_chip > 32); \ __irq_alloc_domain_generic_chips(d, irqs_per_chip, num_ct, name,\ handler, clr, set, flags); \ })
irq_set_chained_handler 为给定的 IRQ 设置高电平链式流处理程序 1 2 3 4 5 6 7 8 9 static inline void irq_set_chained_handler (unsigned int irq, irq_flow_handler_t handle) { __irq_set_handler(irq, handle, 1 , NULL ); }
kernel/irq/devres.c devm_request_irq
和 devm_request_threaded_irq
: 自动管理中断资源的注册这一组函数是Linux内核中断请求API的”设备资源管理”(devm
)版本。它们的核心原理是将中断的注册(request
)和释放(free
)操作与设备的生命周期进行绑定, 从而实现中断资源的自动管理 。当一个驱动程序使用这些函数来请求中断时, 它无需在退出或错误处理路径中显式地调用free_irq
, 内核的设备模型会在驱动卸载时自动完成这个清理工作。
这极大地简化了驱动程序的编写, 尤其是在有多个中断和复杂错误处理逻辑的probe
函数中, 能有效防止因忘记释放中断而导致的资源泄漏。
devm_request_irq
(内联辅助函数)这是一个简单的内联函数, 作为devm_request_threaded_irq
的便捷封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static inline int __must_checkdevm_request_irq (struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { return devm_request_threaded_irq(dev, irq, handler, NULL , irqflags, devname, dev_id); }
devres
相关的内部实现这三个部分是devm
框架的内部组件, 共同实现了中断资源的生命周期管理。
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 struct irq_devres { unsigned int irq; void *dev_id; }; static void devm_irq_release (struct device *dev, void *res) { struct irq_devres *this = res; free_irq(this->irq, this->dev_id); } static int devm_irq_match (struct device *dev, void *res, void *data) { struct irq_devres *this = res, *match = data; return this->irq == match->irq && this->dev_id == match->dev_id; }
devm_request_threaded_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 int devm_request_threaded_irq (struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) { struct irq_devres *dr ; int rc; dr = devres_alloc(devm_irq_release, sizeof (struct irq_devres), GFP_KERNEL); if (!dr) return -ENOMEM; if (!devname) devname = dev_name(dev); rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname, dev_id); if (rc) { devres_free(dr); return rc; } dr->irq = irq; dr->dev_id = dev_id; devres_add(dev, dr); return 0 ; } EXPORT_SYMBOL(devm_request_threaded_irq);
kernel/irq/handle.c set_handle_irq 设置中断处理函数 1 2 3 4 5 6 7 8 int __init set_handle_irq (void (*handle_irq)(struct pt_regs *)) { if (handle_arch_irq) return -EBUSY; handle_arch_irq = handle_irq; return 0 ; }
generic_handle_arch_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 DECLARE_PER_CPU(struct pt_regs *, __irq_regs); static inline struct pt_regs *set_irq_regs (struct pt_regs *new_regs) { struct pt_regs *old_regs ; old_regs = __this_cpu_read(__irq_regs); __this_cpu_write(__irq_regs, new_regs); return old_regs; } asmlinkage void noinstr generic_handle_arch_irq (struct pt_regs *regs) { struct pt_regs *old_regs ; irq_enter(); old_regs = set_irq_regs(regs); handle_arch_irq(regs); set_irq_regs(old_regs); irq_exit(); }
__irq_wake_thread 唤醒中断线程 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 void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action){ if (action->thread->flags & PF_EXITING) return ; if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)) return ; desc->threads_oneshot |= action->thread_mask; atomic_inc (&desc->threads_active); wake_up_process(action->thread); }
__handle_irq_event_percpu 遍历并执行一个中断的所有action 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 irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc){ irqreturn_t retval = IRQ_NONE; unsigned int irq = desc->irq_data.irq; struct irqaction *action ; record_irq_time(desc); for_each_action_of_desc(desc, action) { irqreturn_t res; if (irq_settings_can_thread(desc) && !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))) lockdep_hardirq_threaded(); trace_irq_handler_entry(irq, action); res = action->handler(irq, action->dev_id); trace_irq_handler_exit(irq, action, res); if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n" , irq, action->handler)) local_irq_disable(); switch (res) { case IRQ_WAKE_THREAD: if (unlikely(!action->thread_fn)) { warn_no_thread(irq, action); break ; } __irq_wake_thread(desc, action); break ; default : break ; } retval |= res; } return retval; }
handle_irq_event_percpu 处理中断事件 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 irqreturn_t handle_irq_event_percpu (struct irq_desc *desc) { irqreturn_t retval; retval = __handle_irq_event_percpu(desc); add_interrupt_randomness(desc->irq_data.irq); if (!irq_settings_no_debug(desc)) note_interrupt(desc, retval); return retval; }
handle_irq_event 处理中断事件 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 irqreturn_t handle_irq_event (struct irq_desc *desc) { irqreturn_t ret; desc->istate &= ~IRQS_PENDING; irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock(&desc->lock); ret = handle_irq_event_percpu(desc); raw_spin_lock(&desc->lock); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); return ret; }
kernel/irq/irqdesc.c generic_handle_domain_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 int handle_irq_desc (struct irq_desc *desc) { struct irq_data *data ; if (!desc) return -EINVAL; data = irq_desc_get_irq_data(desc); if (WARN_ON_ONCE(!in_hardirq() && irqd_is_handle_enforce_irqctx(data))) return -EPERM; generic_handle_irq_desc(desc); return 0 ; } int generic_handle_domain_irq (struct irq_domain *domain, unsigned int hwirq) { return handle_irq_desc(irq_resolve_mapping(domain, hwirq)); } EXPORT_SYMBOL_GPL(generic_handle_domain_irq);
alloc_desc 分配中断描述符 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 static void desc_set_defaults (unsigned int irq, struct irq_desc *desc, int node, const struct cpumask *affinity, struct module *owner) { int cpu; desc->irq_common_data.handler_data = NULL ; desc->irq_common_data.msi_desc = NULL ; desc->irq_data.common = &desc->irq_common_data; desc->irq_data.irq = irq; desc->irq_data.chip = &no_irq_chip; desc->irq_data.chip_data = NULL ; irq_settings_clr_and_set(desc, ~0 , _IRQ_DEFAULT_INIT_FLAGS); irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED); irqd_set(&desc->irq_data, IRQD_IRQ_MASKED); desc->handle_irq = handle_bad_irq; desc->depth = 1 ; desc->irq_count = 0 ; desc->irqs_unhandled = 0 ; desc->tot_count = 0 ; desc->name = NULL ; desc->owner = owner; for_each_possible_cpu(cpu) *per_cpu_ptr(desc->kstat_irqs, cpu) = (struct irqstat) { }; desc_smp_init(desc, node, affinity); } static int init_desc (struct irq_desc *desc, int irq, int node, unsigned int flags, const struct cpumask *affinity, struct module *owner) { desc->kstat_irqs = alloc_percpu(struct irqstat); if (!desc->kstat_irqs) return -ENOMEM; if (alloc_masks(desc, node)) { free_percpu(desc->kstat_irqs); return -ENOMEM; } raw_spin_lock_init(&desc->lock); lockdep_set_class(&desc->lock, &irq_desc_lock_class); mutex_init(&desc->request_mutex); init_waitqueue_head(&desc->wait_for_threads); desc_set_defaults(irq, desc, node, affinity, owner); irqd_set(&desc->irq_data, flags); irq_resend_init(desc); #ifdef CONFIG_SPARSE_IRQ kobject_init(&desc->kobj, &irq_kobj_type); init_rcu_head(&desc->rcu); #endif return 0 ; } static struct irq_desc *alloc_desc (int irq, int node, unsigned int flags, const struct cpumask *affinity, struct module *owner) { struct irq_desc *desc ; int ret; desc = kzalloc_node(sizeof (*desc), GFP_KERNEL, node); if (!desc) return NULL ; ret = init_desc(desc, irq, node, flags, affinity, owner); if (unlikely(ret)) { kfree(desc); return NULL ; } return desc; }
sparse_irqs 稀疏中断锁与映射树的定义 这两行代码共同定义了Linux内核中用于管理“稀疏中断”(Sparse IRQs)的核心数据结构及其同步锁。稀疏中断是指中断号(IRQ number)不连续、分布范围很广的场景。使用传统的数组来管理这种情况会浪费大量内存,因此内核采用了一种名为“枫树”(Maple Tree)的高效数据结构。
struct maple_tree sparse_irqs
: 定义并初始化一个枫树。这个树的数据结构被特别设计用于高效地存储和查找基于大范围索引(此处是中断号)的数据。
RCU保护 : MT_FLAGS_USE_RCU
标志使得对这棵树的读取操作(如在中断处理时查找中断描述符)可以无锁、非常快速地进行,这对于性能至关重要。
外部锁 : MT_FLAGS_LOCK_EXTERN
标志告诉枫树的实现代码,写操作的同步将由外部提供的sparse_irq_lock
来负责。
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 static DEFINE_MUTEX (sparse_irq_lock) ;static struct maple_tree sparse_irqs = MTREE_INIT_EXT(sparse_irqs, MT_FLAGS_ALLOC_RANGE | MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU, sparse_irq_lock);
irq_insert_desc 将中断描述符插入到稀疏中断数组中 1 2 3 4 5 static void irq_insert_desc (unsigned int irq, struct irq_desc *desc) { MA_STATE(mas, &sparse_irqs, irq, irq); WARN_ON(mas_store_gfp(&mas, desc, GFP_KERNEL) != 0 ); }
irq_to_desc 获取中断描述符 1 2 3 4 struct irq_desc *irq_to_desc (unsigned int irq) { return mtree_load(&sparse_irqs, irq); }
early_irq_init 早期中断初始化 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 int __init early_irq_init (void ) { int i, initcnt, node = first_online_node; struct irq_desc *desc ; init_irq_default_affinity(); initcnt = arch_probe_nr_irqs(); printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n" , NR_IRQS, nr_irqs, initcnt); if (WARN_ON(nr_irqs > MAX_SPARSE_IRQS)) nr_irqs = MAX_SPARSE_IRQS; if (WARN_ON(initcnt > MAX_SPARSE_IRQS)) initcnt = MAX_SPARSE_IRQS; if (initcnt > nr_irqs) nr_irqs = initcnt; for (i = 0 ; i < initcnt; i++) { desc = alloc_desc(i, node, 0 , NULL , NULL ); irq_insert_desc(i, desc); } return arch_early_irq_init(); }
irq_sysfs_init: 初始化中断相关的sysfs接口 此函数在内核启动的后核心阶段被调用, 其核心作用是在sysfs
文件系统中创建并初始化用于中断(IRQ)管理的目录结构, 即/sys/kernel/irq/
。它会遍历系统中所有已经分配的中断描述符, 并为每一个中断号在/sys/kernel/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 static int __init irq_sysfs_init (void ) { struct irq_desc *desc ; int irq; guard(mutex)(&sparse_irq_lock); irq_kobj_base = kobject_create_and_add("irq" , kernel_kobj); if (!irq_kobj_base) return -ENOMEM; for_each_irq_desc(irq, desc) irq_sysfs_add(irq, desc); return 0 ; } postcore_initcall(irq_sysfs_init);
kernel/irq/irqdomain.c 中断域(IRQ Domain)的创建与实例化 这组函数共同构成了Linux内核irqchip
子系统中创建和实例化中断域的底层核心机制 。中断域的根本原理是提供一个通用的、可配置的软件对象(struct irq_domain
), 作为硬件中断控制器在内核中的抽象表示 。它负责将硬件特定的中断号(hwirq)翻译成内核全局统一的Linux IRQ号(virq), 从而实现了中断处理逻辑与具体硬件的解耦。
irq_domain_add_linear
是这一组函数中最高层的API, 它为中断控制器驱动提供了一个便捷的接口来创建一个”线性映射”的中断域。线性映射是最简单的一种映射方式, 它假设硬件中断号(hwirq)从0到size-1
是连续的, 并且内核可以为它们分配一段同样大小、连续的Linux IRQ号(virq)。
在STM32H750这样的系统中, 顶层的中断控制器驱动(如EXTI驱动)会使用这类函数来向内核注册自己, 建立起硬件和内核中断子系统之间的桥梁。
__irq_domain_create: 创建基础中断域对象 此函数是所有域创建流程的最底层基础, 负责分配内存并对struct irq_domain
对象进行最基本的初始化。
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 static struct irq_domain *__irq_domain_create (const struct irq_domain_info *info ){ struct irq_domain *domain ; int err; if (WARN_ON((info->size && info->direct_max) || (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && info->direct_max) || (info->direct_max && info->direct_max != info->hwirq_max))) return ERR_PTR(-EINVAL); domain = kzalloc_node(struct_size(domain, revmap, info->size), GFP_KERNEL, of_node_to_nid(to_of_node(info->fwnode))); if (!domain) return ERR_PTR(-ENOMEM); err = irq_domain_set_name(domain, info); if (err) { kfree(domain); return ERR_PTR(err); } domain->fwnode = fwnode_handle_get(info->fwnode); fwnode_dev_initialized(domain->fwnode, true ); INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL); domain->ops = info->ops; domain->host_data = info->host_data; domain->bus_token = info->bus_token; domain->hwirq_max = info->hwirq_max; if (info->direct_max) domain->flags |= IRQ_DOMAIN_FLAG_NO_MAP; domain->revmap_size = info->size; mutex_init(&domain->mutex); domain->root = domain; irq_domain_check_hierarchy(domain); return domain; }
__irq_domain_publish: 发布中断域至全局 此函数负责将一个已经创建好的域”发布”出去, 使其对内核可见。
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 static void __irq_domain_publish(struct irq_domain *domain){ mutex_lock(&irq_domain_mutex); debugfs_add_domain_dir(domain); list_add(&domain->link, &irq_domain_list); mutex_unlock(&irq_domain_mutex); pr_debug("Added domain %s\n" , domain->name); }
__irq_domain_instantiate: 实例化并配置中断域 这是核心的实例化函数, 它调用基础创建函数, 并执行所有高级配置, 如建立层次化关系、调用驱动初始化回调等。
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 static struct irq_domain *__irq_domain_instantiate (const struct irq_domain_info *info , bool cond_alloc_descs , bool force_associate ) { struct irq_domain *domain ; int err; domain = __irq_domain_create(info); if (IS_ERR(domain)) return domain; domain->flags |= info->domain_flags; domain->exit = info->exit ; domain->dev = info->dev; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY if (info->parent) { domain->root = info->parent->root; domain->parent = info->parent; } #endif if (info->dgc_info) { err = irq_domain_alloc_generic_chips(domain, info->dgc_info); if (err) goto err_domain_free; } if (info->init) { err = info->init(domain); if (err) goto err_domain_gc_remove; } __irq_domain_publish(domain); if (cond_alloc_descs && info->virq_base > 0 ) irq_domain_instantiate_descs(info); if (force_associate || info->virq_base > 0 ) { irq_domain_associate_many(domain, info->virq_base, info->hwirq_base, info->size - info->hwirq_base); } return domain; err_domain_gc_remove: if (info->dgc_info) irq_domain_remove_generic_chips(domain); err_domain_free: irq_domain_free(domain); return ERR_PTR(err); }
irq_domain_instantiate: 实例化中断域 (公共API) 这是一个简单的封装函数, 向驱动程序提供了一个更简洁的API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct irq_domain *irq_domain_instantiate (const struct irq_domain_info *info) { return __irq_domain_instantiate(info, false , false ); } EXPORT_SYMBOL_GPL(irq_domain_instantiate);
irq_domain_add_linear: 创建并注册一个线性中断域 这是一个提供给驱动使用的高层便捷API, 专门用于创建线性的、非层次化的中断域。
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 static inline struct irq_domain *irq_domain_add_linear (struct device_node *of_node, unsigned int size, const struct irq_domain_ops *ops, void *host_data) { struct irq_domain_info info = { .fwnode = of_node_to_fwnode(of_node), .size = size, .hwirq_max = size, .ops = ops, .host_data = host_data, }; struct irq_domain *d ; d = irq_domain_instantiate(&info); return IS_ERR(d) ? NULL : d; }
IRQ Domain Instantiation and Hierarchy Creation 此代码片段是Linux内核中断子系统(irqchip
)中用于**实例化(instantiate)和创建层级化中断域(hierarchical IRQ domain)**的核心函数。它的核心原理是提供一套结构化的API, 允许中断控制器驱动程序(如STM32的EXTI或GPIO驱动)将其硬件能力注册到内核中, 创建一个名为irq_domain
的软件对象。这个对象是硬件和内核通用中断处理逻辑之间的关键翻译层, 负责将硬件中断号(hwirq
)映射到内核可以识别和使用的、全局唯一的Linux IRQ号(virq
)。
irq_domain_create_hierarchy
: (API) 创建一个层级化中断域这是一个提供给驱动程序使用的高级内联函数, 也是我们之前分析的stm32_gpiolib_register_bank
函数实际调用的API。它的作用是创建一个新的中断域, 并将其明确地链接到一个已存在的父域(parent
)之下, 形成一个层次结构。
原理 : 此函数是一个便捷的封装器(wrapper) 。它并不执行复杂的逻辑, 而是将调用者传入的所有独立参数(如父域、标志、回调函数等)打包进一个标准的struct irq_domain_info
结构体中, 然后调用更底层的irq_domain_instantiate
来完成真正的实例化工作。这种设计简化了API, 使得创建子域的代码更清晰、更不易出错。在STM32中, GPIO Bank的中断域就是作为EXTI主中断域的子域来创建的。
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 static inline struct irq_domain *irq_domain_create_hierarchy (struct irq_domain *parent, unsigned int flags, unsigned int size, struct fwnode_handle *fwnode, const struct irq_domain_ops *ops, void *host_data) { const struct irq_domain_info info = { .fwnode = fwnode, .size = size, .hwirq_max = size ? : ~0U , .ops = ops, .host_data = host_data, .domain_flags = flags, .parent = parent, }; struct irq_domain *d = irq_domain_instantiate(&info); return IS_ERR(d) ? NULL : d; }
irq_domain_instantiate
& __irq_domain_instantiate
: (核心) 实例化中断域irq_domain_instantiate
是一个通用的API, 它调用内部核心函数__irq_domain_instantiate
来执行中断域的创建和初始化全过程。
原理 : __irq_domain_instantiate
是一个复杂的多阶段构造函数。它按照严格的顺序执行一系列操作, 并带有健壮的错误回滚机制:
分配与链接 : 分配irq_domain
结构体, 并根据info
中的parent
指针建立与父域的链接。
分配芯片(Chips) : 如果需要, 为这个域分配并关联一组通用的irq_chip
结构。irq_chip
包含了真正操作硬件的函数指针(如irq_mask
, irq_unmask
)。
驱动回调 : 调用驱动程序通过info->init
提供的自定义初始化回调函数, 允许驱动执行特定的硬件设置。
发布(Publish) : 将新创建的域添加到一个全局链表中, 使其对内核其他部分可见。
分配描述符 : 调用irq_domain_instantiate_descs
为该域将要管理的Linux IRQ号预先分配irq_desc
数据结构。
关联映射 : 如果需要, 调用irq_domain_associate_many
预先建立硬件中断号到Linux 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 static void __irq_domain_publish(struct irq_domain *domain){ mutex_lock(&irq_domain_mutex); debugfs_add_domain_dir(domain); list_add(&domain->link, &irq_domain_list); mutex_unlock(&irq_domain_mutex); pr_debug("Added domain %s\n" , domain->name); } static struct irq_domain *__irq_domain_instantiate (const struct irq_domain_info *info , bool cond_alloc_descs , bool force_associate ) { struct irq_domain *domain ; int err; domain = __irq_domain_create(info); if (IS_ERR(domain)) return domain; domain->flags |= info->domain_flags; domain->exit = info->exit ; domain->dev = info->dev; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY if (info->parent) { domain->root = info->parent->root; domain->parent = info->parent; } #endif if (info->dgc_info) { err = irq_domain_alloc_generic_chips(domain, info->dgc_info); if (err) goto err_domain_free; } if (info->init) { err = info->init(domain); if (err) goto err_domain_gc_remove; } __irq_domain_publish(domain); if (cond_alloc_descs && info->virq_base > 0 ) irq_domain_instantiate_descs(info); if (force_associate || info->virq_base > 0 ) { irq_domain_associate_many(domain, info->virq_base, info->hwirq_base, info->size - info->hwirq_base); } return domain; err_domain_gc_remove: if (info->dgc_info) irq_domain_remove_generic_chips(domain); err_domain_free: irq_domain_free(domain); return ERR_PTR(err); } struct irq_domain *irq_domain_instantiate (const struct irq_domain_info *info) { return __irq_domain_instantiate(info, false , false ); } EXPORT_SYMBOL_GPL(irq_domain_instantiate);
irq_domain_instantiate_descs
: 分配中断描述符原理 : 在配置了CONFIG_SPARSE_IRQ
的内核中, irq_desc
(内核中代表一个Linux IRQ号的核心数据结构)不是在启动时为所有可能的IRQ号静态分配的, 而是按需动态分配, 以节省内存。此函数就负责为新创建的irq_domain
将要使用的一段连续的Linux IRQ号(virq_base
到virq_base + size
)动态分配所需的irq_desc
结构体。这对于资源受限的STM32H750等MCU系统是重要的内存优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void irq_domain_instantiate_descs (const struct irq_domain_info *info) { if (!IS_ENABLED(CONFIG_SPARSE_IRQ)) return ; if (irq_alloc_descs(info->virq_base, info->virq_base, info->size, of_node_to_nid(to_of_node(info->fwnode))) < 0 ) { pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n" , info->virq_base); } }
Linux中断域查找系列函数 这一系列函数是Linux内核中断子系统的核心组成部分, 它们提供了一套API来根据设备树节点(device_node
)或更通用的固件节点句柄(fwnode_handle
)查找并返回已注册的irq_domain
(中断域)。irq_domain
的主要职责是作为一个翻译层, 将硬件特定的中断信息(如中断线编号、触发类型)转换为内核全局唯一的IRQ号。
irq_find_matching_fwspec: 根据固件规约查找匹配的中断域 这是该系列函数中的核心和基础。它遍历系统中所有已注册的irq_domain
, 并根据每个域驱动提供的匹配方法, 寻找第一个能够响应该中断请求的域。
原理: 该函数的核心是遍历一个全局的链表 irq_domain_list
, 这个链表包含了系统中所有通过irq_domain_add_*
系列函数注册的中断域。为了保证链表操作的线程安全(因为中断域可能在运行时被动态添加或移除), 整个遍历过程都被一个互斥锁 irq_domain_mutex
保护。
对于链表中的每一个中断域, 它会按以下优先级顺序尝试进行匹配:
select() 方法 : 如果域的操作函数集(ops
)提供了select
回调, 并且请求中指定了具体的总线类型(bus_token
), 则优先使用select
方法。这是最现代、最精确的匹配方式, 允许一个域根据详细的固件规约(fwspec
)和总线类型来决定是否处理该中断。
match() 方法 : 如果没有select
方法或bus_token
为任意匹配, 则尝试使用传统的match
回调。这个方法主要基于设备树节点(device_node
)和总线类型进行匹配, 兼容了旧的驱动。
直接比较 : 如果上述两个回调函数都未提供, 则执行一个最简单的默认匹配: 检查请求的固件节点句柄(fwnode
)是否与中断域自身注册时保存的固件节点句柄(h->fwnode
)完全相同。
一旦找到匹配项, 立即停止搜索并返回该中断域。
在STM32H750这样的单核抢占式系统上, mutex_lock(&irq_domain_mutex)
依然是必需的。它能防止一个正在遍历链表的任务被另一个更高优先级的任务抢占, 而后者可能会通过加载或卸载模块来修改irq_domain_list
, 从而避免数据竞争和链表损坏。
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 struct irq_domain *irq_find_matching_fwspec (struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token) { struct irq_domain *h , *found = NULL ; struct fwnode_handle *fwnode = fwspec->fwnode; int rc; mutex_lock(&irq_domain_mutex); list_for_each_entry(h, &irq_domain_list, link) { if (h->ops->select && bus_token != DOMAIN_BUS_ANY) rc = h->ops->select(h, fwspec, bus_token); else if (h->ops->match) rc = h->ops->match(h, to_of_node(fwnode), bus_token); else rc = ((fwnode != NULL ) && (h->fwnode == fwnode) && ((bus_token == DOMAIN_BUS_ANY) || (h->bus_token == bus_token))); if (rc) { found = h; break ; } } mutex_unlock(&irq_domain_mutex); return found; } EXPORT_SYMBOL_GPL(irq_find_matching_fwspec);
irq_find_matching_fwnode 与 irq_find_matching_host (内联函数) 这两个 static inline
函数是基于 irq_find_matching_fwspec
的便捷封装。在STM32这样的嵌入式系统中, inline
关键字建议编译器将函数体直接嵌入到调用处, 从而消除函数调用的开销, 提高执行效率。
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 static inline struct irq_domain *irq_find_matching_fwnode (struct fwnode_handle *fwnode, enum irq_domain_bus_token bus_token) { struct irq_fwspec fwspec = { .fwnode = fwnode, }; return irq_find_matching_fwspec(&fwspec, bus_token); } static inline struct irq_domain *irq_find_matching_host (struct device_node *node, enum irq_domain_bus_token bus_token) { return irq_find_matching_fwnode(of_fwnode_handle(node), bus_token); }
irq_find_host: 查找中断主控制器 这是最高层的封装函数, 也是在设备驱动中最常被调用的函数 (如上一轮问题中的stm32_pctrl_get_irq_domain
就调用了它)。它提供了一个带回退机制的简单接口来查找中断域。
原理: 它体现了一种”先精确, 后模糊”的查找策略。首先, 它尝试查找一个总线类型为DOMAIN_BUS_WIRED
的域, 这通常代表一个与设备直接硬线连接的、标准的片上中断控制器(如STM32的EXTI)。如果查找失败, 它会放宽条件, 再次尝试查找任意总线类型(DOMAIN_BUS_ANY
)的域。这种策略提高了在复杂系统中查找成功率, 同时优先选择最匹配的控制器。
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 static inline struct irq_domain *irq_find_host (struct device_node *node) { struct irq_domain *d ; d = irq_find_matching_host(node, DOMAIN_BUS_WIRED); if (!d) d = irq_find_matching_host(node, DOMAIN_BUS_ANY); return d; }
IRQ Domain 数据关联与设置函数 这两个函数是Linux内核中断域(irq_domain
)框架中用于数据查找和配置的核心API。它们协同工作, 在一个层级化的中断控制器结构中, 确保一个虚拟的Linux IRQ号(virq
)能够被正确地关联到其在特定硬件层级上的表示(irq_data
), 并能够被正确地配置其硬件属性(如硬件中断号hwirq
和操作函数集irq_chip
)。
irq_domain_get_irq_data: 在指定域中查找中断数据 此函数的核心作用是: 在一个可能存在多级中断控制器的层级结构中, 为一个给定的Linux IRQ号 (virq
), 查找并返回它在某一个特定中断域 (domain
) 内的上下文表示 (struct irq_data
) 。
在层级化的中断系统中(例如, GPIO Bank
-> EXTI
-> NVIC
), 一个单一的virq
会对应一个irq_data
的链表, 链表中的每个节点都代表了该virq
在某一硬件层级上的视图。此函数的原理就是遍历这个表示层级关系的链表, 找到属于我们目标domain
的那个节点。
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 struct irq_data *irq_domain_get_irq_data (struct irq_domain *domain, unsigned int virq) { struct irq_data *irq_data ; for (irq_data = irq_get_irq_data(virq); irq_data; irq_data = irq_data->parent_data) if (irq_data->domain == domain) return irq_data; return NULL ; } EXPORT_SYMBOL_GPL(irq_domain_get_irq_data);
irq_domain_set_hwirq_and_chip: 为域中的中断设置硬件号和芯片 此函数的核心作用是: 为一个已经分配好的Linux IRQ号(virq
), 在其指定的域(domain
)内, 填充其最重要的硬件属性 。这是在中断分配流程(.alloc
回调)中的一个关键配置步骤。
其原理非常直接:
它首先必须找到正确的”配置表格”, 即调用irq_domain_get_irq_data
来获取virq
在该domain
内的irq_data
结构体。
找到后, 它就将调用者提供的硬件信息——硬件中断号(hwirq
)、硬件操作函数集(chip
)以及私有数据(chip_data
)——直接填入到这个irq_data
结构体的相应字段中。
完成这一步后, 内核就拥有了管理这个中断在当前硬件层级上所需的所有信息。
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 int irq_domain_set_hwirq_and_chip (struct irq_domain *domain, unsigned int virq, irq_hw_number_t hwirq, const struct irq_chip *chip, void *chip_data) { struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq); if (!irq_data) return -ENOENT; irq_data->hwirq = hwirq; irq_data->chip = (struct irq_chip *)(chip ? chip : &no_irq_chip); irq_data->chip_data = chip_data; return 0 ; }
中断反向映射查找函数 此代码片段展示了Linux内核中断子系统中用于反向查找(reverse lookup)的核心函数。其根本原理是 提供一个高效的机制, 根据中断控制器域(domain
)和硬件中断号(hwirq
), 快速地找到与之关联的Linux IRQ号(virq
)以及管理该IRQ的核心数据结构struct irq_desc
。
这个功能是irqchip
子系统能够防止重复映射、正确管理中断资源的基础。例如, 在调用irq_create_mapping
之前, 内核必须先调用irq_find_mapping
来检查是否已经为某个hwirq
创建了映射。
__irq_resolve_mapping: 从hwirq解析映射的核心实现 这是该系列中最低层的核心函数, 负责执行实际的查找操作。它设计用来支持中断域的多种不同映射策略。
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 struct irq_desc *__irq_resolve_mapping (struct irq_domain *domain , irq_hw_number_t hwirq , unsigned int *irq ) { struct irq_desc *desc = NULL ; struct irq_data *data ; if (domain == NULL ) domain = irq_default_domain; if (domain == NULL ) return desc; if (irq_domain_is_nomap(domain)) { if (hwirq < domain->hwirq_max) { data = irq_domain_get_irq_data(domain, hwirq); if (data && data->hwirq == hwirq) desc = irq_data_to_desc(data); if (irq && desc) *irq = hwirq; } return desc; } rcu_read_lock(); if (hwirq < domain->revmap_size) data = rcu_dereference(domain->revmap[hwirq]); else data = radix_tree_lookup(&domain->revmap_tree, hwirq); if (likely(data)) { desc = irq_data_to_desc(data); if (irq) *irq = data->irq; } rcu_read_unlock(); return desc; } EXPORT_SYMBOL_GPL(__irq_resolve_mapping);
上层便捷API 这组内联函数为内核其他部分提供了更友好、更特定于需求的接口, 它们在内部都调用了__irq_resolve_mapping
。
irq_resolve_mapping 此函数只关心获取核心的irq_desc
结构体, 而不关心Linux IRQ号本身。
1 2 3 4 5 6 7 8 9 10 11 12 13 static inline struct irq_desc *irq_resolve_mapping (struct irq_domain *domain, irq_hw_number_t hwirq) { return __irq_resolve_mapping(domain, hwirq, NULL ); }
irq_find_mapping 此函数是irq_create_mapping
的反操作, 它只关心获取Linux IRQ号, 如果找不到则返回0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static inline unsigned int irq_find_mapping (struct irq_domain *domain, irq_hw_number_t hwirq) { unsigned int irq; if (__irq_resolve_mapping(domain, hwirq, &irq)) return irq; return 0 ; }
中断映射创建函数 此代码片段展示了Linux内核中断子系统中用于创建硬件中断号(hwirq)到Linux IRQ号(virq)映射的核心函数 。这是irqchip
子系统的基石, 也是gpio_to_irq()
这类翻译功能最终依赖的底层机制。其核心原理是为一个特定的硬件中断号, 在全局的Linux IRQ编号空间中动态地分配一个唯一的、未使用的IRQ号, 然后在这两者之间建立一个持久的、双向的关联关系 。
这个过程必须是原子性的, 并且能够防止重复映射, 因此它严重依赖锁和内部数据结构来保证正确性。
irq_create_mapping_affinity_locked: 在锁定下执行映射创建的核心逻辑 这是一个内部函数, 负责执行实际的映射创建工作。它的命名后缀_locked
明确告知调用者, 此函数必须在一个已经获取了中断域的root->mutex
锁的上下文中被调用。
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 static unsigned int irq_create_mapping_affinity_locked (struct irq_domain *domain, irq_hw_number_t hwirq, const struct irq_affinity_desc *affinity) { struct device_node *of_node = irq_domain_get_of_node(domain); int virq; pr_debug("irq_create_mapping(0x%p, 0x%lx)\n" , domain, hwirq); virq = irq_domain_alloc_descs(-1 , 1 , hwirq, of_node_to_nid(of_node), affinity); if (virq <= 0 ) { pr_debug("-> virq allocation failed\n" ); return 0 ; } if (irq_domain_associate_locked(domain, virq, hwirq)) { irq_free_desc(virq); return 0 ; } pr_debug("irq %lu on domain %s mapped to virtual irq %u\n" , hwirq, of_node_full_name(of_node), virq); return virq; }
irq_create_mapping_affinity: 创建带CPU亲和性的中断映射 (公共API) 这是一个供内核其他部分(如irqchip
驱动)使用的API函数, 它封装了锁的获取/释放以及对已存在映射的检查。
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 unsigned int irq_create_mapping_affinity (struct irq_domain *domain, irq_hw_number_t hwirq, const struct irq_affinity_desc *affinity) { int virq; if (domain == NULL ) domain = irq_default_domain; if (domain == NULL ) { WARN(1 , "%s(, %lx) called with NULL domain\n" , __func__, hwirq); return 0 ; } mutex_lock(&domain->root->mutex); virq = irq_find_mapping(domain, hwirq); if (virq) { pr_debug("existing mapping on virq %d\n" , virq); goto out; } virq = irq_create_mapping_affinity_locked(domain, hwirq, affinity); out: mutex_unlock(&domain->root->mutex); return virq; } EXPORT_SYMBOL_GPL(irq_create_mapping_affinity);
irq_create_mapping: 创建中断映射 (最常用的便捷API) 这是一个最常用的内联函数, 它为irq_create_mapping_affinity
提供了一个更简单的接口, 用于不需要指定CPU亲和性的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static inline unsigned int irq_create_mapping (struct irq_domain *domain, irq_hw_number_t hwirq) { return irq_create_mapping_affinity(domain, hwirq, NULL ); }
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
(中断描述符)**进行的一系列生命周期管理操作。
中断描述符 (irq_desc
) :内核中有一个全局的irq_desc
数组或基数树,为系统中每一个可能的Linux IRQ号都准备了一个描述符。这个描述符是管理该中断线路的“控制中心”。
申请中断 (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_startup
或irq_enable
)来在硬件层面真正地使能这个中断。
禁用/使能中断 (disable_irq
/ enable_irq
) :
这些函数通常用于驱动需要在一段代码中临时屏蔽中断的场景。
它们会操作irq_desc
中的一个深度计数器(depth
),以支持嵌套调用。
当计数器从0变为1时,disable_irq
会调用底层irq_chip->irq_mask
来屏蔽硬件中断。当计数器从1变回0时,enable_irq
会调用irq_chip->irq_unmask
来取消屏蔽。
释放中断 (free_irq
) :
当驱动模块被卸载时,必须调用此函数。
它会在irq_desc
的链表中找到并移除属于该驱动的irq_action
。
如果这是该中断线上的最后一个irq_action
,manage.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
, tasklet
或workqueue
,而不是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
结构体来巧妙地实现这一转换:
检查与豁免 : 函数首先会进行一系列检查, 以确定是否应该进行强制转换:
!force_irqthreads()
: 检查全局的threadirqs
开关是否打开。如果没打开, 函数直接返回, 不做任何事。
IRQF_NO_THREAD
, IRQF_PERCPU
: 驱动程序可以通过这些标志明确表示其中断处理不应被线程化(例如, 高频率的定时器中断或每CPU中断), 函数会尊重这些请求。
handler == irq_default_primary_handler
: 如果驱动程序已经将handler
设置为默认的占位符, 说明它本来就是一个纯线程化的中断, 无需强制转换。
安全保障 (IRQF_ONESHOT
) : 这是最关键的安全步骤。函数会给irqaction
强制添加IRQF_ONESHOT
标志。这个标志告诉内核中断核心: 在硬中断处理程序返回后, 必须保持硬件中断线被屏蔽(masked), 直到对应的中断线程执行完毕才能重新使能 。这可以完美地防止中断风暴, 尤其是对于电平触发的中断, 如果不这样做, 在线程被调度运行之前, 中断会立即再次触发。
处理程序“搬家” :
简单情况 (只有handler
) : 这是最常见的转换。
new->thread_fn = new->handler;
: 将驱动程序原本的硬中断处理函数(handler
)的指针, “搬到”线程处理函数(thread_fn
)的槽位里。
new->handler = irq_default_primary_handler;
: 将原来的硬中断处理函数槽位用一个内核预设的、极简的irq_default_primary_handler
来填充。这个默认处理程序的唯一工作就是返回IRQ_WAKE_THREAD
, 以唤醒中断线程。
复杂情况 (同时有handler
和thread_fn
) : 如果驱动本身就是一个”分离式”处理模型。函数会更进一步:
它会分配一个次级的(secondary
) irqaction
结构。
将驱动原本的thread_fn
“搬到”这个次级irqaction
的thread_fn
槽位里。
然后, 按照简单情况的逻辑, 将驱动原本的handler
“搬到”主irqaction
的thread_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) { if (!force_irqthreads()) return 0 ; if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)) return 0 ; if (new->handler == irq_default_primary_handler) return 0 ; new->flags |= IRQF_ONESHOT; if (new->handler && new->thread_fn) { new->secondary = kzalloc(sizeof (struct irqaction), GFP_KERNEL); if (!new->secondary) return -ENOMEM; new->secondary->handler = irq_forced_secondary_handler; new->secondary->thread_fn = new->thread_fn; new->secondary->dev_id = new->dev_id; new->secondary->irq = new->irq; new->secondary->name = new->name; } set_bit(IRQTF_FORCED_THREAD, &new->thread_flags); new->thread_fn = new->handler; new->handler = irq_default_primary_handler; return 0 ; }
__irq_set_trigger
: 设置中断线的硬件触发模式这是Linux内核中断子系统中一个至关重要的底层函数 , 负责将一个抽象的中断触发类型请求(如边沿触发、电平触发)转换为对具体中断控制器(irq_chip
)硬件寄存器的编程操作 。当request_irq
被调用时, 或者当驱动程序需要动态改变中断触发方式时, 最终都会通过这个函数来与硬件交互。
此函数的核心原理是充当通用中断核心 与**特定硬件驱动(irq_chip
)**之间的桥梁, 并实施一套关键的安全规程:
委托给irq_chip
: 函数首先会检查与该中断线关联的irq_chip
驱动是否实现了irq_set_type
这个回调函数。如果没有实现, 意味着该硬件不支持动态配置触发模式, 函数会直接返回。如果实现了, 实际的硬件编程工作将完全委托给这个函数来完成。
“先屏蔽, 再配置, 后恢复”的安全序列 : 这是此函数最重要的安全机制。某些中断控制器硬件(如STM32上的EXTI)要求在修改其触发模式配置时, 对应的中断线必须先被屏蔽(masked/disabled)。如果在中断使能的状态下修改配置, 可能会导致不确定的行为或产生伪中断(spurious IRQ)。
函数会检查irq_chip
的标志位, 看它是否声明了IRQCHIP_SET_TYPE_MASKED
。
如果声明了, 函数会在调用irq_set_type
之前, 先调用mask_irq
在硬件上屏蔽该中断。
在irq_set_type
调用完成之后, 如果之前屏蔽了中断, 它会再调用unmask_irq
来恢复中断线之前的使能状态。
这个”屏蔽-配置-恢复”的序列确保了硬件配置更改的原子性和安全性。
内核状态同步 : 在成功配置硬件后, 函数会更新内核内部维护的、与该中断线相关的多个状态描述符(irq_data
和irq_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 int __irq_set_trigger(struct irq_desc *desc, unsigned long flags){ struct irq_chip *chip = desc->irq_data.chip; int ret, unmask = 0 ; if (!chip || !chip->irq_set_type) { pr_debug("No set_type function for IRQ %d (%s)\n" , irq_desc_get_irq(desc), chip ? (chip->name ? : "unknown" ) : "unknown" ); return 0 ; } if (chip->flags & IRQCHIP_SET_TYPE_MASKED) { if (!irqd_irq_masked(&desc->irq_data)) mask_irq(desc); if (!irqd_irq_disabled(&desc->irq_data)) unmask = 1 ; } flags &= IRQ_TYPE_SENSE_MASK; ret = chip->irq_set_type(&desc->irq_data, flags); switch (ret) { case IRQ_SET_MASK_OK: case IRQ_SET_MASK_OK_DONE: irqd_clear(&desc->irq_data, IRQD_TRIGGER_MASK); irqd_set(&desc->irq_data, flags); fallthrough; case IRQ_SET_MASK_OK_NOCOPY: flags = irqd_get_trigger_type(&desc->irq_data); 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 ; break ; default : 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); new->thread = get_task_struct(t); set_bit(IRQTF_AFFINITY, &new->thread_flags); return 0 ; } 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 ; } 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; if (desc->irq_data.chip == &no_irq_chip) return -ENOSYS; 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; } } if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE) new->flags &= ~IRQF_ONESHOT; mutex_lock(&desc->request_mutex); chip_bus_lock(desc); 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; } } raw_spin_lock_irqsave(&desc->lock, flags); old_ptr = &desc->action; old = *old_ptr; if (old) { unsigned int oldtype; 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; if ((old->flags & IRQF_ONESHOT) && (new->flags & IRQF_COND_ONESHOT)) new->flags |= IRQF_ONESHOT; else if ((old->flags ^ new->flags) & IRQF_ONESHOT) goto mismatch; if ((old->flags & IRQF_PERCPU) != (new->flags & IRQF_PERCPU)) goto mismatch; do { thread_mask |= old->thread_mask; old_ptr = &old->next; old = *old_ptr; } while (old); shared = 1 ; } if (new->flags & IRQF_ONESHOT) { if (thread_mask == ~0UL ) { ret = -EBUSY; goto out_unlock; } new->thread_mask = 1UL << ffz(thread_mask); } else if (new->handler == irq_default_primary_handler && !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) { 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; 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) { } *old_ptr = new; raw_spin_unlock_irqrestore(&desc->lock, flags); chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); return 0 ; mismatch: ret = -EBUSY; out_unlock: raw_spin_unlock_irqrestore(&desc->lock, flags); if (!desc->action) irq_release_resources(desc); out_bus_unlock: chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); out_thread: out_mput: module_put(desc->owner); return ret; }
request_threaded_irq
: 注册中断处理程序 (核心函数)这是Linux内核中用于为一个驱动程序申请和注册中断处理程序的根本性函数 。当一个硬件设备(如STM32上的DMA、UART或GPIO)需要通过中断信号通知CPU有事件发生时, 其驱动程序必须调用此函数来建立硬件中断信号与特定软件处理函数之间的连接。
此函数的核心原理是实现了现代Linux内核的中断处理”两阶段”或”分离式处理”模型 , 将中断处理分为两个部分:
硬中断处理程序 (handler
) : 这是”上半部”(Top Half)。当硬件中断发生时, CPU会立即跳转到这里执行。此函数的运行环境非常受限:
它在原子上下文 中运行, 意味着它不能睡眠(不能调用任何可能导致进程调度的函数, 如kmalloc
或mutex_lock
)。
在单核系统(如STM32H750)上, 它运行时本地中断是关闭的, 以防止被其他中断嵌套。
它的职责必须是最小化和快速 的: 检查中断是否真的由其设备产生(在共享中断的情况下), 读取/清除中断状态寄存器, 禁用设备的中断源以防中断风暴, 如果有”下半部”, 则唤醒它。
如果它返回IRQ_WAKE_THREAD
, 内核就会唤醒对应的”下半部”线程。
线程化中断处理程序 (thread_fn
) : 这是”下半部”(Bottom Half)。它在一个普通的内核线程上下文 中运行。这意味着:
它可以被抢占, 也可以睡眠。
它可以调用所有标准的内核服务, 如内存分配、加锁、与用户空间交互等。
它负责执行所有耗时较长的中断处理工作, 如数据拷贝、协议处理、唤醒等待队列等。
此函数的工作流程是:
参数验证 : 首先进行一系列严格的健全性检查。最重要的是, 如果中断被声明为共享的 (IRQF_SHARED
) , 那么dev_id
参数必须是一个唯一的非NULL
值。这是因为当多个设备共享同一条IRQ线时, 内核需要dev_id
来区分应该释放哪一个具体的中断处理程序。
分配irqaction
: 它分配一个irqaction
结构体。这个结构体就像一个”中断注册表单”, 包含了驱动程序提供的所有信息: 两个处理函数指针、标志位、名称和dev_id
。
安装irqaction
: 它调用内部函数__setup_irq
, 将这个irqaction
结构体添加到内核为该IRQ号维护的irq_desc
(中断描述符)的动作链表中。对于非共享中断, 这个链表只有一个节点; 对于共享中断, 则有多个。
使能中断 : __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 static irqreturn_t irq_default_primary_handler (int irq, void *dev_id) { return IRQ_WAKE_THREAD; } 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; 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; if (!handler) { if (!thread_fn) return -EINVAL; handler = irq_default_primary_handler; } action = kzalloc(sizeof (struct irqaction), GFP_KERNEL); if (!action) return -ENOMEM; action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id; retval = irq_chip_pm_get(&desc->irq_data); if (retval < 0 ) { kfree(action); return retval; } retval = __setup_irq(irq, desc, action); if (retval) { irq_chip_pm_put(&desc->irq_data); kfree(action->secondary); kfree(action); } #ifdef CONFIG_DEBUG_SHIRQ_FIXME if (!retval && (irqflags & IRQF_SHARED)) { unsigned long flags; disable_irq(irq); local_irq_save(flags); handler(irq, dev_id); local_irq_restore(flags); enable_irq(irq); } #endif return retval; } EXPORT_SYMBOL(request_threaded_irq);
kernel/irq/proc.c init_irq_proc: 初始化/proc/irq中断信息接口 此函数在内核启动期间被调用,其核心作用是在/proc
虚拟文件系统中创建用于监控和管理硬件中断的目录结构。它首先创建了一个顶层目录/proc/irq/
,然后遍历系统中所有已注册的中断描述符(IRQ descriptor),为每一个中断号(IRQ number)创建一个对应的子目录(例如/proc/irq/16/
),并在其中填充用于显示该中断状态的属性文件。
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 void init_irq_proc (void ) { unsigned int irq; struct irq_desc *desc ; root_irq_dir = proc_mkdir("irq" , NULL ); if (!root_irq_dir) return ; register_default_affinity_proc(); for_each_irq_desc(irq, desc) register_irq_proc(irq, desc); }
register_irq_proc: 为指定的中断号创建/proc接口 此函数的核心作用是为一个具体的中断(IRQ)在/proc/irq/
目录下创建一个对应的子目录(例如/proc/irq/42/
),并在这个子目录中填充一系列的虚拟文件,用于展示和(在多核系统中)控制该中断的属性。这个函数通常在中断处理程序首次被注册时调用,而不是在中断描述符被创建时调用。
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 #define MAX_NAMELEN 10 void register_irq_proc (unsigned int irq, struct irq_desc *desc) { static DEFINE_MUTEX (register_lock) ; void __maybe_unused *irqp = (void *)(unsigned long ) irq; char name [MAX_NAMELEN]; if (!root_irq_dir || (desc->irq_data.chip == &no_irq_chip)) return ; guard(mutex)(®ister_lock); if (desc->dir) return ; sprintf (name, "%u" , irq); desc->dir = proc_mkdir(name, root_irq_dir); if (!desc->dir) return ; proc_create_single_data("spurious" , 0444 , desc->dir, irq_spurious_proc_show, (void *)(long )irq); }