net: cdc_ether: allow combined control and data interface
authorBjørn Mork <bjorn@mork.no>
Sat, 29 Jun 2013 10:03:06 +0000 (12:03 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 2 Jul 2013 08:47:41 +0000 (01:47 -0700)
Some Icera based Huawei modems handled by this driver are not
completely CDC ECM compliant, using the same USB interface for both
control and data. The CDC functional descriptors include a Union
naming this interface as both master and slave, so it is supportable
by relaxing the descriptor parsing in case these interfaces are
identical.

This has been tested on a Huawei K3806 and verified to add support
for that device.

Reported-and-tested-by: Enrico Mioso <mrkiko.rs@gmail.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Acked-by: Oliver Neukum <oliver@neukum.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/cdc_ether.c

index 04ee044..4393f14 100644 (file)
@@ -215,6 +215,10 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
                                        goto bad_desc;
                        }
 
+                       /* some devices merge these - skip class check */
+                       if (info->control == info->data)
+                               goto next_desc;
+
                        /* a data interface altsetting does the real i/o */
                        d = &info->data->cur_altsetting->desc;
                        if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
@@ -304,19 +308,23 @@ next_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;
+       if (info->data != info->control) {
+               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);
+               if (info->data != info->control)
+                       usb_driver_release_interface(driver, info->data);
                return status;
        }
 
        /* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */
-       dev->status = NULL;
+       if (info->data != info->control)
+               dev->status = NULL;
        if (info->control->cur_altsetting->desc.bNumEndpoints == 1) {
                struct usb_endpoint_descriptor  *desc;
 
@@ -349,6 +357,10 @@ 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);
 
+       /* combined interface - nothing  to do */
+       if (info->data == info->control)
+               return;
+
        /* disconnect master --> disconnect slave */
        if (intf == info->control && info->data) {
                /* ensure immediate exit from usbnet_disconnect */