RFC-0070:PCI 协议变更以支持旧版中断

RFC-0070:更改 PCI 协议以支持旧版中断
状态已接受
领域
  • 内核
说明

针对虚假的 PCI 旧版中断缓解措施。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2020-01-17
审核日期(年-月-日)2020-02-25

摘要

在用户空间中,PCI 总线驱动程序需要能够停用旧版级别 在设备中断得到处理之前,会触发中断 相同的 IRQ,从而不断意外唤醒总线驱动程序的 IRQ 线程。 为了实现这一点,我们需要一种让设备驱动程序通知总线驱动程序的方法 它已准备好为新的中断提供服务并重新启用旧版中断服务 中断生成。

设计初衷

大多数新型 PCI 设备均通过消息信号中断 (MSI) 运行 通过 PCI 中的可选 MSI 或 MSI-X 功能控制 配置空间这些中断特定于给定设备 由内核句柄和 MsiInterruptDispatcher。每个 MSI 都只提供给一台设备, 可视为标准系统中断。

不过,PCI 旧版中断通过一组共享的中断行来运行 适用于所有 PCI 设备,且在系统固件表中有详细说明,例如 PCI 固件规范中定义的 ACPI。这些中断 被规范触发并处于低活跃状态当某个中断被触发时 由系统软件负责确定 以便能够得到处理并释放代码行。在 内核 PCI 总线驱动程序 (kPCI),这由所有旧版中断处理 向所有 PciInterruptDispatchers 注册共享中断处理程序 内核中。然后,该处理程序会确定是哪个设备生成了 中断并指示相应的中断对象。设备的功能 因此停用该功能司机下次等待时 调度程序“Unmask”钩子将取消掩码并重新启用设备的 旧版中断生成功能。

借助用户空间 PCI 总线驱动程序 (uPCI),所有这些设备都已迁移至 用户空间。uPCI 本身现在运行了一个低开销 IRQ 工作器, 确定引起问题的设备,并发出虚拟中断信号,表示 并与之互动。不过,由于这些中断基于 如果我们缺少设备的驱动程序,或者 指定驱动程序无法正确处理中断。但如果 uPCI 停用了 以便驱动程序能够处理它,那么我们就没有 方法,使 PCI 设备驱动程序重新启用中断。目前有 无法通知设备驱动程序已调用的 uPCI 总线驱动程序 zx_interrupt_waitzx_port_wait 权限,因此 总线驱动程序不知道何时应重新启用设备的中断。

设计

我们需要针对 PCI 驱动程序中两种不同用途的中断进行设计。

  1. 知道自己是 PCI 驱动程序并直接调用 PciProtocol 方法的驱动程序。
  2. 以阻止使用 PciProtocol 方法的方式使用中断的驱动程序。

针对某个事件触发使用 PCI_IRQ_MODE_LEGACY 配置的旧版中断时 共享线路,总线驱动程序负责通知正确的设备。 与 kPCI 驱动程序类似,总线将停用设备的旧版中断 在向驱动程序发出有中断可供处理的信号时, 来有效遮盖它。我们将会添加新的PCI 协议 方法 以允许设备驱动程序请求重新启用其中断 / 已公开。可能与 在某些配置中使用旧版中断,但需要 只使用 MSI 运行的设备不会有任何变化。这会导致 虚假中断,以满足所描述的第一次使用的需求。

这与 Linux 对用户空间 I/O 中断的处理方式类似。

对于第二种用途,我们将创建备用的旧版 IRQ 模式 PCI_IRQ_MODE_LEGACY_ACKLESS。此模式不会由以下用户选择: ConfigureIrqMode(),需要由驾驶员专门选择 满足这一独特要求在此模式下配置中断的设备 以便确定每秒中断次数 超过配置的数量。如果发生这种情况,设备就能够 所有打扰项都会被停用。其运作方式类似于 Linux 对 启动中断

实现

