Linux下PCI設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解(五)
本章及其以后的幾章,我們將從用戶態(tài)軟件、內(nèi)核態(tài)驅(qū)動(dòng)、FPGA邏輯介紹一個(gè)通過(guò)PCI Express總線實(shí)現(xiàn)CPU和FPGA數(shù)據(jù)通信的簡(jiǎn)單框架。
這個(gè)框架就是開(kāi)源界非常有名的RIFFA(reuseable integration framework for FPGA accelerators),它是一個(gè)FPGA加速器的一種可重用性集成框架,是一個(gè)第三方開(kāi)源PCIe框架。
該框架要求具備一個(gè)支持PCIe的工作站和一個(gè)帶有PCIe連接器的FPGA板卡。RIFFA支持windows、linux,altera和xilinx,可以通過(guò)c/c++、python、matlab、java驅(qū)動(dòng)來(lái)實(shí)現(xiàn)數(shù)據(jù)的發(fā)送和接收。驅(qū)動(dòng)程序可以在linux或windows上運(yùn)行,每一個(gè)系統(tǒng)最多支持5個(gè)FPGA device。
在用戶端有獨(dú)立的發(fā)送和接收端口,用戶只需要編寫(xiě)幾行簡(jiǎn)單代碼即可實(shí)現(xiàn)和FPGA IP內(nèi)核通信。
riffa使用直接存儲(chǔ)器訪問(wèn)(DMA)傳輸和中斷信號(hào)傳輸數(shù)據(jù)。這實(shí)現(xiàn)了PCIe鏈路上的高帶寬,運(yùn)行速率可以達(dá)到PCIe鏈路飽和點(diǎn)。
開(kāi)源地址:https://github.com/KastnerRG/riffa
一、riffa體系結(jié)構(gòu)
在硬件方面,簡(jiǎn)化了接口,以便通過(guò)FIFO簡(jiǎn)便的將數(shù)據(jù)取出或存入。數(shù)據(jù)的傳輸由riffa的rx和tx DMA engine模塊用分散聚合方法來(lái)實(shí)現(xiàn)。rx engine模塊收集上位機(jī)來(lái)的有效數(shù)據(jù),收集完成發(fā)給channel模塊,tx engine收集channel模塊傳送來(lái)的數(shù)據(jù),打包發(fā)給PCI express端點(diǎn)。
在軟件方面,PC接收FPGA板卡數(shù)據(jù)是用戶應(yīng)用程序調(diào)用庫(kù)函數(shù)fpga_recv,然后由FPGA端啟動(dòng)。用戶應(yīng)用程序線程進(jìn)入內(nèi)核驅(qū)動(dòng)程序,然后開(kāi)始接收上游FPGA的讀請(qǐng)求,將數(shù)據(jù)分包發(fā)送,如果沒(méi)收到請(qǐng)求,將會(huì)等待它達(dá)到。
啟動(dòng)發(fā)送函數(shù)后,服務(wù)器將建立一個(gè)散列收集元素的列表,將數(shù)據(jù)存儲(chǔ)地址和長(zhǎng)度等信息放入其中,將其寫(xiě)入共享緩沖區(qū)。用戶應(yīng)用程序?qū)⒕彌_區(qū)地址和數(shù)據(jù)長(zhǎng)度等信息發(fā)送給FPGA。FPGA讀取散射收集數(shù)據(jù),然后發(fā)出相應(yīng)地址的數(shù)據(jù)寫(xiě)入請(qǐng)求,如果散列收集元素列表的地址有多個(gè),F(xiàn)PGA將通過(guò)中斷發(fā)出多次請(qǐng)求。
TX搬移的數(shù)據(jù)全部寫(xiě)入緩存區(qū)后,驅(qū)動(dòng)程序讀取FPGA寫(xiě)入的字節(jié)數(shù),確認(rèn)是否與發(fā)送數(shù)據(jù)長(zhǎng)度一致。這樣就完成了傳輸。其過(guò)程如下圖所示:
PC機(jī)發(fā)送數(shù)據(jù)到FPGA板卡過(guò)程與PC機(jī)接收FPGA板卡數(shù)據(jù)過(guò)程相似,下圖說(shuō)明了該流程。 剛開(kāi)始也是用戶應(yīng)用程序調(diào)用庫(kù)函數(shù)fpga_send,傳輸線程進(jìn)入內(nèi)核驅(qū)動(dòng)程序,然后FPGA啟動(dòng)傳輸。
啟動(dòng)fpga_send,服務(wù)器將申請(qǐng)一些空間,將要發(fā)送的數(shù)據(jù)寫(xiě)入其中,然后建立一個(gè)分散收集列表,將存儲(chǔ)數(shù)據(jù)的地址和長(zhǎng)度放入其中,并將分散收集列表的地址和要發(fā)生的數(shù)據(jù)長(zhǎng)度等信息發(fā)給FPGA。FPGA 收到列表地址后,讀取該列表的信息,然后發(fā)出相應(yīng)地址和長(zhǎng)度的讀請(qǐng)求,然后將數(shù)據(jù)存儲(chǔ),最后一起發(fā)給FPGA板卡。
驅(qū)動(dòng)程序首先調(diào)用pci_present()檢查PCI總線是否被linux內(nèi)核支持,如果系統(tǒng)支持PCI總線結(jié)構(gòu),這個(gè)函數(shù)的返回值為0,如果驅(qū)動(dòng)程序在調(diào)用這個(gè)函數(shù)時(shí)得到一個(gè)非0的返回值,那么驅(qū)動(dòng)程序就必須得中止自己的任務(wù)。調(diào)用pci_register_driver()函數(shù)來(lái)注冊(cè)PCI設(shè)備的驅(qū)動(dòng)程序,此時(shí)需要提供一個(gè)“demo_pci_driver”結(jié)構(gòu),在該結(jié)構(gòu)中給出的probe探測(cè)例程負(fù)責(zé)完成對(duì)硬件的檢測(cè)工作。
下面將從user logic -> PCIe IP -> 驅(qū)動(dòng)層 -> library-> 用戶態(tài)C++/C按照實(shí)例一一進(jìn)行分析,包括理論基礎(chǔ)、實(shí)際操作、源代碼分析。
首先我們先從FPGA xilinx integrated block for PCI express分析,因?yàn)檫@個(gè)有承上啟下的作用。
二、PCIe hard IP分析
1. PCIE IP 核配置
AXI總線時(shí)鐘選擇62.5M,AXI總線接口位寬設(shè)置為64bit。
在IDs界面是PCIe設(shè)備的相關(guān)信息,主機(jī)在上電時(shí)BIOS系統(tǒng)中識(shí)別到的PCIe設(shè)備,就是通過(guò)這些ID號(hào)來(lái)進(jìn)行識(shí)別的。
在本實(shí)驗(yàn)中,關(guān)于ID的設(shè)定全部保持為默認(rèn)值即可,若用戶對(duì)ID進(jìn)行了更改,可能導(dǎo)致計(jì)算機(jī)在啟動(dòng)時(shí)不能正確識(shí)別設(shè)備從而導(dǎo)致藍(lán)屏死機(jī)。
Vendor ID是廠商ID,本實(shí)驗(yàn)中的廠商ID代值的就是Xilinx;
Device ID代表了PCIe設(shè)備,其中7指的是Xilinx 7 系列FPGA,02指的是使用的PCIe 2.0的協(xié)議,1指的是含有一個(gè)PCIe的傳輸lane;
Base class Menu指的是PCIe設(shè)備的種類,常見(jiàn)的有聲卡、顯卡、網(wǎng)卡等,各種不同種類的設(shè)備都有其對(duì)應(yīng)的驅(qū)動(dòng),若驅(qū)動(dòng)與其PCIe的種類不對(duì)應(yīng),就會(huì)導(dǎo)致系統(tǒng)的內(nèi)存訪問(wèn)錯(cuò)誤,從而導(dǎo)致藍(lán)屏。
在PCIe的bars配置界面對(duì)PCIe的bar進(jìn)行設(shè)置,BAR空間對(duì)應(yīng)的就是在內(nèi)存中開(kāi)辟一段空間用于存放PCIe設(shè)備的信息。只使用到一個(gè)bar0一個(gè)bar且將其內(nèi)存空間的大小設(shè)置為1k。
IP核的負(fù)載選擇最大負(fù)載長(zhǎng)度為512字節(jié),勾選對(duì)數(shù)據(jù)進(jìn)行緩沖。
在中斷配置界面,取消勾選傳統(tǒng)類型的中斷,只選擇消息類型的中斷。
在share logic界面取消勾選包含其他邏輯,這樣在PCIe的IP核中就包含了全部功能。
在IP核接口參數(shù)配置界面,只選擇其中用于配置核控制的參數(shù),這是由于riffa框架的特性所提供的。
下面代碼是實(shí)際的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 )
);
從頂層代碼接口可以看出來(lái),TX/RX差分信號(hào)、AXIS數(shù)據(jù)common接口信號(hào)、tx/rx數(shù)據(jù)面信號(hào)、FC流控信號(hào)、configuration(CFG)interface、EP中斷信號(hào)、系統(tǒng)時(shí)鐘/復(fù)位信號(hào)。
詳細(xì)使用參考xilinx PCIe spec官方文檔。
PCIe IP位于整個(gè)設(shè)計(jì)架構(gòu)的這個(gè)位置:
2. tx_engine和rx_engine
下面我們分析一下tx_engine和rx_engine這兩個(gè)核心模塊,這兩個(gè)模塊負(fù)責(zé)轉(zhuǎn)換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*/);
這個(gè)模塊可以通過(guò)C_NUM_CHNL配置通道個(gè)數(shù),C_PCI_DATA_WIDTH配置PCIe data的位寬,C_LOG_NUM_TAGS配置PCIe tag的個(gè)數(shù)。
module riffa_wrapper_ac701負(fù)責(zé)將xilinx PCIe IP的TX、RX、Configuration、flow control、中斷信號(hào)轉(zhuǎn)換為riffa的輸入輸出信號(hào)。
閱讀riffa_wrapper_ac701.v源代碼發(fā)現(xiàn)這個(gè)模塊分為兩個(gè)模塊:
· translation_xilinx
· engine_layer
· riffa
transtion_xilinx:負(fù)責(zé)提供統(tǒng)一的PCIe接口信息,比如altera、xilinx;
engine_layer:負(fù)責(zé)封裝了tx_engine和rx_engine。其中tx engine負(fù)責(zé)上傳DMA通道(寫(xiě)通道),即Interface: TX Classic;rx engine負(fù)責(zé)下發(fā)DMA通道(讀通道),即Interface: RX Classic;
riffa:負(fù)責(zé)將tx/rx engine的信號(hào)轉(zhuǎn)換為user logic的通道信號(hào),方便我們使用,接口代碼如下:
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
對(duì)應(yīng)整個(gè)實(shí)際框架的這一部分,如下圖所示:
3. user logic
經(jīng)過(guò)tx engine和rx engine模塊,輸出CHNL_RX_*和CHNL_TX_*信號(hào),下面我們看一下user如何使用的,源代碼如下:
generate
for (chnl = 0; chnl < C_NUM_CHNL; chnl = chnl + 1) begin : test_channels
chnl_tester
#(
.C_PCI_DATA_WIDTH(C_PCI_DATA_WIDTH)
)
module1
(.CLK(user_clk),
.RST(rst_out), // riffa_reset includes riffa_endpoint resets
// Rx interface
.CHNL_RX_CLK(chnl_rx_clk[chnl]),
.CHNL_RX(chnl_rx[chnl]),
.CHNL_RX_ACK(chnl_rx_ack[chnl]),
.CHNL_RX_LAST(chnl_rx_last[chnl]),
.CHNL_RX_LEN(chnl_rx_len[32*chnl +:32]),
.CHNL_RX_OFF(chnl_rx_off[31*chnl +:31]),
.CHNL_RX_DATA(chnl_rx_data[C_PCI_DATA_WIDTH*chnl +:C_PCI_DATA_WIDTH]),
.CHNL_RX_DATA_VALID(chnl_rx_data_valid[chnl]),
.CHNL_RX_DATA_REN(chnl_rx_data_ren[chnl]),
// Tx interface
.CHNL_TX_CLK(chnl_tx_clk[chnl]),
.CHNL_TX(chnl_tx[chnl]),
.CHNL_TX_ACK(chnl_tx_ack[chnl]),
.CHNL_TX_LAST(chnl_tx_last[chnl]),
.CHNL_TX_LEN(chnl_tx_len[32*chnl +:32]),
.CHNL_TX_OFF(chnl_tx_off[31*chnl +:31]),
.CHNL_TX_DATA(chnl_tx_data[C_PCI_DATA_WIDTH*chnl +:C_PCI_DATA_WIDTH]),
.CHNL_TX_DATA_VALID(chnl_tx_data_valid[chnl]),
.CHNL_TX_DATA_REN(chnl_tx_data_ren[chnl])
);
end
endgenerate
這個(gè)模塊的用于執(zhí)行RIFFA TX 和 RX 接口。在RX接口上接收數(shù)據(jù)并保存最后接收的值。在TX接口上發(fā)送回相同數(shù)量的數(shù)據(jù)。返回的數(shù)據(jù)從接收到的最后一個(gè)值開(kāi)始,重置并遞增,直到等于TX接口上發(fā)回的(4字節(jié))字?jǐn)?shù)的值結(jié)束。
三、總結(jié)
這篇文章通過(guò)經(jīng)典開(kāi)源項(xiàng)目RIIFA的FPGA部分,結(jié)合源代碼詳細(xì)分析了框架部分的Xilinx PCIe hard IP、TX/RX engine和RIFFA模塊,最后結(jié)合了user logic部分的chnl_tester,介紹了如何使用CHNL_TX_*和CHNL_RX_*接口。
四、未完待續(xù)
Linux下PCI設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解(六),將結(jié)合經(jīng)典開(kāi)源項(xiàng)目RIIFA,詳細(xì)介紹內(nèi)核態(tài)驅(qū)動(dòng)的設(shè)計(jì)、開(kāi)發(fā)、安裝等。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-783896.html
五、參考資料
https://blog.csdn.net/qq_41332806/article/details/105864632文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-783896.html
到了這里,關(guān)于Linux下PCI設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解(五)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!