当前位置:   article > 正文

基于PCIE4C的数据传输(三)——使用遗留中断与MSI中断

基于PCIE4C的数据传输(三)——使用遗留中断与MSI中断

   本文继续基于PCIE4C IP核实现主机(RHEL 8.9)与FPGA(Xilinx Ultrascale+HBM VCU128开发板)间DMA数据传输时的中断控制。本文分为三个部分:FPGA设计、驱动程序设计、上板测试。

FPGA设计

   基于PCIE4C的数据传输(二)——DMA一文实现了主机与FPGA间通过PCIe进行DMA数据传输:主机通过将DMA相关描述字段写入FPGA指定地址后,由FPGA工作进行批量的数据搬运,主机会每隔一段时间T后通过PCIe访问FPGA指定地址的DMA工作状态字段判断DMA是否完成。

   上述判断DMA是否完成的过程存在可以优化的地方,若主机等待时间T较小,主机通过PCIe访问FPGA指定地址的方式会占用CPU、PCIe总线的资源;若主机等待时间T较大,则会影响DMA传输的效率。为此,本文尝试使用中断解决该问题。

   PCIe协议中共存在三种中断方式:遗留中断(Legacy Interrupt)、MSI中断、MSIX中断。其功能按顺序越来越多,本文介绍PCIE4C提供的专用遗留中断与MSI中断接口的使用方式。

IP核配置及相关引脚说明

   遗留中断与MSI中断的配置需要进行如下配置,其中遗留中断继承自PCI协议,包括四个中断引脚INTA、INTB、INTC、INTD,每个PCIe设备只能使用1个中断引脚,这里均配置为INTA。MSI中断引入了中断向量的概念,个人认为相当于为每个PCIe设备引入了32个中断,本文仅使用1个中断,故配置为1 vector。

图片

   在完成上述配置后,IP核会引出pcie4_cfg_interrupt、pcie4_cfg_msi两组引脚,可结合手册相关章节使用。

   对于遗留中断,主要会用到如下四个信号,cfg_interrupt_int与cfg_interrupt_pending信号的四个比特由低到高对应INTA-D,由于配置选择INTA,因此这里只能使用0比特位。在cfg_interrupt_int与cfg_interrupt_pending信号拉高后,PCIE4C核会产生ASSERT_INTA的TLP报文给主机。在确认主机收到该报文后,cfg_interrupt_sent首次拉高。在经过一段合适的时间后(本文选择32个时钟周期),cfg_interrupt_int与cfg_interrupt_pending信号同时拉低,PCIE4C核会产生DEASSERT_INTA的TLP报文给主机,在确认主机收到该报文后,cfg_interrupt_sent二次拉高。中断过程结束。

图片

   由于遗留中断在主机启用MSI/MSIX中断后可以关闭,PCIE4C IP核提供了cfg_function_status信号用于告知用户逻辑遗留中断是否被启用。

   对于MSI中断,主要用到信号如下。

图片

FPGA代码实现

   根据上述描述,可写出FPGA相关代码,在每次DMA操作结束后,通过调用中断产生模块产生一个合适的中断。

图片

   状态机如下:

    always @(*) begin
        case (fsm_r)
        RESET: begin
            if (rst) begin
                fsm_s = RESET;
            end else begin
                fsm_s = IDLE;
            end
        end
        IDLE: begin
            if (irq_valid & irq_ready) begin
                case ({cfg_interrupt_msi_enable, cfg_interrupt_msix_enable})
                2'b00: begin    // legacy INTx
                    if (|(irq_func)) begin
                         fsm_s = SEND_LEGACY_INTR;
                    end else begin
                        fsm_s = IDLE;
                    end
                end
                2'b01: begin    // MSI-X
                    if (|(irq_func & cfg_interrupt_msix_enable)) begin
                        fsm_s = SEND_MSIX_INTR;
                    end else begin
                        fsm_s = IDLE;
                    end
                end
                2'b10,          // MSI
                2'b11: begin    // illegal, but msi
                    if (|(irq_func & cfg_interrupt_msi_enable)) begin
                        fsm_s = SEND_MSI_INTR;
                    end else begin
                        fsm_s = IDLE;
                    end
                end
                endcase
            end else begin
                fsm_s = IDLE;
            end
        end
        SEND_LEGACY_INTR: begin
            if (cfg_interrupt_sent) begin 
                fsm_s = WAIT_LEGACY_INTR;
            end else begin
                fsm_s=  SEND_LEGACY_INTR;
            end
        end
        WAIT_LEGACY_INTR: begin
            if (cfg_interrupt_sent) begin 
                fsm_s = IDLE;
            end else begin
                fsm_s = WAIT_LEGACY_INTR;
            end
        end
        SEND_MSI_INTR: begin
            fsm_s = WAIT_MSI_INTR;
        end
        WAIT_MSI_INTR: begin
            if (cfg_interrupt_msi_sent | cfg_interrupt_msi_fail) begin 
                fsm_s = IDLE;
            end else begin
                fsm_s = WAIT_MSI_INTR;
            end
        end
        default: fsm_s = RESET;
        endcase
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

驱动程序设计

   本文基于linux(RHEL8.9)开发,驱动程序的设计依然可参考Kernel.org关于PCI驱动的介绍,对于遗留中断向量号的获取,可参考StackOverflow

   对于遗留中断,可以采用如下方式使用:

            printk("start create legacy interrupt");
            // pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &pcieirq);
            pcieirq = dev->irq; 
            free_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 irq
            if (ret != 0) { 
                printk("cannot register irq %d", ret);
                goto irq_alloc_err; 
            }

            printk("finish create legacy interrupt");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

   对于MSI中断,可以使用如下方式使用:

            printk("start create msi interrupt");
            ret = pci_alloc_irq_vectors(dev, MIN_VEC_NUM, MAX_VEC_NUM, PCI_IRQ_MSI); // allocate specific amount of interrupts
            if (ret < MIN_VEC_NUM) { // real allocated interrupts amount
                printk("cannot register enough irq %d", ret);
                goto irq_alloc_err; 
            }

            pcieirq = pci_irq_vector(dev, 0); // get IRQ number

            free_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 irq
            if (ret != 0) { 
                printk("cannot register irq %d", ret);
                goto irq_alloc_err; 
            }

            printk("finish create msi interrupt");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上板测试

   对于遗留中断,每次DMA传输完成后产生波形如下:

图片

   在驱动侧,相关输出如下:

图片

图片

工程代码

   完整代码可于同名公众号回复PCIE4C_IRQ获取。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/519676
推荐阅读
相关标签
  

闽ICP备14008679号