MSI
- 4. MSI驱动指南HOWTO
- 4.1. 关于本指南
- 4.2. 什么是 MSI?
- 4.3. 为什么要使用 MSI?
- 4.4. 如何使用 MSI
- 4.4.1. 包括对 MSI 的内核支持
- 4.4.2. 使用MSI
- 4.4.3. 传统API
- 4.4.4. 使用 MSI 时的注意事项
- 4.4.4.1 自旋锁
- 4.4.5. 如何判断设备上是否启用了 MSI/MSI-X
- 4.5. MSI quirks
- 4.5.1. 全局禁用 MSI
- 4.5.2. 在网桥下禁用 MSI
- 4.5.3. 在单个设备上禁用 MSI
- 4.5.4. 查找设备上禁用 MSI 的原因
英文链接:The MSI Driver Guide HOWTO
4. MSI驱动指南HOWTO
作者
Tom L Nguyen; Martine Silbermann; Matthew Wilcox
版权
2003、2008 英特尔公司
4.1. 关于本指南
本指南介绍了消息信号中断Message Signaled Interrupts (MSIs) 的基础知识、使用 MSI 相对于传统中断机制的优势、如何更改驱动程序以使用 MSI 或 MSI-X 以及在设备不支持 MSI 时尝试的一些基本诊断。
4.2. 什么是 MSI?
Message Signaled Interrupt 是从设备写入特殊地址,导致 CPU 接收中断。
MSI 功能首先在PCI 2.2中指定,后来在PCI 3.0中得到增强,允许单独屏蔽每个中断。 PCI 3.0还引入了 MSI-X 功能。与 MSI 相比,它支持每个设备更多的中断,并允许独立配置中断。
设备可能同时支持 MSI 和 MSI-X,但一次只能启用一个。
4.3. 为什么要使用 MSI?
使用 MSI 比传统的基于引脚的中断具有优势的原因有三个。
- 基于引脚的 PCI 中断通常在多个设备之间共享。为了支持这一点,内核必须调用与中断相关的每个中断处理程序,这会导致整个系统的性能下降。 MSI 从不共享,因此不会出现此问题。
- 当设备将数据写入内存,然后引发基于引脚的中断时,中断可能会在所有数据到达内存之前到达(这在 PCI-PCI 桥后面的设备更可能发生)。为了确保所有数据都已到达内存,中断处理程序必须读取引发中断的设备上的寄存器。 PCI 事务排序规则要求所有数据在值可以从寄存器返回之前到达内存。使用 MSI 可以避免这个问题,因为产生中断的写入不能传递数据写入,所以在引发中断时,驱动程序知道所有数据都已到达内存。
- PCI 设备的每个功能只能支持一个基于引脚的中断。通常,驱动程序必须查询设备以找出发生了什么事件,从而减慢常见情况的中断处理速度。使用 MSI,设备可以支持更多中断,允许每个中断专门用于不同的目的。一种可能的设计为不常见的条件(例如错误)提供了自己的中断,这允许驱动程序更有效地处理正常的中断处理路径。其他可能的设计包括为网卡中的每个数据包队列或存储控制器中的每个端口提供一个中断。
4.4. 如何使用 MSI
PCI 设备被初始化为使用基于引脚的中断。设备驱动程序必须将设备设置为使用 MSI 或 MSI-X。并非所有机器都正确支持 MSI,对于这些机器,下面描述的 API 将简单地失败,并且设备将继续使用基于引脚的中断。
4.4.1. 包括对 MSI 的内核支持
要支持 MSI 或 MSI-X,必须在构建内核时启用 CONFIG_PCI_MSI 选项。此选项仅在某些架构上可用,并且可能取决于还设置的其他一些选项。例如,在 x86 上,您还必须启用 X86_UP_APIC 或 SMP 才能看到 CONFIG_PCI_MSI 选项。
4.4.2. 使用MSI
大部分困难工作已经在PCI层为驱动完成了。驱动程序只需请求 PCI 层为此设备设置 MSI 功能。
- 要自动使用 MSI 或 MSI-X 中断向量,请使用以下函数:
int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,unsigned int max_vecs, unsigned int flags);
它为 PCI 设备分配最多max_vecs
个中断向量。它返回申请的vectors数量
或负数
。如果设备对vectors的最小数量有要求,驱动程序可以传递一个min_vecs
参数配置这个限制,如果它不能满足vectors的最小数量,PCI core 将返回 -ENOSPC
。
flags 参数用于指定设备和驱动程序可以使用哪种类型的中断(PCI_IRQ_LEGACY、PCI_IRQ_MSI、PCI_IRQ_MSIX)。
一个方便的简写 (PCI_IRQ_ALL_TYPES)
也可用于请求任何可能的中断类型。如果设置了PCI_IRQ_AFFINITY
标志,pci_alloc_irq_vectors()
将在可用 CPU间广播中断。
- 为了获取传递给
request_irq()
和free_irq()
的 Linux IRQ 编号以及vectors,请使用以下函数:
int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
- 在使用以下函数删除设备之前,应释放所有分配的资源:
void pci_free_irq_vectors(struct pci_dev *dev);
如果设备同时支持 MSI-X 和 MSI 功能,则此 API 将优先使用 MSI-X 而不是 MSI 。 MSI-X 支持 1 到 2048 之间的任意数量的中断。相比之下,MSI 被限制为最多 32 个中断(并且必须是 2 的幂)。此外,必须连续分配 MSI 中断向量,因此系统可能无法为 MSI 分配足够多的向量,因为它优先为 MSI-X 分配。在某些平台上,MSI 中断必须针对同一组 CPU,而 MSI-X 中断可以针对不同的 CPU。
如果设备既不支持 MSI-X 也不支持 MSI,它将回退到单个传统 IRQ 向量。
- MSI或MSI-X中断的典型用法是分配尽可能多的向量,可能达到设备支持的限制。如果
nvec
大于设备支持的数量,它会自动设置为支持的限制,因此无需事先查询支持的向量数量:
nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_ALL_TYPES)
if (nvec < 0)goto out_err;
- 如果驱动程序无法或不愿意处理可变数量的 MSI 中断,它可以申请指定数量的中断。通过将该数量作为
min_vecs
和max_vecs
参数传递给pci_alloc_irq_vectors()
函数:
nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_ALL_TYPES)
if (nvec < 0)goto out_err;
- 上述请求类型中最臭名昭著的示例是为设备启用单一 MSI 模式。可以通过传递两个 1 作为“min_vecs”和“max_vecs”来完成:
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0)goto out_err;
- 某些设备可能不支持使用传统线路中断,在这种情况下,驱动程序可以指定只接受 MSI 或 MSI-X:
nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (nvec < 0)goto out_err;
4.4.3. 传统API
以下用于启用和禁用 MSI 或 MSI-X 中断的旧 API 不应在新代码中使用:
pci_enable_msi() /* deprecated */
pci_disable_msi() /* deprecated */
pci_enable_msix_range() /* deprecated */
pci_enable_msix_exact() /* deprecated */
pci_disable_msix() /* deprecated */
此外,还有一些 API 可提供支持的 MSI 或 MSI-X 向量的数量:pci_msi_vec_count()
和 pci_msix_vec_count()
。一般来说,应该避免这些,而是让 pci_alloc_irq_vectors() 限制向量的数量。如果您对向量计数有一个合法的特殊用例,我们可能不得不重新考虑该决定并添加一个 pci_nr_irq_vectors() 帮助程序来透明地处理 MSI 和 MSI-X。
4.4.4. 使用 MSI 时的注意事项
4.4.4.1 自旋锁
大多数设备驱动程序都有一个每个设备的自旋锁,它在中断处理程序中被采用。对于基于引脚的中断或单个 MSI,没有必要禁用中断(Linux 保证不会重新进入相同的中断)。如果设备使用多个中断,则驱动程序必须在保持锁定时禁用中断。如果设备发送不同的中断,驱动程序将死锁尝试递归获取自旋锁。这种死锁可以通过使用 spin_lock_irqsave() 或 spin_lock_irq() 来避免,它们禁用本地中断并获取锁(参见不可靠的锁定指南)。
4.4.5. 如何判断设备上是否启用了 MSI/MSI-X
使用“lspci -v”(以 root 身份)可能会显示一些具有“MSI”、“消息信号中断”或“MSI-X”功能的设备。这些功能中的每一个都有一个“Enable”标志,后跟“+”(启用)或“-”(禁用)。
4.5. MSI quirks
已知有几个 PCI 芯片组或设备不支持 MSI。 PCI 堆栈提供了三种禁用 MSI 的方法:
- 全局的
- 在特定网桥后面的所有设备上
- 在单个设备上
4.5.1. 全局禁用 MSI
一些主机芯片组根本无法正确支持 MSI。如果幸运的话,制造商知道这一点,并在 ACPI FADT 表中指出了这一点。在这种情况下,Linux 会自动禁用 MSI。有些板子没有在表中包含这些信息,所以我们必须自己检测它们。这些的完整列表可以在 drivers/pci/quirks.c 中的 quirk_disable_all_msi() 函数附近找到。
如果您的主板存在 MSI 问题,您可以在内核命令行上传递 pci=nomsi 以禁用所有设备上的 MSI。将问题报告给 linux-pci@vger.kernel.org 将符合您的最佳利益,包括完整的“lspci -v”,以便我们可以将怪癖添加到内核中。
4.5.2. 在网桥下禁用 MSI
某些 PCI 桥无法在总线之间正确路由 MSI。在这种情况下,必须在网桥后面的所有设备上禁用 MSI。
某些网桥允许您通过更改其 PCI 配置空间中的某些位来启用 MSI(尤其是 Hypertransport 芯片组,例如 nVidia nForce 和 Serverworks HT2000)。与主机芯片组一样,Linux 大多了解它们,并在可能的情况下自动启用 MSI。如果您有一个 Linux 未知的网桥,您可以使用您知道的任何方法在配置空间中启用 MSI,然后通过执行以下操作在该网桥上启用 MSI:
echo 1 > /sys/bus/pci/devices/$bridge/msi_bus
其中 $bridge 是您启用的网桥的 PCI 地址(例如 0000:00:0e.0)。
要禁用 MSI,请回显 0 而不是 1。更改此值时应谨慎,因为它可能会中断此桥下所有设备的中断处理。
再次,请通知 linux-pci@vger.kernel.org 任何需要特殊处理的网桥。
4.5.3. 在单个设备上禁用 MSI
已知某些设备具有错误的 MSI 实现。通常这是在单独的设备驱动程序中处理的,但有时有必要用一个怪癖来处理它。某些驱动程序可以选择禁用 MSI。虽然这对驱动程序作者来说是一种方便的解决方法,但它不是一个好的做法,不应该被模仿。
4.5.4. 查找设备上禁用 MSI 的原因
从以上三个部分中,您可以看到可能无法为给定设备启用 MSI 的原因有很多。您的第一步应该是仔细检查您的 dmesg 以确定您的机器是否启用了 MSI。您还应该检查您的 .config 以确保您已启用 CONFIG_PCI_MSI。
然后,“lspci -t”给出设备上方的网桥列表。读取 /sys/bus/pci/devices/*/msi_bus 将告诉您 MSI 是启用 (1) 还是禁用 (0)。如果在属于 PCI 根和设备之间的桥的任何 msi_bus 文件中找到 0,则禁用 MSI。
检查设备驱动程序以查看它是否支持 MSI 也是值得的。例如,它可能包含对带有PCI_IRQ_MSI 或 PCI_IRQ_MSIX
标志的pci_alloc_irq_vectors() 的调用
。