什么是 linux irq 域,为什么需要它们?
Documentation/IRQ-domain.txt 的第一段完美地记录了它,所以我假设你已经知道了。如果不是 - 请询问关于该文档的不清楚之处。下面的文字解释了如何使用 IRQ 域 API 以及它是如何工作的。
GPIO 控制器如何被称为中断控制器?
让我以max732x.c 驱动程序作为参考(driver code)来回答这个问题。它是一个 GPIO 驱动程序,它也充当中断控制器,因此它应该是 IRQ 域 API 工作原理的一个很好的例子。
体力水平
为了完全理解进一步的解释,让我们先来看看 MAX732x 的机制。来自datasheet的应用电路(为我们的例子做了简化):
当P0-P7 引脚电压电平发生变化时,MAX7325 将在INT 引脚产生中断。驱动程序(在 SoC 上运行)可以通过 I2C(SCL/SDA 引脚)读取 P0-P7 引脚的状态,并为每个 P0-P7 引脚生成单独的中断。这就是该驱动程序充当中断控制器的原因。
考虑下一个配置:
“Some device”改变 P4 引脚的电平,诱使 MAX7325 产生中断。来自 MAX7325 的中断连接到 GPIO4 IP 内核(SoC 内部),它使用该 GPIO4 模块的第 29 行来通知 CPU 中断。所以我们可以说 MAX7325 级联到 GPIO4 控制器。 GPIO4也作为中断控制器,级联到GIC中断控制器。
设备树
让我们在设备树中声明上述配置。我们可以使用来自Documentation/devicetree/bindings/gpio/gpio-max732x.txt 的绑定作为参考:
expander: max7325@6d {
compatible = "maxim,max7325";
reg = <0x6d>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gpio4>;
interrupts = <29 IRQ_TYPE_EDGE_FALLING>;
};
属性含义如下:
-
interrupt-controller 属性定义设备产生中断;需要进一步将此节点用作“某些设备”节点中的interrupt-parent。
-
#interrupt-cells 定义interrupts 属性的格式;在我们的例子中是2:1 个单元格用于行号,1 个单元格用于中断类型
-
interrupt-parent 和 interrupts 属性描述中断线连接
假设我们有 MAX7325 的驱动程序和“某些设备”的驱动程序。当然,两者都在 CPU 中运行。在“Some device”驱动程序中,当“Some device”在 MAX7325 的 P4 引脚上改变电平时,我们希望请求中断事件。让我们首先在设备树中声明它:
some_device: some_device@1c {
reg = <0x1c>;
interrupt-parent = <&expander>;
interrupts = <4 IRQ_TYPE_EDGE_RISING>;
};
中断传播
现在我们可以这样做(在“某些设备”驱动程序中):
devm_request_threaded_irq(core->dev, core->gpio_irq, NULL,
some_device_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT,
dev_name(core->dev), core);
而some_device_isr()会在每次MAX7325的P4引脚电平由低变高(上升沿)时被调用。这个怎么运作?如果你看上图,从左到右:
- “Some device”在 MAX7325 的 P4 上改变电平
- MAX7325 改变其 INT 引脚的电平
- GPIO4 模块被配置为捕捉这样的变化,因此它会对 GIC 产生中断
- GIC 通知 CPU
所有这些操作都发生在硬件级别。让我们看看在软件层面发生了什么。它实际上是倒退的(图片上从右到左):
- CPU 现在处于 GIC 中断处理程序的中断上下文中。它从gic_handle_irq() 调用
handle_domain_irq(),而后者又调用generic_handle_irq()。有关详细信息,请参阅Documentation/gpio/driver.txt。现在我们在 SoC 的 GPIO 控制器 IRQ 处理程序中。
- SoC 的 GPIO 驱动程序还调用
generic_handle_irq() 来运行为每个特定引脚设置的处理程序。例如在omap_gpio_irq_handler() 中查看它是如何完成的。现在我们在 MAX7325 IRQ 处理程序中。
- MAX7325 IRQ 处理程序 (here) 调用
handle_nested_irq(),因此连接到 MAX7325 的设备的所有 IRQ 处理程序(在我们的例子中为“某些设备”IRQ 处理程序)将在max732x_irq_handler() 线程中调用
- 最后,“某些设备”驱动程序的 IRQ 处理程序被调用
IRQ 域 API
GIC 驱动程序、GPIO 驱动程序和 MAX7325 驱动程序——它们都使用 IRQ 域 API 将这些驱动程序表示为中断控制器。让我们看看它是如何在MAX732x驱动中完成的。它是在this 提交中添加的。只需阅读 IRQ 域文档并查看此提交,就很容易弄清楚它是如何工作的。该提交中最有趣的部分是这一行(max732x_irq_handler()):
handle_nested_irq(irq_find_mapping(chip->gpio_chip.irqdomain, level));
irq_find_mapping() 将通过硬件 IRQ 号查找 linux IRQ 号(使用 IRQ 域 mapping 功能)。然后将调用handle_nested_irq() 函数,该函数将运行“某些设备”驱动程序的 IRQ 处理程序。
GPIOLIB_IRQCHIP
由于许多 GPIO 驱动程序都以相同的方式使用 IRQ 域,因此决定将该代码提取到 GPIOLIB 框架,更具体地说,提取到 GPIOLIB_IRQCHIP。来自Documentation/gpio/driver.txt:
帮助处理 GPIO irqchips 的设置和管理以及
相关的 irqdomain 和资源分配回调,gpiolib 有
一些可以通过选择GPIOLIB_IRQCHIP Kconfig 来启用的助手
符号:
gpiochip_irqchip_add():将 irqchip 添加到 gpiochip。会过去的
struct gpio_chip* 用于所有 IRQ 回调的芯片,所以回调
需要在其状态容器中嵌入gpio_chip 并获取一个指针
使用container_of() 到容器。
(见Documentation/driver-model/design-patterns.txt)
gpiochip_set_chained_irqchip():为一个
gpio_chip 来自父 IRQ 并将 struct gpio_chip* 作为处理程序传递
数据。 (注意处理程序数据,因为 irqchip 数据很可能被
parent irqchip!)这是针对链式芯片的。这也被使用
如果 NULL 作为处理程序传递,则设置嵌套的 irqchip。
This commit 将 IRQ 域 API 转换为 MAX732x 驱动程序中的 GPIOLIB_IRQCHIP API。
下一个问题
这里有进一步的讨论: