什么是 linux irq 域,为什么需要它们? [英] What are linux irq domains, why are they needed?

查看:26
本文介绍了什么是 linux irq 域,为什么需要它们?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么是 irq 域,我阅读内核文档(

当 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 作为参考:

扩展器:max7325@6d {兼容 = "maxim,max7325";reg = <0x6d>;gpio 控制器;#gpio-cells = <2>;中断控制器;#interrupt-cells = <2>;中断父 = <&gpio4>;中断 = <29 IRQ_TYPE_EDGE_FALLING>;};

属性的含义如下:

  • interrupt-controller 属性定义设备产生中断;进一步需要将此节点用作某些设备"节点中的 interrupt-parent.
  • #interrupt-cells 定义了interrupts 属性的格式;在我们的例子中是 2:1 个单元格用于行号,1 个单元格用于中断类型
  • interrupt-parentinterrupts 属性描述中断线连接

假设我们有 MAX7325 的驱动程序和某些设备"的驱动程序.当然,两者都在 CPU 中运行.在Some device"驱动程序中,当Some device"改变 MAX7325 的 P4 引脚电平时,我们希望请求事件中断.让我们首先在设备树中声明:

some_device: some_device@1c {reg = <0x1c>;中断父 = <&expander>;中断 = <4 IRQ_TYPE_EDGE_RISING>;};

中断传播

现在我们可以做这样的事情(在Some device"驱动程序中):

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引脚电平从低变高(上升沿)时被调用.怎么运行的?从左到右,如果你看上图:

  • 某些设备"更改 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 处理程序(此处)调用handle_nested_irq(),以便在max732x_irq_handler()线程
  • 最后,调用Some device"驱动程序的 IRQ 处理程序

IRQ 域 API

GIC 驱动程序、GPIO 驱动程序和 MAX7325 驱动程序——它们都使用 IRQ 域 API 将这些驱动程序表示为中断控制器.让我们来看看它是如何在 MAX732x 驱动程序中完成的.它被添加到这个 提交.只需阅读 IRQ 域文档并查看此提交,就很容易弄清楚它是如何工作的.该提交中最有趣的部分是这一行(在 max732x_irq_handler() 中):

handle_nested_irq(irq_find_mapping(chip->gpio_chip.irqdomain, level));

irq_find_mapping() 将通过硬件 IRQ 号查找 linux IRQ 号(使用 IRQ 域 ma​​pping 函数).然后 handle_nested_irq() 函数将被调用,该函数将运行Some device"驱动程序的 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.

这个commit 将 IRQ 域 API 转换为 MAX732x 驱动程序中的 GPIOLIB_IRQCHIP API.

接下来的问题

进一步讨论在这里:

What are irq domains, i read kernel documentation (https://www.kernel.org/doc/Documentation/IRQ-domain.txt) they say:

The number of interrupt controllers registered as unique irqchips show a rising tendency: for example subdrivers of different kinds such as GPIO controllers avoid reimplementing identical callback mechanisms as the IRQ core system by modeling their interrupt handlers as irqchips, i.e. in effect cascading interrupt controllers.

How GPIO controller can be called as interrupt controller?

解决方案

What are linux irq domains, why are they needed?

It's documented perfectly in the first paragraph of Documentation/IRQ-domain.txt, so I will assume that you already know it. If no -- please ask what is unclear regarding that documentation. The text below explains how to use IRQ domain API and how it works.

How GPIO controller can be called as interrupt controller?

Let me answer this question using driver as a reference (driver code). It's a GPIO driver and it also acts like interrupt controller, so it should be a good example of how IRQ domain API works.

Physical level

To completely understand further explanation, let's first look into MAX732x mechanics. Application circuit from datasheet (simplified for our example):

When there is a change of voltage level on P0-P7 pins, MAX7325 will generate interrupt on INT pin. The driver (running on SoC) can read the status of P0-P7 pins via I2C (SCL/SDA pins) and generate separate interrupts for each of P0-P7 pins. This is why this driver acts as interrupt controller.

Consider next configuration:

"Some device" changes level on P4 pin, tempting MAX7325 to generate interrupt. Interrupt from MAX7325 is connected to GPIO4 IP-core (inside of SoC), and it uses line #29 of that GPIO4 module to notify CPU about interrupt. So we can say that MAX7325 is cascaded to GPIO4 controller. GPIO4 also acts as interrupt controller, and it's cascaded to GIC interrupt controller.

Device tree

Let's declare above configuration in device tree. We can use bindings from Documentation/devicetree/bindings/gpio/gpio-max732x.txt as reference:

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>;
};

The meaning of properties is as follows:

  • interrupt-controller property defines that device generates interrupts; it will be needed further to use this node as interrupt-parent in "Some device" node.
  • #interrupt-cells defines format of interrupts property; in our case it's 2: 1 cell for line number and 1 cell for interrupt type
  • interrupt-parent and interrupts properties are describing interrupt line connection

Let's say we have driver for MAX7325 and driver for "Some device". Both are running in CPU, of course. In "Some device" driver we want to request interrupt for event when "Some device" changes level on P4 pin of MAX7325. Let's first declare this in device tree:

some_device: some_device@1c {
    reg = <0x1c>;
    interrupt-parent = <&expander>;
    interrupts = <4 IRQ_TYPE_EDGE_RISING>;
};

Interrupt propagation

Now we can do something like this (in "Some device" driver):

devm_request_threaded_irq(core->dev, core->gpio_irq, NULL,
        some_device_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT,
        dev_name(core->dev), core);

And some_device_isr() will be called each time when level on P4 pin of MAX7325 goes from low to high (rising edge). How it works? From left to the right, if you look to the picture above:

  • "Some device" changes level on P4 of MAX7325
  • MAX7325 changes level on its INT pin
  • GPIO4 module is configured to catch such a change, so it's generates interrupt to GIC
  • GIC notifies CPU

All those actions are happening on hardware level. Let's see what's happening on software level. It actually goes backwards (from right to the left on the picture):

  • CPU now is in interrupt context in GIC interrupt handler. From gic_handle_irq() it calls handle_domain_irq(), which in turn calls generic_handle_irq(). See Documentation/gpio/driver.txt for details. Now we are in SoC's GPIO controller IRQ handler.
  • SoC's GPIO driver also calls generic_handle_irq() to run handler, which is set for each particular pin. See for example how it's done in omap_gpio_irq_handler(). Now we are in MAX7325 IRQ handler.
  • MAX7325 IRQ handler (here) calls handle_nested_irq(), so that all IRQ handlers of devices connected to MAX7325 ("Some device" IRQ handler, in our case) will be called in max732x_irq_handler() thread
  • finally, IRQ handler of "Some device" driver is called

IRQ domain API

GIC driver, GPIO driver and MAX7325 driver -- they all are using IRQ domain API to represent those drivers as interrupt controllers. Let's take a look how it's done in MAX732x driver. It was added in this commit. It's easy to figure out how it works just by reading IRQ domain documentation and looking to this commit. The most interesting part of that commit is this line (in max732x_irq_handler()):

handle_nested_irq(irq_find_mapping(chip->gpio_chip.irqdomain, level));

irq_find_mapping() will find linux IRQ number by hardware IRQ number (using IRQ domain mapping function). Then handle_nested_irq() function will be called, which will run IRQ handler of "Some device" driver.

GPIOLIB_IRQCHIP

Since many GPIO drivers are using IRQ domain in the same way, it was decided to extract that code to GPIOLIB framework, more specifically to GPIOLIB_IRQCHIP. From Documentation/gpio/driver.txt:

To help out in handling the set-up and management of GPIO irqchips and the associated irqdomain and resource allocation callbacks, the gpiolib has some helpers that can be enabled by selecting the GPIOLIB_IRQCHIP Kconfig symbol:

  • gpiochip_irqchip_add(): adds an irqchip to a gpiochip. It will pass the struct gpio_chip* for the chip to all IRQ callbacks, so the callbacks need to embed the gpio_chip in its state container and obtain a pointer to the container using container_of(). (See Documentation/driver-model/design-patterns.txt)

  • gpiochip_set_chained_irqchip(): sets up a chained irq handler for a gpio_chip from a parent IRQ and passes the struct gpio_chip* as handler data. (Notice handler data, since the irqchip data is likely used by the parent irqchip!) This is for the chained type of chip. This is also used to set up a nested irqchip if NULL is passed as handler.

This commit converts IRQ domain API to GPIOLIB_IRQCHIP API in MAX732x driver.

Next questions

Further discussion is here:

这篇关于什么是 linux irq 域,为什么需要它们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