• USB设备驱动概述


     

    第17章  USB设备驱动

    USB设备驱动和PCI设备驱动是PC中最基本的两种设备驱动程序。与PCI协议相比,USB协议更复杂,涉及面较多。

    本章将介绍USB设备驱动开发。首先介绍USB协议,使读者对USB协议有个总体认识。然后介绍USB设备在WDM中的开发框架。由于操作系统的USB总线驱动程序提供了丰富的功能调用,因此开发USB驱动开发变得相对简单,仅仅须要调用USB总线驱动接口。

    17.1  USB总线协议

    USB总线协议比PCI协议复杂的多,涉及USB物理层协议,又涉及USB传输层协议等。对于USB驱动程序开发人员来说,不须要对USB协议的每一个细节都非常清楚。本节概要地介绍USB总线协议,并对驱动开发人员须要了解的地方进行详细介绍。

    17.1.1  USB设备简单介绍

    USB即通用串行总线(UniversalSerial Bus)。是一种支持即插即用的新型串行接口。

    也有人称之为“菊链(daisy-chaining)”,是由于在一条“线缆”上有链接127 个设备的能力。USB要比标准串行口快得多。其传输数据率可达每秒4Mb~12Mb(而老式的串行口最多是每秒115Kb)。

    除了具有较高的传输率外,它还能给外围设备提供支持。

    须要注意的是,这不是一种新的总线标准,而是计算机系统连接外围设备(如键盘、鼠标、打印机等)的输入/输出接口标准。到如今为止,计算机系统连接外围设备的接口还没有统一的标准,比如。键盘的插口是圆的、连接打印机要用9针或25针的并行接口、鼠标则要用9针或25针的串行接口。

    USB能把这些不同的接口统一起来,仅用一个4针插头作为标准插头,如图17-1所看到的。通过这个标准插头,採用菊花链形式能够把全部的外设连接起来,而且不会损失带宽。

    USB正在代替当前PC上的串口和并口。

     

    17-1  USB的四条传输线

    以USB方式连接设备时,全部的外设都在机箱外连接,连接外设不必再打开机箱。同意外设热插拔。而不必关闭主机电源。USB採用“级联”方式,即每一个USB设备用一个USB 插头连接到另一个外设的USB插座上。而其本身又提供一个USB插座供下一个USB外设连接用。通过这样的相似菊花链式的连接。一个USB控制器能够连接多达127个外设。而每一个外设间距离(线缆长度)可达5米。USB能智能识别USB链上外围设备的插入或拆卸。

    它可使多个设备在一个port上执行,速度也比方今的串行口或并行口快得多,而且其总的连线在理论上说能够无限延长。对PC来说。以上这些都是一些难得的长处,由于不再须要PS/2port、MIDIport等各种不同的port了,还能够随时随地在各种设备上随意插拔。能够在一个port上执行鼠标、控制手柄、键盘以及其它输入装置(比如数码相机),而且,也不必又一次启动系统去做这些工作。如今USB设备正在高速增多,且由于操作系统已内置支持USB的功能,因而用户如今就能够方便地使用。显然,USB为PC的外设扩充提供了一个非常好的解决方式。

    眼下USB技术的发展,已经同意用户在不使用网卡、HUB的情况下,直接通过USB技术将几台计算机连接起来组成小型局域网。用户仅仅须要给各台计算机起个名字就能够開始工作。这样的网络具备Ethernet网络的各种长处,同一时候少了Ethernet网络的很多限制。

    假设一位用户上班时使用笔记本电脑,回家时使用PC机,为实现传输数据,他能够通过採用USB技术的接口将两部电脑连接起来交换资源,其传输数据速度可达12Mbps,这是传统串行口无法比拟的。而且用户在组网的时候根本无须考虑DIP、IRQ等问题。此类技术除支持兼容Ethernet的软硬件外。也支持标准的网络通信协议。包含IPX/SPX、NetBEUI和TCP/IP,这为通过USB技术组成的小局域网连接至大型网络或Internet提供了条件。

    17.1.2  USB连接拓扑结构

    USB设备的连接如图17-2所看到的,对于每一个PC来说,都有一个或者多个称为Host控制器的设备。该Host控制器和一个根Hub作为一个总体。

    这个根Hub下能够接多级的Hub,每一个子Hub又能够接子Hub。每一个USB作为一个节点接在不同级别的Hub上。

    (1)USB Host控制器:每一个PC的主板上都会有多个Host控制器,这个Host控制器事实上就是一个PCI设备,挂载在PCI总线上。Host控制器的驱动由微软公司提供,如图17-3所看到的。这是笔者PC中的Host控制器及USB Hub的驱动。值得注意的是,这里Host分别有两种驱动。一种是1.0,另一种是2.0。分别相应着USB协议1.0和USB协议2.0。

     

    (点击查看大图)图17-2  USB连接拓扑结构

    2USB Hub:每一个USBHost控制器都会自带一个USB Hub,被称为根(Root)Hub。这个根Hub能够接子(Sub)Hub,每一个Hub上挂载USB设备。

    一般PC8USB口,通过外接USB Hub,能够插很多其它的USB设备。

    USB设备插入到USBHub或从上面拔出时。都会发出电信号通知系统。这样能够枚举USB设备,比如当被插入的时候,系统就会创建一个USB物理总线。并询问用户安装设备驱动。如图17-4所看到的为一个典型的USB Hub的示意图。

     

    (点击查看大图)图17-3  USB HostUSB Hub驱动 

     

     

    (点击查看大图)图17-4  USB Hub示意图

    (3)USB设备:USB设备就是插在USB总线上工作的设备。广义地讲USB Hub也算是USB设备。

    每一个根USB Hub下能够直接或间接地连接127个设备。而且彼此不会干扰。对于用户来说。能够看成是USB设备和USB控制器直接相连,之间通信须要满足USB的通信协议。

    有的USB设备功能单一,直接挂载在USB Hub上。

    而有的USB设备功能复杂,会将多个USB功能连在一起。成为一个复合设备,它甚至能够自己内部带一个Hub,这个Hub下接多个USB子设备。其和多个子设备作为一个总体当做一个USB设备,如图17-5所看到的。

    以上是USB的物理拓扑结构。但对于用户来说。能够略去USB Hub的概念,或者说USB Hub的概念对于用户能够看成是透明的。用户仅仅须要将USB设备理解成一个USB Host连接多个逻辑设备。

    可能逻辑设备1和逻辑设备2是集中在第一个物理设备里。比如有的手机连接计算机后,系统会当做多个USB设备载入。因此,作为用户须要用如图17-6所看到的的逻辑拓扑结构理解USB拓扑结构。

     

    17-5  符合设备

     

     

    17-6  USB逻辑拓扑结构

    但对于详细USB设备来说,每一个USB设备的传输绝对不会影响其它USB设备的传输。比如,在有USB设备传输的时候,其它USB设备的带宽不会被占用。对于USB设备来说,每一个USB设备是直接连接到USB Host控制器上的。因此,应该用如图17-7所看到的的视角考虑USB设备的通信。

     

    17-7  用户对USB设备的观察

    17.1.3  USB通信的流程

    USB的连接模式是Host和Devcie的连接模式,它不同于早期的串口和并口,全部的请求必须是Host向Device发出,这就使Host端设计相对复杂,而Device端设计相对简单。Host端会在主板的南桥设计好,而Device的厂商众多,厂商仅仅须要遵循USB协议。重点精力能够放在设备的研发上,而与PC的通信不用过多考虑。

    在USB的通信中,能够看成是一个分层的协议。分为三个层次,即最底层USB总线接口层、USB设备层、功能通信层。如图17-8所看到的。

     

    17-8  USB协议

    以USB摄像头设备为例,视频播放软件想通过USB总线得到USB摄像头捕捉的视频数据。这就相当于在功能层上。Clinet SW是视频播放软件,Function是USB摄像头。而这些数据的读取须要USB设备层提供的服务。在这一层上,主要是USB设备的驱动调度Host控制器向USB摄像头发出读请求。每一个USB设备会有多个管道。使用哪个管道,传输的大小都须要指定。这个层次的USBSystem SW就是USB摄像头的驱动程序。而在USB设备一端通常会有小单片机或者处理芯片负责响应这样的读请求。而这一层的传输又依赖于USB总线接口层的服务。在这一层。全然是USB的物理协议,包含怎样分成更小的包(packages)传输,怎样保证每次包传输不丢失数据等。

    对于USB设备驱动程序猿,主要是工作在USB设备层,向“上”相应用程序提供读写等接口,向“下”将读取某个管道的请求发往USB Host控制器驱动程序。它实现了最底层的传输请求。

    对于每一个USB设备,都有一个或者多个的接口(Interface)。每一个Interface都有多个端点(Endpoints)。每一个端点通过管道(Pipes)和USB Host控制器连接。

    每一个USB设备都会有一个特殊的端点,即Endpoint0。它负责传输设备的描写叙述信息,同一时候也负责传输PC与设备之间的控制信息。如图17-9所看到的。

     

    17-9  USB管道与端点

    17.1.4  USB四种传输模式

    当USB插入USB总线时。USB控制器会自己主动为该USB设备分配一个数字来标示这个设备。另外。在设备的每一个端点都有一个数字来表明这个端点。
    USB设备驱动向USB控制器驱动请求的每次传输被称为一个事务(Transaction)。事务有四种类型。各自是Bulk Transaction、Control Transaction、Interrupt Transaction和IsochronousTransaction。每次事务都会分解成若干个数据包在USB总线上传输。每次传输必须历经两个或三个部分,第一部分是USB控制器向USB设备发出命令,第二部分是USB控制器和USB设备之间传递读写请求。其方向主要看第一部分的命令是读还是写,第二部分有时候能够没有。第三部分是握手信号。

    下面针对这四种传输。分别进行解说。

    1.Bulk传输事务

    顾名思义,改种事务传输主要是大块的数据,传送这样的事务的管道叫做Bulk管道。这样的事务传输的时候分为三部分,如图17-10所看到的。

    第一部分是Host端发出一个Bulk的令牌请求,假设令牌是IN请求则是从Device到Host的请求,假设是OUT令牌,则是从Host到Device端的请求。
    第二部分是传送数据的阶段。依据先前请求的令牌的类型,传输数据有可能是IN方向,也有可能是OUT方向。传输数据的时候用DATA0和DATA1令牌携带着数据交替传送。

    第三部分是握手信号。假设数据是IN方向。握手信号应该是Host端发出,假设是OUT方向,握手信号应该是Device端发出。

    握手信号能够为ACK,表示正常响应,也能够是NAK表示没有正确传送。

    STALL表示出现主机不可预知的错误。
    在第二部分。即传输数据包的时候,数据传送由DATA0和DATA1数据包交替发送。传输数据格式DATA1和DATA0,这两个是反复数据。确保在1数据丢失时0能够补上。不至于数据丢失。

    如图17-11所看到的。

     

    (点击查看大图)图17-10  Bulk传输

     

     

    17-11  Bulk传输时的令牌

    2.控制传输事务

    控制传输是负责向USB设置一些控制信息。传送这样的事务的管道是控制管道。在每一个USB设备中都会有控制管道。也就是说控制管道在USB设备中是必须的。

    控制传输也分为三个阶段。即令牌阶段、数据传送阶段、握手阶段,如图17-12所看到的。

     

    17-12  控制传输事务

    3.中断传输事务

    在USB设备中。有种处理机制相似于PCI中断的机制,这就是中断事务。中断事务的数据量非常小。一般用于通知Host某个事件的来临,比如USB鼠标,鼠标移动或者鼠标单击等操作都会通过中断管道来向Host传送事件。

    在中断事务中,也分为三个阶段,即令牌阶段、传输数据阶段、握手阶段,如图17-13所看到的。

     

    (点击查看大图)图17-13  中断传输事务

    4.同步传输事务

    USB设备中另一种事务叫同步传输事务,这样的事务能保证传输的同步性。比如,在USB摄像头中传输视频数据的时候会採用这样的事务,这样的事务能保证每秒有固定的传输量,但与Bulk传输不同。它同意有一定的误码率,这样符合视频会议等传输的需求,由于视频会议首先要保证实时性,在一定条件下。同意有一定的误码率。

    同步传输事务有仅仅有两个阶段。即令牌阶段、数据阶段,由于不关心数据的正确性。故没有握手阶段。如图17-14所看到的。

     

    17-14  同步传输事务

    17.2  Windows下的USB驱动

    在Windows上开发USB驱动相对来说比較简单,主要是由于微软已经提供了完备的USB总线驱动,程序猿编写的设备驱动仅仅需调用总线驱动就可以。在Windows上另一些工具软件能够帮助开发人员查看USB的各类信息,包含设备描写叙述符、配置描写叙述符等。当然,这些描写叙述符在驱动中也会用到。

    本节将介绍这些工具软件。并介绍这些描写叙述符。

    17.2.1  观察USB设备的工具

    在学习编写USB驱动之前,有几个USB查看工具须要向读者介绍一下,通过用这些工具能方便地学习USB协议。

    首先须要介绍的就是DDK中提供的工具。该工具叫usbview。位于DDK的子文件夹srcwdmusbusbview下,须要用DDK编译环境进行编译。如图17-15所看到的为usbview的界面。在笔者的计算机里插入了一个USB移动硬盘。在这个软件中已经清楚地列举除了该USB设备的各个信息,如图设备描写叙述符、管道描写叙述符等。

     

    (点击查看大图)图17-15  USBView

    另一个实用的工具是BusHound,如图17-16所看到的。BusHound用于监视USB设备的传输数据。它的实现原理是在USB设备驱动之上载入一层过滤驱动程序,将IRP进行拦截。因此能够观察到全部USB数据的传输。

    使用该软件时须要指明监视哪种USB设备,如图17-16所看到的,在须要监视的设备上打钩。

    笔者这里监视的是一个USB移动硬盘。

    另外。在下面会列出该设备的基本信息。如管道0是控制管道,管道1是输出管道,管道2是输入管道。


     

    (点击查看大图)图17-16  BusHound

    该软件将USB的传输全然进行监视,包含每一个USB的各个管道中的传输情况,都一一进行记录,非常有利于调试驱动。

    如图17-17所看到的,Device一栏中标识是何种设备,比如27.2意味着第27号设备的第2号管道。Phase一栏标识传输是输入还是输出。

    Data一栏中记录着一次传输的详细内容。

     

    (点击查看大图)图17-17  BusHound监视传输数据

    17.2.2  USB设备请求

    USB的请求是通过控制管道传输的。请求是8个字节,依照如表17-1所看到的的排列发送:

    表17-1

    偏移量

    变量

    大小

    数值

    描    

    0

    bmRequestType

    1字节

    位图

    第7位: 数据方向位

    0 = 主机到设备

    1 = 设备到主机

    第6-5位:类型

    0 = 标准

    1 = 

    2 = 厂商自己定义

    3 = 保留

    第4-0位:接收者

    0 = 对设备的请求

    1 = 对接口的请求

    2 = 对管道(端点)的请求

    3 = 其它

    4-31 = 保留

    1

    bRequest

    1字节

    数值

    请求类别

    2

    wValue

    2字节

    数值

    不同请求含义不同

    4

    wIndex

    2字节

    数值

    不同请求含义不同

    6

    wLength

    2字节

    数值

    表示须要有多少数据返回

    当中,bRequest代表不同的USB请求,它们各自是下面的几种请求。定义在DDK的usb100.h文件里:

    #define USB_REQUEST_GET_STATUS                   0x00
    #define USB_REQUEST_CLEAR_FEATURE                0x01
    #define USB_REQUEST_SET_FEATURE                 0x03
    #define USB_REQUEST_SET_ADDRESS                   0x05
    #define USB_REQUEST_GET_DESCRIPTOR                0x06
    #define USB_REQUEST_SET_DESCRIPTOR               0x07
    #define USB_REQUEST_GET_CONFIGURATION           0x08
    #define USB_REQUEST_SET_CONFIGURATION          0x09
    #define USB_REQUEST_GET_INTERFACE                0x0A
    #define USB_REQUEST_SET_INTERFACE                0x0B

    17.2.3  设备描写叙述符

    在控制管道发起USB设备请求,当中非经常见的请求是USB_REQUEST_GET_ DESCRIPTOR,即请求USB设备回答设备或者管道描写叙述符。在请求描写叙述符时。bmRequestType能够指定是针对设备还是针对管道的。

    当请求设备描写叙述符后,设备会回答主机该设备的设备描写叙述符。设备描写叙述符是一种固定的数据结构,它定义在DDK中的usb100.h文件里。

    typedef struct _USB_DEVICE_DESCRIPTOR {
    UCHAR bLength;
    UCHAR bDescriptorType;
    USHORT bcdUSB;
    UCHAR bDeviceClass;
    UCHAR bDeviceSubClass;
    UCHAR bDeviceProtocol;
    UCHAR bMaxPacketSize0;
    USHORT idVendor;
    USHORT idProduct;
    USHORT bcdDevice;
    UCHAR iManufacturer;
    UCHAR iProduct;
    UCHAR iSerialNumber;
    UCHAR bNumConfigurations;
    } USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;

    bLength:设备描写叙述符的bLength域应等于18。

    bDescriptorType:bDescriptorType域应等于1。以指出该结构是一个设备描写叙述符。

    bcdUSB:bcdUSB域包含该描写叙述符遵循的USB规范的版本号号(以BCD编码)。如今。设备能够使用值0x0100或0x0110来指出它所遵循的是1.0版本号还是1.1版本号的USB规范。

    bDeviceClass:指出设备类型。

    bDeviceSubClass:指出设备子类型。

    bDeviceProtocol:指出设备类型所使用的协议。

    bMaxPacketSize0:设备描写叙述符的bMaxPacketSize0域,给出了默认控制端点(端点0)上的数据包容量的最大值。

    idVendor:厂商代码。

    idProduct:厂商专用的产品标识。

    bcdDevice:bcdDevice指出设备的发行版本号号(0x0100相应版本号1.0)。

    iManufacturer、iProduct、iSerialNumber:iManufacturer、iProduct和iSerialNumber域指向一个串描写叙述符。该串描写叙述符用人类可读的语言描写叙述设备生产厂商、产品和序列号。这些串是可选的,0值代表没有描写叙述串。假设在设备上放入了序列号串。Microsoft建议应使每一个物理设备的序列号唯一。

    bNumConfigurations:bNumConfigurations指出该设备能实现多少种配置。Microsoft的驱动程序仅工作于设备的第一种配置(1号配置)。


    如图17-18所看到的为笔者用BusHound截获的USB移动硬盘的请求设备描写叙述符。前面已经介绍过。在控制管道中,传输分为三个阶段。第一阶段是令牌阶段,这里Host向设备发送“80 06 00 01 00 00 12 00”8个字节,能够參见表17-1中的解释。第二阶段是传输数据阶段,方向是由设备传给主机,这个样例中设备给主机传递了18(0x12)个字节。这18个字节相应着USB_DEVICE_DESCRIPTOR数据结构。第三阶段是握手阶段,在BusHound软件中没有体现出来。

     

    (点击查看大图)图17-18  BusHound抓取设备描写叙述符

    17.2.4  配置描写叙述符

    每一个设备有一个或多个配置描写叙述符。它们描写叙述了设备能实行的各种配置方式。DDK中定义的配置描写叙述符结构例如以下:

    typedef struct _USB_CONFIGURATION_DESCRIPTOR {
    UCHAR bLength;
    UCHAR bDescriptorType;
    USHORT wTotalLength;
    UCHAR bNumInterfaces;
    UCHAR bConfigurationValue;
    UCHAR iConfiguration;
    UCHAR bmAttributes;
    UCHAR MaxPower;
    } USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR;

    bLength:bLength应该为9。

    bDescriptorType:bDescriptorType应该为2,即是一个9字节长的配置描写叙述符。

    wTotalLength:wTotalLength域为该配置描写叙述符长度加上该配置内全部接口和端点描写叙述符长度的总和。

    通常,主机在发出一个GET_DESCRIPTOR请求并正确接收到9字节长的配置描写叙述符后,就会再发出一个GET_DESCRIPTOR请求并指定这个总长度。第二个请求把这个大联合描写叙述符传输回来。

    bNumInterfaces:指出该配置有多少个接口。

    bConfigurationValue:bConfigurationValue域是该配置的索引值。

    iConfiguration:iConfiguration域是一个可选的串描写叙述符索引,指向描写叙述该配置的Unicode字符串。此值为0表明该配置没有串描写叙述符。

    bmAttributes:bmAttributes字节包含描写叙述该配置中设备电源和其它特性的位掩码。

    MaxPower:MaxPower域中指出要从USB总线上获取的最大电流量。

    如图17-19所看到的为笔者用BusHound截获的USB移动硬盘的请求配置描写叙述符。

    当中第一行是Host向Device发送Token令牌,而第二行是Device向Host返回的数据。


     

    (点击查看大图)图17-19  BusHound抓取设配置描写叙述符

    17.2.5  接口描写叙述符

    每一个配置有一个或多个接口描写叙述符。它们描写叙述了设备提供功能的接口。

     typedef struct _USB_INTERFACE_DESCRIPTOR {
    UCHAR bLength;
    UCHAR bDescriptorType;
    UCHAR bInterfaceNumber;
    UCHAR bAlternateSetting;
    UCHAR bNumEndpoints;
    UCHAR bInterfaceClass;
    UCHAR bInterfaceSubClass;
    UCHAR bInterfaceProtocol;
    UCHAR iInterface;
    } USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;

    bLength:bLength应该为9。

    bDescriptorType:bDescriptorType域应为4。

    bInterfaceNumber:bInterfaceNumber是索引值。

    bAlternateSetting:bAlternateSetting是索引值。用在SET_INTERFACE控制事务中以指定要激活的接口。

    bNumEndpoints:bNumEndpoints域指出该接口有多少个端点。不包含端点0,端点0被觉得总是存在的,而且是接口的一部分。

    bInterfaceClass:bInterfaceClass为接口类。

    bInterfaceSubClass:bInterfaceSubClass为子接口类。

    bInterfaceProtocol:bInterfaceProtocol为协议。

    iInterface:iInterface是一个串描写叙述符的索引。0表示该接口无描写叙述串。

    如图17-20所看到的为笔者用BusHound截获的USB移动硬盘的请求配置描写叙述符和接口描写叙述符。

    当中第一行是Host向Device发送Token令牌。而第二行是Device向Host返回的数据。

    能够看出图中有一个配置描写叙述符。后面紧接着一个接口描写叙述符(“09 04 00 00 02 00 06 50 05”)。后面还有两个接口描写叙述符(下一节介绍)。

     

    (点击查看大图)图17-20  BusHound抓取接口描写叙述符

    17.2.6  端点描写叙述符

    接口能够没有或有多个端点描写叙述符,它们描写叙述了处理事务的端点。

    DDK中定义的端点描写叙述符结构例如以下:

     typedef struct _USB_ENDPOINT_DESCRIPTOR {
    UCHAR bLength;
    UCHAR bDescriptorType;
    UCHAR bEndpointAddress;
    UCHAR bmAttributes;
    USHORT wMaxPacketSize;
    UCHAR bInterval;
    } USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;

    bLength:bLength应为7。

    bDescriptorType:bDescriptorType应该为5。

    bEndpointAddress:bEndpointAddress域编码端点的方向性和端点号。

    bmAttributes:bmAttributes的低两位指出端点的类型。0代表控制端点,1代表等时端点。2代表批量端点,3代表中断端点。

    wMaxPacketSize:wMaxPacketSize值指出该端点在一个事务中能传输的最大数据量。

    bInterval:中断端点和等时端点描写叙述符另一个用于指定循检间隔时间的bInterval域。

    17.3  USB驱动开发实例

    本节详细介绍怎样进行USB驱动的开发,本节採用的源代码来源自DDK的源程序。其位置在DDK子文件夹的srcwdmusbulkusb文件夹下。该演示样例非常全面地支持了即插即用IRP的处理。也非常全面地支持了电源管理。同一时候非常好地支持了USB设备的bulk读写。

    假设从头开发USB驱动。往往非常难达到USB驱动的稳定性,所以强烈建议读者在此驱动改动的基础上进行USB驱动开发。

    17.3.1  功能驱动与物理总线驱动

    DDK已经为USB驱动开发人员提供了功能强大的USB物理总线驱动(PDO),程序猿须要做的事情是完毕功能驱动(FDO)的开发。驱动开发人员不须要了解USB怎样将请求转化成数据包等细节。程序猿仅仅须要指定何种管道。发送何种数据就可以。

    当功能驱动想向某个管道发出读写请求时。首先构造请求发给USB总线驱动。

    这样的请求是标准的USB请求,被称为URB(USB Request Block),即USB请求块。

    这样的URB被发送到USB物理总线驱动以后,被USB总线驱动所解释。进而转化成请求发往USB HOST驱动或者USB HUB驱动,如图17-21所看到的。

     

    17-21  总线驱动与功能驱动的关系

    能够看出,USB总线驱动完毕了大部分工作,并留给USB功能驱动标准的接口,即URB请求。

    USB驱动开发人员仅仅须要依据不同的USB设备的设计要求,在相应的管道中发起URB请求就可以。

    17.3.2  构造USB请求包

    USB驱动在与USB设备通信的时候,如在控制管道中获取设备描写叙述符、配置描写叙述符、端点描写叙述符,或者在Bulk管道中获取大量数据。都是通过创建USB请求包(URB)来完毕的。URB中填充须要对USB的请求,然后将URB作为IRP的一个參数传递给底层的USB总线驱动。

    在USB总线驱动中。能够解释不同URB,并将其转化为USB总线上的相应数据包。

    DDK提供了构造URB的内核函数UsbBuildGetDescriptorRequest。其声明例如以下:

    VOID 
    UsbBuildGetDescriptorRequest(
    IN OUT PURB  Urb,
    IN USHORT  Length,
    IN UCHAR  DescriptorType,
    IN UCHAR  Index,
    IN USHORT  LanguageId,
    IN PVOID  TransferBuffer  OPTIONAL,
    IN PMDL  TransferBufferMDL  OPTIONAL,
    IN ULONG  TransferBufferLength,
    IN PURB  Link  OPTIONAL
    );

    Urb:用来输出的URB结构的指针。

    Length:用来描写叙述该URB结构的大小。

    DescriptorType:描写叙述该URB的类型。它能够是USB_DEVICE_DESCRIPTOR_TYPE、USB_CONFIGURATION_DESCRIPTOR_TYPE和USB_STRING_DESCRIPTOR_ TYPE。

    Index:用来描写叙述设备描写叙述符的索引。

    LanguageId:用来描写叙述语言ID。

    TransferBuffer:假设用缓冲区读取设备,TransferBuffer是缓冲区内存的指针。

    TransferBufferMDL:假设用直接读取内存时。TransferBufferMDL是直接读取内存时MDL的指针。

    TransferBufferLength:对于该URB所操作内存的大小。

    在功能驱动中。全部与USB的通信,都须要用这个函数创建URB。并通过IRP发送究竟层USB总线驱动。下面是一个最基本的演示样例。

    #001               UsbBuildGetDescriptorRequest(
    #002                       urb,
    #003                       (USHORT) sizeof(struct _URB_
    CONTROL_DESCRIPTOR_REQUEST),
    #004                       USB_DEVICE_DESCRIPTOR_TYPE,
    #005                       0,
    #006                       0,
    #007                       deviceDescriptor,
    #008                       NULL,
    #009                       siz,
    #010                       NULL);

    17.3.3  发送USB请求包

    功能驱动将URB包构造完毕后。就能够发送究竟层总线驱动上了。

    URB包要和一个IRP相关联起来。这就须要用IoBuildDeviceIoControlRequest创建一个IO控制码的IRP,然后将URB作为IRP的參数。用IoCallDriver将URB发送究竟层总线驱动上。

    由于上层驱动无法知道底层驱动是同步还是异步完毕的,因此须要做一个推断。

    if语句推断当异步完毕IRP时,用事件等待总线驱动完毕这个IRP。

     #001   //该函数实现对发送URBUSB物理总线驱动
    #002   NTSTATUS
    #003   CallUSBD(
    #004       IN PDEVICE_OBJECT DeviceObject,
    #005       IN PURB           Urb
    #006       )
    #007   {
    #008       PIRP               irp;
    #009       KEVENT             event;
    #010       NTSTATUS           ntStatus;
    #011       IO_STATUS_BLOCK    ioStatus;
    #012       PIO_STACK_LOCATION nextStack;
    #013       PDEVICE_EXTENSION  deviceExtension;
    #014  
    #015    //
    首先是变量初始化
    #016       irp = NULL;
    #017       deviceExtension = DeviceObject->DeviceExtension;
    #018       //
    初始化事件
    #019       KeInitializeEvent(&event, NotificationEvent, FALSE);
    #020    //
    创建IO控制码相关的IRP
    #021       irp = IoBuildDeviceIoControlRequest
    (IOCTL_INTERNAL_USB_SUBMIT_URB,
    #022                                           deviceExtension->
    TopOfStackDeviceObject,
    #023                                           NULL,
    #024                                           0,
    #025                                           NULL,
    #026                                           0,
    #027                                           TRUE,
    #028                                           &event,
    #029                                           &ioStatus);
    #030  
    #031       if(!irp) {
    #032     //
    假设IRP创建失败则返回
    #033           BulkUsb_DbgPrint(1, ("IoBuildDeviceIo
    ControlRequest failed "));
    #034           return STATUS_INSUFFICIENT_RESOURCES;
    #035       }
    #036    //
    得到下一层设备栈
    #037       nextStack = IoGetNextIrpStackLocation(irp);
    #038       ASSERT(nextStack != NULL);
    #039       nextStack->Parameters.Others.Argument1 = Urb;
    #040       BulkUsb_DbgPrint(3, ("CallUSBD::"));
    #041       BulkUsb_IoIncrement(deviceExtension);
    #042    //
    通过IoCallDriverIRP发送究竟层驱动
    #043       ntStatus = IoCallDriver(deviceExtension->
    TopOfStackDeviceObject, irp);
    #044    //
    假设IRP是异步完毕时,等待其结束
    #045       if(ntStatus == STATUS_PENDING) {
    #046     //
    等待IRP结束
    #047           KeWaitForSingleObject(&event,
    #048                                 Executive,
    #049                                 KernelMode,
    #050                                 FALSE,
    #051                                 NULL);
    #052           ntStatus = ioStatus.Status;
    #053       }
    #054       //
    调用结束
    #055       BulkUsb_DbgPrint(3, ("CallUSBD::"));
    #056       BulkUsb_IoDecrement(deviceExtension);
    #057       return ntStatus;
    #058   }

    此段代码能够在配套光盘中本章的sys文件夹下找到。

    17.3.4  USB设备初始化

    USB驱动的初始化和一般驱动相似,首先是进入入口函数DriverEntry。在DriverEntry函数中,分别指定各个IRP的派遣函数地址、指定AddDevice例程函数地址、指定Unload例程函数地址等。

    在AddDevice例程中,创建功能设备对象。然后将该对象挂载在总线设备对象之上,从而形成设备栈。另外为设备创建一个设备链接。便于应用程序能够找到这个设备。也能够依据详细须要,从注冊表中读取一些必要的设置。

    17.3.5  USB设备的插拔

    由于USB设备驱动是基于WDM框架的,因此须要对即插即用消息进行处理。

    BulkUSB程序对即插即用IRP的支持非常完好。详细能够參照其代码,这里简单提一下其对插拔的处理。

    插拔设备会设计4个即插即用IRP,包含IRP_MN_START_DEVICE、IRP_MN_STOP_ DEVICE、IRP_MN_EJECT和IRP_MN_SURPRISE_REMOVAL。当中,IRP_MN_START_DEVICE消息是当驱动争取载入并执行时。操作系统的即插即用管理器会将这个IRP发往设备驱动。因此,当获得这个IRP后,USB驱动须要获得USB设备类别描写叙述符,如设备描写叙述符、配置描写叙述符、接口描写叙述符、端点描写叙述符等。并通过这些描写叙述符,从中获取实用信息。记录在设备扩展中。

    IRP_MN_STOP_DEVICE是设备关闭前,即插即用管理器发的IRP。USB驱动获得这个IRP时,应该尽快结束当前执行的IRP。并将其逐个取消掉。

    另外,在设备扩展中还应该有表示当前状态的变量,当IRP_MN_STOP_DEVICE来暂时,将当前状态记录成停止状态。

    IRP_MN_EJECT是设备被正常弹出,而IRP_MN_SURPRISE_REMOVAL则是设备非自然弹出,有可能意外掉电或者强行拔出等。在这样的IRP到来的时候,应该强迫全部未完毕的读写IRP结束并取消。

    而且将当前的设备状态设置成设备被拔掉。

    17.3.6  USB设备的读写

    USB设备接口主要是为了传送数据,80%的传输是通过Bulk管道。在BulkUSB驱动中。Bulk管道的读取是在IRP_MJ_READ和IRP_MJ_WRITE的派遣函数中。这样在应用程序中就能够通过ReadFile和WriteFile等API对设备进行操作了。

    在IRP_MJ_READ和IRP_MJ_WRITE的派遣例程中设置了完毕例程,如图17-22所看到的。其原理是将读写的大小分成单位为BULKUSB_MAX_TRANSFER_SIZE的若干块,依次将请求发往底层USB总线驱动。

    第一个块是派遣例程先设置BULKUSB_MAX_TRANSFER_SIZE大小的读写,并设置完毕例程,然后将请求发往USB总线驱动。

    当USB总线驱动完毕BULKUSB_MAX_TRANSFER_SIZE大小的读写后,会调用读写的完毕例程。

    这时候在完毕例程中再次发起BULKUSB_MAX_TRANSFER_SIZE大小的读写,并将请求发往底层USB总线驱动。当USB总线驱动完毕后,又会进入完毕例程。之后发送第三个数据块,而且依此类推直到传送完毕。

     

    17-22  USB读写派遣例程与完毕例程

    下面是BulkUSB的读写派遣函数的部分代码:

     #001   NTSTATUS
    #002   BulkUsb_DispatchReadWrite(
    #003       IN PDEVICE_OBJECT DeviceObject,
    #004       IN PIRP           Irp
    #005       )
    #006   {
    #007       PMDL                    mdl;
    #008       PURB                    urb;
    #009       ULONG                   totalLength;
    #010       ULONG                   stageLength;
    #011       ULONG                  urbFlags;
    #012       BOOLEAN               read;
    #013       NTSTATUS               ntStatus;
    #014       ULONG_PTR             virtualAddress;
    #015       PFILE_OBJECT          fileObject;
    #016       PDEVICE_EXTENSION     deviceExtension;
    #017       PIO_STACK_LOCATION   irpStack;
    #018       PIO_STACK_LOCATION   nextStack;
    #019       PBULKUSB_RW_CONTEXT   rwContext;
    #020       PUSBD_PIPE_INFORMATION pipeInformation;
    #021  
    #022    //
    初始化变量
    #023    urb = NULL;
    #024       mdl = NULL;
    #025       rwContext = NULL;
    #026       totalLength = 0;
    #027       irpStack = IoGetCurrentIrpStackLocation(Irp);
    #028       fileObject = irpStack->FileObject;
    #029       read = (irpStack->MajorFunction == IRP_MJ_READ) ? TRUE : FALSE;
    #030       deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    #031    //....

    #032  
    #033    //
    设置完毕例程的參数
    #034       rwContext = (PBULKUSB_RW_CONTEXT) ExAllocatePool(NonPagedPool,
    #035                                  sizeof(BULKUSB_RW_CONTEXT));
    #036    //...
     
    #037       if(Irp->MdlAddress) {
    #038           totalLength = MmGetMdlByteCount(Irp->MdlAddress);
    #039       }
    #040    //
    设置URB标志
    #041       urbFlags = USBD_SHORT_TRANSFER_OK;
    #042       virtualAddress = (ULONG_PTR) MmGetMdlVirtualAddress(Irp->MdlAddress);
    #043  
    #044    //
    推断是读还是写
    #045       if(read) {
    #046           urbFlags |= USBD_TRANSFER_DIRECTION_IN;
    #047       }
    #048       else {
    #049           urbFlags |= USBD_TRANSFER_DIRECTION_OUT;
    #050       }
    #051  
    #052    //
    设置本次读写的大小
    #053       if(totalLength > BULKUSB_MAX_TRANSFER_SIZE) {
    #054           stageLength = BULKUSB_MAX_TRANSFER_SIZE;
    #055       }
    #056       else {
    #057           stageLength = totalLength;
    #058       }
    #059  
    #060    //
    建立MDL
    #061       mdl = IoAllocateMdl((PVOID) virtualAddress,
    #062                           totalLength,
    #063                           FALSE,
    #064                           FALSE,
    #065                           NULL);
    #066    //
    将新MDL进行映射
    #067       IoBuildPartialMdl(Irp->MdlAddress,
    #068                         mdl,
    #069                         (PVOID) virtualAddress,
    #070                         stageLength);
    #071  
    #072    //
    申请URB数据结构
    #073       urb = ExAllocatePool(NonPagedPool,sizeof
    (struct _URB_BULK_OR_INTERRUPT_ TRANSFER));
    #074  
    #075    //
    建立Bulk管道的URB
    #076       UsbBuildInterruptOrBulkTransferRequest(
    #077                               urb,
    #078                               sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
    #079                               pipeInformation->PipeHandle,
    #080                               NULL,
    #081                               mdl,
    #082                               stageLength,
    #083                               urbFlags,
    #084                               NULL);
    #085  
    #086    //
    设置完毕例程參数  
    #087       rwContext->Urb             = urb;
    #088       rwContext->Mdl             = mdl;
    #089       rwContext->Length          = totalLength - stageLength;
    #090       rwContext->Numxfer         = 0;
    #091       rwContext->VirtualAddress  = virtualAddress + stageLength;
    #092       rwContext->DeviceExtension = deviceExtension;
    #093    //
    设置设备堆栈
    #094       nextStack = IoGetNextIrpStackLocation(Irp);
    #095       nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    #096       nextStack->Parameters.Others.Argument1 = (PVOID) urb;
    #097       nextStack->Parameters.DeviceIoControl.IoControlCode =
    #098                                                IOCTL_INTERNAL_USB_SUBMIT_URB;
    #099    //
    设置完毕例程
    #100       IoSetCompletionRoutine(Irp,
    #101                              (PIO_COMPLETION_ROUTINE)BulkUsb_ReadWriteCompletion,
    #102                              rwContext,
    #103                              TRUE,
    #104                              TRUE,
    #105                              TRUE);
    #106    //
    将当前IRP堵塞
    #107       IoMarkIrpPending(Irp);
    #108    //
    IRP转发究竟层USB总线驱动
    #109       ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,
    #110                               Irp);
    #111    //...
    略去对不成功时的处理
    #112       return STATUS_PENDING;
    #113   }

    此段代码能够在配套光盘中本章的sys文件夹下找到。

    17.4  小结

    本章介绍了USB总线协议的基本框架。当中包含USB总线的拓扑结构,USB通信的流程。还有USB的四种传输模式。

    笔者用一些工具软件带领读者分析了各种USB令牌、设备描写叙述符等。

    USB驱动程序的主要功能就是设置这些USB令牌,和获取USB设备描写叙述符。

    USB驱动程序将这些请求终于转化为USB请求包(URB包)。然后发往USB总线驱动程序。USB总线驱动提供了丰富的功能,它封装了USB协议。提供了标准的接口。这使得USB驱动程序的编写变得简单。程序猿不必过多地了解USB总线协议,就能够编写出功能强大的USB驱动程序。

  • 相关阅读:
    【JavaSE】成员方法快速入门和方法的调用机制原理
    HarmonyOS实战—实现注册登录和修改密码页面
    苹果CMS自动定时采集教程
    HarmonyOS实战—统计按钮点击次数
    HarmonyOS实战—点击更换随机图片
    C语言 main 函数
    C语言 vprintf 函数和 printf 函数区别
    C语言 vprintf 函数
    C语言 va_start / va_end / va_arg 自定义 printf 函数
    C语言 va_arg 宏
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7213999.html
Copyright © 2020-2023  润新知