在USB认证中,otgeh_compliance_plan_1_2.pdf
测试文档有一项关于连接超时显示Device No Response的测试。
描述如下:
说白了,就是说连接一个无法识别的USB设备到Embedded Host上,判断其能否在30s内给出“Device No Response”的提示,如果有,那么测试pass,如果没有或者超过30s给出“Device No Response”的提示,那么这个测试项是NG的。
在做这个测试,USB认证实验室的工程师会专门拿一个特制的USB设备,这个设备是无法正常响应USB Host发起的USB_REQ_GET_DESCRIPTOR
命令,会出现超时的情况,以此来判定待测的Embedded Host是否能在30s之内给出“Device No Response”的提示。连接这个特制的USB设备,内核的打印如下:
[ 32.809100] usb 2-1: new high-speed USB device number 2 using xxx-ehci
[ 36.429085] usb 2-1: device descriptor read/64, error -110
[ 40.149029] usb 2-1: device descriptor read/64, error -110
[ 40.369041] usb 2-1: new high-speed USB device number 3 using xxx-ehci
[ 43.985098] usb 2-1: device descriptor read/64, error -110
[ 47.705018] usb 2-1: device descriptor read/64, error -110
[ 47.925103] usb 2-1: new high-speed USB device number 4 using xxx-ehci
[ 52.949071] usb 2-1: device descriptor read/8, error -110
[ 58.073024] usb 2-1: device descriptor read/8, error -110
[ 58.293048] usb 2-1: new high-speed USB device number 5 using xxx-ehci
[ 63.317040] usb 2-1: device descriptor read/8, error -110
[ 68.441045] usb 2-1: device descriptor read/8, error -110
[ 68.549040] hub 2-0:1.0: unable to enumerate USB device on port 1
根据内核打印,我们可以知道:
这次枚举总共用时36s,超过了30s的限定时间,所以这个测试项肯定是NG的。
从上面的内核log来看,总共尝试枚举4次,每次枚举尝试读取2次设备描述符信息,但是读取失败,报告的错误信息是-100,也就是timeout。
枚举过程是在
中的hub_port_connect_change()
函数进行的,宏 SET_CONFIG_TRIES
决定枚举的次数,宏 GET_DESCRIPTOR_TRIES
决定获取描述符的次数。
static bool use_both_schemes = 1;
module_param(use_both_schemes, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(use_both_schemes,
"try the other device initialization scheme if the "
"first one fails");
#define PORT_RESET_TRIES 5
#define SET_ADDRESS_TRIES 2
#define GET_DESCRIPTOR_TRIES 2
#define SET_CONFIG_TRIES (2 * (use_both_schemes + 1))
#define USE_NEW_SCHEME(i) ((i) / 2 == (int)old_scheme_first)
在
中的hub_port_init()
函数会去获取描述符。
/* Reset device, (re)assign address, get device descriptor.
* Device connection must be stable, no more debouncing needed.
* Returns device in USB_STATE_ADDRESS, except on error.
*
* If this is called for an already-existing device (as part of
* usb_reset_and_verify_device), the caller must own the device lock. For a
* newly detected device that is not accessible through any global
* pointers, it's not necessary to lock the device.
*/
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
//……
for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { // 获取描述符
//……
retval = usb_get_device_descriptor(udev, 8);
if (retval < 8) {
if (retval != -ENODEV)
dev_err(&udev->dev,
"device descriptor read/8, error %d\n",
retval);
if (retval >= 0)
retval = -EMSGSIZE;
} else {
retval = 0;
break;
}
//……
}
//……
}
// /drivers/usb/core/message.c
/**
* usb_get_descriptor - issues a generic GET_DESCRIPTOR request
* @dev: the device whose descriptor is being retrieved
* @type: the descriptor type (USB_DT_*)
* @index: the number of the descriptor
* @buf: where to put the descriptor
* @size: how big is "buf"?
* Context: !in_interrupt ()
*
* Gets a USB descriptor. Convenience functions exist to simplify
* getting some types of descriptors. Use
* usb_get_string() or usb_string() for USB_DT_STRING.
* Device (USB_DT_DEVICE) and configuration descriptors (USB_DT_CONFIG)
* are part of the device structure.
* In addition to a number of USB-standard descriptors, some
* devices also use class-specific or vendor-specific descriptors.
*
* This call is synchronous, and may not be used in an interrupt context.
*
* Return: The number of bytes received on success, or else the status code
* returned by the underlying usb_control_msg() call.
*/
int usb_get_descriptor(struct usb_device *dev, unsigned char type,
unsigned char index, void *buf, int size)
{
int i;
int result;
memset(buf, 0, size); /* Make sure we parse really received data */
for (i = 0; i < 3; ++i) {
/* retry on length 0 or error; some devices are flakey */
result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
(type << 8) + index, 0, buf, size,
USB_CTRL_GET_TIMEOUT);
if (result <= 0 && result != -ETIMEDOUT)
continue;
if (result > 1 && ((u8 *)buf)[1] != type) {
result = -ENODATA;
continue;
}
break;
}
return result;
}
/*
* usb_get_device_descriptor - (re)reads the device descriptor (usbcore)
* @dev: the device whose device descriptor is being updated
* @size: how much of the descriptor to read
* Context: !in_interrupt ()
*
* Updates the copy of the device descriptor stored in the device structure,
* which dedicates space for this purpose.
*
* Not exported, only for use by the core. If drivers really want to read
* the device descriptor directly, they can call usb_get_descriptor() with
* type = USB_DT_DEVICE and index = 0.
*
* This call is synchronous, and may not be used in an interrupt context.
*
* Return: The number of bytes received on success, or else the status code
* returned by the underlying usb_control_msg() call.
*/
int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
struct usb_device_descriptor *desc;
int ret;
if (size > sizeof(*desc))
return -EINVAL;
desc = kmalloc(sizeof(*desc), GFP_NOIO);
if (!desc)
return -ENOMEM;
ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size);
if (ret >= 0)
memcpy(&dev->descriptor, desc, size);
kfree(desc);
return ret;
}
在 usb_get_descriptor()
函数中通过 usb_control_msg()
发送获取设备描述符命令 USB_REQ_GET_DESCRIPTOR
,设定timeout时间为USB_CTRL_GET_TIMEOUT(5s)
,如果发生timeout,返回-ETIMEDOUT
的错误。
// /include/linux/usb.h
/*
* timeouts, in milliseconds, used for sending/receiving control messages
* they typically complete within a few frames (msec) after they're issued
* USB identifies 5 second timeouts, maybe more in a few cases, and a few
* slow devices (like some MGE Ellipse UPSes) actually push that limit.
*/
#define USB_CTRL_GET_TIMEOUT 5000
#define USB_CTRL_SET_TIMEOUT 5000
这里的timeout跟每次尝试获取描述符的间隔时间差不多一致。
根据上述的分析,为了在30s之内检测到设备无响应,我们可以尝试减小
SET_CONFIG_TRIES
、GET_DESCRIPTOR_TRIES
、USB_CTRL_GET_TIMEOUT
这三个参数。保证在规定的时间内检测到设备无响应。
为了模拟出USB设备无响应的问题,可以拿一条Android手机充电线,剪掉Micro A的那口,留下Type A的公头。将暴露出来的D+(绿色)信号与VCC和GND构成一个5V的上拉。做出来的“USB设备”插入USB Host中就会枚举失败,出现设备无响应的提示。对应的内核打印如下:
[ 1552.533095] usb 2-1: new full-speed USB device number 2 using xxx-ehci
[ 1552.745029] usb 2-1: device descriptor read/64, error -71
[ 1553.061115] usb 2-1: device descriptor read/64, error -71
[ 1553.281032] usb 2-1: new full-speed USB device number 3 using xxx-ehci
[ 1553.493081] usb 2-1: device descriptor read/64, error -71
[ 1553.809078] usb 2-1: device descriptor read/64, error -71
[ 1554.029027] usb 2-1: new full-speed USB device number 4 using xxx-ehci
[ 1554.504983] usb 2-1: device not accepting address 4, error -71
[ 1554.621111] usb 2-1: new full-speed USB device number 5 using xxx-ehci
[ 1555.096976] usb 2-1: device not accepting address 5, error -71
[ 1555.103295] hub 2-0:1.0: unable to enumerate USB device on port 1
这里的出错不是-110(Connection timed out),而是-71(Protocol error),也能能快的检测到USB设备无响应的问题。
关于USB设备的Kernel中的枚举过程,可以参照Linux kernel U盘识别流程
关于常见的Linux返回错误,可以参照Linux 驱动常见错误返回值