[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 ); }
irq_set_irq_wake: 中断唤醒功能的启用与禁用 本代码片段展示了Linux内核中用于控制单个中断请求(IRQ)作为系统唤醒源的核心机制。其主要功能由irq_set_irq_wake函数实现,该函数提供了一个统一的接口,允许设备驱动程序将其使用的中断配置为能够将系统从低功耗睡眠状态(如挂起到内存)中唤醒。为了支持共享中断,该机制内部实现了一个引用计数器。
实现原理分析 该机制是内核电源管理与中断子系统交互的关键部分,它通过分层设计将通用API与具体的硬件中断控制器驱动解耦。
API与封装 :
irq_set_irq_wake是核心的API函数,接受中断号和开关标志(on)作为参数。
enable_irq_wake和disable_irq_wake是两个便利的内联函数,它们分别通过向irq_set_irq_wake传递1或0来简化调用,提高了代码的可读性。
引用计数 (wake_depth) :
这是支持共享中断的关键。一个物理中断线可能被多个设备驱动共享。每个驱动都可以独立请求将该中断设置为唤醒源。
struct irq_desc 结构中的 wake_depth 成员作为一个引用计数器。
当调用enable_irq_wake时,wake_depth会递增。只有在wake_depth从0变为1时(即第一次请求唤醒),才会真正调用硬件相关的函数set_irq_wake_real来配置硬件。
当调用disable_irq_wake时,wake_depth会递减。只有在wake_depth从1变为0时(即最后一次释放唤醒请求),才会调用set_irq_wake_real来取消硬件的唤醒设置。
这种设计确保了一个驱动禁用唤醒功能不会影响到同样共享该中断且仍需要唤醒功能的另一个驱动。
硬件抽象 (irq_chip->irq_set_wake) :
实际的硬件配置操作被抽象出来,由set_irq_wake_real函数执行。
该函数通过irq_to_desc找到对应中断的描述符irq_desc,然后访问其中的中断控制器芯片描述irq_chip。
最终,它会调用irq_chip结构体中的irq_set_wake函数指针。这个函数指针指向的是具体中断控制器驱动(例如ARM GIC或STM32 EXTI的驱动)中实现的、用于配置硬件唤醒功能的函数。这种方式使得上层代码与具体硬件实现完全分离。
状态管理与同步 :
scoped_irqdesc_get_and_buslock宏用于安全地获取中断描述符并锁定它。这个锁(通常是自旋锁)确保了对wake_depth计数器的读-改-写操作是原子的,防止了在多核系统或中断上下文中的竞争条件。
IRQD_WAKEUP_STATE标志位被用来在irq_data的状态中标记当前中断是否处于可唤醒状态,便于内核其他部分快速查询。
特定场景分析:单核、无MMU的STM32H750平台 硬件交互
目标硬件 : 在STM32H750平台上,中断控制器由ARM Cortex-M7内核内置的NVIC(嵌套向量中断控制器)和STM32片上外设EXTI(外部中断/事件控制器)共同组成。能够将系统从STOP等低功耗模式唤醒的通常是EXTI。
函数指针的实现 : 当代码调用desc->irq_data.chip->irq_set_wake时,它实际会调用到STM32 EXTI中断控制器驱动中的一个函数。这个函数会执行以下操作:
将EXTI对应线路配置为中断模式(而不是事件模式)。
清除该线路的挂起位。
通过设置EXTI的唤醒中断屏蔽寄存器(EXTI_WIMR),使能该线路的唤醒能力。
可能还需要配置电源控制器(PWR)以允许特定的唤醒源(如EXTI)生效。
触发流程 : 当一个外设驱动(如UART驱动)在执行uart_suspend_port时,如果发现该串口被配置为唤醒源,就会调用enable_irq_wake。这将触发上述硬件配置流程,使得当STM32H750进入STOP模式后,如果UART的RX引脚上出现电平变化,EXTI能够产生一个唤醒信号,让微控制器恢复运行。
单核环境影响 在单核处理器上,scoped_irqdesc_get_and_buslock宏获取的锁(bus_lock)的主要作用是禁用本地中断 。这可以防止在修改wake_depth计数器的过程中,被一个中断服务程序抢占。如果中断服务程序中也尝试修改同一个或另一个中断的唤醒状态,就可能导致数据不一致。因此,即使在单核系统上,这种锁机制对于保证操作的原子性和数据完整性也是至关重要的。
无MMU影响 中断唤醒机制完全工作在内核的特权级别,它处理的是中断号、中断描述符和对硬件寄存器的直接操作。这些操作与内存的虚拟化和地址翻译无关。因此,没有MMU对这段代码的逻辑和执行没有任何影响。它可以在无MMU的STM32H750上正确地与硬件交互,配置中断唤醒功能。
代码分析 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 extern int irq_set_irq_wake (unsigned int irq, unsigned int on) ;static inline int enable_irq_wake (unsigned int irq) { return irq_set_irq_wake(irq, 1 ); } static inline int disable_irq_wake (unsigned int irq) { return irq_set_irq_wake(irq, 0 ); } static int set_irq_wake_real (unsigned int irq, unsigned int on) { struct irq_desc *desc = irq_to_desc(irq); int ret = -ENXIO; if (irq_desc_get_chip(desc)->flags & IRQCHIP_SKIP_SET_WAKE) return 0 ; if (desc->irq_data.chip->irq_set_wake) ret = desc->irq_data.chip->irq_set_wake(&desc->irq_data, on); return ret; } static inline void irqd_clear (struct irq_data *d, unsigned int mask) { __irqd_to_state(d) &= ~mask; } static inline void irqd_set (struct irq_data *d, unsigned int mask) { __irqd_to_state(d) |= mask; } int irq_set_irq_wake (unsigned int irq, unsigned int on) { scoped_irqdesc_get_and_buslock(irq, IRQ_GET_DESC_CHECK_GLOBAL) { struct irq_desc *desc = scoped_irqdesc; int ret = 0 ; if (irq_is_nmi(desc)) return -EINVAL; if (on) { if (desc->wake_depth++ == 0 ) { ret = set_irq_wake_real(irq, on); if (ret) desc->wake_depth = 0 ; else irqd_set(&desc->irq_data, IRQD_WAKEUP_STATE); } } else { if (desc->wake_depth == 0 ) { WARN(1 , "不配对的IRQ %d唤醒禁用\n" , irq); } else if (--desc->wake_depth == 0 ) { ret = set_irq_wake_real(irq, on); if (ret) desc->wake_depth = 1 ; else irqd_clear(&desc->irq_data, IRQD_WAKEUP_STATE); } } return ret; } return -EINVAL; } EXPORT_SYMBOL(irq_set_irq_wake);
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); }