MSI和MSIX中断详解以及驱动分析

MSI和MSIX中断详解以及驱动分析当前数据中心服务器 内存 显卡 网卡等设备均通过 PCIe 总线与 CPU 相连 而 PCIe 设备使用的最多的就是 MSI 和 MSI X 中断

大家好,欢迎来到IT知识分享网。

当前数据中心服务器,内存,显卡,网卡等设备均通过PCIe总线与CPU相连,而PCIe设备使用的最多的就是MSI和MSI-X中断。MSI中断是通过在内存写入信息来触发的一种消息中断类型,其中内存地址由硬件设备和系统协商决定。而MSI-X中断是在MSI中断的基础上扩展的一种消息类型,主要目的是解决MSI中断存在的一些限制。两者区别如下

类型 MSI MSI-X
中断数 32 2048
中断号 必须连续 可以任意分配
中断信息 存放于capbility寄存器 MSIX-table,存放BAR空间

下面将对MSI和MSI-X中断进行分析,最后会以笔者在工作中使用的MSI中断出现的单中断问题进行研究。

1.MSI中断 

下图为 MSI中断的Capabilities结构。

MSI和MSIX中断详解以及驱动分析

MSI Capability 的ID是5,共有四种组成方式,分别是32位和64位的Message结构。

Capability ID:记录msi capibility的ID号,固定为0x5

Next Cap Ptr:指向下一个新的Capability寄存器的地址

Message Address:系统软件配置此寄存器分配给PCI设备,CPU可产生中断的寄存器的地址;

Message Data: 系统软件配置此寄存器为符合CPU中断解析规则的内容,系统会在初始化MSI中断的时候写入中断向量号到Message Data。当PCIe设备需要上送中断时,就将Meeage Data中的中断向量号写入Message Address中。

Message Control Register:存放当前PCIe设备使用MSI机制进行中断请求的状态和控制信息。

MSI和MSIX中断详解以及驱动分析

MSI enable控制msi是否使能,Multiple Message Capable表示设备能够支持的中断向量数量,Multi Message表示实际使用的中断数量。可见MSI中断数量最大支持32哥。

MSI和MSIX中断详解以及驱动分析

以笔者的驱动设备,查看PCIe设备的MSI 的Capabilities。可见MSI的 Capabilities相对偏移为0x50。

 MSI和MSIX中断详解以及驱动分析

 对于网卡驱动,如果需要使用MSI中断,就需要通过probe函数进行MSI中断的申请,同时在open函数中完成中断的使能;

1.驱动调用接口申请msi中断:ret = pci_alloc_irq_vectors(pdev, msi_num + 1, msi_num + 1, PCI_IRQ_MSI);

查看该函数调用关系:

pci_alloc_irq_vectors pci_alloc_irq_vectors_affinity __pci_enable_msi_range msi_capability_init pci_msi_setup_msi_irqs arch_setup_msi_irqs // 调用硬件(如X86)相关的接口获得IRQ Domain信息,Domain负责将硬件中断ID映射到软件的IRQ Number(vector) native_setup_msi_irqs [ msi_domain_alloc_irqs ] [ msi_domain_alloc_irqs ] irq_domain_activate_irq ( __irq_domain_activate_irq ) msi_domain_activate ( domain->ops->activate ) irq_chip_write_msi_msg pci_msi_domain_write_msg (data->chip->irq_write_msi_msg) __pci_write_msi_msg

查看_pci_write_msi_msg函数,对于msi中断来说,它负责配置msi的message control,message addr,message data。一般message data的数据为中断向量,message data的数据有什么作用呢,它的主要作用是当PCIe设备需要上报中断时,会将message的data的数据写入message addr上,这样系统感知到message addr上数据有变化时,则会知道将中断传递给哪一个中断向量上。

void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) { ...... else { int pos = dev->msi_cap; u16 msgctl; pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl); msgctl &= ~PCI_MSI_FLAGS_QSIZE; msgctl |= entry->msi_attrib.multiple << 4; pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl); pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msg->address_lo); if (entry->msi_attrib.is_64) { pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, msg->address_hi); pci_write_config_word(dev, pos + PCI_MSI_DATA_64, msg->data); } else { pci_write_config_word(dev, pos + PCI_MSI_DATA_32, msg->data); } /* Ensure that the writes are visible in the device */ pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl); } ...... }

2.使能MSI中断:devm_request_irq。

2.MSI-X中断

与MSI Capability寄存器相比,MSI-X Capability寄存器使用一个数组存放Message Address字段和Message Data字段,而不是将这两个字段放入Capability寄存器中,这个数组称为MSI-X Table。从而当PCIe设备使用MSI-X机制时,每一个中断请求可以使用独立的Message Address字段和Message Data字段。可以通过lspci 查看msix table位于哪个bar空间以及相对偏移多少。

MSI和MSIX中断详解以及驱动分析

由上图可知,该PCIe网卡设备开放了30个MSI-X中断(驱动如果只需要30个以下的中断,那么对于网卡设备开放了30个中断也是可以的),并且中断已经使能,其MSI-X table位于bar 0空间处,相对偏移为0x4000,PBA也位于bar 0处,相对偏移在0xa000.

MSI和MSIX中断详解以及驱动分析

MSI和MSIX中断详解以及驱动分析

Message address和Message Upper address字段存放的是MSI-X memory write请求需要使用的地址。

Message Data字段存放的是MSI-X memory write请求需要使用的data。该地址和CPU的架构相关,是使能MSI-X时,系统软件写入的。

下图表示MSI-X table以及PBA表存放的位置,不一定在bar 0上也有可能在其他的bar上,主要看PCIe设备如何分配。

MSI和MSIX中断详解以及驱动分析

对于网卡驱动,如果需要使用msix中断,就需要通过probe函数进行msix中断的申请,同时在open函数中完成中断的使能;

1.通过pci_msix_vec_count获取当前pcie设备支持的msix中断总数,如果需要申请的msix中断小于支持的总数,则会有异常;

if (num_vectors > pci_msix_vec_count(pdev)) { return -1; }

2.调用pci_enable_msix_range函数分配msix table。

struct msix_entry { u16 vector; /* kernel uses to write allocated vector */ u16 entry; /* driver uses to specify entry, OS writes */ }; msix_entries = devm_kzalloc(ctrl->dev, (sizeof(struct msix_entry) * num_vectors), GFP_KERNEL); for (i = 0; i < num_vectors; i++) { msix_entries[i].entry = i; } total_vecs = pci_enable_msix_range(pdev, msix_entries, num_vectors, num_vectors); if(total_vecs < num_vectors) { ret = total_vecs; goto no_msix; }

3.根据msix_entry中的vector申请irq,这个步骤主要是将msix table的controller位打开,使得设备能够上送中断。

irq_num = msix_entries[0].vector; memset(name, 0, sizeof(name)); sprintf(name, "%s:%s-%d", RESOURCE_NAME, "dev", 0); ret = devm_request_irq(dev, irq_num, irq_handler, 0, name, dev);

查看pci_enable_msix_range函数的调用栈,可见最后会调用__pci_write_msi_msg函数进行初始化。

 __pci_write_msi_msg+1 msi_domain_activate+108 __irq_domain_activate_irq+85 irq_domain_activate_irq+45 __msi_domain_alloc_irqs+471 msi_domain_alloc_irqs+23 msix_capability_init+805 pci_enable_msix_range+339

查看一下该函数关于MSI-X部分。

void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) { ...... else if (entry->msi_attrib.is_msix) { void __iomem *base = pci_msix_desc_addr(entry); u32 ctrl = entry->msix_ctrl; bool unmasked = !(ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT); if (entry->msi_attrib.is_virtual) goto skip; /* * The specification mandates that the entry is masked * when the message is modified: * * "If software changes the Address or Data value of an * entry while the entry is unmasked, the result is * undefined." */ if (unmasked) pci_msix_write_vector_ctrl(entry, ctrl | PCI_MSIX_ENTRY_CTRL_MASKBIT); writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR); writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR); writel(msg->data, base + PCI_MSIX_ENTRY_DATA); if (unmasked) pci_msix_write_vector_ctrl(entry, ctrl); /* Ensure that the writes are visible in the device */ readl(base + PCI_MSIX_ENTRY_DATA); } ...... }

可见该函数关于MSI-X 部分主要是将地址写入MSI-X table表中的upper-addry以及lower-addr,同时将msg-data写入 MSI-X table表中的DATA部分。

那么msg-data又是从哪里获取呢。这里以irq_gcc_v3_its.c中文件进行说明(针对arm架构的服务器),可见它会把中断向量号传给msg->data,从而写入MSI-Xtable表中DATA部分。

static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) { struct its_device *its_dev = irq_data_get_irq_chip_data(d); struct its_node *its; u64 addr; its = its_dev->its; addr = its->get_msi_base(its_dev); msg->address_lo = lower_32_bits(addr); msg->address_hi = upper_32_bits(addr); msg->data = its_get_event_id(d); iommu_dma_compose_msi_msg(irq_data_get_msi_desc(d), msg); } static inline u32 its_get_event_id(struct irq_data *d) { struct its_device *its_dev = irq_data_get_irq_chip_data(d); return d->hwirq - its_dev->event_map.lpi_base; } 

3.MSI单中断分析

笔者在一个项目中使用了MSI中断,但在申请MSI中断的时候,发现只能申请一个,查看该PCIe设备的能力发现是具有支持32个MSI中断的能力的,那么笔者判断无法申请MSI多中断的原因不在硬件,而可能在操作系统的系统架构上。

本部分源码均来源于linux-5.15

驱动调用接口申请msi中断:ret = pci_alloc_irq_vectors(pdev, msi_num + 1, msi_num + 1, PCI_IRQ_MSI);

查看该函数调用栈:

pci_alloc_irq_vectors pci_alloc_irq_vectors_affinity __pci_enable_msi_range msi_capability_init pci_msi_setup_msi_irqs

最终定位到pci_msi_setup_msi_irqs函数,该函数的实现依赖于CONFIG_CPI_MSI_IRQ_DOMAIN.如果定义了CONFIG_PCI_MSI_IRQ_DOMAIN,则pci_msi_setup_msi_irqs进入上面的分支实现。查看ubuntu下的linux-header文件,CONFIG_PCI_MSI_IRQ_DOMAIN是定义的。

dev_get_msi_domain函数根据是否定义CONFIG_GENERIC_MSI_IRQ_DOMAIN,返回dev->msi_domain,或者NULL.

pci_msi_setup_msi_irqs最终会调用msi_domain_alloc_irqs和arch_setup_msi_irqs中一个。现将这两个函数分别分析。

 #ifdef CONFIG_PCI_MSI_IRQ_DOMAIN static int pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) { struct irq_domain *domain; domain = dev_get_msi_domain(&dev->dev); /*获取domain*/ if (domain && irq_domain_is_hierarchy(domain)) return msi_domain_alloc_irqs(domain, &dev->dev, nvec); return arch_setup_msi_irqs(dev, nvec, type); } static void pci_msi_teardown_msi_irqs(struct pci_dev *dev) { struct irq_domain *domain; domain = dev_get_msi_domain(&dev->dev); if (domain && irq_domain_is_hierarchy(domain)) msi_domain_free_irqs(domain, &dev->dev); else arch_teardown_msi_irqs(dev); } #else #define pci_msi_setup_msi_irqs arch_setup_msi_irqs #define pci_msi_teardown_msi_irqs arch_teardown_msi_irqs #endif 

而msi_domain_alloc_irqs的函数调用如下 

msi_domain_alloc_irqs ops->domain_alloc_irqs(domain, dev, nvec) msi_domain_prepare_irqs(domain, dev, nvec, &arg); __irq_domain_alloc_irqs

MSI和MSIX中断详解以及驱动分析

最终会调用msi_domain_prepare_irqs函数,重点看ops->msi_check函数实现。

int pci_msi_domain_check_cap(struct irq_domain *domain, struct msi_domain_info *info, struct device *dev) { struct msi_desc *desc = first_pci_msi_entry(to_pci_dev(dev)); /* Special handling to support __pci_enable_msi_range() */ if (pci_msi_desc_is_multi_msi(desc) && !(info->flags & MSI_FLAG_MULTI_PCI_MSI)) return 1; else if (desc->msi_attrib.is_msix && !(info->flags & MSI_FLAG_PCI_MSIX)) return -ENOTSUPP; return 0; }

最终指引到了激动人心的地方,MSI_FLAG_MULTI_MSI这个标志,如果info->flags没有多中断标志,则返回msi中断数量为1.

那么MSI_FLAG_MULTI_PCI_MSI标志如何产生的呢。

MSI和MSIX中断详解以及驱动分析

MSI和MSIX中断详解以及驱动分析

可见MSI_FLAG_MULTI_PCI_MSI来自pcie 的controller驱动。这表明,对于有些PCIe 的controller驱动并不支持MSI多中断。

最终笔者在考虑到有些主板可能无法申请到MSI多中断,从而最终决定全都使用MSI-X中断。 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125672.html

(0)
上一篇 2025-09-25 12:26
下一篇 2025-09-25 12:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信