上一篇:https://blog.csdn.net/qq_40088639/article/details/109741653

总述

1. 设备枚举的整个过程

USB设备枚举过程,可大致分为下面的几个阶段:

一、获取设备描述符

二、复位总线

三、设置地址阶段

四、再次获取设备描述符

五、获取配置描述符集合请求(第一次)

六、语言ID描述符和字符串描述符的请求

七、又获取了一次设备描述符

八、又再一次获取配置描述符

九、设置配置请求

十、设置接口请求

十一、重复两次请求产品字符串描述符

十二、Set_Idle请求

十三、获取报告描述符

十四、又一次获取产品描述符

十五、又一次设置接口

十六、一个未知的HID中断输出请求

十七、最后再获取一次配置描述符

2. 说明

(1)根据实例,分析枚举过程,使用的是实时操作系统Zephyr的设备协议栈架构,并且该实例是一个组合设备[ HID + UAC ]。

(2)USB设备枚举过程并不是固定的,比如,有多种因素会导致Host重复请求获取某个描述符,并且每次获取的长度是不一样的;不同的PC可能枚举过程也有差异;不同操作系统枚举过程也有差异(这是开发USB面临的最大问题之一,兼容性问题)。但是,过程大致是一样的,指令也是一样的,都遵循同一套USB协议。

(3)关于Zephyr操作系统的USB协议栈,后续有介绍。

(4)USB设备枚举过程比较复杂,因此篇幅比较长,这里差分为连续的几篇博文进行记录。

(5)分析枚举过程,又把前面四篇的内容给串起来了[对协议有粗略的解析]。

                                                                           设备枚举过程分析

USB检测到USB设备插入,会先对其进行复位。USB设备在总线复位后,地址为0。接下来,主机就使用地址0和设备进行通信。

一、Host获取设备描述符

主机向 地址为0的设备 的端点0发送获取设备描述符标准请求

1. USB的标准请求

USB协议定义了一个8字节的标准设备请求,标准请求发生在设备的枚举过程中。

(1) 标准请求的结构

偏移量/字节

大小/字节

取值

描述

0

bmRequestType

1

位图

见下表

1

bRequest

1

数值

见下表

2

wValue

2

数值

见下表

4

wIndex

2

索引或偏移量

见下表

6

wLength

2

字节数

见下表

对各个字段的描述:

请求的特性[bmRequestType]:

1)该字节数据的第七位D7:请求的数据传输方向

0:表示主机到设备

1:表示设备到主机

2)该字节数据的第五和第六位D6~D5:表示请求的类型

0:表示这是一个标准请求

1:表示这是一个类请求

2:表示这是一个厂商请求

3:保留

3)第0到第4位D4~D0:表示该请求的接收者。

0:表示该请求的接收者是设备

1:表示该请求的接收者是接口

2:表示该请求的接收者是端点

3:表示该请求的接收者是其他

实例:如果是一个获取设备描述符的标准请求,那么这个字节数据就是0x80 = 1000 0000 B

[bRequest]: 请求代码

[wValue]: 意义由具体的请求来决定

[wIndex]: 意义由具体的请求来决定

[wLength]: 数据过程需要传输的字节数(可能为0,没有数据过程,那就是0)

2. USB获取设备描述符的标准请求

USB协议定义了11个标准请求(bRequest)。这11个标准请求的名字和对应的编号如下表。

bRequest

Value

bRequest

Value

GET_STATUS

0 (0x00)

GET_CONFIGURATIOIN

8 (0x08)

CLEAR_FEATURE

1 (0x01)

SET_CONFIGURATIOIN

9 (0x09)

SET_FEATURE

2 (0x02)

GET_INTERFACE

10 (0x0a)

SET_ADDRESS

5 (0x05)

SET_INTERFACE

11 (0x0b)

GET_DESCRIPTOR

6 (0x06)

SYNCH_FRAME

12 (0x0c)

SET_DESCRIPTOR

7 (0x07)

常用的几个标准请求为GET_DESCRIPTOR、SET_ADDRESS、GET_CONFIGURATIOIN、SET_CONFIGURATIOIN

(1) GET_DESCRIPTOR

