2008-05-13 23:01:25 +08:00
|
|
|
/*
|
|
|
|
* cdc-wdm.c
|
|
|
|
*
|
|
|
|
* This driver supports USB CDC WCM Device Management.
|
|
|
|
*
|
2009-04-20 23:24:49 +08:00
|
|
|
* Copyright (c) 2007-2009 Oliver Neukum
|
2008-05-13 23:01:25 +08:00
|
|
|
*
|
|
|
|
* Some code taken from cdc-acm.c
|
|
|
|
*
|
|
|
|
* Released under the GPLv2.
|
|
|
|
*
|
|
|
|
* Many thanks to Carl Nordbeck
|
|
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/poll.h>
|
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/usb/cdc.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
|
|
#include <asm/unaligned.h>
|
2012-03-07 00:29:22 +08:00
|
|
|
#include <linux/usb/cdc-wdm.h>
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Version Information
|
|
|
|
*/
|
2008-06-19 20:20:18 +08:00
|
|
|
#define DRIVER_VERSION "v0.03"
|
2008-05-13 23:01:25 +08:00
|
|
|
#define DRIVER_AUTHOR "Oliver Neukum"
|
2008-06-19 20:20:18 +08:00
|
|
|
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
|
2008-05-13 23:01:25 +08:00
|
|
|
|
2012-01-25 20:03:29 +08:00
|
|
|
#define HUAWEI_VENDOR_ID 0x12D1
|
|
|
|
|
2010-01-10 22:33:45 +08:00
|
|
|
static const struct usb_device_id wdm_ids[] = {
|
2008-05-13 23:01:25 +08:00
|
|
|
{
|
|
|
|
.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
|
|
|
|
USB_DEVICE_ID_MATCH_INT_SUBCLASS,
|
|
|
|
.bInterfaceClass = USB_CLASS_COMM,
|
|
|
|
.bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
|
|
|
|
},
|
2012-01-25 20:03:29 +08:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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! */
|
|
|
|
},
|
2008-05-13 23:01:25 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2008-10-13 20:05:20 +08:00
|
|
|
MODULE_DEVICE_TABLE (usb, wdm_ids);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
#define WDM_MINOR_BASE 176
|
|
|
|
|
|
|
|
|
|
|
|
#define WDM_IN_USE 1
|
|
|
|
#define WDM_DISCONNECTING 2
|
|
|
|
#define WDM_RESULT 3
|
|
|
|
#define WDM_READ 4
|
|
|
|
#define WDM_INT_STALL 5
|
|
|
|
#define WDM_POLL_RUNNING 6
|
2010-02-28 03:54:59 +08:00
|
|
|
#define WDM_RESPONDING 7
|
2010-02-28 03:55:52 +08:00
|
|
|
#define WDM_SUSPENDING 8
|
2012-02-10 16:44:08 +08:00
|
|
|
#define WDM_RESETTING 9
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
#define WDM_MAX 16
|
|
|
|
|
2012-01-20 08:49:57 +08:00
|
|
|
/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
|
|
|
|
#define WDM_DEFAULT_BUFSIZE 256
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
static DEFINE_MUTEX(wdm_mutex);
|
2012-03-07 00:29:21 +08:00
|
|
|
static DEFINE_SPINLOCK(wdm_device_list_lock);
|
|
|
|
static LIST_HEAD(wdm_device_list);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
/* --- method tables --- */
|
|
|
|
|
|
|
|
struct wdm_device {
|
|
|
|
u8 *inbuf; /* buffer for response */
|
|
|
|
u8 *outbuf; /* buffer for command */
|
|
|
|
u8 *sbuf; /* buffer for status */
|
|
|
|
u8 *ubuf; /* buffer for copy to user space */
|
|
|
|
|
|
|
|
struct urb *command;
|
|
|
|
struct urb *response;
|
|
|
|
struct urb *validity;
|
|
|
|
struct usb_interface *intf;
|
|
|
|
struct usb_ctrlrequest *orq;
|
|
|
|
struct usb_ctrlrequest *irq;
|
|
|
|
spinlock_t iuspin;
|
|
|
|
|
|
|
|
unsigned long flags;
|
|
|
|
u16 bufsize;
|
|
|
|
u16 wMaxCommand;
|
|
|
|
u16 wMaxPacketSize;
|
|
|
|
__le16 inum;
|
|
|
|
int reslength;
|
|
|
|
int length;
|
|
|
|
int read;
|
|
|
|
int count;
|
|
|
|
dma_addr_t shandle;
|
|
|
|
dma_addr_t ihandle;
|
2012-01-16 19:41:48 +08:00
|
|
|
struct mutex wlock;
|
|
|
|
struct mutex rlock;
|
2008-05-13 23:01:25 +08:00
|
|
|
wait_queue_head_t wait;
|
|
|
|
struct work_struct rxwork;
|
|
|
|
int werr;
|
|
|
|
int rerr;
|
2012-03-07 00:29:21 +08:00
|
|
|
|
|
|
|
struct list_head device_list;
|
2012-03-07 00:29:22 +08:00
|
|
|
int (*manage_power)(struct usb_interface *, int);
|
2008-05-13 23:01:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_driver wdm_driver;
|
|
|
|
|
2012-03-07 00:29:21 +08:00
|
|
|
/* return intfdata if we own the interface, else look up intf in the list */
|
|
|
|
static struct wdm_device *wdm_find_device(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = NULL;
|
|
|
|
|
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_for_each_entry(desc, &wdm_device_list, device_list)
|
|
|
|
if (desc->intf == intf)
|
|
|
|
break;
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct wdm_device *wdm_find_device_by_minor(int minor)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = NULL;
|
|
|
|
|
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_for_each_entry(desc, &wdm_device_list, device_list)
|
|
|
|
if (desc->intf->minor == minor)
|
|
|
|
break;
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
/* --- callbacks --- */
|
|
|
|
static void wdm_out_callback(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc;
|
|
|
|
desc = urb->context;
|
|
|
|
spin_lock(&desc->iuspin);
|
|
|
|
desc->werr = urb->status;
|
|
|
|
spin_unlock(&desc->iuspin);
|
|
|
|
kfree(desc->outbuf);
|
2012-04-27 03:59:10 +08:00
|
|
|
desc->outbuf = NULL;
|
|
|
|
clear_bit(WDM_IN_USE, &desc->flags);
|
2008-05-13 23:01:25 +08:00
|
|
|
wake_up(&desc->wait);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_in_callback(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = urb->context;
|
|
|
|
int status = urb->status;
|
|
|
|
|
|
|
|
spin_lock(&desc->iuspin);
|
2010-02-28 03:54:59 +08:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
if (status) {
|
|
|
|
switch (status) {
|
|
|
|
case -ENOENT:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
|
|
|
"nonzero urb status received: -ENOENT");
|
2010-02-28 03:54:59 +08:00
|
|
|
goto skip_error;
|
2008-05-13 23:01:25 +08:00
|
|
|
case -ECONNRESET:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
|
|
|
"nonzero urb status received: -ECONNRESET");
|
2010-02-28 03:54:59 +08:00
|
|
|
goto skip_error;
|
2008-05-13 23:01:25 +08:00
|
|
|
case -ESHUTDOWN:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
|
|
|
"nonzero urb status received: -ESHUTDOWN");
|
2010-02-28 03:54:59 +08:00
|
|
|
goto skip_error;
|
2008-05-13 23:01:25 +08:00
|
|
|
case -EPIPE:
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"nonzero urb status received: -EPIPE\n");
|
2008-05-13 23:01:25 +08:00
|
|
|
break;
|
|
|
|
default:
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Unexpected error %d\n", status);
|
2008-05-13 23:01:25 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
desc->rerr = status;
|
|
|
|
desc->reslength = urb->actual_length;
|
|
|
|
memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
|
|
|
|
desc->length += desc->reslength;
|
2010-02-28 03:54:59 +08:00
|
|
|
skip_error:
|
2008-05-13 23:01:25 +08:00
|
|
|
wake_up(&desc->wait);
|
|
|
|
|
|
|
|
set_bit(WDM_READ, &desc->flags);
|
|
|
|
spin_unlock(&desc->iuspin);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_int_callback(struct urb *urb)
|
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
int status = urb->status;
|
|
|
|
struct wdm_device *desc;
|
|
|
|
struct usb_cdc_notification *dr;
|
|
|
|
|
|
|
|
desc = urb->context;
|
|
|
|
dr = (struct usb_cdc_notification *)desc->sbuf;
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
switch (status) {
|
|
|
|
case -ESHUTDOWN:
|
|
|
|
case -ENOENT:
|
|
|
|
case -ECONNRESET:
|
|
|
|
return; /* unplug */
|
|
|
|
case -EPIPE:
|
|
|
|
set_bit(WDM_INT_STALL, &desc->flags);
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev, "Stall on int endpoint\n");
|
2008-05-13 23:01:25 +08:00
|
|
|
goto sw; /* halt is cleared in work */
|
|
|
|
default:
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"nonzero urb status received: %d\n", status);
|
2008-05-13 23:01:25 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n",
|
|
|
|
urb->actual_length);
|
2008-05-13 23:01:25 +08:00
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dr->bNotificationType) {
|
|
|
|
case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
|
|
|
"NOTIFY_RESPONSE_AVAILABLE received: index %d len %d",
|
|
|
|
dr->wIndex, dr->wLength);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev,
|
|
|
|
"NOTIFY_NETWORK_CONNECTION %s network",
|
|
|
|
dr->wValue ? "connected to" : "disconnected from");
|
|
|
|
goto exit;
|
|
|
|
default:
|
|
|
|
clear_bit(WDM_POLL_RUNNING, &desc->flags);
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"unknown notification %d received: index %d len %d\n",
|
2008-05-13 23:01:25 +08:00
|
|
|
dr->bNotificationType, dr->wIndex, dr->wLength);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock(&desc->iuspin);
|
|
|
|
clear_bit(WDM_READ, &desc->flags);
|
2010-02-28 03:54:59 +08:00
|
|
|
set_bit(WDM_RESPONDING, &desc->flags);
|
2010-02-28 03:55:52 +08:00
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags)
|
|
|
|
&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
|
2008-05-13 23:01:25 +08:00
|
|
|
rv = usb_submit_urb(desc->response, GFP_ATOMIC);
|
|
|
|
dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d",
|
|
|
|
__func__, rv);
|
|
|
|
}
|
|
|
|
spin_unlock(&desc->iuspin);
|
|
|
|
if (rv < 0) {
|
2010-02-28 03:54:59 +08:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 23:01:25 +08:00
|
|
|
if (rv == -EPERM)
|
|
|
|
return;
|
|
|
|
if (rv == -ENOMEM) {
|
|
|
|
sw:
|
|
|
|
rv = schedule_work(&desc->rxwork);
|
|
|
|
if (rv)
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Cannot schedule work\n");
|
2008-05-13 23:01:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
exit:
|
|
|
|
rv = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rv)
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"%s - usb_submit_urb failed with result %d\n",
|
|
|
|
__func__, rv);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void kill_urbs(struct wdm_device *desc)
|
|
|
|
{
|
2008-06-24 21:56:10 +08:00
|
|
|
/* the order here is essential */
|
2008-05-13 23:01:25 +08:00
|
|
|
usb_kill_urb(desc->command);
|
|
|
|
usb_kill_urb(desc->validity);
|
|
|
|
usb_kill_urb(desc->response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_urbs(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
usb_free_urb(desc->validity);
|
|
|
|
usb_free_urb(desc->response);
|
|
|
|
usb_free_urb(desc->command);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cleanup(struct wdm_device *desc)
|
|
|
|
{
|
2012-01-16 22:12:00 +08:00
|
|
|
kfree(desc->sbuf);
|
|
|
|
kfree(desc->inbuf);
|
2008-05-13 23:01:25 +08:00
|
|
|
kfree(desc->orq);
|
|
|
|
kfree(desc->irq);
|
|
|
|
kfree(desc->ubuf);
|
|
|
|
free_urbs(desc);
|
|
|
|
kfree(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t wdm_write
|
|
|
|
(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
u8 *buf;
|
|
|
|
int rv = -EMSGSIZE, r, we;
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
struct usb_ctrlrequest *req;
|
|
|
|
|
|
|
|
if (count > desc->wMaxCommand)
|
|
|
|
count = desc->wMaxCommand;
|
|
|
|
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
we = desc->werr;
|
|
|
|
desc->werr = 0;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
if (we < 0)
|
|
|
|
return -EIO;
|
|
|
|
|
2012-04-27 03:59:10 +08:00
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
2010-02-28 03:54:24 +08:00
|
|
|
if (!buf) {
|
|
|
|
rv = -ENOMEM;
|
|
|
|
goto outnl;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = copy_from_user(buf, buffer, count);
|
|
|
|
if (r > 0) {
|
|
|
|
kfree(buf);
|
|
|
|
rv = -EFAULT;
|
|
|
|
goto outnl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* concurrent writes and disconnect */
|
2012-01-16 19:41:48 +08:00
|
|
|
r = mutex_lock_interruptible(&desc->wlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
rv = -ERESTARTSYS;
|
2010-02-28 03:54:24 +08:00
|
|
|
if (r) {
|
|
|
|
kfree(buf);
|
2008-05-13 23:01:25 +08:00
|
|
|
goto outnl;
|
2010-02-28 03:54:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
kfree(buf);
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto outnp;
|
|
|
|
}
|
2008-05-13 23:01:25 +08:00
|
|
|
|
2008-06-24 21:56:10 +08:00
|
|
|
r = usb_autopm_get_interface(desc->intf);
|
2010-02-28 03:54:24 +08:00
|
|
|
if (r < 0) {
|
|
|
|
kfree(buf);
|
2012-04-30 15:57:31 +08:00
|
|
|
rv = usb_translate_errors(r);
|
2008-06-24 21:56:10 +08:00
|
|
|
goto outnp;
|
2010-02-28 03:54:24 +08:00
|
|
|
}
|
2009-09-09 16:12:48 +08:00
|
|
|
|
2010-12-28 01:49:58 +08:00
|
|
|
if (!(file->f_flags & O_NONBLOCK))
|
2009-09-09 16:12:48 +08:00
|
|
|
r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
|
|
|
|
&desc->flags));
|
|
|
|
else
|
|
|
|
if (test_bit(WDM_IN_USE, &desc->flags))
|
|
|
|
r = -EAGAIN;
|
2012-02-10 16:44:08 +08:00
|
|
|
|
|
|
|
if (test_bit(WDM_RESETTING, &desc->flags))
|
|
|
|
r = -EIO;
|
|
|
|
|
2010-02-28 03:54:24 +08:00
|
|
|
if (r < 0) {
|
2008-05-13 23:01:25 +08:00
|
|
|
kfree(buf);
|
2012-04-30 15:57:31 +08:00
|
|
|
rv = r;
|
2008-05-13 23:01:25 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
req = desc->orq;
|
|
|
|
usb_fill_control_urb(
|
|
|
|
desc->command,
|
|
|
|
interface_to_usbdev(desc->intf),
|
|
|
|
/* using common endpoint 0 */
|
|
|
|
usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0),
|
|
|
|
(unsigned char *)req,
|
|
|
|
buf,
|
|
|
|
count,
|
|
|
|
wdm_out_callback,
|
|
|
|
desc
|
|
|
|
);
|
|
|
|
|
|
|
|
req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
|
|
|
|
USB_RECIP_INTERFACE);
|
|
|
|
req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
|
|
|
|
req->wValue = 0;
|
|
|
|
req->wIndex = desc->inum;
|
|
|
|
req->wLength = cpu_to_le16(count);
|
|
|
|
set_bit(WDM_IN_USE, &desc->flags);
|
2012-04-27 03:59:10 +08:00
|
|
|
desc->outbuf = buf;
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
rv = usb_submit_urb(desc->command, GFP_KERNEL);
|
|
|
|
if (rv < 0) {
|
|
|
|
kfree(buf);
|
2012-04-27 03:59:10 +08:00
|
|
|
desc->outbuf = NULL;
|
2008-05-13 23:01:25 +08:00
|
|
|
clear_bit(WDM_IN_USE, &desc->flags);
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
|
2012-04-30 15:57:31 +08:00
|
|
|
rv = usb_translate_errors(rv);
|
2008-05-13 23:01:25 +08:00
|
|
|
} else {
|
|
|
|
dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
|
|
|
|
req->wIndex);
|
|
|
|
}
|
|
|
|
out:
|
2008-06-24 21:56:10 +08:00
|
|
|
usb_autopm_put_interface(desc->intf);
|
|
|
|
outnp:
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->wlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
outnl:
|
|
|
|
return rv < 0 ? rv : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t wdm_read
|
|
|
|
(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
|
|
|
|
{
|
2012-02-12 14:00:41 +08:00
|
|
|
int rv, cntr;
|
2008-05-13 23:01:25 +08:00
|
|
|
int i = 0;
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
|
|
|
|
|
2012-01-16 19:41:48 +08:00
|
|
|
rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */
|
2008-05-13 23:01:25 +08:00
|
|
|
if (rv < 0)
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
2012-02-12 14:00:41 +08:00
|
|
|
cntr = ACCESS_ONCE(desc->length);
|
|
|
|
if (cntr == 0) {
|
2008-05-13 23:01:25 +08:00
|
|
|
desc->read = 0;
|
|
|
|
retry:
|
2009-09-09 16:12:48 +08:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto err;
|
|
|
|
}
|
2008-05-13 23:01:25 +08:00
|
|
|
i++;
|
2009-09-09 16:12:48 +08:00
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
if (!test_bit(WDM_READ, &desc->flags)) {
|
|
|
|
rv = cntr ? cntr : -EAGAIN;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
rv = 0;
|
|
|
|
} else {
|
|
|
|
rv = wait_event_interruptible(desc->wait,
|
|
|
|
test_bit(WDM_READ, &desc->flags));
|
|
|
|
}
|
2008-05-13 23:01:25 +08:00
|
|
|
|
2009-09-09 16:12:48 +08:00
|
|
|
/* may have happened while we slept */
|
2008-06-24 21:56:10 +08:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto err;
|
|
|
|
}
|
2012-02-10 16:44:08 +08:00
|
|
|
if (test_bit(WDM_RESETTING, &desc->flags)) {
|
|
|
|
rv = -EIO;
|
|
|
|
goto err;
|
|
|
|
}
|
2008-06-24 21:56:10 +08:00
|
|
|
usb_mark_last_busy(interface_to_usbdev(desc->intf));
|
2008-05-13 23:01:25 +08:00
|
|
|
if (rv < 0) {
|
|
|
|
rv = -ERESTARTSYS;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
|
|
|
|
if (desc->rerr) { /* read completed, error happened */
|
|
|
|
desc->rerr = 0;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
rv = -EIO;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* recheck whether we've lost the race
|
|
|
|
* against the completion handler
|
|
|
|
*/
|
|
|
|
if (!test_bit(WDM_READ, &desc->flags)) { /* lost race */
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
if (!desc->reslength) { /* zero length read */
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
goto retry;
|
|
|
|
}
|
2012-02-12 14:00:41 +08:00
|
|
|
cntr = desc->length;
|
2008-05-13 23:01:25 +08:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
}
|
|
|
|
|
2012-02-12 14:00:41 +08:00
|
|
|
if (cntr > count)
|
|
|
|
cntr = count;
|
2008-05-13 23:01:25 +08:00
|
|
|
rv = copy_to_user(buffer, desc->ubuf, cntr);
|
|
|
|
if (rv > 0) {
|
|
|
|
rv = -EFAULT;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2012-02-12 14:00:41 +08:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
for (i = 0; i < desc->length - cntr; i++)
|
|
|
|
desc->ubuf[i] = desc->ubuf[i + cntr];
|
|
|
|
|
|
|
|
desc->length -= cntr;
|
2008-06-19 20:20:18 +08:00
|
|
|
/* in case we had outstanding data */
|
|
|
|
if (!desc->length)
|
|
|
|
clear_bit(WDM_READ, &desc->flags);
|
2012-02-12 14:00:41 +08:00
|
|
|
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
rv = cntr;
|
|
|
|
|
|
|
|
err:
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->rlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
2012-05-09 19:53:22 +08:00
|
|
|
|
|
|
|
/* cannot dereference desc->intf if WDM_DISCONNECTING */
|
|
|
|
if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags))
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev, "Error in flush path: %d\n",
|
|
|
|
desc->werr);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
2012-04-27 20:23:54 +08:00
|
|
|
return usb_translate_errors(desc->werr);
|
2008-05-13 23:01:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned int mask = 0;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
2012-05-09 19:53:21 +08:00
|
|
|
mask = POLLHUP | POLLERR;
|
2008-05-13 23:01:25 +08:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
goto desc_out;
|
|
|
|
}
|
|
|
|
if (test_bit(WDM_READ, &desc->flags))
|
|
|
|
mask = POLLIN | POLLRDNORM;
|
|
|
|
if (desc->rerr || desc->werr)
|
|
|
|
mask |= POLLERR;
|
|
|
|
if (!test_bit(WDM_IN_USE, &desc->flags))
|
|
|
|
mask |= POLLOUT | POLLWRNORM;
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
|
|
|
|
poll_wait(file, &desc->wait, wait);
|
|
|
|
|
|
|
|
desc_out:
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
int minor = iminor(inode);
|
|
|
|
int rv = -ENODEV;
|
|
|
|
struct usb_interface *intf;
|
|
|
|
struct wdm_device *desc;
|
|
|
|
|
|
|
|
mutex_lock(&wdm_mutex);
|
2012-03-07 00:29:21 +08:00
|
|
|
desc = wdm_find_device_by_minor(minor);
|
|
|
|
if (!desc)
|
2008-05-13 23:01:25 +08:00
|
|
|
goto out;
|
|
|
|
|
2012-03-07 00:29:21 +08:00
|
|
|
intf = desc->intf;
|
2008-05-13 23:01:25 +08:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
goto out;
|
|
|
|
file->private_data = desc;
|
|
|
|
|
2008-06-24 21:56:10 +08:00
|
|
|
rv = usb_autopm_get_interface(desc->intf);
|
2008-05-13 23:01:25 +08:00
|
|
|
if (rv < 0) {
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
|
2008-05-13 23:01:25 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2012-01-16 19:41:48 +08:00
|
|
|
/* using write lock to protect desc->count */
|
|
|
|
mutex_lock(&desc->wlock);
|
2008-06-24 21:56:10 +08:00
|
|
|
if (!desc->count++) {
|
2011-04-29 20:12:21 +08:00
|
|
|
desc->werr = 0;
|
|
|
|
desc->rerr = 0;
|
2008-06-24 21:56:10 +08:00
|
|
|
rv = usb_submit_urb(desc->validity, GFP_KERNEL);
|
|
|
|
if (rv < 0) {
|
|
|
|
desc->count--;
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Error submitting int urb - %d\n", rv);
|
2012-04-30 15:57:31 +08:00
|
|
|
rv = usb_translate_errors(rv);
|
2008-06-24 21:56:10 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rv = 0;
|
|
|
|
}
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->wlock);
|
2012-03-07 00:29:22 +08:00
|
|
|
if (desc->count == 1)
|
|
|
|
desc->manage_power(intf, 1);
|
2008-06-24 21:56:10 +08:00
|
|
|
usb_autopm_put_interface(desc->intf);
|
2008-05-13 23:01:25 +08:00
|
|
|
out:
|
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
|
|
|
|
mutex_lock(&wdm_mutex);
|
2012-01-16 19:41:48 +08:00
|
|
|
|
|
|
|
/* using write lock to protect desc->count */
|
|
|
|
mutex_lock(&desc->wlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
desc->count--;
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->wlock);
|
2008-06-24 21:56:10 +08:00
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
if (!desc->count) {
|
2012-04-30 15:26:11 +08:00
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
2012-05-09 19:53:22 +08:00
|
|
|
dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
|
|
|
|
kill_urbs(desc);
|
2012-03-07 00:29:22 +08:00
|
|
|
desc->manage_power(desc->intf, 0);
|
2012-04-30 15:26:11 +08:00
|
|
|
} else {
|
2012-05-09 19:53:22 +08:00
|
|
|
/* must avoid dev_printk here as desc->intf is invalid */
|
|
|
|
pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
|
2012-04-27 20:36:37 +08:00
|
|
|
cleanup(desc);
|
2012-04-30 15:26:11 +08:00
|
|
|
}
|
2008-05-13 23:01:25 +08:00
|
|
|
}
|
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations wdm_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = wdm_read,
|
|
|
|
.write = wdm_write,
|
|
|
|
.open = wdm_open,
|
|
|
|
.flush = wdm_flush,
|
|
|
|
.release = wdm_release,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-16 00:52:59 +08:00
|
|
|
.poll = wdm_poll,
|
|
|
|
.llseek = noop_llseek,
|
2008-05-13 23:01:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_class_driver wdm_class = {
|
|
|
|
.name = "cdc-wdm%d",
|
|
|
|
.fops = &wdm_fops,
|
|
|
|
.minor_base = WDM_MINOR_BASE,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* --- error handling --- */
|
|
|
|
static void wdm_rxwork(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);
|
|
|
|
unsigned long flags;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
} else {
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
rv = usb_submit_urb(desc->response, GFP_KERNEL);
|
|
|
|
if (rv < 0 && rv != -EPERM) {
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
schedule_work(&desc->rxwork);
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --- hotplug --- */
|
|
|
|
|
2012-03-07 00:29:22 +08:00
|
|
|
static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
|
|
|
|
u16 bufsize, int (*manage_power)(struct usb_interface *, int))
|
2008-05-13 23:01:25 +08:00
|
|
|
{
|
2012-03-07 00:29:20 +08:00
|
|
|
int rv = -ENOMEM;
|
2008-05-13 23:01:25 +08:00
|
|
|
struct wdm_device *desc;
|
|
|
|
|
|
|
|
desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
|
|
|
|
if (!desc)
|
|
|
|
goto out;
|
2012-03-07 00:29:21 +08:00
|
|
|
INIT_LIST_HEAD(&desc->device_list);
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_init(&desc->rlock);
|
|
|
|
mutex_init(&desc->wlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
spin_lock_init(&desc->iuspin);
|
|
|
|
init_waitqueue_head(&desc->wait);
|
2012-03-07 00:29:20 +08:00
|
|
|
desc->wMaxCommand = bufsize;
|
2009-04-20 23:24:49 +08:00
|
|
|
/* this will be expanded and needed in hardware endianness */
|
2008-05-13 23:01:25 +08:00
|
|
|
desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
|
|
|
|
desc->intf = intf;
|
|
|
|
INIT_WORK(&desc->rxwork, wdm_rxwork);
|
|
|
|
|
2009-04-20 23:24:49 +08:00
|
|
|
rv = -EINVAL;
|
2012-03-07 00:29:20 +08:00
|
|
|
if (!usb_endpoint_is_int_in(ep))
|
2008-05-13 23:01:25 +08:00
|
|
|
goto err;
|
|
|
|
|
2011-08-23 18:12:03 +08:00
|
|
|
desc->wMaxPacketSize = usb_endpoint_maxp(ep);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
|
|
|
|
if (!desc->orq)
|
|
|
|
goto err;
|
|
|
|
desc->irq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
|
|
|
|
if (!desc->irq)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->validity = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->validity)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->response = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->response)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->command = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->command)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->ubuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
|
|
|
|
if (!desc->ubuf)
|
|
|
|
goto err;
|
|
|
|
|
2012-01-16 22:12:00 +08:00
|
|
|
desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);
|
2008-05-13 23:01:25 +08:00
|
|
|
if (!desc->sbuf)
|
|
|
|
goto err;
|
|
|
|
|
2012-01-16 22:12:00 +08:00
|
|
|
desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
|
2008-05-13 23:01:25 +08:00
|
|
|
if (!desc->inbuf)
|
2012-01-16 22:12:00 +08:00
|
|
|
goto err;
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
usb_fill_int_urb(
|
|
|
|
desc->validity,
|
|
|
|
interface_to_usbdev(intf),
|
|
|
|
usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress),
|
|
|
|
desc->sbuf,
|
|
|
|
desc->wMaxPacketSize,
|
|
|
|
wdm_int_callback,
|
|
|
|
desc,
|
|
|
|
ep->bInterval
|
|
|
|
);
|
|
|
|
|
2012-01-16 22:11:58 +08:00
|
|
|
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,
|
2012-01-16 22:12:01 +08:00
|
|
|
interface_to_usbdev(intf),
|
2012-01-16 22:11:58 +08:00
|
|
|
/* using common endpoint 0 */
|
|
|
|
usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
|
|
|
|
(unsigned char *)desc->irq,
|
|
|
|
desc->inbuf,
|
|
|
|
desc->wMaxCommand,
|
|
|
|
wdm_in_callback,
|
|
|
|
desc
|
|
|
|
);
|
|
|
|
|
2012-03-07 00:29:22 +08:00
|
|
|
desc->manage_power = manage_power;
|
|
|
|
|
2012-03-07 00:29:21 +08:00
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_add(&desc->device_list, &wdm_device_list);
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
rv = usb_register_dev(intf, &wdm_class);
|
|
|
|
if (rv < 0)
|
2012-03-07 00:29:21 +08:00
|
|
|
goto err;
|
2009-04-20 23:24:49 +08:00
|
|
|
else
|
2012-01-20 11:17:25 +08:00
|
|
|
dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));
|
2008-05-13 23:01:25 +08:00
|
|
|
out:
|
|
|
|
return rv;
|
|
|
|
err:
|
2012-05-09 19:53:23 +08:00
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_del(&desc->device_list);
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
2012-03-07 00:29:20 +08:00
|
|
|
cleanup(desc);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-03-07 00:29:22 +08:00
|
|
|
static int wdm_manage_power(struct usb_interface *intf, int on)
|
|
|
|
{
|
|
|
|
/* need autopm_get/put here to ensure the usbcore sees the new value */
|
|
|
|
int rv = usb_autopm_get_interface(intf);
|
|
|
|
if (rv < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
intf->needs_remote_wakeup = on;
|
|
|
|
usb_autopm_put_interface(intf);
|
|
|
|
err:
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-03-07 00:29:20 +08:00
|
|
|
static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
|
|
|
{
|
|
|
|
int rv = -EINVAL;
|
|
|
|
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 = WDM_DEFAULT_BUFSIZE;
|
|
|
|
|
|
|
|
if (!buffer)
|
|
|
|
goto err;
|
|
|
|
while (buflen > 2) {
|
|
|
|
if (buffer[1] != USB_DT_CS_INTERFACE) {
|
|
|
|
dev_err(&intf->dev, "skipping garbage\n");
|
|
|
|
goto next_desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (buffer[2]) {
|
|
|
|
case USB_CDC_HEADER_TYPE:
|
|
|
|
break;
|
|
|
|
case USB_CDC_DMM_TYPE:
|
|
|
|
dmhd = (struct usb_cdc_dmm_desc *)buffer;
|
|
|
|
maxcom = le16_to_cpu(dmhd->wMaxCommand);
|
|
|
|
dev_dbg(&intf->dev,
|
|
|
|
"Finding maximum buffer length: %d", maxcom);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(&intf->dev,
|
|
|
|
"Ignoring extra header, type %d, length %d\n",
|
|
|
|
buffer[2], buffer[0]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
next_desc:
|
|
|
|
buflen -= buffer[0];
|
|
|
|
buffer += buffer[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
iface = intf->cur_altsetting;
|
|
|
|
if (iface->desc.bNumEndpoints != 1)
|
|
|
|
goto err;
|
|
|
|
ep = &iface->endpoint[0].desc;
|
|
|
|
|
2012-03-07 00:29:22 +08:00
|
|
|
rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
|
2012-03-07 00:29:20 +08:00
|
|
|
|
|
|
|
err:
|
2008-05-13 23:01:25 +08:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-03-07 00:29:22 +08:00
|
|
|
/**
|
|
|
|
* usb_cdc_wdm_register - register a WDM subdriver
|
|
|
|
* @intf: usb interface the subdriver will associate with
|
|
|
|
* @ep: interrupt endpoint to monitor for notifications
|
|
|
|
* @bufsize: maximum message size to support for read/write
|
|
|
|
*
|
|
|
|
* Create WDM usb class character device and associate it with intf
|
|
|
|
* without binding, allowing another driver to manage the interface.
|
|
|
|
*
|
|
|
|
* The subdriver will manage the given interrupt endpoint exclusively
|
|
|
|
* and will issue control requests referring to the given intf. It
|
|
|
|
* will otherwise avoid interferring, and in particular not do
|
|
|
|
* usb_set_intfdata/usb_get_intfdata on intf.
|
|
|
|
*
|
|
|
|
* The return value is a pointer to the subdriver's struct usb_driver.
|
|
|
|
* The registering driver is responsible for calling this subdriver's
|
|
|
|
* disconnect, suspend, resume, pre_reset and post_reset methods from
|
|
|
|
* its own.
|
|
|
|
*/
|
|
|
|
struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
|
|
|
|
struct usb_endpoint_descriptor *ep,
|
|
|
|
int bufsize,
|
|
|
|
int (*manage_power)(struct usb_interface *, int))
|
|
|
|
{
|
|
|
|
int rv = -EINVAL;
|
|
|
|
|
|
|
|
rv = wdm_create(intf, ep, bufsize, manage_power);
|
|
|
|
if (rv < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
return &wdm_driver;
|
|
|
|
err:
|
|
|
|
return ERR_PTR(rv);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(usb_cdc_wdm_register);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
static void wdm_disconnect(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
usb_deregister_dev(intf, &wdm_class);
|
2012-03-07 00:29:21 +08:00
|
|
|
desc = wdm_find_device(intf);
|
2008-05-13 23:01:25 +08:00
|
|
|
mutex_lock(&wdm_mutex);
|
|
|
|
|
|
|
|
/* the spinlock makes sure no new urbs are generated in the callbacks */
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
set_bit(WDM_DISCONNECTING, &desc->flags);
|
|
|
|
set_bit(WDM_READ, &desc->flags);
|
2008-06-24 21:56:10 +08:00
|
|
|
/* to terminate pending flushes */
|
2008-05-13 23:01:25 +08:00
|
|
|
clear_bit(WDM_IN_USE, &desc->flags);
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
2012-01-16 22:11:57 +08:00
|
|
|
wake_up_all(&desc->wait);
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
2008-05-13 23:01:25 +08:00
|
|
|
kill_urbs(desc);
|
2010-02-28 03:56:47 +08:00
|
|
|
cancel_work_sync(&desc->rxwork);
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
2012-05-09 19:53:23 +08:00
|
|
|
|
|
|
|
/* the desc->intf pointer used as list key is now invalid */
|
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_del(&desc->device_list);
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
if (!desc->count)
|
|
|
|
cleanup(desc);
|
2012-04-30 15:26:11 +08:00
|
|
|
else
|
|
|
|
dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count);
|
2008-05-13 23:01:25 +08:00
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
}
|
|
|
|
|
2010-02-28 03:56:47 +08:00
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 21:56:10 +08:00
|
|
|
static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
|
|
|
|
{
|
2012-03-07 00:29:21 +08:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 21:56:10 +08:00
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
|
|
|
|
|
2010-02-28 03:56:47 +08:00
|
|
|
/* if this is an autosuspend the caller does the locking */
|
2012-01-16 19:41:48 +08:00
|
|
|
if (!PMSG_IS_AUTO(message)) {
|
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
|
|
|
}
|
2010-02-28 03:56:22 +08:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
2010-02-28 03:56:47 +08:00
|
|
|
|
2011-08-20 05:49:48 +08:00
|
|
|
if (PMSG_IS_AUTO(message) &&
|
2010-02-28 03:54:59 +08:00
|
|
|
(test_bit(WDM_IN_USE, &desc->flags)
|
|
|
|
|| test_bit(WDM_RESPONDING, &desc->flags))) {
|
2010-02-28 03:56:22 +08:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2008-06-24 21:56:10 +08:00
|
|
|
rv = -EBUSY;
|
|
|
|
} else {
|
2010-02-28 03:56:47 +08:00
|
|
|
|
2010-02-28 03:55:52 +08:00
|
|
|
set_bit(WDM_SUSPENDING, &desc->flags);
|
2010-02-28 03:56:22 +08:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2010-02-28 03:56:47 +08:00
|
|
|
/* callback submits work - order is essential */
|
2008-06-24 21:56:10 +08:00
|
|
|
kill_urbs(desc);
|
2010-02-28 03:56:47 +08:00
|
|
|
cancel_work_sync(&desc->rxwork);
|
2008-06-24 21:56:10 +08:00
|
|
|
}
|
2012-01-16 19:41:48 +08:00
|
|
|
if (!PMSG_IS_AUTO(message)) {
|
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
|
|
|
}
|
2008-06-24 21:56:10 +08:00
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
2010-02-28 03:56:47 +08:00
|
|
|
#endif
|
2008-06-24 21:56:10 +08:00
|
|
|
|
|
|
|
static int recover_from_urb_loss(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
if (desc->count) {
|
|
|
|
rv = usb_submit_urb(desc->validity, GFP_NOIO);
|
|
|
|
if (rv < 0)
|
2008-08-15 00:37:34 +08:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Error resume submitting int urb - %d\n", rv);
|
2008-06-24 21:56:10 +08:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
2010-02-28 03:56:47 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 21:56:10 +08:00
|
|
|
static int wdm_resume(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-07 00:29:21 +08:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 21:56:10 +08:00
|
|
|
int rv;
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
|
2010-02-28 03:57:12 +08:00
|
|
|
|
2010-02-28 03:55:52 +08:00
|
|
|
clear_bit(WDM_SUSPENDING, &desc->flags);
|
2010-02-28 03:56:22 +08:00
|
|
|
rv = recover_from_urb_loss(desc);
|
2010-02-28 03:57:12 +08:00
|
|
|
|
2008-06-24 21:56:10 +08:00
|
|
|
return rv;
|
|
|
|
}
|
2010-02-28 03:56:47 +08:00
|
|
|
#endif
|
2008-06-24 21:56:10 +08:00
|
|
|
|
|
|
|
static int wdm_pre_reset(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-07 00:29:21 +08:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 21:56:10 +08:00
|
|
|
|
2011-04-29 20:12:21 +08:00
|
|
|
/*
|
|
|
|
* we notify everybody using poll of
|
|
|
|
* an exceptional situation
|
|
|
|
* must be done before recovery lest a spontaneous
|
|
|
|
* message from the device is lost
|
|
|
|
*/
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
2012-02-10 16:44:08 +08:00
|
|
|
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 */
|
2011-04-29 20:12:21 +08:00
|
|
|
desc->rerr = -EINTR;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
wake_up_all(&desc->wait);
|
2012-02-10 16:44:08 +08:00
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
|
|
|
kill_urbs(desc);
|
|
|
|
cancel_work_sync(&desc->rxwork);
|
2008-06-24 21:56:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_post_reset(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-07 00:29:21 +08:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 21:56:10 +08:00
|
|
|
int rv;
|
|
|
|
|
2012-02-10 16:44:08 +08:00
|
|
|
clear_bit(WDM_RESETTING, &desc->flags);
|
2008-06-24 21:56:10 +08:00
|
|
|
rv = recover_from_urb_loss(desc);
|
2012-01-16 19:41:48 +08:00
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
2008-06-24 21:56:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-05-13 23:01:25 +08:00
|
|
|
static struct usb_driver wdm_driver = {
|
|
|
|
.name = "cdc_wdm",
|
|
|
|
.probe = wdm_probe,
|
|
|
|
.disconnect = wdm_disconnect,
|
2010-02-28 03:56:47 +08:00
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 21:56:10 +08:00
|
|
|
.suspend = wdm_suspend,
|
|
|
|
.resume = wdm_resume,
|
|
|
|
.reset_resume = wdm_resume,
|
2010-02-28 03:56:47 +08:00
|
|
|
#endif
|
2008-06-24 21:56:10 +08:00
|
|
|
.pre_reset = wdm_pre_reset,
|
|
|
|
.post_reset = wdm_post_reset,
|
2008-05-13 23:01:25 +08:00
|
|
|
.id_table = wdm_ids,
|
2008-06-24 21:56:10 +08:00
|
|
|
.supports_autosuspend = 1,
|
2008-05-13 23:01:25 +08:00
|
|
|
};
|
|
|
|
|
2011-11-19 01:34:02 +08:00
|
|
|
module_usb_driver(wdm_driver);
|
2008-05-13 23:01:25 +08:00
|
|
|
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
2008-06-19 20:20:18 +08:00
|
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
2008-05-13 23:01:25 +08:00
|
|
|
MODULE_LICENSE("GPL");
|