1、dts msi控制器描述
1.1、dts描述
msi: msi-controller1@1572000 {
compatible = "fsl,ls1012a-msi";
reg = <0x0 0x1572000 0x0 0x8>;
msi-controller;
interrupts = <0 126 IRQ_TYPE_LEVEL_HIGH>;
};
ls1012a msi控制器具体介绍可以参考官网手册”25.1.1 PCI Express MSI implementation
“,直接注册账号即可下载芯片手册。
1.2、说明
msi中断控制器中断号为126(这里需要加上32,具体内核会根据中断类型加上一个32的偏移,前面32个中断号为SGI、PPI,SPI实际中断为158,计算过程可以看内核其他代码)
有两个寄存器,Shared Message Signaled Interrupt Index Register和Shared Message Signaled Interrupt Register,pcie设备往Shared Message Signaled Interrupt Index Register写数据触发中断,cpu读Shared Message Signaled Interrupt Register清除中断,根据代码Shared Message Signaled Interrupt Register里面还包含中断信息,具体介绍参考芯片手册的“11.2.39 Shared message signaled interrupt register (PEX1MSIR)
”。
(里面的寄存器地址1571000地址不太清楚是不是写错了,根据调试内核代码以及dts描述,似乎应该是1572000)
2、msi寄存器映射
2.1、msi寄存器映射
寄存器0x1572000映射调用栈如下,phys_addr也就是0x1572000,msi寄存器的基地址,size为8,与dts描述一致:
2.2、MSI-X Capability查找
内核调用__pci_bus_find_cap_start获取第一个capability的地址,函数调用栈及代码如下:
上面函数栈返回PCI_CAPABILITY_LIST,也就如下Capabilities Pointer寄存器在配置空间的偏移34h,Capabilities Pointer指向第一个capability,每个capability有一个指针有个Next Capability Offset,指向下一个capability,Capabilities Pointer即可遍历pcie的所有capability:
对于msix的Capability ID为PCI_CAP_ID_MSIX,查找msix的capability的过程就是根据前面获取到Capabilities Pointer遍历capability,找到Capability ID为PCI_CAP_ID_MSIX的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);
while ((*ttl)--) {
if (pos < 0x40)
break;
pos &= ~3;
pci_bus_read_config_word(bus, devfn, pos, &ent);
id = ent & 0xff;
if (id == 0xff)
break;
if (id == cap)
return pos;
pos = (ent >> 8);
}
return 0;
}
ttl参数与网络一样,最多查找次数,超过*ttl指定的次数就结束查找;”pci_bus_read_config_byte(bus, devfn, pos, &pos)“获取capability在配置空间的地址,”pci_bus_read_config_word(bus, devfn, pos, &ent)“读取配置空间,里面包含Capability ID以及下一个capability的在配置空间的偏移,如下所示。
__pci_find_next_cap_ttl获取到一个capability的Capability ID之后,检查是否是需要查找的,如果不是查找的,则根据当前capability的Next Capability Offset读取下一个capability,直到找到对应Capability ID的capability或者*ttl减为0。找到之后返回capability在配置空间的偏移,结果保存到dev->msix_cap。
2.3、MSI-X Capability配置
MSI-X Capability Structures结构如下:
MSI-X Table Structure结构如下:
内核msix_map_region函数将MSI-X Table映射到虚拟地址,从代码看,MSI-X Table是在某个BAR空间里面,bir也就是BAR编号,pci_resource_start也就是获取bir BAR资源的起始地址,也就是cpu域地址,加上MSI-X Table的偏移table_offset即为MSI-X Table的cpu域地址,映射完之后,就可以通过虚拟地址访问MSI-X Table,虚拟地址将保存到msi_desc的mask_base里面,函数及调用栈如下,具体协议相关可以参考《PCI Local Bus Specification Revision 3.0》”6.8.2. MSI-X Capability and Table Structures“:
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);
bir = (u8)(table_offset & PCI_MSIX_TABLE_BIR);
flags = pci_resource_flags(dev, bir);
if (!flags || (flags & IORESOURCE_UNSET))
return NULL;
table_offset &= PCI_MSIX_TABLE_OFFSET;
phys_addr = pci_resource_start(dev, bir) + table_offset;
return ioremap(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE);
}
2.4、写MSI-X Table
内核调用__pci_write_msi_msg写MSI-X Table,告诉pcie设备,往哪个地址发送消息,这里就是要把msi控制寄存器的地址写到MSI-X Table里面,函数调用栈如下:
写MSI-X Table代码如下,其中base通过pci_msix_desc_addr获取,也就是前面msix_map_region映射后的MSI-X Table的虚拟地址;将msi控制器寄存器的高低地址以及Message Data分别写入,MSI-X Table,代码如下:
2.5、MSI-X中断使能
写Vector Control for MSI-X Table Entries使能/禁止中断,为1时禁止中断,为0时使能中断,Vector Control for MSI-X Table Entries详细说明如下:
函数代码及调用栈如下:
u32 __pci_msix_desc_mask_irq(struct msi_desc *desc, u32 flag)
{
u32 mask_bits = desc->masked;
void __iomem *desc_addr;
if (pci_msi_ignore_mask)
return 0;
desc_addr = pci_msix_desc_addr(desc);
if (!desc_addr)
return 0;
mask_bits &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT;
if (flag & PCI_MSIX_ENTRY_CTRL_MASKBIT)
mask_bits |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
writel(mask_bits, desc_addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
return mask_bits;
}
简单来说就是根据前面映射的MSI-X Table虚拟地址,找到Vector Control,然后往里面写0或者1。
3、中断处理
3.1、申请中断号ls_scfg_msi_domain_irq_alloc
首先设备驱动会申请一个虚拟中断号,然后调用ls_scfg_msi_domain_irq_alloc申请一个msi物理中断号,最后调用irq_domain_get_irq_data获取virq对应的irq_data,填充物理中断号等信息,物理中断号信息最终保存在irq_data里面,调用栈如下:
irq_domain_set_hwirq_and_chip代码如下:
3.2、中断号设置__pci_write_msi_msg
msi_domain_activate激活中断时,调用ls_scfg_msi_compose_msg设置msg,ls_scfg_msi_compose_msg的data参数也就是前面virq虚拟中断号对应的irq_data,从里面取出物理中断号,写到消息里面,代码如下:
static void ls_scfg_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)
{
struct ls_scfg_msi *msi_data = irq_data_get_irq_chip_data(data);
msg->address_hi = upper_32_bits(msi_data->msiir_addr);
msg->address_lo = lower_32_bits(msi_data->msiir_addr);
msg->data = data->hwirq;
if (msi_affinity_flag) {
const struct cpumask *mask;
mask = irq_data_get_effective_affinity_mask(data);
msg->data |= cpumask_first(mask);
}
iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg);
}
设置好消息之后,需要把消息写MSI-X Table,调用栈及函数代码如下:
(上面调用栈是初始化时的调用栈,在网卡open的时候,还会设置,过程是一样的)
3.3、 msi中断控制器中断ls_scfg_msi_irq_handler
msi中断号为158,中断处理函数为ls_scfg_msi_irq_handler,调用栈如下:
ls_scfg_msi_irq_handler中断处理代码如下,主要就是读msi寄存器,遍历为1的比特位,通过为1的比特位的位置计算对应的硬件中断号,然后根据硬件中断号获取虚拟中断号,最后调用对应的中断处理函数:
(芯片手册“11.2.39 Shared message signaled interrupt register (PEX1MSIR)
”及其他章节暂时没有找到具体寄存器与中断号的计算关系,只能从代码上推理)