(获取描述符)请求是枚举过程中,用得最多的一个请求。主机通过发送获取描述符请求,设备返回相应的数据,主机解析设备的各种描述符数据。也就是说,设备描述符还有分类,

USB协议规定,USB设备具有5种标准的描述符类型,如下表:

5种标准描述符类型

编号

设备描述符

1(0x01)

配置描述符

2(0x02)

字符串描述符

3(0x03)

接口描述符

4(0x04)

端点描述符

5(0x05)

GET_DESCRIPTOR的请求结构

bmRequestType

bRequest

wValue(2 Byte

wIndex(2 Byte)

wIndex (2 Byte)

1000 0000

(0x80

GET_DESCRIPTOR

(0x06

描述符的类型和索引值

0或者语言ID

描述符的长度

说明:

A.对于wValue这个域,第一字节(低字节)表示索引号,从0开始。第二字节(高字节)表示描述符的类型编号。

B除了获取字符串描述符之外,获取其他类型的描述符时,该域的值都为0。当获取的是字符串描述符中的语言ID字符串时(字符串描述符也有分类,下文会解析到),该域值就表示语言ID号。

C. wLength域是主机期望设备能返回的总数据字节数,那么,设备实际返回的字节数可以比该域指定的字节数少。比如:Host请求设备描述符的时候,只能返回18字节的数据(因为设备描述符只有18个字节)。

D. wValue、wIndex、wIndex的长度都是2字节,USB协议规定,使用小端模式进行数据传输,也就是低字节在前,高字节在后。

(2) 实例

Host请求设备描述符,则请求结构中各个域的为:

bmRequestType

bRequest

wValue

wIndex

wLength

1000 0000

(0x80

GET_DESCRIPTOR

(0x06

描述符的类型和索引值

0x01 00

0或者语言ID 0x00 00

长度

0x0040

那么Host下发的DATA0数据包的数据为[总线上的数据]:80 06  00 01  00 00  40 00

(3) 主机请求设备描述符的流程

在枚举阶段,USB使用的是控制传输。一次数据传输可认为是一个事务,事务一般由两三个包组成。事务=令牌包+数据包+握手包

控制传输分为三个过程:

第一过程:建立过程

第二过程:可选的数据过程

第三过程:状态过程

对于建立过程,使用一个建立事务一次数据传输过程[有发送方 和 接收方])。建立事务是一个数据输出的过程,也就是数据从主机到设备

令牌包只能使用SETUP令牌包;数据包只能使用DATA0数据包;最后是握手包,设备只能用ACK来应该(如果出错,则不应答)。使用USB协议分析仪分析建立事务,如下图:

数据包的处理

在枚举阶段,有很多的数据包以及复杂的传输过程,作为开发者,要怎么去处理呢?其实很多地方,USB接口芯片已经处理好了。我们只需要弄清楚过程就可以,区分哪部分是芯片完成的,哪部分是代码中需要处理的。

芯片自动完成的(可认为是芯片内部的硬件自动处理的):CRC校验、数据包切换等。

需要关注的:SETUP令牌包的中断、IN令牌包的中断、ACK握手包的回复等,对于程序来说,就是处理一系列的中断事件。Host下发的数据指令,比如上面这个例子中的DATA0数据包(80 06 00 01 00 00 40 00)。在程序中会收到这些数据,并且做逻辑上的处理(比如接下来就要把设备描述符的数据返回给主机,回应它的请求)。

芯片自动完成的那些事情,数据是没法捕获到的,比如,使用Bus Hound这个上位机软件,只能捕获到DATA0数据包的数据,在Bus Hound中只能看到成功传输的数据,也就是只有ACK确认过的数据包。而使用比较高级的协议分析设备,比如专业的USB协议分析仪,就可以将整个过程都可视化。

数据过程是可选的,也就是一个控制传输,可以没有数据过程。一旦数据传输方向发生了改变,就会进行入状态过程。状态过程数据传输方向和前面的数据阶段相反,状态过程使用的是DATA1数据包。

继续接着分析下一个事务,也就是下一个数据传输。主机接着下发IN令牌包。设备切换到DATA1数据包,将设备描述符(18 Byte)发送给主机。主机收到数据,进行ACK回应。

设备返回描述符数据的过程如下图:

因为主机请求的是设备描述符,所以,这里返回的是USB设备的设备描述符数据。

3. 设备描述符

每个USB设备都必须并且只有一个设备描述符(在程序中有定义)。USB协议对设备描述符的定义如下:

(1) 设备描述符的结构

偏移量/字节

大小/字节

说明

0

bLength

1

描述符的长度 (18Byte=0x12)

1

bDescriptorType

1

描述符类型(设备描述符=0x01)

2

bcdUSB

2

本设备使用的USB协议版本

4

bDeviceClass

1

类代码

5

bDeviceSubClass

1

子类代码

6

bDeviceProtocol

1

设备所使用的协议

7

bMaxPacketSize0

1

端点0的最大包长(64 bytes)

8

idVendor

2

厂商ID

10

idProduct

2

产品ID

12

bcdDevice

2

设备版本号

14

iManufacturer

1

描述厂商字符串的索引

15

iProduct

1

描述产品字符串的索引

16

iSerialNumber

1

产品序列号字符串的索引

17

bNumConfigurations

1

可能的配置数

说明:

1)bcdUSB是该设备所使用的USB协议版本号,长度2字节。比如可以取2.0或者1.1等版本号。需要特别注意的是,协议规定使用BCD码来表示版本号,比如:USB2.0协议就是0x0200,USB1.1协议就是0x0110。对照USB协议分析仪来看的时候,要注意,USB协议中使用的是小端结构,也就是低字节在前。比如说,USB2.0协议拆分成两个字节就是0x00 0x02,那么对照协议分析仪里面的数据就是:00 02  ;USB1.1在协议分析仪里面的数据就是:10 01。

2)bDeviceClass是设备所使用的类代码(XX类接口描述符码)。常用的类如下(根据协议,进行C宏定义):

//HID设备类接口描述符码

#define HID_CLASS                    0x03

//音频类接口描述符码

#define Audio_CLASS                 0x01

//视频类接口描述符码

#define Vedio_CLASS                 0x0E

//大容量设备类接口描述符码

MASS_STORAGE_CLASS           0x08

//杂项类或者混合类接口描述符码

#define MISC_CLASS                           0xEF

//厂商自定义的设备类接口描述符码

#define CUSTOM_CLASS                    0xFF

//特定应用类接口描述符码

#define DFU_DEVICE_CLASS            0xFE

3)bDeviceSubClass设备所使用的子类代码。当类代码不为0也不是0xFF时,子类代码就得根据协议来进行赋值。当类代码为0的时候,子类代码也必须为0。

4)bDeviceProtocol是设备使用的协议。协议代码由USB协议规定。当该字段为0的时候,表示设备不使用类所定义的协议。该字段为0xFF的时候,表示使用的是厂商自定义的协议。也就是说,bDeviceProtocol要结合设备类和设备子类来进行赋值,如果设备类代码不为0,则子类代码肯定也不为0,进而bDeviceProtocol也就不为0(具体的取值,得深入研究USB协议)。如果类代码为0,则子类代码也就为0,进而bDeviceProtocol的值也就为0。综上所述,bDeviceClass、bDeviceSubClass、bDeviceProtocol这三者,要么都同时为0,要么都不为0。一般来说,设备类的定义放到接口里面,所以这三个字段一般都设置为0。

