USB: ch341: forward USB errors to USB serial core
[pandora-kernel.git] / drivers / usb / class / cdc-wdm.c
index efe6849..d34005b 100644 (file)
@@ -31,6 +31,8 @@
 #define DRIVER_AUTHOR "Oliver Neukum"
 #define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
 
+#define HUAWEI_VENDOR_ID       0x12D1
+
 static const struct usb_device_id wdm_ids[] = {
        {
                .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
@@ -38,6 +40,20 @@ static const struct usb_device_id wdm_ids[] = {
                .bInterfaceClass = USB_CLASS_COMM,
                .bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
        },
+       {
+               /* 
+                * Huawei E392, E398 and possibly other Qualcomm based modems
+                * embed the Qualcomm QMI protocol inside CDC on CDC ECM like
+                * control interfaces.  Userspace access to this is required
+                * to configure the accompanying data interface
+                */
+               .match_flags        = USB_DEVICE_ID_MATCH_VENDOR |
+                                       USB_DEVICE_ID_MATCH_INT_INFO,
+               .idVendor           = HUAWEI_VENDOR_ID,
+               .bInterfaceClass    = USB_CLASS_VENDOR_SPEC,
+               .bInterfaceSubClass = 1,
+               .bInterfaceProtocol = 9, /* NOTE: CDC ECM control interface! */
+       },
        { }
 };
 
@@ -54,9 +70,12 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
 #define WDM_POLL_RUNNING       6
 #define WDM_RESPONDING         7
 #define WDM_SUSPENDING         8
+#define WDM_OVERFLOW           10
 
 #define WDM_MAX                        16
 
+/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
+#define WDM_DEFAULT_BUFSIZE    256
 
 static DEFINE_MUTEX(wdm_mutex);
 
@@ -88,7 +107,8 @@ struct wdm_device {
        int                     count;
        dma_addr_t              shandle;
        dma_addr_t              ihandle;
-       struct mutex            lock;
+       struct mutex            wlock;
+       struct mutex            rlock;
        wait_queue_head_t       wait;
        struct work_struct      rxwork;
        int                     werr;
@@ -105,8 +125,9 @@ static void wdm_out_callback(struct urb *urb)
        spin_lock(&desc->iuspin);
        desc->werr = urb->status;
        spin_unlock(&desc->iuspin);
-       clear_bit(WDM_IN_USE, &desc->flags);
        kfree(desc->outbuf);
+       desc->outbuf = NULL;
+       clear_bit(WDM_IN_USE, &desc->flags);
        wake_up(&desc->wait);
 }
 
@@ -114,6 +135,7 @@ static void wdm_in_callback(struct urb *urb)
 {
        struct wdm_device *desc = urb->context;
        int status = urb->status;
+       int length = urb->actual_length;
 
        spin_lock(&desc->iuspin);
        clear_bit(WDM_RESPONDING, &desc->flags);
@@ -144,9 +166,17 @@ static void wdm_in_callback(struct urb *urb)
        }
 
        desc->rerr = status;
-       desc->reslength = urb->actual_length;
-       memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
-       desc->length += desc->reslength;
+       if (length + desc->length > desc->wMaxCommand) {
+               /* The buffer would overflow */
+               set_bit(WDM_OVERFLOW, &desc->flags);
+       } else {
+               /* we may already be in overflow */
+               if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
+                       memmove(desc->ubuf + desc->length, desc->inbuf, length);
+                       desc->length += length;
+                       desc->reslength = length;
+               }
+       }
 skip_error:
        wake_up(&desc->wait);
 
@@ -157,6 +187,7 @@ skip_error:
 static void wdm_int_callback(struct urb *urb)
 {
        int rv = 0;
+       int responding;
        int status = urb->status;
        struct wdm_device *desc;
        struct usb_ctrlrequest *req;
@@ -193,7 +224,7 @@ static void wdm_int_callback(struct urb *urb)
        case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
                dev_dbg(&desc->intf->dev,
                        "NOTIFY_RESPONSE_AVAILABLE received: index %d len %d",
-                       dr->wIndex, dr->wLength);
+                       le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength));
                break;
 
        case USB_CDC_NOTIFY_NETWORK_CONNECTION:
@@ -206,14 +237,16 @@ static void wdm_int_callback(struct urb *urb)
                clear_bit(WDM_POLL_RUNNING, &desc->flags);
                dev_err(&desc->intf->dev,
                        "unknown notification %d received: index %d len %d\n",
-                       dr->bNotificationType, dr->wIndex, dr->wLength);
+                       dr->bNotificationType,
+                       le16_to_cpu(dr->wIndex),
+                       le16_to_cpu(dr->wLength));
                goto exit;
        }
 
        req->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
        req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
        req->wValue = 0;
-       req->wIndex = desc->inum;
+       req->wIndex = desc->inum; /* already converted */
        req->wLength = cpu_to_le16(desc->wMaxCommand);
 
        usb_fill_control_urb(
@@ -230,8 +263,8 @@ static void wdm_int_callback(struct urb *urb)
        desc->response->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        spin_lock(&desc->iuspin);
        clear_bit(WDM_READ, &desc->flags);
-       set_bit(WDM_RESPONDING, &desc->flags);
-       if (!test_bit(WDM_DISCONNECTING, &desc->flags)
+       responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
+       if (!responding && !test_bit(WDM_DISCONNECTING, &desc->flags)
                && !test_bit(WDM_SUSPENDING, &desc->flags)) {
                rv = usb_submit_urb(desc->response, GFP_ATOMIC);
                dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d",
@@ -281,7 +314,7 @@ static void cleanup(struct wdm_device *desc)
                          desc->sbuf,
                          desc->validity->transfer_dma);
        usb_free_coherent(interface_to_usbdev(desc->intf),
-                         desc->bMaxPacketSize0,
+                         desc->wMaxCommand,
                          desc->inbuf,
                          desc->response->transfer_dma);
        kfree(desc->orq);
@@ -309,7 +342,7 @@ static ssize_t wdm_write
        if (we < 0)
                return -EIO;
 
-       desc->outbuf = buf = kmalloc(count, GFP_KERNEL);
+       buf = kmalloc(count, GFP_KERNEL);
        if (!buf) {
                rv = -ENOMEM;
                goto outnl;
@@ -323,7 +356,7 @@ static ssize_t wdm_write
        }
 
        /* concurrent writes and disconnect */
-       r = mutex_lock_interruptible(&desc->lock);
+       r = mutex_lock_interruptible(&desc->wlock);
        rv = -ERESTARTSYS;
        if (r) {
                kfree(buf);
@@ -370,23 +403,25 @@ static ssize_t wdm_write
                             USB_RECIP_INTERFACE);
        req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
        req->wValue = 0;
-       req->wIndex = desc->inum;
+       req->wIndex = desc->inum; /* already converted */
        req->wLength = cpu_to_le16(count);
        set_bit(WDM_IN_USE, &desc->flags);
+       desc->outbuf = buf;
 
        rv = usb_submit_urb(desc->command, GFP_KERNEL);
        if (rv < 0) {
                kfree(buf);
+               desc->outbuf = NULL;
                clear_bit(WDM_IN_USE, &desc->flags);
                dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
        } else {
                dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
-                       req->wIndex);
+                       le16_to_cpu(req->wIndex));
        }
 out:
        usb_autopm_put_interface(desc->intf);
 outnp:
-       mutex_unlock(&desc->lock);
+       mutex_unlock(&desc->wlock);
 outnl:
        return rv < 0 ? rv : count;
 }
