MSI及MSIX详解

news/2024/10/29 12:36:50/

在PCI总线中,所有需要提交中断请求的设备,必须能够通过INTx引脚提交中断请求,而MSI机制是一个可选机制。而在PCIe总线中,PCIe设备必须支持MSI或者MSI-X中断请求机制,而可以不支持INTx中断消息。

在PCIe总线中,MSI和MSI-X中断机制使用存储器写请求TLP向处理器提交中断请求,下文为简便起见将传递MSI/MSI-X中断消息的存储器写报文简称为MSI/MSI-X报文。不同的处理器使用了不同的机制处理这些MSI/MSI-X中断请求,如PowerPC处理器使用MPIC中断控制器处理MSI/MSI-X中断请求,本章将在第6.2节中介绍这种处理情况;而x86处理器使用FSB Interrupt Message方式处理MSI/MSI-X中断请求。

不同的处理器对PCIe设备发出的MSI报文的解释并不相同。但是PCIe设备在提交MSI中断请求时,都是向MSI/MSI-X Capability结构中的Message Address的地址写Message Data数据,从而组成一个存储器写TLP,向处理器提交中断请求。

有些PCIe设备还可以支持Legacy中断方式[1]。但是PCIe总线并不鼓励其设备使用Legacy中断方式,在绝大多数情况下,PCIe设备使用MSI或者MSI/X方式进行中断请求。

PCIe总线提供Legacy中断方式的主要原因是,在PCIe体系结构中,存在许多PCI设备,而这些设备通过PCIe桥连接到PCIe总线中。这些PCI设备可能并不支持MSI/MSI-X中断机制,因此必须使用INTx信号进行中断请求。

当PCIe桥收到PCI设备的INTx信号后,并不能将其直接转换为MSI/MSI-X中断报文,因为PCI设备使用INTx信号进行中断请求的机制与电平触发方式类似,而MSI/MSI-X中断机制与边沿触发方式类似。这两种中断触发方式不能直接进行转换。因此当PCI设备的INTx信号有效时,PCIe桥将该信号转换为Assert_INTx报文,当这些INTx信号无效时,PCIe桥将该信号转换为Deassert_INTx报文。

与Legacy中断方式相比,PCIe设备使用MSI或者MSI-X中断机制,可以消除INTx这个边带信号,而且可以更加合理地处理PCIe总线的“序”。目前绝大多数PCIe设备使用MSI或者MSI-X中断机制提交中断请求。

MSI和MSI-X机制的基本原理相同,其中MSI中断机制最多只能支持32个中断请求,而且要求中断向量连续,而MSI-X中断机制可以支持更多的中断请求,而并不要求中断向量连续。与MSI中断机制相比,MSI-X中断机制更为合理。本章将首先介绍MSI/MSI-X Capability结构,之后分别以PowerPC处理器和x86处理器为例介绍MSI和MSI-X中断机制。

1. 什么是MSI

MSI全称Message Signaled Interrupt。
当设备向一个特殊地址写入时,会向CPU产生一个中断,即也MSI中断。
MSI能力最初在PCI 2.2里定义,在PCI 3.0里被强化,使得每个中断都可以单独控制。
PCI 3.0还引入了MSI-X能力,相比MSI,每个设备可以支持更多的中断,并且可以独立配置。

设备可以同时支持MSI和MSI-X,但同一时刻只能使能其中一种。

2. 为什么使用MSI

与传统引脚中断相比,有三个方面的优势。

  1. 基于引脚的PCI中断经常在几个设备间共享,内核必须调用与该中断相关的每一个中断处理函数,降低了效率。MSI不是共享的,所以不存在这个问题。

  2. 当设备向内存写入数据,然后发起引脚中断时,有可能在CPU收到中断时,数据还未到达内存(在PCI-PCI桥后的设备更有可能如此)。为了保证数据已达 内存,中断处理程序必须轮询产生该中断的设备的一个寄存器,PCI事务保序规则会确保所有数据到达内存后,寄存器才会返回值。 使用MSI时,产生中断的写不能越过数据写,因而避免了这个问题。当中断产生时,驱动可以确信所有数据已经到达内存。

  3. PCI的每个功能设备只支持一个基于引脚的中断,驱动常常需要查询设备来确定发生的事件,降低了中断处理的效率。通过MSI,一个设备可以支持多个中断,这样可以为不同的使用不同的中断。比如:
    a.给不常发生的事件(如错误)指定独立的中断,这样驱动可以正常中断路径进行更有效的处理。
    b.给网卡的每个报文队列或者存储控制器的每个端口分配中断,提高中断效率。