可以按顺序进行更改,而不会有任何迁移或 CQ 问题。

  1. 修改 pci_configure_irq_mode 以添加一个 out 参数来存储 IRQ 为与中断模式和更新无关的驱动程序选择的模式 现有调用方。
  2. 添加新的协议方法 pci_legacy_interrupt_ack(或者直接 pci_interrupt_ack),这可重新启用设备的旧版中断功能 并返回 ZX_OKZX_ERR_BAD_STATE(如果设备不是 来使用旧版中断功能
  3. 更新 pci_configure_irq_mode 的现有调用方和以下用户的用户 PCI_IRQ_MODE_LEGACY,以便在其中断中使用新协议方法 处理。
  4. 更新以抽象方式处理中断的驱动程序,并确保它们使用 PCI_IRQ_MODE_LEGACY_NOACK,而非 PCI_IRQ_MODE_LEGACY
  5. 让 uPCI IRQ 工作器停用设备的旧版中断生成功能 当所有驱动程序均完成后发出设备驱动程序的虚拟中断信号时 数据。
  6. 详尽地记录了在 PciProtocol 班卓琴中 PCI 中断的使用情况 以及 Fuchsia.dev

性能

遇到的大多数 PCI 设备都将使用 MSI 运行。提供的类型 仍在使用旧版中断功能的设备通常仅限使用旧版中断功能的设备 硬件,对性能要求较低的集成 SOC 设备,模拟 不支持 MSI 的环境以及使用中断功能的设备 很少见。

想要处理旧版中断的驱动程序将添加一个额外的信道 写入这一新 PCI 协议的本质上的中断处理例程 方法要求从驱动程序 devhost 代理写入 uPCI。可以是 对通话本身进行基准化分析,或查看 Zircon 汇总基准中的渠道写入次数。

安全注意事项

无。

隐私注意事项

无。

测试

CQ/CI 中的现有集成和端到端测试将验证 更改后,中断仍然正常工作,并且新的单元测试 将验证对 pci_configure_irq_mode 所做的更改是否有效 协议方法。

文档

需要扩充 PCI 文档以解释运行理论 以及中断模式等此外,有必要指出需要 zx_interrupt_waitzx_port_waitpci_legacy_interrupt_ack 文档。

缺点、替代方案和未知问题

缺点

大多数驱动程序更倾向于仅使用 MSI / MSI-X 中断模式,并会 完全不需要关注此 API,因此它的 对系统进行了更改,以仅要求驱动程序可能遇到以下情况的设备: 来处理这种情况,而不是所有驱动程序。不过, 这确实存在为特定设备设置编写的驱动程序 应用会遇到这样的问题 则将无法确认。如果驱动程序支持各种 设备。

使用多个后端的情况也增加了一定的复杂性。例如: xHCI 驱动程序的内容与以下内容类似。如果其 PCI 支持 涉及旧版中断,可能如下所示:

// Initialize the proper setup and obtain an interrupt
if (pci_.is_valid()) {
  pci_init();
} else {
  mmio_init();
}

do {
  // Wait loop on the interrupt
  // Handle the interrupt

  if (mode_ == XHCI_MODE_PCI && irq_mode_ == PCI_IRQ_MODE_LEGACY)
    status = pci_.LegacyInterruptAck();
  }
}

如果省略确认代码,则此驱动程序可与 MSI 配合使用,但 在传统模式下,在第一次中断之后不会收到中断。改进测试 围绕 PCI 驱动程序的框架将有助于更好地捕获这些 错误。

考虑的替代方案

将过多的未处理中断标记为虚假中断并停用中断

与 Linux 类似,我们只需为顺序虚假事件设置一个阈值 如果达到,我们可以停用或忽略该中断行 直到重新启动。这种方法的一个主要问题是 处理共享中断,它通过硬 IRQ 调用处理程序链 确认中断之前,内核中的处理程序 已完成。这样可确保所有驱动程序中断处理程序均已运行 因此,只有在没有适当的处理程序的情况下,才会出现虚假中断 处理了中断。在 Zircon 中配置 uPCI 驱动程序,并在设备驱动程序中引入 在进程外,我们可以发出信号来唤醒他们的 IRQ 处理线程,但是 没办法知道它们是否已完成这会导致 虚假中断,具体取决于驱动程序 IRQ 线程会被调度并处理指定的中断条件。不过, 与确认建议相比,这种方法仍然会产生更多的虚假中断