@@ -394,22 +429,28 @@ outnl:
 static ssize_t wdm_read
 (struct file *file, char __user *buffer, size_t count, loff_t *ppos)
 {
-       int rv, cntr = 0;
+       int rv, cntr;
        int i = 0;
        struct wdm_device *desc = file->private_data;
 
 
-       rv = mutex_lock_interruptible(&desc->lock); /*concurrent reads */
+       rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */
        if (rv < 0)
                return -ERESTARTSYS;
 
-       if (desc->length == 0) {
+       cntr = ACCESS_ONCE(desc->length);
+       if (cntr == 0) {
                desc->read = 0;
 retry:
                if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
                        rv = -ENODEV;
                        goto err;
                }
+               if (test_bit(WDM_OVERFLOW, &desc->flags)) {
+                       clear_bit(WDM_OVERFLOW, &desc->flags);
+                       rv = -ENOBUFS;
+                       goto err;
+               }
                i++;
                if (file->f_flags & O_NONBLOCK) {
                        if (!test_bit(WDM_READ, &desc->flags)) {
@@ -449,21 +490,27 @@ retry:
                        spin_unlock_irq(&desc->iuspin);
                        goto retry;
                }
+
                if (!desc->reslength) { /* zero length read */
+                       dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__);
+                       clear_bit(WDM_READ, &desc->flags);
                        spin_unlock_irq(&desc->iuspin);
                        goto retry;
                }
-               clear_bit(WDM_READ, &desc->flags);
+               cntr = desc->length;
                spin_unlock_irq(&desc->iuspin);
        }
 
-       cntr = count > desc->length ? desc->length : count;
+       if (cntr > count)
+               cntr = count;
        rv = copy_to_user(buffer, desc->ubuf, cntr);
        if (rv > 0) {
                rv = -EFAULT;
                goto err;
        }
 
+       spin_lock_irq(&desc->iuspin);
+
        for (i = 0; i < desc->length - cntr; i++)
                desc->ubuf[i] = desc->ubuf[i + cntr];
 
@@ -471,10 +518,13 @@ retry:
        /* in case we had outstanding data */
        if (!desc->length)
                clear_bit(WDM_READ, &desc->flags);
+
+       spin_unlock_irq(&desc->iuspin);
+
        rv = cntr;
 
 err:
-       mutex_unlock(&desc->lock);
+       mutex_unlock(&desc->rlock);
        return rv;
 }
 
@@ -483,11 +533,13 @@ static int wdm_flush(struct file *file, fl_owner_t id)
        struct wdm_device *desc = file->private_data;
 
        wait_event(desc->wait, !test_bit(WDM_IN_USE, &desc->flags));
-       if (desc->werr < 0)
+
+       /* cannot dereference desc->intf if WDM_DISCONNECTING */
+       if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags))
                dev_err(&desc->intf->dev, "Error in flush path: %d\n",
                        desc->werr);
 
-       return desc->werr;
+       return usb_translate_errors(desc->werr);
 }
 
 static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
@@ -498,7 +550,7 @@ static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
 
        spin_lock_irqsave(&desc->iuspin, flags);
        if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
-               mask = POLLERR;
+               mask = POLLHUP | POLLERR;
                spin_unlock_irqrestore(&desc->iuspin, flags);
                goto desc_out;
        }
@@ -540,7 +592,8 @@ static int wdm_open(struct inode *inode, struct file *file)
        }
        intf->needs_remote_wakeup = 1;
 
-       mutex_lock(&desc->lock);
+       /* using write lock to protect desc->count */
+       mutex_lock(&desc->wlock);
        if (!desc->count++) {
                desc->werr = 0;
                desc->rerr = 0;
@@ -553,7 +606,7 @@ static int wdm_open(struct inode *inode, struct file *file)
        } else {
                rv = 0;
        }
-       mutex_unlock(&desc->lock);
+       mutex_unlock(&desc->wlock);
        usb_autopm_put_interface(desc->intf);
 out:
        mutex_unlock(&wdm_mutex);
@@ -565,15 +618,22 @@ static int wdm_release(struct inode *inode, struct file *file)
        struct wdm_device *desc = file->private_data;
 
        mutex_lock(&wdm_mutex);
-       mutex_lock(&desc->lock);
+
+       /* using write lock to protect desc->count */
+       mutex_lock(&desc->wlock);
        desc->count--;
-       mutex_unlock(&desc->lock);
+       mutex_unlock(&desc->wlock);
 
        if (!desc->count) {
-               dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
-               kill_urbs(desc);
-               if (!test_bit(WDM_DISCONNECTING, &desc->flags))
+               if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
+                       dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
+                       kill_urbs(desc);
                        desc->intf->needs_remote_wakeup = 0;
+               } else {
+                       /* must avoid dev_printk here as desc->intf is invalid */
+                       pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
+                       cleanup(desc);
+               }
        }
        mutex_unlock(&wdm_mutex);
        return 0;
@@ -601,16 +661,20 @@ static void wdm_rxwork(struct work_struct *work)
 {
        struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);
        unsigned long flags;
