在前面的文章中,我们已经给出了USB协议的链接地址,从这篇文章起,我们会涉及到许多USB 1.1的内容,我们的指导思想是先从熟悉USB 1.1协议入手,先使用现成的HCD和USBD,直接面对客户端驱动编程,尽快看到成果,使读者对USB的开发充满信心,进而去研究USBD和HCD的编程方法。请读者自行阅读协议,文章中有关协议的详细情况,由于会涉及非常多的文字,恕不能过多解释。
  1、USB系统主机端的软件结构
   一般来说,教科书或者协议上都会把USB主机端的软件说成有三层,第一层叫主机控制器驱动程序HCD(Host Controller Driver),第二层叫USB驱动程序USBD(USB Driver),第三层叫客户端驱动程序(Client Driver);实际上,我们实际看到的东西,往往HCD和USBD是由一个程序完成的,比如windows就提供了HCD和USBD,如果你自己开发了一个USB设备,只需要在HCD和USBD上面开发一个客户端驱动程序即可;linux也是同样,linux内核已经提供了HCD和USBD;所以在windows和linux下我们基本上没有开发HCD和USBD的必要,而且linux还提供源代码;但DOS就不一样了,DOS本身对USB没有任何支持,所以要想在DOS下彻底玩转USB,需要研究HCD、USBD和客户端驱动程序。
  2、DOSUSB介绍
  很显然,HCD和USBD更加底层一些,需要理解的东西也更多一些;如果我们能够绕过HCD和USBD,直接从客户端驱动程序入手,将会容易许多。幸运的是我们可以找到一个免费的DOS下的USB驱动程序,叫DOSUSB,该驱动程序实现了大部分的HCD和USBD的功能,使我们进行USB编程的好帮手。
  DOSUSB目前还没有实现EHIC的驱动,也就是说还不支持USB2.0,这也是我们从USB 1.1开始的原因之一,另一方面,由于USB2.0是兼容USB1.1的,所以,即便你在USB2.0的设备下,仍然可以使用USB1.1的驱动程序,只不过不能实现480MB/秒的传送速度而已。
    下面我们介绍一下DOSUSB。DOSUSB的官方网站如下:   http://www.usbdos.net

可以从其官方网站上下载DOGUSB的最新版本,当前版本是1.1.1。或者在下面网址下载这个版本的DOSUSB。   http://blog.hengch.com/software/dosusb/dosusb.zip

DOSUSB可以在非商业领域免费使用,如果肯花费费用,可以购买到源代码,从其官方网站的论坛上看到,在2006年9月作者开出的源代码的价格是1000欧元。

DOSUSB的安装十分简便,只需要解压缩到某一个目录下即可,比如放在c:\dosusb目录下,请自行阅读DOSUSB自带的文档,使用也非常简单,在DOS提示符下键入dosusb执行即可。

c:\dosusb>dosusb

缺省情况下,DOSUSB使用int 65h作为其驱动的调用软中断,如果和你的系统有冲突,在运行dosusb时可以加参数/I,请自行阅读DOSUSB的文档。

