原文地址:http://www.cnblogs.com/jacklu/p/6139347.html
如果你覺得這篇博客對你的項目有用,請引用以下論文:
Meng Shengwei, Lu Jianjie. Design of a PCIe Interface Card Control Software Based on WDF. Fifth International Conference on Instrumentation and Measurement, Computer, Communication and Control. IEEE, 2016:767-770.
正如前幾篇博客所說,使用WDF開發PCIe驅動程序是我本科畢業設計的主要工作。在讀研的兩年,我也分別為所在課題組移植了自己編寫的驅動程序,在Windows 32位和64位平台下的PXI、PXIe、PCI、PCIe板卡分別得到了驗證。
這篇文章根據自己最新編寫的驅動代碼(源代碼請找博主索取),主要講述如何為自己的硬件板卡移植驅動程序,並簡單講述如何使用Altera系列FPGA配置PCI IP核,然后對INF文件作簡要描述,最后描述如何使用Qt編寫上位機軟件調用底層驅動。
准備去讀博了,這一篇將作為這個系列的完結,之后將把更多精力放到機器學習上來。
1概述
所編寫的驅動代碼程序包括7個源代碼文件,分別是Device.h, Driver.h, Public.h, Trace.h, Device.c,Driver.c, Queue.c。其中Device.h 定義了與硬件相關的地址偏移;Public.h定義了DeviceIoControl 用到的用戶自定義命令字, 此頭文件由上層應用程序和驅動程序共同使用;Queue.c定義了I/O回調例程,分別使用了read、write和I/O Control 三個隊列。 除了這三個文件外, 不建議更改其他4個文件的代碼。三個源文件函數列表分別如圖 1-1、1-2、1-3所示:
2驅動程序移植說明
2.1 Public.h說明
- 代碼中定義了GUID值,開發者可以使用 VS2013 下的工具 GUIDGen.exe 生成 GUID值,該GUID標識驅動程序,應用程序根據這個GUID值來找到對應的驅動程序。
- 代碼中定義了CTL_CODE, I/O 處理例程 DeviceIoControl 的第二個參數dwIoControlCode 就是由 CTL_CODE 宏定義的。 CTL_CODE 是一個用於創建一個唯一的32 位系統I/O控制代碼的宏,這個控制代碼包括4部分組成:DeviceType(設備類型,高16位(16-31 位)),Access(訪問限制,14-15 位),Function(功能2-13位),Method(I/O訪問內存使用方式)。CTL_CODE 定義中有一個Method域,該2域定義了驅動程序中獲取應用程序數據緩沖區的地址方式。 已經定義的 CTL_CODE 命令字說明如表 2-1 所示, 用戶可以根據格式自定義 CTL_CODE, 實現不同功能。
什么是 CRA 寄存器組? 打開 quartus 的 sopc builder,可以看到sopc架構如圖 2-1 所示,在PCI IP核一欄中有 Control Register Access 寄存器組,地址范圍0x00000000-0x00003fff。 里面關鍵的寄存器地址如圖 2-2 所示。 通過讀黃色標識的寄存器,可以通過驅動程序調試驗證 PCI 核。 關於 CRA 寄存器組的配置說明會在 2.3 節詳細說明。
2.2 Device.h說明
- 代碼對 FPGA 上硬件資源的偏移地址進行宏定義,在 Altera 系列的 FPGA 里,這些偏移地址也叫 Avalon 地址, 在 sopc builder 可以自定義分配, 如圖 2-1 中的 Base和End 所示, “ 小鎖頭” 標志表示地址鎖定, 點擊該標志解鎖后可以自定義便宜地址。這些地址必須與驅動程序中所用的地址一一對應;
- 代碼定義了設備對象結構體, 對幾個重要的成員變量注釋如下:
- 代碼對一些事件回調例程進行了說明, 一般不需要用戶進行二次修改;
2.3 Queue.c說明
- 代碼是用戶需要針對功能開發的代碼。 以從應用程序向驅動程序寫入偏移地址為例,即原代碼第 xx-xx 行 , 首先在 Public.h 文件里定義 IoControlCode 為qd41_IOCTL_WRITE_OFFSETADDRESS, 第 48-53 行為獲取應用程序輸入緩存數據的指針( 代碼中為 inBuffer) , 通過賦值語句將 inBuffer 指向的數據內容賦給設備對象的成員變量 OffseAddressFromApp, 即完成了本功能;
- 代碼定義了寫隊列功能, 對 DMA 的寫功能配置在此函數中完成。 Altera的 DMA IP 核共有 5 個寄存器, 如圖 2-3 所示。
- 在配置 DMA 前需要配置 PCI CRA 寄存器, 使能 PCI 中斷, 配置 Avalon-PCI 地址轉換表, 如代碼所示;
- 為什么需要配置 Avalon-PCI 地址轉換表? 因為 PCI IP核一端是 PCI 總線,一端是Avalon總線,地址轉換過程圖 2-4 所示,類似MMU地址翻譯原理,不在贅述,此時需要把PC機獲得的DMA傳輸緩存物理地址的高16位地址寫入地址轉換表;
- 配置好 PCI 后就可以配置 DMA 控制寄存器了, 首先將狀態寄存器和控制寄存器清零,如代碼所示;
- 然后將讀寫地址分別寫入讀寫寄存器, 注意PC機上的內存地址為低16位,而高16位要配置在 Avalon-PCI 地址轉換表中, 如代碼所示, 其中 0x20000 為 PCI IP核的 Avalon 地址, 如圖 2-1 所示;
- 向長度寄存器寫入 DMA 傳輸長度(單位:字節) , 如代碼所示;
- 向控制寄存器寫入 DMA 控制字 0x8c, 即長度寄存器降低為0時傳輸完成, 使能DMA,32 位字傳輸, 如代碼所示;
3應用程序如何調用驅動程序
3.1 GUID說明
GUID( Globally Unique Identifier) 是微軟推出的全局唯一標識符, 通過使用某個特定的算法( 比如根據時間或地點等信息) 生成一組128 位二進制數,來標識某一個實體,比如硬盤上的一張圖片。 GUID 廣泛應用於微軟的產品中, 用於識別接口、文件等對象。開發者可以使用 VS2013 下的工具 GUIDGen.exe 生成 GUID 值, 該 GUID 標識驅動程序, 應用程序根據這個 GUID 值來找到對應的驅動程序。
應用程序總體流程設計為: 首先通過 Win32API 函數 CreateFile 打開設備, 然后調用DeviceIoControl 函數與驅動程序通信,即讀寫數據,當應用程序退出時,調用CloseHandle函數關閉設備。
應用程序根據底層設置的 GUID 獲取設備路徑, 從而與指定設備建立連接: 首先調用SetupDiGetClassDevs 函數獲得符合傳入參數 GUID 的設備信息集合 hDevInfo; 然后根據設備信息集合 hDevInfo 和 GUID 調用 SetupDiEnumDeviceInterfaces 函數枚舉設備信息集合中的設備,並輸出設備接口數據信息 DeviceInterfaceData ; 再 根 據 hDevInfo和 DeviceInterfaceData 調用 SetupDiGetDeviceInterfaceDetail函數得到保存設備接口詳細信息DeviceInterfaceDetailData 的緩沖區大小 ,接着為其開辟空間 , 最后再調用一次SetupDiGetDeviceInterfaceDetail 函數獲得設備接口詳細信息 pDeviceInterfaceDetailData; 最后根據設備接口詳細信息的設備接口路徑 pDeviceInterfaceDetailData->DevicePath 調用CreateFile 函數來創建設備句柄。
3.2 應用程序如何打開設備
在測試程序 Source.c 的代碼第 62-70 行,完成打開設備句柄功能, 不需要用戶改動。
3.3 應用程序如何讀設備
成功打開設備后, 調用 DeviceIoControl 即可通過 IOControl 隊列與驅動程序通信, 以讀32bit 數據為例, 首先向驅動程序寫入需要讀的存儲單元地址, 如代碼第 257-272 行所示,然后向驅動程序傳入讀數據緩存 outBuffer, 如代碼第 274-289 所示, 即可獲得FPGA 上對應偏移地址的數據。
3.4 應用程序如何寫設備
成功打開設備后, 調用 DeviceIoControl 即可通過 IOControl 隊列與驅動程序通信, 以寫32bit 數據為例, 首先向驅動程序寫入需要寫的存儲單元地址, 如代碼第 303-317 行所示,然后向驅動程序傳入縮寫數據緩存 inBuffer, 如代碼第 322-336 所示, 即可將數據寫入 FPGA上對應偏移地址的內存單元。
3.5 應用程序如何對設備進行DMA傳輸
通過Win32API 函數ReadFile和WriteFile對設備句柄進行文件讀寫操作, 在內核驅動中會調用驅動程序的 qd41EvtIoRead和qd41EvtIoWrite,實際的DMA配置在這兩個驅動回調例程中實現, ReadFile和WriteFile 只是完成了數據從用戶層到內核層的搬運。詳細代碼在第 340-378 行,不再贅述。
4 INF文件與如何調用Qt編寫上位機軟件調用底層驅動
我把C語言編寫的應用程序每一個獨立的控制命令編譯成一個可執行文件,這樣Qt可以在新的進程中調用 C 程序編寫的命令字讀寫程序。使用Qt 封裝好的進程類 QProcess, 使用其成員函數 start 即可調用一個外部程序。
廣告時間~本人博士賺外快,如需要完整的驅動程序源代碼請聯系合作email: 346457821@qq.com