3. 如何使用MSI

PCI设备初始化为使用基于引脚的中断,设备驱动需要将设备配置为使用MSI或MSI-X。不是所有的设备可以完整支持MSI,下面有些API就直接返回失败,因此仍然使用基于引脚的中断。

3.1 内核提供MSI支持

内核需要配置CONFIG_PCI_MSI以支持MSI或者MSI-X,该配置是否可以配置受架构和其它一些配置的影响。

3.2 使用MSI

大部分工作在PCI层的驱动里完成,只需要请求PCI层为设备设置MSI能力即可。

3.2.1 pci_enable_msi

int pci_enable_msi(struct pci_dev *dev) 这个调用只给设备分配一个中断,不管设备支持多少个MSI中断。设备会从基于引脚中断模式切换为MSI模式。dev->irq会赋予一个新的代表MSI的编号。

在驱动需要在调用request_irq()之前调用这个接口。因为打开MSI中断的同时会禁用引脚中断IRQ,驱动因此不会收到旧的中断。

3.2.2 pci_enable_msi_block

int pci_enable_msi_block(struct pci_dev *dev, int count)这个接口是pci_enable_msi的变种,它可以请求多个MSI中断。MSI规范要求中断必须以2的幂分配,最多为2^5(32)。
如果函数返回0,则表示成功分配了不少于驱动请求的中断数量(可能会大于请求数量)。该函数打开设备的MSI,并将中断组的最小编号赋给dev->irq,该设备的中断范围为dev->irq至dev->irq + count - 1。
如果函数返回负值,表示设备无法提供更多的中断,驱动不应试图再去请求。如果返回的是正值,这个值会小于count,表示目前最分配的最大中断数量。对这于两种情况,函数都不会更新irq值,设备也不会切换到MSI模式。

设备驱动必须要正确处理上述第二种情况。在中断数量不够的情况下,有的设备尚可工作,驱动就应再次调用pci_enable_msi_block()。不过由于一些限制,第二次调用未必也可以成功。

3.2.3 pci_disable_msi

void pci_disable_msi(struct pci_dev *dev)
该函数的功能是撤消pci_enable_msi()或pci_enable_msi_block()的工作。它恢复dev->irq为引脚中断号,释放此前分配的MSI。中断号之后有可能分配给其它设备,因此驱动不应保留dev->irq的值。

驱动必须调用free_irq()以释放之前request_irq()分配的中断号。否则会产生BUG_ON(),设备将继续保持MSI使能,并泄露中断向量。

3.3 使用MSI-X

MSI-X能力比MSI更为灵活,它支持2048个中断,每个中断都可以单独控制。要支持这种能力,驱动必须使用结构数组struct msix_entry。

 view plain copy print ?
struct msix_entry { 
u16 vector; /* kernel uses to write alloc vector */ 
u16 entry; /* driver uses to specify entry */ 
} 

这个定义允许设备以分散的方式使用中断(比如使用中断3和1027,只需分配两个数组元素)。驱动负责填充数组各元素的entry部分,以使内核为其分配中断。不能给多个entry赋予相同的编号。

3.3.1 pci_enable_msix

int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec)
该函数向PCI子系统请求分配nvec个MSI中断。入参entries指向结构数组,元素个数不少于nvec。函数返回0表示调用成功,设备被切换至MSI-X中断模式,数组元素中的vector字段也被填充为中断号。驱动接下来为每个需要使用的vector调用request_irq()。
如果函数返回负值,则表示出错,驱动不应再从该设备申请分配MSI-X中断。如果返回的是正值,则表示最大可以分配的中断数。
该函数与pci_enable_msi()不同之处在于它不修改dev->irq,一旦MSI-X使能后,dev->irq这个中断号不会再产生中断。驱动需要记录已分配的MSI-X中断号,以保证后续的资源释放。
一般来说,驱动在设备初始化时调用一次该函数。
由于种种原因,内核有可能无法提供驱动所要求的中断数量,一个好的驱动应该能够处理可变数量的MSI-X中断。下面是一个例子:

