中國電子科技集團公司第三十研究所 王燁 張穎 唐璞
文章提出并實現了一種基于Linux系統的PCIE高速數據處理卡驅動設計方案,解決了Linux上位機與PCIE數據卡之間高效可靠的數據傳輸。驅動設計過程應用了DMA機制、軟中斷機制和多通道的思想,實現了用戶層數據封裝和數據交換。通過系統測試,驗證了驅動設計的有效性、可靠性。
隨著軟硬件國產化進程的加快,基于國產Linux操作系統的軟件開發越來越重要。眾多外設要想在國產化平臺上使用,就必須開發對應的設備驅動程序。一款CPU提供的外設接口很多,例如I2C、SPI、SDIO、USB等,但是要論高速訪問,PCIE接口通常是首選。
PCI-Express,簡稱PCIE,是一種高速串行計算機擴展總線標準。從PCI Express X1到PCI Express X32,能滿足將來一定時間內出現的低速設備到高速設備的需求。在獨立顯卡、獨立網卡、固態硬盤領域常見PCIE的身影。本文介紹的是一款高速PCIE數據卡的驅動設計,其中數據卡使用國產FPGA(國微690T)做數據處理,硬件平臺使用國產飛騰2500,數據卡與服務器之間采用PCIE總線通信。為了保證數據處理的吞吐量,驅動采用DMA數據傳輸技術進行用戶層和數據卡的數據交換。在上位機設計了測試驗證,驗證了該驅動設計的有效性和穩定性。
本文的數據卡使用FPGA芯片做數據處理,FPGA芯片與CPU之間使用PCIE總線進行數據傳輸,如圖1所示。
驅動程序的設計思想是申請2個大小為1024×sizeof(bd)的DMA空間tx_bd和rx_bd,其中tx_bd用于CPU向FPGA發送數據,rx_bd用于FPGA向CPU發送數據。將這2個DMA空間的物理地址分別存儲在PCIE內存BAR空間的tx_bd和rx_bd基址寄存器中。同時定義tx_bd_tail、rx_bd_head、rx_bd_tail三個變量,控制收發包的順序。由于FPGA數據處理的速度較快,為了提高數據處理性能,FPGA和驅動程序之間定義4個通道Channel,每個通道獨立完成數據收發,每個Channel各自維護一組tx_bd、rx_bd、tx_bd_tail、rx_bd_head、rx_bd_tail。驅動架構設計如圖2所示。
數據發送過程:
(1)驅動收到數據包后,首先數據拷貝到內核地址中,將tx_bd_tail作為索引在tx_bd空間中找到當前可用的bd元素,用數據包的發送、接收物理地址封裝該bd元素;
(2)tx_bd_tail值加1;
(3)修改PCIE內存BAR空間的tx_bd_tail值。
FPGA在輪詢tx_bd_tail寄存器時發現了內容變化,便會在tx_bd中找到相應的數據做數據處理。數據處理完成后,FPGA將數據處理結果填入rx_bd結構中物理地址指向位置,產生中斷。
數據接收過程:
(1)驅動收到中斷,將rx_bd_head(初始為0)作為索引在rx_bd中找到bd元素;
(2)判斷bd結構中的標識位,以此確認這個bd代表的數據包是否已經處理完成,如果數據包未完成,結束此次中斷處理。
(3)如果bd代表的數據包已經處理完成,將數據從物理地址拷貝到用戶層地址;
(4)通知應用程序處理完成。
本驅動定義的加載函數和卸載函數分別是:
static int __init pciedev_init ();
static void __exit pciedev_cleanup ();
在加載函數中完成兩件事,(1)注冊PCI驅動;(2)注冊字符設備。部分代碼實現如圖3所示。
編譯驅動后生成pciedev.ko,使用insmod pciedev.ko命令加載驅動,驅動執行pciedev_init函數,完成PCI驅動注冊。
內核遍歷PCI總線得到總線上所有PCI外設的列表,通過對比設備ID找到對應驅動[1]。如果ID為0x1234,0x5678,則設備和驅動匹配成功,調用pciedev_probe函數進行設備初始化,初始化流程如圖4所示。
初始化4個Channel。調用dma_alloc_coherent函數[2]分配2個1024×sizeof(bd)大小的DMA空間,分別作為tx_bd(驅動向FPGA發送數據)表和rx_bd(FPGA向驅動發送數據),將物理地址對應存儲在PCIE的內存BAR空間的tx_bd、rx_bd地址中(地址在BAR中的偏移和FPGA協商而定),初始化本地變量tx_bd_tail為0,該值控制發送順序,初始化rx_bd_head為0,rx_bd_tail為1023,該值控制接收順序,將其分別存儲在BAR空間的tx_bd_tail和rx_bd_tail地址中(地址在BAR中的偏移和FPGA協商而定)。初始化tx_bd和rx_bd中的每一個bd結構,部分代碼實現如圖5所示。
中斷服務程序初始化。PCIE數據卡支持MSIX中斷,FPGA通過MSIX中斷通知驅動接收返回的數據包。為了提高中斷響應速度,驅動程序在中斷服務程序中調用軟中斷作為中斷處理的下半部機制。首先調用tasklet_init初始化軟中斷,然后調用pci_enable_msix_range使能MSIX中斷,最后調用request_irq申請中斷。部分代碼實現如圖6所示。
用戶發送數據到驅動后,用戶數據地址是不能直接使用的,需要將內容拷貝到內核地址空間,因此驅動首先調用Kmalloc分配一段內核空間,然后通過copy_from_user函數將用戶數據拷貝到內核空間,使用這部分內核空間。為例避免每次發包都分配內存,影響數據傳輸速率,驅動在設備初始化1024個struct pci_transaction節點構成鏈表,每一個struct pci_transaction結構體節點代表一次數據傳輸,其中的Vaddr指向一個分配的4096字節的內核地址。struct pci_transaction部分定義如圖7所示。
當用戶通過已經創建的字符設備執行ioctl操作時,驅動收到數據包,執行過程如下:
(1)收到一個包,判斷當前鏈路上的正在處理的包的個數,如果大于等于1024,說明1024個發送bd表已經滿了,那么驅動向應用層返回-EBUSY錯誤;
(2)如果發送bd表未滿,從預分配1024個struct pci_transaction鏈表中找到第一個可用的節點,并標記已用,拷貝用戶數據到該節點Vaddr成員變量指向的內核空間,并在該節點記錄下用戶發送地址、接收地址以及數據長度。
將當前tx_bd_tail的值作為索引找到發送bd表中的對應bd節點,對bd節點的成員變量進行封裝,然后tx_bd_tail加1,將其寫入PCIE的BAR空間的tx_bd_tail中,數據卡的FPGA程序讀取tx_bd_tail值,并根據這個值讀出數據并處理。數據發送流程和部分代碼如圖8、圖9所示。
當PCIE數據卡處理完數據后,產生中斷,驅動的中斷服務程序被觸發,中斷服務程序需要做兩件事,(1)調度Tasklet軟中斷,將中斷處理的下半部放在軟中斷中執行[3],這里指收包過程;(2)返回IRQ_HANDLED。由于中斷不能被相同類型的中斷打斷,而下半部依然可以被中斷打斷,使用Tasklet軟中斷處理收包的機制對提高通信速率大有裨益。中斷處理部分代碼如圖10所示。
FPGA處理數據后會在rx_bd中找到下一個可用的bd結構,置Control標志位,填充bd結構,發送中斷。驅動使用rx_bd_head作為索引取出rx_bd表的bd結構。首先判斷Control標識位,FPGA處理完成置位;然后取出數據包加入完成鏈表,rx_bd_head加1,循環處理下一個bd結構。與此同時,應用程序可以通過poll機制查詢是否有包返回,如果有,那么通過ioctl調用取包命令,驅動將數據從內核地址拷貝到用戶層地址,完成此次數據傳輸。接收數據處理流程和部分代碼實現如圖11、圖12所示。
系統測試主要是測試PCIE數據卡的性能和穩定性,因此FPGA事前設計成數據回環模式,對收到的數據包直接返回,測試鏈路的性能和驅動穩定性。測試程序設計成4個線程,數據卡的4個中斷分別綁在CPU0、CPU1、CPU2、CPU3上,測試數據長度為4000字節。測試記錄如圖13所示。
如圖13可見,數據通信性能大于9.4Gbps。
基于Linux系統的PCIE數據卡驅動程序,在保障數據傳輸性能的同時,代碼具有很高的移植性,基于這種設計的數據卡驅動已經在云服務器PCIE數據卡上使用。