Subject: USB: usbnet (7/9) module for CDC Ethernet

From: David Brownell <[email-masked]>
Date: Sat, 13 Aug 2005 14:13:36 -0700

This supports various cable modems and Linux-USB gadgets,
and exports functions used by some proprietary protocols.
Makes the CDC Ethernet support live in a separate driver module.
This module is a bit special since it also exports some functions
that are reused by the the Zaurus and RNDIS drivers.

Signed-off-by: David Brownell <[email-masked]>
Signed-off-by: Greg Kroah-Hartman <[email-masked]>

---
 drivers/usb/net/Kconfig     |   56 ++--
 drivers/usb/net/Makefile    |    1 
 drivers/usb/net/cdc_ether.c |  500 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/net/usbnet.c    |  400 -----------------------------------
 4 files changed, 528 insertions(+), 429 deletions(-)

--- gregkh-2.6.orig/drivers/usb/net/Makefile	2005-08-15 17:51:24.000000000 -0700
+++ gregkh-2.6/drivers/usb/net/Makefile	2005-08-15 17:51:25.000000000 -0700
@@ -7,6 +7,7 @@
 obj-$(CONFIG_USB_PEGASUS)	+= pegasus.o
 obj-$(CONFIG_USB_RTL8150)	+= rtl8150.o
 obj-$(CONFIG_USB_NET_AX8817X)	+= asix.o
+obj-$(CONFIG_USB_NET_CDCETHER)	+= cdc_ether.o
 obj-$(CONFIG_USB_NET_GL620A)	+= gl620a.o
 obj-$(CONFIG_USB_NET_NET1080)	+= net1080.o
 obj-$(CONFIG_USB_NET_SIMPLE)	+= simple.o
--- gregkh-2.6.orig/drivers/usb/net/Kconfig	2005-08-15 17:51:24.000000000 -0700
+++ gregkh-2.6/drivers/usb/net/Kconfig	2005-08-15 17:51:25.000000000 -0700
@@ -137,35 +137,6 @@
 	  Choose this option if you're using a host-to-host cable
 	  with one of these chips.
 
-comment "Intelligent USB Devices/Gadgets"
-	depends on USB_USBNET
-
-config USB_CDCETHER
-	boolean "CDC Ethernet support (smart devices such as cable modems)"
-	depends on USB_USBNET
-	default y
-	help
-	  This option supports devices conforming to the Communication Device
-	  Class (CDC) Ethernet Control Model, a specification that's easy to
-	  implement in device firmware.  The CDC specifications are available
-	  from <http://www.usb.org/>.
-	  
-	  CDC Ethernet is an implementation option for DOCSIS cable modems
-	  that support USB connectivity, used for non-Microsoft USB hosts.
-	  The Linux-USB CDC Ethernet Gadget driver is an open implementation.
- 	  This driver should work with at least the following devices:
-
- 	    * Ericsson PipeRider (all variants)
- 	    * Motorola (DM100 and SB4100)
- 	    * Broadcom Cable Modem (reference design)
- 	    * Toshiba PCX1100U
-	    * ...
-
-	  This driver creates an interface named "ethX", where X depends on
-	  what other networking devices you have in use.  However, if the
-	  IEEE 802 "local assignment" bit is set in the address, a "usbX"
-	  name is used instead.
-
 comment "Drivers built using the usbnet core"
 
 config USB_NET_AX8817X
@@ -197,6 +168,32 @@
 	  what other networking devices you have in use.  

+config USB_NET_CDCETHER
+	tristate "CDC Ethernet support (smart devices such as cable modems)"
+	depends on USB_USBNET
+	default y
+	help
+	  This option supports devices conforming to the Communication Device
+	  Class (CDC) Ethernet Control Model, a specification that's easy to
+	  implement in device firmware.  The CDC specifications are available
+	  from <http://www.usb.org/>.
+
+	  CDC Ethernet is an implementation option for DOCSIS cable modems
+	  that support USB connectivity, used for non-Microsoft USB hosts.
+	  The Linux-USB CDC Ethernet Gadget driver is an open implementation.
+ 	  This driver should work with at least the following devices:
+
+ 	    * Ericsson PipeRider (all variants)
+ 	    * Motorola (DM100 and SB4100)
+ 	    * Broadcom Cable Modem (reference design)
+ 	    * Toshiba PCX1100U
+	    * ...
+
+	  This driver creates an interface named "ethX", where X depends on
+	  what other networking devices you have in use.  However, if the
+	  IEEE 802 "local assignment" bit is set in the address, a "usbX"
+	  name is used instead.
+
 config USB_NET_GL620A
 	tristate "GeneSys GL620USB-A based cables"
 	default y
