Merge git://git.kernel.org/pub/scm/linux/kernel/git/brodo/pcmcia-2.6
[safe/jmp/linux-2.6] / drivers / usb / class / cdc-acm.c
index 912d97a..e4eca78 100644 (file)
@@ -16,7 +16,8 @@
  *     v0.9  - thorough cleaning, URBification, almost a rewrite
  *     v0.10 - some more cleanups
  *     v0.11 - fixed flow control, read error doesn't stop reads
- *     v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced
+ *     v0.12 - added TIOCM ioctls, added break handling, made struct acm
+ *             kmalloced
  *     v0.13 - added termios, added hangup
  *     v0.14 - sized down struct acm
  *     v0.15 - fixed flow control again - characters could be lost
@@ -31,6 +32,7 @@
  *     v0.23 - use softirq for rx processing, as needed by tty layer
  *     v0.24 - change probe method to evaluate CDC union descriptor
  *     v0.25 - downstream tasks paralelized to maximize throughput
+ *     v0.26 - multiple write urbs, writesize increased
  */
 
 /*
  */
 
 #undef DEBUG
+#undef VERBOSE_DEBUG
 
 #include <linux/kernel.h>
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
+#include <linux/serial.h>
 #include <linux/tty_driver.h>
 #include <linux/tty_flip.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
 #include <linux/usb.h>
 #include <linux/usb/cdc.h>
 #include <asm/byteorder.h>
 
 #include "cdc-acm.h"
 
+
+#define ACM_CLOSE_TIMEOUT      15      /* seconds to let writes drain */
+
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.25"
+#define DRIVER_VERSION "v0.26"
 #define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek"
 #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
 
@@ -82,19 +89,30 @@ static struct acm *acm_table[ACM_TTY_MINORS];
 
 static DEFINE_MUTEX(open_mutex);
 
-#define ACM_READY(acm) (acm && acm->dev && acm->used)
+#define ACM_READY(acm) (acm && acm->dev && acm->port.count)
+
+static const struct tty_port_operations acm_port_ops = {
+};
+
+#ifdef VERBOSE_DEBUG
+#define verbose        1
+#else
+#define verbose        0
+#endif
 
 /*
  * Functions for ACM control messages.
  */
 
-static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len)
+static int acm_ctrl_msg(struct acm *acm, int request, int value,
+                                                       void *buf, int len)
 {
        int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0),
                request, USB_RT_ACM, value,
                acm->control->altsetting[0].desc.bInterfaceNumber,
                buf, len, 5000);
-       dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval);
+       dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d",
+                                               request, value, len, retval);
        return retval < 0 ? retval : 0;
 }
 
@@ -118,7 +136,7 @@ static int acm_wb_alloc(struct acm *acm)
        int i, wbn;
        struct acm_wb *wb;
 
-       wbn = acm->write_current;
+       wbn = 0;
        i = 0;
        for (;;) {
                wb = &acm->wb[wbn];
@@ -132,84 +150,80 @@ static int acm_wb_alloc(struct acm *acm)
        }
 }
 
-static void acm_wb_free(struct acm *acm, int wbn)
-{
-       acm->wb[wbn].use = 0;
-}
-
 static int acm_wb_is_avail(struct acm *acm)
 {
        int i, n;
+       unsigned long flags;
 
        n = ACM_NW;
-       for (i = 0; i < ACM_NW; i++) {
+       spin_lock_irqsave(&acm->write_lock, flags);
+       for (i = 0; i < ACM_NW; i++)
                n -= acm->wb[i].use;
-       }
+       spin_unlock_irqrestore(&acm->write_lock, flags);
        return n;
 }
 
-static inline int acm_wb_is_used(struct acm *acm, int wbn)
+/*
+ * Finish write. Caller must hold acm->write_lock
+ */
+static void acm_write_done(struct acm *acm, struct acm_wb *wb)
 {
-       return acm->wb[wbn].use;
+       wb->use = 0;
+       acm->transmitting--;
 }
 
 /*
- * Finish write.
+ * Poke write.
+ *
+ * the caller is responsible for locking
  */
-static void acm_write_done(struct acm *acm)
+
+static int acm_start_wb(struct acm *acm, struct acm_wb *wb)
 {
-       unsigned long flags;
-       int wbn;
+       int rc;
 
-       spin_lock_irqsave(&acm->write_lock, flags);
-       acm->write_ready = 1;
-       wbn = acm->write_current;
-       acm_wb_free(acm, wbn);
-       acm->write_current = (wbn + 1) % ACM_NW;
-       spin_unlock_irqrestore(&acm->write_lock, flags);
+       acm->transmitting++;
+
+       wb->urb->transfer_buffer = wb->buf;
+       wb->urb->transfer_dma = wb->dmah;
+       wb->urb->transfer_buffer_length = wb->len;
+       wb->urb->dev = acm->dev;
+
+       rc = usb_submit_urb(wb->urb, GFP_ATOMIC);
+       if (rc < 0) {
+               dbg("usb_submit_urb(write bulk) failed: %d", rc);
+               acm_write_done(acm, wb);
+       }
+       return rc;
 }
 
-/*
- * Poke write.
- */
-static int acm_write_start(struct acm *acm)
+static int acm_write_start(struct acm *acm, int wbn)
 {
        unsigned long flags;
-       int wbn;
-       struct acm_wb *wb;
+       struct acm_wb *wb = &acm->wb[wbn];
        int rc;
 
        spin_lock_irqsave(&acm->write_lock, flags);
        if (!acm->dev) {
+               wb->use = 0;
                spin_unlock_irqrestore(&acm->write_lock, flags);
                return -ENODEV;
        }
 
-       if (!acm->write_ready) {
+       dbg("%s susp_count: %d", __func__, acm->susp_count);
+       if (acm->susp_count) {
+               acm->delayed_wb = wb;
+               schedule_work(&acm->waker);
                spin_unlock_irqrestore(&acm->write_lock, flags);
                return 0;       /* A white lie */
        }
+       usb_mark_last_busy(acm->dev);
 
-       wbn = acm->write_current;
-       if (!acm_wb_is_used(acm, wbn)) {
-               spin_unlock_irqrestore(&acm->write_lock, flags);
-               return 0;
-       }
-       wb = &acm->wb[wbn];
-
-       acm->write_ready = 0;
+       rc = acm_start_wb(acm, wb);
        spin_unlock_irqrestore(&acm->write_lock, flags);
 
-       acm->writeurb->transfer_buffer = wb->buf;
-       acm->writeurb->transfer_dma = wb->dmah;
-       acm->writeurb->transfer_buffer_length = wb->len;
-       acm->writeurb->dev = acm->dev;
-
-       if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) {
-               dbg("usb_submit_urb(write bulk) failed: %d", rc);
-               acm_write_done(acm);
-       }
        return rc;
+
 }
 /*
  * attributes exported through sysfs
@@ -255,6 +269,7 @@ static void acm_ctrl_irq(struct urb *urb)
 {
        struct acm *acm = urb->context;
        struct usb_cdc_notification *dr = urb->transfer_buffer;
+       struct tty_struct *tty;
        unsigned char *data;
        int newctrl;
        int retval;
@@ -268,10 +283,10 @@ static void acm_ctrl_irq(struct urb *urb)
        case -ENOENT:
        case -ESHUTDOWN:
                /* this urb is terminated, clean up */
