X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=blobdiff_plain;f=drivers%2Fusb%2Fclass%2Fcdc-wdm.c;h=7ca54d4dea929aee1b8812a4bc58afe968e1a442;hp=1c50baff7725ec2d8e368b3c34f50925a76f3804;hb=711c68b3c0f7a924ffbee4aa962d8f62b85188ff;hpb=282f445a779ed76fca9884fe377bf56a3088b208 diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 1c50baff7725..7ca54d4dea92 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -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_RESETTING 9 #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); @@ -80,7 +99,6 @@ struct wdm_device { u16 bufsize; u16 wMaxCommand; u16 wMaxPacketSize; - u16 bMaxPacketSize0; __le16 inum; int reslength; int length; @@ -88,7 +106,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; @@ -159,11 +178,9 @@ static void wdm_int_callback(struct urb *urb) int rv = 0; int status = urb->status; struct wdm_device *desc; - struct usb_ctrlrequest *req; struct usb_cdc_notification *dr; desc = urb->context; - req = desc->irq; dr = (struct usb_cdc_notification *)desc->sbuf; if (status) { @@ -210,24 +227,6 @@ static void wdm_int_callback(struct urb *urb) 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->wLength = cpu_to_le16(desc->wMaxCommand); - - usb_fill_control_urb( - desc->response, - interface_to_usbdev(desc->intf), - /* using common endpoint 0 */ - usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), - (unsigned char *)req, - desc->inbuf, - desc->wMaxCommand, - wdm_in_callback, - desc - ); - 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); @@ -276,14 +275,8 @@ static void free_urbs(struct wdm_device *desc) static void cleanup(struct wdm_device *desc) { - usb_free_coherent(interface_to_usbdev(desc->intf), - desc->wMaxPacketSize, - desc->sbuf, - desc->validity->transfer_dma); - usb_free_coherent(interface_to_usbdev(desc->intf), - desc->bMaxPacketSize0, - desc->inbuf, - desc->response->transfer_dma); + kfree(desc->sbuf); + kfree(desc->inbuf); kfree(desc->orq); kfree(desc->irq); kfree(desc->ubuf); @@ -323,7 +316,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); @@ -348,6 +341,10 @@ static ssize_t wdm_write else if (test_bit(WDM_IN_USE, &desc->flags)) r = -EAGAIN; + + if (test_bit(WDM_RESETTING, &desc->flags)) + r = -EIO; + if (r < 0) { kfree(buf); goto out; @@ -386,7 +383,7 @@ static ssize_t wdm_write out: usb_autopm_put_interface(desc->intf); outnp: - mutex_unlock(&desc->lock); + mutex_unlock(&desc->wlock); outnl: return rv < 0 ? rv : count; } @@ -394,16 +391,17 @@ 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)) { @@ -427,6 +425,10 @@ retry: rv = -ENODEV; goto err; } + if (test_bit(WDM_RESETTING, &desc->flags)) { + rv = -EIO; + goto err; + } usb_mark_last_busy(interface_to_usbdev(desc->intf)); if (rv < 0) { rv = -ERESTARTSYS; @@ -454,16 +456,20 @@ retry: 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 +477,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; } @@ -540,7 +549,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 +563,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,9 +575,11 @@ 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"); @@ -623,14 +635,13 @@ static void wdm_rxwork(struct work_struct *work) static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) { int rv = -EINVAL; - struct usb_device *udev = interface_to_usbdev(intf); struct wdm_device *desc; struct usb_host_interface *iface; struct usb_endpoint_descriptor *ep; 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 +676,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; @@ -683,7 +695,6 @@ next_desc: goto err; desc->wMaxPacketSize = usb_endpoint_maxp(ep); - desc->bMaxPacketSize0 = udev->descriptor.bMaxPacketSize0; desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); if (!desc->orq) @@ -708,19 +719,13 @@ next_desc: if (!desc->ubuf) goto err; - desc->sbuf = usb_alloc_coherent(interface_to_usbdev(intf), - desc->wMaxPacketSize, - GFP_KERNEL, - &desc->validity->transfer_dma); + desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL); if (!desc->sbuf) goto err; - desc->inbuf = usb_alloc_coherent(interface_to_usbdev(intf), - desc->bMaxPacketSize0, - GFP_KERNEL, - &desc->response->transfer_dma); + desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL); if (!desc->inbuf) - goto err2; + goto err; usb_fill_int_urb( desc->validity, @@ -732,30 +737,39 @@ next_desc: desc, ep->bInterval ); - desc->validity->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + desc->irq->wValue = 0; + desc->irq->wIndex = desc->inum; + desc->irq->wLength = cpu_to_le16(desc->wMaxCommand); + + usb_fill_control_urb( + desc->response, + interface_to_usbdev(intf), + /* using common endpoint 0 */ + usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), + (unsigned char *)desc->irq, + desc->inbuf, + desc->wMaxCommand, + wdm_in_callback, + desc + ); usb_set_intfdata(intf, desc); rv = usb_register_dev(intf, &wdm_class); if (rv < 0) - goto err3; + goto err2; else - dev_info(&intf->dev, "cdc-wdm%d: USB WDM device\n", - intf->minor - WDM_MINOR_BASE); + dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev)); out: return rv; -err3: - usb_set_intfdata(intf, NULL); - usb_free_coherent(interface_to_usbdev(desc->intf), - desc->bMaxPacketSize0, - desc->inbuf, - desc->response->transfer_dma); err2: - usb_free_coherent(interface_to_usbdev(desc->intf), - desc->wMaxPacketSize, - desc->sbuf, - desc->validity->transfer_dma); + usb_set_intfdata(intf, NULL); err: free_urbs(desc); + kfree(desc->inbuf); + kfree(desc->sbuf); kfree(desc->ubuf); kfree(desc->orq); kfree(desc->irq); @@ -779,11 +793,13 @@ 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); mutex_unlock(&wdm_mutex); @@ -798,8 +814,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 +833,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,9 +874,6 @@ static int wdm_pre_reset(struct usb_interface *intf) { struct wdm_device *desc = usb_get_intfdata(intf); - mutex_lock(&desc->lock); - kill_urbs(desc); - /* * we notify everybody using poll of * an exceptional situation @@ -864,9 +881,16 @@ static int wdm_pre_reset(struct usb_interface *intf) * message from the device is lost */ spin_lock_irq(&desc->iuspin); + set_bit(WDM_RESETTING, &desc->flags); /* inform read/write */ + set_bit(WDM_READ, &desc->flags); /* unblock read */ + clear_bit(WDM_IN_USE, &desc->flags); /* unblock write */ desc->rerr = -EINTR; spin_unlock_irq(&desc->iuspin); wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); return 0; } @@ -875,8 +899,10 @@ static int wdm_post_reset(struct usb_interface *intf) struct wdm_device *desc = usb_get_intfdata(intf); int rv; + clear_bit(WDM_RESETTING, &desc->flags); rv = recover_from_urb_loss(desc); - mutex_unlock(&desc->lock); + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); return 0; }