Commit bae6e21c authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[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.
parent 4c077592
...@@ -773,12 +773,15 @@ static int cdc_bind (struct usbnet *dev, struct usb_interface *intf) ...@@ -773,12 +773,15 @@ static int cdc_bind (struct usbnet *dev, struct usb_interface *intf)
/* expect strict spec conformance for the descriptors, but /* expect strict spec conformance for the descriptors, but
* cope with firmware which stores them in the wrong place * cope with firmware which stores them in the wrong place
*/ */
if (len == 0 && dev->udev->config->extralen) { if (len == 0 && dev->udev->actconfig->extralen) {
/* Motorola SB4100 (and maybe others) put /* Motorola SB4100 (and others: Brad Hards says it's
* CDC descriptors here * from a Broadcom design) put CDC descriptors here
*/ */
buf = dev->udev->config->extra; buf = dev->udev->actconfig->extra;
len = dev->udev->config->extralen; len = dev->udev->actconfig->extralen;
if (len)
dev_dbg (&intf->dev,
"CDC descriptors on config\n");
} }
memset (info, 0, sizeof *info); memset (info, 0, sizeof *info);
...@@ -793,48 +796,92 @@ static int cdc_bind (struct usbnet *dev, struct usb_interface *intf) ...@@ -793,48 +796,92 @@ static int cdc_bind (struct usbnet *dev, struct usb_interface *intf)
*/ */
switch (buf [2]) { switch (buf [2]) {
case 0x00: /* Header, mostly useless */ case 0x00: /* Header, mostly useless */
if (info->header) if (info->header) {
dev_dbg (&intf->dev, "extra CDC header\n");
goto bad_desc; goto bad_desc;
}
info->header = (void *) buf; 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; goto bad_desc;
}
break; break;
case 0x06: /* Union (groups interfaces) */ case 0x06: /* Union (groups interfaces) */
if (info->u) if (info->u) {
dev_dbg (&intf->dev, "extra CDC union\n");
goto bad_desc; goto bad_desc;
}
info->u = (void *) buf; info->u = (void *) buf;
if (info->u->bLength != sizeof *info->u) if (info->u->bLength != sizeof *info->u) {
dev_dbg (&intf->dev, "CDC union len %u\n",
info->u->bLength);
goto bad_desc; goto bad_desc;
d = &intf->altsetting->desc; }
if (info->u->bMasterInterface0 != d->bInterfaceNumber)
goto bad_desc; /* we need a master/control interface (what we're
info->data = dev->udev->actconfig->interface[0]; * probed with) and a slave/data interface; union
if (intf != (info->data + info->u->bMasterInterface0)) * 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; 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 */ /* a data interface altsetting does the real i/o */
info->data += info->u->bSlaveInterface0;
d = &info->data->altsetting->desc; d = &info->data->altsetting->desc;
if (info->u->bSlaveInterface0 != d->bInterfaceNumber if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
|| d->bInterfaceClass != USB_CLASS_CDC_DATA) dev_dbg (&intf->dev, "slave class %u\n",
d->bInterfaceClass);
goto bad_desc; goto bad_desc;
}
if (usb_interface_claimed (info->data)) if (usb_interface_claimed (info->data))
return -EBUSY; return -EBUSY;
break; break;
case 0x0F: /* Ethernet Networking */ case 0x0F: /* Ethernet Networking */
if (info->ether) if (info->ether) {
dev_dbg (&intf->dev, "extra CDC ether\n");
goto bad_desc; goto bad_desc;
}
info->ether = (void *) buf; 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; goto bad_desc;
}
break; break;
} }
next_desc: next_desc:
len -= buf [0]; /* bLength */ len -= buf [0]; /* bLength */
buf += buf [0]; 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; goto bad_desc;
}
#ifdef CONFIG_USB_ZAURUS #ifdef CONFIG_USB_ZAURUS
/* Zaurus ethernet addresses aren't unique ... */ /* Zaurus ethernet addresses aren't unique ... */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment