PCIe 的 MSI-X 中断
前言
- 什么是 MSI-X 中断
MSI-X 中断与 MSI 中断类似,是 MSI 中断在使用中的升级优化版本。为了解决 MSI 中断的一些限制而优化。主要解决了如下限制:a. MSI 只支持 32 个中断,因为 MSI 相关的配置寄存器都在 EP 的配置空间里,这个空间有限。b. MSI 要求中断向量号必须连续。
MSI-X 初始化配置流程
- 找到 msix-cap
void pci_msi_setup_pci_dev(struct pci_dev *dev)
MSI-X 能力 ID :PCI_CAP_ID_MSIX 0x11 /* MSI-X */
这个函数会去读 EP 的配置空间,从配置空间获取 capability。PCIE 定义了这么些 CAP
#define PCI_CAP_ID_PM 0x01 /* Power Management */
#define PCI_CAP_ID_AGP 0x02 /* Accelerated Graphics Port */
#define PCI_CAP_ID_VPD 0x03 /* Vital Product Data */
#define PCI_CAP_ID_SLOTID 0x04 /* Slot Identification */
#define PCI_CAP_ID_MSI 0x05 /* Message Signalled Interrupts */
#define PCI_CAP_ID_CHSWP 0x06 /* CompactPCI HotSwap */
#define PCI_CAP_ID_PCIX 0x07 /* PCI-X */
#define PCI_CAP_ID_HT 0x08 /* HyperTransport */
#define PCI_CAP_ID_VNDR 0x09 /* Vendor-Specific */
#define PCI_CAP_ID_DBG 0x0A /* Debug port */
#define PCI_CAP_ID_CCRC 0x0B /* CompactPCI Central Resource Control */
#define PCI_CAP_ID_SHPC 0x0C /* PCI Standard Hot-Plug Controller */
#define PCI_CAP_ID_SSVID 0x0D /* Bridge subsystem vendor/device ID */
#define PCI_CAP_ID_AGP3 0x0E /* AGP Target PCI-PCI bridge */
#define PCI_CAP_ID_SECDEV 0x0F /* Secure Device */
#define PCI_CAP_ID_EXP 0x10 /* PCI Express */
#define PCI_CAP_ID_MSIX 0x11 /* MSI-X */
#define PCI_CAP_ID_SATA 0x12 /* SATA Data/Index Conf. */
#define PCI_CAP_ID_AF 0x13 /* PCI Advanced Features */
#define PCI_CAP_ID_EA 0x14 /* PCI Enhanced Allocation */
+-----------------+ +----------------------+
|offset=0x34 | | || |
| CAP-Pointer +----------------> CAP-Pr || CPA-ID |
| | | || |
+-----------------+ +----+-----------------+
|
|
+----v-----------------+
| || |
> CAP-Pr || CPA-ID |
| || |
+----+-----------------+
|
|
+----v-----------------+
| || |
> CAP-Pr || CPA-ID |
| || |
+----------------------+
规范中规定 EP 的配置空间 0x34 的地址存放了第一个 CAP 的地址偏移,后续的 CAP 都如此串成链,像一个链表,有 next 指针,有携带的值(CAP-ID)。CAP ID标识该地址存放的 CAP 类型。
题外话:解释 MSI 要求中断向量连续?PCIe 规范里并没有明确规定。
2. 分析 MSI-X cap
主要分析 Table BIR,这个寄存器是控制将 MSI-X Table Structure 存放在 BAR 内存空间的位置的。Table BIR 有 3Bit,指示 MSI-X Table Structure 将映射到 BARn 空间的某一个,Table Offset 指示BARn基地址的偏移。PBA 的使用方法相同。然后在 CPU 的对应 RAM 空间来初始化如下结构,即完成了 MSI-X 的结构初始化。
3. 代码分析
- 读取 MSI CAP 能力
pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
> pci_init_capabilities(dev);
> pci_msi_setup_pci_dev(dev);
> dev->msi_cap = pci_find_capability(dev, PCI_CAP_ID_MSI);
> dev->msix_cap = pci_find_capability(dev, PCI_CAP_ID_MSIX);
- 初始化 MSI-X Table Structure
static int msix_capability_init(struct pci_dev *dev, struct msix_entry *entries,
int nvec, struct irq_affinity *affd)
{
void __iomem *base;
int ret, tsize;
u16 control;
/*
* Some devices require MSI-X to be enabled before the MSI-X
* registers can be accessed. Mask all the vectors to prevent
* interrupts coming in before they're fully set up.
*/
pci_msix_clear_and_set_ctrl(dev, 0, PCI_MSIX_FLAGS_MASKALL |
PCI_MSIX_FLAGS_ENABLE);
pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &control);
/* Request & Map MSI-X table region */
tsize = msix_table_size(control);
base = msix_map_region(dev, tsize);
if (!base) {
ret = -ENOMEM;
goto out_disable;
}
ret = msix_setup_entries(dev, base, entries, nvec, affd);
if (ret)
goto out_disable;
ret = pci_msi_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX);
if (ret)
goto out_avail;
/* Check if all MSI entries honor device restrictions */
ret = msi_verify_entries(dev);
if (ret)
goto out_free;
msix_update_entries(dev, entries);
ret = populate_msi_sysfs(dev);
if (ret)
goto out_free;
/* Set MSI-X enabled bits and unmask the function */
pci_intx_for_msi(dev, 0);
dev->msix_enabled = 1;
/*
* Ensure that all table entries are masked to prevent
* stale entries from firing in a crash kernel.
*
* Done late to deal with a broken Marvell NVME device
* which takes the MSI-X mask bits into account even
* when MSI-X is disabled, which prevents MSI delivery.
*/
msix_mask_all(base, tsize);
pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0);
pcibios_free_irq(dev);
return 0;
out_avail:
if (ret < 0) {
/*
* If we had some success, report the number of IRQs
* we succeeded in setting up.
*/
struct msi_desc *entry;
int avail = 0;
for_each_pci_msi_entry(entry, dev) {
if (entry->irq != 0)
avail++;
}
if (avail != 0)
ret = avail;
}
out_free:
free_msi_irqs(dev);
out_disable:
pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL | PCI_MSIX_FLAGS_ENABLE, 0);
return ret;
}
分析代码:
msix_map_region(struct pci_dev *dev, unsigned nr_entries)
这个函数是映射 Table Offset + Table BIR 指定的地址。msix_setup_entries(dev, base, entries, nvec, affd);
这个函数是初始化内存结构的struct msi_desc *entry
,将其初始化完成。pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
这个函数是配置 IRQ,最后会调用到__msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev, int nvec)
, 这个函数会 prepare_irqs,alloc_irqs, 最后 activate_irq。此时中断已经生效了,中断使能已经写入了 EP 设备了。msix_update_entries(dev, entries);
更新获取的 virqmsix_mask_all(base, tsize);
初始化完成之后屏蔽所有的 MSI-X 中断。
其他:MSI irq_domain 是如何获取 ITS 中断的