Linux USB Test Mode

在USB2.0 spec的7.1.20章节规定了USB必须支持以下几种模式的Test Mode。本文将结合USB2.0 spec来实现Linux下的Test Mode做USB认证使用。


7.1.20 Test Mode Support

为了便于符合性测试,主机控制器,集线器和高速功能必须支持以下测试模式:

  • Test_SE0_NAK

    Test mode Test_SE0_NAK: Upon command, a port transceiver must enter the high-speed receive modeand remain in that mode until the exit action is taken. This enables the testing of output impedance, lowlevel output voltage, and loading characteristics.
    In addition, while in this mode, upstream facing ports (andonly upstream facing ports) must respond to any IN token packet with a NAK handshake (only if the packet CRC is determined to be correct) within the normal allowed device response time. This enables testing ofthe device squelch level circuitry and, additionally, provides a general purpose stimulus/response test for basic functional testing.

  • Test_J

    Test mode Test_J: Upon command, a port’s transceiver must enter the high-speed J state and remain in thatstate until the exit action is taken. This enables the testing of the high output drive level on the D+ line.

  • Test_K

    Test mode Test_K: Upon command, a port’s transceiver must enter the high-speed K state and remain inthat state until the exit action is taken. This enables the testing of the high output drive level on the D- line.

  • Test_Packet

    Test mode Test_Packet: Upon command, a port must repetitively transmit the following test packet untilthe exit action is taken. This enables the testing of rise and fall times, eye patterns, jitter, and any other dynamic waveform specifications.

  • Test_Force_Enable

    Test mode Test_Force_Enable: Upon command, downstream facing hub ports (and only downstreamfacing hub ports) must be enabled in high-speed mode, even if there is no device attached. Packets arriving at the hub’s upstream facing port must be repeated on the port which is in this test mode.
    This enablestesting of the hub’s disconnect detection; the disconnect detect bit can be polled while varying the loadingon the port, allowing the disconnect detection threshold voltage to be measured.


Test Mode的进入和退出

使用设备标准请求SetFeature(TEST_MODE),向上端口,该定义在9.4.9章节或者使用hub类请求SetPortFeature(PORT_TEST),向下端口,该定义在11.24.2.13章节。


SetPortFeature(PORT_TEST)

11.24.2.13 Set Port Feature章节详细讲述了通过Set Port Feature进入Test Mode的方法,现摘录如下:
linux usb test mode-1
linux usb test mode-2
linux usb test mode-3


Linux USB Test Mode测试接口

将下面的代码添加到<Kernel_Dir>/drivers/usb/core/sysfs.c文件中,在sysfs中新增一个port_testmode节点用于Test Mode使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "usb.h"

/* use a short timeout for hub/port status fetches */
#define USB_STS_TIMEOUT 1000
#define USB_STS_RETRIES 5

/*
* USB 2.0 spec Section 11.24.2.2
*/
static int clear_port_feature(struct usb_device *hdev, int port1, int feature)
{
return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1,
NULL, 0, 1000);
}

/*
* USB 2.0 spec Section 11.24.2.13
*/
static int set_port_feature(struct usb_device *hdev, int port1, int feature)
{
return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1,
NULL, 0, 1000);
}

/*
* USB 2.0 spec Section 11.24.2.7
*/
static int get_port_status(struct usb_device *hdev, int port1,
struct usb_port_status *data)
{
int i, status = -ETIMEDOUT;

for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) {
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
data, sizeof(*data), USB_STS_TIMEOUT);
}
return status;
}

/* Output Format: PortNum USBTest
PortNum: 1~maxchild
USBTest: 0 = This port is not in the Port Test Mode.
1 = This port is in Port Test Mode.
*/
static ssize_t
show_port_usbtest(struct device *dev, struct device_attribute *attr, char *buf)
{
struct usb_device *udev = to_usb_device(dev);
int i, len = 0;

usb_lock_device(udev);
for (i = 1; i <= udev->maxchild; ++i) {
struct usb_port_status ps;
if (sizeof(ps) == get_port_status(udev, i, &ps)) {
len += sprintf(buf + len, "%d %d\n", i, (le16_to_cpu(ps.wPortStatus) & 0x800) ? 1 : 0);
}
}
usb_unlock_device(udev);

return len;
}

/* Input Format: PortNum USBTest
PortNum: 1~maxchild
USBTest: 0 - Disable Test Mode
1 - Test_J
2 - Test_K
3 - Test_SE0_NAK
4 - Test_Packet
5 - Test_Force_Enable
*/
static ssize_t
set_port_usbtest(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_device *udev = to_usb_device(dev);
int port, value;
int ret;
int i;

if (sscanf(buf, "%d %d", &port, &value) != 2)
return -EINVAL;

if (port < 1 || port > udev->maxchild || value < 0)
return -EINVAL;

usb_lock_device(udev);

/* Disable usb device autosuspend function */
ret = usb_autoresume_device(udev);
if (ret < 0) {
goto end;
}
if (udev->parent){
ret = usb_reset_device(udev);
if (ret < 0) {
goto end;
}
}

if (value >= 0) {
#if 0
/* Suspend all downstream ports */
for (i = 1; i <= udev->maxchild; ++i) {
ret = set_port_feature(udev, i, USB_PORT_FEAT_SUSPEND);
if (ret < 0) {
goto end2;
}
}
#endif
ret = set_port_feature(udev, (value << 8) | port, USB_PORT_FEAT_TEST);
if (ret < 0) {
goto end2;
}
ret = count;
goto end;
} else {
ret = count;
goto end;
}

end:
usb_unlock_device(udev);
return ret;
end2:
/* Resume all downstream ports */
for (i = 1; i <= udev->maxchild; ++i) {
clear_port_feature(udev, i, USB_PORT_FEAT_SUSPEND);
}
}

static DEVICE_ATTR(port_usbtest, S_IRUGO | S_IWUSR, show_port_usbtest, set_port_usbtest);

static int add_port_usbtest_attributes(struct device *dev)
{
int rc = 0;

if (is_usb_device(dev)) {
struct usb_device *udev = to_usb_device(dev);

/* Presently only support hub downstream ports' testmode */
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
rc = sysfs_create_file(&dev->kobj,
&dev_attr_port_usbtest.attr);
}
return rc;
}

static void remove_port_usbtest_attributes(struct device *dev)
{
sysfs_remove_file(&dev->kobj,
&dev_attr_port_usbtest.attr);
}


int usb_create_sysfs_dev_files(struct usb_device *udev)
{
struct device *dev = &udev->dev;
int retval;

// ......

retval = add_port_usbtest_attributes(dev);
if (retval)
goto error;

return retval;
error:
usb_remove_sysfs_dev_files(udev);
return retval;
}

void usb_remove_sysfs_dev_files(struct usb_device *udev)
{
struct device *dev = &udev->dev;

remove_port_usbtest_attributes(dev);
// ......
}

NXP提供了i.MX6DQ的USB Test Mode代码,在https://community.nxp.com/docs/DOC-94460 网站上有详细的记录。里面的patch可以参考一下,整合到自己的项目中。

Title:Linux USB Test Mode

Author:Victor Huang

Time:2019-03-17 / 16:03

Link:http://wowothink.com/488cc6d0/

License: Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)