一,在标准的USB请求命令中,经常会看到描述符(Descriptor),这是什么来的呢?
Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。它的作用就是通过命令来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作,所以描述符也是十分重要的部分,要好好掌握。
USB描述符类似USB外围设备的"身份证"一样,详细地记录着外围设备相关的一切信息(设备类型、厂商的ID和产品的ID、端点情况、本版本号等)。为了描述不同的数据,需要以不同的数据类型的USB描述符加以描述,共有11种类型,每种描述符的第一个字节描述该描述符包含的字节数目,第二个字节描述该描述符的类型。
USB1.1协议定义的标准描述符是:设备描述符,配置描述符、接口描述符、端点描述符和字符串描述符。
1.1 设备描述符
设备描述符具有18字节的长度,并且是主机向设备请求的第一个描述符。包含的信息有:设备所使用的USB协议版本号,设备类型、端点0的最大包大小、厂商ID(VID)和产品ID(PID)、设备版本号、厂商字符索引、产品字符索引、设备序列号索引、可能的配置数等。以下列出设备描述符的范围、数值以及各个字段的意义:
struct _DEVICE_DEscriptOR_STRUCT{BYTE bLength; //设备描述符的字节数大小,为0x12BYTE bDescriptorType; //描述符类型编号,为0x01WORD bcdUSB; //USB版本号BYTE bDeviceClass; //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型//0x00不是在设备描述符中定义的,如HIDBYTE bDeviceSubClass; //usb分配的子类代码,同上,值由USB规定和分配的BYTE bDeviceProtocl; //USB分配的设备协议代码,同上BYTE bMaxPacketSize0; //端点0的最大包的大小WORD idVendor; //厂商编号WORD idProduct; //产品编号WORD bcdDevice; //设备出厂编号BYTE iManufacturer; //描述厂商字符串的索引BYTE iProduct; //描述产品字符串的索引BYTE iSerialNumber; //描述设备序列号字符串的索引BYTE bNumConfiguration; //可能的配置数量
}
bLength: 表示描述符的长度,对于设备描述符来说,其值为18,即0x12。
bDescriptorType: 描述符类型,对应表1中的值,设备描述符为0x01。
bcdUSB:该设备遵循的USB版本号,以BCD码表示,USB1.1为0x0101,USB2.0为0x0200。
bDeviceClass:该设备所属的标准设备类,USB协议中对常见的设备进行了分类。该字段值为0x01~0xFE时,表示是USB协议中已定义的设备类,常用的HID设备类编号为0x03,其它设备类编号参:http://www.usb.org/developers/defined_class
bDeviceProtocol:用于表示USB设备类所采用的设备类协议,其值和bDeviceClass和bDeviceSubClass有关。当此 字段为0时,表示不使用任何设备类协议。如果该USB设备属于某个设备类和设备子类,则应该继续指明所采用的设备类协议。当该字段为0xFF时,表明设备 类协议由供应商自定义。
bMaxPacketSize0:用于表示在USB设备中,端点0所支持最大数据包的长度,它以字节为单位。对于低速USB设 备,bMaxPacketSize0为8;对于全速USB设备,bMaxPacketSize0为8、16、32、64;对于高速USB设 备,bMaxPacketSize0为64。
IdVendor:用于表示USB设备供应商的ID。USB组织中规定每种产品都必须包含一个供应商ID,这样可以使主机加载合适的驱动程序。
idProduct:用于表示USB产品的ID,由设备供应商提供。idProduct用于表示特定的USB设备,在USB设备上电的时候可以帮助USB主机选择合适的驱动程序。
bcdDevice:用于表示USB设备的版本号,它以BCD码的形式表示。一般来说bcdDevcie由设备供应商指定,在USB设备上电的时候可以帮助USB主机选择合适的驱动程序。
iManufacturer:用于表示供应商字符串描述符的索引值。具体字符串的内容在后面字符串描述符中定义。如果没有供应商字符串,可以置0。
iSerialNumber:用于表示设备序列号字符串描述符的索引值,如果没有,可以置为0。
bNumConfigurations:用于表示该USB设备所支持的配置数。
1.2 配置描述符
配置描述符具有9字节长度,针对设备给予配置的信息,包括配置所包含的接口数、配置的编号、供电方式、是否支持远程唤醒、电流需求量等。以下列出配置描述符的范例、数值以及各个字段的意义。
struct _CONFIGURATION_DEscriptOR_STRUCT{BYTE bLength; //配置描述符的长度,固定为9个字节BYTE bDescriptorType; //描述符类型编号,为0x02WORD wTotalLength; //配置所返回的所有数量的大小BYTE bNumInterface; //此配置所支持的接口数量BYTE bConfigurationVale; //Set_Configuration命令需要的参数值BYTE iConfiguration; //描述该配置的字符串的索引值BYTE bmAttribute; //供电模式的选择BYTE MaxPower; //设备从总线提取的最大电流}
bLength:用于表示配置描述符的长度,固定为9个字节,即0x09。
bDescriptorType:用于表示配置描述符的类型值,固定为0x02。
wTotalLength:用于表示配置信息的总长度,包括配置描述符、接口描述符、端点描述符长度的总和。
bNumInterfaces:用于表示配置所支持的接口数。一般来说,USB设备的接口至少有一个,因此其最小值为1。
bConfigurationValue:用于表示USB设备的配置值。
iConfiguration:用于指出配置字符串描述符的索引值。具体字符串的内容在后面字符串描述符中定义。如果没有配置字符串,可以置为0。
bmAttributes:用于表示USB设备特性。bmAttributes是接位寻址的,第6位置1表示使用总线电源;第5位置1表示支持远程唤醒功能;该字段其他位均保留,一般来说,第0~4位置0即可,第7位置1即可。
bMaxPower:用于表示USB设备运行时所需要消耗的总线电流,单位以2mA为基准。USB设备可以从USB总线上获得最大的电流为500mA,因此bMaxPower字段的最大值可以设置为250。
1.3 字符描述符
struct _STRING_DEscriptOR_STRUCT{BYTE bLength; //设备描述符的字节数大小,为0x12BYTE bDescriptorType; //描述符类型编号,为0x03BYTE SomeDescriptor[36]; //UNICODE编码的字符串}
1.4 接口描述符
接口描述符具有9字节的长度,用来描述每个设备的接口特性,包括接口的编号、接口的端点数、接口所使用的类、子类、协议等。以下列出配置描述符的范例、数值以及各个字段的意义。
struct _INTERFACE_DEscriptOR_STRUCT{BYTE bLength; //设备描述符的字节数大小,为9BYTE bDescriptorType; //描述符类型编号,为0x04BYTE bInterfaceNunber; //接口的编号BYTE bAlternateSetting;//备用的接口描述符编号BYTE bNumEndpoints; //该接口使用端点数,不包括端点0BYTE bInterfaceClass; //接口类型BYTE bInterfaceSubClass;//接口子类型BYTE bInterfaceProtocol;//接口所遵循的协议BYTE iInterface; //描述该接口的字符串索引值}
1.5 端点描述符
端点描述符具有7字节的长度,用来描述端点的属性以及各个端点的位置,有端点号及方向、端点的传输类型、最大包长度、查询时间间隔等。该实例中有两个端点,首先介绍端点的描述符:
struct _ENDPOIN_DEscriptOR_STRUCT{BYTE bLength; //设备描述符的字节数大小,为7BYTE bDescriptorType; //描述符类型编号,为0x05BYTE bEndpointAddress; //端点地址及输入输出属性BYTE bmAttribute; //端点的传输类型属性WORD wMaxPacketSize; //端点收、发的最大包的大小BYTE bInterval; //主机查询端点的时间间隔}
bLength:用于表示端点描述符的长度,固定为7字节,即0x07。
bDescriptorType:用于表示接口描述符的类型值,固定为0x05。
bEndpointAddress:用于表示端点的端点号以及端点的数据传输方向。第七位表示端点的数据传输方向,0表示OUT数据传输,1表示IN数据传输;第0~位表示端点号,例如001B表示端点1、010B表示端点2;其余位均保留,必须置0。
bmAttributes:用于表示端点的特性。其中第0位和第1位表示端点的数据传输类型,00B表示控制传输、01B表示同步传输、10B表示块传 输、11B表示中断传输;如果是同步传输,第2位和第3位表示同步类型,00B表示非同步、01B表示异步、10B表示自适应、11B表示同步;第4、5 位表示端点的用法类型,00B表示数据端点、01B表示显示反馈端点、10B表示隐匿反馈端点、11B保留。其余位保留。
wMaxPacketSize:用于表示端点所支持最大数据包的长度。其中第0~10位表示数据包的长度,第11位和12位指出每小帧最多传输的事务数,其余位均保留,必须置0。
bInterval:用于指定端点数据传输的访问间隔。低速中断端点,取值范围为10~255,对应的访问间隔为10~255ms;对于全速中断端点,取值范围为1~255,对应的访问间隔为1~255ms;对于其他端点,可以参阅USB相关协议。
1.6 端点类型和传输类型的关系
一个具体的端点,只能工作在一种传输模式下。通常,我们把工作在什么模式下的端点就叫做什么端点,如控制端点、批量端点等。
端点0是每个USB设备都必须具备的默认控制端点,它一上电就存在并且可用。设备的各种描述符以及主机发送的一些命令,都是通过端点0传输的。其他端点都是可选的,需要通过具体设备来决定。非0端点只有在Set Config之后才能使用
1.7 传输类型和端点支持的最大包长
每个端点描述符都规定了端点支持的最大数据包长,主机每次发送数据包,都不能超过端点的最大包长。
控制传输的端点,低速模式最大包长固定为8字节,高速模式最大包长固定为64字节,而全速模式在8,16,32,64中选择。
等时传输的端点,全速模式最大包长的上限为1023字节,高速模式最大包长上限是1024字节,低速模式不支持等时传输。
中断传输的端点,低速模式最大包长的上限为8字节,全速模式最大包长上限是64字节,高速模式最大包长上限是1024字节。
批量传输的端点,高速模式固定为512字节,全速模式最大包长可在8、16、32、64字节中选择,低速模式不支持批量传输。
二 USB标准请求
2.1 USB标准请求的数据结构
USB设备请求命令 :bmRequestType + bRequest + wValue + wIndex + wLength
USB协议定义了一个8字节的标准设备请求,主要用在设备的枚举过程中。这8个字节的数据使用在控制传输的过程中通过默认的控制端点0发出的。在这8字节的数据中,包含了数据过程所需要的传输数据的方向、长度以及数据类型等信息。正式由于8字节的标准请求的原因,USB协议规定,端点0的最大包长度至少为8字节,就是说,任何一个USB设备都能够(而且必须)接收8字节的标准请求。
注意:wValue、wIndex、wLength这三个域都是两字节的。在USB协议中用的是小端结构,即低字节在先,高字节在后。
这里只介绍了USB协议定义的标准请求,即bmRequestType的D6~5位位00的标准请求。
获取描述符请求,设置端点地址,设置配置请求:
1.2 USB设备请求
取得描述符(Get Descriptor)
该请求是在枚举过程中使用最多的一个请求,主机通过发送获得描述符请求读取设备的各种描述符,从而获得设备类型,端点情况等众多重要信息。获得描述符的接收者只能是设备,从bmRequestType的第7位可以看出,它是请求数据输入的。
该请求可以取得USB设备中存在的特定的描述符,其格式如下:
该请求的wValue的高字节表示要取的描述字符类型,低字节表示描述符的索引值,描述的类型有:1=设备描述符;2=配置描述符;3=字符串描述符;4=接口描述符;5=端点描述符。
wIndex的值为0或语言ID,当要取得的描述符是字符描述符时,该域的值为语言ID;当为其他的描述符时,该域为0。
wLength表示要返回的数据长度,如果SETUP阶段的地址使用的是预设地址0(ENDP字段0),这时的wLength值会大于实际描述的值(这是因为用户以预设地址0来取得设备描述符时,不管多少字节,用户最多只能取得前8字节,即在控制传输过程中只有一次数据阶段)。
对于全速模式和低速模式,获取描述符的标准请求只有三种:获取设备描述符、获取配置描述符、获取字符串描述符。另外的接口描述符和端点描述符是跟配置描述符一并返回的,不能单独请求返回(如果单独返回,主机无法确认他们属于哪个配置)。
设置地址(Set Address)
该请求给USB设备设置地址,从而可以对USB设备进行进一步的访问。每个连接在同一主控器上的USB设备都需要有一个唯一的设备地址,这样主机才能区分每个不同的设备。当设备复位后,都是用默认的地址0,主机从地址位0的设备获取设备描述符,一旦收到第一次设备描述符之后,主机就会发送设置地址请求,以尽量减少设备使用公共地址0的时间。设置地址请求是没有数据的,因此wLength的值为0,wIndex用不到,值为0。当设备收到设置地址请求后,就直接进入状态过程,等待主机读取0长度的状态数据包。主机成功读取到状态数据包(用ACK响应设备),设备将启用新的地址。格式如下:
该请求与其他请求有一个重要的不同,该请求下,USB设备一直不改变它的地址,直到该请求的状态阶段被成功的完成,而其他请求的操作都是在状态阶段之前完成的。若特定的设备地址大于127,或者wIndex或我Length为非0值,那么该请求不执行。
设置配置(SET_CONFIGURATION)
该请求中的wValue域的低字节表示设置的值,该值必须为0或者域配置描述符中的配置值相匹配。如果与配置描述符中的配置编号一致时,表示选中该配置。该值通常为1,因为大多数USB设备只有一种配置,配置编号为1,如果设置值等于0,表示设备在地址状态。设备只有收到非0的配置后,才能启动它的非0端点。如果Index或wLength为非0值,那么该请求不执行。
清除特性(Clear Feature)
该请求是用来取消一个特性,其格式如下:
该请求的wValue表示特性选择器,它对应的值为:0=端点,1=设备。当某个特点不允许取消,或该特性根本不存在,或者指向一个根本不存在的端点或者接口时,该请求将会导致设备请求失败。如果端点被固件设为停止状态,主机软件(总线驱动程序)也可以发送一个值为0的CLEAR_FEATURE命令清除该端点的停止状态。
1.3 设备描述符的返回
当主机由端点0向设备发送设备描述符请求时,设备将通过控制输入端0来返回。在端点0的输出中断处理函数中,设备先对接收到的建立过程的数据进行判断,如果是获取设备设备描述符的请求时,那么将设备描述符数组内容写入到端点0输入缓冲区中,并使能端点发送。当下一次主机发送IN令牌后,将自动将端点0的输入缓冲区内的数据返回给主机,首先获取设备描述符的请求。
三 介绍usb消息格式
3.1 usb_control_msg
之所以介绍这个函数,是因为usb使用这个函数进行消息的收发,后面源码解读会大量涉及到这个函数。该函书定义在drivers/usb/core/message.c:
/* returns status (negative) or length (positive) */
static int usb_internal_control_msg(struct usb_device *usb_dev,unsigned int pipe,struct usb_ctrlrequest *cmd,void *data, int len, int timeout)
{struct urb *urb;int retv;int length;urb = usb_alloc_urb(0, GFP_NOIO);if (!urb)return -ENOMEM;usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,len, usb_api_blocking_completion, NULL);retv = usb_start_wait_urb(urb, timeout, &length);if (retv < 0)return retv;elsereturn length;
}int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,__u8 requesttype, __u16 value, __u16 index, void *data,__u16 size, int timeout)
{struct usb_ctrlrequest *dr;int ret;dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO);if (!dr)return -ENOMEM;dr->bRequestType = requesttype;dr->bRequest = request;dr->wValue = cpu_to_le16(value);dr->wIndex = cpu_to_le16(index);dr->wLength = cpu_to_le16(size);ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);/* Linger a bit, prior to the next control message. */if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG)msleep(200);kfree(dr);return ret;
}
函数参数如下:
dev:参数dev指向目标设备的usb_device数据结构;
pipe:pipe是一个32位无符号正数,其最高两位表示传输的类型(实时、中断、控制、批量),端点号、设备号,以及数据传输方式;
request:usb消息的请求值;其实就是命令,所有的命令都是以不同编码值的方式传递给设备的,bRequest就表示usb命令的编码值。可以是usb标准命令,也可以用户自定义命令:
requesttype:usb消息请求类型值,其最高位表示传输的方向,最低5位则表明传输对象的类别(设备、接口、端口、其他);
value:usb消息的值;2个字节,不同的usb命令其有不同的含义;
index:usb消息的索引值;2个字节,不同的usb命令其有不同的含义;它用于向设备传递特定于请求的参数。wIndex字段通常用于在请求中指定端点或接口,格式如下:
data:指向要发送的消息指针;
size:要发送的消息的字节长度;一般不论是输入还是输出都要求给出准确的数字。当命令不需要传输数据时,此字段设为0;
timeout:超时时间;
这个函数用于发送一个简单的控制信息到一个指定的端点并等待信息传输完成或者传输超时,如果成功,返回已经传输成功的字节数,否则返回一个负的错误值。
以获取描述符标准命令举例,我们参考usb spec2.0手册 9.4章节:
wValue的高字节表示描述符类型,低字节表示描述符的索引,描述符索引用于在一个设备中实现多个相同类型的描述符时选择一个特定的描述符(仅用于配置和字符描述符),从0开始。
100:表示获取设备描述符usb_device_descriptor;
200:表示获取配置描述符usb_config_descriptor;
300:表示获取字符描述符;
301:iSerialNumber;
302:iProduct;
303:iManufacturer;
当mValue为字符描述符时,wIndex指定为语言ID,否者设置为零;
wLength字段指定返回的字节数。如果描述符比wLength字段长,则只返回描述符的初始字节。如果描述符短于wLength字段,当请求进一步数据时,设备通过发送一个短数据包来表示控制传输的结束。短报文定义为小于最大负载大小的报文或零长度的数据包;
在获取描述符的命令中,我们可以发现在请求参数中并没有指定目标usb设备的端口号和设备地址,这主要由于此时还未对新设备分配新的设备地址,此时只能通过控制端点0进行数据的收发。
3.2 get_hub_descriptor
/* USB 2.0 spec Section 11.24.4.5 */
static int get_hub_descriptor(struct usb_device *hdev,struct usb_hub_descriptor *desc)
{int i, ret, size;unsigned dtype;if (hub_is_superspeed(hdev)) {dtype = USB_DT_SS_HUB;size = USB_DT_SS_HUB_SIZE;} else {dtype = USB_DT_HUB;size = sizeof(struct usb_hub_descriptor);}for (i = 0; i < 3; i++) {ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,dtype << 8, 0, desc, size,USB_CTRL_GET_TIMEOUT);if (hub_is_superspeed(hdev)) {if (ret == size)return ret;} else if (ret >= USB_DT_HUB_NONVAR_SIZE + 2) {/* Make sure we have the DeviceRemovable field. */size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1;if (ret < size)return -EMSGSIZE;return ret;}}return -EINVAL;
}
我们来看一下usb_control_msg这个函数的参数:
dev:指向了为根hub申请的struct usb_device结构;
pipe:usb_rcvctrlpipe(hdev, 0),创建一个接受接收(rcv)控制类型的端点管道(pipe);
request:USB_REQ_GET_DESCRIPTOR,请求值wRequest设置为GET_DESCRIPTOR,值为0x06;
requestType:USB_DIR_IN | USB_RT_HUB,请求类型值wRequestType设置为0xA0,是Get Hub Descriptor的请求类型(需要注意的是Get Hub Descriptor和Get Descriptor是完全不一样的请求类型),方向为输入,即从usb设备到主机;具体可以参考usb 2.0 spec 11.24.2.5;
#define USB_DIR_IN 0x80 /* to host */
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE) //0x10
#define USB_TYPE_CLASS (0x01 << 5)
#define USB_RECIP_DEVICE 0x00
#define USB_REQ_GET_DESCRIPTOR 0x06
value:dtype << 8,dtype = USB_DT_HUB,USB_DT_HUB的值为0x29,所以wValue被设置为0x2900;wValue高字节表示描述符类型,低字节表示描述符索引,所以描述符类型是0x29,描述符索引是0x00;
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09)
#define USB_TYPE_CLASS (0x01 << 5)
index:wIndex设置为0;
data: 将获取到的描述符信息保存到desc;
size:表示usb hub描述符的长度;