ChangeSet 1.1276.1.53, 2003/08/27 17:31:58-07:00, david-b@pacbell.net

[PATCH] USB: usbnet, cdc ethernet descriptor parsing fixes

This makes the new CDC Ethernet code handle more devices:

  - Uses the active config, not just the default one, if it's
    coping "descriptors in wrong place" quirk.  (bugfix)

  - Uses usb_ifnum_to_if() to get interfaces.  (bugfix)

  - AMBIT USB cable modems have bogus CDC Union descriptors;
    workaround by switching master and slave.  (add quirk)

  - To make it easier the next time we run into firmware
    that violates the class spec, add debug messages saying
    exactly why it's giving up on a given CDC device.

Net result, this code now handles at least one more
cable modem design.


 drivers/usb/net/usbnet.c |   89 +++++++++++++++++++++++++++++++++++------------
 1 files changed, 68 insertions(+), 21 deletions(-)


diff -Nru a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c
--- a/drivers/usb/net/usbnet.c	Tue Sep  2 12:43:15 2003
+++ b/drivers/usb/net/usbnet.c	Tue Sep  2 12:43:15 2003
@@ -773,12 +773,15 @@
 	/* expect strict spec conformance for the descriptors, but
 	 * cope with firmware which stores them in the wrong place
 	 */
-	if (len == 0 && dev->udev->config->extralen) {
-		/* Motorola SB4100 (and maybe others) put
-		 * CDC descriptors here
+	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->config->extra;
-		len = dev->udev->config->extralen;
+		buf = dev->udev->actconfig->extra;
+		len = dev->udev->actconfig->extralen;
+		if (len)
+			dev_dbg (&intf->dev,
+				"CDC descriptors on config\n");
 	}
 
 	memset (info, 0, sizeof *info);
@@ -793,48 +796,92 @@
 		 */
 		switch (buf [2]) {
 		case 0x00:		/* Header, mostly useless */
-			if (info->header)
+			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)
+			if (info->header->bLength != sizeof *info->header) {
+				dev_dbg (&intf->dev, "CDC header len %u\n",
+					info->header->bLength);
 				goto bad_desc;
+			}
 			break;
 		case 0x06:		/* Union (groups interfaces) */
-			if (info->u)
+			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)
-				goto bad_desc;
-			d = &intf->altsetting->desc;
-			if (info->u->bMasterInterface0 != d->bInterfaceNumber)
-				goto bad_desc;
-			info->data = dev->udev->actconfig->interface[0];
-			if (intf != (info->data + info->u->bMasterInterface0))
+			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 */
-			info->data += info->u->bSlaveInterface0;
 			d = &info->data->altsetting->desc;
-			if (info->u->bSlaveInterface0 != d->bInterfaceNumber
-				    || d->bInterfaceClass != USB_CLASS_CDC_DATA)
+			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
+				dev_dbg (&intf->dev, "slave class %u\n",
+					d->bInterfaceClass);
 				goto bad_desc;
+			}
 			if (usb_interface_claimed (info->data))
 				return -EBUSY;
 			break;
 		case 0x0F:		/* Ethernet Networking */
-			if (info->ether)
+			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)
+			if (info->ether->bLength != sizeof *info->ether) {
+				dev_dbg (&intf->dev, "CDC ether len %u\n",
+					info->u->bLength);
 				goto bad_desc;
+			}
 			break;
 		}
 next_desc:
 		len -= buf [0];	/* bLength */
 		buf += buf [0];
 	}
-	if (!info->header || !info ->u || !info->ether)
+	if (!info->header || !info ->u || !info->ether) {
+		dev_dbg (&intf->dev, "missing cdc %s%s%sdescriptor\n",
+			info->header ? "" : "header ",
+			info->u ? "" : "union ",
+			info->ether ? "" : "ether ");
 		goto bad_desc;
+	}
 
 #ifdef CONFIG_USB_ZAURUS
 	/* Zaurus ethernet addresses aren't unique ... */
