Linux 内核如何处理共享的 IRQ?

bsi*_*ang 14 linux kernel pci interrupt irq

根据我目前所读到的内容,“当内核收到中断时,所有注册的处理程序都会被调用。”

我知道每个 IRQ 的注册处理程序可以通过查看/proc/interrupts,我也知道注册的处理程序来自调用request_irq了回调的驱动程序,大致形式如下:

irqreturn_t (*handler)(int, void *)
Run Code Online (Sandbox Code Playgroud)

根据我所知道的,每个与特定 IRQ 关联的中断处理程序回调都应该被调用,并且由处理程序来确定是否确实应该由它处理中断。如果处理程序不应该处理特定的中断,它必须返回内核宏IRQ_NONE

我无法理解的是,每个驱动程序如何确定它是否应该处理中断。我想如果他们应该期待中断,他们可以在内部跟踪。如果是这样,我不知道他们将如何处理同一 IRQ 后面的多个驱动程序期望中断的情况。

我试图了解这些细节的原因是因为我正在搞乱kexec在系统操作中间重新执行内核的机制,同时在 PCIe 桥接器以及下游 PCI 上使用复位引脚和各种寄存器设备。在这样做的过程中,重新启动后,我要么遇到内核恐慌,要么其他驱动程序抱怨即使没有进行任何操作,他们也收到了中断。

处理程序如何决定中断应该由它处理是个谜。

编辑:如果相关,有问题的 CPU 架构是x86.

War*_*ung 14

这在涉及第10章Linux设备驱动程序,第三版,由科比特等。它可以在线免费获得,或者您可以通过O'Reilly 的方式为死树或电子书形式折腾一些谢克尔。与您的问题相关的部分从第 278 页的第一个链接开始。

就其价值而言,这是我试图解释这三页以及我在 Google 上搜索到的其他内容的尝试:

  • 当您注册共享 IRQ 处理程序时,内核会检查:

    一种。该中断不存在其他处理程序,或

    湾 所有先前注册的人请求中断共享

    如果任一情况适用,它就会检查您的dev_id参数是否唯一,以便内核可以区分多个处理程序,例如在处理程序删除期间。

  • 当 PCI¹ 硬件设备引发 IRQ 线时,内核的低级中断处理程序被调用,它依次调用所有已注册的中断处理程序,dev_id通过request_irq().

    dev_id值需要是机器唯一的。执行此操作的常用方法是将指针传递给struct驱动程序用于管理该设备的每个设备。由于该指针必须位于驱动程序的内存空间内才能对驱动程序有用,因此它实际上对该驱动程序来说是唯一的。²

    如果有一个给定的中断注册了多个驱动器,它们将全部被调用时该共享中断线的设备加注。如果不是您的驱动程序的设备执行此操作,您的驱动程序的中断处理程序将被传递一个dev_id不属于它的值。发生这种情况时,您的驱动程序的中断处理程序必须立即返回。

    另一种情况是您的驱动程序正在管理多个设备。驱动程序的中断处理程序将获得dev_id驱动程序已知的值之一。您的代码应该轮询每个设备以找出哪个设备引发了中断。

    Corbet等人的例子给的是PC并行端口。当它断言中断线时,它还设置其第一个设备寄存器中的最高位。(也就是说inb(0x378) & 0x80 == true,假设标准 I/O 端口编号。)当您的处理程序检测到这一点时,它应该完成其工作,然后通过将从 I/O 端口读取的值写回具有顶部的端口来清除 IRQ位清除。

    我看不出有任何特殊机制是特殊的。不同的硬件设备可以选择不同的机制。唯一重要的是,对于允许共享中断的设备,它必须有某种方式让驱动程序读取设备的中断状态,以及某种方式来清除中断。您必须阅读设备的数据表或编程手册,以了解您的特定设备使用的机制。

  • 当您的中断处理程序告诉内核它处理了中断时,这并不会阻止内核继续调用为同一中断注册的任何其他处理程序。如果您在使用电平触发中断时共享一条中断线,这是不可避免的。

    想象一下,两个设备同时断言同一条中断线。(或者至少,时间如此接近,以至于内核没有时间调用中断处理程序来清除该行,从而将第二个断言视为单独的。)内核必须调用该中断行的所有处理程序,以便为每个有机会查询其相关硬件以查看它是否需要注意。两个不同的驱动程序很可能在同一次通过给定中断的处理程序列表中成功处理中断。

    因此,您的驱动程序必须在中断处理程序返回之前的某个时间告诉设备它正在设法清除其中断断言。我不清楚否则会发生什么。连续断言的中断线要么导致内核不断调用共享中断处理程序,要么屏蔽内核查看新中断的能力,因此永远不会调用处理程序。不管怎样,灾难。


脚注:

  1. 我在上面指定了 PCI,因为以上所有内容都假设原始 PCI 规范中使用的级别触发的中断。ISA 使用边缘触发的中断,这使得共享充其量是棘手的,并且只有在硬件支持的情况下才有可能。PCIe 使用消息信号中断;中断消息包含一个唯一值,内核可以使用它来避免 PCI 中断共享所需的循环猜测游戏。PCIe 可能完全不需要中断共享。(我不知道它是否真的这样做,只是它有潜力。)

  2. Linux 内核驱动程序都共享相同的内存空间,但一个不相关的驱动程序不应该在另一个内存空间中乱搞。除非你传递那个指针,否则你可以很确定另一个驱动程序不会自己意外地想出相同的值。


bsi*_*ang 5

当驱动程序请求共享 IRQ 时,它会将指向内核的指针传递给对驱动程序内存空间中设备特定结构的引用。

根据 LDD3:

每当两个或更多驱动程序共享一条中断线并且硬件中断该线上的处理器时,内核会调用为该中断注册的每个处理程序,并传递每个自己的 dev_id。

在检查几个驱动程序的 IRQ 处理程序时,它们似乎在探测硬件本身以确定它是否应该处理中断或返回IRQ_NONE

例子

UHCI-HCD 驱动程序
  status = inw(uhci->io_addr + USBSTS);
  if (!(status & ~USBSTS_HCH))  /* shared interrupt, not mine */
    return IRQ_NONE;
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,驱动程序正在读取USBSTS寄存器以确定是否有中断服务。

SDHCI驱动程序
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

  if (!intmask || intmask == 0xffffffff) {
    result = IRQ_NONE;
    goto out;
  }
Run Code Online (Sandbox Code Playgroud)

就像在前面的例子中一样,驱动程序正在检查状态寄存器,SDHCI_INT_STATUS以确定它是否需要服务中断。

Ath5k 驱动程序
  struct ath5k_softc *sc = dev_id;
  struct ath5k_hw *ah = sc->ah;
  enum ath5k_int status;
  unsigned int counter = 1000;

  if (unlikely(test_bit(ATH_STAT_INVALID, sc->status) ||
        !ath5k_hw_is_intr_pending(ah)))
    return IRQ_NONE;
Run Code Online (Sandbox Code Playgroud)

再举一个例子。