摘要:介紹了VxWorks下USB驅動的層次結構,分析了USB設備驅動程序開發的一般方法和關鍵技術。在此基礎上實現了VxWorks下LM9833設備驅動,達到了預期目標。所給出的VxWorks下USB設備驅動設計的一般步驟為在VxWorks下開發其它USB設備驅動提供了參考。
關鍵詞:VxWorks;USB設備驅動;管道;回調
中圖分類號:TP316文獻標識碼:A文章編號:1009-3044(2008)24-1200-04
Design of USB Device Driver Based on Real Time Operation System VxWorks
WANG Hao
(College of Computer, Xidian University, Xi'an 710071, China)
Abstract:The architecture of USB dirver based on VxWorks is given, general method and key technology in developing USB device dirver are analyzed.Then the device driver of LM9833 is implemented, expectant performace of target system is achieved. The general process of developing USB device dirver used in this paper can be refered by others USB device driver developing based on VxWorks.
Key words: VxWorks; USB device driver; pipe; callback
1 VxWorks下USB驅動概述
VxWorks是WindRiver公司開發的具有工業領導地位的高性能實時操作系統(Real Time Operation System, RTOS)內核。VxWorks5.5實現了USB1.1協議棧。圖1提供一個VxWorks下USB主驅動棧的簡單結構。
在棧的最低層是USB主控制器(USB Host Controller, HC),這是主系統中控制每一個USB設備的硬件。在主控制器上層是一個與硬件獨立的主控制器驅動(USB Host Controller Driver,HCD)。USBD是在HCD之上的與硬件獨立的模塊,USBD管理每一個與主機相連的設備,向高層提供可與USB設備通信的路徑,USBD實現了USB總線枚舉、總線帶寬分配、傳輸控制等操作。在棧的頂層是USB Client 模塊,一般是特定的USB Class Driver,負責管理與USB主機連接的不同類型的設備。用戶自己的USB設備驅動程序通常是在USBD這一層上完成。
2 VxWorks下USB設備驅動詳解
2.1 驅動程序提供的函數
2.1.1 向應用程序提供的接口函數
設備驅動程序的主要作用是向上層應用程序屏蔽硬件,向上層應用程序提供統一的接口函數,驅動程序一般需要實現的函數如表1所示。

圖1 USB主驅動棧結構
表1 驅動程序提供的接口

usbMSCDevInit();這是一個通用的初始化例程,可以在BSP中調用,也可以由應用程序來調用,但只能被調用一次,該例程初始化必須的數據結構,并向USBD注冊驅動程序。USB設備與USB主控制器之間的所有操作都通過USBD來完成,因此在調用usbMSCDevInit()之前必須確定USBD已經初始化,在使用USB設備之前也要確保USB主控制器已經掛接到USBD。
usbMSCDevCreate();這是一個創建設備例程,當有設備接入時,回調函數usbMSCDevAttachCallback()調用該例程創建一個邏輯設備,當這個例程被調用時必須在系統中存在一個實際的USB物理設備。
usbMSCDevDestroy();從系統中刪除設備。首先釋放設備占有的資源,從設備鏈表中移除該設備,同時調用usbdPipeDestroy()銷毀該設備與主機之間的通信管道,最后釋放設備結構體。
usbMSCDevShutDown();該例程卸載已注冊的設備驅動程序,并回收所有已分配資源,包括刪除設備、回收信號量等。
2.1.2 兩個重要的回調函數
在編寫USB設備驅動程序時,除了上述接口函數外,還需要編寫另外兩個重要的回調函數:usbMSCDevAttachCallback()和usbMSCIrpCallback()。usbMSCDevAttachCallback()用于跟蹤設備的請求實現動態接入或刪除;usbMSCIrpCallback()是一個IRP callback,當每一個IRP執行完成之后調用該回調函數,實現IRP之間同步。
2.2 設備標識
在VxWorks中USB設備用USBD_NODE_ID來唯一區別,它是USBD用來跟蹤一個特定設備的句柄,它與USB設備的真正USB地址無關。這表明Client并不關心設備是物理上與哪一個USB主控制器連接,應用為每個設備抽象一個Node ID,使Client可以不用考慮物理設備的連接細節以及USB地址分配。當一個Client通知有一個設備連接或斷開時,USBD經常通過Node Id來定位設備。同樣,當USB設備與USBD通信時,它必須向USBD傳遞該設備的Node Id。Node ID通常作為設備結構體的一個重要成員。
2.3 回調(Callback)
USB操作是嚴格遵守時序的,WindRiver USBD采用回調機制來解決時序問題。例如在USBD識別一個動態連接事件之后會激活一個動態attach callback操作,這是一個注冊到USBD的回調例程,回調函數會判斷當前的操作,如果是接入(USBD_DYNA_ATTACH),就會調用usbMSCDevCreate()來在當前的USB句柄上創建一個邏輯設備結構體;如果是移出(USBD_DYNA_REMOVE)就會調用usbMSCDevDestroy()來刪除設備。同樣,當USBD處理完成一個USB IRP之后,Client的一個IRP callback被激活,該回調例程是由Irp.userCallback域來指定。在IRP callback中釋放IRP同步信號量。
2.4 數據傳輸
USB設備與主機之間是通過USB管道進行通信的。一旦Client配置完一個設備,就可以利用USBD提供的管道(pipe)傳輸功能與設備進行數據交換。管道是建立在USBD Client與設備特定的endpoint之間的單向通道。調用usbdPipeCreate()函數創建一個管道后返回一個管道句柄USBD_PIPE_HANDLE,以后所有的讀寫操作都分別在各自的管道句柄上進行。管道在剛創建完后是處于終止的狀態,為了能有效使用它們,還必須用usbdFeatureClear()來清除該終止狀態。每一個管道有以下性質:USBD_NODE_ID、端點地址,管道類型、數據流方向、包的最大有效載荷量、帶寬需求等。對于塊傳輸沒有帶寬限制。VxWorks的USB驅動程序的各層驅動程序間通過IRP機制進行通信,當有數據傳輸請求發生時,上層向下傳遞IRP。USBD得到請求后,調用HCD接口,將IRP轉化為usb的傳輸。這就需要提供一個特殊的回調函數usbMSCIrpCallback(),當塊傳輸結束時調用該回調函數。以下是讀設備的具體過程。
1) 創建設備時創建out和in管道:
usbdPipeCreate(usbdHandle, NodeId, outEpAddress, configuration, interface, USB_XFRTYPE_BULK, USB_DIR_OUT,maxPacketSize,0,0, outPipeHandle);
usbdPipeCreate(usbdHandle, NodeId, inEpAddress, configuration, interface, USB_XFRTYPE_BULK, USB_DIR_IN,maxPacketSize,0,0, inoutPipeHandle);
2) 定義IRP callback:usbMSCIrpCallback(pVOID p)。
3) 構造USB_IRP outIrp請求包。
4) 提交outIrp:usbdTransfer (usbdHandle, outPipeHandle, outIrp)。讀命令在outIrp.bfrList[0].pBfr域中。
5) 等待outIrp處理結束,結束調用usbMSCIrpCallback()釋放IRP同步信號量。
6) 構造USB_IRP inIrp請求包。
7) 提交inIrp:usbdTransfer (usbdHandle, inPipeHandle, inIrp)。
8) 等待inIrp處理結束,讀取的數據在inIrp.bfrList[0].pBfr域中。
2.5 多個設備管理
驅動程序用LIST_HEAD 來存儲多個設備,每個接入的USB設備利用其LINK節點加入到LIST_HEAD鏈表當中;一個LINK節點包含三部分:linkFwd指針、linkBack指針和pStruct指針;其中pStruct指向一個待連接的設備結構體。當有一個設備創建完成時調用usbListLink()將該設備加入鏈表,刪除設備之前調用usbListUnlink()從鏈表中移出該設備。由于USB設備是用Node ID來唯一標識的,在查找設備的時需要利用usbListNext()來遍歷設備鏈表,直到某個設備的Node ID與特定設備的Node ID相匹配為止。在多個設備管理時,采用USBD_NODE_ID usbd_scan_id[DEVICE_NUM]數組來存放多個設備的Node ID,在對設備進行讀寫時只需提供設備的索引序號就可以直接得到設備Node ID,提高了對設備的訪問速度。
3 VxWorks下LM9833驅動程序實現
3.1 LM9833 USB接口簡介
LM9833掃描儀控制器在一個單獨的IC上可以提供一個完整的USB圖像掃描控制系統。它的USB接口提供4個USB端點(Control Endpoint,Interrupt Endpoint,Bulk In Endpoint和Bulk Out Endpoint),內部有00~7F個寄存器,其中00寄存器是存放一個8bits的圖象數據,其它分別為控制或狀態寄存器。通過向bulk out端點發送四字節的讀命令可以從bulk in端點讀取這些寄存器的值,也可以向bulk out端點發送四字節的寫命令加待寫數據完成寫寄存器。四字節命令依次表示:讀寫模式(1字節)、起始地址(1字節)、讀寫長度(2字節)。其中讀寫模式bit0為0表示寫,1表示讀;bit1為0表示非增模式,為1表示增模式,即讀寫寄存器完成之后寄存器地址加1。LM9833的工作過程就是通過讀寫這些寄存器來完成的。
3.2 設備描述符結構
typedef struct _usbScanDev
{
USBD_NODE_IDscanDevId;/* USBD node ID of the device */
UINT16configuration;/* Configuration value*/
UINT16interface;/* Interface number */
UINT16altSetting; /* Alternate setting of interface */
UINT16outEpAddress; /* Bulk out EP address*/
UINT16inEpAddress;/* Bulk in EP address */
USBD_PIPE_HANDLEoutPipe;/* Pipe handle for Bulk out EP*/
USBD_PIPE_HANDLEinPipe; /* Pipe handle for Bulk in EP */
USB_IRP inIrp;/* IRP used for bulk-in data*/
USB_IRP outIrp; /* IRP used for bulk-out data */
USB_IRP statusIrp;/* IRP used for reading status*/
UINT8 * scanInData; /* Pointer for bulk-in data */
UINT8 * scanOutData;/* Pointer for bulk-out data*/
BOOLconnected;/* TRUE if USB_SCAN device connected*/
LINKscanDevLink;/* Link to other SCAN devices*/
UINT8CommandByte[4]; /* Which read/write command the device*/
UINT16actBytes;/* actual bytes will be transfered */
UINT8direction;/* data transfer direction*/
} USB_SCAN_DEV, *pUSB_SCAN_DEV;
設備描述符結構中包含了設備的一些重要信息和訪問該設備時的必須資源,如Node ID、IN/OUT管道、IN/OUT IRP等等。
3.3 注冊設備(LM9833)驅動程序
注冊驅動程序一般包含兩大步:與USBD建立連接和注冊attach callback。以下是注冊LM9833驅動程序的源代碼。
#define USB_SCAN_CLASS 0xff
#define USB_SCAN_SUB_CLASS0x00
#define USB_SCAN_DRIVE_PROTOCOL0xff
STATUS usbScanDevInit()
{……
if(usbdClientRegister (\"SCAN_CLASS\", usbdHandle)!=OK||
usbdDynamicAttachRegister (usbdHandle, USB_SCAN_CLASS, USB_SCAN_SUB_CLASS,
USB_SCAN_DRIVE_PROTOCOL, usbScanDevAttachCallback) != OK)
……
}
usbScanDevInit()調用usbdClientRegister()向USBD注冊一個名為SCAN_CLASS的Client,同時調用usbdDynamicAttachRegister()向USBD注冊一個回調例程usbScanDevAttachCallback (),跟蹤該Client請求,當設備動態地接入或移出系統時會自動地調用該回調函數。一個Client在利用usbdDynamicAttachRegister()進行注冊時只對特定的class、subclass、protocol感興趣。在成功注冊Client后,USBD返回一個Client句柄(USBD_CLIENT_HANDLE),以后對USBD的調用都會用到這個句柄。Attach callback 如下。
LOCAL VOID usbScanDevAttachCallback
(
USBD_NODE_ID nodeId,
UINT16 attachAction,
UINT16 configuration,
UINT16 interface,
UINT16 deviceClass,
UINT16 deviceSubClass,
UINT16 deviceProtocol
)
該回調函數主要響應外部設備的動作,實現USB設備的動態接入和移除。
3.4 創建設備
創建設備函數如下:
LOCAL STATUS usbScanPhysDevCreate(USBD_NODE_ID nodeId, UINT16 configuration, UINT16 interface)
當接入設備時激活usbScanDevAttachCallback()操作,回調函數會根據接入(USBD_DYNA_ATTACH)動作調用usbScanPhysDevCreate()創建一個邏輯設備,在創建設備的同時,創建設備與USB主機之間通信的管道(pipe)。管道是建立在USB設備端點(endpoint)之上,是主機與設備之間數據傳輸的單向通道。設備與主機之間數據傳輸管道是建立在批量端點(bulk endpoint)之上,有BULK_IN和BULK_OUT兩個端點,從而建立雙向的數據通路。最后將該設備加入設備鏈表,進行多個設備的管理。創建設備流程如圖2所示。

圖2 創建設備流程
3.5 讀寫設備
對設備進行讀寫時,首先需要將讀寫函數轉換成設備能夠識別的命令,對于LM9833來說,需將讀寫函數轉換成LM9833所能識別的四字節讀寫命令,讀寫時將這四字節的命令置于IRP包數據域的最前端,這樣到數據到達設備時首先接收到的是四個字節的命令,LM9833會根據這四個字節的命令完成相應的功能。讀寫函數原型為:
STATUS usbScanRead/usbScanWrite
(
UINT dev, /* sequence number of the device*/
UINT Addr, /* start address in register */
UINT8 *pBuffer, /* pBuffer to receive/send data from/to device*/
UINT Len, /* lenth of pBuffer */
BOOL bIncrement /* incremece of address in register or not */
)
LM9833的一個讀寫控制流程如圖3所示,查找設備流程如圖4所示。

圖3 LM9833讀寫控制流程

圖4 查找設備流程
設備命令組幀:
const unsigned int MODE_INC_READ= 0x03;
const unsigned int MODE_NOINC_READ = 0x01;
const unsigned int MODE_INC_WRITE= 0x02;
const unsigned int MODE_NOINC_WRITE = 0x00;
switch (Cmd)
{
case USB_SCAN_WRITE:/* bulk out*/
pScanDev->CommandByte[0] = (bIncrement>0)? MODE_INC_WRITE : MODE_NOINC_WRITE;
pScanDev->CommandByte[1] = Addr+((bIncrement>0)? i : 0);
pScanDev->CommandByte[2] = (Len >> 8); /* length of the data to be written */
pScanDev->CommandByte[3] = (Len 0xff);
memcpy(pScanDev->scanOutData, pScanDev->CommandByte, 4);/* 4 bytes Lm9833 command followed by write data */
memcpy(pScanDev->scanOutData + 4, pBuffer + i, Len);
pScanDev->actBytes = Len+4; /* actual length to be transfered*/
pScanDev->direction = USB_SCAN_DIR_OUT;
break;
case USB_SCAN_READ: /* bulk in*/
pScanDev->CommandByte[0] = (bIncrement>0)? MODE_INC_READ : MODE_NOINC_READ;
pScanDev->CommandByte[1] = Addr+((bIncrement>0)? i : 0);
pScanDev->CommandByte[2] = (Len >> 8);
pScanDev->CommandByte[3] = (Len 0xff);
pScanDev->actBytes = Len;/*length of receive buf*/
pScanDev->direction= USB_SCAN_DIR_IN;
break;
default:
status= ERROR;
break;
}
命令組幀完成之后進行IRP打包,寫設備在構造outIrp時用pScanDev->scanOutData填充outIrp.bfrList[0].pBfr域,pScanDev->scanOutData不僅包含了LM9833的四字節命令,而且在其后包含了待寫數據;而讀設備在構造outIrp時用pScanDev->CommandByte填充outIrp.bfrList[0].pBfr域,pScanDev->CommandByte只包含了LM9833的四字節命令。之后調用usbdTransfer()提交IRP完成主機與設備之間的數據傳輸。
3.6 卸載設備驅動程序
在卸載驅動程序時,首先遍歷設備鏈表,刪除設備鏈表中所有的設備,然后再調用usbdDynamicAttachUnRegister()和usbdClientUnregister()分別取消對attach callback和client的注冊。
STATUS usbScanDevShutDown()
{
pUSB_SCAN_DEV pScanDev;/* Pointer to Scan Device */
……
while ((pScanDev = usbListFirst (scanDevList)) != NULL)/* release any Scan devices */
usbScanDevDestroy (pScanDev);
/* Unregister attach callback*/
usbdDynamicAttachUnRegister (usbdHandle, USB_SCAN_CLASS, USB_SCAN_SUB_CLASS,
USB_SCAN_DRIVE_PROTOCOL, usbScanDevAttachCallback);
if (usbdHandle != NULL)/* Unregister with the USBD*/
{
usbdClientUnregister (usbdHandle);
usbdHandle = NULL;
}
/* release other resources */
……
}
4 結束語
VxWorks下USB設備驅動具體分為Clinet注冊,注銷,創建管道,配置,數據發送,以及回調函數等。本文以編寫LM9833驅動程序為例,介紹了VxWorks嵌入式實時操作系統下USB設備驅動程序的開發的一般方法和流程,對一些關鍵步驟進行了詳細討論。作為實踐成果,該驅動開發已經良好地應用到某大型掃描儀設備中,實現了多路CCD的數據獲取;同時作者認為,文中所涉及的模型也為在VxWorks下開發其它USB設備提供了一種方法,具有一定指導意義。
參考文獻:
[1] WindRiver.USB devloper's kit user's guide and release notes[M].USA:WindRiver Company,1998.
[2] 周啟平,張楊.VxWorks下設備驅動程序及BSP開發指南[M].北京:中國電力出版社,2004.
[3] 羅國慶. VxWorks與嵌入式軟件開發[M].北京:機械工業出版社,2003.
[4] 埃克爾遜.USB大全[M].陳逸,譯.北京:中國電力出版社,2001.
[5] 朱澤誠,王興元,李潔.VxWorks實時操作系統的USB驅動程序原理與分析[J].計算機工程與應用,2003,(22):122-125.
[6] 林寶如,張帆,陳怡. 基于VxWorks操作系統的USB驅動分析[J].重慶建筑大學學報,2005,27(3):98-100.