/*
* cdc-wdm.c
*
* This driver supports USB CDC WCM Device Management.
*
* Copyright (c) 2007-2009 Oliver Neukum
*
* 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>
/*
* Version Information
*/
#define DRIVER_VERSION "v0.03"
#define DRIVER_AUTHOR "Oliver Neukum"
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
static const struct usb_device_id wdm_ids[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
USB_DEVICE_ID_MATCH_INT_SUBCLASS,
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
},
{ }
};
MODULE_DEVICE_TABLE (usb, wdm_ids);
#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
#define WDM_RESPONDING 7
#define WDM_SUSPENDING 8
#define WDM_MAX 16
static DEFINE_MUTEX(wdm_mutex);
/* --- 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;
u16 bMaxPacketSize0;
__le16 inum;
int reslength;
int length;
int read;
int count;
dma_addr_t shandle;
dma_addr_t ihandle;
struct mutex lock;
wait_queue_head_t wait;
struct work_struct rxwork;
int werr;
int rerr;
};
static struct usb_driver wdm_driver;
/* --- 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);
clear_bit(WDM_IN_USE, &desc->flags);
kfree(desc->outbuf);
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);
clear_bit(WDM_RESPONDING, &desc->flags);
if (status) {
switch (status) {
case -ENOENT:
dev_dbg(&desc->intf->dev,
"nonzero urb status received: -ENOENT");
goto skip_error;
case -ECONNRESET:
dev_dbg(&desc->intf->dev,
"nonzero urb status received: -ECONNRESET");
goto skip_error;
case -ESHUTDOWN:
dev_dbg(&desc->intf->dev,
"nonzero urb status received: -ESHUTDOWN");
goto skip_error;
case -EPIPE:
dev_err(&desc->intf->dev,
"nonzero urb status received: -EPIPE\n");
break;
default:
dev_err(&desc->intf->dev,
"Unexpected error %d\n", status);
break;
}
}
desc->rerr = status;
desc->reslength = urb->actual_length;
memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
desc->length += desc->reslength;
skip_error:
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_ctrlrequest *req;
struct usb_cdc_notification *dr;
desc = urb->context;
req = desc->irq;
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);
dev_err(&desc->intf->dev, "Stall on int endpoint\n");
goto sw; /* halt is cleared in work */
default:
dev_err(&desc->intf->dev,
"nonzero urb status received: %d\n", status);
break;
}
}
if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n",