从零开始学USB(十七、USB的枚举)

一、什么是枚举?

USB枚举,USB Emulation,从字面意思看,就是去列举USB,而列举啥呢,其实就是USB的初始化。
简单来说,USB的枚举,对应的就是USB的Host和Device之间的对话,即Host根据Device所报告上来的参数,得知USB的device是啥类型的,具有啥功能,然后初始化相关参数,这样主机就可以根据这些信息来加载合适的驱动程序。只要枚举成功了,那么就已经成功大半了。接下来,就USB Device就可以正常工作了。
所以,可以简单的理解为,USB枚举,就是USB设备的初始化(init)。

二、枚举的步骤

下面列出USB2.0协议中,给出的枚举步骤。

  1. The hub to which the USB device is now attached informs the host of the event via a reply on its status change pipe (refer to Section 11.12.3 for more information). At this point, the USB device is in the Powered state and the port to which it is attached is disabled.
  2. The host determines the exact nature of the change by querying the hub.
  3. Now that the host knows the port to which the new device has been attached, the host then waits for at least 100 ms to allow completion of an insertion process and for power at the device to become stable.The host then issues a port enable and reset command to that port. Refer to Section 7.1.7.5 for sequence of events and timings of connection through device reset.
  4. The hub performs the required reset processing for that port (see Section 11.5.1.5). When the reset signal is released, the port has been enabled. The USB device is now in the Default state and can draw no more than 100 mA from VBUS. All of its registers and state have been reset and it answers to the default address.
  5. The host assigns a unique address to the USB device, moving the device to the Address state.
  6. Before the USB device receives a unique address, its Default Control Pipe is still accessible via the default address. The host reads the device descriptor to determine what actual maximum data payload size this USB device’s default pipe can use.
  7. The host reads the configuration information from the device by reading each configuration zero to n-1, where n is the number of configurations. This process may take several milliseconds to complete.
  8. Based on the configuration information and how the USB device will be used, the host assigns a configuration value to the device. The device is now in the Configured state and all of the endpoints in this configuration have taken on their described characteristics. The USB device may now draw the amount of VBUS power described in its descriptor for the selected configuration. From the device’s point of view, it is now ready for use.

When the USB device is removed, the hub again sends a notification to the host. Detaching a device disables the port to which it had been attached. Upon receiving the detach notification, the host will update its local topological information.

上面USB2.0协议中给的步骤太官方化了,下面我根据网友的总结和自己的理解,用白话来描述一下。

1.设备上电

用户把USB设备插入USB端口(主机下的根hub或主机下行端口上的hub端口)或给系统启动时设备上电。此时,USB设备处于加电状态,它所连接的端口是无效的。

2.检测电压变化,报告主机

USB设备上电后,一直监测USB设备接口电平变化HUB检测到有电压变化,将利用自己的中断端点将信息反馈给主控制器有设备连接。

这一步是HUB给主机报告有设备连接。(如下图,空闲状态,接上设备HUB总线电平会变化)

3.主机了解连接设备

每个hub利用它自己的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不必关心),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生,那么host会发送一个 Get_Port_Status请求(request)给hub以了解此次状态改变的确切含义。Get_Port_Status等请求属于所有hub都要求支持的hub类标准请求(standard hub-class requests)。

4.Hub检测所插入的设备是高速还是低速

 hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。USB 2.0规范要求速度检测要先于复位(Reset)操作。

根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口(全速、高速设备的区分在前面的文章中有描述)

5.hub复位设备

主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms。当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。(如下图,hub的每个接口是单独检测的)
 

6. Host检测所连接的全速设备是否是支持高速模式

 因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一直以全速工作。

7.Hub建立设备和主机之间的信息通道
主机不停地向hub发送Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。
当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)

8.主机发送Get_Descriptor请求获取默认管道的最大包长度

默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
设备描述符的第8字节代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。

9.主机给设备分配一个地址

主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。
10.主机获取设备的信息

