黎順杰 張艷榮
(西南交通大學(xué)機(jī)械工程學(xué)院,成都 610031)
設(shè)備驅(qū)動程序是硬件設(shè)備連接到計(jì)算機(jī)操作系統(tǒng)的軟件接口,驅(qū)動程序的好壞直接影響到硬件的運(yùn)行與系統(tǒng)的安全。所以,穩(wěn)定高效的驅(qū)動程序?qū)τ谠O(shè)備硬件的開發(fā)具有重要的意義。
本文所述驅(qū)動程序是基于自行研發(fā)的PCI-CAN總線設(shè)備,該設(shè)備的系統(tǒng)結(jié)構(gòu)框圖如圖1所示:
圖1 PCI-CAN設(shè)備硬件結(jié)構(gòu)框圖
系統(tǒng)是在S1300 PCI開發(fā)平臺上利用硬件描述語言(Verilog HDL)實(shí)現(xiàn),核心芯片采用的是ALTERA公司CYCLONE2 FPGA系列的EP2C5Q208C8。通過使用開發(fā)平臺內(nèi)部集成的PCI總線控制器模塊,可實(shí)現(xiàn)符合PCI總線協(xié)議的數(shù)據(jù)傳輸。PCICAN總線設(shè)備的工作流程如下:計(jì)算機(jī)通過IO端口寫控制命令到控制寄存器模塊,控制寄存器模塊接收指令并初始化CAN總線控制器,如果是讀CAN指令,控制寄存器模塊將CAN總線上傳的數(shù)據(jù)緩存到 SDRAM,利用 DMA(Direct Memory Access)方式寫入主機(jī)內(nèi)存,由主機(jī)進(jìn)行相應(yīng)的處理;如果是寫CAN指令,計(jì)算機(jī)將數(shù)據(jù)通過內(nèi)存映射寫入FIFO,控制寄存器模塊控制CAN(Controller Area Network)總線控制器從FIFO中取出數(shù)據(jù)。在整個系統(tǒng)流程中,驅(qū)動程序?qū)崿F(xiàn)了應(yīng)用程序與硬件之間的控制引導(dǎo),作用不可取代。
WDF驅(qū)動程序框架中,所有的事物都由對象來表示,所有操作都被定義為一個事件(Event)或回調(diào)(Callback)?;趯ο蠹夹g(shù)的WDF實(shí)現(xiàn)了驅(qū)動程序與操作系統(tǒng)內(nèi)核之間的分離,驅(qū)動程序通過框架內(nèi)定義的對象與方法來實(shí)現(xiàn)自己的功能,具體的驅(qū)動程序與操作系統(tǒng)內(nèi)核的交互工作則交給框架內(nèi)封裝的方法(函數(shù))完成。這樣使得驅(qū)動程序的設(shè)計(jì)變得更加簡單明了。
WDF 之中包含了 KMDF(Kernel-Mode Driver Framework)與UMDF(User-Mode Driver Framework),本文所涉及的設(shè)備驅(qū)動程序開發(fā)是基于KDMF實(shí)現(xiàn)的。
PCI-CAN設(shè)備驅(qū)動程序結(jié)構(gòu)框架如圖2所示:
圖2 PCI-CAN設(shè)備驅(qū)動程序結(jié)構(gòu)框圖
PCI-CAN設(shè)備驅(qū)動程序設(shè)計(jì)從功能上可分為設(shè)備初始化、控制設(shè)置與數(shù)據(jù)交換三個部分。設(shè)備初始化主要實(shí)現(xiàn)設(shè)備識別、相關(guān)信息狀態(tài)獲取與硬件資源分配;控制設(shè)置負(fù)責(zé)系統(tǒng)初始化與啟動;數(shù)據(jù)交換處理的是設(shè)備功能的具體應(yīng)用,即PCI總線與CAN總線之間的數(shù)據(jù)傳輸。
從本質(zhì)上來說,WDF驅(qū)動程序就是入口函數(shù)DriverEntry與事件函數(shù)及其子函數(shù)的集合。操作系統(tǒng)第一次裝載驅(qū)動程序時會調(diào)用DriverEntry例程以完成驅(qū)動程序初始化。不同類型的驅(qū)動函數(shù)其DriverEntry也不同,可分為設(shè)備驅(qū)動、過濾驅(qū)動與純軟件驅(qū)動。本文所述驅(qū)動程序?qū)儆谠O(shè)備驅(qū)動,在入口函數(shù)DriverEntry中,除了創(chuàng)建和初始化WDFDRIVER對象,還需要注冊EvtDriverDeviceAdd事件回調(diào)。
status=WdfDriverCreate(DriverObject,RegistryPat h,……);
//創(chuàng)建WDFDRIVER對象
WDF_DRIVER_CONFIG_INIT(&config,PCICAN_EvtDeviceAdd);//注冊EvtDriverDeviceAdd事件回調(diào)
(1)設(shè)備初始化
驅(qū)動程序成功初始化后,操作系統(tǒng)會順序調(diào)用EvtDriverDeviceAdd、EvtDevicePrepareHardware、EvtDeviceReleaseHardware 和InitializeDMA等回調(diào)函數(shù)以實(shí)現(xiàn)設(shè)備的初始化。
作為在設(shè)備初始化過程中第一個被調(diào)用的回調(diào)函數(shù),EvtDriverDeviceAdd需要完成:
(1)創(chuàng)建設(shè)備對象;
(2)創(chuàng)建設(shè)備對象GUID接口或符號鏈接;
(3)創(chuàng)建一個或多個的I/O隊(duì)列;
(4)設(shè)置各種事件的回調(diào)例程,如電源管理、即插即用、I/O處理例程等。
status=WdfDeviceCreate(&DeviceInit,&deviceAttributes, &device);//創(chuàng)建設(shè)備對象
status=WdfDeviceCreateDeviceInterface(device,(LPGUID)&PCICAN_DEVINTERFACE_GUID, NULL); //創(chuàng)建GUID接口
X86處理器有兩種獨(dú)立的地址空間,分別是I/O地址與內(nèi)存地址。I/O地址空間只有64KB,內(nèi)存地址空間可以達(dá)到4G。對于微機(jī)接口卡,可以將I/O端口與存儲器芯片分別映射到這兩個地址空間中。而對這兩種不同的地址空間的訪問,需要驅(qū)動程序進(jìn)行相應(yīng)的預(yù)處理工作:在EvtDevicePrepareHardware中,首先獲取配置資源與資源描述符:對于I/O端口,將首地址與空間大小值保存起來;對于存儲器芯片,調(diào)用MmMapIoSpace函數(shù)將物理地址轉(zhuǎn)換為系統(tǒng)內(nèi)核地址然后保存。相對應(yīng)的,在卸載設(shè)備時,系統(tǒng)會調(diào)用EvtDeviceReleaseHardware回調(diào)函數(shù)釋放之前申請的硬件資源,對于存儲器地址,要用MmUnmapIoSpace函數(shù)解除物理地址與系統(tǒng)內(nèi)核地址之間的關(guān)聯(lián)。
for(i=0; i descriptor=WdfCmResourceListGetDescriptor(Resource ListTranslated,i);//獲取資源描述符 case CmResourceTypeMemory:pDeviceContext->MemBaseAddress=MmMapIoSpace( descriptor->u.Memory.Start, descriptor->u.Memory.Length,MmNonCached); pDeviceContext->MemLength=descriptor->u.Memory.Length;break; case CmResourceTypePort:pDeviceContext->IoBaseAddress=descriptor->u.Port.Start.LowPart;//I/O地址配置 pDeviceContext->IoLength = descriptor->u.Port.Length;break; MmUnmapIoSpace(pDeviceContext->MemBaseAddress,pDeviceContext->MemLength);//解除地址關(guān)聯(lián) 設(shè)備初始化第三步,利用函數(shù)InitializeDMA()創(chuàng)建DMA適配器與一個DMA傳輸。 InitializeDMA(IN WDFDEVICE Device){…… status=WdfDmaEnablerCreate(…);//創(chuàng)建DMA適配器 status=WdfDmaTransactionCreate(pDeviceContext->DmaEnabler,WDF_NO_OBJECT_ATTRIBUTES,&pDeviceContext->DmaTransaction ); //創(chuàng)建一個DMA傳輸 ……} (2)控制設(shè)置及數(shù)據(jù)交換 應(yīng)用程序?qū)︱?qū)動程序的通信需要調(diào)用CreateFile、ReadFile、WriteFile、DeviceIoControl 與 CloseHandle。CreateFile打開設(shè)備的方式有兩種:GUID接口與符號鏈接名,本文所述驅(qū)動程序采用的是GUID接口方式。 DevicePath=GetDevicePath((LPGUID)&PCICAN_DEVINTERFACE_GUID);//獲取設(shè)備路徑 hDevice=CreateFile(DevicePath,…… );//打開設(shè)備 獲得了設(shè)備的有效句柄,應(yīng)用程序就可以調(diào)用DeviceIoControl函數(shù)寫設(shè)備控制寄存器。應(yīng)用程序的請求會被放入請求隊(duì)列之中,并在EvtIoDeviceControl函數(shù)之中被處理。EvtIoDeviceControl函數(shù)之中可以定義多個不同的分支,對應(yīng)于不同的I/O控制命令。應(yīng)用程序通過I/O控制命令鎖定具體的操作分支。I/O控制命令定義了4種數(shù)據(jù)訪問方式,本文采用的是METHOD_BUFFERED,系統(tǒng)會分配一個緩沖區(qū)用作輸入與輸出。由于系統(tǒng)的控制寄存器被映射到了I/O地址空間之中,可以使用下面兩條指令對寄存器進(jìn)行讀寫:WRITE_PORT_XXX();//寫端口數(shù)據(jù) READ_PORT_XXX()//讀端口數(shù)據(jù) 數(shù)據(jù)交換操作可以分為輸出數(shù)據(jù)與輸入數(shù)據(jù)。應(yīng)用程序調(diào)用WriteFile寫數(shù)據(jù),在DeviceIoWrite中實(shí)現(xiàn)。對于數(shù)據(jù)讀寫,使用的是內(nèi)存映射方式。DeviceIoWrite首先需要獲得一個輸入緩沖,然后利用WdfMemoryCopyToBuffer()函數(shù)將數(shù)據(jù)送入到設(shè)備存儲器。 status=WdfRequestRetrieveInputMemory(Request,&memory);//輸入緩沖 本文重點(diǎn)討論了基于WDF的PCI-CAN設(shè)備驅(qū)動程序設(shè)計(jì)方法。本文所述的PCI-CAN卡驅(qū)動程序,在WDK1.9中成功編譯,自動生成INF文件(設(shè)備安裝信息)與SYS文件(驅(qū)動程序代碼),成功安裝且運(yùn)行穩(wěn)定可靠。經(jīng)過測試,支持5kbps-1MbpsCAN總線波特率,數(shù)據(jù)流量最高可達(dá)3000幀/秒,相關(guān)參數(shù)均可由應(yīng)用程序設(shè)置后傳遞到FPGA。另外,測試DMA讀極限速率為116M/s,大大降低了總線數(shù)據(jù)交換的系統(tǒng)延遲??偠灾琖DF驅(qū)動模型簡化并優(yōu)化了驅(qū)動開發(fā)技術(shù),同WDM驅(qū)動模型相比,變得更加穩(wěn)定、靈活與高效。 [1] 譚文.寒江獨(dú)釣—Windows內(nèi)核安全編程[M].北京:電子工業(yè)出版社,2009. [2] 武安河.Windows設(shè)備驅(qū)動程序WDF開發(fā)[M].北京:電子工業(yè)出版社,2009. [3] Penny Orwick,Guy Smith . Developing Drivers with the Microsoft Windows Driver Foundations[M]. Microsoft Press, 2007. [4] 鄒敬軒,蔡皖東.基于WDF過濾驅(qū)動的USB存儲設(shè)備監(jiān)控系統(tǒng)[J]. 計(jì)算機(jī)工程與科學(xué),2010 ;32(3):42. [5] 錢宇紅.USB數(shù)據(jù)傳輸卡WDF驅(qū)動程序開發(fā)[J].計(jì)算機(jī)應(yīng)用與軟件,2012 ;29(6):225-227.3 結(jié)束語