1、查找保存MSI/MSI-x Capability地址
1.1、获取Capabilities Pointer指针的地址(__pci_bus_find_cap_start)
根据header type获取Capabilities Pointer在配置空间的偏移,header type 1的Capabilities Pointer如上图所示为13(13*4):
1.2、查找MSI-X Capability(pci_find_capability)
PCI Express Capability List Register如下,有个8位Capability ID,针对MSI-X Capability,该ID为0x11,还有一个Next Capability Pointer,指向下一个Capability在配置空间的地址,所有Capability通过Next Capability Pointer链接成一个单向链表,表头通过前面的Capabilities Pointer获取:
MSI-X Capability结构:
首先调用__pci_bus_find_cap_start,获取Capabilities Pointer在配置空间的地址:
然后调用__pci_find_next_cap查找Capability,直到找到MSI-X Capability或者超过TTL为止:
根据Capabilities Pointer、Next Capability Pointer遍历Capability链表,查找对应的Capability:
static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn,
u8 pos, int cap, int *ttl)
{
u8 id;
u16 ent;
pci_bus_read_config_byte(bus, devfn, pos, &pos); // 读取Capabilities Pointer的值,也就是第一个Capability在配置空间的地址
while ((*ttl)--) { // 循环查找Capability,直到*ttl为0,超过允许查找到最大次数
if (pos < 0x40)
break;
pos &= ~3; // 参考《PCI Local Bus Specification, Revision 3.0.pdf》P224“Capabilities Pointer”,低两位需要清零
pci_bus_read_config_word(bus, devfn, pos, &ent); // 读取Capability的Capability ID及Next Capability Pointer
id = ent & 0xff;
if (id == 0xff) // 判断Next Capability Pointer
break;
if (id == cap) // 如果id是要查找的Capability ID那么返回Capability地址
return pos; // 返回Capability地址
pos = (ent >> 8); // pos是16位,前面读取的Capability的Capability ID及Next Capability Pointer,高8位是Next Capability Pointer,for循环继续读取下个Capability
}
return 0;
}
函数调用栈如下:
1.3、保存MSI-X Capability地址(pci_msi_setup_pci_dev)
将查找到的MSI-X Capability地址保存到pci_dev->msix_cap。
2、MSI-X Capability初始化(msix_capability_init映射MSI‐X Table)
2.1、MSI-X Capability及MSI‐X Table Entries
Table Size、MSI-X Table offset、Table BIR如下,其中Table BIR表示MSI-X table在哪个BAR,MSI-X Table offset表示MSI-X table在BAR的偏移地址,Table Size表示MSI-X Table的大小:
MSI‐X Table Entries如下:
2.2、获取MSI‐X Table Size
读取Table Size的代码如下,比较简单,就是读取配置空间对应的值进行计算:
2.3、映射MSI‐X Table(msix_map_region)
映射MSI‐X Table的代码也比较简单,就是通过MSI-X Capability获取MSI‐X Table所在的BAR及在BAR的偏移地址,然后获取该BAR的cpu域地址,加上MSI‐X Table偏移就是MSI‐X Table的cpu域地址,映射完之后返回映射后的虚拟地址:
static void __iomem *msix_map_region(struct pci_dev *dev, unsigned nr_entries)
{
resource_size_t phys_addr;
u32 table_offset;
unsigned long flags;
u8 bir;
pci_read_config_dword(dev, dev->msix_cap + PCI_MSIX_TABLE,
&table_offset); // 读取MSI-X Table Offset及Table BIR
bir = (u8)(table_offset & PCI_MSIX_TABLE_BIR); // 获取BAR索引Table BIR
flags = pci_resource_flags(dev, bir);
if (!flags || (flags & IORESOURCE_UNSET))
return NULL;
table_offset &= PCI_MSIX_TABLE_OFFSET; // 获取MSI-X Table Offset
phys_addr = pci_resource_start(dev, bir) + table_offset; // pci_resource_start(dev, bir)获取BAR的cpu域地址(具体可以参考前面的《linux-5.10.110内核源码分析 - bcm2711 pcie BAR地址分配》),加table_offset就是MSI-X Table的cpu域地址
return ioremap(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); // 映射整个MSI-X Table,返回虚拟地址,通过该虚拟地址就可以像访问普通内存一样访问整个MSI-X Table
}
2.4、初始化MSI‐X Table Entry(msix_setup_entries)
msix_setup_entries主要就是为每个MSI‐X Table Entry分配一个msi_desc,msi_desc记录MSI‐X Table Entry的索引、MSI‐X Table地址、vector Mask Bit,最终msi_desc链接到了设备的msi_list链表里面:
static int msix_setup_entries(struct pci_dev *dev, void __iomem *base,
struct msix_entry *entries, int nvec,
struct irq_affinity *affd)
{
struct irq_affinity_desc *curmsk, *masks = NULL;
struct msi_desc *entry;
void __iomem *addr;
int ret, i;
int vec_count = pci_msix_vec_count(dev); // 获取MSI‐X Table Size
if (affd)
masks = irq_create_affinity_masks(nvec, affd);
for (i = 0, curmsk = masks; i < nvec; i++) {
entry = alloc_msi_entry(&dev->dev, 1, curmsk); // 分配一个msi_desc,用于描述一个MSI‐X Table Entry
if (!entry) {
if (!i)
iounmap(base);
else
free_msi_irqs(dev);
/* No enough memory. Don't try again */
ret = -ENOMEM;
goto out;
}
entry->msi_attrib.is_msix = 1;
entry->msi_attrib.is_64 = 1;
if (entries)
entry->msi_attrib.entry_nr = entries[i].entry;
else
entry->msi_attrib.entry_nr = i; // MSI‐X Table Entry索引
entry->msi_attrib.is_virtual =
entry->msi_attrib.entry_nr >= vec_count;
entry->msi_attrib.default_irq = dev->irq;
entry->mask_base = base; // MSI‐X Table的地址,不是当前MSI‐X Table Entry的地址,前面已经保存了MSI‐X Table Entry的索引,通过索引及MSI‐X Table的地址可以计算得到当前MSI‐X Table Entry的地址
addr = pci_msix_desc_addr(entry); // 获取当前MSI‐X Table Entry的地址(已经设置了entry的MSI‐X Table的地址及索引,根据这两个值计算得到当前MSI‐X Table Entry的地址)
if (addr)
entry->masked = readl(addr + PCI_MSIX_ENTRY_VECTOR_CTRL); // 读MSI‐X Table Entry的Vector Control,获取vector Mask Bit
list_add_tail(&entry->list, dev_to_msi_list(&dev->dev)); // 添加MSI‐X Table Entry到设备的msi_list链表里面,后续需要对设备的某个MSI‐X Table Entry操作的时候,通过索引在链表里面查找对应的MSI‐X Table Entry即可
if (masks)
curmsk++;
}
ret = 0;
out:
kfree(masks);
return ret;
}
3、MSI/MSI-x中断(MSI-X Table Entry分配irq_desc及硬件中断号)
3.1、为MSI-X Table Entry分配虚拟中断号(irq_domain_alloc_descs)
__msi_domain_alloc_irqs循环为每个MSI-X Table Entry分配虚拟中断号,一个virq对应一个irq_desc,用于描述一个irq中断:
调用栈:
3.2、为MSI-X Table Entry分配物理中断号(brcm_irq_domain_alloc)
调用brcm_irq_domain_alloc为MSI-X Table Entry的virq分配一个中断号,代码中的domain->host_data为bcm2711 msi控制指针,最终保存到irq_desc->irq_data->chip_data,里面包含msi消息地址:
调用栈:
将硬件中断号保存到irq_desc->irq_data->hwirq里面:
调用栈:
4、MSI/MSI-x中断(__msi_domain_alloc_irqs写MSI-X Table Entry)
4.1、激活irq(irq_domain_activate_irq)
循环遍历pci设备的msi_list,对每个MSI-X Table Entry调用irq_domain_activate_irq激活中断,也就是把每个MSI-X Table Entry的中断信息写入到MSI-X Table Entry:
4.2、获取MSI/MSI-x消息地址(brcm_msi_compose_msi_msg)
brcm_msi_compose_msi_msg通过irq_data获取brcm_msi,将brcm_msi消息地址保存到msi_msg里面,然后将硬件中断号也保存到msi_msg,硬件中断号也就是MSI/MSI-x的消息:
调用栈:
4.3、写MSI/MSI-x消息(__pci_write_msi_msg)
写MSI-X Table Entry的过程比较简单,前面初始化的时候为每个MSI-X Table Entry分配msi_desc的时候,在msi_desc里面保存了MSI-X Table的地址以及MSI-X Table Entry的索引,通过这两个即可获取到MSI-X Table Entry的地址,然后对相应的地址写数据即可。
void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
{
struct pci_dev *dev = msi_desc_to_pci_dev(entry);
if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) {
/* Don't touch the hardware now */
} else if (entry->msi_attrib.is_msix) {
void __iomem *base = pci_msix_desc_addr(entry); // 获取MSI-X Table Entry的地址(通过MSI-X Table地址与索引计算得到)
bool unmasked = !(entry->masked & PCI_MSIX_ENTRY_CTRL_MASKBIT);
if (!base)
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_desc_mask_irq(entry, PCI_MSIX_ENTRY_CTRL_MASKBIT);
writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR); // 将消息地址到低地址部分写入MSI-X Table Entry
writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR); // 将消息地址到高地址部分写入MSI-X Table Entry
writel(msg->data, base + PCI_MSIX_ENTRY_DATA); // 将消息值写入MSI-X Table Entry
if (unmasked)
__pci_msix_desc_mask_irq(entry, 0);
/* Ensure that the writes are visible in the device */
readl(base + PCI_MSIX_ENTRY_DATA);
} 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);
}
skip:
entry->msg = *msg;
if (entry->write_msi_msg)
entry->write_msi_msg(entry, entry->write_msi_msg_data);
}
5、MSI/MSI-x中断使能(pci_msi_unmask_irq)
5.1、使能MSI控制器中断(brcm_pcie_enable_msi)
pcie设备的消息发送到MSI控制器,MSI控制再发送中断到cpu,需要使能MSI控制中断:
调用栈:
5.2、使能MSI/MSI-x中断(__pci_msix_desc_mask_irq)
前面已经介绍在申请中断号的时候,MSI-X Table Entry的msi_desc已经保存到irq_data里面,使能中断的时候,通过中断号调用irq_to_desc即可获取到irq_desc,再获取irq_data,然后可以获取到MSI-X Table Entry的msi_desc。
写vector Mask Bit:
u32 __pci_msix_desc_mask_irq(struct msi_desc *desc, u32 flag)
{
u32 mask_bits = desc->masked; // 初始化的是已经把MSI-X Table Entryvector Mask Bit所在的整个word读取上来,vector Mask Bit仅占最低位
void __iomem *desc_addr;
if (pci_msi_ignore_mask)
return 0;
desc_addr = pci_msix_desc_addr(desc); // 获取MSI-X Table Entry的地址
if (!desc_addr)
return 0;
mask_bits &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT; // 清除mask_bits
if (flag & PCI_MSIX_ENTRY_CTRL_MASKBIT) // 禁止中断
mask_bits |= PCI_MSIX_ENTRY_CTRL_MASKBIT; // vector Mask Bit写1
writel(mask_bits, desc_addr + PCI_MSIX_ENTRY_VECTOR_CTRL); // 写MSI-X Table Entry的vector Mask Bit
return mask_bits;
}
调用栈:
6、MSI/MSI-x中断服务程序(brcm_pcie_msi_isr)
6.1、brcm_pcie_msi_isr中断调用栈
pcie设备发送消息的bcm2711的msi控制器,msi控制器发送中断到cpu,内核先调用msi中断处理函数brcm_pcie_msi_isr,如下图所示中断号180是msi控制器的硬件中断号:
6.2、获取MSI/MSI-x中断
通过读取bcm2711 msi中断状态寄存器,获取有哪些中断发生:
6.3、处理MSI/MSI-x中断
MSI_INT_STATUS中断寄存器的每个bit表示一个中断,从中断号初始化及中断处理代码看brcm_msi->used对应bit位为1,表示该中断号已经分配,中断号即为该bit位在brcm_msi->used对应的索引,MSI_INT_STATUS中断号同样是MSI_INT_STATUS对应bit位在MSI_INT_STATUS里面的索引,也就是pcie设备向msi控制器发送消息N,那么MSI_INT_STATUS的第N位会被设置为1,—— 没有具体芯片资料,从代码推理如此:
MSI Capability与MSI-X Capability大部分代码都是相同的,具体可以参考PCIe协议手册。