主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。
之后主机发送Get_Descriptor请求,读取配置描述符(Configuration Descriptor),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。

11.主机给设备挂载驱动(复合设备除外)

主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。  然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match) 函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。   
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。

12.设备驱动选择一个配置
驱动程序根据前面设备回复的信息,发送Set_Configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。
对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。
 

USB协议定义了设备的6种状态,仅在枚举过程中,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态(Attached)挂起状态(Suspend),前面文章中有描述)。

三、举例

下面以一个鼠标的枚举过程为例,来学习枚举的过程。

注:这个鼠标是一个低速设备,从传输后面的LS就可以看出。

可以看到,鼠标的枚举总共经历了0~10共11个传输(标号11的传输为中断传输,已经不是枚举使用的控制传输了)

下面就根据这11个传输,单独分析,学习总结USB设备的枚举(这里是从上面总结的枚举的第八步开始的)。

3.1.获取设备描述符

3.1.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 00 01 00 00 12 00

注:这个阶段的设备地址(address)用的0,当然默认端点(endpoint)也是0。

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四自己传的是0x0100 ,查看描述符表,得知高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的1代表 设备,低字节在本设备没用到。

第四五字节为0x0,这个参数如果为0,则不关心;如果为非零,则表示Langurage ID,每一位都有对应的意义。

第六七字节为0x12,即代表返回的数据不应该多于18个字节。

从上一节的学习可以很容易得到,这是一个获取设备描述符的请求。

3.1.2 数据传输分析

设备给主机回复的数据为18个字节HEX:12 01 00 02 00 00 00 08 64 0D 18 C0 01 43 01 02 00 01 

接下来看一下设备给主机回复的设备描述符信息。


  
  
  1. /* USB_DT_DEVICE: Device descriptor */
  2. struct usb_device_descriptor {
  3. __u8 bLength = 0x12; //该描述符长度为18字节
  4. __u8 bDescriptorType = 0x1; //从16节的表9-5可知,1代表的描述符时设备描述符
  5. __le16 bcdUSB = 0x200; //该位表示版本号,使用BCD码表示,即USB2.0版本
  6. __u8 bDeviceClass = 0x0; //0代表由接口指出类信息
  7. __u8 bDeviceSubClass = 0x0; //子类,bDeviceClass 域为零,此域也须为零
  8. __u8 bDeviceProtocol = 0x0; //协议码,0代表没指定任何协议
  9. __u8 bMaxPacketSize0 = 0x8; //端点0,最大包支持8个字节(低速8个字节)
  10. __le16 idVendor = 0x046D; //厂商标志(学习可以不用关心)
  11. __le16 idProduct = 0xC010; //产品标志(学习可以不用关心)
  12. __le16 bcdDevice = 0x431; //设备发行号,用BCD码表示,4.31版本(学习可以不用关心)
  13. __u8 iManufacturer = 0x1; //描述厂商信息的字符串描述符的索引值
  14. __u8 iProduct = 0x2; //描述产品信息的字串描述符的索引值
  15. __u8 iSerialNumber = 0x0; //描述设备序列号信息的字串描述符的索引值
  16. __u8 bNumConfigurations = 0x1; //可能的配置描述符数目,这个鼠标设备就支持一个配置
  17. } __attribute__ ((packed));

3.1.3 状态阶段

状态阶段比较简单,是以一个固定的,数据数量为0的 事务结束这次控制传输。

本次控制传输如果有数据包,状态阶段的令牌包和最后一个数据包方向相反。

本次控制传输如果无数据包,则固定的IN令牌包,结束这次控制传输。

3.2 再次对设备复位

hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms(可以看到这里hub对设备的复位时间是10.113ms)。

3.3 设置地址

数据为HEX:00 05 03 00 00 00 00 00

注:这个阶段的设备地址(address)用的0,当然默认端点(endpoint)也是0。

第一个字节0x0,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是主机和设备发送(bit7 == 0)信息。

第二个字节0x05,从table 9-4可以看到,SET_ADDRESS,是主机给从机设置地址。