-               dbg("%s - urb shutting down with status: %d", __FUNCTION__, status);
+               dbg("%s - urb shutting down with status: %d", __func__, status);
                return;
        default:
-               dbg("%s - nonzero urb status received: %d", __FUNCTION__, status);
+               dbg("%s - nonzero urb status received: %d", __func__, status);
                goto exit;
        }
 
@@ -280,42 +295,48 @@ static void acm_ctrl_irq(struct urb *urb)
 
        data = (unsigned char *)(dr + 1);
        switch (dr->bNotificationType) {
+       case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+               dbg("%s network", dr->wValue ?
+                                       "connected to" : "disconnected from");
+               break;
 
-               case USB_CDC_NOTIFY_NETWORK_CONNECTION:
-
-                       dbg("%s network", dr->wValue ? "connected to" : "disconnected from");
-                       break;
-
-               case USB_CDC_NOTIFY_SERIAL_STATE:
-
-                       newctrl = le16_to_cpu(get_unaligned((__le16 *) data));
+       case USB_CDC_NOTIFY_SERIAL_STATE:
+               tty = tty_port_tty_get(&acm->port);
+               newctrl = get_unaligned_le16(data);
 
-                       if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
+               if (tty) {
+                       if (!acm->clocal &&
+                               (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
                                dbg("calling hangup");
-                               tty_hangup(acm->tty);
+                               tty_hangup(tty);
                        }
+                       tty_kref_put(tty);
+               }
 
-                       acm->ctrlin = newctrl;
-
-                       dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c",
-                               acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
-                               acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', acm->ctrlin & ACM_CTRL_RI  ? '+' : '-',
-                               acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-',     acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-',
-                               acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-');
+               acm->ctrlin = newctrl;
 
+               dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c",
+                       acm->ctrlin & ACM_CTRL_DCD ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_BRK ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_RI  ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-',
+                       acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-');
                        break;
 
-               default:
-                       dbg("unknown notification %d received: index %d len %d data0 %d data1 %d",
-                               dr->bNotificationType, dr->wIndex,
-                               dr->wLength, data[0], data[1]);
-                       break;
+       default:
+               dbg("unknown notification %d received: index %d len %d data0 %d data1 %d",
+                       dr->bNotificationType, dr->wIndex,
+                       dr->wLength, data[0], data[1]);
+               break;
        }
 exit:
-       retval = usb_submit_urb (urb, GFP_ATOMIC);
+       usb_mark_last_busy(acm->dev);
+       retval = usb_submit_urb(urb, GFP_ATOMIC);
        if (retval)
-               err ("%s - usb_submit_urb failed with result %d",
-                    __FUNCTION__, retval);
+               dev_err(&urb->dev->dev, "%s - usb_submit_urb failed with "
+                       "result %d", __func__, retval);
 }
 
 /* data interface returns incoming bytes, or we got unthrottled */
@@ -328,8 +349,11 @@ static void acm_read_bulk(struct urb *urb)
 
        dbg("Entering acm_read_bulk with status %d", status);
 
-       if (!ACM_READY(acm))
+       if (!ACM_READY(acm)) {
+               dev_dbg(&acm->data->dev, "Aborting, acm not ready");
                return;
+       }
+       usb_mark_last_busy(acm->dev);
 
        if (status)
                dev_dbg(&acm->data->dev, "bulk rx status %d\n", status);
@@ -339,6 +363,7 @@ static void acm_read_bulk(struct urb *urb)
 
        if (likely(status == 0)) {
                spin_lock(&acm->read_lock);
+               acm->processing++;
                list_add_tail(&rcv->list, &acm->spare_read_urbs);
                list_add_tail(&buf->list, &acm->filled_read_bufs);
                spin_unlock(&acm->read_lock);
@@ -351,27 +376,35 @@ static void acm_read_bulk(struct urb *urb)
                /* nevertheless the tasklet must be kicked unconditionally
                so the queue cannot dry up */
        }
