基于PCIE4C的数据传输(三)——遗留中断与MSI中断 一文介绍了遗留中断与MSI中断两种中断方式的代码实现,本文继续基于Xilinx Ultrascale+HBM VCU128开发板与linux(RHEL8.9),介绍MSIX中断方式的代码实现。本文分为MSIX中断简述、FPGA逻辑设计、驱动程序设计、上板测试四个部分。
MSIX中断简述
MSIX中断是PCIe3.0引入的中断方式,其主要目的是解决系统中空闲中断向量号碎片化的情况下,MSI中断方式要求中断向量必须连续分配进而导致无法分配足够多中断向量的问题,可参考《PCIe系列第八讲、MSI和MSI-X中断机制》。
MSIX方式为了解决中断向量不连续的问题,将申请每个中断所需的信息作为两张表(MSI-X Table及Pending Bit Table)存储到PCIe终端设备的BAR空间中。配置空间负责提供中断信息表所在的BAR空间编号(Table BIR)及所在BAR空间的基地址(Table Offset及PBA Offset)。
如下图所示,每条32字节(4 DWORD)的entry为1个MSIX中断表项,当PCIe终端设备想要引起对应的MSIX中断时,只需发送向对应地址(Msg Upper Addr + Msg Addr)写特定数据(Msg Data)的TLP包。
FPGA逻辑设计
PCIE4C IP核配置
MSIX中断可在前文IP核配置的基础上添加,在 功能 选项卡中的 MSI-X选项 部分,可选择外置MSI-X接口、内置MSI-X接口、AXI MSI-X接口三种方式,三种方式在IP核引脚上略有区别,本文介绍外置MSI-X接口方式的使用方法。
在启用MSI-X接口后,可于MSI-X功能部分配置MSI-X中断表与MSI-X暂挂表在指定BAR空间中的特定地址,本文选择使用如下配置,使用1个MSIX中断(大小32字节),中断表和暂挂表均在BAR0中,起始地址分别为0x40和0x50。
用户逻辑实现
MSIX的接口功能及时序要求可于PCIE4C手册查看,对应输出波形如下,其中addr、data需要填写响应中断系统分配并填入用户存储器的对应数据,以本文IP配置启用1个MSIX中断为例,想要发送中断需要向0x40-0x47存储的Msg addr发送0x48-0x4b存储的Msg Data(在系统分配中断完成后):
参考PCIE4C手册对于中断相关引脚的定义,最终状态机跳转实现如下:
always @(*) begincase (fsm_r)RESET: beginif (rst) beginfsm_s = RESET;end else beginfsm_s = IDLE;endendIDLE: beginif (irq_valid & irq_ready) beginif (|(irq_func & cfg_interrupt_msi_enable)) beginfsm_s = SEND_MSI_INTR;end else if (|(irq_func & cfg_interrupt_msix_enable)) beginfsm_s = SEND_MSIX_INTR;end else if (|(irq_func)) beginfsm_s = SEND_LEGACY_INTR;end else beginfsm_s = IDLE;endend else beginfsm_s = IDLE;endendSEND_LEGACY_INTR: beginif (cfg_interrupt_sent) beginfsm_s = WAIT_LEGACY_INTR;end else beginfsm_s = SEND_LEGACY_INTR;endendWAIT_LEGACY_INTR: beginif (cfg_interrupt_sent) beginfsm_s = IDLE;end else beginfsm_s = WAIT_LEGACY_INTR;endendSEND_MSI_INTR: beginfsm_s = WAIT_MSI_INTR;endWAIT_MSI_INTR: beginif (cfg_interrupt_msi_sent | cfg_interrupt_msi_fail) beginfsm_s = IDLE;end else beginfsm_s = WAIT_MSI_INTR;endendSEND_MSIX_INTR: beginfsm_s = WAIT_MSIX_INTR;endWAIT_MSIX_INTR: beginif (cfg_interrupt_msi_sent | cfg_interrupt_msi_fail) beginfsm_s = IDLE;end else beginfsm_s = WAIT_MSIX_INTR;endendendcaseend
驱动程序设计
MSIX中断驱动同样可参考Kernel.org,本文基于RHEL8.9,对应设置MSIX中断的驱动代码如下,从驱动代码上看,MSIX中断与MSI中断几乎一致:
printk("start create msix interrupt");interrupt_type = INTERRUPT_MSIX;ret = pci_alloc_irq_vectors(dev, MIN_VEC_NUM, MAX_VEC_NUM, PCI_IRQ_MSIX); // allocate specific amount of interruptsif (ret < MIN_VEC_NUM) { // real allocated interrupts amountprintk("cannot register enough irq %d", ret);goto irq_alloc_err; }pcieirq = pci_irq_vector(dev, 0); // get IRQ numberfree_irq(pcieirq, (void*)legacy_irq_handler); // clear exist pending interrupt (if any)ret = request_irq(pcieirq, legacy_irq_handler, 0, "test_driver", (void*)legacy_irq_handler); // associate handler and enable irqif (ret != 0) { printk("cannot register irq %d", ret);goto irq_alloc_err; }printk("finish create msix interrupt");
上板测试
Vivado查看MSIX中断接口相关波形
使用lspci -vv
查看PCIe链路情况
使用cat /proc/interrupts
查看中断情况
加载驱动并查看系统分配的MSIX中断表项内容,可以看到MSIX中断表项内容为0x00000000_00000021_00000000_FEE08000,根据MSIX中断表项的定义,系统分配给MSIX0中断的Msg Addr为0xffe08000,Msg Data为0x21。
完整代码
工程代码可于同名公众号回复PCIE4C_MSIX获取。