第三四个字节合起来是0x03,对照标准的设备请求表,可以知道,这是就是主机给设备分配的设备地址3。

第五六字节合起来是0x0,从标准的设备请求表可以看到,这里没任何作用,只是为了满足标准的SETUP令牌请求格式。

第七八字节同上。

本次传输(设置地址)无可选的数据阶段。

同时既然设备地址已经设置好,那么下一阶段,就应该用新的设置的设备地址了。

3.4 再次获取设备描述符

这次要注意,这里以及使用新分配的设备地址3了,而不是默认地址0。同时后面也都会使用新分配的地址来通信

当然这里因为是再次获取的设备描述符,所以它和3.1分析的数据是一样的。这里就不再次分析了,直接搬过来。

3.4.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 00 01 00 00 08 00

注:这个阶段的设备地址(address)用的0,当然默认端点(endpoint)也是0。

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0100 ,查看描述符表,得知高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的1代表 设备,低字节在本设备没用到。

第四五字节为0x0,这个参数如果为0,则不关心;如果为非零,则表示Langurage ID,每一位都有对应的意义。

第六七字节为0x12,即代表返回的数据不应该多于18个字节。

从上一节的学习可以很容易得到,这是一个获取设备描述符的请求。

3.4.2 数据传输分析

设备给主机回复的数据为18个字节HEX:12 01 00 02 00 00 00 08 64 0D 18 C0 01 43 01 02 00 01 

接下来看一下设备给主机回复的设备描述符信息。


  
  
  1. /* USB_DT_DEVICE: Device descriptor */
  2. struct usb_device_descriptor {
  3. __u8 bLength = 0x12; //该描述符长度为18字节
  4. __u8 bDescriptorType = 0x1; //从16节的表9-5可知,1代表的描述符是设备描述符
  5. __le16 bcdUSB = 0x200; //该位表示版本号,使用BCD码表示,即USB2.0版本
  6. __u8 bDeviceClass = 0x0; //0代表由接口指出类信息
  7. __u8 bDeviceSubClass = 0x0; //子类,bDeviceClass 域为零,此域也须为零
  8. __u8 bDeviceProtocol = 0x0; //协议码,0代表没指定任何协议
  9. __u8 bMaxPacketSize0 = 0x8; //端点0,最大包支持8个字节(低速8个字节)
  10. __le16 idVendor = 0x046D; //厂商标志(学习可以不用关心)
  11. __le16 idProduct = 0xC010; //产品标志(学习可以不用关心)
  12. __le16 bcdDevice = 0x4301; //设备发行号,用BCD码表示,43.01版本(学习可以不用关心)
  13. __u8 iManufacturer = 0x1; //描述厂商信息的字符串描述符的索引值
  14. __u8 iProduct = 0x2; //描述产品信息的字串描述符的索引值
  15. __u8 iSerialNumber = 0x0; //描述设备序列号信息的字串描述符的索引值
  16. __u8 bNumConfigurations = 0x1; //可能的配置描述符数目,这个鼠标设备就支持一个配置
  17. } __attribute__ ((packed));

3.5 获取配置描述符

3.5.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 00 02 00 00 09 00

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0100 ,查看描述符表,得知高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的2代表配置,低字节在本设备没用到。

第四五字节为0x0,这个参数如果为0,则不关心;如果为非零,则表示Langurage ID,每一位都有对应的意义。

第六七字节为0x9,即代表返回的数据不应该多于9个字节。

从上一节的学习可以很容易得到,这是一个获取配置描述符的请求。

3.5.2 数据传输分析 

设备给主机回复的数据为9个字节HEX:09 02 22 00 01 01 00 A0 32