5)端点0的最大包长,取值可以是:8、16、32、64字节。注意,对应的十六进制数分别就是:0x08、0x10、0x20、0x40(分析源码和协议分析仪里面的数据,注意进行转换)。

6)关于厂商ID (2Byte),在开发中,可以随意设定一个值。真正做产品,要使用公司的ID(向USB协会申请),避免侵权。对于插入的设备,主机是依靠厂商ID号、产品ID号、产品序列号来安装驱动的。

7)产品ID是生产厂商自己定义的,比较自由。

8)bcdDevice设备版本号。同一个产品,升级之后(比如固件修改,新增功能),可以通过修改设备版本号来进行区别。

9)iManufacturer是描述厂商字符串的索引值。如果设为0,则表示该USB设备没有厂商字符串。主机单独获取厂商字符串的时候,下发的标准请求数据包中,wValue域的第一个字节【低字节】就是厂商字符串的索引值,而高字节就是描述符的类型(字符串描述符0x03)。

厂商字符串就是一串普通的字符串,在设备描述符中,有三个非0的索引值:厂商字符串的索引值为1;产品字符串的索引值为2;产品序列号字符串的索引为3。设备在收到主机的字符串描述符请求之后,根据索引值,将对应的字符串数据返回给主机。所以,如果解析到主机字符串描述符请求的数据包,如果wValue=0x0301,则表示主机请求获得厂商字符串

10)iProduct是描述产品的字符串的索引值。同样的,如果设置为0,则表示该USB设备没有产品字符串。第一次插上设备时,提示发现新硬件,并显示设备的名称,其实这里显示的信息就是从产品字符串中获取的。实验:可通过修改产品字符串,再编译固件烧录,插入设备,就可以看到提示信息了。

同理,当主机请求产品字符串的时候,wValue这个域的值应该是:0x0302

11)iSerialNumber是设备的序列号字符串的索引值。同样的,如果设置为0,则表示该USB设备没有设备序列号。最好一个产品指定一个唯一的序列号,因为有可能主机会结合产品序列号和VID、PID来进行设备的区分和加载对应的驱动。

同理,当主机请求序列号字符串的时候,wValue这个域的值应该是:0x0303

注:

厂商ID 、产品ID和序列号字符串是不一样的。


下文对源码的分析,仅供参考。

(2) 设备描述符在源码中的定义

对于设备描述符这个数据结构,可以抽象定义成一个结构体。zephyr的原生代码中,设备描述符的定义处如下:

4. 代码流程分析

(1) usb_handle_control_transfer()函数

这是设备核心层中的API,端点0有数据(数据输入/输出事务)的时候,usb_handle_control_transfer()会被回调。假如是一个标准请求,那么可以根据bmRequest域的值来进行判断,接下来数据的传输方向,只有两种情况:

A. 设备---->主机[bmRequest=0x80],也就是设备收到了一个请求,主机要求它接下来要把特定的请求的数据发给主机。

B. 主机---->设备[bmRequest=0x00],也就是设备收到了一个请求,通过解析请求,它知道接下来主机要下发数据。

在USB通信中,数据的收发是通过端点来进行的。各种描述符之间的关系,大概如下:

设备描述符(一个设备有且只有一个设备描述符)【up】

--------------------------------------------------------------------------------------

配置描述符

接口描述符

---------------------------------------------------------------------------------------

端点描述符 【bottom】

端点存在这几种状态,在源码中,定义如下:

文件:usb_dc.h

/**

* USB Endpoint Callback Status Codes

* 端点回调状态码定义

*/

enum usb_dc_ep_cb_status_code {

/*已经收到SETUP数据包 */

/*如果是标准请求,那么SETUP数据包就是完整的8字节标准请求数据包 */

USB_DC_EP_SETUP,    /* SETUP received =0*/

/* Out transaction on this EP, data is available for read =1*/

/* Host通知设备,在该端点上进行的是:数据输出事务 */

/* 并且,数据已经可以进行读取 */

USB_DC_EP_DATA_OUT,

//在该端点上,一个数据输入事务已经完成。

//一个事务,就是一次数据传输,

USB_DC_EP_DATA_IN,  /* In transaction done on this EP =2*/

};

回到usb_handle_control_transfer()的分析,该函数主要是进行数据传输方向的判断和端点状态的检测,对于枚举过程,首先从这个函数入手进行分析(整个API是处理控制传输的),原型定义如下:

(2) 一个标准请求处理的流程

首先一个标准请求会使得usb_handle_standard_request()被回调[暂时不分析底层控制器]。然后在函数体内,usb_handle_std_device_req()会被调用,真正地去处理标准请求指令是在usb_handle_std_device_req()里面。也就是前者是被触发,进行回调,后者是被调用。这两个函数定义的地方分别如下:

请求获得设备描述符、请求设置地址、请求获得配置描述符等标准请求,都要从下面这个函数开始进行分析。

在这个阶段,以Host请求获得设备描述符为例,接着进行分析。

分析usb_get_descriptor()这个函数,可以大概知道:先提取描述符的类型,判断是属于哪一种描述符,比如是设备描述符,然后,到描述符空间[一个地址,里面存放的是描述符的数据,设备描述符定义好之后,会进行注册]进行遍历,根据偏移量进行遍历进行寻找,该函数的定义处:

看函数体里面的注释,结合USB协议,可以知道:对于全速模式和低速模式的设备,获取描述符的标准请求只有三种。单独请求获取设备描述符、单独请求获取配置描述符、单独请求获取字符串描述符。接口描述符和端点描述符是跟着配置描述符一起返回的,不可单独返回,根据他们之间的包含关系(一个配置描述符可以有很多个接口,一个接口可以有很多个端点……),如果单独返回,主机没法解析(不符合协议)。实际上,主机也不会单独请求获取。

找到设备描述符之后,通过usb_data_to_host()将数据发送到主机。一层层剖析,可以发现,数据的发送是通过端点0进行发送的(符合协议)。usb_data_to_host()里面调用的是usb_dc_ep_write(),进入usb_dc_ep_write()可以发现,该函数完成数据的发送,还可以获取到发送成功的数据字节数。

发送的大概过程(结合代码和协议): 将设备描述符数据写入到端点0的输入缓冲区中,并使能端点发送。主机下发IN令牌包之后,设备的USB控制器就将缓冲区中的数据返回给主机,主机收到数据,则下发ACK握手包到设备,设备收到握手包就可以确认数据已经成功传输到主机了(这部分也是硬件自动完成的,对用户不可见)。

两个函数的定义处,分别如下:

主机在成功获取第一个数据包的设备描述符并确认无误之后,就会返回一个0长度的确认数据包(状态过程,使用的是DATA1数据包),设备收到确认数据包,就知道发送的数据是无误的,接下来设备发送握手包给主机,结束一个控制传输过程。控制传输过程比较复杂,几次交互,有可见的数据交互,也有不可见的数据交互,虽然复杂,但是能保证数据的可靠性。

到此,一个控制传输过程结束【建立过程(主机下发请求)、状态过程(设备返回数据) 状态过程(主机返回0长度的数据包)】。设备描述符总共有18个字节。

串口跟踪打印如下:

总结大概的流程: 有请求----->解析请求----->返回数据

需要注意的是,端点状态的回调(能反映数据的通信过程是否完整,开始或者结束)。

USB协议分析仪上捕获到的完整的总线数据:

Packet: 包         Transation: 事务        Transfer: 传输

5. 总结

(1) Host请求返回64个字节(0x40)的数据,但是实际上,设备描述符只有18字节。即返回的字节数可以小于或者等于wLength(协议规定)。

(2) 芯片自动处理的令牌包: ACK握手包(对用户不可见,一般的USB上位机捕获软件也没能捕获到,只有USB协议分析仪可以)。

(3) 这个过程总共有三个事务,每个事务的构成 = 令牌包 + 数据包 + 回应包,这三个事务又构成一次传输。

(4) 主机请求设备描述符这个阶段,使用的是控制传输类型,数据包在DATA0和DATA1之间进行切换。

(5) USB协议分析仪能够图形化分析每个流程。注意结合: USB协议分析仪 + 协议文档 + 源码。

(6) 主机在这个阶段【还没有复位总线】是和地址为0的USB设备的端点0进行数据通信。

(7) 都是Host在主动

Host检测到DP/DM电平有变化,说明有device接入;

紧接着就获取设备描述符(发出SETUP 和 IN_TOKEN包);

...........................................................................................

以上是Host检测到Device插入,第一次请求设备描述符的过程。

下一篇:https://blog.csdn.net/qq_40088639/article/details/109752441