@@ -283,6 +280,7 @@
 config USB_NET_ZAURUS
 	tristate "Sharp Zaurus (stock ROMs) and compatible"
 	depends on USB_USBNET
+	select USB_NET_CDCETHER
 	select CRC32
 	help
 	  Choose this option to support the usb networking links used by
--- gregkh-2.6.orig/drivers/usb/net/usbnet.c	2005-08-15 17:51:24.000000000 -0700
+++ gregkh-2.6/drivers/usb/net/usbnet.c	2005-08-15 17:51:25.000000000 -0700
@@ -297,385 +297,6 @@
 EXPORT_SYMBOL_GPL(usbnet_skb_return);
 
 
-/*-------------------------------------------------------------------------
- *
- * Communications Device Class declarations.
- * Used by CDC Ethernet, and some CDC variants
- *
- *-------------------------------------------------------------------------*/
-
-#ifdef	CONFIG_USB_CDCETHER
-#define NEED_GENERIC_CDC
-#endif
-
-#if defined(CONFIG_USB_ZAURUS) || defined(CONFIG_USB_ZAURUS_MODULE)
-/* Ethernet variant uses funky framing, broken ethernet addressing */
-#define NEED_GENERIC_CDC
-#endif
-
-#ifdef	CONFIG_USB_RNDIS
-/* ACM variant uses even funkier framing, complex control RPC scheme */
-#define NEED_GENERIC_CDC
-#endif
-
-
-#ifdef	NEED_GENERIC_CDC
-
-#include <linux/usb_cdc.h>
-
-struct cdc_state {
-	struct usb_cdc_header_desc	*header;
-	struct usb_cdc_union_desc	*u;
-	struct usb_cdc_ether_desc	*ether;
-	struct usb_interface		*control;
-	struct usb_interface		*data;
-};
-
-static struct usb_driver usbnet_driver;
-
-/*
- * probes control interface, claims data interface, collects the bulk
- * endpoints, activates data interface (if needed), maybe sets MTU.
- * all pure cdc, except for certain firmware workarounds.
- */
-int usbnet_generic_cdc_bind (struct usbnet *dev, struct usb_interface *intf)
-{
-	u8				*buf = intf->cur_altsetting->extra;
-	int				len = intf->cur_altsetting->extralen;
-	struct usb_interface_descriptor	*d;
-	struct cdc_state		*info = (void *) &dev->data;
-	int				status;
-	int				rndis;
-
-	if (sizeof dev->data < sizeof *info)
-		return -EDOM;
-
-	/* expect strict spec conformance for the descriptors, but
-	 * cope with firmware which stores them in the wrong place
-	 */
-	if (len == 0 && dev->udev->actconfig->extralen) {
-		/* Motorola SB4100 (and others: Brad Hards says it's
-		 * from a Broadcom design) put CDC descriptors here
-		 */
-		buf = dev->udev->actconfig->extra;
-		len = dev->udev->actconfig->extralen;
-		if (len)
-			dev_dbg (&intf->dev,
-				"CDC descriptors on config\n");
-	}
-
-	/* this assumes that if there's a non-RNDIS vendor variant
-	 * of cdc-acm, it'll fail RNDIS requests cleanly.
-	 */
-	rndis = (intf->cur_altsetting->desc.bInterfaceProtocol == 0xff);
-
-	memset (info, 0, sizeof *info);
-	info->control = intf;
-	while (len > 3) {
-		if (buf [1] != USB_DT_CS_INTERFACE)
-			goto next_desc;
-
-		/* use bDescriptorSubType to identify the CDC descriptors.
-		 * We expect devices with CDC header and union descriptors.
-		 * For CDC Ethernet we need the ethernet descriptor.
-		 * For RNDIS, ignore two (pointless) CDC modem descriptors
-		 * in favor of a complicated OID-based RPC scheme doing what
-		 * CDC Ethernet achieves with a simple descriptor.
-		 */
-		switch (buf [2]) {
-		case USB_CDC_HEADER_TYPE:
-			if (info->header) {
-				dev_dbg (&intf->dev, "extra CDC header\n");
-				goto bad_desc;
-			}
-			info->header = (void *) buf;
-			if (info->header->bLength != sizeof *info->header) {
-				dev_dbg (&intf->dev, "CDC header len %u\n",
-					info->header->bLength);
-				goto bad_desc;
-			}
-			break;
-		case USB_CDC_UNION_TYPE:
-			if (info->u) {
-				dev_dbg (&intf->dev, "extra CDC union\n");
-				goto bad_desc;
-			}
-			info->u = (void *) buf;
-			if (info->u->bLength != sizeof *info->u) {
-				dev_dbg (&intf->dev, "CDC union len %u\n",
-					info->u->bLength);
-				goto bad_desc;
-			}
-
-			/* we need a master/control interface (what we're
-			 * probed with) and a slave/data interface; union
-			 * descriptors sort this all out.
-			 */
-			info->control = usb_ifnum_to_if(dev->udev,
-						info->u->bMasterInterface0);
-			info->data = usb_ifnum_to_if(dev->udev,
-						info->u->bSlaveInterface0);
-			if (!info->control || !info->data) {
-				dev_dbg (&intf->dev,
-					"master #%u/%p slave #%u/%p\n",
-					info->u->bMasterInterface0,
-					info->control,
-					info->u->bSlaveInterface0,
-					info->data);
-				goto bad_desc;
-			}
-			if (info->control != intf) {
-				dev_dbg (&intf->dev, "bogus CDC Union\n");
-				/* Ambit USB Cable Modem (and maybe others)
-				 * interchanges master and slave interface.
-				 */
-				if (info->data == intf) {
-					info->data = info->control;
-					info->control = intf;
-				} else
-					goto bad_desc;
-			}
-
-			/* a data interface altsetting does the real i/o */
-			d = &info->data->cur_altsetting->desc;
-			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
-				dev_dbg (&intf->dev, "slave class %u\n",
-					d->bInterfaceClass);
-				goto bad_desc;
-			}
-			break;
-		case USB_CDC_ETHERNET_TYPE:
-			if (info->ether) {
-				dev_dbg (&intf->dev, "extra CDC ether\n");
-				goto bad_desc;
-			}
-			info->ether = (void *) buf;
-			if (info->ether->bLength != sizeof *info->ether) {
-				dev_dbg (&intf->dev, "CDC ether len %u\n",
-					info->ether->bLength);
-				goto bad_desc;
-			}
-			dev->hard_mtu = le16_to_cpu(
-						info->ether->wMaxSegmentSize);
-			/* because of Zaurus, we may be ignoring the host
-			 * side link address we were given.
-			 */
-			break;
-		}
-next_desc:
-		len -= buf [0];	/* bLength */
-		buf += buf [0];
-	}
-
-	if (!info->header || !info->u || (!rndis && !info->ether)) {
-		dev_dbg (&intf->dev, "missing cdc %s%s%sdescriptor\n",
-			info->header ? "" : "header ",
-			info->u ? "" : "union ",
-			info->ether ? "" : "ether ");
-		goto bad_desc;
-	}
-
-	/* claim data interface and set it up ... with side effects.
-	 * network traffic can't flow until an altsetting is enabled.
-	 */
-	status = usb_driver_claim_interface (&usbnet_driver, info->data, dev);
-	if (status < 0)
-		return status;
-	status = usbnet_get_endpoints (dev, info->data);
-	if (status < 0) {
-		/* ensure immediate exit from usbnet_disconnect */
-		usb_set_intfdata(info->data, NULL);
-		usb_driver_release_interface (&usbnet_driver, info->data);
-		return status;
-	}
-
-	/* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */
-	dev->status = NULL;
-	if (info->control->cur_altsetting->desc.bNumEndpoints == 1) {
-		struct usb_endpoint_descriptor	*desc;
-
-		dev->status = &info->control->cur_altsetting->endpoint [0];
-		desc = &dev->status->desc;
-		if (desc->bmAttributes != USB_ENDPOINT_XFER_INT
-				|| !(desc->bEndpointAddress & USB_DIR_IN)
-				|| (le16_to_cpu(desc->wMaxPacketSize)
-					< sizeof (struct usb_cdc_notification))
-				|| !desc->bInterval) {
-			dev_dbg (&intf->dev, "bad notification endpoint\n");
-			dev->status = NULL;
-		}
-	}
-	if (rndis && !dev->status) {
-		dev_dbg (&intf->dev, "missing RNDIS status endpoint\n");
-		usb_set_intfdata(info->data, NULL);
-		usb_driver_release_interface (&usbnet_driver, info->data);
-		return -ENODEV;
-	}
-	return 0;
-
-bad_desc:
-	dev_info (&dev->udev->dev, "bad CDC descriptors\n");
-	return -ENODEV;
-}
-EXPORT_SYMBOL_GPL(usbnet_generic_cdc_bind);
-
-void usbnet_cdc_unbind (struct usbnet *dev, struct usb_interface *intf)
-{
-	struct cdc_state		*info = (void *) &dev->data;
-
-	/* disconnect master --> disconnect slave */
-	if (intf == info->control && info->data) {
-		/* ensure immediate exit from usbnet_disconnect */
-		usb_set_intfdata(info->data, NULL);
-		usb_driver_release_interface (&usbnet_driver, info->data);
-		info->data = NULL;
-	}
-
-	/* and vice versa (just in case) */
-	else if (intf == info->data && info->control) {
-		/* ensure immediate exit from usbnet_disconnect */
-		usb_set_intfdata(info->control, NULL);
-		usb_driver_release_interface (&usbnet_driver, info->control);
-		info->control = NULL;
-	}
-}
-EXPORT_SYMBOL_GPL(usbnet_cdc_unbind);
-
-#endif	/* NEED_GENERIC_CDC */
-
-
-#ifdef	CONFIG_USB_CDCETHER
-#define	HAVE_HARDWARE
-
-/*-------------------------------------------------------------------------
- *
- * Communications Device Class, Ethernet Control model
- *
- * Takes two interfaces.  The DATA interface is inactive till an altsetting
- * is selected.  Configuration data includes class descriptors.
- *
- * This should interop with whatever the 2.4 "CDCEther.c" driver
- * (by Brad Hards) talked with.
- *
- *-------------------------------------------------------------------------*/
-
-#include <linux/ctype.h>
-
-
-static void dumpspeed (struct usbnet *dev, __le32 *speeds)
-{
-	if (netif_msg_timer (dev))
-		devinfo (dev, "link speeds: %u kbps up, %u kbps down",
-			__le32_to_cpu(speeds[0]) / 1000,
-		__le32_to_cpu(speeds[1]) / 1000);
-}
-
-static void cdc_status (struct usbnet *dev, struct urb *urb)
-{
-	struct usb_cdc_notification	*event;
-
-	if (urb->actual_length < sizeof *event)
-		return;
-	
-	/* SPEED_CHANGE can get split into two 8-byte packets */
-	if (test_and_clear_bit (EVENT_STS_SPLIT, &dev->flags)) {
-		dumpspeed (dev, (__le32 *) urb->transfer_buffer);
-		return;
-	}
-
-	event = urb->transfer_buffer;
-	switch (event->bNotificationType) {
-	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
-		if (netif_msg_timer (dev))
-			devdbg (dev, "CDC: carrier %s",
-					event->wValue ? "on" : "off");
-		if (event->wValue)
-			netif_carrier_on(dev->net);
-		else
-			netif_carrier_off(dev->net);
-		break;
-	case USB_CDC_NOTIFY_SPEED_CHANGE:	/* tx/rx rates */
-		if (netif_msg_timer (dev))
-			devdbg (dev, "CDC: speed change (len %d)",
-					urb->actual_length);
-		if (urb->actual_length != (sizeof *event + 8))
-			set_bit (EVENT_STS_SPLIT, &dev->flags);
-		else
-			dumpspeed (dev, (__le32 *) &event[1]);
-		break;
-	// case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:	/* RNDIS; or unsolicited */
-	default:
-		deverr (dev, "CDC: unexpected notification %02x!",
-				 event->bNotificationType);
-		break;
-	}
-}
-
-static u8 nibble (unsigned char c)
-{
-	if (likely (isdigit (c)))
-		return c - '0';
-	c = toupper (c);
-	if (likely (isxdigit (c)))
-		return 10 + c - 'A';
-	return 0;
-}
-
-static inline int
-get_ethernet_addr (struct usbnet *dev, struct usb_cdc_ether_desc *e)
-{
-	int 		tmp, i;
-	unsigned char	buf [13];
-
-	tmp = usb_string (dev->udev, e->iMACAddress, buf, sizeof buf);
-	if (tmp != 12) {
-		dev_dbg (&dev->udev->dev,
-			"bad MAC string %d fetch, %d\n", e->iMACAddress, tmp);
-		if (tmp >= 0)
-			tmp = -EINVAL;
-		return tmp;
-	}
-	for (i = tmp = 0; i < 6; i++, tmp += 2)
-		dev->net->dev_addr [i] =
-			 (nibble (buf [tmp]) << 4) + nibble (buf [tmp + 1]);
-	return 0;
-}
-
-static int cdc_bind (struct usbnet *dev, struct usb_interface *intf)
-{
-	int				status;
-	struct cdc_state		*info = (void *) &dev->data;
-
-	status = usbnet_generic_cdc_bind (dev, intf);
-	if (status < 0)
-		return status;
-
-	status = get_ethernet_addr (dev, info->ether);
-	if (status < 0) {
-		usb_set_intfdata(info->data, NULL);
-		usb_driver_release_interface (&usbnet_driver, info->data);
-		return status;
-	}
-
-	/* FIXME cdc-ether has some multicast code too, though it complains
-	 * in routine cases.  info->ether describes the multicast support.
-	 */
-	return 0;
-}
-
-static const struct driver_info	cdc_info = {
-	.description =	"CDC Ethernet Device",
-	.flags =	FLAG_ETHER,
-	// .check_connect = cdc_check_connect,
-	.bind =		cdc_bind,
-	.unbind =	usbnet_cdc_unbind,
-	.status =	cdc_status,
-};
-
-#endif	/* CONFIG_USB_CDCETHER */
-
-
-
 #ifdef CONFIG_USB_PL2301
 #define	HAVE_HARDWARE
 