-       tasklet_schedule(&acm->urb_task);
+       if (likely(!acm->susp_count))
+               tasklet_schedule(&acm->urb_task);
 }
 
 static void acm_rx_tasklet(unsigned long _acm)
 {
        struct acm *acm = (void *)_acm;
        struct acm_rb *buf;
-       struct tty_struct *tty = acm->tty;
+       struct tty_struct *tty;
        struct acm_ru *rcv;
        unsigned long flags;
        unsigned char throttled;
+
        dbg("Entering acm_rx_tasklet");
 
-       if (!ACM_READY(acm))
+       if (!ACM_READY(acm)) {
+               dbg("acm_rx_tasklet: ACM not ready");
                return;
+       }
 
        spin_lock_irqsave(&acm->throttle_lock, flags);
        throttled = acm->throttle;
        spin_unlock_irqrestore(&acm->throttle_lock, flags);
-       if (throttled)
+       if (throttled) {
+               dbg("acm_rx_tasklet: throttled");
                return;
+       }
+
+       tty = tty_port_tty_get(&acm->port);
 
 next_buffer:
        spin_lock_irqsave(&acm->read_lock, flags);
@@ -386,20 +419,22 @@ next_buffer:
 
        dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d", buf, buf->size);
 
-       tty_buffer_request_room(tty, buf->size);
-       spin_lock_irqsave(&acm->throttle_lock, flags);
-       throttled = acm->throttle;
-       spin_unlock_irqrestore(&acm->throttle_lock, flags);
-       if (!throttled)
-               tty_insert_flip_string(tty, buf->base, buf->size);
-       tty_flip_buffer_push(tty);
-
-       if (throttled) {
-               dbg("Throttling noticed");
-               spin_lock_irqsave(&acm->read_lock, flags);
-               list_add(&buf->list, &acm->filled_read_bufs);
-               spin_unlock_irqrestore(&acm->read_lock, flags);
-               return;
+       if (tty) {
+               spin_lock_irqsave(&acm->throttle_lock, flags);
+               throttled = acm->throttle;
+               spin_unlock_irqrestore(&acm->throttle_lock, flags);
+               if (!throttled) {
+                       tty_buffer_request_room(tty, buf->size);
+                       tty_insert_flip_string(tty, buf->base, buf->size);
+                       tty_flip_buffer_push(tty);
+               } else {
+                       tty_kref_put(tty);
+                       dbg("Throttling noticed");
+                       spin_lock_irqsave(&acm->read_lock, flags);
+                       list_add(&buf->list, &acm->filled_read_bufs);
+                       spin_unlock_irqrestore(&acm->read_lock, flags);
+                       return;
+               }
        }
 
        spin_lock_irqsave(&acm->read_lock, flags);
@@ -408,9 +443,12 @@ next_buffer:
        goto next_buffer;
 
 urbs:
+       tty_kref_put(tty);
+
        while (!list_empty(&acm->spare_read_bufs)) {
                spin_lock_irqsave(&acm->read_lock, flags);
                if (list_empty(&acm->spare_read_urbs)) {
+                       acm->processing = 0;
                        spin_unlock_irqrestore(&acm->read_lock, flags);
                        return;
                }
@@ -425,49 +463,92 @@ urbs:
 
                rcv->buffer = buf;
 
-               usb_fill_bulk_urb(rcv->urb, acm->dev,
-                                 acm->rx_endpoint,
-                                 buf->base,
-                                 acm->readsize,
-                                 acm_read_bulk, rcv);
+               if (acm->is_int_ep)
+                       usb_fill_int_urb(rcv->urb, acm->dev,
+                                        acm->rx_endpoint,
+                                        buf->base,
+                                        acm->readsize,
+                                        acm_read_bulk, rcv, acm->bInterval);
+               else
+                       usb_fill_bulk_urb(rcv->urb, acm->dev,
+                                         acm->rx_endpoint,
+                                         buf->base,
+                                         acm->readsize,
+                                         acm_read_bulk, rcv);
                rcv->urb->transfer_dma = buf->dma;
                rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
 
-               dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
-
-               /* This shouldn't kill the driver as unsuccessful URBs are returned to the
-                  free-urbs-pool and resubmited ASAP */
-               if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
+               /* This shouldn't kill the driver as unsuccessful URBs are
+                  returned to the free-urbs-pool and resubmited ASAP */
+               spin_lock_irqsave(&acm->read_lock, flags);
+               if (acm->susp_count ||
+                               usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) {
                        list_add(&buf->list, &acm->spare_read_bufs);
-                       spin_lock_irqsave(&acm->read_lock, flags);
                        list_add(&rcv->list, &acm->spare_read_urbs);
+                       acm->processing = 0;
                        spin_unlock_irqrestore(&acm->read_lock, flags);
                        return;
+               } else {
+                       spin_unlock_irqrestore(&acm->read_lock, flags);
+                       dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf);
                }
        }
+       spin_lock_irqsave(&acm->read_lock, flags);
+       acm->processing = 0;
+       spin_unlock_irqrestore(&acm->read_lock, flags);
 }
 
 /* data interface wrote those outgoing bytes */
 static void acm_write_bulk(struct urb *urb)
 {
-       struct acm *acm = (struct acm *)urb->context;
+       struct acm_wb *wb = urb->context;
+       struct acm *acm = wb->instance;
+       unsigned long flags;
 
-       dbg("Entering acm_write_bulk with status %d", urb->status);
+       if (verbose || urb->status
+                       || (urb->actual_length != urb->transfer_buffer_length))
+               dev_dbg(&acm->data->dev, "tx %d/%d bytes -- > %d\n",
+                       urb->actual_length,
+                       urb->transfer_buffer_length,
+                       urb->status);
 
-       acm_write_done(acm);
-       acm_write_start(acm);
+       spin_lock_irqsave(&acm->write_lock, flags);
+       acm_write_done(acm, wb);
+       spin_unlock_irqrestore(&acm->write_lock, flags);
        if (ACM_READY(acm))
                schedule_work(&acm->work);
+       else
+               wake_up_interruptible(&acm->drain_wait);
 }
 
 static void acm_softint(struct work_struct *work)
 {
        struct acm *acm = container_of(work, struct acm, work);
-       dbg("Entering acm_softint.");
-       
+       struct tty_struct *tty;
+
+       dev_vdbg(&acm->data->dev, "tx work\n");
        if (!ACM_READY(acm))
                return;
-       tty_wakeup(acm->tty);
+       tty = tty_port_tty_get(&acm->port);
+       tty_wakeup(tty);
+       tty_kref_put(tty);
+}
+
+static void acm_waker(struct work_struct *waker)
+{
+       struct acm *acm = container_of(waker, struct acm, waker);
+       int rv;
+
+       rv = usb_autopm_get_interface(acm->control);
+       if (rv < 0) {
+               dev_err(&acm->dev->dev, "Autopm failure in %s\n", __func__);
+               return;
+       }
+       if (acm->delayed_wb) {
+               acm_start_wb(acm, acm->delayed_wb);
+               acm->delayed_wb = NULL;
+       }
+       usb_autopm_put_interface(acm->control);
 }
 
 /*
@@ -477,7 +558,7 @@ static void acm_softint(struct work_struct *work)
 static int acm_tty_open(struct tty_struct *tty, struct file *filp)
 {
        struct acm *acm;
-       int rv = -EINVAL;
+       int rv = -ENODEV;
        int i;
        dbg("Entering acm_tty_open.");
 
@@ -489,16 +570,21 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
        else
                rv = 0;
 
+       set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
+
        tty->driver_data = acm;
-       acm->tty = tty;
+       tty_port_tty_set(&acm->port, tty);
 
-       /* force low_latency on so that our tty_push actually forces the data through,
-          otherwise it is scheduled, and with high data rates data can get lost. */
-       tty->low_latency = 1;
+       if (usb_autopm_get_interface(acm->control) < 0)
+               goto early_bail;
+       else
+               acm->control->needs_remote_wakeup = 1;
 
-       if (acm->used++) {
+       mutex_lock(&acm->mutex);
+       if (acm->port.count++) {
+               usb_autopm_put_interface(acm->control);
                goto done;
-        }
+       }
 
        acm->ctrlurb->dev = acm->dev;
        if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
@@ -510,21 +596,24 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
            (acm->ctrl_caps & USB_CDC_CAP_LINE))
                goto full_bailout;
 
+       usb_autopm_put_interface(acm->control);
+
        INIT_LIST_HEAD(&acm->spare_read_urbs);
        INIT_LIST_HEAD(&acm->spare_read_bufs);
        INIT_LIST_HEAD(&acm->filled_read_bufs);
-       for (i = 0; i < acm->rx_buflimit; i++) {
+
+       for (i = 0; i < acm->rx_buflimit; i++)
                list_add(&(acm->ru[i].list), &acm->spare_read_urbs);
-       }
-       for (i = 0; i < acm->rx_buflimit; i++) {
+       for (i = 0; i < acm->rx_buflimit; i++)
                list_add(&(acm->rb[i].list), &acm->spare_read_bufs);
-       }
 
        acm->throttle = 0;
 
+       set_bit(ASYNCB_INITIALIZED, &acm->port.flags);
+       rv = tty_port_block_til_ready(&acm->port, tty, filp);
        tasklet_schedule(&acm->urb_task);
-
 done:
+       mutex_unlock(&acm->mutex);
 err_out:
        mutex_unlock(&open_mutex);
        return rv;
@@ -532,51 +621,90 @@ err_out:
 full_bailout:
        usb_kill_urb(acm->ctrlurb);
 bail_out:
