泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux实验盘,即刻上手内核与嵌入式开发
请稍侯

LWN 184750: 一个新的通用中断(IRQ)框架

Wang Chen 创作于 2018/04/23

原文:A new generic IRQ layer 原创:By corbet @ May 23, 2006 翻译:By unicornx 校对:By guojian-at-wowo

The Linux kernel has a generic layer for the handling of hardware interrupts, hidden behind a standard API. There’s only one problem: not all architectures use this layer. In particular, ARM is a holdout. It seems that interrupt handling in the ARM world is a complicated, subarchitecture-specific business which does not fit into the current “generic” code at all, so ARM sticks with its own code - even though there is a fair amount of overlap with code found in the generic subsystem. But, even for the architectures which are able to use it, the current IRQ subsystem has shortcomings which are becoming increasingly apparent.

虽然 Linux 内核中已经提供了一个处理硬件中断的通用层,并封装了一个标准的接口。但问题是:并不是所有的体系结构都使用它,特别是 ARM。在 ARM 体系架构中,中断处理尤其复杂,各个子体系架构中的处理逻辑和当前的 “通用” 框架都不匹配,因此 ARM 体系架构只能使用自己的处理逻辑 - 尽管其代码中有相当部分和通用子系统中的代码存在重复。另一方面,目前的中断(IRQ)子系统自身也不够完善,在使用过程中不断暴露出各种问题。

An attempt to change the situation can be seen in the genirq patch set by Thomas Gleixner and Ingo Molnar. These patches attempt to take lessons learned about optimal interrupt handling on all architectures, mix in the quirks found in the fifty (yes, fifty) ARM subarchitectures, and create a new IRQ subsystem which is truly generic, and more powerful as well. It is a big patch set which reworks a great deal of crucially important low-level code. Expect some interesting discussion before any eventual mainline merge.

Thomas Gleixner 和 Ingo Molnar 所提交的 通用中断补丁集(genirq patch set)(以下简称 genirq 补丁)力图对现有状况进行改进。这个补丁吸取了所有体系架构中有关中断处理方面的经验教训,特别是考虑了五十个(没错,有五十个)ARM 的子体系架构中的特殊处理情况,并在此基础上创建了一个全新的,真正通用并且功能更强大的 IRQ 子系统。这是一个很大的补丁集,它重写了大量的关键底层代码。在最终被合入主线之前,欢迎大家对它提出宝贵意见。

After some cleanup work, the patch gets serious with the creation of a new irq_chip structure. This structure is based on the old hw_interrupt_type structure, but it includes a rather longer list of low-level operations. The things for which the kernel can now request a specific interrupt controller include:

  • startup(): enable the interrupt and generally get the controller ready to handle it.
  • shutdown(): completely shut down the interrupt.
  • enable(): enable the interrupt.
  • disable(): disable the interrupt.
  • ack(): inform the controller that the CPU has begun processing the interrupt.
  • end(): inform the controller that interrupt processing is done.
  • mask(): mask a specific interrupt, blocking its delivery.
  • mask_ack(): a combination of mask() and ack() which can be optimized on some platforms.
  • unmask(): unmask an interrupt.
  • set_affinity(): bind an interrupt to a specific CPU.
  • retrigger(): re-create and re-deliver an interrupt.
  • set_type(): set the flow type (described below) of the interrupt.
  • set_wake(): enable or disable wake-on-interrupt behavior.

Many of these methods existed previously, but the mask(), mask_ack(), unmask(), set_type(), and set_wake() functions are new. With this set of functions, kernel code can manage interrupt controller chips in a fine-grained manner.

经过仔细的清理工作后,该补丁基于旧的 hw_interrupt_type 结构体类型创建了一个新的结构体类型 irq_chip。该结构体类型定义了一个特定的中断控制器所要实现的接口,包括一系列底层回调函数,如下所示:

  • startup()):启用某个中断,并让控制器准备好处理该中断。
  • shutdown():完全关闭中断。
  • enable():启用中断。
  • disable():禁用中断。
  • ack():CPU 通知控制器其已经开始处理该中断。
  • end():CPU 通知控制器某个中断处理已完成。
  • mask():屏蔽特定的中断,阻止其传递。
  • mask_ack()mask()ack() 的组合形式,在某些平台上可能可以优化。
  • unmask():去掉对某个中断的屏蔽。
  • set_affinity():将中断绑定到特定的 CPU。
  • retrigger():重新触发并重新传递某个中断。
  • set_type():设置中断的处理流程(flow)类型(见下文所述)。
  • set_wake():启用或禁用中断唤醒(wake-on-interrupt)行为。

