[patch 2/2] g_zero partially updated for new gadget infrastructure

David Brownell <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
Preliminary and partial conversion of Gadget Zero to use an updated
version of the composite device infrastructure patch.

This omits multi-configuration support; and has turned up a few other
issues that should be resolved in that infrastrusture.

Note that it's a net source shrink, but adds new code to allocate
various identifiers.  (And this goes on top of a previous cleanup
patch, now in the merge queue.)

# NYET signed-off
---
 drivers/usb/gadget/Makefile |    4 
 drivers/usb/gadget/zero.c   | 1053 ++++++++++++++++----------------------------
 2 files changed, 405 insertions(+), 652 deletions(-)

--- at91.orig/drivers/usb/gadget/zero.c	2007-08-02 06:51:06.000000000 -0700
+++ at91/drivers/usb/gadget/zero.c	2007-08-02 10:00:23.000000000 -0700
@@ -54,8 +54,7 @@
 #include <linux/utsname.h>
 #include <linux/device.h>
 
-#include <linux/usb/ch9.h>
-#include <linux/usb_gadget.h>
+#include <linux/usb/composite.h>
 
 #include "gadget_chips.h"
 
@@ -79,29 +78,11 @@ static const char loopback [] = "loop in
  * this version autoconfigures as much as possible,
  * which is reasonable for most "bulk-only" drivers.
  */
-static const char *EP_IN_NAME;		/* source */
-static const char *EP_OUT_NAME;		/* sink */
 
