在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()等等。

MSI及MSIX详解相关推荐

  1. PCIe中MSI和MSI-X中断机制详解

    目录 1.MSI和MSI-X中断机制 2.MSI和MSI-X对比 3.MSI/MSI-X Capability结构 3.1 MSI Capability结构 3.2 MSI消息格式及发送方式 3.3 ...

  2. Node.js卸载与重装,zip与msi安装详解

    Node js卸载与重装,zip与msi安装详解 文章目录 Node js卸载与重装,zip与msi安装详解 卸载 安装 选择msi下载安装 第一步: 第二步: 第三步: 选择zip压缩包安装(选择m ...

  3. win mysql5.7 msi_win10 安装 mysql 5.7 msi版的教程图文详解

    我装msi格式的,主要是想看看装完的my.ini, 文件位置C:\ProgramData\MySQL\MySQL Server 5.7\my.ini, 注意:ProgramData是隐藏文件夹 mys ...

  4. 43.XDMA寄存器详解7-MSI-X Vector Table and PBA寄存器组剖析及MSI-X中断详解

    目录 1.上节回顾 2.MSI-X Table and PBA由来 3.XDMA MSI-X Table and PBA寄存器 4.MSI和MSI-X中断机制 5.MSI和MSI-X对比 6.MSI/ ...

  5. NVMe协议详解(一)

    参考文档:NVME手册1.4a,下载网站. NVMe相关定义 queue pair 一对用来承载NVMe命令的队列对,由一个Submission Queue和一个Completion queue组成, ...

  6. NVMe协议详解(二)

    NVMe协议详解(二) 2. PCIe寄存器配置 2.1 PCIe总线的基本结构 2.2寄存器配置 2.2.1 PCI header 2.2.2 PCI Capabilities 2.2.3 PCI ...

  7. 使用VS2010编译MongoDB C++驱动详解

    最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.h ...

  8. SVN的Windows和Linux客户端操作详解

    SVN的Windows和Linux客户端操作详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Windows客户端操作 1.安装SVN客户端 a>.去官网下载svn软件 ...

  9. mysql file-pos_mysql-5.7 调整mysql的复制方式由master_log_file+master_log_pos 到gtid 详解

    一.祖传的master_log_file + master_log_pos的复制方式面临的问题: 在很久以前 那个时候我还没有出道,mysql就已经就有复制这个功能了.如果要告诉slave库从mast ...

最新文章

  1. java jms消息删除_activemq的topic消息一直都会存在数据库中,为什么不会删除
  2. 结构 win32_COM编程攻略(十五 持久化与结构化存储)
  3. android 找不到符号 符号 RequiresApi
  4. python cocos2d菜鸟教程_(译)cocos2d菜单教程:第一部分
  5. android m版本 root,Android M 最大看点:又少了一个 ROOT 的理由
  6. python结巴分词 词频统计_一个txt文档,已经用结巴分词分完词,怎么用python工具对这个分完词的文档进行计算统计词频,求脚本,非...
  7. 客户端码农学习ML —— Numpy基本用法
  8. itextpdf添加表格元素_java使用iText生成pdf表格详解
  9. Spring集成Redis集群(含spring集成redis代码)
  10. Andorid音频工具tinymix,tinyplay,tinycap使用
  11. js鼠标事件大全-Javascript鼠标事件大全
  12. expert php and mysql_Expert PHP and Mysql
  13. 一个比较完整的pytorch项目
  14. php异步学习(2)
  15. 谷歌街景15年乾坤大挪移!带你穿越法老的金字塔
  16. IP地址的分类及范围详解:A、B、C、D、E五类是如何划分的
  17. Vue3 script setup
  18. html5水墨效果,html5 canvas水墨风格的云雾动画特效
  19. jquery滚动条滚动事件_滚动条和jQuery –使用航点的事件处理
  20. 如何使用JMX_Expoter+Prometheus+Grafana监控Hadoop集群

热门文章

  1. 【数模比赛】2023美国大学生数学建模比赛(思路、代码......)
  2. 绝对值得收藏的,关于癌症的文章
  3. 留听阁--多线程系列之锁(十四)
  4. insightface tripletloss源码阅读
  5. linux系统日常管理
  6. 解决虚拟机Reason: The file is too large问题
  7. 有哪些你追了很多女生才明白的道理?
  8. one piece_娜美_01
  9. php解摩斯电码,PHP摩尔斯电码转换器
  10. uniapp 离线打包文档