1e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/*
2e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * f_loopback.c - USB peripheral loopback configuration driver
3e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell *
4e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * Copyright (C) 2003-2008 David Brownell
5e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * Copyright (C) 2008 by Nokia Corporation
6e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell *
7e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * This program is free software; you can redistribute it and/or modify
8e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * it under the terms of the GNU General Public License as published by
9e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * the Free Software Foundation; either version 2 of the License, or
10e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * (at your option) any later version.
11e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell */
12e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
13e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/* #define VERBOSE_DEBUG */
14e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
155a0e3ad6af8660be21ca98a971cd00f331318c05Tejun Heo#include <linux/slab.h>
16e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell#include <linux/kernel.h>
17e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell#include <linux/device.h>
18e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
19e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell#include "g_zero.h"
20e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell#include "gadget_chips.h"
21e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
22e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
23e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/*
24e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * LOOPBACK FUNCTION ... a testing vehicle for USB peripherals,
25e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell *
26e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * This takes messages of various sizes written OUT to a device, and loops
27e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * them back so they can be read IN from it.  It has been used by certain
28e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * test applications.  It supports limited testing of data queueing logic.
29e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell *
30e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell *
31e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * This is currently packaged as a configuration driver, which can't be
32e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * combined with other functions to make composite devices.  However, it
33e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * can be combined with other independent configurations.
34e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell */
35e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstruct f_loopback {
36e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_function	function;
37e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
38e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_ep		*in_ep;
39e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_ep		*out_ep;
40e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
41e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
42e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic inline struct f_loopback *func_to_loop(struct usb_function *f)
43e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
44e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	return container_of(f, struct f_loopback, function);
45e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
46e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
47e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic unsigned qlen = 32;
48e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellmodule_param(qlen, uint, 0);
49e5760fdac8c8aeca060d1afc8a233ea3d7a46720David BrownellMODULE_PARM_DESC(qlenn, "depth of loopback queue");
50e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
51e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/*-------------------------------------------------------------------------*/
52e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
53e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_interface_descriptor loopback_intf = {
54e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bLength =		sizeof loopback_intf,
55e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bDescriptorType =	USB_DT_INTERFACE,
56e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
57e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bNumEndpoints =	2,
58e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
59e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* .iInterface = DYNAMIC */
60e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
61e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
62e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/* full speed support: */
63e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
647e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownellstatic struct usb_endpoint_descriptor fs_loop_source_desc = {
65e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bLength =		USB_DT_ENDPOINT_SIZE,
66e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bDescriptorType =	USB_DT_ENDPOINT,
67e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
68e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bEndpointAddress =	USB_DIR_IN,
69e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
70e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
71e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
727e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownellstatic struct usb_endpoint_descriptor fs_loop_sink_desc = {
73e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bLength =		USB_DT_ENDPOINT_SIZE,
74e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bDescriptorType =	USB_DT_ENDPOINT,
75e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
76e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bEndpointAddress =	USB_DIR_OUT,
77e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
78e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
79e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
80e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_descriptor_header *fs_loopback_descs[] = {
81e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	(struct usb_descriptor_header *) &loopback_intf,
827e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	(struct usb_descriptor_header *) &fs_loop_sink_desc,
837e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	(struct usb_descriptor_header *) &fs_loop_source_desc,
84e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	NULL,
85e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
86e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
87e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/* high speed support: */
88e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
897e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownellstatic struct usb_endpoint_descriptor hs_loop_source_desc = {
90e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bLength =		USB_DT_ENDPOINT_SIZE,
91e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bDescriptorType =	USB_DT_ENDPOINT,
92e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
93e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
94551509d267905705f6d723e51ec706916f06b859Harvey Harrison	.wMaxPacketSize =	cpu_to_le16(512),
95e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
96e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
977e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownellstatic struct usb_endpoint_descriptor hs_loop_sink_desc = {
98e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bLength =		USB_DT_ENDPOINT_SIZE,
99e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bDescriptorType =	USB_DT_ENDPOINT,
100e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
101e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
102551509d267905705f6d723e51ec706916f06b859Harvey Harrison	.wMaxPacketSize =	cpu_to_le16(512),
103e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
104e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
105e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_descriptor_header *hs_loopback_descs[] = {
106e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	(struct usb_descriptor_header *) &loopback_intf,
1077e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	(struct usb_descriptor_header *) &hs_loop_source_desc,
1087e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	(struct usb_descriptor_header *) &hs_loop_sink_desc,
109e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	NULL,
110e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
111e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
11257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay/* super speed support: */
11357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
11457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blaystatic struct usb_endpoint_descriptor ss_loop_source_desc = {
11557c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bLength =		USB_DT_ENDPOINT_SIZE,
11657c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bDescriptorType =	USB_DT_ENDPOINT,
11757c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
11857c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
11957c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.wMaxPacketSize =	cpu_to_le16(1024),
12057c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay};
12157c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
12257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blaystruct usb_ss_ep_comp_descriptor ss_loop_source_comp_desc = {
12357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bLength =		USB_DT_SS_EP_COMP_SIZE,
12457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
12557c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bMaxBurst =		0,
12657c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bmAttributes =		0,
12757c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.wBytesPerInterval =	0,
12857c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay};
12957c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
13057c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blaystatic struct usb_endpoint_descriptor ss_loop_sink_desc = {
13157c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bLength =		USB_DT_ENDPOINT_SIZE,
13257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bDescriptorType =	USB_DT_ENDPOINT,
13357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
13457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
13557c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.wMaxPacketSize =	cpu_to_le16(1024),
13657c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay};
13757c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
13857c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blaystruct usb_ss_ep_comp_descriptor ss_loop_sink_comp_desc = {
13957c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bLength =		USB_DT_SS_EP_COMP_SIZE,
14057c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
14157c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bMaxBurst =		0,
14257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.bmAttributes =		0,
14357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	.wBytesPerInterval =	0,
14457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay};
14557c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
14657c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blaystatic struct usb_descriptor_header *ss_loopback_descs[] = {
14757c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	(struct usb_descriptor_header *) &loopback_intf,
14857c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	(struct usb_descriptor_header *) &ss_loop_source_desc,
14957c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	(struct usb_descriptor_header *) &ss_loop_source_comp_desc,
15057c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	(struct usb_descriptor_header *) &ss_loop_sink_desc,
15157c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	(struct usb_descriptor_header *) &ss_loop_sink_comp_desc,
15257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	NULL,
15357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay};
15457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
155e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/* function-specific strings: */
156e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
157e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_string strings_loopback[] = {
158e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	[0].s = "loop input to output",
159e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	{  }			/* end of list */
160e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
161e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
162e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_gadget_strings stringtab_loop = {
163e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.language	= 0x0409,	/* en-us */
164e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.strings	= strings_loopback,
165e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
166e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
167e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic struct usb_gadget_strings *loopback_strings[] = {
168e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	&stringtab_loop,
169e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	NULL,
170e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
171e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
172e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/*-------------------------------------------------------------------------*/
173e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
174e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic int __init
175e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellloopback_bind(struct usb_configuration *c, struct usb_function *f)
176e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
177e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_composite_dev *cdev = c->cdev;
178e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct f_loopback	*loop = func_to_loop(f);
179e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	int			id;
180e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
181e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* allocate interface ID(s) */
182e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	id = usb_interface_id(c, f);
183e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (id < 0)
184e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return id;
185e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loopback_intf.bInterfaceNumber = id;
186e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
187e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* allocate endpoints */
188e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
1897e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
190e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (!loop->in_ep) {
191e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellautoconf_fail:
192e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		ERROR(cdev, "%s: can't autoconfigure on %s\n",
193e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			f->name, cdev->gadget->name);
194e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return -ENODEV;
195e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
196e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->in_ep->driver_data = cdev;	/* claim */
197e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
1987e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell	loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
199e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (!loop->out_ep)
200e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		goto autoconf_fail;
201e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->out_ep->driver_data = cdev;	/* claim */
202e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
203e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* support high speed hardware */
204e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (gadget_is_dualspeed(c->cdev->gadget)) {
2057e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell		hs_loop_source_desc.bEndpointAddress =
2067e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell				fs_loop_source_desc.bEndpointAddress;
2077e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell		hs_loop_sink_desc.bEndpointAddress =
2087e75bc0f9006e995a0fa25f0a285addc3d5fd5cbDavid Brownell				fs_loop_sink_desc.bEndpointAddress;
209e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		f->hs_descriptors = hs_loopback_descs;
210e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
211e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
21257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	/* support super speed hardware */
21357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	if (gadget_is_superspeed(c->cdev->gadget)) {
21457c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay		ss_loop_source_desc.bEndpointAddress =
21557c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay				fs_loop_source_desc.bEndpointAddress;
21657c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay		ss_loop_sink_desc.bEndpointAddress =
21757c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay				fs_loop_sink_desc.bEndpointAddress;
21857c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay		f->ss_descriptors = ss_loopback_descs;
21957c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	}
22057c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay
221e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
22257c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	    (gadget_is_superspeed(c->cdev->gadget) ? "super" :
22357c97c02de0e7a59cb48d3d7666f4afaf9968e84Amit Blay	     (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
224e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			f->name, loop->in_ep->name, loop->out_ep->name);
225e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	return 0;
226e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
227e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
228e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic void
229e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellloopback_unbind(struct usb_configuration *c, struct usb_function *f)
230e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
231e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	kfree(func_to_loop(f));
232e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
233e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
234e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic void loopback_complete(struct usb_ep *ep, struct usb_request *req)
235e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
236e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct f_loopback	*loop = ep->driver_data;
237e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_composite_dev *cdev = loop->function.config->cdev;
238e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	int			status = req->status;
239e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
240e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	switch (status) {
241e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
242e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	case 0:				/* normal completion? */
243e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		if (ep == loop->out_ep) {
244e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			/* loop this OUT packet back IN to the host */
245e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			req->zero = (req->actual < req->length);
246e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			req->length = req->actual;
247e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC);
248e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			if (status == 0)
249e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell				return;
250e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
251e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			/* "should never get here" */
252e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			ERROR(cdev, "can't loop %s to %s: %d\n",
253e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell				ep->name, loop->in_ep->name,
254e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell				status);
255e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		}
256e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
257e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		/* queue the buffer for some later OUT packet */
258e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		req->length = buflen;
259e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC);
260e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		if (status == 0)
261e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			return;
262e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
263e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		/* "should never get here" */
264e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		/* FALLTHROUGH */
265e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
266e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	default:
267e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
268e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell				status, req->actual, req->length);
269e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		/* FALLTHROUGH */
270e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
271e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* NOTE:  since this driver doesn't maintain an explicit record
272e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 * of requests it submitted (just maintains qlen count), we
273e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 * rely on the hardware driver to clean up on disconnect or
274e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 * endpoint disable.
275e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 */
276e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	case -ECONNABORTED:		/* hardware forced ep reset */
277e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	case -ECONNRESET:		/* request dequeued */
278e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	case -ESHUTDOWN:		/* disconnect from host */
279e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		free_ep_req(ep, req);
280e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return;
281e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
282e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
283e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
284e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic void disable_loopback(struct f_loopback *loop)
285e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
286e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_composite_dev	*cdev;
287e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
288e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	cdev = loop->function.config->cdev;
289e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	disable_endpoints(cdev, loop->in_ep, loop->out_ep);
290e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	VDBG(cdev, "%s disabled\n", loop->function.name);
291e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
292e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
293e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic int
294e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellenable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
295e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
296e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	int					result = 0;
297e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_ep				*ep;
298e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_request			*req;
299e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	unsigned				i;
300e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
301e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* one endpoint writes data back IN to the host */
302e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	ep = loop->in_ep;
303ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman	result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
304ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman	if (result)
305ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman		return result;
30672c973dd2b01b212a159faa330a2bc641a3ed809Tatyana Brokhman	result = usb_ep_enable(ep);
307e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (result < 0)
308e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return result;
309e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	ep->driver_data = loop;
310e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
311e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* one endpoint just reads OUT packets */
312e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	ep = loop->out_ep;
313ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman	result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
314ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman	if (result)
315ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman		goto fail0;
316ea2a1df7b2b1de839a72217d85bfb4b7b049010cTatyana Brokhman
31772c973dd2b01b212a159faa330a2bc641a3ed809Tatyana Brokhman	result = usb_ep_enable(ep);
318e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (result < 0) {
319e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellfail0:
320e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		ep = loop->in_ep;
321e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		usb_ep_disable(ep);
322e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		ep->driver_data = NULL;
323e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return result;
324e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
325e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	ep->driver_data = loop;
326e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
327e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* allocate a bunch of read buffers and queue them all at once.
328e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 * we buffer at most 'qlen' transfers; fewer if any need more
329e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 * than 'buflen' bytes each.
330e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	 */
331e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	for (i = 0; i < qlen && result == 0; i++) {
332e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		req = alloc_ep_req(ep);
333e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		if (req) {
334e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			req->complete = loopback_complete;
335e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			result = usb_ep_queue(ep, req, GFP_ATOMIC);
336e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			if (result)
337e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell				ERROR(cdev, "%s queue req --> %d\n",
338e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell						ep->name, result);
339e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		} else {
340e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			usb_ep_disable(ep);
341e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			ep->driver_data = NULL;
342e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			result = -ENOMEM;
343e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell			goto fail0;
344e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		}
345e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
346e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
347e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	DBG(cdev, "%s enabled\n", loop->function.name);
348e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	return result;
349e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
350e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
351e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic int loopback_set_alt(struct usb_function *f,
352e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		unsigned intf, unsigned alt)
353e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
354e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct f_loopback	*loop = func_to_loop(f);
355e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct usb_composite_dev *cdev = f->config->cdev;
356e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
357e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* we know alt is zero */
358e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (loop->in_ep->driver_data)
359e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		disable_loopback(loop);
360e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	return enable_loopback(cdev, loop);
361e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
362e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
363e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownellstatic void loopback_disable(struct usb_function *f)
364e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
365e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct f_loopback	*loop = func_to_loop(f);
366e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
367e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	disable_loopback(loop);
368e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
369e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
370e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/*-------------------------------------------------------------------------*/
371e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
372e12995ec8f8d99f2a339541fc28998af2d60af0fMichal Nazarewiczstatic int __init loopback_bind_config(struct usb_configuration *c)
373e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
374e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	struct f_loopback	*loop;
375e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	int			status;
376e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
377e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop = kzalloc(sizeof *loop, GFP_KERNEL);
378e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (!loop)
379e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return -ENOMEM;
380e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
381e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.name = "loopback";
382e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.descriptors = fs_loopback_descs;
383e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.bind = loopback_bind;
384e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.unbind = loopback_unbind;
385e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.set_alt = loopback_set_alt;
386e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loop->function.disable = loopback_disable;
387e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
388e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	status = usb_add_function(c, &loop->function);
389e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (status)
390e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		kfree(loop);
391e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	return status;
392e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
393e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
394e12995ec8f8d99f2a339541fc28998af2d60af0fMichal Nazarewiczstatic struct usb_configuration loopback_driver = {
395e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.label		= "loopback",
396e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.strings	= loopback_strings,
397e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bConfigurationValue = 2,
398e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	.bmAttributes	= USB_CONFIG_ATT_SELFPOWER,
399e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* .iConfiguration = DYNAMIC */
400e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell};
401e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
402e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell/**
403e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * loopback_add - add a loopback testing configuration to a device
404e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell * @cdev: the device to support the loopback configuration
405e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell */
406ab943a2e125b098489ccaa0166c2c52f8266d9edDavid Brownellint __init loopback_add(struct usb_composite_dev *cdev, bool autoresume)
407e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell{
408e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	int id;
409e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
410e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* allocate string ID(s) */
411e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	id = usb_string_id(cdev);
412e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (id < 0)
413e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		return id;
414e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	strings_loopback[0].id = id;
415e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
416e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loopback_intf.iInterface = id;
417e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	loopback_driver.iConfiguration = id;
418e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
419ab943a2e125b098489ccaa0166c2c52f8266d9edDavid Brownell	/* support autoresume for remote wakeup testing */
420ab943a2e125b098489ccaa0166c2c52f8266d9edDavid Brownell	if (autoresume)
421683da59d7b8ae04891636d4b59893cd4e9b0b7e5Timo Juhani Lindfors		loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
422ab943a2e125b098489ccaa0166c2c52f8266d9edDavid Brownell
423e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	/* support OTG systems */
424e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	if (gadget_is_otg(cdev->gadget)) {
425e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		loopback_driver.descriptors = otg_desc;
426e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell		loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
427e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell	}
428e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell
429c9bfff9c98671ad50e4abbfe1ab606a9957f7539Uwe Kleine-König	return usb_add_config(cdev, &loopback_driver, loopback_bind_config);
430e5760fdac8c8aeca060d1afc8a233ea3d7a46720David Brownell}
431