-/*-------------------------------------------------------------------------*/
-
-/* big enough to hold our biggest descriptor */
-#define USB_BUFSIZ	256
-
-struct zero_dev {
-	spinlock_t		lock;
-	struct usb_gadget	*gadget;
-	struct usb_request	*req;		/* for control responses */
-
-	/* when configured, we have one of two configs:
-	 * - source data (in to host) and sink it (out from host)
-	 * - or loop it back (out from host back in to host)
-	 */
-	u8			config;
-	struct usb_ep		*in_ep, *out_ep;
+static struct usb_ep		*in_ep, *out_ep;
+static struct timer_list	resume;
 
-	/* autoresume timer */
-	struct timer_list	resume;
-};
+/*-------------------------------------------------------------------------*/
 
 #define DBG(d, fmt, args...) \
 	dev_dbg(&(d)->gadget->dev , fmt , ## args)
@@ -163,18 +144,11 @@ module_param (loopdefault, bool, S_IRUGO
  * configuration descriptors are built on demand.
  */
 
-#define STRING_MANUFACTURER		25
-#define STRING_PRODUCT			42
-#define STRING_SERIAL			101
-#define STRING_SOURCE_SINK		250
-#define STRING_LOOPBACK			251
-
-/*
- * This device advertises two configurations; these numbers work
- * on a pxa250 as well as more flexible hardware.
- */
-#define	CONFIG_SOURCE_SINK	3
-#define	CONFIG_LOOPBACK		2
+#define STRING_MANUFACTURER_IDX		0
+#define STRING_PRODUCT_IDX		1
+#define STRING_SERIAL_IDX		2
+#define STRING_SOURCE_SINK_IDX		3
+#define STRING_LOOPBACK_IDX		4
 
 static struct usb_device_descriptor
 device_desc = {
@@ -186,38 +160,9 @@ device_desc = {
 
 	.idVendor =		__constant_cpu_to_le16 (DRIVER_VENDOR_NUM),
 	.idProduct =		__constant_cpu_to_le16 (DRIVER_PRODUCT_NUM),
-	.iManufacturer =	STRING_MANUFACTURER,
-	.iProduct =		STRING_PRODUCT,
-	.iSerialNumber =	STRING_SERIAL,
 	.bNumConfigurations =	2,
 };
 
-static struct usb_config_descriptor
-source_sink_config = {
-	.bLength =		sizeof source_sink_config,
-	.bDescriptorType =	USB_DT_CONFIG,
-
-	/* compute wTotalLength on the fly */
-	.bNumInterfaces =	1,
-	.bConfigurationValue =	CONFIG_SOURCE_SINK,
-	.iConfiguration =	STRING_SOURCE_SINK,
-	.bmAttributes =		USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
-	.bMaxPower =		1,	/* self-powered */
-};
-
-static struct usb_config_descriptor
-loopback_config = {
-	.bLength =		sizeof loopback_config,
-	.bDescriptorType =	USB_DT_CONFIG,
-
-	/* compute wTotalLength on the fly */
-	.bNumInterfaces =	1,
-	.bConfigurationValue =	CONFIG_LOOPBACK,
-	.iConfiguration =	STRING_LOOPBACK,
-	.bmAttributes =		USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
-	.bMaxPower =		1,	/* self-powered */
-};
-
 static struct usb_otg_descriptor
 otg_descriptor = {
 	.bLength =		sizeof otg_descriptor,
@@ -228,24 +173,22 @@ otg_descriptor = {
 
 /* one interface in each configuration */
 
-static const struct usb_interface_descriptor
+static struct usb_interface_descriptor
 source_sink_intf = {
 	.bLength =		sizeof source_sink_intf,
 	.bDescriptorType =	USB_DT_INTERFACE,
 
 	.bNumEndpoints =	2,
 	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
-	.iInterface =		STRING_SOURCE_SINK,
 };
 
-static const struct usb_interface_descriptor
+static struct usb_interface_descriptor
 loopback_intf = {
 	.bLength =		sizeof loopback_intf,
 	.bDescriptorType =	USB_DT_INTERFACE,
 
 	.bNumEndpoints =	2,
 	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
-	.iInterface =		STRING_LOOPBACK,
 };
 
 /* two full speed bulk endpoints; their use is config-dependent */
@@ -311,17 +254,6 @@ hs_sink_desc = {
 	.wMaxPacketSize =	__constant_cpu_to_le16 (512),
 };
 
-static struct usb_qualifier_descriptor
-dev_qualifier = {
-	.bLength =		sizeof dev_qualifier,
-	.bDescriptorType =	USB_DT_DEVICE_QUALIFIER,
-
-	.bcdUSB =		__constant_cpu_to_le16 (0x0200),
-	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,
-
-	.bNumConfigurations =	2,
-};
-
 static const struct usb_descriptor_header *hs_source_sink_function [] = {
 	(struct usb_descriptor_header *) &otg_descriptor,
 	(struct usb_descriptor_header *) &source_sink_intf,
@@ -353,14 +285,13 @@ static char manufacturer[50];
 /* default serial number takes at least two packets */
 static char serial[] = "0123456789.0123456789.0123456789";
 
-
 /* static strings, in UTF-8 */
 static struct usb_string		strings [] = {
-	{ STRING_MANUFACTURER, manufacturer, },
-	{ STRING_PRODUCT, longname, },
-	{ STRING_SERIAL, serial, },
-	{ STRING_LOOPBACK, loopback, },
-	{ STRING_SOURCE_SINK, source_sink, },
+	[STRING_MANUFACTURER_IDX].s = manufacturer,
+	[STRING_PRODUCT_IDX].s = longname,
+	[STRING_SERIAL_IDX].s = serial,
+	[STRING_LOOPBACK_IDX].s = loopback,
+	[STRING_SOURCE_SINK_IDX].s = source_sink,
 	{  }			/* end of list */
 };
 
@@ -369,65 +300,13 @@ static struct usb_gadget_strings	stringt
 	.strings	= strings,
 };
 
-/*
- * config descriptors are also handcrafted.  these must agree with code
- * that sets configurations, and with code managing interfaces and their
- * altsettings.  other complexity may come from:
- *
- *  - high speed support, including "other speed config" rules
- *  - multiple configurations
- *  - interfaces with alternate settings
- *  - embedded class or vendor-specific descriptors
- *
- * this handles high speed, and has a second config that could as easily
- * have been an alternate interface setting (on most hardware).
- *
- * NOTE:  to demonstrate (and test) more USB capabilities, this driver
- * should include an altsetting to test interrupt transfers, including
- * high bandwidth modes at high speed.  (Maybe work like Intel's test
- * device?)
+/* we're lazy here: all functions and the gadget driver itself
+ * will share these same string tables.
  */
-static int
-config_buf (struct usb_gadget *gadget,
-		u8 *buf, u8 type, unsigned index)
-{
-	int				is_source_sink;
-	int				len;
-	const struct usb_descriptor_header **function;
-	int				hs = 0;
-
-	/* two configurations will always be index 0 and index 1 */
-	if (index > 1)
-		return -EINVAL;
-	is_source_sink = loopdefault ? (index == 1) : (index == 0);
-
-	if (gadget_is_dualspeed(gadget)) {
-		hs = (gadget->speed == USB_SPEED_HIGH);
-		if (type == USB_DT_OTHER_SPEED_CONFIG)
-			hs = !hs;
-	}
-	if (hs)
-		function = is_source_sink
-			? hs_source_sink_function
-			: hs_loopback_function;
-	else
-		function = is_source_sink
-			? fs_source_sink_function
-			: fs_loopback_function;
-
-	/* for now, don't advertise srp-only devices */
-	if (!gadget_is_otg(gadget))
-		function++;
-
-	len = usb_gadget_config_buf (is_source_sink
-					? &source_sink_config
-					: &loopback_config,
-			buf, USB_BUFSIZ, function);
-	if (len < 0)
-		return len;
-	((struct usb_config_descriptor *) buf)->bDescriptorType = type;
-	return len;
-}
+static struct usb_gadget_strings *allstrings[] = {
+	&stringtab,
+	NULL,
+};
 
 /*-------------------------------------------------------------------------*/
 
@@ -473,7 +352,7 @@ static void free_ep_req (struct usb_ep *
 
 static int
 check_read_data (
-	struct zero_dev		*dev,
+	struct usb_composite_dev *cdev,
 	struct usb_ep		*ep,
 	struct usb_request	*req
 )
@@ -498,7 +377,7 @@ check_read_data (
 				continue;
 			break;
 		}
-		ERROR (dev, "bad OUT byte, buf [%d] = %d\n", i, *buf);
+		ERROR(cdev, "bad OUT byte, buf [%d] = %d\n", i, *buf);
 		usb_ep_set_halt (ep);
 		return -EINVAL;
 	}
@@ -527,14 +406,14 @@ static void reinit_write_data(struct usb
  */
 static void source_sink_complete (struct usb_ep *ep, struct usb_request *req)
 {
-	struct zero_dev	*dev = ep->driver_data;
-	int		status = req->status;
+	struct usb_composite_dev *cdev = ep->driver_data;
+	int status = req->status;
 
 	switch (status) {
 
 	case 0:				/* normal completion? */
-		if (ep == dev->out_ep) {
-			check_read_data (dev, ep, req);
+		if (ep == out_ep) {
+			check_read_data(cdev, ep, req);
 			memset (req->buf, 0x55, req->length);
 		} else
 			reinit_write_data(ep, req);
@@ -544,10 +423,10 @@ static void source_sink_complete (struct
 	case -ECONNABORTED:		/* hardware forced ep reset */
 	case -ECONNRESET:		/* request dequeued */
 	case -ESHUTDOWN:		/* disconnect from host */
-		VDBG (dev, "%s gone (%d), %d/%d\n", ep->name, status,
+		VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
 				req->actual, req->length);
-		if (ep == dev->out_ep)
-			check_read_data (dev, ep, req);
+		if (ep == out_ep)
+			check_read_data(cdev, ep, req);
 		free_ep_req (ep, req);
 		return;
 
@@ -557,7 +436,7 @@ static void source_sink_complete (struct
 					 */
 	default:
 #if 1
-		DBG (dev, "%s complete --> %d, %d/%d\n", ep->name,
+		DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name,
 				status, req->actual, req->length);
 #endif
 	case -EREMOTEIO:		/* short read */
@@ -566,7 +445,7 @@ static void source_sink_complete (struct
 
 	status = usb_ep_queue (ep, req, GFP_ATOMIC);
 	if (status) {
-		ERROR (dev, "kill %s:  resubmit %d bytes --> %d\n",
+		ERROR(cdev, "kill %s:  resubmit %d bytes --> %d\n",
 				ep->name, req->length, status);
 		usb_ep_set_halt (ep);
 		/* FIXME recover later ... somehow */
@@ -582,19 +461,17 @@ static struct usb_request *source_sink_s
 	if (!req)
 		return NULL;
 
-	memset (req->buf, 0, req->length);
 	req->complete = source_sink_complete;
-
-	if (strcmp (ep->name, EP_IN_NAME) == 0)
+	if (ep == in_ep)
 		reinit_write_data(ep, req);
 	else
 		memset (req->buf, 0x55, req->length);
 
 	status = usb_ep_queue(ep, req, GFP_ATOMIC);
 	if (status) {
-		struct zero_dev	*dev = ep->driver_data;
+		struct usb_composite_dev *cdev = ep->driver_data;
 
-		ERROR (dev, "start %s --> %d\n", ep->name, status);
+		ERROR(cdev, "start %s --> %d\n", ep->name, status);
 		free_ep_req (ep, req);
 		req = NULL;
 	}
@@ -602,85 +479,244 @@ static struct usb_request *source_sink_s
 	return req;
 }
 
-static int set_source_sink_config(struct zero_dev *dev)
+/* deconfigure if needed */
+static void reset_bulk_config(struct usb_composite_dev *cdev)
 {
-	int			result = 0;
-	struct usb_ep		*ep;
-	struct usb_gadget	*gadget = dev->gadget;
+	int	value;
 
-	gadget_for_each_ep (ep, gadget) {
-		const struct usb_endpoint_descriptor	*d;
+	if (in_ep->driver_data) {
+		value = usb_ep_disable(in_ep);
+		if (value < 0)
+			ERROR(cdev, "disable %s --> %d\n", in_ep->name, value);
+		in_ep->driver_data = NULL;
+	}
+	if (out_ep->driver_data) {
+		value = usb_ep_disable(out_ep);
+		if (value < 0)
+			ERROR(cdev, "disable %s --> %d\n", in_ep->name, value);
+		out_ep->driver_data = NULL;
+	}
+	del_timer(&resume);
+}
 
-		/* one endpoint writes (sources) zeroes in (to the host) */
-		if (strcmp (ep->name, EP_IN_NAME) == 0) {
-			d = ep_desc (gadget, &hs_source_desc, &fs_source_desc);
-			result = usb_ep_enable (ep, d);
-			if (result == 0) {
-				ep->driver_data = dev;
-				if (source_sink_start_ep(ep) != NULL) {
-					dev->in_ep = ep;
-					continue;
-				}
-				usb_ep_disable (ep);
-				result = -EIO;
-			}
+static int set_source_sink_config(struct usb_composite_dev *cdev)
+{
+	int					result = 0;
+	const struct usb_endpoint_descriptor	*d;
 
-		/* one endpoint reads (sinks) anything out (from the host) */
-		} else if (strcmp (ep->name, EP_OUT_NAME) == 0) {
-			d = ep_desc (gadget, &hs_sink_desc, &fs_sink_desc);
-			result = usb_ep_enable (ep, d);
-			if (result == 0) {
-				ep->driver_data = dev;
-				if (source_sink_start_ep(ep) != NULL) {
-					dev->out_ep = ep;
-					continue;
-				}
-				usb_ep_disable (ep);
-				result = -EIO;
-			}
+	/* one endpoint writes (sources) zeroes in (to the host) */
+	d = ep_desc(cdev->gadget, &hs_source_desc, &fs_source_desc);
+	result = usb_ep_enable(in_ep, d);
+	if (result < 0)
+		return result;
+	in_ep->driver_data = cdev;
 
-		/* ignore any other endpoints */
-		} else
-			continue;
+	if (source_sink_start_ep(in_ep) == NULL) {
+fail1:
+		result = -EIO;
+fail0:
+		usb_ep_disable(in_ep);
+		in_ep->driver_data = NULL;
+		return result;
+	}
 
-		/* stop on error */
-		ERROR (dev, "can't start %s, result %d\n", ep->name, result);
-		break;
+	/* one endpoint reads (sinks) anything out (from the host) */
+	d = ep_desc(cdev->gadget, &hs_sink_desc, &fs_sink_desc);
+	result = usb_ep_enable(out_ep, d);
+	if (result < 0)
+		goto fail0;
+	out_ep->driver_data = cdev;
+
+	if (source_sink_start_ep(out_ep) == NULL) {
+		usb_ep_disable(out_ep);
+		out_ep->driver_data = NULL;
+		goto fail1;
 	}
-	if (result == 0)
-		DBG (dev, "buflen %d\n", buflen);
 
-	/* caller is responsible for cleanup on error */
 	return result;
 }
 
+static void sourcesink_autoresume(unsigned long _g)
+{
+	struct usb_gadget	*g = (void *)_g;
+
+	/* normally the host would be woken up for something
+	 * more significant than just a timer firing...
+	 */
+	if (g->speed != USB_SPEED_UNKNOWN) {
+		int status = usb_gadget_wakeup(g);
+		pr_debug("%s --> %d\n", __FUNCTION__, status);
+	}
+}
+
+static int
+sourcesink_bind(struct usb_composite_dev *cdev, struct usb_function *f)
+{
+	int	id;
+
+	/* allocate interface ID(s) */
+	id = usb_composite_interface_id(cdev);
+	if (id < 0)
+		return id;
+	source_sink_intf.bInterfaceNumber = id;
+
+	/* allocate string ID(s) */
+	id = usb_composite_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings[STRING_SOURCE_SINK_IDX].id = id;
+	cdev->iConfiguration = id;
+	source_sink_intf.iInterface = id;
+
+	if (!gadget_is_otg(cdev->gadget)) {
+		f->descriptors++;
+		if (gadget_is_dualspeed(cdev->gadget))
+			f->hs_descriptors++;
+	}
+
+	return 0;
+}
+
+static int sourcesink_setup(struct usb_composite_dev *cdev,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything except:
+	 *	- SET_CONFIGURATION (reset/deconfigure),
+	 *	- SET_INTERFACE
+	 *	- GET_INTERFACE
+	 *	- and the two control test requests.
+	 */
+	req->zero = 0;
+	switch (ctrl->bRequest) {
+
+	/* SET_CONFIGURATION is used only to deconfigure */
+	case USB_REQ_SET_CONFIGURATION:
+		reset_bulk_config(cdev);
+		value = 0;
+		break;
+
+	/* SET_INTERFACE is used for to configure all/part of the dev */
+	case USB_REQ_SET_INTERFACE:
+		reset_bulk_config(cdev);
+		value = set_source_sink_config(cdev);
+		break;
+
+	/* GET_INTERFACE ... there's only one altsetting */
+	case USB_REQ_GET_INTERFACE:
+		*((u8 *)req->buf) = 0;
+		break;
+
+	/*
+	 * These are the same vendor-specific requests supported by
+	 * Intel's USB 2.0 compliance test devices.  We exceed that
+	 * device spec by allowing multiple-packet requests.
+	 *
+	 * REVISIT:  the Control-OUT data stays in req->buf ... best
+	 * would be to copy it into a scratch buffer.
+	 */
+	case 0x5b:	/* control WRITE test -- fill the buffer */
+		if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR))
+			goto unknown;
+		if (w_value || w_index)
+			break;
+		/* just read that many bytes into the buffer */
+		if (w_length > req->length)
+			break;
+		value = w_length;
+		break;
+	case 0x5c:	/* control READ test -- return the buffer */
+		if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR))
+			goto unknown;
+		if (w_value || w_index)
+			break;
+		/* expect those bytes are still in the buffer; send back */
+		if (w_length > req->length)
+			break;
+		value = w_length;
+		break;
+
+	default:
+unknown:
+		VDBG(cdev,
+			"unknown control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static void bulk_disconnect(struct usb_composite_dev *cdev)
+{
+	reset_bulk_config(cdev);
+	del_timer(&resume);
+}
+
+static void sourcesink_suspend(struct usb_composite_dev *cdev)
+{
+	if (cdev->gadget->speed == USB_SPEED_UNKNOWN)
+		return;
+
+	if (autoresume) {
+		mod_timer(&resume, jiffies + (HZ * autoresume));
+		DBG(cdev, "suspend, wakeup in %d seconds\n", autoresume);
+	} else
+		DBG(cdev, "%s\n", __FUNCTION__);
+}
+
+static void sourcesink_resume(struct usb_composite_dev *cdev)
+{
+	DBG(cdev, "%s\n", __FUNCTION__);
+	del_timer(&resume);
+}
+
+static struct usb_function sourcesink_function = {
+	.name		= "source/sink",
+	.strings	= allstrings,
+	.descriptors	= fs_source_sink_function,
+
+	.bind		= sourcesink_bind,
+	/* unbind */
+	.setup		= sourcesink_setup,
+	.disconnect	= bulk_disconnect,
+	.suspend	= sourcesink_suspend,
+	.resume		= sourcesink_resume,
+};
+
 /*-------------------------------------------------------------------------*/
 
 static void loopback_complete (struct usb_ep *ep, struct usb_request *req)
 {
-	struct zero_dev	*dev = ep->driver_data;
-	int		status = req->status;
+	struct usb_composite_dev *cdev = ep->driver_data;
+	int status = req->status;
 
 	switch (status) {
 
 	case 0:				/* normal completion? */
-		if (ep == dev->out_ep) {
+		if (ep == out_ep) {
 			/* loop this OUT packet back IN to the host */
 			req->zero = (req->actual < req->length);
 			req->length = req->actual;
-			status = usb_ep_queue (dev->in_ep, req, GFP_ATOMIC);
+			status = usb_ep_queue(in_ep, req, GFP_ATOMIC);
 			if (status == 0)
 				return;
 
 			/* "should never get here" */
-			ERROR (dev, "can't loop %s to %s: %d\n",
-				ep->name, dev->in_ep->name,
+			ERROR(cdev, "can't loop %s to %s: %d\n",
+				ep->name, in_ep->name,
 				status);
 		}
 
 		/* queue the buffer for some later OUT packet */
 		req->length = buflen;
-		status = usb_ep_queue (dev->out_ep, req, GFP_ATOMIC);
+		status = usb_ep_queue(out_ep, req, GFP_ATOMIC);
 		if (status == 0)
 			return;
 
@@ -688,7 +724,7 @@ static void loopback_complete (struct us
 		/* FALLTHROUGH */
 
 	default:
-		ERROR (dev, "%s loop complete --> %d, %d/%d\n", ep->name,
+		ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
 				status, req->actual, req->length);
 		/* FALLTHROUGH */
 
@@ -705,428 +741,218 @@ static void loopback_complete (struct us
 	}
 }
 
-static int set_loopback_config(struct zero_dev *dev)
+static int set_loopback_config(struct usb_composite_dev *cdev)
 {
-	int			result = 0;
-	struct usb_ep		*ep;
-	struct usb_gadget	*gadget = dev->gadget;
-
-	gadget_for_each_ep (ep, gadget) {
-		const struct usb_endpoint_descriptor	*d;
-
-		/* one endpoint writes data back IN to the host */
-		if (strcmp (ep->name, EP_IN_NAME) == 0) {
-			d = ep_desc (gadget, &hs_source_desc, &fs_source_desc);
-			result = usb_ep_enable (ep, d);
-			if (result == 0) {
-				ep->driver_data = dev;
-				dev->in_ep = ep;
-				continue;
-			}
-
-		/* one endpoint just reads OUT packets */
-		} else if (strcmp (ep->name, EP_OUT_NAME) == 0) {
-			d = ep_desc (gadget, &hs_sink_desc, &fs_sink_desc);
-			result = usb_ep_enable (ep, d);
-			if (result == 0) {
-				ep->driver_data = dev;
-				dev->out_ep = ep;
-				continue;
-			}
+	const struct usb_endpoint_descriptor	*d;
+	int					result = 0;
+	struct usb_request			*req;
+	unsigned				i;
 
-		/* ignore any other endpoints */
-		} else
-			continue;
+	/* one endpoint writes data back IN to the host */
+	d = ep_desc(cdev->gadget, &hs_source_desc, &fs_source_desc);
+	result = usb_ep_enable(in_ep, d);
+	if (result < 0)
+		return result;
+	in_ep->driver_data = cdev;
 
-		/* stop on error */
-		ERROR (dev, "can't enable %s, result %d\n", ep->name, result);
-		break;
+	/* one endpoint just reads OUT packets */
+	d = ep_desc(cdev->gadget, &hs_sink_desc, &fs_sink_desc);
+	result = usb_ep_enable(out_ep, d);
+	if (result < 0) {
+fail0:
+		usb_ep_disable(in_ep);
+		in_ep->driver_data = NULL;
+		return result;
 	}
+	out_ep->driver_data = cdev;
 
 	/* allocate a bunch of read buffers and queue them all at once.
 	 * we buffer at most 'qlen' transfers; fewer if any need more
 	 * than 'buflen' bytes each.
 	 */
-	if (result == 0) {
-		struct usb_request	*req;
-		unsigned		i;
-
-		ep = dev->out_ep;
-		for (i = 0; i < qlen && result == 0; i++) {
-			req = alloc_ep_req (ep, buflen);
-			if (req) {
-				req->complete = loopback_complete;
-				result = usb_ep_queue (ep, req, GFP_ATOMIC);
-				if (result)
-					DBG (dev, "%s queue req --> %d\n",
-							ep->name, result);
-			} else
-				result = -ENOMEM;
+	for (i = 0; i < qlen && result == 0; i++) {
+		req = alloc_ep_req(out_ep, buflen);
+		if (req) {
+			req->complete = loopback_complete;
+			result = usb_ep_queue(out_ep, req, GFP_ATOMIC);
+			if (result)
+				DBG(cdev, "%s queue req --> %d\n",
+						out_ep->name, result);
+		} else {
+			usb_ep_disable(out_ep);
+			out_ep->driver_data = NULL;
+			result = -ENOMEM;
+			goto fail0;
 		}
 	}
-	if (result == 0)
-		DBG (dev, "qlen %d, buflen %d\n", qlen, buflen);
 
-	/* caller is responsible for cleanup on error */
 	return result;
 }
 
-/*-------------------------------------------------------------------------*/
-
-static void zero_reset_config (struct zero_dev *dev)
-{
-	if (dev->config == 0)
-		return;
-
-	DBG (dev, "reset config\n");
-
-	/* just disable endpoints, forcing completion of pending i/o.
-	 * all our completion handlers free their requests in this case.
-	 */
-	if (dev->in_ep) {
-		usb_ep_disable (dev->in_ep);
-		dev->in_ep = NULL;
-	}
-	if (dev->out_ep) {
-		usb_ep_disable (dev->out_ep);
-		dev->out_ep = NULL;
-	}
-	dev->config = 0;
-	del_timer (&dev->resume);
-}
-
-/* change our operational config.  this code must agree with the code
- * that returns config descriptors, and altsetting code.
- *
- * it's also responsible for power management interactions. some
- * configurations might not work with our current power sources.
- *
- * note that some device controller hardware will constrain what this
- * code can do, perhaps by disallowing more than one configuration or
- * by limiting configuration choices (like the pxa2xx).
- */
-static int zero_set_config(struct zero_dev *dev, unsigned number)
+static int
+loopback_bind(struct usb_composite_dev *cdev, struct usb_function *f)
 {
-	int			result = 0;
-	struct usb_gadget	*gadget = dev->gadget;
-
-	if (number == dev->config)
-		return 0;
-
-	if (gadget_is_sa1100 (gadget) && dev->config) {
-		/* tx fifo is full, but we can't clear it...*/
-		ERROR(dev, "can't change configurations\n");
-		return -ESPIPE;
-	}
-	zero_reset_config (dev);
+	int	id;
 
-	switch (number) {
-	case CONFIG_SOURCE_SINK:
-		result = set_source_sink_config(dev);
-		break;
-	case CONFIG_LOOPBACK:
-		result = set_loopback_config(dev);
-		break;
-	default:
-		result = -EINVAL;
-		/* FALL THROUGH */
-	case 0:
-		return result;
-	}
+	/* FIXME  each configuration has its own interface ID space... */
 
-	if (!result && (!dev->in_ep || !dev->out_ep))
-		result = -ENODEV;
-	if (result)
-		zero_reset_config (dev);
-	else {
-		char *speed;
+	/* allocate interface ID(s) */
+	id = usb_composite_interface_id(cdev);
+	if (id < 0)
+		return id;
+	source_sink_intf.bInterfaceNumber = id;
 
-		switch (gadget->speed) {
-		case USB_SPEED_LOW:	speed = "low"; break;
-		case USB_SPEED_FULL:	speed = "full"; break;
-		case USB_SPEED_HIGH:	speed = "high"; break;
-		default:		speed = "?"; break;
-		}
+	/* allocate string ID(s) */
+	id = usb_composite_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings[STRING_LOOPBACK_IDX].id = id;
+	cdev->iConfiguration = id;
+	loopback_intf.iInterface = id;
 
-		dev->config = number;
-		INFO (dev, "%s speed config #%d: %s\n", speed, number,
-				(number == CONFIG_SOURCE_SINK)
-					? source_sink : loopback);
+	if (!gadget_is_otg(cdev->gadget)) {
+		f->descriptors++;
+		if (gadget_is_dualspeed(cdev->gadget))
+			f->hs_descriptors++;
 	}
-	return result;
-}
 
-/*-------------------------------------------------------------------------*/
-
-static void zero_setup_complete (struct usb_ep *ep, struct usb_request *req)
-{
-	if (req->status || req->actual != req->length)
-		DBG ((struct zero_dev *) ep->driver_data,
-				"setup complete --> %d, %d/%d\n",
-				req->status, req->actual, req->length);
+	return 0;
 }
 
-/*
- * The setup() callback implements all the ep0 functionality that's
- * not handled lower down, in hardware or the hardware driver (like
- * device and endpoint feature flags, and their status).  It's all
- * housekeeping for the gadget function we're implementing.  Most of
- * the work is in config-specific setup.
- */
-static int
-zero_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+static int loopback_setup(struct usb_composite_dev *cdev,
+		const struct usb_ctrlrequest *ctrl)
 {
-	struct zero_dev		*dev = get_gadget_data (gadget);
-	struct usb_request	*req = dev->req;
+	struct usb_request	*req = cdev->req;
 	int			value = -EOPNOTSUPP;
 	u16			w_index = le16_to_cpu(ctrl->wIndex);
 	u16			w_value = le16_to_cpu(ctrl->wValue);
 	u16			w_length = le16_to_cpu(ctrl->wLength);
 
-	/* usually this stores reply data in the pre-allocated ep0 buffer,
-	 * but config change events will reconfigure hardware.
+	/* composite driver infrastructure handles everything except:
+	 *	- SET_CONFIGURATION (reset/deconfigure),
+	 *	- SET_INTERFACE
+	 *	- GET_INTERFACE
 	 */
 	req->zero = 0;
 	switch (ctrl->bRequest) {
 
-	case USB_REQ_GET_DESCRIPTOR:
-		if (ctrl->bRequestType != USB_DIR_IN)
-			goto unknown;
-		switch (w_value >> 8) {
-
-		case USB_DT_DEVICE:
-			value = min (w_length, (u16) sizeof device_desc);
-			memcpy (req->buf, &device_desc, value);
-			break;
-		case USB_DT_DEVICE_QUALIFIER:
-			if (!gadget_is_dualspeed(gadget))
-				break;
-			value = min (w_length, (u16) sizeof dev_qualifier);
-			memcpy (req->buf, &dev_qualifier, value);
-			break;
-
-		case USB_DT_OTHER_SPEED_CONFIG:
-			if (!gadget_is_dualspeed(gadget))
-				break;
-			// FALLTHROUGH
-		case USB_DT_CONFIG:
-			value = config_buf (gadget, req->buf,
-					w_value >> 8,
-					w_value & 0xff);
-			if (value >= 0)
-				value = min (w_length, (u16) value);
-			break;
-
-		case USB_DT_STRING:
-			/* wIndex == language code.
-			 * this driver only handles one language, you can
-			 * add string tables for other languages, using
-			 * any UTF-8 characters
-			 */
-			value = usb_gadget_get_string (&stringtab,
-					w_value & 0xff, req->buf);
-			if (value >= 0)
-				value = min (w_length, (u16) value);
-			break;
-		}
-		break;
-
-	/* currently two configs, two speeds */
+	/* SET_CONFIGURATION is used only to deconfigure */
 	case USB_REQ_SET_CONFIGURATION:
-		if (ctrl->bRequestType != 0)
-			goto unknown;
-		if (gadget->a_hnp_support)
-			DBG (dev, "HNP available\n");
-		else if (gadget->a_alt_hnp_support)
-			DBG (dev, "HNP needs a different root port\n");
-		else
-			VDBG (dev, "HNP inactive\n");
-		spin_lock (&dev->lock);
-		value = zero_set_config(dev, w_value);
-		spin_unlock (&dev->lock);
-		break;
-	case USB_REQ_GET_CONFIGURATION:
-		if (ctrl->bRequestType != USB_DIR_IN)
-			goto unknown;
-		*(u8 *)req->buf = dev->config;
-		value = min (w_length, (u16) 1);
+		reset_bulk_config(cdev);
+		value = 0;
 		break;
 
-	/* until we add altsetting support, or other interfaces,
-	 * only 0/0 are possible.  pxa2xx only supports 0/0 (poorly)
-	 * and already killed pending endpoint I/O.
-	 */
+	/* SET_INTERFACE is used for to configure all/part of the dev */
 	case USB_REQ_SET_INTERFACE:
-		if (ctrl->bRequestType != USB_RECIP_INTERFACE)
-			goto unknown;
-		spin_lock (&dev->lock);
-		if (dev->config && w_index == 0 && w_value == 0) {
-			u8		config = dev->config;
-
-			/* resets interface configuration, forgets about
-			 * previous transaction state (queued bufs, etc)
-			 * and re-inits endpoint state (toggle etc)
-			 * no response queued, just zero status == success.
-			 * if we had more than one interface we couldn't
-			 * use this "reset the config" shortcut.
-			 */
-			zero_reset_config (dev);
-			zero_set_config(dev, config);
-			value = 0;
-		}
-		spin_unlock (&dev->lock);
-		break;
-	case USB_REQ_GET_INTERFACE:
-		if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
-			goto unknown;
-		if (!dev->config)
-			break;
-		if (w_index != 0) {
-			value = -EDOM;
-			break;
-		}
-		*(u8 *)req->buf = 0;
-		value = min (w_length, (u16) 1);
+		reset_bulk_config(cdev);
+		value = set_loopback_config(cdev);
 		break;
 
-	/*
-	 * These are the same vendor-specific requests supported by
-	 * Intel's USB 2.0 compliance test devices.  We exceed that
-	 * device spec by allowing multiple-packet requests.
-	 */
-	case 0x5b:	/* control WRITE test -- fill the buffer */
-		if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR))
-			goto unknown;
-		if (w_value || w_index)
-			break;
-		/* just read that many bytes into the buffer */
-		if (w_length > USB_BUFSIZ)
-			break;
-		value = w_length;
-		break;
-	case 0x5c:	/* control READ test -- return the buffer */
-		if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR))
-			goto unknown;
-		if (w_value || w_index)
-			break;
-		/* expect those bytes are still in the buffer; send back */
-		if (w_length > USB_BUFSIZ
-				|| w_length != req->length)
-			break;
-		value = w_length;
+	/* GET_INTERFACE ... there's only one altsetting */
+	case USB_REQ_GET_INTERFACE:
+		*((u8 *)req->buf) = 0;
 		break;
 
 	default:
-unknown:
-		VDBG (dev,
+		VDBG(cdev,
 			"unknown control req%02x.%02x v%04x i%04x l%d\n",
 			ctrl->bRequestType, ctrl->bRequest,
 			w_value, w_index, w_length);
 	}
 
-	/* respond with data transfer before status phase? */
-	if (value >= 0) {
-		req->length = value;
-		req->zero = value < w_length;
-		value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC);
-		if (value < 0) {
-			DBG (dev, "ep_queue --> %d\n", value);
-			req->status = 0;
-			zero_setup_complete (gadget->ep0, req);
-		}
-	}
-
 	/* device either stalls (value < 0) or reports success */
 	return value;
 }
 
-static void
-zero_disconnect (struct usb_gadget *gadget)
-{
-	struct zero_dev		*dev = get_gadget_data (gadget);
-	unsigned long		flags;
-
-	spin_lock_irqsave (&dev->lock, flags);
-	zero_reset_config (dev);
+static struct usb_function loopback_function = {
+	.name		= "loopback",
+	.strings	= allstrings,
+	.descriptors	= fs_loopback_function,
 
-	/* a more significant application might have some non-usb
-	 * activities to quiesce here, saving resources like power
-	 * or pushing the notification up a network stack.
-	 */
-	spin_unlock_irqrestore (&dev->lock, flags);
+	.bind		= loopback_bind,
+	.setup		= loopback_setup,
+	.disconnect	= bulk_disconnect,
+};
 
-	/* next we may get setup() calls to enumerate new connections;
-	 * or an unbind() during shutdown (including removing module).
-	 */
-}
+/*-------------------------------------------------------------------------*/
 
-static void
-zero_autoresume (unsigned long _dev)
+static int zero_bind(struct usb_composite_dev *cdev)
 {
-	struct zero_dev	*dev = (struct zero_dev *) _dev;
-	int		status;
-
-	/* normally the host would be woken up for something
-	 * more significant than just a timer firing...
-	 */
-	if (dev->gadget->speed != USB_SPEED_UNKNOWN) {
-		status = usb_gadget_wakeup (dev->gadget);
-		DBG (dev, "wakeup --> %d\n", status);
-	}
-}
+	int			gcnum;
+	struct usb_gadget	*gadget = cdev->gadget;
+	int			id;
 
-/*-------------------------------------------------------------------------*/
+	/* FIXME -- this is device-wide stuff, not config-wide */
 
-static void /* __init_or_exit */
-zero_unbind (struct usb_gadget *gadget)
-{
-	struct zero_dev		*dev = get_gadget_data (gadget);
+	/* allocate string descriptor numbers */
+	id = usb_composite_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings[STRING_MANUFACTURER_IDX].id = id;
+	device_desc.iManufacturer = id;
 
-	DBG (dev, "unbind\n");
+	id = usb_composite_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings[STRING_PRODUCT_IDX].id = id;
+	device_desc.iProduct = id;
 
-	/* we've already been disconnected ... no i/o is active */
-	if (dev->req) {
-		dev->req->length = USB_BUFSIZ;
-		free_ep_req (gadget->ep0, dev->req);
-	}
-	del_timer_sync (&dev->resume);
-	kfree (dev);
-	set_gadget_data (gadget, NULL);
-}
+	id = usb_composite_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings[STRING_SERIAL_IDX].id = id;
+	device_desc.iSerialNumber = id;
 
-static int __init
-zero_bind (struct usb_gadget *gadget)
-{
-	struct zero_dev		*dev;
-	struct usb_ep		*ep;
-	int			gcnum;
+	/*
+	 * config-wide stuff:
+	 */
+	cdev->bmAttributes = USB_CONFIG_ATT_SELFPOWER;
+	cdev->bMaxPower = 1;	/* 2 mA, minimal */
 
-	/* FIXME this can't yet work right with SH ... it has only
-	 * one configuration, numbered one.
+	/*
+	 * Register primary, then secondary configuration.  Note that
+	 * SH4 only allows one config...
+	 *
+	 * FIXME this isn't how to handle multiple configs ...
 	 */
-	if (gadget_is_sh(gadget))
-		return -ENODEV;
+	if (loopdefault) {
+		if (gadget_is_dualspeed(cdev->gadget))
+			loopback_function.hs_descriptors =
+					hs_loopback_function;
+		usb_composite_add_function(cdev, &loopback_function);
+	} else {
+		if (gadget_is_dualspeed(cdev->gadget))
+			sourcesink_function.hs_descriptors =
+					hs_source_sink_function;
+		usb_composite_add_function(cdev, &sourcesink_function);
+	}
 
 	/* Bulk-only drivers like this one SHOULD be able to
 	 * autoconfigure on any sane usb controller driver,
 	 * but there may also be important quirks to address.
+	 *
+	 * In this case we don't do much per-function setup since it's
+	 * all done here ... we do *NOT* intend to ever combine this
+	 * "vendor-specific" driver with a class driver.
 	 */
 	usb_ep_autoconfig_reset (gadget);
-	ep = usb_ep_autoconfig (gadget, &fs_source_desc);
-	if (!ep) {
+	in_ep = usb_ep_autoconfig(gadget, &fs_source_desc);
+	if (!in_ep) {
 autoconf_fail:
 		printk (KERN_ERR "%s: can't autoconfigure on %s\n",
 			shortname, gadget->name);
 		return -ENODEV;
 	}
-	EP_IN_NAME = ep->name;
-	ep->driver_data = ep;	/* claim */
+	in_ep->driver_data = cdev;	/* claim */
 
-	ep = usb_ep_autoconfig (gadget, &fs_sink_desc);
-	if (!ep)
+	out_ep = usb_ep_autoconfig(gadget, &fs_sink_desc);
+	if (!out_ep)
 		goto autoconf_fail;
-	EP_OUT_NAME = ep->name;
-	ep->driver_data = ep;	/* claim */
+
+	/* autoconf is done; from now on we'll use the "it's allocated"
+	 * flag as "it's activated"
+	 */
+	in_ep->driver_data = NULL;
 
 	gcnum = usb_gadget_controller_number (gadget);
 	if (gcnum >= 0)
@@ -1144,135 +970,60 @@ autoconf_fail:
 		device_desc.bcdDevice = __constant_cpu_to_le16 (0x9999);
 	}
 
-
-	/* ok, we made sense of the hardware ... */
-	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
-	if (!dev)
-		return -ENOMEM;
-	spin_lock_init (&dev->lock);
-	dev->gadget = gadget;
-	set_gadget_data (gadget, dev);
-
-	/* preallocate control response and buffer */
-	dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL);
-	if (!dev->req)
-		goto enomem;
-	dev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
-	if (!dev->req->buf)
-		goto enomem;
-
-	dev->req->complete = zero_setup_complete;
-
-	device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
-
 	if (gadget_is_dualspeed(gadget)) {
-		/* assume ep0 uses the same value for both speeds ... */
-		dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0;
-
-		/* and that all endpoints are dual-speed */
+		/* assume all endpoints are dual-speed */
 		hs_source_desc.bEndpointAddress =
-				fs_source_desc.bEndpointAddress;
+			fs_source_desc.bEndpointAddress;
 		hs_sink_desc.bEndpointAddress =
-				fs_sink_desc.bEndpointAddress;
+			fs_sink_desc.bEndpointAddress;
 	}
 
 	if (gadget_is_otg(gadget)) {
-		otg_descriptor.bmAttributes |= USB_OTG_HNP,
-		source_sink_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
-		loopback_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
-	}
-
-	usb_gadget_set_selfpowered (gadget);
-
-	init_timer (&dev->resume);
-	dev->resume.function = zero_autoresume;
-	dev->resume.data = (unsigned long) dev;
-	if (autoresume) {
-		source_sink_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
-		loopback_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+		otg_descriptor.bmAttributes |= USB_OTG_HNP;
+		cdev->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 	}
 
-	gadget->ep0->driver_data = dev;
+	setup_timer(&resume, sourcesink_autoresume, (unsigned long) gadget);
+	if (autoresume)
+		cdev->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 
-	INFO (dev, "%s, version: " DRIVER_VERSION "\n", longname);
-	INFO (dev, "using %s, OUT %s IN %s\n", gadget->name,
-		EP_OUT_NAME, EP_IN_NAME);
+	INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname);
+	INFO(cdev, "using %s, OUT %s IN %s\n", gadget->name,
+		out_ep->name, in_ep->name);
 
 	snprintf (manufacturer, sizeof manufacturer, "%s %s with %s",
 		init_utsname()->sysname, init_utsname()->release,
 		gadget->name);
 
 	return 0;
-
-enomem:
-	zero_unbind (gadget);
-	return -ENOMEM;
 }
 
-/*-------------------------------------------------------------------------*/
-
-static void
-zero_suspend (struct usb_gadget *gadget)
-{
-	struct zero_dev		*dev = get_gadget_data (gadget);
-
-	if (gadget->speed == USB_SPEED_UNKNOWN)
-		return;
-
-	if (autoresume) {
-		mod_timer (&dev->resume, jiffies + (HZ * autoresume));
-		DBG (dev, "suspend, wakeup in %d seconds\n", autoresume);
-	} else
-		DBG (dev, "suspend\n");
-}
-
-static void
-zero_resume (struct usb_gadget *gadget)
+static int zero_unbind(struct usb_composite_dev *cdev)
 {
-	struct zero_dev		*dev = get_gadget_data (gadget);
-
-	DBG (dev, "resume\n");
-	del_timer (&dev->resume);
+	in_ep = NULL;
+	out_ep = NULL;
+	return 0;
 }
 
-
-/*-------------------------------------------------------------------------*/
-
-static struct usb_gadget_driver zero_driver = {
-#ifdef CONFIG_USB_GADGET_DUALSPEED
-	.speed		= USB_SPEED_HIGH,
-#else
-	.speed		= USB_SPEED_FULL,
-#endif
-	.function	= (char *) longname,
+static struct usb_composite_driver zero_driver = {
+	.name		= "zero",
+	.dev		= &device_desc,
+	.strings	= allstrings,
 	.bind		= zero_bind,
-	.unbind		= __exit_p(zero_unbind),
-
-	.setup		= zero_setup,
-	.disconnect	= zero_disconnect,
-
-	.suspend	= zero_suspend,
-	.resume		= zero_resume,
-
-	.driver		= {
-		.name		= (char *) shortname,
-		.owner		= THIS_MODULE,
-	},
+	.unbind		= zero_unbind,
 };
 
 MODULE_AUTHOR("David Brownell");
 MODULE_LICENSE("GPL");
 
-
 static int __init init (void)
 {
-	return usb_gadget_register_driver (&zero_driver);
+	return usb_composite_register(&zero_driver);
 }
 module_init (init);
 
 static void __exit cleanup (void)
 {
-	usb_gadget_unregister_driver (&zero_driver);
+	usb_composite_unregister(&zero_driver);
 }
 module_exit (cleanup);
-
--- at91.orig/drivers/usb/gadget/Makefile	2007-08-02 06:05:11.000000000 -0700
+++ at91/drivers/usb/gadget/Makefile	2007-08-02 10:02:47.000000000 -0700
@@ -20,7 +20,9 @@ obj-$(CONFIG_USB_M66592)	+= m66592-udc.o
 #
 # USB gadget drivers
 #
-g_zero-objs			:= zero.o usbstring.o config.o epautoconf.o
+C_UTILS =	composite.o usbstring.o config.o epautoconf.o
+
+g_zero-objs			:= zero.o $(C_UTILS)
 g_ether-objs			:= ether.o usbstring.o config.o epautoconf.o
 g_serial-objs			:= serial.o usbstring.o config.o epautoconf.o
 g_midi-objs			:= gmidi.o usbstring.o config.o epautoconf.o

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >>  http://get.splunk.com/
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.