英语

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 号,如果没有映射则返回 0

  • irq_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

涉及三个中断控制器

  1. IOAPIC 控制器

  2. 中断重映射控制器

  3. 本地 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 有四个主要接口

  1. irq_domain_alloc_irqs():分配 IRQ 描述符和中断控制器相关资源,以传递这些中断。

  2. irq_domain_free_irqs():释放 IRQ 描述符和与这些中断关联的中断控制器相关资源。

  3. irq_domain_activate_irq():激活中断控制器硬件以传递中断。

  4. irq_domain_deactivate_irq():停用中断控制器硬件以停止传递中断。

需要进行以下更改以支持分层 irq_domain

  1. struct irq_domain 中添加了一个新的字段 'parent';它用于维护 irq_domain 层次结构信息。

  2. struct irq_data 中添加了一个新的字段 'parent_data';它用于构建分层 irq_data 以匹配分层 irq_domain。 irq_data 用于存储 irq_domain 指针和硬件 irq 号。

  3. 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 的中断控制器驱动程序,它需要

  1. 实现 irq_domain_ops.alloc 和 irq_domain_ops.free

  2. (可选)实现 irq_domain_ops.activate 和 irq_domain_ops.deactivate。

  3. (可选)实现一个 irq_chip 来管理中断控制器硬件。

  4. 无需实现 irq_domain_ops.map 和 irq_domain_ops.unmap,它们在分层 irq_domain 中未使用。

分层 irq_domain 绝不是 x86 特有的,它被大量用于支持其他架构,例如 ARM、ARM64 等。

调试

通过打开 CONFIG_GENERIC_IRQ_DEBUGFS,IRQ 子系统的大部分内部结构都会在 debugfs 中公开。