-       acm->used--;
+       usb_autopm_put_interface(acm->control);
+       acm->port.count--;
+       mutex_unlock(&acm->mutex);
+early_bail:
        mutex_unlock(&open_mutex);
+       tty_port_tty_set(&acm->port, NULL);
        return -EIO;
 }
 
 static void acm_tty_unregister(struct acm *acm)
 {
-       int i,nr;
+       int i, nr;
 
        nr = acm->rx_buflimit;
        tty_unregister_device(acm_tty_driver, acm->minor);
        usb_put_intf(acm->control);
        acm_table[acm->minor] = NULL;
        usb_free_urb(acm->ctrlurb);
-       usb_free_urb(acm->writeurb);
+       for (i = 0; i < ACM_NW; i++)
+               usb_free_urb(acm->wb[i].urb);
        for (i = 0; i < nr; i++)
                usb_free_urb(acm->ru[i].urb);
        kfree(acm->country_codes);
        kfree(acm);
 }
 
+static int acm_tty_chars_in_buffer(struct tty_struct *tty);
+
+static void acm_port_down(struct acm *acm, int drain)
+{
+       int i, nr = acm->rx_buflimit;
+       mutex_lock(&open_mutex);
+       if (acm->dev) {
+               usb_autopm_get_interface(acm->control);
+               acm_set_control(acm, acm->ctrlout = 0);
+               /* try letting the last writes drain naturally */
+               if (drain) {
+                       wait_event_interruptible_timeout(acm->drain_wait,
+                               (ACM_NW == acm_wb_is_avail(acm)) || !acm->dev,
+                                       ACM_CLOSE_TIMEOUT * HZ);
+               }
+               usb_kill_urb(acm->ctrlurb);
+               for (i = 0; i < ACM_NW; i++)
+                       usb_kill_urb(acm->wb[i].urb);
+               for (i = 0; i < nr; i++)
+                       usb_kill_urb(acm->ru[i].urb);
+               acm->control->needs_remote_wakeup = 0;
+               usb_autopm_put_interface(acm->control);
+       }
+       mutex_unlock(&open_mutex);
+}
+
+static void acm_tty_hangup(struct tty_struct *tty)
+{
+       struct acm *acm = tty->driver_data;
+       tty_port_hangup(&acm->port);
+       acm_port_down(acm, 0);
+}
+
 static void acm_tty_close(struct tty_struct *tty, struct file *filp)
 {
        struct acm *acm = tty->driver_data;
-       int i,nr;
 
-       if (!acm || !acm->used)
+       /* Perform the closing process and see if we need to do the hardware
+          shutdown */
+       if (!acm)
                return;
-
-       nr = acm->rx_buflimit;
-       mutex_lock(&open_mutex);
-       if (!--acm->used) {
-               if (acm->dev) {
-                       acm_set_control(acm, acm->ctrlout = 0);
-                       usb_kill_urb(acm->ctrlurb);
-                       usb_kill_urb(acm->writeurb);
-                       for (i = 0; i < nr; i++)
-                               usb_kill_urb(acm->ru[i].urb);
-               } else
+       if (tty_port_close_start(&acm->port, tty, filp) == 0) {
+               mutex_lock(&open_mutex);
+               if (!acm->dev) {
+                       tty_port_tty_set(&acm->port, NULL);
                        acm_tty_unregister(acm);
+                       tty->driver_data = NULL;
+               }
+               mutex_unlock(&open_mutex);
+               return;
        }
-       mutex_unlock(&open_mutex);
+       acm_port_down(acm, 0);
+       tty_port_close_end(&acm->port, tty);
+       tty_port_tty_set(&acm->port, NULL);
 }
 
-static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+static int acm_tty_write(struct tty_struct *tty,
+                                       const unsigned char *buf, int count)
 {
        struct acm *acm = tty->driver_data;
        int stat;
@@ -592,9 +720,9 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c
                return 0;
 
        spin_lock_irqsave(&acm->write_lock, flags);
-       if ((wbn = acm_wb_alloc(acm)) < 0) {
+       wbn = acm_wb_alloc(acm);
+       if (wbn < 0) {
                spin_unlock_irqrestore(&acm->write_lock, flags);
-               acm_write_start(acm);
                return 0;
        }
        wb = &acm->wb[wbn];
@@ -605,7 +733,8 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c
        wb->len = count;
        spin_unlock_irqrestore(&acm->write_lock, flags);
 
-       if ((stat = acm_write_start(acm)) < 0)
+       stat = acm_write_start(acm, wbn);
+       if (stat < 0)
                return stat;
        return count;
 }
@@ -619,14 +748,14 @@ static int acm_tty_write_room(struct tty_struct *tty)
         * Do not let the line discipline to know that we have a reserve,
         * or it might get too enthusiastic.
         */
-       return (acm->write_ready && acm_wb_is_avail(acm)) ? acm->writesize : 0;
+       return acm_wb_is_avail(acm) ? acm->writesize : 0;
 }
 
 static int acm_tty_chars_in_buffer(struct tty_struct *tty)
 {
        struct acm *acm = tty->driver_data;
        if (!ACM_READY(acm))
-               return -EINVAL;
+               return 0;
        /*
         * This is inaccurate (overcounts), but it works.
         */
@@ -654,13 +783,16 @@ static void acm_tty_unthrottle(struct tty_struct *tty)
        tasklet_schedule(&acm->urb_task);
 }
 
-static void acm_tty_break_ctl(struct tty_struct *tty, int state)
+static int acm_tty_break_ctl(struct tty_struct *tty, int state)
 {
        struct acm *acm = tty->driver_data;
+       int retval;
        if (!ACM_READY(acm))
-               return;
-       if (acm_send_break(acm, state ? 0xffff : 0))
+               return -EINVAL;
+       retval = acm_send_break(acm, state ? 0xffff : 0);
+       if (retval < 0)
                dbg("send break failed");
+       return retval;
 }
 
 static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file)
@@ -688,8 +820,10 @@ static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file,
                return -EINVAL;
 
        newctrl = acm->ctrlout;
-       set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0);
-       clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0);
+       set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) |
+                                       (set & TIOCM_RTS ? ACM_CTRL_RTS : 0);
+       clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) |
+                                       (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0);
 
        newctrl = (newctrl & ~clear) | set;
 
@@ -698,7 +832,8 @@ static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file,
        return acm_set_control(acm, acm->ctrlout = newctrl);
 }
 
-static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+static int acm_tty_ioctl(struct tty_struct *tty, struct file *file,
+                                       unsigned int cmd, unsigned long arg)
 {
        struct acm *acm = tty->driver_data;
 
@@ -720,7 +855,8 @@ static const __u8 acm_tty_size[] = {
        5, 6, 7, 8
 };
 
-static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old)
+static void acm_tty_set_termios(struct tty_struct *tty,
+                                               struct ktermios *termios_old)
 {
        struct acm *acm = tty->driver_data;
        struct ktermios *termios = tty->termios;
@@ -730,19 +866,20 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios
        if (!ACM_READY(acm))
                return;
 
-       newline.dwDTERate = cpu_to_le32p(acm_tty_speed +
-               (termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0));
+       newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty));
        newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0;
        newline.bParityType = termios->c_cflag & PARENB ?
-               (termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
+                               (termios->c_cflag & PARODD ? 1 : 2) +
+                               (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
        newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
-
+       /* FIXME: Needs to clear unsupported bits in the termios */
        acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
 
        if (!newline.dwDTERate) {
                newline.dwDTERate = acm->line.dwDTERate;
                newctrl &= ~ACM_CTRL_DTR;
-       } else  newctrl |=  ACM_CTRL_DTR;
+       } else
+               newctrl |=  ACM_CTRL_DTR;
 
        if (newctrl != acm->ctrlout)
                acm_set_control(acm, acm->ctrlout = newctrl);
@@ -760,15 +897,25 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios
  * USB probe and disconnect routines.
  */
 
-/* Little helper: write buffers free */
+/* Little helpers: write/read buffers free */
 static void acm_write_buffers_free(struct acm *acm)
 {
        int i;
        struct acm_wb *wb;
+       struct usb_device *usb_dev = interface_to_usbdev(acm->control);
 
-       for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) {
-               usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah);
-       }
+       for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++)
+               usb_buffer_free(usb_dev, acm->writesize, wb->buf, wb->dmah);
+}
+
+static void acm_read_buffers_free(struct acm *acm)
+{
+       struct usb_device *usb_dev = interface_to_usbdev(acm->control);
+       int i, n = acm->rx_buflimit;
+
+       for (i = 0; i < n; i++)
+               usb_buffer_free(usb_dev, acm->readsize,
+                                       acm->rb[i].base, acm->rb[i].dma);
 }
 
 /* Little helper: write buffers allocate */
@@ -793,22 +940,22 @@ static int acm_write_buffers_alloc(struct acm *acm)
        return 0;
 }
 
-static int acm_probe (struct usb_interface *intf,
-                     const struct usb_device_id *id)
+static int acm_probe(struct usb_interface *intf,
+                    const struct usb_device_id *id)
 {
        struct usb_cdc_union_desc *union_header = NULL;
        struct usb_cdc_country_functional_desc *cfd = NULL;
-       char *buffer = intf->altsetting->extra;
+       unsigned char *buffer = intf->altsetting->extra;
        int buflen = intf->altsetting->extralen;
        struct usb_interface *control_interface;
        struct usb_interface *data_interface;
-       struct usb_endpoint_descriptor *epctrl;
-       struct usb_endpoint_descriptor *epread;
-       struct usb_endpoint_descriptor *epwrite;
+       struct usb_endpoint_descriptor *epctrl = NULL;
+       struct usb_endpoint_descriptor *epread = NULL;
+       struct usb_endpoint_descriptor *epwrite = NULL;
        struct usb_device *usb_dev = interface_to_usbdev(intf);
        struct acm *acm;
        int minor;
-       int ctrlsize,readsize;
+       int ctrlsize, readsize;
        u8 *buf;
        u8 ac_management_function = 0;
        u8 call_management_function = 0;
@@ -817,6 +964,7 @@ static int acm_probe (struct usb_interface *intf,
        unsigned long quirks;
        int num_rx_buf;
        int i;
+       int combined_interfaces = 0;
 
        /* normal quirks */
        quirks = (unsigned long)id->driver_info;
@@ -828,58 +976,65 @@ static int acm_probe (struct usb_interface *intf,
                control_interface = usb_ifnum_to_if(usb_dev, 0);
                goto skip_normal_probe;
        }
-       
+
        /* normal probing*/
        if (!buffer) {
-               err("Weird descriptor references\n");
+               dev_err(&intf->dev, "Weird descriptor references\n");
                return -EINVAL;
        }
 
        if (!buflen) {
-               if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) {
-                       dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint\n");
+               if (intf->cur_altsetting->endpoint->extralen &&
+                               intf->cur_altsetting->endpoint->extra) {
+                       dev_dbg(&intf->dev,
+                               "Seeking extra descriptors on endpoint\n");
                        buflen = intf->cur_altsetting->endpoint->extralen;
                        buffer = intf->cur_altsetting->endpoint->extra;
                } else {
-                       err("Zero length descriptor references\n");
+                       dev_err(&intf->dev,
+                               "Zero length descriptor references\n");
                        return -EINVAL;
                }
        }
 
        while (buflen > 0) {
-               if (buffer [1] != USB_DT_CS_INTERFACE) {
-                       err("skipping garbage\n");
+               if (buffer[1] != USB_DT_CS_INTERFACE) {
+                       dev_err(&intf->dev, "skipping garbage\n");
                        goto next_desc;
                }
 
-               switch (buffer [2]) {
-                       case USB_CDC_UNION_TYPE: /* we've found it */
-                               if (union_header) {
-                                       err("More than one union descriptor, skipping ...");
-                                       goto next_desc;
-                               }
-                               union_header = (struct usb_cdc_union_desc *)
-                                                       buffer;
-                               break;
-                       case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/
-                               cfd = (struct usb_cdc_country_functional_desc *)buffer;
-                               break;
-                       case USB_CDC_HEADER_TYPE: /* maybe check version */ 
-                               break; /* for now we ignore it */ 
-                       case USB_CDC_ACM_TYPE:
-                               ac_management_function = buffer[3];
-                               break;
-                       case USB_CDC_CALL_MANAGEMENT_TYPE:
-                               call_management_function = buffer[3];
-                               call_interface_num = buffer[4];
-                               if ((call_management_function & 3) != 3)
-                                       err("This device cannot do calls on its own. It is no modem.");
-                               break;
-                               
-                       default:
-                               err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]);
-                               break;
+               switch (buffer[2]) {
+               case USB_CDC_UNION_TYPE: /* we've found it */
+                       if (union_header) {
+                               dev_err(&intf->dev, "More than one "
+                                       "union descriptor, skipping ...\n");
+                               goto next_desc;
                        }
+                       union_header = (struct usb_cdc_union_desc *)buffer;
+                       break;
+               case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/
+                       cfd = (struct usb_cdc_country_functional_desc *)buffer;
+                       break;
+               case USB_CDC_HEADER_TYPE: /* maybe check version */
+                       break; /* for now we ignore it */
+               case USB_CDC_ACM_TYPE:
+                       ac_management_function = buffer[3];
+                       break;
+               case USB_CDC_CALL_MANAGEMENT_TYPE:
+                       call_management_function = buffer[3];
+                       call_interface_num = buffer[4];
+                       if ((call_management_function & 3) != 3)
+                               dev_err(&intf->dev, "This device cannot do calls on its own. It is not a modem.\n");
+                       break;
+               default:
+                       /* there are LOTS more CDC descriptors that
+                        * could legitimately be found here.
+                        */
+                       dev_dbg(&intf->dev, "Ignoring descriptor: "
+                                       "type %02x, length %d\n",
+                                       buffer[2], buffer[0]);
+                       break;
+               }
 next_desc:
                buflen -= buffer[0];
                buffer += buffer[0];
@@ -887,33 +1042,72 @@ next_desc:
 
        if (!union_header) {
                if (call_interface_num > 0) {
-                       dev_dbg(&intf->dev,"No union descriptor, using call management descriptor\n");
+                       dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n");
                        data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num));
                        control_interface = intf;
                } else {
-                       dev_dbg(&intf->dev,"No union descriptor, giving up\n");
-                       return -ENODEV;
+                       if (intf->cur_altsetting->desc.bNumEndpoints != 3) {
+                               dev_dbg(&intf->dev,"No union descriptor, giving up\n");
+                               return -ENODEV;
+                       } else {
+                               dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n");
+                               combined_interfaces = 1;
+                               control_interface = data_interface = intf;
+                               goto look_for_collapsed_interface;
+                       }
                }
        } else {
                control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);
                data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0));
                if (!control_interface || !data_interface) {
-                       dev_dbg(&intf->dev,"no interfaces\n");
+                       dev_dbg(&intf->dev, "no interfaces\n");
                        return -ENODEV;
                }
        }
