irq_domain 中断号映射库¶
Linux 内核的当前设计使用一个单一的大数字空间,其中每个单独的 IRQ 源都被分配一个不同的数字。当只有一个中断控制器时,这很简单,但在具有多个中断控制器的系统中,内核必须确保每个中断控制器都被分配不重叠的 Linux IRQ 号。
注册为唯一 irqchip 的中断控制器的数量呈上升趋势:例如,不同类型的子驱动程序(如 GPIO 控制器)通过将其中断处理程序建模为 irqchip 来避免重新实现与 IRQ 核心系统相同的回调机制,实际上是级联中断控制器。
在这里,中断号失去了与硬件中断号的任何对应关系:过去,IRQ 号可以选择与根中断控制器(即实际向 CPU 发出中断线的组件)的硬件 IRQ 线相匹配,而现在这个数字只是一个数字。
因此,我们需要一种机制来分离控制器本地中断号(称为硬件 irq)和 Linux IRQ 号。
irq_alloc_desc*() 和 irq_free_desc*() API 提供 irq 号的分配,但它们不提供将控制器本地 IRQ (hwirq) 号反向映射到 Linux IRQ 号空间的任何支持。
irq_domain 库在 irq_alloc_desc*() API 之上添加了 hwirq 和 IRQ 号之间的映射。与中断控制器驱动程序开放编码其自己的反向映射方案相比,首选使用 irq_domain 来管理映射。
irq_domain 还实现了从抽象的 irq_fwspec 结构到 hwirq 号的转换(目前是设备树和 ACPI GSI),并且可以轻松扩展以支持其他 IRQ 拓扑数据源。
irq_domain 的用法¶
中断控制器驱动程序通过调用 irq_domain_add_*() 或 irq_domain_create_*() 函数之一来创建和注册 irq_domain(每种映射方法都有不同的分配器函数,稍后会详细介绍)。该函数将在成功时返回指向 irq_domain 的指针。调用者必须向分配器函数提供 irq_domain_ops 结构。
在大多数情况下,irq_domain 将开始为空,没有任何 hwirq 和 IRQ 号之间的映射。通过调用 irq_create_mapping() 将映射添加到 irq_domain,该函数接受 irq_domain 和 hwirq 号作为参数。如果 hwirq 的映射尚不存在,则它将分配一个新的 Linux irq_desc,将其与 hwirq 关联,并调用 .map() 回调,以便驱动程序可以执行任何所需的硬件设置。
建立映射后,可以通过多种方法检索或使用它
irq_resolve_mapping() 返回给定域和 hwirq 号的 irq_desc 结构的指针,如果没有映射则返回 NULL。
irq_find_mapping()
返回给定域和 hwirq 号的 Linux IRQ 号,如果没有映射则返回 0irq_linear_revmap() 现在与
irq_find_mapping()
相同,并且已弃用generic_handle_domain_irq()
处理由域和 hwirq 号描述的中断
请注意,irq 域查找必须在与 RCU 读取端临界区兼容的上下文中进行。
在任何调用 irq_find_mapping()
之前,必须至少调用一次 irq_create_mapping() 函数,否则不会分配描述符。
如果驱动程序具有 Linux IRQ 号或 irq_data 指针,并且需要知道关联的 hwirq 号(例如在 irq_chip 回调中),则可以直接从 irq_data->hwirq 获取。
irq_domain 映射的类型¶
有几种可用于从 hwirq 到 Linux irq 的反向映射的机制,每种机制都使用不同的分配函数。应使用哪种反向映射类型取决于用例。下面描述每种反向映射类型
线性¶
irq_domain_add_linear()
irq_domain_create_linear()
线性反向映射维护一个由 hwirq 号索引的固定大小的表。当映射 hwirq 时,将为 hwirq 分配一个 irq_desc,并将 IRQ 号存储在表中。
当 hwirq 的最大数量是固定的且数量相对较小(约 < 256)时,线性映射是一个不错的选择。此映射的优点是 IRQ 号的查找时间是固定的,并且仅为正在使用的 IRQ 分配 irq_descs。缺点是该表必须与最大可能的 hwirq 号一样大。
irq_domain_add_linear()
和 irq_domain_create_linear() 在功能上是等效的,只是第一个参数不同 - 前者接受一个特定于 Open Firmware 的 'struct device_node',而后者接受一个更通用的抽象 'struct fwnode_handle'。
大多数驱动程序应使用线性映射。
树¶
irq_domain_add_tree()
irq_domain_create_tree()
irq_domain 维护一个从 hwirq 号到 Linux IRQ 的基数树映射。当映射 hwirq 时,分配一个 irq_desc,并且 hwirq 用作基数树的查找键。
如果 hwirq 号可能非常大,则树映射是一个不错的选择,因为它不需要分配一个与最大 hwirq 号一样大的表。缺点是 hwirq 到 IRQ 号的查找取决于表中存在多少条目。
irq_domain_add_tree() 和 irq_domain_create_tree() 在功能上是等效的,只是第一个参数不同 - 前者接受一个特定于 Open Firmware 的 'struct device_node',而后者接受一个更通用的抽象 'struct fwnode_handle'。
很少有驱动程序需要这种映射。
无映射¶
irq_domain_add_nomap()
当硬件中的 hwirq 号可编程时,应使用无映射映射。在这种情况下,最好将 Linux IRQ 号编程到硬件本身中,这样就不需要映射。调用 irq_create_direct_mapping() 将分配一个 Linux IRQ 号并调用 .map() 回调,以便驱动程序可以将 Linux IRQ 号编程到硬件中。
大多数驱动程序不能使用此映射,并且现在已在 CONFIG_IRQ_DOMAIN_NOMAP 选项上进行门控。请避免引入此 API 的新用户。
旧式¶
irq_domain_add_simple()
irq_domain_add_legacy()
irq_domain_create_simple()
irq_domain_create_legacy()
旧式映射是驱动程序的特殊情况,这些驱动程序已经为 hwirq 分配了一系列 irq_descs。当无法立即将驱动程序转换为使用线性映射时,将使用它。例如,许多嵌入式系统板支持文件对传递给 struct device
注册的 IRQ 号使用一组 #define。在这种情况下,无法动态分配 Linux IRQ 号,应使用旧式映射。
顾名思义,*_legacy() 函数已弃用,仅用于简化对旧平台的支援。不应添加新用户。当他们的使用导致旧式行为时,*_simple() 函数也是如此。
旧式映射假设已为控制器分配了连续的 IRQ 号范围,并且可以通过将固定偏移量添加到 hwirq 号来计算 IRQ 号,反之亦然。缺点是它需要中断控制器来管理 IRQ 分配,并且它需要为每个 hwirq 分配一个 irq_desc,即使它未使用也是如此。
仅当必须支持固定 IRQ 映射时,才应使用旧式映射。例如,ISA 控制器将使用旧式映射来映射 Linux IRQ 0-15,以便现有的 ISA 驱动程序获得正确的 IRQ 号。
大多数旧式映射的用户应使用 irq_domain_add_simple() 或 irq_domain_create_simple(),如果系统提供 IRQ 范围,则将使用旧式域,否则将使用线性域映射。此调用的语义是,如果指定了 IRQ 范围,则将为它动态分配描述符,如果未指定范围,则将回退到 irq_domain_add_linear()
或 irq_domain_create_linear(),这意味着将不分配 irq 描述符。
简单域的典型用例是 irqchip 提供程序同时支持动态和静态 IRQ 分配。
为了避免在使用线性域时,没有分配描述符的情况发生,非常重要的是确保驱动程序在使用简单域之前调用 irq_create_mapping(),然后再调用 irq_find_mapping()
,因为后者实际上适用于静态 IRQ 分配的情况。
irq_domain_add_simple() 和 irq_domain_create_simple() 以及 irq_domain_add_legacy() 和 irq_domain_create_legacy() 在功能上是等效的,除了第一个参数不同 - 前者接受 Open Firmware 特定的 ‘struct device_node’,而后者接受更通用的抽象 ‘struct fwnode_handle’。
分层 IRQ 域¶
在某些架构上,可能涉及多个中断控制器,将中断从设备传递到目标 CPU。让我们看看 x86 平台上典型的中断传递路径。
Device --> IOAPIC -> Interrupt remapping Controller -> Local APIC -> CPU
涉及三个中断控制器
IOAPIC 控制器
中断重映射控制器
本地 APIC 控制器
为了支持这种硬件拓扑并使软件架构与硬件架构匹配,为每个中断控制器构建一个 irq_domain 数据结构,并将这些 irq_domain 组织成层次结构。构建 irq_domain 层次结构时,靠近设备的 irq_domain 是子域,靠近 CPU 的 irq_domain 是父域。因此,对于上面的示例,将构建如下的层次结构:
CPU Vector irq_domain (root irq_domain to manage CPU vectors)
^
|
Interrupt Remapping irq_domain (manage irq_remapping entries)
^
|
IOAPIC irq_domain (manage IOAPIC delivery entries/pins)
使用分层 irq_domain 有四个主要接口
irq_domain_alloc_irqs():分配 IRQ 描述符和中断控制器相关资源,以传递这些中断。
irq_domain_free_irqs():释放 IRQ 描述符和与这些中断关联的中断控制器相关资源。
irq_domain_activate_irq():激活中断控制器硬件以传递中断。
irq_domain_deactivate_irq():停用中断控制器硬件以停止传递中断。
需要进行以下更改以支持分层 irq_domain
在
struct irq_domain
中添加了一个新的字段 'parent';它用于维护 irq_domain 层次结构信息。在
struct irq_data
中添加了一个新的字段 'parent_data';它用于构建分层 irq_data 以匹配分层 irq_domain。 irq_data 用于存储 irq_domain 指针和硬件 irq 号。在
struct irq_domain_ops
中添加了新的回调,以支持分层 irq_domain 操作。
在支持分层 irq_domain 和分层 irq_data 的情况下,为每个中断控制器构建一个 irq_domain 结构,并为每个与 IRQ 关联的 irq_domain 分配一个 irq_data 结构。现在,我们可以更进一步支持堆叠(分层)的 irq_chip。也就是说,一个 irq_chip 与层次结构中的每个 irq_data 相关联。子 irq_chip 可以自行实现所需的操作,也可以通过与其父 irq_chip 协作来实现。
通过堆叠的 irq_chip,中断控制器驱动程序只需要处理其自身管理的硬件,并且可以在需要时从其父 irq_chip 请求服务。因此,我们可以实现更简洁的软件架构。
对于支持分层 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,它们在分层 irq_domain 中未使用。
分层 irq_domain 绝不是 x86 特有的,它被大量用于支持其他架构,例如 ARM、ARM64 等。
调试¶
通过打开 CONFIG_GENERIC_IRQ_DEBUGFS,IRQ 子系统的大部分内部结构都会在 debugfs 中公开。