USB系列之二:读取USB设备的描述符

  在前面的文章中,我们已经给出了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_STATUS=0;CLEAR_FEATURE=1;SET_FEATURE=3;SET_ADDRESS=5;GET_DESCRIPTOR=6;SET_DESCRIPTOR=7;GET_CONFIGURATION=8;SET_CONFIGURATION=9;GET_INTERFACE=10;SET_INTERFACE=11;SYNCH_FRAME=12。我们的命令是GET_DESCRIPTOR,所以应该填6。

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值一致才行。
    • 剩下的事情就是如何显示我们得到的描述符,我想这对每一位读者而言都不是什么困难的事。
时间: 2024-10-19 15:03:01

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

[转载]JNI学习积累之二 ---- 数据类型映射、域描述符说明

本文转载于:http://blog.csdn.net/qinjuning 在Java存在两种数据类型: 基本类型 和 引用类型 ,大家都懂的 . 在JNI的世界里也存在类似的数据类型,与Java比较起来,其范围更具严格性,如下: 1.primitive types ----基本数据类型,如:int. float .char等基本类型 2.reference types----引用类型,如:类.实例.数组. 特别需要注意:数组 ------ 不管是对象数组还是基本类型数组,都作为reference

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

× 目录 [1]类型 [2]方法 [3]详述[4]状态 前面的话 对于操作系统中的文件,我们可以驾轻就熟将其设置为只读.隐藏.系统文件或普通文件.于对象来说,属性描述符提供类似的功能,用来描述对象的值.是否可配置.是否可修改以及是否可枚举.本文就来介绍对象中神秘的属性描述符 描述符类型 对象属性描述符的类型分为两种: 数据属性和访问器属性 数据属性 数据属性(data property)包含一个数据值的位置,在这个位置可以读取和写入值.数据属性有4个特性 [1]Configurable(可配置性

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

Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些"中级"的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因如下: 有关描述符的官方文档相当难懂,而且没有包含优秀的示例告诉你为什么需要编写描述符(我得为Raymond Hettinger辩护一

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

U盘是我们最常使用的一种USB设备,本文继续使用DOSUSB做驱动,试图以读取扇区的方式读取你的U盘.    本文可能涉及的协议可能会比较多.一.了解你的U盘    首先我们用上一篇文章介绍的程序usbview.exe去看一下你的U盘,我在本文中用于测试的U盘情况如下: Device Descriptor: (设备描述符) USB Address: 1 Length: 18 Descriptor Type: 1 USB Specification nr.: 0x0110 Calss Code:

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

首先要说明的是,该驱动程序仅实现了部分块设备的功能,如果作为成品软件使用,会感觉性能比较差,而且有些功能(比如FORMAT)是不能完成的,发表此驱动程序的目的旨在说明USB的编程原理以及DOS下驱动程序的工作原理:同时要说明的是,此驱动程序仅支持32M(包括32M)以下的U盘,当然这个问题解决起来并不困难,有兴趣的读者可以在阅读本文并理解的基础上加以改进使其支持32M以上2G以下的U盘.    前面的博文中提到由于DOSUSB是在命令行加载的,如果从config.sys中加载这个基于DOSUSB

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

前面的USB系列一至四,实现了我们需要的一些USB功能,但都是用C语言的32位代码,之后我们插进了三篇关于DOS下设备驱动程序的文章,我们现在应该清楚,当我们要在DOS下写一个U盘的驱动时,最好使用汇编语言,而且不得不在实模式下编程. 基于这样一个原因,本文计划把<USB系列二>到<USB系列四>中的三段程序代码,用汇编语言再重新实现一遍,而且使用16位的8086模式编程,在下载下面的源代码之前,希望读者能够认真阅读USB系列以前所有的文章,最好能把其中的代码都看明白并亲自试一试,

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

USB设备是端口,接口,配置的集合,USB协议是以各种USB描述符来表征USB设备的功能.计算机通过这些描述符来获得USB设备的功能. USB描述符包括: USB标准设备描述符,USB集线器描述符.HID描述符,这里主要讲解USB描述符. USB标准设备描述符包括:设备描述符,端点描述符,接口描述符,配置描述符,设备限定描述符,其他描述符.这些所有的描述符都在 官方固件中的 dscr.a51 文件中,注意!!!!!   1.设备描述符 一共18个字节,14个字段.在官方给的固件程序中的描述如下图

usb命令格式、描述符详解

一.USB命令 在USB规范里,对命令一词提供的单词为“Request”,但这里为了更好的理解主机与设备之间的主从关系,将它定义成“命令”. 所有的USB设备都要求对主机发给自己的控制命令作出响应,USB规范定义了11个标准命令,它们分别是:Clear_Feature. Get_Configuration.Get_Descriptor.Get_Interface.Get_Status.Set_Address. Set_Configuration.Set_Descriptor.Set_Interf

usb协议分析-设备描述符配置包-描述符

/* usb协议分析仅供大家参考---设备描述符配置包,设备描述符, 地址设置, 配置描述符, 字符串描述符 */ /* -1- usb设备描述符配置包 */ typedef struct _USB_SETUP_PACKET { REQUEST_TYPE bmRequestType; BYTE bRequest; WORD_BYTE wValue; WORD_BYTE wIndex; WORD wLength; } USB_SETUP_PACKET; 1.bmRequestType 是包含有下面