-       
+
        if (data_interface_num != call_interface_num)
-               dev_dbg(&intf->dev,"Seperate call control interface. That is not fully supported.\n");
+               dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n");
+
+       if (control_interface == data_interface) {
+               /* some broken devices designed for windows work this way */
+               dev_warn(&intf->dev,"Control and data interfaces are not separated!\n");
+               combined_interfaces = 1;
+               /* a popular other OS doesn't use it */
+               quirks |= NO_CAP_LINE;
+               if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {
+                       dev_err(&intf->dev, "This needs exactly 3 endpoints\n");
+                       return -EINVAL;
+               }
+look_for_collapsed_interface:
+               for (i = 0; i < 3; i++) {
+                       struct usb_endpoint_descriptor *ep;
+                       ep = &data_interface->cur_altsetting->endpoint[i].desc;
+
+                       if (usb_endpoint_is_int_in(ep))
+                               epctrl = ep;
+                       else if (usb_endpoint_is_bulk_out(ep))
+                               epwrite = ep;
+                       else if (usb_endpoint_is_bulk_in(ep))
+                               epread = ep;
+                       else
+                               return -EINVAL;
+               }
+               if (!epctrl || !epread || !epwrite)
+                       return -ENODEV;
+               else
+                       goto made_compressed_probe;
+       }
 
 skip_normal_probe:
 
        /*workaround for switched interfaces */
-       if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) {
-               if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) {
+       if (data_interface->cur_altsetting->desc.bInterfaceClass
+                                               != CDC_DATA_INTERFACE_TYPE) {
+               if (control_interface->cur_altsetting->desc.bInterfaceClass
+                                               == CDC_DATA_INTERFACE_TYPE) {
                        struct usb_interface *t;
-                       dev_dbg(&intf->dev,"Your device has switched interfaces.\n");
-
+                       dev_dbg(&intf->dev,
+                               "Your device has switched interfaces.\n");
                        t = control_interface;
                        control_interface = data_interface;
                        data_interface = t;
@@ -923,11 +1117,12 @@ skip_normal_probe:
        }
 
        /* Accept probe requests only for the control interface */
-       if (intf != control_interface)
+       if (!combined_interfaces && intf != control_interface)
                return -ENODEV;
-       
-       if (usb_interface_claimed(data_interface)) { /* valid in this context */
-               dev_dbg(&intf->dev,"The data interface isn't available\n");
+
+       if (!combined_interfaces && usb_interface_claimed(data_interface)) {
+               /* valid in this context */
+               dev_dbg(&intf->dev, "The data interface isn't available\n");
                return -EBUSY;
        }
 
@@ -944,44 +1139,57 @@ skip_normal_probe:
        if (!usb_endpoint_dir_in(epread)) {
                /* descriptors are swapped */
                struct usb_endpoint_descriptor *t;
-               dev_dbg(&intf->dev,"The data interface has switched endpoints\n");
-               
+               dev_dbg(&intf->dev,
+                       "The data interface has switched endpoints\n");
                t = epread;
                epread = epwrite;
                epwrite = t;
        }
+made_compressed_probe:
        dbg("interfaces are valid");
        for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++);
 
        if (minor == ACM_TTY_MINORS) {
-               err("no more free acm devices");
+               dev_err(&intf->dev, "no more free acm devices\n");
                return -ENODEV;
        }
 
-       if (!(acm = kzalloc(sizeof(struct acm), GFP_KERNEL))) {
+       acm = kzalloc(sizeof(struct acm), GFP_KERNEL);
+       if (acm == NULL) {
                dev_dbg(&intf->dev, "out of memory (acm kzalloc)\n");
                goto alloc_fail;
        }
 
        ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize);
-       readsize = le16_to_cpu(epread->wMaxPacketSize)* ( quirks == SINGLE_RX_URB ? 1 : 2);
-       acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize);
+       readsize = le16_to_cpu(epread->wMaxPacketSize) *
+                               (quirks == SINGLE_RX_URB ? 1 : 2);
+       acm->combined_interfaces = combined_interfaces;
+       acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20;
        acm->control = control_interface;
        acm->data = data_interface;
        acm->minor = minor;
        acm->dev = usb_dev;
        acm->ctrl_caps = ac_management_function;
+       if (quirks & NO_CAP_LINE)
+               acm->ctrl_caps &= ~USB_CDC_CAP_LINE;
        acm->ctrlsize = ctrlsize;
        acm->readsize = readsize;
        acm->rx_buflimit = num_rx_buf;
        acm->urb_task.func = acm_rx_tasklet;
        acm->urb_task.data = (unsigned long) acm;
        INIT_WORK(&acm->work, acm_softint);
+       INIT_WORK(&acm->waker, acm_waker);
+       init_waitqueue_head(&acm->drain_wait);
        spin_lock_init(&acm->throttle_lock);
        spin_lock_init(&acm->write_lock);
        spin_lock_init(&acm->read_lock);
-       acm->write_ready = 1;
+       mutex_init(&acm->mutex);
        acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
+       acm->is_int_ep = usb_endpoint_xfer_int(epread);
+       if (acm->is_int_ep)
+               acm->bInterval = epread->bInterval;
+       tty_port_init(&acm->port);
+       acm->port.ops = &acm_port_ops;
 
        buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
        if (!buf) {
@@ -1003,8 +1211,10 @@ skip_normal_probe:
        for (i = 0; i < num_rx_buf; i++) {
                struct acm_ru *rcv = &(acm->ru[i]);
 
-               if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) {
-                       dev_dbg(&intf->dev, "out of memory (read urbs usb_alloc_urb)\n");
+               rcv->urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (rcv->urb == NULL) {
+                       dev_dbg(&intf->dev,
+                               "out of memory (read urbs usb_alloc_urb)\n");
                        goto alloc_fail7;
                }
 
@@ -1012,20 +1222,39 @@ skip_normal_probe:
                rcv->instance = acm;
        }
        for (i = 0; i < num_rx_buf; i++) {
-               struct acm_rb *buf = &(acm->rb[i]);
+               struct acm_rb *rb = &(acm->rb[i]);
 
-               if (!(buf->base = usb_buffer_alloc(acm->dev, readsize, GFP_KERNEL, &buf->dma))) {
-                       dev_dbg(&intf->dev, "out of memory (read bufs usb_buffer_alloc)\n");
+               rb->base = usb_buffer_alloc(acm->dev, readsize,
+                               GFP_KERNEL, &rb->dma);
+               if (!rb->base) {
+                       dev_dbg(&intf->dev,
+                               "out of memory (read bufs usb_buffer_alloc)\n");
                        goto alloc_fail7;
                }
        }
-       acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
-       if (!acm->writeurb) {
-               dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)\n");
-               goto alloc_fail7;
+       for (i = 0; i < ACM_NW; i++) {
+               struct acm_wb *snd = &(acm->wb[i]);
+
+               snd->urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (snd->urb == NULL) {
+                       dev_dbg(&intf->dev,
+                               "out of memory (write urbs usb_alloc_urb)");
+                       goto alloc_fail7;
+               }
+
+               if (usb_endpoint_xfer_int(epwrite))
+                       usb_fill_int_urb(snd->urb, usb_dev,
+                               usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
+                               NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);
+               else
+                       usb_fill_bulk_urb(snd->urb, usb_dev,
+                               usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
+                               NULL, acm->writesize, acm_write_bulk, snd);
+               snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+               snd->instance = acm;
        }
 
-       usb_set_intfdata (intf, acm);
+       usb_set_intfdata(intf, acm);
 
        i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
        if (i < 0)
@@ -1036,7 +1265,8 @@ skip_normal_probe:
                if (!acm->country_codes)
                        goto skip_countries;
                acm->country_code_size = cfd->bLength - 4;
-               memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, cfd->bLength - 4);
+               memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0,
+                                                       cfd->bLength - 4);
                acm->country_rel_date = cfd->iCountryCodeRelDate;
 
                i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);