-       int rv;
+       int rv = 0;
+       int responding;
 
        spin_lock_irqsave(&desc->iuspin, flags);
        if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
                spin_unlock_irqrestore(&desc->iuspin, flags);
        } else {
+               responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
                spin_unlock_irqrestore(&desc->iuspin, flags);
-               rv = usb_submit_urb(desc->response, GFP_KERNEL);
+               if (!responding)
+                       rv = usb_submit_urb(desc->response, GFP_KERNEL);
                if (rv < 0 && rv != -EPERM) {
                        spin_lock_irqsave(&desc->iuspin, flags);
+                       clear_bit(WDM_RESPONDING, &desc->flags);
                        if (!test_bit(WDM_DISCONNECTING, &desc->flags))
                                schedule_work(&desc->rxwork);
                        spin_unlock_irqrestore(&desc->iuspin, flags);
@@ -630,7 +694,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
        struct usb_cdc_dmm_desc *dmhd;
        u8 *buffer = intf->altsetting->extra;
        int buflen = intf->altsetting->extralen;
-       u16 maxcom = 0;
+       u16 maxcom = WDM_DEFAULT_BUFSIZE;
 
        if (!buffer)
                goto out;
@@ -665,7 +729,8 @@ next_desc:
        desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
        if (!desc)
                goto out;
-       mutex_init(&desc->lock);
+       mutex_init(&desc->rlock);
+       mutex_init(&desc->wlock);
        spin_lock_init(&desc->iuspin);
        init_waitqueue_head(&desc->wait);
        desc->wMaxCommand = maxcom;
@@ -716,7 +781,7 @@ next_desc:
                goto err;
 
        desc->inbuf = usb_alloc_coherent(interface_to_usbdev(intf),
-                                        desc->bMaxPacketSize0,
+                                        desc->wMaxCommand,
                                         GFP_KERNEL,
                                         &desc->response->transfer_dma);
        if (!desc->inbuf)
@@ -746,7 +811,7 @@ out:
 err3:
        usb_set_intfdata(intf, NULL);
        usb_free_coherent(interface_to_usbdev(desc->intf),
-                         desc->bMaxPacketSize0,
+                         desc->wMaxCommand,
                        desc->inbuf,
                        desc->response->transfer_dma);
 err2:
@@ -779,13 +844,17 @@ static void wdm_disconnect(struct usb_interface *intf)
        /* to terminate pending flushes */
        clear_bit(WDM_IN_USE, &desc->flags);
        spin_unlock_irqrestore(&desc->iuspin, flags);
-       mutex_lock(&desc->lock);
+       wake_up_all(&desc->wait);
+       mutex_lock(&desc->rlock);
+       mutex_lock(&desc->wlock);
        kill_urbs(desc);
        cancel_work_sync(&desc->rxwork);
-       mutex_unlock(&desc->lock);
-       wake_up_all(&desc->wait);
+       mutex_unlock(&desc->wlock);
+       mutex_unlock(&desc->rlock);
        if (!desc->count)
                cleanup(desc);
+       else
+               dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count);
        mutex_unlock(&wdm_mutex);
 }
 
@@ -798,8 +867,10 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
        dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
 
        /* if this is an autosuspend the caller does the locking */
-       if (!PMSG_IS_AUTO(message))
-               mutex_lock(&desc->lock);
+       if (!PMSG_IS_AUTO(message)) {
+               mutex_lock(&desc->rlock);
+               mutex_lock(&desc->wlock);
+       }
        spin_lock_irq(&desc->iuspin);
 
        if (PMSG_IS_AUTO(message) &&
@@ -815,8 +886,10 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
                kill_urbs(desc);
                cancel_work_sync(&desc->rxwork);
        }
-       if (!PMSG_IS_AUTO(message))
-               mutex_unlock(&desc->lock);
+       if (!PMSG_IS_AUTO(message)) {
+               mutex_unlock(&desc->wlock);
+               mutex_unlock(&desc->rlock);
+       }
 
        return rv;
 }
@@ -854,7 +927,8 @@ static int wdm_pre_reset(struct usb_interface *intf)
 {
        struct wdm_device *desc = usb_get_intfdata(intf);
 
-       mutex_lock(&desc->lock);
+       mutex_lock(&desc->rlock);
+       mutex_lock(&desc->wlock);
        kill_urbs(desc);
 
        /*
@@ -875,8 +949,10 @@ static int wdm_post_reset(struct usb_interface *intf)
        struct wdm_device *desc = usb_get_intfdata(intf);
        int rv;
 
+       clear_bit(WDM_OVERFLOW, &desc->flags);
        rv = recover_from_urb_loss(desc);
-       mutex_unlock(&desc->lock);
+       mutex_unlock(&desc->wlock);
+       mutex_unlock(&desc->rlock);
        return 0;
 }