DOSUSB通过一个叫做URB(USB Request Block)的数据结构与客户端驱动程序进行通讯,这一点和linux非常相似,估计作者参考了linux下的源代码,在DOSUSB文档里给出了这个结构的定义,如下:

    struct {
      BYTE  transaction_type;   // 设置事务(控制传输)(2Dh)、输入事务(69h)、输出事务(E1h)
      BYTE  chain_end_flag;     // 备用
      BYTE  dev_add;            // 设备地址
      BYTE  end_point;          // 端点号
      BYTE  error_code;         // 错误吗
      BYTE  status;             // 设备状态
      WORD  transaction_flags;  // 备用
      WORD  buffer_off;         // 接收/发送缓冲区偏移地址
      WORD  buffer_seg;         // 接收/发送缓冲区段地址
      WORD  buffer_length;      // 接收/发送缓冲区长度
      WORD  actual_length;      // 接收/发送时每个包的最大长度
      WORD  setup_buffer_off;   // setup_request结构的偏移地址
      WORD  setup_buffer_seg;   // setup_request结构的段地址
      WORD  start_frame;        // 备用
      WORD  nr_of_packets;      // >0时会启动实时传输
      WORD  int_interval;       // 备用
      WORD  error_count;        // 重试的次数
      WORD  timeout;            // 备用
      WORD  next_urb_off;       // 备用
      WORD  next_urb_seg;       // 备用
    } urb                       // 32字节

  之所以列出这个结构,是因为我们将使用这个结构与USBD进行交互。关于结构中字段的定义,在DOSUSB的文档中有详细的说明。除备用字段不需要填以外,error_code和status由DOSUSB返回,故填0即可,后面还会介绍更详细的填写方法。

  在DOSUSB的发行包中,有一个sample目录,里面有很多例子,但大多是使用power basic写的,不过仍然有很好的参考价值。

  3、USB 1.1协议中的一些内容

  USB协议为USB设备定义了一套描述设备功能和属性的固有结构的描述符,包括标准描述符(设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符),还有非标准描述符,如类描述符等。按照协议,设备描述符下可以有若干个配置描述符,每个配置描述符可以有若干个接口描述符,每个接口描述符下又可以有若干个端点描述符,字符串描述符主要用于描述一些文字信息,比如厂家名称,产品名称等。这篇文章的目的就是要读出这些描述符。

  实际上,所谓描述符就是一个数据结构,不管是什么描述符,其前两个字节的含义都是一样的,第一个字节是描述符的长度,第二个字节是描述符的类型。

  • 设备描述符(Device Descriptor):
   struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 设备描述符类型,0x01
      WORD    bcdUSB;             // 设备支持的USB协议版本,BCD码
      BYTE    bDeviceClass;       // 设备类代码(由USB-IF分配)
      BYTE    bDeviceSubClass;    // 子类代码
      BYTE    bDeviceProtocol;    // 协议码
      BYTE    bMaxPacketSize0;    // 端点0的最大包长度(仅为8,16,32,64)
      WORD    idVendor;           // 厂商ID(由USB-IF分配)
      WORD    idProduct;          // 产品ID(由制造商定义)
      WORD    bcdDevice;          // 设备发行号(BCD码)
      BYTE    iManufacture;       // 描述厂商信息的字符串描述符的索引值
      BYTE    iProduct;           // 描述产品信息的字符串描述符的索引值
      BYTE    iSerialNumber;      // 描述设备序列号信息的字符串描述符的索引值
      BYTE    bNumConfigurations; // 可能的配置描述符的数目
    } device_descriptor
  • 配置描述符(Configuration Descriptor)
  struct {
      BYTE    bLength;             // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;     // 配置描述符类型,0x02
      WORD    wTotalLength;        // 配置信息的总长
      BYTE    bNumInterfaces;      // 该配置所支持的接口数目
      BYTE    bConfigurationValue; // 被SetCongiguration()请求用做参数来选定该配置
      BYTE    bConfiguration;      // 描述该配置的字符串描述符的索引值
      BYTE    bmAttributes;        // 配置特性
      BYTE    MaxPower;            // 该配置下的总线电源耗费量,以2mA为一个单位
    }configuration_descriptor;

  bmAttributes :b7:备用,b6:自供电,b5:远程唤醒,b4--b0:备用

  另外,在读取配置描述符时可以把该配置下的所有描述符全部读出,这些描述符的总长度就是wTotalLength字段的值,读出所有描述符后,在一个一个地拆分。

  • 接口描述符(Interface Descriptor):
    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 接口描述符类型,0x04
      BYTE    bInterfaceNumber;   // 接口号,从0开始
      BYTE    bAlternateSetting;  // 可选设置的索引值.
      BYTE    bNumEndpoints;      // 此接口的端点数量。
      BYTE    bInterfaceClass;    // 接口类编码(由USB-IF分配)
      BYTE    bInterfaceSubClass; // 接口子类编码(由USB-IF分配)
      BYTE    bInterfaceProtocol; // 协议码(由USB-IF分配)
      BYTE    iInterface;         // 描述该接口的字符串描述符的索引值
    }interface_descriptor;

bInterfaceClass:USB协议根据功能将不同的接口划分成不同的类,如下:

1:音频类,2:CDC控制类,3:人机接口类(HID),5:物理类,6:图像类,7:打印机类,8:大数据存储类,9:集线器类,10:CDC数据类,11:智能卡类,13:安全类,220:诊断设备类,224:无线控制类,254:特定应用类,255厂商定义的设备。

  • 端点描述符(Endpoint Descriptor):
    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 端点描述符类型,0x05
      BYTE    bEndpointAddress;   // 端点地址
      BYTE    bmAttributes;       // 在bconfigurationValue所指的配置下的端点特性.
      WORD    wMaxPacketSize;     // 接收/发送的最大数据报长度.
      BYTE    bInterval;          // 周期数据传输端点的时间间隙.
    }endpoint_descriptor;

  bmAttributes:bit 1:0--传送类型,00=控制传输,01=实时传输,10=批量传输,11=中断传输;所有其他位均保留。

  • 字符串描述符(String Descriptor):
    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 字符串描述符类型,0x03
      char    bString[];          // UNICODE编码的字符串
    }string_descriptor;
  • USB命令请求(USB DEVICE REQUEST)

  为了更好地协调USB主机与设备之间的数据通信,USB规范定义了一套命令请求,用于完成主机对总线上所有USB设备的统一控制,USB命令请求由统一的格式,其结构如下:

    struct {
      BYTE  bmRequestType;  // 请求类型
      BYTE  bRequest;       // 命令请求的编码
      WORD  wValue;         // 命令不同,含义不同
      WORD  wIndex;         // 命令不同,含义不同
      WORD  wLength;        // 如果有数据阶段,此字段为数据字节数
    } setup_request;

  后面我们向设备发出指令就全靠这个结构了。作为我们本文要用到的读取USB设备描述符的命令请求,该结构各字段的填法如下。

  bmRequestType : b7--数据传输方向,0=主机到设备,1=设备到主机;b6:5--命令的类型,0=标准命令,1=类命令,2=厂商提供的命令,3=保留;b4:0--接收对象,0=设备,1=接口,2=端点,3=其他。我们发出的得到描述符的命令,数据传输方向为设备到主机,标准命令,接收对象为设备,所以该字段填0x80。

bRequest : 标准命令的编码如下,我们的命令是GET_DESCRIPTOR,所以应该填6。

