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

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

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

当设备向内存写入数据,然后发起引脚中断时,有可能在CPU收到中断时,数据还未到达内存(在PCI-PCI桥后的设备更有可能如此)。为了保证数据已达内存,中断处理程序必须轮询产生该中断的设备的一个寄存器,PCI事务保序规则会确保所有数据到达内存后,寄存器才会返回值。

使用MSI时,产生中断的写不能越过数据写,因而避免了这个问题。当中断产生时,驱动可以确信所有数据已经到达内存。

PCI的每个功能设备只支持一个基于引脚的中断,驱动常常需要查询设备来确定发生的事件,降低了中断处理的效率。通过MSI,一个设备可以支持多个中断,这样可以为不同的使用不同的中断。比如:

1. 给不常发生的事件(如错误)指定独立的中断,这样驱动可以正常中断路径进行更有效的处理。

2. 给网卡的每个报文队列或者存储控制器的每个端口分配中断。

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。

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 Howto中文版相关推荐

  1. Linux TC流量控制HOWTO中文版

    <本文摘自Linux的高级路由和流量控制HOWTO中文版 第9章节>网人郭工进行再次编译: 利用队列,我们可以控制数据发送的方式.记住我们只能对发送数据进行控制(或称为整形). 其实,我们 ...

  2. (转) GPG入门教程

    转载至:http://www.ruanyifeng.com/blog/2013/07/gpg.html GPG入门教程 一.什么是GPG 要了解什么是GPG,就要先了解PGP. 1991年,程序员Ph ...

  3. C/C++ 类库开发库参考【资料整理】

    转自:http://bbs.chinaunix.net/thread-1858444-1-1.html 这里收集一些著名的 C/C++ 开发库.SDK.类库.可复用类与结构代码 等信息,列举它们的介绍 ...

  4. C/C++ Development Library

    这里收集一些著名的 C/C++ 开发库.SDK.类库.可复用类与结构代码 等信息,列举它们的介绍.参考和网站链接,为各位 C/C++ 程序员和爱好者提供检索和查阅类库的方便 下面收集的 C/C++ 类 ...

  5. 用GPG保卫你的重要文件-GnuPG/Gpg4win使用说明

    GnuPG官网: https://www.gnupg.org/ Gpg4win官网:https://www.gpg4win.org/ GnuPG教程: 官方中文帮助:https://www.gnupg ...

  6. 我自己关于C语言,编译器,标准库,GUN glibc,CRT ,API之类的理解。

    先声明,只是我自己看了一些东东的想法. 因为以前这一套东东太缠绕人了. C语言,是一种语言标准,和它对照的是C++,JAVA,BASIC,FORTRAN,PASCAL等等概念.它定义的是自己一套适用于 ...

  7. C++ 著名程序库 概览

    本文转载自: http://ace.acejoy.com/thread-3777-1-1.html 1.C++各大有名库的介绍--C++标准库 2.C++各大有名库的介绍--准标准库Boost 3.C ...

  8. 学llinux的资料

    MOOC https://freevideolectures.com/5-websites-learning-linux/ https://www.quora.com/What-are-the-bes ...

  9. 网上很牛逼的IT技术网站

    首先是比较著名的博客型的网站!一般来说在国外比较著名的博客基本上都是比较有影响力发起的或者建立的经常发布一些比较有思考力深入分析的文章! 博客媒体网站 1.www.ArsTechnica.com 2. ...

最新文章

  1. 信息安全系统设计基础家庭作业
  2. php二维数组按照自定义方式对键值排序,PHP 对一个给定的二维数组按照指定的键值进行排序...
  3. Html 教程 (9)列表
  4. 跟着JAMA论文学习重复测量资料分析方法
  5. 机器学习实践:TensorFlow最后一个epoch训练损失函数显著增大
  6. python 对excel文件进行分词并进行词频统计_python 词频分析
  7. 编程之美3——N!末尾有多少个0
  8. 2018-2019-1 20165211 实验四 外设驱动程序设计
  9. 让微信扫描直接下载你的APK
  10. 面试题 数组长度可否赋值
  11. 【源码阅读】看Spring Boot如何自动装配ActiveMQ收发组件
  12. 如何将数据库文件进行压缩
  13. 关于win10 64位安装 XP 32位注意事项
  14. Redis深度历险——原理与集群篇
  15. ERROR sqoop.Sqoop: Got exception running Sqoop: java.lang.NullPointerException 解决方案【SOLVED】
  16. 我只想保持本色,和少年的心气。
  17. 农业统计分析系列2-试验设计
  18. Mooc视频字幕提取
  19. Qpython SL4A服务调用GPS定位获取获取位置信息
  20. python批量采集欢乐之源——表情包数据~

热门文章

  1. java 模板方法_设计模式(java实现)_模板方法模式(Template method)
  2. SOLD格雷母线的简介和运用
  3. PTA-1001 害死人不偿命的(3n+1)猜想 (15 分)-python
  4. hdu2232 机器人的舞蹈
  5. 用纸箱做出来的机器人铠甲_萌萌哒!用废旧纸箱制作时装 小小“机器人”要当地球守护者...
  6. What's the DCOM?
  7. 净辐射的计算-landsat
  8. worm/spybot_通过Spybot搜索和销毁有效地消除恶意软件
  9. lodash 常用总结
  10. 易语言使用线程时钟后MYSQL_易语言多线程时钟周期 易语言三个时钟同时运行