除了新增的 mask()mask_ack()unmask()set_type(),和 set_wake() 这些函数,其他函数以前就存在。使用这组函数,内核代码可以以更加精细地管理中断控制器芯片。

Moving up a level, the existing irq_desc structure, which holds all of the kernel’s information about any specific interrupt, now has a pointer to an associated irq_chip structure. It also has a new method, handle_irq(), pointing to the function which actually handles this interrupt. That, perhaps, is the most fundamental change from the existing system, which uses a single handler function (__do_IRQ()) for all interrupts. It is a recognition of the fact that not all interrupts are equal, so there is little to gain by trying to deal with them all in a single, big function.

结构体类型 irq_desc 位于现有通用中断子系统的上层(译者注:指相对于面向底层中断控制器的 irq_chip),它包含内核中与一个特定中断相关的所有信息。新的补丁在该结构体类型中新增了一个指针成员,指向与该中断相关的 irq_chip 对象。除此之外还新增了一个函数指针成员,handle_irq(),指向实际处理这个中断的函数。这也许是新补丁相对于原有子系统最大的不同之处,原来的做法是对所有中断使用同一个处理函数(__do_IRQ())。新做法允许通过函数指针调用不同的处理函数,这么做的原因是考虑到,并非所有的中断处理过程都是相同的,所以试图在一个单一的大函数中处理所有类型的中断并不合适。

The biggest difference between interrupts is what is called the “flow type” - a combination of how the interrupt is signaled and how the system processes it. The genirq patches define these flow types:

  • Level-triggered interrupts are active as long as the device asserts its IRQ line. These interrupts must be masked while being processed, and can only be unmasked after the device has stopped asserting the interrupt.
  • Edge-triggered interrupts are signaled by a change in the interrupt line - from low voltage to high, from high to low, or both. These interrupts do not necessarily have to be masked while being processed, but, if they are not masked, more interrupts can arrive before the first has been handled. So the kernel must track “pending” interrupts, and the interrupt handler must loop until all interrupts have been dealt with.
  • “Simple” interrupts do not require any special control, and can be processed directly.
  • Per-CPU interrupts are bound to a single CPU. They are much like simple interrupts, but even simpler: since the handler will only run on one CPU, there is no need for locking.

中断之间最大的区别就是所谓的 “处理流程类型”(”flow type”) - 它取决于中断信号的触发形式和以及对应的系统处理它的方式。genirq 补丁定义了以下类型的处理流程:

  • 电平触发式(Level-triggered)中断:只要设备将中断线(IRQ line)保持在预设的触发电平,则中断就一直处在激活状态。处理器处理该类中断时必须屏蔽(mask)中断线,直到外设的挂起(pending)状态被清除后,屏蔽才可以被撤消(unmask)。
  • 边沿触发式(Edge-triggered)中断:设备通过在中断线上产生电平跳变(电压变化包括从低到高,从高到低或兼有两者)的方式来向处理器报告中断发生(译者注:通过中断控制器)。处理器在处理该类中断时一般不屏蔽(mask)中断线(译者注:以防屏蔽期间遗漏新产生的中断),但同时带来的另一个问题是:在处理第一个中断的过程中,该中断线上可能会产生更多的中断。因此需要在完成对第一个中断的处理之后,继续检查是否存在未决(pending)的中断需要处理,而且此步骤需要循环执行,直到所有的“未决”中断都被处理掉。
  • “简单”(”Simple”)类型的中断不需要任何特殊控制,并且可以直接处理。
  • Per-CPU 中断绑定到单个的 CPU。它们很像“简单”类型的中断,但处理更为简单:因为对该类型中断的处理程序只能在同一个 CPU 上运行,因此不需要考虑互斥加锁的问题。

The current IRQ code attempts to handle all of the above cases in a single, large routine. The new code, instead, creates a number of flow-specific handler functions, then sets the appropriate one as the handle_irq() method in the interrupt descriptor. The result is code which can be optimized for specific needs, and shorter code paths in the interrupt system as a whole. If a particular hardware platform has quirks which are not addressed by the current handlers, creating a new one is a relatively straightforward task.

当前的中断处理代码试图在一个很大的函数中处理上述所有情况。与之相反,新的补丁代码针对不同的处理流程提供了各自特定的处理函数,然后为每个中断描述符(译者注:指每个中断对应的 struct irq_desc)安装相应的处理函数(通过设置结构体的函数指针成员 handle_irq())。这么做的好处是可以针对特定需求优化代码,总体上使得整个中断的执行路径较短。如果针对一个新硬件平台的中断流程,无法直接使用缺省提供的处理函数,那么也可以为其新建一个处理流程的函数。