接下来看一下设备给主机回复的配置描述符信息。


  
  
  1. struct usb_config_descriptor {
  2. __u8 bLength = 0x09; //此描述符长度为9
  3. __u8 bDescriptorType = 0x02; ///从16节的表9-5可知,2代表的配置描述符
  4. __le16 wTotalLength = 0x22; //此配置信息的总长为34字节(包括配置,接口,端点和设备类及厂商定义的描述符)
  5. __u8 bNumInterfaces = 0x01; //表示有一个接口描述符
  6. __u8 bConfigurationValue= 0x01; //在SetConfiguration()请求中用1作参数来选定此配置
  7. __u8 iConfiguration = 0x00; //描述此配置的字串描述表索引
  8. __u8 bmAttributes = 0xA0; //D7: 保留(设为一)D6: 自给电源 D5: 远程唤醒 D4..0:保留(设为一) 表示这是一个由总线供电,并支持远程唤醒功能(可以睡眠节约电)
  9. __u8 bMaxPower = 0x32; //在此配置下的总线电源耗费量。以 2mA 为一个单位 即2 * 50 = 100ms
  10. } __attribute__ ((packed));

3.5.3 状态阶段

所有的状态阶段都遵循3.1.3的要求,这里不再分析。

3.6 获取其它描述符

上一步在配置描述符中已经知道了配置描述符+接口描述符+端点描述符+设备类描述符+厂商定义的描述符的总字节长度为34个字节。

本次传输,就会一次性的全部获取过来。

一张图片放不下,分两次截图放下。

3.6.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 00 02 00 00 22 00

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0100 ,查看描述符表,得知高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的2代表配置,低字节在本设备没用到。

第四五字节为0x0,这个参数如果为0,则不关心;如果为非零,则表示Langurage ID,每一位都有对应的意义。

第六七字节为0x22,即代表返回的数据不应该多于34个字节(因为实际上次读取配置描述符已经知道多个描述符总和为34字节了)。

从上一节的学习可以很容易得到,这是一个获取配置描述符的请求。

3.6.2 数据传输分析 

设备给主机回复的数据为34个字节HEX:

09 02 22 00 01 01 00 A0 32 09 04 00 00 01 03 01 02 00 09 21 11 01 00 01 22 34 00 07 05 81 03 05 00 0A

接下来看一下设备给主机回复的配置描述符信息。

第一步肯定是先拆分数据了。

从我描述符学习这一章可以知道 (如下链接)

https://blog.csdn.net/qq_16777851/article/details/85222030

所有描述的第一个字节都是这个描述符的长度,那么我们只要根据第一个字节确定这描述符的个数,一次类推,就能查分成多个描述符。

  1. 09 02 22 00 01 01 00 A0 32
  2. 09 04 00 00 01 03 01 02 00
  3. 09 21 11 01 00 01 22 34 00
  4. 07 05 81 03 05 00 0A

拆分后的如上所示,每行的第一个字节都是本行所代表的描述符的字节数。

3.6.2.1 配置描述符 

09 02 22 00 01 01 00 A0 32

第一行的在3.5节已经分析过了,是配置描述符。

3.6.2.2 接口描述符

09 04 00 00 01 03 01 02 00

从第二个字节的04,通常查看16节的表9-5可以知道,这是一个接口描述符。


  
  
  1. struct usb_interface_descriptor {
  2. __u8 bLength = 0x09; //该描述符的字节数
  3. __u8 bDescriptorType = 0x04; //描述符类型,4代表接口描述符
  4. __u8 bInterfaceNumber = 0x0; //接口号,当前配置支持的接口数组索引(从零开始)
  5. __u8 bAlternateSetting = 0x0; //可选设置的索引值,这里无
  6. __u8 bNumEndpoints = 0x01; //端点描述符数量,1个
  7. __u8 bInterfaceClass = 0x03; //接口所属的类值,由USB说明保留, 3代表人机接口类(HID)
  8. __u8 bInterfaceSubClass = 0x01; //子类码 这些值的定义视bInterfaceClass域而定
  9. __u8 bInterfaceProtocol = 0x02; //协议码:bInterfaceClass 和bInterfaceSubClass 域的值而定
  10. __u8 iInterface = 0x00; //描述此接口的字串描述表的索引值
  11. } __attribute__ ((packed));

前面章节,没有学到类这个分类概念,这里把用到的这个先列出来,下一节再学习由接口描述符中,指定的各种设备类型。

 

USB Class Codes    

Base Class

Descriptor Usage

Description

00h

Device

Use class information in the Interface Descriptors

01h

Interface

Audio  

02h

Both

Communications and CDC Control

03h

Interface

HID (Human Interface Device)

05h

Interface

Physical

06h

Interface

Image

07h

Interface

Printer

08h

Interface

Mass Storage

09h

Device

Hub

0Ah

Interface

CDC-Data

0Bh

Interface

Smart Card

0Dh

Interface

Content Security

0Eh

Interface

Video

0Fh

Interface

Personal Healthcare

10h

Interface

Audio/Video Devices

11h

Device

Billboard Device Class

12h

Interface

USB Type-C Bridge Class

DCh

Both

Diagnostic Device

E0h

Interface

Wireless Controller

EFh

Both

Miscellaneous

FEh

Interface

Application Specific

FFh

Both

Vendor Specific

类-子类-协议码 最终得到我们这个是一个鼠标设备。有一个端点,可想而知就是输入端点,比较鼠标就是一个输入类设备。

3.6.2.3 HID类描述符

09 21 11 01 00 01 22 34 00

因为标准的USB2.0协议没定义class的信息,所以只能去USB官网查找补充资料。

下面是USB1.11版本的HID手册中的HID描述符信息。

下面是linux内核中定义的HID描述符。


  
  
  1. /*
  2. * USB types, the second of three bRequestType fields
  3. */
  4. #define USB_TYPE_MASK (0x03 << 5)
  5. #define USB_TYPE_STANDARD (0x00 << 5) //标准的
  6. #define USB_TYPE_CLASS (0x01 << 5) //类的
  7. #define USB_TYPE_VENDOR (0x02 << 5) //厂商自己定义的
  8. #define USB_TYPE_RESERVED (0x03 << 5) //保留的
  9. /*
  10. * HID class descriptor types
  11. */
  12. #define HID_DT_HID (USB_TYPE_CLASS | 0x01)
  13. #define HID_DT_REPORT (USB_TYPE_CLASS | 0x02)
  14. #define HID_DT_PHYSICAL (USB_TYPE_CLASS | 0x03)
  15. struct hid_class_descriptor {
  16. __u8 bDescriptorType;
  17. __le16 wDescriptorLength;
  18. } __attribute__ ((packed));
  19. struct hid_descriptor {
  20. __u8 bLength;
  21. __u8 bDescriptorType;
  22. __le16 bcdHID;
  23. __u8 bCountryCode;
  24. __u8 bNumDescriptors;
  25. struct hid_class_descriptor desc[1];
  26. } __attribute__ ((packed));
  27. /**********************************************************/
  28. struct hid_class_descriptor {
  29. __u8 bDescriptorType = 0x22; //HID_DT_REPORT,表示这个类描述符是一个报告类的描述符
  30. __le16 wDescriptorLength = 0x0034; //描述符的总字节数为52字节
  31. } __attribute__ ((packed));
  32. struct hid_descriptor {
  33. __u8 bLength = 0x09; //描述符长度
  34. __u8 bDescriptorType = 0x21; //HID_DT_HID
  35. __le16 bcdHID = 0x0111; //1.11版本,bcd码表示
  36. __u8 bCountryCode = 0x0; //本地化支持,0不支持
  37. __u8 bNumDescriptors = 0x1; //表示这设备有1个类描述符
  38. struct hid_class_descriptor desc[1];
  39. } __attribute__ ((packed));

报表描述符看后面分析,注意将来就用这里的52来读取报表描述符的字节数。

3.6.2.4 端点描述符

07 05 81 03 05 00 0A

