Linux下PCI设备驱动开发详解(五)
本章及其以后的几章,我们将从用户态软件、内核态驱动、FPGA逻辑介绍一个通过PCI Express总线实现CPU和FPGA数据通信的简单框架。
这个框架就是开源界非常有名的RIFFA(reuseable integration framework for FPGA accelerators),它是一个FPGA加速器的一种可重用性集成框架,是一个第三方开源PCIe框架。
该框架要求具备一个支持PCIe的工作站和一个带有PCIe连接器的FPGA板卡。RIFFA支持windows、linux,altera和xilinx,可以通过c/c++、python、matlab、java驱动来实现数据的发送和接收。驱动程序可以在linux或windows上运行,每一个系统最多支持5个FPGA device。
在用户端有独立的发送和接收端口,用户只需要编写几行简单代码即可实现和FPGA IP内核通信。
riffa使用直接存储器访问(DMA)传输和中断信号传输数据。这实现了PCIe链路上的高带宽,运行速率可以达到PCIe链路饱和点。
开源地址:https://github.com/KastnerRG/riffa
一、riffa体系结构
在硬件方面,简化了接口,以便通过FIFO简便的将数据取出或存入。数据的传输由riffa的rx和tx DMA engine模块用分散聚合方法来实现。rx engine模块收集上位机来的有效数据,收集完成发给channel模块,tx engine收集channel模块传送来的数据,打包发给PCI express端点。
在软件方面,PC接收FPGA板卡数据是用户应用程序调用库函数fpga_recv,然后由FPGA端启动。用户应用程序线程进入内核驱动程序,然后开始接收上游FPGA的读请求,将数据分包发送,如果没收到请求,将会等待它达到。
启动发送函数后,服务器将建立一个散列收集元素的列表,将数据存储地址和长度等信息放入其中,将其写入共享缓冲区。用户应用程序将缓冲区地址和数据长度等信息发送给FPGA。FPGA读取散射收集数据,然后发出相应地址的数据写入请求,如果散列收集元素列表的地址有多个,FPGA将通过中断发出多次请求。
TX搬移的数据全部写入缓存区后,驱动程序读取FPGA写入的字节数,确认是否与发送数据长度一致。这样就完成了传输。其过程如下图所示:
PC机发送数据到FPGA板卡过程与PC机接收FPGA板卡数据过程相似,下图说明了该流程。 刚开始也是用户应用程序调用库函数fpga_send,传输线程进入内核驱动程序,然后FPGA启动传输。
启动fpga_send,服务器将申请一些空间,将要发送的数据写入其中,然后建立一个分散收集列表,将存储数据的地址和长度放入其中,并将分散收集列表的地址和要发生的数据长度等信息发给FPGA。FPGA 收到列表地址后,读取该列表的信息,然后发出相应地址和长度的读请求,然后将数据存储,最后一起发给FPGA板卡。
驱动程序首先调用pci_present()检查PCI总线是否被linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到一个非0的返回值,那么驱动程序就必须得中止自己的任务。调用pci_register_driver()函数来注册PCI设备的驱动程序,此时需要提供一个“demo_pci_driver”结构,在该结构中给出的probe探测例程负责完成对硬件的检测工作。
下面将从user logic -> PCIe IP -> 驱动层 -> library-> 用户态C++/C按照实例一一进行分析,包括理论基础、实际操作、源代码分析。
首先我们先从FPGA xilinx服务器托管网 integrated block for PCI express分析,因为这个有承上启下的作用。
二、PCIe hard IP分析
1. PCIE IP 核配置
AXI总线时钟选择62.5M,AXI总线接口位宽设置为64bit。
在IDs界面是PCIe设备的相关信息,主机在上电时BIOS系统中识别到的PCIe设备,就是通过这些ID号来进行识别的。
在本实验中,关于ID的设定全部保持为默认值即可,若用户对ID进行了更改,可能导致计算机在启动时不能正确识别设备从而导致蓝屏死机。
Vendor ID是厂商ID,本实验中的厂商ID代值的就是Xilinx;
Device ID代表了PCIe设备,其中7指的是Xilinx 7 系列FPGA,02指的是使用的PCIe 2.0的协议,1指的是含有一个PCIe的传输lane;
Base class Menu指的是PCIe设备的种类,常见的有声卡、显卡、网卡等,各种不同种类的设备都有其对应的驱动,若驱动与其PCIe的种类不对应,就会导致系统的内存访问错误,从而导致蓝屏。
在PCIe的bars配置界面对PCIe的bar进行设置,BAR空间对应的就是在内存中开辟一段空间用于存放PCIe设备的信息。只使用到一个bar0一个bar且将其内存空间的大小设置为1k。
IP核的负载选择最大负载长度为512字节,勾选对数据进行缓冲。
在中断配置界面,取消勾选传统类型的中断,只选择消息类型的中断。
在share logic界面取消勾选包含其他逻辑,这样在PCIe的IP核中就包含了全部功能。
在IP核接口参数配置界面,只选择其中用于配置核控制的参数,这是由于riffa框架的特性所提供的。
下面代码是实际的top level,
PCIeGen1x4If64 PCIeGen1x4If64_i
(//---------------------------------------------------------------------
// PCI Express (pci_exp) Interface
//---------------------------------------------------------------------
// Tx
.pci_exp_txn ( PCI_EXP_TXN ),
.pci_exp_txp ( PCI_EXP_TXP ),
// Rx
.pci_exp_rxn ( PCI_EXP_RXN ),
.pci_exp_rxp ( PCI_EXP_RXP ),
//---------------------------------------------------------------------
// AXI-S Interface
//---------------------------------------------------------------------
// Common
.user_clk_out ( user_clk ),
.user_reset_out ( user_reset ),
.user_lnk_up ( user_lnk_up ),
.user_app_rdy ( user_app_rdy ),
// TX
.s_axis_tx_tready ( s_axis_tx_tready ),
.s_axis_tx_tdata ( s_axis_tx_tdata ),
.s_axis_tx_tkeep ( s_axis_tx_tkeep ),
.s_axis_tx_tuser ( s_axis_tx_tuser ),
.s_axis_tx_tlast ( s_axis_tx_tlast ),
.s_axis_tx_tvalid ( s_axis_tx_tvalid ),
// Rx
.m_axis_rx_tdata ( m_axis_rx_tdata ),
.m_axis_rx_tkeep ( m_axis_rx_tkeep ),
.m_axis_rx_tlast ( m_axis_rx_tlast ),
.m_axis_rx_tvalid ( m_axis_rx_tvalid ),
.m_axis_rx_tready ( m_axis_rx_tready ),
.m_axis_rx_tuser ( m_axis_rx_tuser ),
.tx_cfg_gnt ( tx_cfg_gnt ),
.rx_np_ok ( rx_np_ok ),
.rx_np_req ( rx_np_req ),
.cfg_trn_pending ( cfg_trn_pending ),
.cfg_pm_halt_aspm_l0s ( cfg_pm_halt_aspm_l0s ),
.cfg_pm_halt_aspm_l1 ( cfg_pm_halt_aspm_l1 ),
.cfg_pm_force_state_en ( cfg_pm_force_state_en ),
.cfg_pm_force_state ( cfg_pm_force_state ),
.cfg_dsn ( cfg_dsn ),
.cfg_turnoff_ok ( cfg_turnoff_ok ),
.cfg_pm_wake ( cfg_pm_wake ),
.cfg_pm_send_pme_to ( 1'b0 ),
.cfg_ds_bus_number ( 8'b0 ),
.cfg_ds_device_number ( 5'b0 ),
.cfg_ds_function_number ( 3'b0 ),
//---------------------------------------------------------------------
// Flow Control Interface
//---------------------------------------------------------------------
.fc_cpld ( fc_cpld ),
.fc_cplh ( fc_cplh ),
.fc_npd ( fc_npd ),
.fc_nph ( fc_nph ),
.fc_pd ( fc_pd ),
.fc_ph ( fc_ph ),
.fc_sel ( fc_sel ),
//---------------------------------------------------------------------
// Configuration (CFG) Interface
//---------------------------------------------------------------------
.cfg_device_number ( cfg_device_number ),
.cfg_dcommand2 ( cfg_dcommand2 ),
.cfg_pmcsr_pme_status ( cfg_pmcsr_pme_status ),
.cfg_status ( cfg_status ),
.cfg_to_turnoff ( cfg_to_turnoff ),
.cfg_received_func_lvl_rst ( cfg_received_func_lvl_rst ),
.cfg_dcommand ( cfg_dcommand ),
.cfg_bus_number ( cfg_bus_number ),
.cfg_function_number ( cfg_function_number ),
.cfg_command ( cfg_command ),
.cfg_dstatus ( cfg_dstatus ),
.cfg_lstatus ( cfg_lstatus ),
.cfg_pcie_link_state ( cfg_pcie_link_state ),
.cfg_lcommand ( cfg_lcommand ),
.cfg_pmcsr_pme_en ( cfg_pmcsr_pme_en ),
.cfg_pmcsr_powerstate ( cfg_pmcsr_powerstate ),
//------------------------------------------------//
// EP Only //
//-------服务器托管网-----------------------------------------//
.cfg_interrupt ( cfg_interrupt ),
.cfg_interrupt_rdy ( cfg_interrupt_rdy ),
.cfg_interrupt_assert ( cfg_interrupt_assert ),
.cfg_interrupt_di ( cfg_interrupt_di ),
.cfg_interrupt_do ( cfg_interrupt_do ),
.cfg_interrupt_mmenable ( cfg_interrupt_mmenable ),
.cfg_interrupt_msienable ( cfg_interrupt_msienable ),
.cfg_interrupt_msixenable ( cfg_interrupt_msixenable ),
.cfg_interrupt_msixfm ( cfg_interrupt_msixfm ),
.cfg_interrupt_stat ( cfg_interrupt_stat ),
.cfg_pciecap_interrupt_msgnum ( cfg_pciecap_interrupt_msgnum ),
//---------------------------------------------------------------------
// System (SYS) Interface
//---------------------------------------------------------------------
.sys_clk ( pcie_refclk ),
.sys_rst_n ( pcie_reset_n )
);
从顶层代码接口可以看出来,TX/RX差分信号、AXIS数据common接口信号、tx/rx数据面信号、FC流控信号、configuration(CFG)interface、EP中断信号、系统时钟/复位信号。
详细使用参考xilinx PCIe spec官方文档。
PCIe IP位于整个设计架构的这个位置:
2. tx_engine和rx_engine
下面我们分析一下tx_engine和rx_engine这两个核心模块,这两个模块负责转换axis data和tlp data。
我们先看一下top level的源代码:
riffa_wrapper_ac701
#(/*AUTOINSTPARAM*/
// Parameters
.C_LOG_NUM_TAGS (C_LOG_NUM_TAGS),
.C_NUM_CHNL (C_NUM_CHNL),
.C_PCI_DATA_WIDTH (C_PCI_DATA_WIDTH),
.C_MAX_PAYLOAD_BYTES (C_MAX_PAYLOAD_BYTES))
riffa
(
// Outputs
.CFG_INTERRUPT (cfg_interrupt),
.M_AXIS_RX_TREADY (m_axis_rx_tready),
.S_AXIS_TX_TDATA (s_axis_tx_tdata[C_PCI_DATA_WIDTH-1:0]),
.S_AXIS_TX_TKEEP (s_axis_tx_tkeep[(C_PCI_DATA_WIDTH/8)-1:0]),
.S_AXIS_TX_TLAST (s_axis_tx_tlast),
.S_AXIS_TX_TVALID (s_axis_tx_tvalid),
.S_AXIS_TX_TUSER (s_axis_tx_tuser[`SIG_XIL_TX_TUSER_W-1:0]),
.FC_SEL (fc_sel[`SIG_FC_SEL_W-1:0]),
.RST_OUT (rst_out),
.CHNL_RX (chnl_rx[C_NUM_CHNL-1:0]),
.CHNL_RX_LAST (chnl_rx_last[C_NUM_CHNL-1:0]),
.CHNL_RX_LEN (chnl_rx_len[(C_NUM_CHNL*`SIG_CHNL_LENGTH_W)-1:0]),
.CHNL_RX_OFF (chnl_rx_off[(C_NUM_CHNL*`SIG_CHNL_OFFSET_W)-1:0]),
.CHNL_RX_DATA (chnl_rx_data[(C_NUM_CHNL*C_PCI_DATA_WIDTH)-1:0]),
.CHNL_RX_DATA_VALID (chnl_rx_data_valid[C_NUM_CHNL-1:0]),
.CHNL_TX_ACK (chnl_tx_ack[C_NUM_CHNL-1:0]),
.CHNL_TX_DATA_REN (chnl_tx_data_ren[C_NUM_CHNL-1:0]),
// Inputs
.M_AXIS_RX_TDATA (m_axis_rx_tdata[C_PCI_DATA_WIDTH-1:0]),
.M_AXIS_RX_TKEEP (m_axis_rx_tkeep[(C_PCI_DATA_WIDTH/8)-1:0]),
.M_AXIS_RX_TLAST (m_axis_rx_tlast),
.M_AXIS_RX_TVALID (m_axis_rx_tvalid),
.M_AXIS_RX_TUSER (m_axis_rx_tuser[`SIG_XIL_RX_TUSER_W-1:0]),
.S_AXIS_TX_TREADY (s_axis_tx_tready),
.CFG_BUS_NUMBER (cfg_bus_number[`SIG_BUSID_W-1:0]),
.CFG_DEVICE_NUMBER (cfg_device_number[`SIG_DEVID_W-1:0]),
.CFG_FUNCTION_NUMBER (cfg_function_number[`SIG_FNID_W-1:0]),
.CFG_COMMAND (cfg_command[`SIG_CFGREG_W-1:0]),
.CFG_DCOMMAND (cfg_dcommand[`SIG_CFGREG_W-1:0]),
.CFG_LSTATUS (cfg_lstatus[`SIG_CFGREG_W-1:0]),
.CFG_LCOMMAND (cfg_lcommand[`SIG_CFGREG_W-1:0]),
.FC_CPLD (fc_cpld[`SIG_FC_CPLD_W-1:0]),
.FC_CPLH (fc_cplh[`SIG_FC_CPLH_W-1:0]),
.CFG_INTERRUPT_MSIEN (cfg_interrupt_msienable),// TODO: Rename
.CFG_INTERRUPT_RDY (cfg_interrupt_rdy),
.USER_CLK (user_clk),
.USER_RESET (user_reset),
.CHNL_RX_CLK (chnl_rx_clk[C_NUM_CHNL-1:0]),
.CHNL_RX_ACK (chnl_rx_ack[C_NUM_CHNL-1:0]),
.CHNL_RX_DATA_REN (chnl_rx_data_ren[C_NUM_CHNL-1:0]),
.CHNL_TX_CLK (chnl_tx_clk[C_NUM_CHNL-1:0]),
.CHNL_TX (chnl_tx[C_NUM_CHNL-1:0]),
.CHNL_TX_LAST (chnl_tx_last[C_NUM_CHNL-1:0]),
.CHNL_TX_LEN (chnl_tx_len[(C_NUM_CHNL*`SIG_CHNL_LENGTH_W)-1:0]),
.CHNL_TX_OFF (chnl_tx_off[(C_NUM_CHNL*`SIG_CHNL_OFFSET_W)-1:0]),
.CHNL_TX_DATA (chnl_tx_data[(C_NUM_CHNL*C_PCI_DATA_WIDTH)-1:0]),
.CHNL_TX_DATA_VALID (chnl_tx_data_valid[C_NUM_CHNL-1:0]),
.RX_NP_OK (rx_np_ok),
.TX_CFG_GNT (tx_cfg_gnt),
.RX_NP_REQ (rx_np_req)
/*AUTOINST*/);
这个模块可以通过C_NUM_CHNL配置通道个数,C_PCI_DATA_WIDTH配置PCIe data的位宽,C_LOG_NUM_TAGS配置PCIe tag的个数。
module riffa_wrapper_ac701负责将xilinx PCIe IP的TX、RX、Configuration、flow control、中断信号转换为riffa的输入输出信号。
阅读riffa_wrapper_ac701.v源代码发现这个模块分为两个模块:
translation_xilinx
engine_layer
riffa
transtion_xilinx:负责提供统一的PCIe接口信息,比如altera、xilinx;
engine_layer:负责封装了tx_engine和rx_engine。其中tx engine负责上传DMA通道(写通道),即Interface: TX Classic;rx engine负责下发DMA通道(读通道),即Interface: RX Classic;
riffa:负责将tx/rx engine的信号转换为user logic的通道信号,方便我们使用,接口代码如下:
input [C_NUM_CHNL-1:0] CHNL_RX_CLK,
output [C_NUM_CHNL-1:0] CHNL_RX,
input [C_NUM_CHNL-1:0] CHNL_RX_ACK,
output [C_NUM_CHNL-1:0] CHNL_RX_LAST,
output [(C_NUM_CHNL*32)-1:0] CHNL_RX_LEN,
output [(C_NUM_CHNL*31)-1:0] CHNL_RX_OFF,
output [(C_NUM_CHNL*C_PCI_DATA_WIDTH)-1:0] CHNL_RX_DATA,
output [C_NUM_CHNL-1:0] CHNL_RX_DATA_VALID,
input [C_NUM_CHNL-1:0] CHNL_RX_DATA_REN,
input [C_NUM_CHNL-1:0] CHNL_TX_CLK,
input [C_NUM_CHNL-1:0] CHNL_TX,
output [C_NUM_CHNL-1:0] CHNL_TX_ACK,
input [C_NUM_CHNL-1:0] CHNL_TX_LAST,
input [(C_NUM_CHNL*32)-1:0] CHNL_TX_LEN,
input [(C_NUM_CHNL*31)-1:0] CHNL_TX_OFF,
input [(C_NUM_CHNL*C_PCI_DATA_WIDTH)-1:0] CHNL_TX_DATA,
input [C_NUM_CHNL-1:0] CHNL_TX_DATA_VALID,
output [C_NUM_CHNL-1:0] CHNL_TX_DATA_REN
对应整个实际框架的这一部分,如下图所示:
3. user logic
经过tx engine和rx engine模块,输出CHNL_RX_*和CHNL_TX_*信号,下面我们看一下user如何使用的,源代码如下:
generate
for (chnl = 0; chnl
这个模块的用于执行RIFFA TX 和 RX 接口。在RX接口上接收数据并保存最后接收的值。在TX接口上发送回相同数量的数据。返回的数据从接收到的最后一个值开始,重置并递增,直到等于TX接口上发回的(4字节)字数的值结束。
三、总结
这篇文章通过经典开源项目RIIFA的FPGA部分,结合源代码详细分析了框架部分的Xilinx PCIe hard IP、TX/RX engine和RIFFA模块,最后结合了user logic部分的chnl_tester,介绍了如何使用CHNL_TX_*和CHNL_RX_*接口。
四、未完待续
Linux下PCI设备驱动开发详解(六),将结合经典开源项目RIIFA,详细介绍内核态驱动的设计、开发、安装等。
五、参考资料
https://blog.csdn.net/qq_41332806/article/details/105864632
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
今天是2023年09月16日,服务器托管网以下是为您准备的15条信息差 第一、天猫超市首单“茅小凌”已由菜鸟送达,首单已由菜鸟供应链完成履约,18分钟送达消费者手中 第二、软银考虑对OpenAI进行投资。此外,软银还初步拟收购英国人工智能芯片制造商 Graph…