static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec) 
{ while (nvec >= FOO_DRIVER_MINIMUM_NVEC) { rc = pci_enable_msix(adapter->pdev, adapter->msix_entries, nvec); if (rc > 0) nvec = rc; else return rc; } return -ENOSPC; 
} 

3.3.2 pci_disable_msix

void pci_disable_msix(struct pci_dev *dev)
这个函数的作用是撤销pci_enable_msix()的工作,它释放之前分配的MSI中断。同样,释放的中断号后续可能会分配给其它设备,驱动不应再记录使用这些中断号。

在调用这个函数之前,驱动必须调用free_irq()以释放request_irq()分配的中断号,否则会产生BUG_ON,设备将维持在MSI使能的状态,并泄漏中断向量。

3.3.3 MSI-X表

MSI-X能力指定了一个BAR及BAR内偏移量用于访问MSI-X表,这个地址由PCI子系统映射,驱动不应该直接访问。如果驱动想屏蔽或者开启一个中断,应该调用disable_irq()/enable_irq()。

3.4 处理同时支持MSI和MSI-X能力的设备

如果设备同时支持MSI和MSI-X,则可以运行在MSI模式或者MSI-X模式下,但不能同时运行,这是PCI规则的要求,因此PCI层也进行了 限制。在MSI-X使能的情况下调用pci_enable_msi()或者在MSI使能的情况下调用pci_enable_msix()将产生错误。如果 设备驱动在运行时希望在MSI和MSI-X之间切换,它必须先停止设备,然后将其切换为引脚中断模式,然后再通过pci_enable_msi()或pci_enable_msix()进入MSI或MSI-X模式。这种操作并不常见,在开发过程中用于调试/测试。

3.5 使用MSI的考虑

3.5.1 选择MSI-X和MSI

如果设备同时支持MSI-X和MSI能力,应优先考虑使用MSI-X。MSI-X支持1~2048间任意数量的中断,而MSI只支持32个中断(并 且必须是2的冪)。MSI中断必须是连续分配的,系统不能像MSI-X那样分配这么多的中断向量。在某些平台上,MSI中断只能发送给一个CPU组,MSI-X中断可以发给不同的CPU。

3.5.2 spinlock

多数驱动为每个设备定义了一个spinlock,在中断处理函数中取锁,对于引脚中断或者单个MSI中断,不需要禁用中断(Linux保证同一个中断不会 重入)。如果设备使用了多个中断,驱动在持锁期间必须禁用中断,否则在设备产生另一个中断时,驱动会递归取锁从而产生死锁。
有两个解决方法,一个是使用spin_lock_irqsave()或spin_lock_irq(),另一个是在request_irq()调用时指定IRQF_DISABLED,内核会在禁用中断的环境下完成整个中断处理过程。

如果MSI中断处理程序不在整个过程中持锁,使用第一种方法是最好的。如果想避免在中断禁用/使能状态间切换,则选择第二种方法。

3.6 如何得知设备的MSI/MSI-X已经使能

使用lspci -v。有些设备会显示"MSI"、“Message Signaled Interrupts"或者"MSI-X"能力,使能的会在前面显示+,禁用的会显示”-"。

4. MSI quirks

一些PCI芯片或设备不支持MSI,PCI子系统提供了三种方法禁用MSI:

  1. 全局禁用

  2. 禁用特定桥之下的所有设备

  3. 禁用某个设备

4.1 全局禁用

有些host芯片不能正确支持MSI,如果厂家在ACPI FADT表中明确了,Linux会自动禁用MSI。有些单板没有在这个表包含这样的信息,需要自己检测,这些都列在drivers/pci /quriks.c中的quirk_disable_all_msi()中了。

如果单板在MSI支持上有问题,可以在内核命令参数里加上pci=nomsi以禁用所有设备的MSI。

4.2 禁用特定桥之下的所有设备

有些PCI桥不能在总线间正确地传递MSI,这种情况必须禁用该桥之下所有设备的MSI。
有些桥允许通过配置空间的某些位来使能MSI。在可能的情况下,Linux会尽量打开host芯片的MSI支持。如果某个桥片Linux并不识别,而你确定它可以使用MSI,可以通过下面的命令打开MSI支持。
echo 1 > /sys/bus/pci/devices/$bridge/msi_bus
$bridge是桥的PCI地址(比如0000:00:0e.0)。