这里05,在标准的描述符表中有,所以很容易查找,这是一个端点描述符。


  
  
  1. struct usb_endpoint_descriptor {
  2. __u8 bLength = 0x07; //该描述符占用7个字节
  3. __u8 bDescriptorType = 0x05; //是一个端点描述符
  4. //Bit3..0:端点号 Bit6..4:保留,为零 Bit7:方向,如果控制端点则略。
  5. //0:输出端点(主机到设备) 1:输入端点(设备到主机) 所以在这是一个端点号为1的输入端点
  6. __u8 bEndpointAddress = 0x81;
  7. __u8 bmAttributes = 0x3; //00=控制传送 01=同步传送 10=批传送 11=中断传送 这是一个中断端点
  8. __le16 wMaxPacketSize = 0x0005; //最大包大小为5个字节
  9. __u8 bInterval = 0x0A; //周期数据传输端点的时间间隙。1ms * 10 = 10ms
  10. /* NOTE: these two are _only_ in audio endpoints. */
  11. /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
  12. /* 下面两个只有在图像视频类中才会有 */
  13. __u8 bRefresh;
  14. __u8 bSynchAddress;
  15. } __attribute__ ((packed));

3.7 获取字符串描述符语言ID

3.7.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 00 03 00 00 FF 00

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0300 ,查看9-5描述符表,得知高字节表示描述符类型,01表示设备,02表示配置,3表示字符串;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的3表示字符串,低字节为索引0。

第四五字节为0x0,当这个请求的是字符串描述符时,则表示Langurage ID。

第六七字节为0xFF,即代表返回的数据不应该多于255个字节。

从上一节的学习可以很容易得到,这是一个获取字符串描述符的请求。

3.7.2 数据传输分析 

设备给主机回复的数据为4个字节HEX:04 03 09 04

接下来看一下设备给主机回复字符串描述符信息。


  
  
  1. struct usb_string_descriptor {
  2. __u8 bLength = 0x04; //长度4字节
  3. __u8 bDescriptorType = 0x03; //3代表字符串描述符
  4. __le16 wData[ 1] = 0x0409; /* UTF-16LE encoded 代表0x0409的语言编码 */
  5. } __attribute__ ((packed));

0x0409代表的是美式英语,0x0804代表的是简体中文,这个具体的语言编码可以从USB或其它类似网站获取到。

既然以及获取到了具体的语言ID,那么就可以用前面设备描述符中读到的厂商和产品字符串描述符的索引index读到具体的商家和产品信息了。对应的字符串,并按照响应的语言格式来解析了。

3.8 获取字符串描述符(产品信息)

今天先分析到

3.8.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 02 03 09 04 FF 00 

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0302 ,查看9-5描述符表,得知高字节表示描述符类型,01表示设备,02表示配置,3表示字符串;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的3表示字符串,低字节为索引2

第四五字节为0x0409,当这个请求的是字符串描述符时,则表示Langurage ID,0x0409表示美式英语

第六七字节为0xFF,即代表返回的数据不应该多于255个字节。

从上一节的学习可以很容易得到,这是一个获取字符串描述符的请求,索引值为2。

从前面设备描述符中的字符串描述符的索引值可以看出,2代表是索引的产品信息。

3.8.2 数据传输分析 

设备给主机回复的数据为36个字节HEX:

24 03 55 00 53 00 42 00 20 00 4F 00 70 00 74 00 69 00 63 00 61 00 6C 00 20 00 4D 00 6F 00 75 00 73 00 65 00 

接下来看一下设备给主机回复字符串描述符信息。


  
  
  1. struct usb_string_descriptor {
  2. __u8 bLength = 0x24; //36字节的字符串描述符
  3. __u8 bDescriptorType = 0x3; //3表示字符串描述符
  4. __le16 wData[ 1]; /* UTF-16LE encoded,一个字符用16bit表示 */
  5. } __attribute__ ((packed));

当然,因为美式英语都死ASCII码内的字符表示的,所以高字节都是保留为0的,如果是简体中文,繁体中文,日语,韩语这类一个字符需要两个字节表示的则高字节也要用到。

简单对照ASCII码表,即可找到数字对应的这几个字符。