添加等待中断的 PCI 协议方法

可以考虑使用一种方法,即添加一种方法来处理对某类 中断,有效 pci_interrupt_wait,避免需要额外的 有条件的中断。

遗憾的是,这会导致需要处理 PCI 中断 与其他中断不同尽我们的努力,我相信 使任何中断对象与任何其他中断对象具有相同的接口 应用具有很多价值,因此,驱动程序作者必须始终如一地 能够使用 zx_interrupt_waitzx_port_bindzx_port_waitzx_object_wait_async。我们系统中的大多数驱动程序都 支持多个中断的 IRQ 端口或具有多个后端(UART、PCI、USB) 因此,有一点很重要 对象的操作。

在派生的 InterruptDispatchers 中处理此问题

我们可以保留 kPCI 的 PciInterruptDispatcher 概念, 使 PCI 设备驱动程序和 PCI 设备之间的中断处理 由内核执行遗憾的是,这之间存在很大的耦合, 用户空间 PCI 驱动程序和 Zircon 内核。

  1. 每个专用的 InterruptDispatcher 都需要能够写入 PCI 设备的控制寄存器来停用旧版中断。这会 理想情况下需要 VMO,这与我们的 MSI 方法不同 传递给任何创建此对象的系统调用。没有办法 因为调度程序必须知道它对应于哪台设备 目标。
  2. 中断停用是控制寄存器中的单个位, 用户空间总线驱动程序在总线和设备期间会频繁修改 如果存在以下情况,则存在严重的竞态条件风险: 所有待处理的中断。
  3. 如果我们在设备本身中使用派生的 InterruptDispatcher, 需要处理确定谁的中断在共享行上触发。开始时间 设备不再通过总线进行中断处理 这意味着我们需要保持与 中 kPCI 的 SharedIrqHandler 由内核执行
  4. 现在,我们还需要深入了解 PCI 旧版 IRQ 从 ACPI / 板级文件导入路由表。这是 在用户空间中进行处理,ACPI。

目前,我认为这种方法并不合理, 我们愿意承认,PCI 是一种特殊的驱动程序, 内核代码以及大约 2 个其他系统调用来完成其工作。

先验技术和参考资料

在我的研究中,完全在用户空间中处理这个问题是 Zircon 特有的一个问题。

  1. OSX 的 DriverKit 使用以串联方式工作的 IOInterruptDispatchSource 以及内核中的中断配置和处理。此外, PCIDriverKit 仅支持 MSI 和 MSI-X 中断模式。

    PCIDriverKit>IOPCIDevice

  2. 大多数 Linux PCI 中断都在内核本身中进行处理。已分享 中断具有由驱动程序注册到中断的处理程序链。当 内核会按顺序调用每个处理程序,直到有一个处理程序 处理了中断。如果共享中断行有足够的虚假 如果中断未被任何句柄处理,内核会停用 中断。

    Linux 启动中断

    Linux 还通过其用户空间 I/O 支持简单的用户空间驱动程序 (UIO) 接口。这样,驱动程序便可对read() 提供了 /dev/uioX 个 sysfs 节点来等待中断。中断 该功能在触发时停用,但驱动程序可通过执行 对 sysfs 节点执行 write() 调用。

    用户空间 I/O 大会指南

  3. 大多数 Windows PCI 驱动程序都是使用内核模式驱动程序框架构建的 (KMDF)。其中断处理程序作为内核中断的一部分进行调用 调度和驱动程序可注册在 IRQ 上下文中运行的处理程序。

    中断服务日常安排简介