MSI Howto中文版

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 Package Builder Tools Overview For creating MSI installation packages, several tools offer comprehensive support for this task. One popular choice is **WiX Toolset**, which allows developers to create Windows Installer files from XML source code descriptions of the resources that need installing. Another widely used option is **InstallShield**, known for providing both graphical user interface and command-line capabilities. This tool supports not only MSI creation but also offers advanced features like multi-platform deployment options and robust customization abilities[^3]. Additionally, **Advanced Installer** serves as another powerful alternative offering an intuitive UI along with extensive documentation and community support. It simplifies complex tasks such as registry modifications or service installations within the MSI format[^4]. #### Example Using WiX Toolset To demonstrate how one might use WiX Toolset for building an MSI: ```xml <?xml version="1.0" encoding="UTF-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Product Id="*" Name="Example Product Name" Language="1033" Version="1.0.0.0" Manufacturer="Example Company Name" UpgradeCode="PUT-GUID-HERE"> <Package InstallerVersion="500" Compressed="yes"/> <!-- Define directory structure --> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="INSTALLFOLDER" Name="ExampleProductName"/> </Directory> </Directory> <!-- Add files to install --> <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> <Component Id="MainExecutable"> <File Source="$(var.SolutionDir)\bin\$(var.Configuration)\example.exe"/> </Component> </ComponentGroup> <!-- Specify feature(s) --> <Feature Id="DefaultFeature" Level="1"> <ComponentRef Id="MainExecutable"/> </Feature> </Product> </Wix> ``` This example demonstrates setting up a basic Wix script to define what should be installed on target machines when deploying via an MSI package.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值