VxWorks下USB设备驱动程序设计

时间:2022-09-04 06:27:51

VxWorks下USB设备驱动程序设计

摘要:介绍了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_ID scanDevId; /* USBD node ID of the device */

UINT16 configuration; /* Configuration value */

UINT16 interface; /* Interface number */

UINT16 altSetting; /* Alternate setting of interface */

UINT16 outEpAddress; /* Bulk out EP address */

UINT16 inEpAddress; /* Bulk in EP address */

USBD_PIPE_HANDLE outPipe; /* Pipe handle for Bulk out EP */

USBD_PIPE_HANDLE inPipe; /* 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 */

BOOL connected; /* TRUE if USB_SCAN device connected*/

LINK scanDevLink; /* Link to other SCAN devices */

UINT8 CommandByte[4]; /* Which read/write command the device */

UINT16 actBytes; /* actual bytes will be transfered */

UINT8 direction; /* 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);

上一篇:谈网络安全的防范措施 下一篇:浅谈中学信息技术教学的实践教学