@@ -1045,7 +1275,8 @@ skip_normal_probe:
                        goto skip_countries;
                }
 
-               i = device_create_file(&intf->dev, &dev_attr_iCountryCodeRelDate);
+               i = device_create_file(&intf->dev,
+                                               &dev_attr_iCountryCodeRelDate);
                if (i < 0) {
                        kfree(acm->country_codes);
                        goto skip_countries;
@@ -1053,15 +1284,14 @@ skip_normal_probe:
        }
 
 skip_countries:
-       usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
-                        acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
+       usb_fill_int_urb(acm->ctrlurb, usb_dev,
+                        usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
+                        acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm,
+                        /* works around buggy devices */
+                        epctrl->bInterval ? epctrl->bInterval : 0xff);
        acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        acm->ctrlurb->transfer_dma = acm->ctrl_dma;
 
-       usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
-                         NULL, acm->writesize, acm_write_bulk, acm);
-       acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
-
        dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
 
        acm_set_control(acm, acm->ctrlout);
@@ -1071,6 +1301,7 @@ skip_countries:
        acm_set_line(acm, &acm->line);
 
        usb_driver_claim_interface(&acm_driver, data_interface, acm);
+       usb_set_intfdata(data_interface, acm);
 
        usb_get_intf(control_interface);
        tty_register_device(acm_tty_driver, minor, &control_interface->dev);
@@ -1079,10 +1310,10 @@ skip_countries:
 
        return 0;
 alloc_fail8:
-       usb_free_urb(acm->writeurb);
+       for (i = 0; i < ACM_NW; i++)
+               usb_free_urb(acm->wb[i].urb);
 alloc_fail7:
-       for (i = 0; i < num_rx_buf; i++)
-               usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
+       acm_read_buffers_free(acm);
        for (i = 0; i < num_rx_buf; i++)
                usb_free_urb(acm->ru[i].urb);
        usb_free_urb(acm->ctrlurb);
@@ -1096,23 +1327,37 @@ alloc_fail:
        return -ENOMEM;
 }
 