要禁用MSI,使用echo 0即可。

4.3 禁用某个设备

如果某些设备已知在MSI实现上有问题,一般是在设备驱动里处理,如果有必要,也可以在quirk里处理。

4.4 设备MSI被禁用的原因查找

除上述情况外,还有很多原因会导致一个设备的MSI没有使能,第一步应该仔细检查dmesg,看MSI有没有使能,还要检查CONFIG_PCI_MSI配置有没有打开。
通过lspci -t可以查看设备之上的桥,读取/sys/bus/pci/devices/*/msi_bus看MSI是否使能了。
检查设备驱动是否支持MSI,比如是否调用了pci_enable_msi(), pci_enable_msix()或者pci_enable_msi_block()等等。


http://www.ppmy.cn/news/292856.html

相关文章

MSI(Message Signaled Interrupt)

MSI是什么? MSI(Message Signaled Interrupts)是一种中断方式, 依靠设备将一小段中断描述数据写入特定地址 【注一】来通知CPU中断的产生。 MSI从PCI 2.2开始支持, 在PCI 3.0中得到扩展. 支持更多中断以及拥有独立配置各个中断能力的MSI-X则从PCI 3.0开始被支持. …

【嵌入式环境下linux内核及驱动学习笔记-(15)linux总线、设备、驱动模型之I2C总线】

目录 1、 I2C总线机制1.1 导入1.2 时序1.3 地址格式 2、华清fs4412上I2C的实现2.1 寄存器2.2 寄存器位具体含义2.3 fs4412上针对具本设备的I2C工作逻辑2.3.1 主机读写工作流程**2.3.1.1 主机发送时序及操作流程2.3.1.2 主机接收的时序及流程 2.3.2 从机读写工作流程 3、LINUX内…

【Android】WMS(二)Window的添加

软件盘相关模式 在 Android 应用开发中,软键盘的显示与隐藏是一个经常出现的问题,而 WindowManager 的 LayoutParams 中定义的软键盘相关模式则为开发者提供了一些解决方案。 其中,SoftInputMode 就是用于描述软键盘的显示方式和窗口的调整…

【Flutter】如何移除 Flutter 右上角的 DEBUG 标识

文章目录 一、前言二、什么是 DEBUG 标识三、为什么我们需要移除 DEBUG 标识四、如何移除 DEBUG 标识五、完整代码六、总结 一、前言 欢迎来到 Flutter 的世界!在这篇文章中,我们将探索 Flutter 的一些基础知识。但是,你知道吗?这…

诺基亚n9支不支持java_诺基亚N9支持720p播放吗

诺基亚N9可以播放720p视频。诺基亚N9采用1GHz德州仪器OMAP3630处理器,GPU型号为Imagination PowerVR SGX530,搭配1GB的运行内存 RAM,可以播放MP4和H.264格式的高清视频。 但是诺基亚N9有的视频还播不了,要另下播放器,同…

BlackBerry 多媒体播放编程

概述 移动多媒 体包括 使用移动 终端播放 音乐, 视频,拍 照,录制 视频, 和在线影 音 。 Bla ck Berry 支持 移动多媒 体,你 可以通 过 Bla ckB erry Java 或 Bla ck Berry 浏览器 来创建 自己 的媒体应 用。功 能包括…

专家看衰Windows Phone 8前景 诺基亚又要悲剧?

尽管诺基亚连续三个季度出现10亿美元以上的亏损,不过近来诺基亚的股价出现了强势反弹,股票价格目前已经在第二季度财报发布之后翻了两番。然而 Research的分析师Pierre Ferragu认为,股价之所以上涨,是受下周即将召开的诺基亚世界大…

诺基亚N97沃达丰多媒体设备无缝连接选件

诺基亚N97沃达丰多媒体设备无缝连接选件   诺基亚世界顶级手机品牌一直带给我们惊喜与它的辉煌和最新技术的产品。它已经抓住了移动通信市场的N系列。现在,这个品牌已经想出了另一个多媒体惊叹诺基亚N97手机是现在挤满了世界领先的沃达丰的网络。该网络连接方便了…