
Linux 内核 irq_domain 中断号映射库完全指南从 hwirq 到 Linux IRQ 的架构与实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读irq_domain是 Linux 内核中断子系统位于 kernel/irq/irqdomain.c提供的核心基础设施负责把中断控制器本地的硬件中断号hwirq与全局的 Linux 虚拟中断号Linux IRQ number / virq建立双向映射。本文以内核官方文档 Documentation/core-api/irq/irq-domain.rst 为主线深入讲解 irq_domain 的设计动机、创建与使用流程、四类反向映射机制Linear / Tree / No Map / Legacy的适用场景与取舍以及面向多级中断控制器拓扑的 Hierarchy IRQ Domain 与 Stacked irq_chip 架构并辅以源码级实现佐证。读完本文你将掌握中断控制器驱动中irq_domain的选型依据、完整调用链与调试手段。一、为什么需要 irq_domain从一个数字到两层数字空间1.1 单一中断控制器时代的简单模型Linux 内核传统的中断设计采用一个单一的大号空间single large number space每一个独立的中断源被分配一个唯一的 Linux IRQ 号。当系统中只有一个中断控制器时这个模型非常简单——Linux IRQ 号可以直接与中断控制器上的硬件中断线一一对应。1.2 多控制器时代的挑战随着系统复杂度上升情况发生了根本变化中断控制器数量持续增长。各类子驱动如 GPIO 控制器为了避免在 IRQ 核心系统之外重复实现相同的回调机制会把自己的中断处理建模为 irqchip这实质上形成了级联中断控制器cascading interrupt controllers。文档原文即指出subdrivers of different kinds such as GPIO controllers avoid reimplementing identical callback mechanisms as the IRQ core system by modelling their interrupt handlers as irqchips。硬件号与 Linux 号不再相等。过去 IRQ 号可以选成与根中断控制器真正向 CPU 触发中断线的部件的硬件中断线一致如今这个数字仅仅是一个数字与硬件中断号没有任何关系。因此内核迫切需要一种机制把**控制器本地中断号hardware IRQ / hwirq**与Linux IRQ 号分离开来。irq_alloc_desc*()与irq_free_desc*()系列 API 只负责 Linux IRQ 号的分配与释放并不提供从 hwirq 到 Linux IRQ 号的反向映射reverse mapping能力。1.3 irq_domain 的定位irq_domain库在irq_alloc_desc*()API 之上增加了 hwirq 与 Linux IRQ 号之间的映射管理且优先于中断控制器驱动自行实现私有反向映射方案。此外irq_domain 还实现了从抽象的struct irq_fwspec到 hwirq 号的翻译translation目前支持的数据源包括Device Tree非 DT 的固件节点firmware nodeACPI GSIsoftware node该翻译实现不需要任何额外的平台支持代码The implementation is performed without any extra platform support code并可以方便地扩展以支持其他 IRQ 拓扑数据源。二、irq_domain 的基本用法创建、映射、查询2.1 核心概念domain、fwnode 与 opsstruct irq_domain可以被理解为一个 irq domain 控制器它负责管理某一中断域内硬件中断号与虚拟中断号之间的映射。该结构体通常由 PIC可编程中断控制器代码为某个 PIC 实例创建不过如果多个 PIC 采用扁平编号模型一个 domain 也可以覆盖多个 PIC。从源码 include/linux/irqdomain.h 可以看到struct irq_domain的关键成员成员作用link全局 irq_domain 链表元素name中断域名称ops指向irq_domain_ops方法表host_data供属主使用的私有数据指针irq_domain 核心代码不触碰flags每个 irq_domain 的标志位mapcount已映射的中断数量mutex/root域锁层级域复用根域的锁fwnode与该 irq_domain 关联的固件节点bus_token与 fwnode 配对用于唯一标识一个 irq_domainparent指向父 irq_domain支持层级域受CONFIG_IRQ_DOMAIN_HIERARCHY保护hwirq_maxhwirq 号的上限避免与保留的 hwirq 冲突可以为~0revmap_size线性映射表revmap的大小revmap_tree用于容纳无法放进线性表的 hwirq 的基数树revmap线性表存放irq_data指针柔性数组成员追加在结构体尾部主机标识domain 的主机代码与数据结构通过fwnode_handle指针来标识自身。在某些情况下为了保持源码兼容这个 fwnode 指针会被升级为 DT 的device_node。对于那些无法为中断控制器提供唯一标识符的固件基础设施irq_domain 代码提供了fwnode 分配器对应源码中的__irq_domain_alloc_fwnode()见 kernel/irq/irqdomain.c。从源码看该分配器支持IRQCHIP_FWNODE_NAMED、IRQCHIP_FWNODE_NAMED_ID等命名模式默认命名为irqchip物理地址格式irq_domain_free_fwnode()负责释放这些 fwnode。domain 回调负责在某个 irq_desc 被映射之后为它设置 irq_chip 的正是 domain 的回调函数。struct irq_domain_ops见 include/linux/irqdomain.h包含match()/select()domain 匹配与选择map()/unmap()建立 / 解除映射时的回调map()中驱动可以执行所需的硬件初始化xlate()/translate()把 DT/fwspec 中断描述翻译成 hwirq 与触发类型层级域扩展接口alloc()、free()、activate()、deactivate()、get_fwspec_info()受CONFIG_IRQ_DOMAIN_HIERARCHY保护调试接口debug_show()受CONFIG_GENERIC_IRQ_DEBUGFS保护2.2 创建 domain中断控制器驱动通过调用irq_domain_create_*()系列函数之一创建并注册struct irq_domain每种映射方法对应不同的分配函数详见第三节。调用时必须提供struct irq_domain_ops指针成功后返回指向struct irq_domain的指针。从源码看这些创建函数最终都会汇聚到内部实现__irq_domain_create()与__irq_domain_instantiate()kernel/irq/irqdomain.c完成以下工作校验参数如size与direct_max不能同时设置、No Map 域必须启用CONFIG_IRQ_DOMAIN_NOMAP等分配 domain 结构体并初始化 radix treeINIT_RADIX_TREE(domain-revmap_tree, GFP_KERNEL)依据 fwnode 类型生成 domain 名称irqchip fwnode / OF 节点 / ACPI 节点 / software node缺省时命名为unknown-id格式初始化互斥锁并确定root指针层级域取根域的锁调用irq_domain_check_hierarchy()检查层级支持通过__irq_domain_publish()把 domain 加入全局链表irq_domain_list并在 debugfs 中创建对应目录若为 legacy 语义则调用irq_domain_associate_many()预先关联一批 virq 与 hwirq见irq_domain_create_legacy()的实现。2.3 建立映射irq_create_mapping()大多数情况下新建的 irq_domain 初始为空没有任何 hwirq 与 Linux IRQ 号之间的映射。映射通过调用irq_create_mapping()添加其原型见 include/linux/irqdomain.hunsigned int irq_create_mapping(struct irq_domain *domain, irq_hw_number_t hwirq);参数domain拥有该硬件中断的 domain传 NULL 表示使用默认 domainhwirq该 domain 空间内的硬件中断号返回值Linux IRQ 号出错时返回 0。注意每个硬件中断只允许一个映射如需设置触发/电平类型应在返回的号上调用set_irq_type()。调用流程如果该 hwirq 的映射尚不存在irq_create_mapping()内部实现为irq_create_mapping_affinity()会分配一个新的 Linuxirq_desc将其与 hwirq 关联调用irq_domain_ops.map()回调——驱动在此执行任何必要的硬件设置包括给 irq_desc 挂上合适的 irq_chip。重要约束文档明确强调irq_create_mapping()必须在任何irq_find_mapping()调用之前至少调用一次否则描述符不会被分配。2.4 查询与使用映射映射建立后可通过多种方法检索或使用API作用irq_resolve_mapping()返回给定 domain hwirq 对应的irq_desc指针无映射时返回 NULLirq_find_mapping()返回给定 domain hwirq 对应的 Linux IRQ 号无映射时返回 0generic_handle_domain_irq()直接处理由 domain hwirq 描述的中断RCU 注意事项irq_domain 查找必须在与 RCU 读侧临界区兼容的上下文中进行文档原话irq_domain lookups must happen in contexts that are compatible with an RCU read-side critical section。从 kernel/irq/irqdomain.c 的__irq_resolve_mapping()实现可以印证查找过程被rcu_read_lock()/rcu_read_unlock()包裹先在revmap_size范围内线性查表rcu_dereference(domain-revmap[hwirq])超出范围则回退到 radix treeradix_tree_lookup(domain-revmap_tree, hwirq)。反向获取 hwirq如果驱动已经持有 Linux IRQ 号或irq_data指针并需要知道对应的 hwirq例如在 irq_chip 回调中可以直接读取irq_data.hwirq成员——这是文档明确推荐的做法。三、四类反向映射机制选型与取舍从 hwirq 到 Linux IRQ 的反向映射共有四种机制每种对应不同的分配函数。选型完全取决于使用场景下面逐一详解。3.1 Linear线性映射绝大多数驱动的首选struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode, unsigned int size, const struct irq_domain_ops *ops, void *host_data);工作原理维护一张以 hwirq 为索引的定长表即struct irq_domain尾部的revmap[]柔性数组。当某个 hwirq 被映射时为其分配 irq_desc并把 Linux IRQ 号存入表中对应槽位。适用场景与优劣势文档明确给出适合 hwirq 最大值固定且相对较小约 256的场景优点查找 Linux IRQ 号的时间恒定O(1)且只为实际使用的中断分配 irq_desc缺点线性表的大小必须达到最大可能的 hwirq 号即表空间与 hwirq 上限成正比。结论文档原话The majority of drivers should use the Linear map.——大多数驱动应当使用 Linear 映射。3.2 Tree基数树映射应对超大 hwirq 号struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode, unsigned int size, const struct irq_domain_ops *ops, void *host_data);工作原理irq_domain 维护一棵从 hwirq 到 Linux IRQ 的radix tree即domain-revmap_tree初始化见__irq_domain_create()中的INIT_RADIX_TREE。映射某个 hwirq 时分配 irq_desc并以 hwirq 为键插入基数树。适用场景与优劣势当 hwirq 号可能非常大时Tree 是很好的选择因为它无需分配与最大 hwirq 号等大的表缺点hwirq 到 Linux IRQ 的查找耗时取决于表中条目数量非恒定时间。结论文档原话Very few drivers should need this mapping.——很少驱动需要这种映射。3.3 No Map无映射硬件可编程的场景已受限struct irq_domain *irq_domain_create_nomap(struct fwnode_handle *fwnode, unsigned int hwirq_max, const struct irq_domain_ops *ops, void *host_data);工作原理当hwirq 号在硬件中可编程时最佳做法是直接把 Linux IRQ 号编程进硬件本身从而完全不需要映射。调用irq_create_direct_mapping()会分配一个 Linux IRQ 号并调用.map()回调驱动在其中把 Linux IRQ 号写入硬件。使用限制大多数驱动无法使用这种映射。文档明确指出Most drivers cannot use this mapping, and it is now gated on theCONFIG_IRQ_DOMAIN_NOMAPoption. Please refrain from introducing new users of this API.即该功能已由CONFIG_IRQ_DOMAIN_NOMAP配置项门控请勿为这个 API 引入新的使用者。从源码__irq_domain_create()的校验逻辑也可以看到direct_max对应 No Map 域的 hwirq 上限只有在启用CONFIG_IRQ_DOMAIN_NOMAP时才被接受否则直接返回-EINVAL。3.4 Legacy遗留映射固定 IRQ 号的特殊场景已弃用struct irq_domain *irq_domain_create_simple(struct fwnode_handle *fwnode, unsigned int size, unsigned int first_irq, const struct irq_domain_ops *ops, void *host_data); struct irq_domain *irq_domain_create_legacy(struct fwnode_handle *fwnode, unsigned int size, unsigned int first_irq, irq_hw_number_t first_hwirq, const struct irq_domain_ops *ops, void *host_data);适用场景针对那些已经为 hwirq 分配好一段 irq_desc 区间、无法立即改用 Linear 映射的驱动。典型例子许多嵌入式系统板级支持文件board support files使用一组#define定义 IRQ 号并传给struct device注册流程此时 Linux IRQ 号无法动态分配只能使用 Legacy 映射。工作原理Legacy 映射假定控制器已经分配了一段连续的 IRQ 号区间Linux IRQ 号 hwirq 固定偏移反之亦然。从源码irq_domain_create_legacy()可以看到其内部把size设为first_hwirq size、hwirq_base设为first_hwirq、virq_base设为first_irq并在实例化时以force_associate true触发irq_domain_associate_many()一次性关联整段 hwirq 与 virqkernel/irq/irqdomain.c。缺点要求中断控制器自行管理 IRQ 分配并且即使某个 hwirq 未被使用也必须为其分配 irq_desc。弃用声明与替代方案文档原话As the name implies, the*_legacy()functions are deprecated and only exist to ease the support of ancient platforms. No new users should be added. Same goes for the*_simple()functions when their use results in the legacy behaviour.Legacy 映射只应在必须支持固定 IRQ 映射时使用。例如 ISA 控制器用 Legacy 映射把 Linux IRQ 0-15 固定下来使既有 ISA 驱动获得正确的 IRQ 号。大多数 Legacy 使用者应改用irq_domain_create_simple()它只有在系统提供了 IRQ 区间时才使用 legacy domain否则回退为 linear domain。语义要点指定了 IRQ 区间 → 为区间即时on-the-fly分配描述符未指定区间 → 回退到irq_domain_create_linear()不分配任何 IRQ 描述符。simple域的典型使用场景某个 irqchip 提供方需要同时支持动态与静态 IRQ 分配。使用陷阱文档重点强调为了避免落入使用 linear domain 却没有分配描述符的困境使用 simple domain 的驱动必须在任何irq_find_mapping()之前调用irq_create_mapping()——因为irq_find_mapping()在静态 IRQ 分配场景下确实能正常工作从而掩盖问题。3.5 四类映射快速对比映射类型创建函数数据结构适用场景优点缺点状态Linearirq_domain_create_linear()定长数组revmap[]hwirq 上限固定且较小256查找 O(1)只为在用 IRQ 分配 desc表大小与最大 hwirq 成正比推荐绝大多数驱动使用Treeirq_domain_create_tree()radix treerevmap_treehwirq 可能非常大无需超大表查找耗时随条目数增长很少驱动需要No Mapirq_domain_create_nomap()无直接编程硬件hwirq 在硬件中可编程无需映射仅少数硬件可用由CONFIG_IRQ_DOMAIN_NOMAP门控禁止新增使用者Legacyirq_domain_create_simple()/irq_domain_create_legacy()固定偏移 预分配 desc 区间必须固定 IRQ 号如 ISA 0-15、板级 #define兼容固定分配需管理分配未用 hwirq 也占 desc已弃用仅服务古老平台新代码用 simple 回退 linear四、Hierarchy IRQ Domain让软件架构匹配硬件拓扑4.1 问题多级中断控制器在某些架构中一个设备的中断从产生到送达 CPU 会经过多个中断控制器。文档以 x86 平台的典型中断投递路径为例Device -- IOAPIC - Interrupt remapping Controller - Local APIC - CPU这条路径涉及三个中断控制器IOAPIC 控制器Interrupt remapping中断重映射控制器Local APIC 控制器4.2 解决方案为每个控制器建一个 irq_domain 并组织成层级为了支撑这样的硬件拓扑并让软件架构与硬件架构匹配内核为每个中断控制器构建一个irq_domain数据结构并把它们组织成层级hierarchy。建层规则离设备最近的 irq_domain 是子child离 CPU 最近的 irq_domain 是父parent。于是上例对应的层级结构为CPU Vector irq_domain (root irq_domain管理 CPU vectors) ^ | Interrupt Remapping irq_domain (管理 irq_remapping 条目) ^ | IOAPIC irq_domain (管理 IOAPIC delivery 条目/引脚)4.3 四大核心接口使用 hierarchy irq_domain 的四个主要接口均声明于 include/linux/irqdomain.hirq_domain_alloc_irqs()分配 IRQ 描述符以及投递这些中断所需的中断控制器相关资源irq_domain_free_irqs()释放上述 IRQ 描述符与中断控制器相关资源irq_domain_activate_irq()激活中断控制器硬件以投递中断irq_domain_deactivate_irq()去激活中断控制器硬件以停止投递中断。4.4 支持层级域的三个前提struct irq_domain中的parent字段用于维护 irq_domain 层级信息从 include/linux/irqdomain.h 可见其受CONFIG_IRQ_DOMAIN_HIERARCHY保护struct irq_data中的parent_data字段用于构建与层级 irq_domains 匹配的层级 irq_data。irq_data 用来存储 irq_domain 指针与硬件中断号struct irq_domain_ops中的alloc()、free()及其他回调支撑层级域的各类操作。准备好层级 irq_domain 与层级 irq_data 后为每个中断控制器构建一个 irq_domain为每个与某 IRQ 关联的 irq_domain 分配一个 irq_data。从源码__irq_domain_instantiate()kernel/irq/irqdomain.c可以看到层级链接逻辑若info-parent存在则domain-root info-parent-root; domain-parent info-parent;——子域复用父域的锁避免死锁。4.5 中断控制器驱动接入层级域的四步清单要让一个中断控制器驱动支持 hierarchy irq_domain需要实现irq_domain_ops.alloc()与irq_domain_ops.free()必选可选实现irq_domain_ops.activate()与irq_domain_ops.deactivate()可选实现一个irq_chip来管理中断控制器硬件无需实现irq_domain_ops.map()与irq_domain_ops.unmap()——它们在层级域中不被使用。4.6 适用架构绝非 x86 专属文档特别强调the hierarchy irq_domain is in no way x86-specific, and is heavily used to support other architectures, such as ARM, ARM64 etc.即 hierarchy irq_domain 绝非 x86 专属而是被大量用于支持其他架构如 ARM、ARM64 等——ARM 常见的 GIC 级联GIC 分配器 CPU 接口、以及 GICv3 的 ITS 等正是这一模型的实际应用。4.7 Stacked irq_chip更进一步的分层层级域之上可以再进一步支持堆叠stacked / hierarchyirq_chip即层级上每个 irq_data 都关联一个 irq_chip。子 irq_chip 可以实现某个必需动作也可以与父 irq_chip 协作完成。带来的收益文档原话With stacked irq_chip, interrupt controller driver only needs to deal with the hardware managed by itself and may ask for services from its parent irq_chip when needed. So we could achieve a much cleaner software architecture.即中断控制器驱动只需处理由自身管理的硬件需要时可向父 irq_chip 请求服务从而获得更干净的软件架构。五、调试CONFIG_GENERIC_IRQ_DEBUGFSIRQ 子系统的大部分内部结构都可以通过打开CONFIG_GENERIC_IRQ_DEBUGFS在 debugfs 中暴露出来。从源码可以看到 debugfs 支持贯穿整个 domain 生命周期domain 创建时__irq_domain_publish()调用debugfs_add_domain_dir(domain)kernel/irq/irqdomain.cdomain 移除时irq_domain_remove()调用debugfs_remove_domain_dir(domain)kernel/irq/irqdomain.cirq_domain_update_bus_token()在 bus token 变化时会重建 debugfs 目录kernel/irq/irqdomain.c驱动还可通过irq_domain_ops.debug_show()回调受同一 Kconfig 保护输出自定义的调试信息include/linux/irqdomain.h。配合/proc/interrupts与/sys/kernel/debug/irq/domains目录开发者可以直观地检查每个 domain 的名称、fwnode、bus token、映射数量与各 hwirq 的关联情况。注意 fwnode 路径中的/会被替换为:见 kernel/irq/irqdomain.c这是为了满足 debugfs 的命名限制。六、结构体与公共 API 参考本文档对应的核心头文件与实现文件如下其中包含自动生成的完整内核文档kernel-doc 注释公共结构与导出 APIinclude/linux/irqdomain.h 与 kernel/irq/irqdomain.ckernel-doc标记:export:部分即所有EXPORT_SYMBOL_GPL的导出函数如irq_domain_create_simple()、irq_domain_create_legacy()、irq_domain_remove()、irq_find_matching_fwspec()、irq_domain_xlate_onecell()等内部函数kernel/irq/irqdomain.ckernel-doc标记:internal:部分如__irq_domain_create()、__irq_domain_instantiate()、__irq_domain_publish()、__irq_resolve_mapping()等内部实现。这些 API 中值得额外关注的一组通用翻译助手均位于 kernel/irq/irqdomain.cirq_domain_xlate_onecell()单 cell 绑定cell 值直接映射为 hwirqirq_domain_xlate_twocell()双 cell 绑定映射 hwirq 与 Linux IRQ 标志irq_domain_xlate_twothreecell()两或三 cell 绑定irq_domain_xlate_onetwocell()一或两 cell 绑定。它们为 Device Tree 中断描述到 hwirq 的翻译提供了开箱即用的默认实现是驱动xlate/translate回调最常见的落点。结语irq_domain是理解现代 Linux 中断子系统的关键枢纽它以 hwirq ↔ Linux IRQ 的双向映射解耦了硬件与软件的数字空间以 Linear / Tree / No Map / Legacy 四类机制覆盖从常规 SoC 中断控制器到古老固定编号平台的各类需求并通过 Hierarchy IRQ Domain 与 Stacked irq_chip 让驱动代码与多级中断硬件拓扑一一对应、各司其职。编写中断控制器驱动时请遵循文档给出的明确指引绝大多数场景选择irq_domain_create_linear()需要超大 hwirq 号时考虑 Tree 映射远离CONFIG_IRQ_DOMAIN_NOMAP与 legacy 系列 API使用 simple domain 时务必先irq_create_mapping()再irq_find_mapping()。结合 Documentation/core-api/irq/irq-domain.rst、include/linux/irqdomain.h 与 kernel/irq/irqdomain.c 三份资料即可完整掌握这一核心基础设施。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考