GET_STATUS=;
CLEAR_FEATURE=;
SET_FEATURE=;
SET_ADDRESS=;
GET_DESCRIPTOR=;
SET_DESCRIPTOR=;
GET_CONFIGURATION=;
SET_CONFIGURATION=;
GET_INTERFACE=;
SET_INTERFACE=;
SYNCH_FRAME=

  wValue : 高字节表示描述符的类型,0x01=设备描述符,0x02=配置描述符,0x03=字符串描述符,0x04=接口描述符,0x05=端点描述符,0x29=集线器类描述符,0x21=人机接口类描述符,0xFF=厂商定义的描述符。低字节表示表示描述符的索引值。所以,当读取设备描述符时,该字段值为0x100,当读取配置描述符是,应为0x03yy,其中yy为描述符的索引值。

  wIndex : 当读取字符串描述符时,填0x0409,表示希望获得英文字符串,其他情况下填0。

  wLength : 数据长度,一般应该填写,描述符的第一个字节,bLength。由于我们在读描述符时,并不知道其实际长度,通常的做法是先读取8个字节,然后根据第一个字节bLength的值在重新读取一次完整的描述符;注意,当读取配置描述符的钱8个字节后,应该使用wTotalLength字段的值作为长度读取与该配置有关的所有描述符。

  4、读取设备描述符的范例程序

  按照我们文章的习惯,几乎每篇文章都有一个范例程序,本文也不例外,本文的范例程序请在下面地址下载:    http://blog.hengch.com/source/usbview.zip

  程序用C++写成,在DJGPP下编译通过,所以是32位保护模式下的代码,要注意的是,DOSUSB是实模式下的驱动,所以在申请内存块时要申请1M以内实模式可以读取的内存,否则,在使用int 65h调用DOSUSB时一定会出现问题。

  有4个头文件,public.h中定义了一些方便使用的数据类型,比如BYTE为char,WORD为short int等等,可以不必太关注;x86int.h中定义了调用DOS中断所需的函数和数据结构,直到怎么使用就可以了;dosmem.h中定义了一个DOS_MEM类,主要是为了在保护模式下申请和使用DOS内存块更为方便,也是知道其中的方法,能够明白程序中的意义就可以了;usb.h定义了与USB协议有关的所有常数,这些常数与前面介绍的各种数据结构一一对应,由于我们是在保护模式下使用DOS内存,所以把一个内存块映射到一个数据结构上有一些麻烦,读取各个字段主要靠在usb.h中定义的这些常数。

  主要程序在usbview.cc中,主要思路如下:

    • USB设备的地址从1--127,所以我们从1--127做一个循环,逐一读取USB设备描述符,直到出现“非法地址”为止。
    • 每一个USB设备的设备描述符只有一个,所以我们从读取某个地址下的设备描述符开始。
    • 开始我们并不知道设备描述符的长度,即便我们知道其长度为18个字符,但我们仍然不知道端点0允许的包长度(设备描述符中bMaxPacketSize0字段),但我们知道包长度的最小值是8,所以我们先读取8个字节的设备描述符。
    • 在我们得到8个字符的设备描述符后,我们就可以得到该描述符的长度和端点0的包长度,在后面发出的所有命令中,始终要把这个值填在URB结构的actual_length字段中。
    • buffer用于存放USBD返回的描述符,在使用前建议初始化一下,全部清0。
    • 要向设备发出命令请求(Request),需要先填setup_request结构,前面讲过,bmRequestType=0x80,bRequest=6,这两个字段的填法始终不变;我们现在读取设备描述符,所以wValue=0x100,wIndex=0,wLength在首次调用时填8,第二次调用时填返回的bLength字段(应该是18)。
    • 准备好buffer和setup_request后,我们要填URB一边与DOSUSB交互;读取描述符是一个控制传输,所以transaction=0x2D(后面一直是0x2D);dev_add填上面提到的循环变量;end_point=0,因为我们总对端点0(见USB协议);buffer_off和buffer_seg分别填buffer的便宜地址和段地址;setup_buffer_off和setup_buffer_seg分别填前面setup_request结构的偏移地址和段地址;buffer_length同setup_request结构中的wLength,也可以把值设在wLength和buferr的最大长度之间,如果buffer的最大长度小于wLength,我们只能填buffer的最大长度,但这种情况下我们将得不到完整的描述符;actual_length在第一次调用时填8,以后一直填返回的bMaxPacketsize0字段;其他字段均为0。
    • 让DS:DX指向刚刚填完的URB结构,调用软中断65h,DOSUSB将为你处理下面的事情。
    • 如果正常,error_code应该返回0,如果非0,含义如下:
      1--非法的设备地址;2--内部错误;3--非法的transation_type字段;4--非法的buffer长度。
    • 如果正常,status字段应该为0,该字段是是USB控制器返回的状态字节,不同的控制器(OHCI或UHCI)会返回不同的值。
    • 当我们得到设备描述符后,如果设备描述符中的iManufacturer字段不为0,我们可以根据这个所引值得到相应的字符串描述符,从而显示出厂家信息,要注意的是字符串描述符是UNICODE编码,对于ASCII而言,它是一个ASSCII码跟一个ASCII 0组成;同理我们可以得到产品信息和序列号信息。
    • 当我们得到了设备的设备描述符后,我们就可以知道这个设备上有多少个配置(设备描述符中的bNumConfigurations),进而通过一个循环得到所有的配置描述符及其配置下的所有描述符。
    • 读取配置描述符的方法与读取设备描述符大同小异,也是先读取8个字节,然后根据返回的内容再读取所有的描述符内容,要注意的是,实际上,我们不能单独得到接口描述符和端点描述符,唯一的办法是把一个配置下的描述符全部读出来,所以setup_request结构中的wLength字段一定要与配置描述符中返回的wTotalLength值一致才行。
    • 剩下的事情就是如何显示我们得到的描述符,我想这对每一位读者而言都不是什么困难的事。

USB系列之二:读取USB设备的描述符的更多相关文章

  1. USB HID设备报告描述符详解(转)

    转自:http://group.ednchina.com/93/198.aspx. 参考:USB HID usage table 概述:   报告在这里意思是数据传输(data transfer),而 ...

  2. USB学习小记-HID类键盘的报告描述符的理解

    前言 断断续续的学习了将近三个月,才把USB的HID类搞明白,速度真是够慢的.利用晚上+周末的时间学习自己的东西确实是必要的,不过效率是有点低,以后要更专注一些才行,希望自己能做到吧. 在学习过程中, ...

  3. JNI学习积累之二 ---- 数据类型映射、域描述符说明

    本文原创,转载请注明出处:http://blog.csdn.NET/qinjuning 在Java存在两种数据类型: 基本类型 和 引用类型 ,大家都懂的 . 在JNI的世界里也存在类似的数据类型,与 ...

  4. 深入理解javascript对象系列第三篇——神秘的属性描述符

    × 目录 [1]类型 [2]方法 [3]详述[4]状态 前面的话 对于操作系统中的文件,我们可以驾轻就熟将其设置为只读.隐藏.系统文件或普通文件.于对象来说,属性描述符提供类似的功能,用来描述对象的值 ...

  5. python2.7高级编程 笔记二(Python中的描述符)

    Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些" ...

  6. USB系列之六:基于DOSUSB的简单U盘驱动程序

    首先要说明的是,该驱动程序仅实现了部分块设备的功能,如果作为成品软件使用,会感觉性能比较差,而且有些功能(比如FORMAT)是不能完成的,发表此驱动程序的目的旨在说明USB的编程原理以及DOS下驱动程 ...

  7. USB系列之五:用汇编实现的一些USB功能

    前面的USB系列一至四,实现了我们需要的一些USB功能,但都是用C语言的32位代码,之后我们插进了三篇关于DOS下设备驱动程序的文章,我们现在应该清楚,当我们要在DOS下写一个U盘的驱动时,最好使用汇 ...

  8. USB系列之三:从你的U盘里读出更多的内容

    U盘是我们最常使用的一种USB设备,本文继续使用DOSUSB做驱动,试图以读取扇区的方式读取你的U盘.本文可能涉及的协议可能会比较多. 一.了解你的U盘    首先我们用上一篇文章介绍的程序usbvi ...

  9. USB学习笔记连载(十二):USB描述符

    USB设备是端口,接口,配置的集合,USB协议是以各种USB描述符来表征USB设备的功能.计算机通过这些描述符来获得USB设备的功能. USB描述符包括: USB标准设备描述符,USB集线器描述符.H ...

随机推荐

  1. POJ 3675 Telescope

    题意:给定一个不自交的多边形,要求和圆心在原点的圆的面积交. 思路:同POJ2986,是加强版 代码: #include<algorithm> #include<cstdio> ...

  2. 用continue语句的时候,要千万小心内存泄漏,当然还有return和break也是

    疑惑了大半年的内存泄漏,居然是因为这个原因- 有空学学QT的指针使用,可以使得代码更简洁.更不容易内存泄漏-

  3. 分布式文件系统glusterfs安装步骤

    我的系统是 RHEL5 , 可能环境不一样, 需要安装的第三方依赖不一样啊, 反正大家在安装的过程中缺少什么就去安装什么, 一般都会有提示的. 下载   glusterfs-3.2.0.tar.gz  ...

  4. mysql命令行的基本用法

    基础介绍:1.在linux下使用下列命令,请确认mysql的bin目录是否已经加入到PATH路径中,或者是已经进入到mysql安装路径下的bin目录查看PATHshell> echo $PATH ...

  5. UML--核心视图之用例图

    如果说UML是一门语言,那么元素就是UML的基本词汇,视图就是语法. UML通过视图将基本元素组织在一起,形成有意义的句子. 静态视图,顾名思义,就是表达静态事物的.包括用例图.类图和包图. 用例图 ...

  6. bzoj1752 [Usaco2005 qua]Til the Cows Come Home

    Description Bessie is out in the field and wants to get back to the barn to get as much sleep as pos ...

  7. WifiDog系统

    WifiDog:A captive portal suite What is it composed of ? A: It is composed of 2 components: The clien ...

  8. [转载]Linux编程 sockaddr_in 和sockaddr和in_addr详解

    sockaddr sockaddr 是通用的socket地址,具体到Internet socket,用下面的结构,二者可以进行类型转换 sa_family是地址家族,一般都是"AF_xxx& ...

  9. PHP初识

    1. php是一种跨平台的语言,支持几乎全部的数据库. 以前觉得PHP与MYSQL是黄金组合,对于PHP能否支持MSSQL没有过了解,PHP支持几乎全部的数据库,也支持MSSQL(5.2.X版本可以用 ...

  10. WPF动画

    System.Windows.Media.Animation 这个命名空间中,包含了三种动画类:线性插值动画类(17个).关键帧动画(22个).路径动画(3个). 线性插值动画类(17个)如下: By ...