第五篇 USB设备枚举过程(1)相关推荐

  1. USB UVC实战笔记第1篇—UVC设备枚举过程详细分析

    1 UVC枚举过程分析 UVC,全称为:USB video class 或USB video device class.是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标 ...

  2. usb mass storage设备枚举过程

    8月份从PC开发岗位换到底层嵌入式驱动开发来了.接到的第二个活,是在高通8650平台上实现驻留支持SCSI指令.8650平台是高通推出的EVDO RevB的双核平台,和之前6085相比性能更强,软件结 ...

  3. USB 之枚举过程概述

    来源:公众号[鱼鹰谈单片机] 作者:鱼鹰Osprey ID   :emOsprey 上篇笔记我们大概了解了一下 USB 设备插入主机后经历了什么阶段,本篇笔记和接下来的笔记将详细介绍整个流程. 每一个 ...

  4. 5、USB协议学习:USB的枚举过程

    文章目录 枚举顺序 枚举过程 标准请求 bmRequestType bReqest 请求类型 GetDescriptor 设备描述符 设备描述符定义 获取设备描述符 返回设备描述符 配置描述符 配置描 ...

  5. USB驱动程序之一(USB介绍、USB数据传输、USB设备枚举)

    文章目录 USB简介 USB系统架构 USB系统拓扑结构 USB主控制器 USB HUB USB设备 USB设备逻辑结构 USB描述符 设备描述符 配置描述符 接口描述符 端点描述符 USB数据传输 ...

  6. LPC1768的USB使用-枚举过程

    枚举过程如下 #ifndef __USBCORE_H__ #define __USBCORE_H__ /* USB端点0 发送数据结构体*/ typedef struct _USB_EP_DATA { ...

  7. Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程

    目录 前言 1.枚举过程 1.1 acpi_pci_root_add 1.2 pci_acpi_scan_root(枚举开始) 1.3 acpi_pci_root_create 1.4 pci_sca ...

  8. usb子系统分析2(usb设备识别过程)

    问题一:USB总线驱动是USB控制器的驱动程序,那控制器是如何识别usb设备?识别后,如何为该设备匹配驱动?匹配驱动后,app是如何访问该usb设备的? 识别USB设备 1.1 USB设备插入时,D+ ...

  9. 基于OHCI的USB主机 —— USB设备常量定义

    USB设备枚举过程中使用到的常量定义如下: /*-------------------------------------------------------------------------  * ...

最新文章

  1. 第二批重磅嘉宾已就位,邀你共探AI行业新机遇 | MEET2022智能未来大会
  2. xfs_repair 实际工作中的问题
  3. mfc指示灯报警显示_消防百科 | 火灾显示盘的基本功能有哪些?
  4. python fuzzy c-means demo
  5. 结构型模式/设计模式
  6. python基本用法_python基本用法
  7. 《复盘+》把经验转化为能力
  8. boost::hana::not_用法的测试程序
  9. 为bootstrap的tab增加请求操作
  10. XAMPP 1.8.2-2 Apache Web Server won't start, always stops immediately
  11. kafka学习总结之集群部署和zookeeper
  12. 【CVPR 2019】Strong-Weak Distribution Alignment for Adaptive Object Detection
  13. 编码人员和美工的配合问题
  14. C语言国二上机题库,【高分飘过】2013年国二C语言上机题库(必备完美版).doc
  15. 活著就为改变世界---史蒂夫.乔布斯…
  16. DbgView 无法开启Capture Kernel问题
  17. 【物联网】全球SIM连接解决IoT设备换卡难问题
  18. 哈夫曼编码原理分析及代码实现(有注释)
  19. python有道云笔记_Python自动同步有道云笔记到Hexo
  20. 撇开PUE,评估数据中心增长的真正环境影响

热门文章

  1. USB Network Gate SDK Crack,构建USB Network Gate的技术
  2. 怎样知道mysql的驱动是什么_MySQL连接查询到底什么是驱动表?看了这里你应该就明白了...
  3. 蓝桥杯 ALGO-1004 无聊的逗 01背包+回溯 python
  4. Python3程序设计题解: 三连击(升级版)
  5. 华为临阵换帅,云市场掀起“三国杀”
  6. 大四狗:我的java历程(续)
  7. LDO输出为什么并联接地电阻?
  8. Linux系统下服务和运行目标管理——单用户和多用户模式的切换
  9. ant - java 构建工具
  10. 18 张图解支付宝钱包系统架构!