At the kernel API level, the changes are relatively small; changes to drivers are not generally required. There are a few new capabilities, however. One is that there are some new flags which can be passed to request_irq():

  • SA_TRIGGER_LOW and SA_TRIGGER_HIGH: treat the interrupt source as being level-triggered, with interrupts happening at either the high or low level.
  • SA_TRIGGER_FALLING and SA_TRIGGER_RISING: treat the interrupt as being edge-triggered.

在中断子系统所提供的内核 API 上,补丁的变化相对较小;通常情况下不需要更改现有的驱动程序,除非要支持补丁所提供的新功能。譬如补丁新增了一些传递给 request_irq() 的选项,如下所示:

  • SA_TRIGGER_LOWSA_TRIGGER_HIGH:将中断源设置为高电平触发还是低电平触发。
  • SA_TRIGGER_FALLINGSA_TRIGGER_RISING:将中断源设置为下降沿触发还是上升沿触发。

This addition to the API actually happened in 2.6.16, but only the ARM architecture had any support for it at all. With the genirq patches, all architectures support these flags, and the appropriate flow handler will be selected internally. When interrupts are shared, however, all users must agree on how the triggering will be handled.

以上对 API 的修改实际上在 2.6.16 版本时就已经进入内核主线,但那时只有 ARM 架构完全支持它。使用 genirq 补丁后,所有体系结构都将支持这些标志,在 request_irq() 函数内部会根据这些标志为中断选择适当的流程处理函数。注意对于共享某个中断的所有设备必须采用相同的触发流程处理(译者注:因为共享中断的设备共用同一个流程处理函数)。

It is also possible to change the flow type of an IRQ directly with:

也可以通过以下方式直接更改中断的流程处理类型:

int set_irq_type(unsigned int irq, unsigned int type);

Here, type should be one of IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_EDGE_BOTH, IRQ_TYPE_LEVEL_HIGH, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_SIMPLE, or IRQ_TYPE_PERCPU. Calling this function has the same effect as specifying the trigger type with request_irq(), but it offers a wider range of possibilities. It also does not check for compatibility with any other users of a shared interrupt, so a certain potential for confusion exists.

这里,type 的取值为 IRQ_TYPE_EDGE_RISINGIRQ_TYPE_EDGE_FALLINGIRQ_TYPE_EDGE_BOTHIRQ_TYPE_LEVEL_HIGHIRQ_TYPE_LEVEL_LOWIRQ_TYPE_SIMPLEIRQ_TYPE_PERCPU 之一。调用此函数的效果与使用 request_irq() 时指定触发类型的效果相同 ,但它提供了更多的选择。需要注意的是该函数不检查你所设置的处理流程类型与共享中断的设备所支持的处理流程是否冲突,因此可能会在一定程度上引入混乱。

Some devices can generate interrupts which should wake up the system from a suspended state. Wake-on-LAN behavior in network adaptors is one example; allowing the keyboard to wake the system is another. Kernel code can enable or disable this behavior in the interrupt controller with:

某些设备能够通过中断将系统从挂起状态唤醒。网络适​​配器中的网络唤醒行为(Wake-on-LAN)就是一个例子;另一个例子是通过键盘唤醒系统。内核代码可以通过以下函数控制中断控制器启用或禁用此行为:

int set_irq_wake(unsigned int irq, unsigned int on);

An error code will be returned if the chip-level controller does not implement this operation.

如果芯片级控制器不支持此操作,需要返回相应的错误代码。

There has been a relatively small amount of discussion so far; the biggest objection seems to be a claim that the separate flow handlers are an unnecessarily complex addition. The decision on whether genirq is merged very likely depends on whether the ARM maintainers are willing to drop their architecture-specific IRQ implementation and move to the new, generic version. Without that, the genirq code, which contains a lot of work aimed specifically at ARM’s needs, will not truly be a generic solution. In the mean time, genirq has found its way into the -mm tree.

到目前为止,有关该补丁的讨论还不多;最大的 反对意见 是认为定义多个单独的流程处理函数是不必要的,这只会增加代码的复杂度。关于 genirq 补丁是否会被合入主线很大程度上取决于 ARM 的维护人员是否愿意放弃他们特定于其架构的 IRQ 实现并转向使用新的通用版本。要知道 genirq 补丁设计之初特别针对 ARM 需求做了大量的工作,如果得不到 ARM 的支持,在很大程度上这个通用解决方案就失去意义了。当前 genirq 先期合入了 -mm 代码仓库。



Read Album:

Read Related:

Read Latest: