summaryrefslogtreecommitdiffstats
path: root/drivers/usb/misc/ehset.c
blob: 986d6589f05357a515d9c2a209f378e8e4abf774 (plain)
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
183
184
185
186
187
188
189
190
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/ch11.h>

#define TEST_SE0_NAK_PID			0x0101
#define TEST_J_PID				0x0102
#define TEST_K_PID				0x0103
#define TEST_PACKET_PID				0x0104
#define TEST_HS_HOST_PORT_SUSPEND_RESUME	0x0106
#define TEST_SINGLE_STEP_GET_DEV_DESC		0x0107
#define TEST_SINGLE_STEP_SET_FEATURE		0x0108

extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
						const struct usb_device_id *id);

/*
 * A list of USB hubs which requires to disable the power
 * to the port before starting the testing procedures.
 */
static const struct usb_device_id ehset_hub_list[] = {
	{ USB_DEVICE(0x0424, 0x4502) },
	{ USB_DEVICE(0x0424, 0x4913) },
	{ USB_DEVICE(0x0451, 0x8027) },
	{ }
};

static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum)
{
	int ret = 0;

	/*
	 * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is
	 * going under test needs to be put in suspend before sending the
	 * test command. Most hubs don't enforce this precondition, but there
	 * are some hubs which needs to disable the power to the port before
	 * starting the test.
	 */
	if (usb_device_match_id(hub_udev, ehset_hub_list)) {
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_ENABLE,
					   portnum, NULL, 0, 1000, GFP_KERNEL);
		/*
		 * Wait for the port to be disabled. It's an arbitrary value
		 * which worked every time.
		 */
		msleep(100);
	} else {
		/*
		 * For the hubs which are compliant with the spec,
		 * put the port in SUSPEND.
		 */
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
					   portnum, NULL, 0, 1000, GFP_KERNEL);
	}
	return ret;
}

static int ehset_probe(struct usb_interface *intf,
		       const struct usb_device_id *id)
{
	int ret = -EINVAL;
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_device *hub_udev = dev->parent;
	struct usb_device_descriptor buf;
	u8 portnum = dev->portnum;
	u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);

	switch (test_pid) {
	case TEST_SE0_NAK_PID:
		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
		if (!ret)
			break;
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_TEST,
					   (USB_TEST_SE0_NAK << 8) | portnum,
					   NULL, 0, 1000, GFP_KERNEL);
		break;
	case TEST_J_PID:
		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
		if (!ret)
			break;
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_TEST,
					   (USB_TEST_J << 8) | portnum, NULL, 0,
					   1000, GFP_KERNEL);
		break;
	case TEST_K_PID:
		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
		if (!ret)
			break;
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_TEST,
					   (USB_TEST_K << 8) | portnum, NULL, 0,
					   1000, GFP_KERNEL);
		break;
	case TEST_PACKET_PID:
		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
		if (!ret)
			break;
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_TEST,
					   (USB_TEST_PACKET << 8) | portnum,
					   NULL, 0, 1000, GFP_KERNEL);
		break;
	case TEST_HS_HOST_PORT_SUSPEND_RESUME:
		/* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
		msleep(15 * 1000);
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
					   portnum, NULL, 0, 1000, GFP_KERNEL);
		if (ret < 0)
			break;

		msleep(15 * 1000);
		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
					   portnum, NULL, 0, 1000, GFP_KERNEL);
		break;
	case TEST_SINGLE_STEP_GET_DEV_DESC:
		/* Test: wait for 15secs -> GetDescriptor request */
		msleep(15 * 1000);

		ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
					   USB_DIR_IN, USB_DT_DEVICE << 8, 0,
					   &buf, USB_DT_DEVICE_SIZE,
					   USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
		break;
	case TEST_SINGLE_STEP_SET_FEATURE:
		/*
		 * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS
		 *
		 * Note, this test is only supported on root hubs since the
		 * SetPortFeature handling can only be done inside the HCD's
		 * hub_control callback function.
		 */
		if (hub_udev != dev->bus->root_hub) {
			dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n");
			break;
		}

		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
					   USB_RT_PORT, USB_PORT_FEAT_TEST,
					   (6 << 8) | portnum, NULL, 0,
					   60 * 1000, GFP_KERNEL);

		break;
	default:
		dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
			__func__, test_pid);
	}

	return ret;
}

static void ehset_disconnect(struct usb_interface *intf)
{
}

static const struct usb_device_id ehset_id_table[] = {
	{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
	{ USB_DEVICE(0x1a0a, TEST_J_PID) },
	{ USB_DEVICE(0x1a0a, TEST_K_PID) },
	{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
	{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
	{ }			/* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, ehset_id_table);

static struct usb_driver ehset_driver = {
	.name =		"usb_ehset_test",
	.probe =	ehset_probe,
	.disconnect =	ehset_disconnect,
	.id_table =	ehset_id_table,
};

module_usb_driver(ehset_driver);

MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture");
MODULE_LICENSE("GPL v2");