Linux下PCI設備驅(qū)動開發(fā)詳解(五)

Linux下PCI設備驅(qū)動開發(fā)詳解(五)

本章及其以后的幾章,我們將從用戶態(tài)軟件、內(nèi)核態(tài)驅(qū)動、FPGA邏輯介紹一個通過PCI Express總線實現(xiàn)CPU和FPGA數(shù)據(jù)通信的簡單框架。

這個框架就是開源界非常有名的RIFFA(reuseable integration framework for FPGA accelerators),它是一個FPGA加速器的一種可重用性集成框架,是一個第三方開源PCIe框架。

該框架要求具備一個支持PCIe的工作站和一個帶有PCIe連接器的FPGA板卡。RIFFA支持windows、linux,altera和xilinx,可以通過c/c++、python、matlab、java驅(qū)動來實現(xiàn)數(shù)據(jù)的發(fā)送和接收。驅(qū)動程序可以在linux或windows上運行,每一個系統(tǒng)最多支持5個FPGA device。

在用戶端有獨立的發(fā)送和接收端口,用戶只需要編寫幾行簡單代碼即可實現(xiàn)和FPGA IP內(nèi)核通信。

riffa使用直接存儲器訪問(DMA)傳輸和中斷信號傳輸數(shù)據(jù)。這實現(xiàn)了PCIe鏈路上的高帶寬,運行速率可以達到PCIe鏈路飽和點。

開源地址:https://github.com/KastnerRG/riffa

一、riffa體系結構

riffa體系結構.png

在硬件方面,簡化了接口,以便通過FIFO簡便的將數(shù)據(jù)取出或存入。數(shù)據(jù)的傳輸由riffa的rx和tx DMA engine模塊用分散聚合方法來實現(xiàn)。rx engine模塊收集上位機來的有效數(shù)據(jù),收集完成發(fā)給channel模塊,tx engine收集channel模塊傳送來的數(shù)據(jù),打包發(fā)給PCI express端點。

在軟件方面,PC接收FPGA板卡數(shù)據(jù)是用戶應用程序調(diào)用庫函數(shù)fpga_recv,然后由FPGA端啟動。用戶應用程序線程進入內(nèi)核驅(qū)動程序,然后開始接收上游FPGA的讀請求,將數(shù)據(jù)分包發(fā)送,如果沒收到請求,將會等待它達到。

啟動發(fā)送函數(shù)后,服務器將建立一個散列收集元素的列表,將數(shù)據(jù)存儲地址和長度等信息放入其中,將其寫入共享緩沖區(qū)。用戶應用程序?qū)⒕彌_區(qū)地址和數(shù)據(jù)長度等信息發(fā)送給FPGA。FPGA讀取散射收集數(shù)據(jù),然后發(fā)出相應地址的數(shù)據(jù)寫入請求,如果散列收集元素列表的地址有多個,F(xiàn)PGA將通過中斷發(fā)出多次請求。

TX搬移的數(shù)據(jù)全部寫入緩存區(qū)后,驅(qū)動程序讀取FPGA寫入的字節(jié)數(shù),確認是否與發(fā)送數(shù)據(jù)長度一致。這樣就完成了傳輸。其過程如下圖所示:

1703216740800.png

PC機發(fā)送數(shù)據(jù)到FPGA板卡過程與PC機接收FPGA板卡數(shù)據(jù)過程相似,下圖說明了該流程。 剛開始也是用戶應用程序調(diào)用庫函數(shù)fpga_send,傳輸線程進入內(nèi)核驅(qū)動程序,然后FPGA啟動傳輸。

啟動fpga_send,服務器將申請一些空間,將要發(fā)送的數(shù)據(jù)寫入其中,然后建立一個分散收集列表,將存儲數(shù)據(jù)的地址和長度放入其中,并將分散收集列表的地址和要發(fā)生的數(shù)據(jù)長度等信息發(fā)給FPGA。FPGA 收到列表地址后,讀取該列表的信息,然后發(fā)出相應地址和長度的讀請求,然后將數(shù)據(jù)存儲,最后一起發(fā)給FPGA板卡。

驅(qū)動程序首先調(diào)用pci_present()檢查PCI總線是否被linux內(nèi)核支持,如果系統(tǒng)支持PCI總線結構,這個函數(shù)的返回值為0,如果驅(qū)動程序在調(diào)用這個函數(shù)時得到一個非0的返回值,那么驅(qū)動程序就必須得中止自己的任務。調(diào)用pci_register_driver()函數(shù)來注冊PCI設備的驅(qū)動程序,此時需要提供一個“demo_pci_driver”結構,在該結構中給出的probe探測例程負責完成對硬件的檢測工作。

下面將從user logic -> PCIe IP -> 驅(qū)動層 -> library-> 用戶態(tài)C++/C按照實例一一進行分析,包括理論基礎、實際操作、源代碼分析。

首先我們先從FPGA xilinx integrated block for PCI express分析,因為這個有承上啟下的作用。

二、PCIe hard IP分析

1. PCIE IP 核配置

AXI總線時鐘選擇62.5M,AXI總線接口位寬設置為64bit。


時鐘.png

在IDs界面是PCIe設備的相關信息,主機在上電時BIOS系統(tǒng)中識別到的PCIe設備,就是通過這些ID號來進行識別的。

在本實驗中,關于ID的設定全部保持為默認值即可,若用戶對ID進行了更改,可能導致計算機在啟動時不能正確識別設備從而導致藍屏死機。

Vendor ID是廠商ID,本實驗中的廠商ID代值的就是Xilinx;

Device ID代表了PCIe設備,其中7指的是Xilinx 7 系列FPGA,02指的是使用的PCIe 2.0的協(xié)議,1指的是含有一個PCIe的傳輸lane;

Base class Menu指的是PCIe設備的種類,常見的有聲卡、顯卡、網(wǎng)卡等,各種不同種類的設備都有其對應的驅(qū)動,若驅(qū)動與其PCIe的種類不對應,就會導致系統(tǒng)的內(nèi)存訪問錯誤,從而導致藍屏。


base class menu.png

在PCIe的bars配置界面對PCIe的bar進行設置,BAR空間對應的就是在內(nèi)存中開辟一段空間用于存放PCIe設備的信息。只使用到一個bar0一個bar且將其內(nèi)存空間的大小設置為1k。


bars.png

IP核的負載選擇最大負載長度為512字節(jié),勾選對數(shù)據(jù)進行緩沖。


max payload len.png

在中斷配置界面,取消勾選傳統(tǒng)類型的中斷,只選擇消息類型的中斷。


中斷.png

在share logic界面取消勾選包含其他邏輯,這樣在PCIe的IP核中就包含了全部功能。


share logic.png

在IP核接口參數(shù)配置界面,只選擇其中用于配置核控制的參數(shù),這是由于riffa框架的特性所提供的。


控制參數(shù).png

下面代碼是實際的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數(shù)據(jù)common接口信號、tx/rx數(shù)據(jù)面信號、FC流控信號、configuration(CFG)interface、EP中斷信號、系統(tǒng)時鐘/復位信號。

詳細使用參考xilinx PCIe spec官方文檔。

PCIe IP位于整個設計架構的這個位置:


屏幕截圖 2023-12-24 131957.png

2. tx_engine和rx_engine

下面我們分析一下tx_engine和rx_engine這兩個核心模塊,這兩個模塊負責轉(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*/);

這個模塊可以通過C_NUM_CHNL配置通道個數(shù),C_PCI_DATA_WIDTH配置PCIe data的位寬,C_LOG_NUM_TAGS配置PCIe tag的個數(shù)。

module riffa_wrapper_ac701負責將xilinx PCIe IP的TX、RX、Configuration、flow control、中斷信號轉(zhuǎn)換為riffa的輸入輸出信號。

閱讀riffa_wrapper_ac701.v源代碼發(fā)現(xiàn)這個模塊分為兩個模塊:

· translation_xilinx
· engine_layer
· riffa

transtion_xilinx:負責提供統(tǒng)一的PCIe接口信息,比如altera、xilinx;

engine_layer:負責封裝了tx_engine和rx_engine。其中tx engine負責上傳DMA通道(寫通道),即Interface: TX Classic;rx engine負責下發(fā)DMA通道(讀通道),即Interface: RX Classic;

riffa:負責將tx/rx engine的信號轉(zhuǎn)換為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

對應整個實際框架的這一部分,如下圖所示:


engine.png

3. user logic

經(jīng)過tx engine和rx engine模塊,輸出CHNL_RX_和CHNL_TX_信號,下面我們看一下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

這個模塊的用于執(zhí)行RIFFA TX 和 RX 接口。在RX接口上接收數(shù)據(jù)并保存最后接收的值。在TX接口上發(fā)送回相同數(shù)量的數(shù)據(jù)。返回的數(shù)據(jù)從接收到的最后一個值開始,重置并遞增,直到等于TX接口上發(fā)回的(4字節(jié))字數(shù)的值結束。

三、總結

這篇文章通過經(jīng)典開源項目RIIFA的FPGA部分,結合源代碼詳細分析了框架部分的Xilinx PCIe hard IP、TX/RX engine和RIFFA模塊,最后結合了user logic部分的chnl_tester,介紹了如何使用CHNL_TX_和CHNL_RX_接口。

user logic.png

四、未完待續(xù)

Linux下PCI設備驅(qū)動開發(fā)詳解(六),將結合經(jīng)典開源項目RIIFA,詳細介紹內(nèi)核態(tài)驅(qū)動的設計、開發(fā)、安裝等。

五、參考資料

https://blog.csdn.net/qq_41332806/article/details/105864632

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容