@@ -1744,23 +1365,6 @@
 	.driver_info = (unsigned long) &rndis_info,
 },
 #endif
-
-#ifdef	CONFIG_USB_CDCETHER
-{
-	/* CDC Ether uses two interfaces, not necessarily consecutive.
-	 * We match the main interface, ignoring the optional device
-	 * class so we could handle devices that aren't exclusively
-	 * CDC ether.
-	 *
-	 * NOTE:  this match must come AFTER entries working around
-	 * bugs/quirks in a given product (like Zaurus, above).
-	 */
-	USB_INTERFACE_INFO (USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET,
-			USB_CDC_PROTO_NONE),
-	.driver_info = (unsigned long) &cdc_info,
-},
-#endif
-
 	{ },		// END
 };
 MODULE_DEVICE_TABLE (usb, products);
@@ -1782,10 +1386,6 @@
 	// compiler should optimize these out
 	BUG_ON (sizeof (((struct sk_buff *)0)->cb)
 			< sizeof (struct skb_data));
-#ifdef	CONFIG_USB_CDCETHER
-	BUG_ON ((sizeof (((struct usbnet *)0)->data)
-			< sizeof (struct cdc_state)));
-#endif
 
 	random_ether_addr(node_id);
 
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gregkh-2.6/drivers/usb/net/cdc_ether.c	2005-08-15 17:51:25.000000000 -0700
@@ -0,0 +1,500 @@
+/*
+ * CDC Ethernet based networking peripherals
+ * Copyright (C) 2003-2005 by David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// #define	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/config.h>
+#ifdef	CONFIG_USB_DEBUG
+#   define DEBUG
+#endif
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb_cdc.h>
+
+#include "usbnet.h"
+
+
+/*
+ * probes control interface, claims data interface, collects the bulk
+ * endpoints, activates data interface (if needed), maybe sets MTU.
+ * all pure cdc, except for certain firmware workarounds.
+ */
+int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	u8				*buf = intf->cur_altsetting->extra;
+	int				len = intf->cur_altsetting->extralen;
+	struct usb_interface_descriptor	*d;
+	struct cdc_state		*info = (void *) &dev->data;
+	int				status;
+	int				rndis;
+	struct usb_driver		*driver = driver_of(intf);
+
+	if (sizeof dev->data < sizeof *info)
+		return -EDOM;
+
+	/* expect strict spec conformance for the descriptors, but
+	 * cope with firmware which stores them in the wrong place
+	 */
+	if (len == 0 && dev->udev->actconfig->extralen) {
+		/* Motorola SB4100 (and others: Brad Hards says it's
+		 * from a Broadcom design) put CDC descriptors here
+		 */
+		buf = dev->udev->actconfig->extra;
+		len = dev->udev->actconfig->extralen;
+		if (len)
+			dev_dbg(&intf->dev,
+				"CDC descriptors on config\n");
+	}
+
+	/* this assumes that if there's a non-RNDIS vendor variant
+	 * of cdc-acm, it'll fail RNDIS requests cleanly.
+	 */
+	rndis = (intf->cur_altsetting->desc.bInterfaceProtocol == 0xff);
+
+	memset(info, 0, sizeof *info);
+	info->control = intf;
+	while (len > 3) {
+		if (buf [1] != USB_DT_CS_INTERFACE)
+			goto next_desc;
+
+		/* use bDescriptorSubType to identify the CDC descriptors.
+		 * We expect devices with CDC header and union descriptors.
+		 * For CDC Ethernet we need the ethernet descriptor.
+		 * For RNDIS, ignore two (pointless) CDC modem descriptors
+		 * in favor of a complicated OID-based RPC scheme doing what
+		 * CDC Ethernet achieves with a simple descriptor.
+		 */
+		switch (buf [2]) {
+		case USB_CDC_HEADER_TYPE:
+			if (info->header) {
+				dev_dbg(&intf->dev, "extra CDC header\n");
+				goto bad_desc;
+			}
+			info->header = (void *) buf;
+			if (info->header->bLength != sizeof *info->header) {
+				dev_dbg(&intf->dev, "CDC header len %u\n",
+					info->header->bLength);
+				goto bad_desc;
+			}
+			break;
+		case USB_CDC_UNION_TYPE:
+			if (info->u) {
+				dev_dbg(&intf->dev, "extra CDC union\n");
+				goto bad_desc;
+			}
+			info->u = (void *) buf;
+			if (info->u->bLength != sizeof *info->u) {
+				dev_dbg(&intf->dev, "CDC union len %u\n",
+					info->u->bLength);
+				goto bad_desc;
+			}
+
+			/* we need a master/control interface (what we're
+			 * probed with) and a slave/data interface; union
+			 * descriptors sort this all out.
+			 */
+			info->control = usb_ifnum_to_if(dev->udev,
+						info->u->bMasterInterface0);
+			info->data = usb_ifnum_to_if(dev->udev,
+						info->u->bSlaveInterface0);
+			if (!info->control || !info->data) {
+				dev_dbg(&intf->dev,
+					"master #%u/%p slave #%u/%p\n",
+					info->u->bMasterInterface0,
+					info->control,
+					info->u->bSlaveInterface0,
+					info->data);
+				goto bad_desc;
+			}
+			if (info->control != intf) {
+				dev_dbg(&intf->dev, "bogus CDC Union\n");
+				/* Ambit USB Cable Modem (and maybe others)
+				 * interchanges master and slave interface.
+				 */
+				if (info->data == intf) {
+					info->data = info->control;
+					info->control = intf;
+				} else
+					goto bad_desc;
+			}
+
+			/* a data interface altsetting does the real i/o */
+			d = &info->data->cur_altsetting->desc;
+			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
+				dev_dbg(&intf->dev, "slave class %u\n",
+					d->bInterfaceClass);
+				goto bad_desc;
+			}
+			break;
+		case USB_CDC_ETHERNET_TYPE:
+			if (info->ether) {
+				dev_dbg(&intf->dev, "extra CDC ether\n");
+				goto bad_desc;
+			}
+			info->ether = (void *) buf;
+			if (info->ether->bLength != sizeof *info->ether) {
+				dev_dbg(&intf->dev, "CDC ether len %u\n",
+					info->ether->bLength);
+				goto bad_desc;
+			}
+			dev->hard_mtu = le16_to_cpu(
+						info->ether->wMaxSegmentSize);
+			/* because of Zaurus, we may be ignoring the host
+			 * side link address we were given.
+			 */
+			break;
+		}
+next_desc:
+		len -= buf [0];	/* bLength */
+		buf += buf [0];
+	}
+
+	if (!info->header || !info->u || (!rndis && !info->ether)) {
+		dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n",
+			info->header ? "" : "header ",
+			info->u ? "" : "union ",
+			info->ether ? "" : "ether ");
+		goto bad_desc;
+	}
+
+	/* claim data interface and set it up ... with side effects.
+	 * network traffic can't flow until an altsetting is enabled.
+	 */
+	status = usb_driver_claim_interface(driver, info->data, dev);
+	if (status < 0)
+		return status;
+	status = usbnet_get_endpoints(dev, info->data);
+	if (status < 0) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		return status;
+	}
+
+	/* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */
+	dev->status = NULL;
+	if (info->control->cur_altsetting->desc.bNumEndpoints == 1) {
+		struct usb_endpoint_descriptor	*desc;
+
+		dev->status = &info->control->cur_altsetting->endpoint [0];
+		desc = &dev->status->desc;
+		if (desc->bmAttributes != USB_ENDPOINT_XFER_INT
+				|| !(desc->bEndpointAddress & USB_DIR_IN)
+				|| (le16_to_cpu(desc->wMaxPacketSize)
+					< sizeof(struct usb_cdc_notification))
+				|| !desc->bInterval) {
+			dev_dbg(&intf->dev, "bad notification endpoint\n");
+			dev->status = NULL;
+		}
+	}
+	if (rndis && !dev->status) {
+		dev_dbg(&intf->dev, "missing RNDIS status endpoint\n");
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		return -ENODEV;
+	}
+	return 0;
+
+bad_desc:
+	dev_info(&dev->udev->dev, "bad CDC descriptors\n");
+	return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(usbnet_generic_cdc_bind);
+
+void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_state		*info = (void *) &dev->data;
+	struct usb_driver		*driver = driver_of(intf);
+
+	/* disconnect master --> disconnect slave */
+	if (intf == info->control && info->data) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		info->data = NULL;
+	}
+
+	/* and vice versa (just in case) */
+	else if (intf == info->data && info->control) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->control, NULL);
+		usb_driver_release_interface(driver, info->control);
+		info->control = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(usbnet_cdc_unbind);
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Communications Device Class, Ethernet Control model
+ *
+ * Takes two interfaces.  The DATA interface is inactive till an altsetting
+ * is selected.  Configuration data includes class descriptors.
+ *
+ * This should interop with whatever the 2.4 "CDCEther.c" driver
+ * (by Brad Hards) talked with.
+ *
+ *-------------------------------------------------------------------------*/
+
+
+static void dumpspeed(struct usbnet *dev, __le32 *speeds)
+{
+	if (netif_msg_timer(dev))
+		devinfo(dev, "link speeds: %u kbps up, %u kbps down",
+			__le32_to_cpu(speeds[0]) / 1000,
+		__le32_to_cpu(speeds[1]) / 1000);
+}
+
+static void cdc_status(struct usbnet *dev, struct urb *urb)
+{
+	struct usb_cdc_notification	*event;
+
+	if (urb->actual_length < sizeof *event)
+		return;
+
+	/* SPEED_CHANGE can get split into two 8-byte packets */
+	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
+		dumpspeed(dev, (__le32 *) urb->transfer_buffer);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		if (netif_msg_timer(dev))
+			devdbg(dev, "CDC: carrier %s",
+					event->wValue ? "on" : "off");
+		if (event->wValue)
+			netif_carrier_on(dev->net);
+		else
+			netif_carrier_off(dev->net);
+		break;
+	case USB_CDC_NOTIFY_SPEED_CHANGE:	/* tx/rx rates */
+		if (netif_msg_timer(dev))
+			devdbg(dev, "CDC: speed change (len %d)",
+					urb->actual_length);
+		if (urb->actual_length != (sizeof *event + 8))
+			set_bit(EVENT_STS_SPLIT, &dev->flags);
+		else
+			dumpspeed(dev, (__le32 *) &event[1]);
+		break;
+	// case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:	/* RNDIS; or unsolicited */
+	default:
+		deverr(dev, "CDC: unexpected notification %02x!",
+				 event->bNotificationType);
+		break;
+	}
+}
+
+static u8 nibble(unsigned char c)
+{
+	if (likely(isdigit(c)))
+		return c - '0';
+	c = toupper(c);
+	if (likely(isxdigit(c)))
+		return 10 + c - 'A';
+	return 0;
+}
+
+static inline int
+get_ethernet_addr(struct usbnet *dev, struct usb_cdc_ether_desc *e)
+{
+	int 		tmp, i;
+	unsigned char	buf [13];
+
+	tmp = usb_string(dev->udev, e->iMACAddress, buf, sizeof buf);
+	if (tmp != 12) {
+		dev_dbg(&dev->udev->dev,
+			"bad MAC string %d fetch, %d\n", e->iMACAddress, tmp);
+		if (tmp >= 0)
+			tmp = -EINVAL;
+		return tmp;
+	}
+	for (i = tmp = 0; i < 6; i++, tmp += 2)
+		dev->net->dev_addr [i] =
+			(nibble(buf [tmp]) << 4) + nibble(buf [tmp + 1]);
+	return 0;
+}
+
+static int cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int				status;
+	struct cdc_state		*info = (void *) &dev->data;
+
+	status = usbnet_generic_cdc_bind(dev, intf);
+	if (status < 0)
+		return status;
+
+	status = get_ethernet_addr(dev, info->ether);
+	if (status < 0) {
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver_of(intf), info->data);
+		return status;
+	}
+
+	/* FIXME cdc-ether has some multicast code too, though it complains
+	 * in routine cases.  info->ether describes the multicast support.
+	 * Implement that here, manipulating the cdc filter as needed.
+	 */
+	return 0;
+}
+
+static const struct driver_info	cdc_info = {
+	.description =	"CDC Ethernet Device",
+	.flags =	FLAG_ETHER,
+	// .check_connect = cdc_check_connect,
+	.bind =		cdc_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.status =	cdc_status,
+};
+
+/*-------------------------------------------------------------------------*/
+
+
+static const struct usb_device_id	products [] = {
+/*
+ * First, blacklist any products that are egregiously nonconformant
+ * with the CDC Ethernet specs.  Minor braindamage we cope with;
+ * if they're not even trying, they need a separate driver.
+ */
+
+#define	ZAURUS_MASTER_INTERFACE \
+	.bInterfaceClass	= USB_CLASS_COMM, \
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_ETHERNET, \
+	.bInterfaceProtocol	= USB_CDC_PROTO_NONE
+
+/* SA-1100 based Sharp Zaurus ("collie"), or compatible;
+ * wire-incompatible with true CDC Ethernet implementations.
+ * (And, it seems, needlessly so...)
+ */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8004,
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+},
+
+/* PXA-25x based Sharp Zaurii.  Note that it seems some of these
+ * (later models especially) may have shipped only with firmware
+ * advertising false "CDC MDLM" compatibility ... but we're not
+ * clear which models that was, so for now let's assume the worst.
+ */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8005,	/* A-300 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8006,	/* B-500/SL-5600 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+	          | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8007,	/* C-700 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9031,	/* C-750 C-760 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9032,	/* SL-6000 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	/* reported with some C860 units */
+	.idProduct              = 0x9050,	/* C-860 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+},
+
+	/* CDC Ether uses two interfaces, not necessarily consecutive.
+	 * We match the main interface, ignoring the optional device
+	 * class so we could handle devices that aren't exclusively
+	 * CDC ether.
+	 *
+	 * NOTE:  this match must come AFTER entries working around
+	 * bugs/quirks in a given product (like Zaurus, above).
+	 */
+{
+	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET,
+			USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &cdc_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver cdc_driver = {
+	.owner =	THIS_MODULE,
+	.name =		"cdc_ether",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+
+static int __init cdc_init(void)
+{
+	BUG_ON((sizeof(((struct usbnet *)0)->data)
+			< sizeof(struct cdc_state)));
+
+ 	return usb_register(&cdc_driver);
+}
+module_init(cdc_init);
+
+static void __exit cdc_exit(void)
+{
+ 	usb_deregister(&cdc_driver);
+}
+module_exit(cdc_exit);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("USB CDC Ethernet devices");
+MODULE_LICENSE("GPL");