USB Optical Mouse
  
  

即得到这个产品的信息,这是一个USB的光点鼠标。

3.9 获取字符串描述符(商家信息)

3.9.1 建立阶段的请求分析

请求数据为8个字节HEX:80 06 01 03 09 04 FF 00 

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是要求设备给Host返回(bit7 == 1)

第二个字节0x06查看表9-4可以得到这是一个GET_DESCRIPTOR,即获取描述符 的请求

第三四字节传的是0x0302 ,查看9-5描述符表,得知高字节表示描述符类型,01表示设备,02表示配置,3表示字符串;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。或者一个配置下有多个接口,通过索引选择不同的接口。所以这里高字节的3表示字符串,低字节为索引1

第四五字节为0x0409,当这个请求的是字符串描述符时,则表示Langurage ID,0x0409表示美式英语

第六七字节为0xFF,即代表返回的数据不应该多于255个字节。

从上一节的学习可以很容易得到,这是一个获取字符串描述符的请求,索引值为1。

从前面设备描述符中的字符串描述符的索引值可以看出,1代表是索引的厂商信息。

3.9.2 数据传输分析 

设备给主机回复的数据为18个字节HEX:

12 03 4C 00 6F 00 67 00 69 00 74 00 65 00 63 00 68 00 

接下来看一下设备给主机回复字符串描述符信息。


  
  
  1. struct usb_string_descriptor {
  2. __u8 bLength = 0x14; //18字节的字符串描述符
  3. __u8 bDescriptorType = 0x3; //3表示字符串描述符
  4. __le16 wData[ 1]; /* UTF-16LE encoded,一个字符用16bit表示 */
  5. } __attribute__ ((packed));

当然,因为美式英语都死ASCII码内的字符表示的,所以高字节都是保留为0的,如果是简体中文,繁体中文,日语,韩语这类一个字符需要两个字节表示的则高字节也要用到。

简单对照ASCII码表,即可找到数字对应的这几个字符。

Logitech
  
  

即得到这个厂商的信息,这是一个罗技的产品。

3.10 设置配置描述符

3.10.1 建立阶段的请求分析

请求数据为8个字节HEX:00 09 01 00 00 00 00 00 

第一个字节0x80拆分可以得到,这是一个主机发给设备(bit0~bit4)的一个标准(bit5~bit6)的请求命令,请求的结果是主机给设备发送信息(bit7 == 0)

第二个字节0x09查看表9-4可以得到这是一个SET_CONFIGURATION,即设置配置

第三四字节传的是0x1,查看9-3标准请求表,得知这里1就是配置的值。

第四五字节为0x0,在设置配置里,无意义。

第六七字节为0x0,在设置配置里,无意义。

从上一节的学习可以很容易得到,这是一个设置配置描述符的请求。

3.10.2 无数据阶段

3.11 设置限定报表

3.11.1 建立阶段的请求分析

请求数据为8个字节HEX: 21 0A 00 00 00 00 00 00 

第一个字节0x21拆分可以得到,这是一个主机发给接口(bit0~bit4)的一个类(bit5~bit6)的请求命令,请求的结果是主机给接口发送类命令(bit7 == 0)

因为这是一个HID类设备,这里有是设置的类设备,标准手册没有,所以查看HID手册,

可见这里就是使能Set_Idle功能,Set_Idle请求使中断输入管道上的特定报告静默,直到发生新事件或经过指定的时间。

详细来Set_Idle可以看HID手册,说的比较清晰。

3.11.2 无数据阶段

3.12 获取HID报表描述符

设置限定报表和获得报表信息,都属于HID类中的特殊的类描述符,暂时还没学习,在后面章节慢慢学习...

这篇博文就不再加了,后面学完后,我会在这里贴上响应链接,保证知识完整性。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_16777851/article/details/85881863

发布了47 篇原创文章 · 获赞 9 · 访问量 1万+

一、什么是枚举?

猜你喜欢

转载自blog.csdn.net/u013836909/article/details/104902379
今日推荐