名词 | 全称 | 说明 |
---|---|---|
USB | Universal Serial Bus | 通用串行总线,一种主机侧与USB设备交互的总线。 |
CDC | Communications Device Class | 通信类设备 |
NCM | Network Control Model | 基于网络格式的通信模式 |
MBIM | Mobile Broadband Interface Model | 基于MBIM消息的通信模式 |
usbccgp.sys | NA | 微软内置的复合设备inbox驱动 |
cxwmbclass.sys | NA | 微软内置的网络接口inbox驱动 |
usbhub3.sys | NA | 微软usb root hub3.0的inbox驱动 |
关于USB协议相关知识点,本章节只是讲解USB枚举过程中,需要用到的一些知识点,比如USB协议中定义的几种USB设备状态、USB控制器与USB设备之间的控制包交互,也就是setup数据包,以及USB设备需要上报的一些描述符信息,至于其他相关信息,请自行查看USB协议文档。
USB协议规定了USB设备拥有几种设备状态,各个设备状态之间的转换关系,如下图所示,下面重点讲解如下几种状态。
上述只简单讲解了各个状态的一些关键点,如需更详细的信息,请自行查阅USB 3.2协议中的9.1.1.1-9.1.1.7小节。
USB控制器与USB设备之间存在四种传输模式,分别为Data Flow,Control Transfer,Interrupt Transfer和Isochronous Transfer,其中Control Transfer这种传输模式非常重要,因为其可以保证数据的正确性,在设备的枚举过程中都是使用控制传输的。控制传输分为三个阶段:setup阶段、data阶段和ack阶段,下面简单讲解这三个阶段,具体详细细节,请自行查阅USB 3.2协议中的8.12.2小节。
下面重点讲解setup数据包,其大小8个字节,具体格式如下所示。
bmRequestType域大小为1个字节,解决了向谁请求的问题,其格式如下所示。
bmRequest大小为1个字节,解决了请求什么的问题,也就是请求命令码,其中包括了标准请求命令码、类定义请求命令码以及USB设备厂商自定义的命令码。本文只给出标准请求命令码,请参考附录7.1小节,而类定义请求命令码,请查看相关设备类的相关协议文档,而附录7.2小节会给出CDC设备类的请求命令码。
此域用来传送当前请求的参数,随请求不同而不同。
当bmRequestType的Recipient字段为接口或端点时,wIndex域用来表明是哪一个接口或端结。
这个域表明第二阶段的数据传输长度。传输方向由bmRequstType域的Direction位指出。wLength域为0则表明无数据传输。在输入请求下,设备返回的数据长度不应多于wLength,但可以少于。在输出请求下,wLength指出主机发出的确切数据量。如果主机发送多于wLength的数据,设备做出的响应是无定义的。
在USB中USB HOST是通过各种描述符来识别设备的,有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符等,这些描述符之间的关系如下图所示。
设备描述符代表一个USB设备,它由一个或多个配置组成。设备描述符用于说明设备的总体信息,并指出其所包含配置的个数。下面给出设备描述符的结构体,如下所示。
//
// USB 1.1: 9.6.1 Device, Table 9-7. Standard Device Descriptor
// USB 2.0: 9.6.1 Device, Table 9-8. Standard Device Descriptor
// USB 3.0: 9.6.1 Device, Table 9-8. Standard Device Descriptor
//
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;
关于设备描述符的详细含义,请查看USB 3.2协议中的9.6.1小节。
在使用USB设备前,必须为其选择一个合适的配置,如USB设备的低功耗模式和高功耗模式分别对应一个配置。配置描述符用于说明USB设备中各个配置的特性。下面给出配置描述符的结构体,如下所示。
//
// USB 1.1: 9.6.2 Configuration, Table 9-8. Standard Configuration Descriptor
// USB 2.0: 9.6.3 Configuration, Table 9-10. Standard Configuration Descriptor
// USB 3.0: 9.6.3 Configuration, Table 9-15. Standard Configuration Descriptor
//
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;
关于配置描述符的详细含义,请查看USB 3.2协议中的9.6.3小节。
一个配置可以包含一个或多个接口。接口是一个端点的集合。接口描述符用于说明USB设备中各个接口的特性。下面给出接口描述符的结构体,如下所示。
//
// USB 1.1: 9.6.3 Interface, Table 9-9. Standard Interface Descriptor
// USB 2.0: 9.6.5 Interface, Table 9-12. Standard Interface Descriptor
// USB 3.0: 9.6.5 Interface, Table 9-17. Standard Interface Descriptor
//
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;
关于接口描述符的详细含义,请查看USB 3.2协议中的9.6.5小节。
端点是USB设备中的实际物理单元,USB数据传输就是在主机和USB设备各个端点之间进行的。下面给出端点描述符的结构体,如下所示。
//
// USB 1.1: 9.6.4 Endpoint, Table 9-10. Standard Endpoint Descriptor
// USB 2.0: 9.6.6 Endpoint, Table 9-13. Standard Endpoint Descriptor
// USB 3.0: 9.6.6 Endpoint, Table 9-18. Standard Endpoint Descriptor
//
typedef struct _USB_ENDPOINT_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bEndpointAddress;
UCHAR bmAttributes;
USHORT wMaxPacketSize;
UCHAR bInterval;
} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;
关于端点描述符的详细含义,请查看USB 3.2协议中的9.6.6小节。
字符串描述符是可选的,就像前面所示的那样,如果一个USB设备不支持设备描述符、配置描述符和接口描述符所引用的字符串,那么这些描述符中所有引用的字符串索引值必须设置为0。下面给出字符串描述符的结构体,如下所示。
//
// USB 1.1: 9.6.5 String, Table 9-12. UNICODE String Descriptor
// USB 2.0: 9.6.7 String, Table 9-16. UNICODE String Descriptor
// USB 3.0: 9.6.8 String, Table 9-22. UNICODE String Descriptor
//
typedef struct _USB_STRING_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
WCHAR bString[1];
} USB_STRING_DESCRIPTOR, *PUSB_STRING_DESCRIPTOR;
关于字符串描述符的详细含义,请查看USB 3.2协议中的9.6.9小节。
Windows侧主机检测到USB设备插入后,就要对设备进行枚举了。枚举的作用就是从设备那里读取一些信息,知道是什么样的设备,这样主机就可以根据这些信息加载合适的驱动程序。下面给出Windows侧复合设备通用的枚举过程,如下所示。
用户将USB设备插入到集线器端口,也就是HUB端口
HUB端口通过检测D+,D-数据线的电压情况,判断有新设备插入,具体步骤,请查看附录7.3小节。
当检测到设备后,hub继续给设备供电,但并不急于与设备进行USB传输。
当完成1-3之后,USB设备的状态从初始attached-》powered。
每一个hub都有自己的中断端点向主机上报它的端口状态(对于这个过程,设备是不可见的,也就是USB分析仪抓取不到该过程,这个是主机侧自身的行为),报告的内容只是HUB端口的设备连接/端口的事件。
hub端口上报系统有新设备连接后,系统下发Get_Port_Status请求到hub了解新设备的基本信息(比如设备的速度类型等信息,插入或者移除设备事件等)
系统知道有新设备到达,并知道一些基本信息后,至少等待100ms后,主机控制器向hub发出一股Set_Port_Feature请求,让hub执行端口复位操作(hub通过驱动数据线d+,d-到低电平状态,并持续至少10ms)
如果host获取到设备是全速设备的话,那么系统会进行高速检测,查看该设备是否支持高速传输,如果是,切换到高速信号模式,具体过程,请查看USB总线协议。
系统不停向hub发送Get_Port_Status请求,以查询设备是否复位成功。hub返回的信息中,有一位用来标志设备的复位状态。
当复位成功后,设备处于default状态(地址为0),此时主机与设备可以通过address=0,ep=0,进行控制信息的交互,也就是通过setup数据包完成信息的交互。
此时Hub驱动,也就是usbhub3.sys,会下发标准请求命令码GET_DESCRIPTOR(bmRequest=0x6,wValue=0x0100,其中Descriptor Type =0x01,Descriptor Index=0x00)来第一次获取USB设备描述符信息,具体如下图所示,主要获取默认管道EP0的最大包长度bMaxPacketSize0和设备描述符的长度信息,其他信息瞄一眼即可,驱动暂不关心。
当完成上述请求后,系统会要求hub,对设备再次复位(USB规范没有该要求)。再次复位的目的使设备进入到一个确定的状态。
Hub驱动通过SET_ADDRESS(bmRequest=0x5,wValue=addr)标准请求向USB设备分配唯一地址,后面的所有通信都通过该地址进行,此时设备处于address状态,比如如下所示,给USB设备分配的地址为0x24
完成USB设备地址分配后,Hub驱动就会通过该地址,第二次获取该设备的设备描述符信息,就像步骤11中所示的那样,而这一次,诚意满满,Hub驱动会认真解析设备描述符的所有信息,当然Hub驱动也会获取一些其他有用的描述符信息,比如配置描述符以及iProduct字符串等信息,这里就不再详细讲解了。
当Hub驱动完全解析了USB设备描述符后,生成一个PDO节点,其硬件ID等于USB\VID_xxxx&PID_yyyy,其中xxxx为idVendor,而yyyy为idProduct,同时产生一个PNP事件,通知系统有新的PDO产生。
同时也会判断该设备是否为composite device,关于判定为composite device的方法,请查看附录7.4小节。如果USB设备为复合设备的话,Hub驱动会给新生成的PDO,赋予一个兼容ID,其值为USB\COMPOSITE。
在检索到新PDO的硬件和兼容ID后,操作系统会搜索相应的INF文件,搜索原则如下:
(1)优先查找跟硬件ID匹配的INF文件,如果找到,则安装该INF文件指定的驱动。
(2)否则没有硬件ID匹配的INF文件,由于该PDO有一个兼容ID,那边系统就会查找跟兼容ID匹配的INF文件,如果找到的话,则安装INF文件指定的驱动。
(3)由于该PDO兼容ID为USB\COMPOSITE,结果查找到了USB.inf文件,进而触发系统安装usbccgp.sys驱动。
注意:
(1),(2)步骤适用于Windows侧所有的PDO驱动安装。
当usbccgp.sys驱动执行时,会先下发标准请求GET_DESCRIPTOR获取设备描述符信息,之后通过两次下发GET_DESCRIPTOR(bmRequest=0x6,wValue=0x0200,其中Descriptor Type =0x02,Descriptor Index=0x00)获取完整的配置描述符信息。
usbccgp.sys驱动通过SET_CONFIGURATION(bmRequest=0x9,wValue=cfgvalue)下发配置配置值到USB设备,此时USB设备进入到Configured状态,下面给出一个例子,主机侧驱动下发的配置值为0x1,如下所示。
L830模块是基于Intel芯片,而且该模块经过大批量发货,已经验证了稳定性和可靠性,具有典型代表,所以基于该模块进行实例讲解,如下所示。关于windows系统侧的行为,这里就不在讲解了,直接从模块被复位到默认状态开始,也就是模块的addr=0,ep=0,而且主机与模块开始控制信息的交互,下面以PC机root hub3.0为例讲解。
usbhub3.sys驱动下发GET_DESCRIPTOR获取L830模块的配置描述符,iSerialNumber、iProduct以及语言ID描述符信息,如下所示。
当加载usbccgp.sys驱动后,该驱动会再次获取L830模块的设备描述符以及配置描述符信息,如下所示。
usbccgp.sys驱动通过SET_CONFIGURATION给L830模块下发配置参数0x1,让模块处于Configured状态,如下所示。
当usbccgp.sys驱动解析完L830模块的配置描述符后,会生成两个子节点,一个MBIM节点,一个串口节点,如下所示,同时这两个节点都会分别自动加载微软的内置驱动:cxwmbclass.sys和usbser.sys。
当cxwmbclass.sys驱动加载后,会下发两次SET_INTERFACE标准请求命令码(bmRequest=0x0B)分别将interface 0,interface 1的Alternate值先复位为0。
模块会上报两次芯片自定义的标准请求(请求码为0xFE),如下所示。
cxwmbclass.sys驱动下发NCM类标准请求码0x86(set_ntb_input_size)到模块侧,如下所示
cxwmbclass.sys驱动重复步骤14和15的操作,分别再次下发0x05RESET_FUNCTION和0x86(set_ntb_input_size),如下所示,至于为什么再次发送这两个请求码,待研究。
cxwmbclass.sys下发SET_INTERFACE标准请求命令码(bmRequest=0x0B)分别将interface 1的Alternate值设置为2,如下所示。
剩下的就是标准MBIM命令的交互了,这里就不再详细一一讲解了,只简单给出一个标准的MBIM命令,也就是MBIM_COMMAND和MBIM_COMMAND_DONE,如下图所示。
本小节给出了适用所有USB设备的标准设备请求,表9-4概括出了这些标准请求,而表9-5给出了标准请求命令码,表9-6给出了描述符类型信息。
关于各个标准请求命令码的详情,请查看USB 3.2协议中的9.4.1-9.4.13小节。
请求码 | Value | 引用文档 |
---|---|---|
SEND_ENCAPSULATED_COMMAND | 00h | 6.2.1 |
GET_ENCAPSULATED_RESPONSE | 01h | 6.2.2 |
SET_COMM_FEATURE | 02h | [USBPSTN1.2] |
GET_COMM_FEATURE | 03h | [USBPSTN1.2] |
CLEAR_COMM_FEATURE | 04h | [USBPSTN1.2] |
RESET_FUNCTION | 05h | [USBMBIM1.0] |
RESERVED (future use) | 06h-0Fh | |
SET_AUX_LINE_STATE | 10h | [USBPSTN1.2] |
SET_HOOK_STATE | 11h | [USBPSTN1.2] |
PULSE_SETUP | 12h | [USBPSTN1.2] |
SEND_PULSE | 13h | [USBPSTN1.2] |
SET_PULSE_TIME | 14h | [USBPSTN1.2] |
RING_AUX_JACK | 15h | [USBPSTN1.2] |
RESERVED (future use) | 16h-1Fh | |
SET_LINE_CODING | 20h | [USBPSTN1.2] |
GET_LINE_CODING | 21h | [USBPSTN1.2] |
SET_CONTROL_LINE_STATE | 22h | [USBPSTN1.2] |
SEND_BREAK | 23h | [USBPSTN1.2] |
RESERVED (future use) | 24h-2Fh | |
SET_RINGER_PARMS | 30h | [USBPSTN1.2] |
GET_RINGER_PARMS | 31h | [USBPSTN1.2] |
SET_OPERATION_PARMS | 32h | [USBPSTN1.2] |
GET_OPERATION_PARMS | 33h | [USBPSTN1.2] |
SET_LINE_PARMS | 34h | [USBPSTN1.2] |
GET_LINE_PARMS | 35h | [USBPSTN1.2] |
DIAL_DIGITS | 36h | [USBPSTN1.2] |
SET_UNIT_PARAMETER | 37h | [USBISDN1.2] |
GET_UNIT_PARAMETER | 38h | [USBISDN1.2] |
CLEAR_UNIT_PARAMETER | 39h | [USBISDN1.2] |
GET_PROFILE | 3Ah | [USBISDN1.2] |
RESERVED (future use) | 3Bh-3Fh | |
SET_ETHERNET_MULTICAST_FILTERS | 40h | [USBECM1.2] |
SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER | 41h | [USBECM1.2] |
GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER | 42h | [USBECM1.2] |
SET_ETHERNET_PACKET_FILTER | 43h | [USBECM1.2] |
GET_ETHERNET_STATISTIC | 44h | [USBECM1.2] |
RESERVED (future use) | 45h-4Fh | |
SET_ATM_DATA_FORMAT | 50h | [USBATM1.2] |
GET_ATM_DEVICE_STATISTICS | 51h | [USBATM1.2] |
SET_ATM_DEFAULT_VC | 52h | [USBATM1.2] |
GET_ATM_VC_STATISTICS | 53h | [USBATM1.2] |
RESERVED (future use) | 54h-5Fh | |
MDLM Semantic-Model specific Requests | 60h – 7Fh | [USBWMC1.2] |
GET_NTB_PARAMETERS | 80h | [USBNCM1.0] |
GET_NET_ADDRESS | 81h | [USBNCM1.0] |
SET_NET_ADDRESS | 82h | [USBNCM1.0] |
GET_NTB_FORMAT | 83h | [USBNCM1.0] |
SET_NTB_FORMAT | 84h | [USBNCM1.0] |
GET_NTB_INPUT_SIZE | 85h | [USBNCM1.0] |
SET_NTB_INPUT_SIZE | 86h | [USBNCM1.0] |
GET_MAX_DATAGRAM_SIZE | 87h | [USBNCM1.0] |
SET_MAX_DATAGRAM_SIZE | 88h | [USBNCM1.0] |
GET_CRC_MODE | 89h | [USBNCM1.0] |
SET_CRC_MODE | 8Ah | [USBNCM1.0] |
RESERVED (future use) | 8Bh-FFh |
在hub端,数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻Rpd,而在设备端,D+(全速,高速)和D-(低速)上有一个1.5k的上拉电阻Rpu。当设备插入到hub端口时,有上拉电阻的一根数据线被拉高到幅值的90%的电压(大致是3V)。hub检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口(全速、高速设备的区分在我将来的文章中描述),如下图所示。
只要满足如下两个条件的一个,windows系统就认为USB设备为composite设备。
满足如下要求:
满足如下要求:
子节点硬件ID为USB\VID_v(4)&PID_p(4)&MI_z(2),各个字段含义如下所示: