USB设备无响应

Device No Response 测试介绍

在USB认证中,otgeh_compliance_plan_1_2.pdf 测试文档有一项关于连接超时显示Device No Response的测试。
描述如下:
USB设备无响应_第1张图片

测试步骤如下:
USB设备无响应_第2张图片

USB设备无响应_第3张图片

说白了,就是说连接一个无法识别的USB设备到Embedded Host上,判断其能否在30s内给出“Device No Response”的提示,如果有,那么测试pass,如果没有或者超过30s给出“Device No Response”的提示,那么这个测试项是NG的。


术语:

  • A-UUT:Unit Under Test with a Micro-A plug attached.(待认证的设备)
  • PET:Protocol and Electrical Tester. A test unit which is capable of performing the tests specified in Section 6.(测试治具)
  • SRP:Session Request Protocol (see Section [USBOTG&EHv2.0]).(开始枚举)

内核打印

在做这个测试,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的。


USB Host尝试枚举次数

从上面的内核log来看,总共尝试枚举4次,每次枚举尝试读取2次设备描述符信息,但是读取失败,报告的错误信息是-100,也就是timeout。

枚举过程是在 /drivers/usb/core/hub.c 中的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)

USB Host尝试获取描述符次数

/drivers/usb/core/hub.c 中的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跟每次尝试获取描述符的间隔时间差不多一致。


解决NG项

根据上述的分析,为了在30s之内检测到设备无响应,我们可以尝试减小
SET_CONFIG_TRIESGET_DESCRIPTOR_TRIESUSB_CTRL_GET_TIMEOUT 这三个参数。保证在规定的时间内检测到设备无响应。


模拟USB设备无响应

为了模拟出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 驱动常见错误返回值

你可能感兴趣的:(USB)