+static void stop_data_traffic(struct acm *acm)
+{
+       int i;
+       dbg("Entering stop_data_traffic");
+
+       tasklet_disable(&acm->urb_task);
+
+       usb_kill_urb(acm->ctrlurb);
+       for (i = 0; i < ACM_NW; i++)
+               usb_kill_urb(acm->wb[i].urb);
+       for (i = 0; i < acm->rx_buflimit; i++)
+               usb_kill_urb(acm->ru[i].urb);
+
+       tasklet_enable(&acm->urb_task);
+
+       cancel_work_sync(&acm->work);
+       cancel_work_sync(&acm->waker);
+}
+
 static void acm_disconnect(struct usb_interface *intf)
 {
        struct acm *acm = usb_get_intfdata(intf);
        struct usb_device *usb_dev = interface_to_usbdev(intf);
-       int i;
+       struct tty_struct *tty;
 
-       if (!acm || !acm->dev) {
-               dbg("disconnect on nonexisting interface");
+       /* sibling interface is already cleaning up */
+       if (!acm)
                return;
-       }
 
        mutex_lock(&open_mutex);
-       if (!usb_get_intfdata(intf)) {
-               mutex_unlock(&open_mutex);
-               return;
-       }
-       if (acm->country_codes){
+       if (acm->country_codes) {
                device_remove_file(&acm->control->dev,
                                &dev_attr_wCountryCodes);
                device_remove_file(&acm->control->dev,
@@ -1123,39 +1368,99 @@ static void acm_disconnect(struct usb_interface *intf)
        usb_set_intfdata(acm->control, NULL);
        usb_set_intfdata(acm->data, NULL);
 
-       tasklet_disable(&acm->urb_task);
-
-       usb_kill_urb(acm->ctrlurb);
-       usb_kill_urb(acm->writeurb);
-       for (i = 0; i < acm->rx_buflimit; i++)
-               usb_kill_urb(acm->ru[i].urb);
-
-       INIT_LIST_HEAD(&acm->filled_read_bufs);
-       INIT_LIST_HEAD(&acm->spare_read_bufs);
-
-       tasklet_enable(&acm->urb_task);
-
-       flush_scheduled_work(); /* wait for acm_softint */
+       stop_data_traffic(acm);
 
        acm_write_buffers_free(acm);
-       usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
-       for (i = 0; i < acm->rx_buflimit; i++)
-               usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
+       usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer,
+                                                               acm->ctrl_dma);
+       acm_read_buffers_free(acm);
 
-       usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf);
+       if (!acm->combined_interfaces)
+               usb_driver_release_interface(&acm_driver, intf == acm->control ?
+                                       acm->data : acm->control);
 
-       if (!acm->used) {
+       if (acm->port.count == 0) {
                acm_tty_unregister(acm);
                mutex_unlock(&open_mutex);
                return;
        }
 
        mutex_unlock(&open_mutex);
+       tty = tty_port_tty_get(&acm->port);
+       if (tty) {
+               tty_hangup(tty);
+               tty_kref_put(tty);
+       }
+}
+
+#ifdef CONFIG_PM
+static int acm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+       struct acm *acm = usb_get_intfdata(intf);
+       int cnt;
+
+       if (message.event & PM_EVENT_AUTO) {
+               int b;
+
+               spin_lock_irq(&acm->read_lock);
+               spin_lock(&acm->write_lock);
+               b = acm->processing + acm->transmitting;
+               spin_unlock(&acm->write_lock);
+               spin_unlock_irq(&acm->read_lock);
+               if (b)
+                       return -EBUSY;
+       }
+
+       spin_lock_irq(&acm->read_lock);
+       spin_lock(&acm->write_lock);
+       cnt = acm->susp_count++;
+       spin_unlock(&acm->write_lock);
+       spin_unlock_irq(&acm->read_lock);
 
-       if (acm->tty)
-               tty_hangup(acm->tty);
+       if (cnt)
+               return 0;
+       /*
+       we treat opened interfaces differently,
+       we must guard against open
+       */
+       mutex_lock(&acm->mutex);
+
+       if (acm->port.count)
+               stop_data_traffic(acm);
+
+       mutex_unlock(&acm->mutex);
+       return 0;
 }
 
+static int acm_resume(struct usb_interface *intf)
+{
+       struct acm *acm = usb_get_intfdata(intf);
+       int rv = 0;
+       int cnt;
+
+       spin_lock_irq(&acm->read_lock);
+       acm->susp_count -= 1;
+       cnt = acm->susp_count;
+       spin_unlock_irq(&acm->read_lock);
+
+       if (cnt)
+               return 0;
+
+       mutex_lock(&acm->mutex);
+       if (acm->port.count) {
+               rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
+               if (rv < 0)
+                       goto err_out;
+
+               tasklet_schedule(&acm->urb_task);
+       }
+
+err_out:
+       mutex_unlock(&acm->mutex);
+       return rv;
+}
+
+#endif /* CONFIG_PM */
 /*
  * USB driver structure.
  */
@@ -1168,12 +1473,18 @@ static struct usb_device_id acm_ids[] = {
        { USB_DEVICE(0x0e8d, 0x0003), /* FIREFLY, MediaTek Inc; andrey.arapov@gmail.com */
        .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
        },
+       { USB_DEVICE(0x0e8d, 0x3329), /* MediaTek Inc GPS */
+       .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+       },
        { USB_DEVICE(0x0482, 0x0203), /* KYOCERA AH-K3001V */
        .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
        },
        { USB_DEVICE(0x079b, 0x000f), /* BT On-Air USB MODEM */
        .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
        },
+       { USB_DEVICE(0x0ace, 0x1602), /* ZyDAS 56K USB MODEM */
+       .driver_info = SINGLE_RX_URB,
+       },
        { USB_DEVICE(0x0ace, 0x1608), /* ZyDAS 56K USB MODEM */
        .driver_info = SINGLE_RX_URB, /* firmware bug */
        },
@@ -1183,6 +1494,30 @@ static struct usb_device_id acm_ids[] = {
        { USB_DEVICE(0x22b8, 0x7000), /* Motorola Q Phone */
        .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
        },
+       { USB_DEVICE(0x0803, 0x3095), /* Zoom Telephonics Model 3095F USB MODEM */
+       .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+       },
+       { USB_DEVICE(0x0572, 0x1321), /* Conexant USB MODEM CX93010 */
+       .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+       },
+       { USB_DEVICE(0x0572, 0x1324), /* Conexant USB MODEM RD02-D400 */
+       .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+       },
+       { USB_DEVICE(0x0572, 0x1328), /* Shiro / Aztech USB MODEM UM-3100 */
+       .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+       },
+       { USB_DEVICE(0x22b8, 0x6425), /* Motorola MOTOMAGX phones */
+       },
+       { USB_DEVICE(0x0572, 0x1329), /* Hummingbird huc56s (Conexant) */
+       .driver_info = NO_UNION_NORMAL, /* union descriptor misplaced on
+                                          data interface instead of
+                                          communications interface.
+                                          Maybe we should define a new
+                                          quirk for this. */
+       },
+       { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */
+       .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */
+       },
 
        /* control interfaces with various AT-command sets */
        { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
@@ -1194,7 +1529,7 @@ static struct usb_device_id acm_ids[] = {
        { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
                USB_CDC_ACM_PROTO_AT_GSM) },
        { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
-               USB_CDC_ACM_PROTO_AT_3G ) },
+               USB_CDC_ACM_PROTO_AT_3G) },
        { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
                USB_CDC_ACM_PROTO_AT_CDMA) },
 
@@ -1202,13 +1537,20 @@ static struct usb_device_id acm_ids[] = {
        { }
 };
 
-MODULE_DEVICE_TABLE (usb, acm_ids);
+MODULE_DEVICE_TABLE(usb, acm_ids);
 
 static struct usb_driver acm_driver = {
        .name =         "cdc_acm",
        .probe =        acm_probe,
        .disconnect =   acm_disconnect,
+#ifdef CONFIG_PM
+       .suspend =      acm_suspend,
+       .resume =       acm_resume,
+#endif
        .id_table =     acm_ids,
+#ifdef CONFIG_PM
+       .supports_autosuspend = 1,
+#endif
 };
 
 /*
@@ -1218,6 +1560,7 @@ static struct usb_driver acm_driver = {
 static const struct tty_operations acm_ops = {
        .open =                 acm_tty_open,
        .close =                acm_tty_close,
+       .hangup =               acm_tty_hangup,
        .write =                acm_tty_write,
        .write_room =           acm_tty_write_room,
        .ioctl =                acm_tty_ioctl,
@@ -1249,7 +1592,8 @@ static int __init acm_init(void)
        acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
        acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
        acm_tty_driver->init_termios = tty_std_termios;
-       acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+       acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD |
+                                                               HUPCL | CLOCAL;
        tty_set_operations(acm_tty_driver, &acm_ops);
 
        retval = tty_register_driver(acm_tty_driver);
@@ -1265,7 +1609,8 @@ static int __init acm_init(void)
                return retval;
        }
 
-       info(DRIVER_VERSION ":" DRIVER_DESC);
+       printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
+              DRIVER_DESC "\n");
 
        return 0;
 }
@@ -1280,7 +1625,7 @@ static void __exit acm_exit(void)
 module_init(acm_init);
 module_exit(acm_exit);
 
-MODULE_AUTHOR( DRIVER_AUTHOR );
-MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_LICENSE("GPL");
-
+MODULE_ALIAS_CHARDEV_MAJOR(ACM_TTY_MAJOR);