fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / class / cdc-acm.c
index 4418170..9819962 100644 (file)
@@ -5,6 +5,8 @@
  * Copyright (c) 1999 Pavel Machek     <pavel@suse.cz>
  * Copyright (c) 1999 Johannes Erdfelt <johannes@erdfelt.com>
  * Copyright (c) 2000 Vojtech Pavlik   <vojtech@suse.cz>
+ * Copyright (c) 2004 Oliver Neukum    <oliver@neukum.name>
+ * Copyright (c) 2005 David Kubicek    <dave@awk.cz>
  *
  * USB Abstract Control Model driver for USB modems and ISDN adapters
  *
@@ -27,6 +29,8 @@
  *     v0.22 - probe only the control interface. if usbcore doesn't choose the
  *             config we want, sysadmin changes bConfigurationValue in sysfs.
  *     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
  */
 
 /*
 #include <linux/tty_flip.h>
 #include <linux/module.h>
 #include <linux/smp_lock.h>
+#include <linux/mutex.h>
 #include <asm/uaccess.h>
 #include <linux/usb.h>
+#include <linux/usb/cdc.h>
 #include <asm/byteorder.h>
+#include <asm/unaligned.h>
+#include <linux/list.h>
+
+#include "cdc-acm.h"
 
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.23"
-#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
+#define DRIVER_VERSION "v0.25"
+#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"
 
-/*
- * CMSPAR, some architectures can't have space and mark parity.
- */
-
-#ifndef CMSPAR
-#define CMSPAR                 0
-#endif
+static struct usb_driver acm_driver;
+static struct tty_driver *acm_tty_driver;
+static struct acm *acm_table[ACM_TTY_MINORS];
 
-/*
- * Major and minor numbers.
- */
+static DEFINE_MUTEX(open_mutex);
 
-#define ACM_TTY_MAJOR          166
-#define ACM_TTY_MINORS         32
+#define ACM_READY(acm) (acm && acm->dev && acm->used)
 
 /*
- * Requests.
+ * Functions for ACM control messages.
  */
 
-#define USB_RT_ACM             (USB_TYPE_CLASS | USB_RECIP_INTERFACE)
-
-#define ACM_REQ_COMMAND                0x00
-#define ACM_REQ_RESPONSE       0x01
-#define ACM_REQ_SET_FEATURE    0x02
-#define ACM_REQ_GET_FEATURE    0x03
-#define ACM_REQ_CLEAR_FEATURE  0x04
-
-#define ACM_REQ_SET_LINE       0x20
-#define ACM_REQ_GET_LINE       0x21
-#define ACM_REQ_SET_CONTROL    0x22
-#define ACM_REQ_SEND_BREAK     0x23
+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);
+       return retval < 0 ? retval : 0;
+}
 
-/*
- * IRQs.
+/* devices aren't required to support these requests.
+ * the cdc acm descriptor tells whether they do...
  */
-
-#define ACM_IRQ_NETWORK                0x00
-#define ACM_IRQ_LINE_STATE     0x20
+#define acm_set_control(acm, control) \
+       acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0)
+#define acm_set_line(acm, line) \
+       acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line))
+#define acm_send_break(acm, ms) \
+       acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0)
 
 /*
- * Output control lines.
+ * Write buffer management.
+ * All of these assume proper locks taken by the caller.
  */
 
-#define ACM_CTRL_DTR           0x01
-#define ACM_CTRL_RTS           0x02
+static int acm_wb_alloc(struct acm *acm)
+{
+       int i, wbn;
+       struct acm_wb *wb;
+
+       wbn = acm->write_current;
+       i = 0;
+       for (;;) {
+               wb = &acm->wb[wbn];
+               if (!wb->use) {
+                       wb->use = 1;
+                       return wbn;
+               }
+               wbn = (wbn + 1) % ACM_NW;
+               if (++i >= ACM_NW)
+                       return -1;
+       }
+}
 
-/*
- * Input control lines and line errors.
- */
+static void acm_wb_free(struct acm *acm, int wbn)
+{
+       acm->wb[wbn].use = 0;
+}
 
-#define ACM_CTRL_DCD           0x01
-#define ACM_CTRL_DSR           0x02
-#define ACM_CTRL_BRK           0x04
-#define ACM_CTRL_RI            0x08
+static int acm_wb_is_avail(struct acm *acm)
+{
+       int i, n;
+
+       n = ACM_NW;
+       for (i = 0; i < ACM_NW; i++) {
+               n -= acm->wb[i].use;
+       }
+       return n;
+}
 
-#define ACM_CTRL_FRAMING       0x10
-#define ACM_CTRL_PARITY                0x20
-#define ACM_CTRL_OVERRUN       0x40
+static inline int acm_wb_is_used(struct acm *acm, int wbn)
+{
+       return acm->wb[wbn].use;
+}
 
 /*
- * Line speed and caracter encoding.
+ * Finish write.
  */
-
-struct acm_line {
-       __u32 speed;
-       __u8 stopbits;
-       __u8 parity;
-       __u8 databits;
-} __attribute__ ((packed));
+static void acm_write_done(struct acm *acm)
+{
+       unsigned long flags;
+       int wbn;
+
+       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);
+}
 
 /*
- * Internal driver structures.
+ * Poke write.
  */
+static int acm_write_start(struct acm *acm)
+{
+       unsigned long flags;
+       int wbn;
+       struct acm_wb *wb;
+       int rc;
+
+       spin_lock_irqsave(&acm->write_lock, flags);
+       if (!acm->dev) {
+               spin_unlock_irqrestore(&acm->write_lock, flags);
+               return -ENODEV;
+       }
 
-struct acm {
-       struct usb_device *dev;                         /* the corresponding usb device */
-       struct usb_interface *control;                  /* control interface */
-       struct usb_interface *data;                     /* data interface */
-       struct tty_struct *tty;                         /* the corresponding tty */
-       struct urb *ctrlurb, *readurb, *writeurb;       /* urbs */
-       struct acm_line line;                           /* line coding (bits, stop, parity) */
-       struct work_struct work;                        /* work queue entry for line discipline waking up */
-       struct tasklet_struct bh;                       /* rx processing */
-       unsigned int ctrlin;                            /* input control lines (DCD, DSR, RI, break, overruns) */
-       unsigned int ctrlout;                           /* output control lines (DTR, RTS) */
-       unsigned int writesize;                         /* max packet size for the output bulk endpoint */
-       unsigned int used;                              /* someone has this acm's device open */
-       unsigned int minor;                             /* acm minor number */
-       unsigned char throttle;                         /* throttled by tty layer */
-       unsigned char clocal;                           /* termios CLOCAL */
-};
+       if (!acm->write_ready) {
+               spin_unlock_irqrestore(&acm->write_lock, flags);
+               return 0;       /* A white lie */
+       }
 
-static struct usb_driver acm_driver;
-static struct tty_driver *acm_tty_driver;
-static struct acm *acm_table[ACM_TTY_MINORS];
+       wbn = acm->write_current;
+       if (!acm_wb_is_used(acm, wbn)) {
+               spin_unlock_irqrestore(&acm->write_lock, flags);
+               return 0;
+       }
+       wb = &acm->wb[wbn];
 
-#define ACM_READY(acm) (acm && acm->dev && acm->used)
+       acm->write_ready = 0;
+       spin_unlock_irqrestore(&acm->write_lock, flags);
 
-/*
- * Functions for ACM control messages.
- */
+       acm->writeurb->transfer_buffer = wb->buf;
+       acm->writeurb->transfer_dma = wb->dmah;
+       acm->writeurb->transfer_buffer_length = wb->len;
+       acm->writeurb->dev = acm->dev;
 
-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, HZ * 5);
-       dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval);
-       return retval < 0 ? retval : 0;
+       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;
 }
 
-/* devices aren't required to support these requests.
- * the cdc acm descriptor tells whether they do...
- */
-#define acm_set_control(acm, control)  acm_ctrl_msg(acm, ACM_REQ_SET_CONTROL, control, NULL, 0)
-#define acm_set_line(acm, line)                acm_ctrl_msg(acm, ACM_REQ_SET_LINE, 0, line, sizeof(struct acm_line))
-#define acm_send_break(acm, ms)                acm_ctrl_msg(acm, ACM_REQ_SEND_BREAK, ms, NULL, 0)
-
 /*
  * Interrupt handlers for various ACM device responses
  */
 
 /* control interface reports status changes with "interrupt" transfers */
-static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
+static void acm_ctrl_irq(struct urb *urb)
 {
        struct acm *acm = urb->context;
-       struct usb_ctrlrequest *dr = urb->transfer_buffer;
-       unsigned char *data = (unsigned char *)(dr + 1);
+       struct usb_cdc_notification *dr = urb->transfer_buffer;
+       unsigned char *data;
        int newctrl;
        int status;
 
@@ -217,16 +244,17 @@ static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
        if (!ACM_READY(acm))
                goto exit;
 
-       switch (dr->bRequest) {
+       data = (unsigned char *)(dr + 1);
+       switch (dr->bNotificationType) {
 
-               case ACM_IRQ_NETWORK:
+               case USB_CDC_NOTIFY_NETWORK_CONNECTION:
 
                        dbg("%s network", dr->wValue ? "connected to" : "disconnected from");
                        break;
 
-               case ACM_IRQ_LINE_STATE:
+               case USB_CDC_NOTIFY_SERIAL_STATE:
 
-                       newctrl = le16_to_cpup((__u16 *) data);
+                       newctrl = le16_to_cpu(get_unaligned((__le16 *) data));
 
                        if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
                                dbg("calling hangup");
@@ -244,8 +272,9 @@ static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
                        break;
 
                default:
-                       dbg("unknown control event received: request %d index %d len %d data0 %d data1 %d",
-                               dr->bRequest, dr->wIndex, dr->wLength, data[0], data[1]);
+                       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:
@@ -256,80 +285,150 @@ exit:
 }
 
 /* data interface returns incoming bytes, or we got unthrottled */
-static void acm_read_bulk(struct urb *urb, struct pt_regs *regs)
+static void acm_read_bulk(struct urb *urb)
 {
-       struct acm *acm = urb->context;
+       struct acm_rb *buf;
+       struct acm_ru *rcv = urb->context;
+       struct acm *acm = rcv->instance;
+       int status = urb->status;
+       dbg("Entering acm_read_bulk with status %d", urb->status);
 
        if (!ACM_READY(acm))
                return;
 
-       if (urb->status)
-               dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status);
-
-       /* calling tty_flip_buffer_push() in_irq() isn't allowed */
-       tasklet_schedule(&acm->bh);
+       if (status)
+               dev_dbg(&acm->data->dev, "bulk rx status %d", status);
+
+       buf = rcv->buffer;
+       buf->size = urb->actual_length;
+
+       if (likely(status == 0)) {
+               spin_lock(&acm->read_lock);
+               list_add_tail(&rcv->list, &acm->spare_read_urbs);
+               list_add_tail(&buf->list, &acm->filled_read_bufs);
+               spin_unlock(&acm->read_lock);
+       } else {
+               /* we drop the buffer due to an error */
+               spin_lock(&acm->read_lock);
+               list_add_tail(&rcv->list, &acm->spare_read_urbs);
+               list_add(&buf->list, &acm->spare_read_bufs);
+               spin_unlock(&acm->read_lock);
+               /* nevertheless the tasklet must be kicked unconditionally
+               so the queue cannot dry up */
+       }
+       tasklet_schedule(&acm->urb_task);
 }
 
 static void acm_rx_tasklet(unsigned long _acm)
 {
        struct acm *acm = (void *)_acm;
-       struct urb *urb = acm->readurb;
+       struct acm_rb *buf;
        struct tty_struct *tty = acm->tty;
-       unsigned char *data = urb->transfer_buffer;
+       struct acm_ru *rcv;
+       unsigned long flags;
        int i = 0;
+       dbg("Entering acm_rx_tasklet");
 
-       if (urb->actual_length > 0 && !acm->throttle)  {
-               for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
-                       /* if we insert more than TTY_FLIPBUF_SIZE characters,
-                        * we drop them. */
-                       if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
-                               tty_flip_buffer_push(tty);
-                       }
-                       tty_insert_flip_char(tty, data[i], 0);
-               }
-               tty_flip_buffer_push(tty);
+       if (!ACM_READY(acm) || acm->throttle)
+               return;
+
+next_buffer:
+       spin_lock_irqsave(&acm->read_lock, flags);
+       if (list_empty(&acm->filled_read_bufs)) {
+               spin_unlock_irqrestore(&acm->read_lock, flags);
+               goto urbs;
        }
+       buf = list_entry(acm->filled_read_bufs.next,
+                        struct acm_rb, list);
+       list_del(&buf->list);
+       spin_unlock_irqrestore(&acm->read_lock, flags);
+
+       dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d", buf, buf->size);
+
+       tty_buffer_request_room(tty, buf->size);
+       if (!acm->throttle)
+               tty_insert_flip_string(tty, buf->base, buf->size);
+       tty_flip_buffer_push(tty);
 
+       spin_lock(&acm->throttle_lock);
        if (acm->throttle) {
-               memmove(data, data + i, urb->actual_length - i);
-               urb->actual_length -= i;
+               dbg("Throtteling noticed");
+               memmove(buf->base, buf->base + i, buf->size - i);
+               buf->size -= i;
+               spin_unlock(&acm->throttle_lock);
+               spin_lock_irqsave(&acm->read_lock, flags);
+               list_add(&buf->list, &acm->filled_read_bufs);
+               spin_unlock_irqrestore(&acm->read_lock, flags);
                return;
        }
-
-       urb->actual_length = 0;
-       urb->dev = acm->dev;
-
-       i = usb_submit_urb(urb, GFP_ATOMIC);
-       if (i)
-               dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i);
+       spin_unlock(&acm->throttle_lock);
+
+       spin_lock_irqsave(&acm->read_lock, flags);
+       list_add(&buf->list, &acm->spare_read_bufs);
+       spin_unlock_irqrestore(&acm->read_lock, flags);
+       goto next_buffer;
+
+urbs:
+       while (!list_empty(&acm->spare_read_bufs)) {
+               spin_lock_irqsave(&acm->read_lock, flags);
+               if (list_empty(&acm->spare_read_urbs)) {
+                       spin_unlock_irqrestore(&acm->read_lock, flags);
+                       return;
+               }
+               rcv = list_entry(acm->spare_read_urbs.next,
+                                struct acm_ru, list);
+               list_del(&rcv->list);
+               spin_unlock_irqrestore(&acm->read_lock, flags);
+
+               buf = list_entry(acm->spare_read_bufs.next,
+                                struct acm_rb, list);
+               list_del(&buf->list);
+
+               rcv->buffer = buf;
+
+               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) {
+                       list_add(&buf->list, &acm->spare_read_bufs);
+                       spin_lock_irqsave(&acm->read_lock, flags);
+                       list_add(&rcv->list, &acm->spare_read_urbs);
+                       spin_unlock_irqrestore(&acm->read_lock, flags);
+                       return;
+               }
+       }
 }
 
 /* data interface wrote those outgoing bytes */
-static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
+static void acm_write_bulk(struct urb *urb)
 {
        struct acm *acm = (struct acm *)urb->context;
 
-       if (!ACM_READY(acm))
-               return;
-
-       if (urb->status)
-               dbg("nonzero write bulk status received: %d", urb->status);
+       dbg("Entering acm_write_bulk with status %d", urb->status);
 
-       schedule_work(&acm->work);
+       acm_write_done(acm);
+       acm_write_start(acm);
+       if (ACM_READY(acm))
+               schedule_work(&acm->work);
 }
 
-static void acm_softint(void *private)
+static void acm_softint(struct work_struct *work)
 {
-       struct acm *acm = private;
-       struct tty_struct *tty = acm->tty;
-
+       struct acm *acm = container_of(work, struct acm, work);
+       dbg("Entering acm_softint.");
+       
        if (!ACM_READY(acm))
                return;
-
-       if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
-               (tty->ldisc.write_wakeup)(tty);
-
-       wake_up_interruptible(&tty->write_wait);
+       tty_wakeup(acm->tty);
 }
 
 /*
@@ -338,94 +437,133 @@ static void acm_softint(void *private)
 
 static int acm_tty_open(struct tty_struct *tty, struct file *filp)
 {
-       struct acm *acm = acm_table[tty->index];
+       struct acm *acm;
+       int rv = -EINVAL;
+       int i;
+       dbg("Entering acm_tty_open.");
+
+       mutex_lock(&open_mutex);
 
+       acm = acm_table[tty->index];
        if (!acm || !acm->dev)
-               return -EINVAL;
+               goto err_out;
+       else
+               rv = 0;
 
        tty->driver_data = acm;
        acm->tty = tty;
 
-        lock_kernel();
+       /* 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 (acm->used++) {
-                unlock_kernel();
-                return 0;
+               goto done;
         }
 
-        unlock_kernel();
-
        acm->ctrlurb->dev = acm->dev;
-       if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL))
+       if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
                dbg("usb_submit_urb(ctrl irq) failed");
+               goto bail_out;
+       }
 
-       acm->readurb->dev = acm->dev;
-       if (usb_submit_urb(acm->readurb, GFP_KERNEL))
-               dbg("usb_submit_urb(read bulk) failed");
+       if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS))
+               goto full_bailout;
 
-       acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS);
+       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++) {
+               list_add(&(acm->ru[i].list), &acm->spare_read_urbs);
+       }
+       for (i = 0; i < acm->rx_buflimit; i++) {
+               list_add(&(acm->rb[i].list), &acm->spare_read_bufs);
+       }
 
-       /* 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;
+       tasklet_schedule(&acm->urb_task);
 
-       return 0;
+done:
+err_out:
+       mutex_unlock(&open_mutex);
+       return rv;
+
+full_bailout:
+       usb_kill_urb(acm->ctrlurb);
+bail_out:
+       acm->used--;
+       mutex_unlock(&open_mutex);
+       return -EIO;
+}
+
+static void acm_tty_unregister(struct acm *acm)
+{
+       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 < nr; i++)
+               usb_free_urb(acm->ru[i].urb);
+       kfree(acm);
 }
 
 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)
                return;
 
+       nr = acm->rx_buflimit;
+       mutex_lock(&open_mutex);
        if (!--acm->used) {
                if (acm->dev) {
                        acm_set_control(acm, acm->ctrlout = 0);
-                       usb_unlink_urb(acm->ctrlurb);
-                       usb_unlink_urb(acm->writeurb);
-                       usb_unlink_urb(acm->readurb);
-               } else {
-                       tty_unregister_device(acm_tty_driver, acm->minor);
-                       acm_table[acm->minor] = NULL;
-                       usb_free_urb(acm->ctrlurb);
-                       usb_free_urb(acm->readurb);
-                       usb_free_urb(acm->writeurb);
-                       kfree(acm);
-               }
+                       usb_kill_urb(acm->ctrlurb);
+                       usb_kill_urb(acm->writeurb);
+                       for (i = 0; i < nr; i++)
+                               usb_kill_urb(acm->ru[i].urb);
+               } else
+                       acm_tty_unregister(acm);
        }
+       mutex_unlock(&open_mutex);
 }
 
-static int acm_tty_write(struct tty_struct *tty, int from_user, 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;
+       unsigned long flags;
+       int wbn;
+       struct acm_wb *wb;
+
+       dbg("Entering acm_tty_write to write %d bytes,", count);
 
        if (!ACM_READY(acm))
                return -EINVAL;
-       if (acm->writeurb->status == -EINPROGRESS)
-               return 0;
        if (!count)
                return 0;
 
-       count = (count > acm->writesize) ? acm->writesize : count;
-
-       if (from_user) {
-               if (copy_from_user(acm->writeurb->transfer_buffer, (void __user *)buf, count))
-                       return -EFAULT;
-       } else
-               memcpy(acm->writeurb->transfer_buffer, buf, count);
+       spin_lock_irqsave(&acm->write_lock, flags);
+       if ((wbn = acm_wb_alloc(acm)) < 0) {
+               spin_unlock_irqrestore(&acm->write_lock, flags);
+               acm_write_start(acm);
+               return 0;
+       }
+       wb = &acm->wb[wbn];
 
-       acm->writeurb->transfer_buffer_length = count;
-       acm->writeurb->dev = acm->dev;
+       count = (count > acm->writesize) ? acm->writesize : count;
+       dbg("Get %d bytes...", count);
+       memcpy(wb->buf, buf, count);
+       wb->len = count;
+       spin_unlock_irqrestore(&acm->write_lock, flags);
 
-       /* GFP_KERNEL probably works if from_user */
-       stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC);
-       if (stat < 0) {
-               dbg("usb_submit_urb(write bulk) failed");
+       if ((stat = acm_write_start(acm)) < 0)
                return stat;
-       }
-
        return count;
 }
 
@@ -434,7 +572,11 @@ static int acm_tty_write_room(struct tty_struct *tty)
        struct acm *acm = tty->driver_data;
        if (!ACM_READY(acm))
                return -EINVAL;
-       return acm->writeurb->status == -EINPROGRESS ? 0 : acm->writesize;
+       /*
+        * 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;
 }
 
 static int acm_tty_chars_in_buffer(struct tty_struct *tty)
@@ -442,7 +584,10 @@ static int acm_tty_chars_in_buffer(struct tty_struct *tty)
        struct acm *acm = tty->driver_data;
        if (!ACM_READY(acm))
                return -EINVAL;
-       return acm->writeurb->status == -EINPROGRESS ? acm->writeurb->transfer_buffer_length : 0;
+       /*
+        * This is inaccurate (overcounts), but it works.
+        */
+       return (ACM_NW - acm_wb_is_avail(acm)) * acm->writesize;
 }
 
 static void acm_tty_throttle(struct tty_struct *tty)
@@ -450,7 +595,9 @@ static void acm_tty_throttle(struct tty_struct *tty)
        struct acm *acm = tty->driver_data;
        if (!ACM_READY(acm))
                return;
+       spin_lock_bh(&acm->throttle_lock);
        acm->throttle = 1;
+       spin_unlock_bh(&acm->throttle_lock);
 }
 
 static void acm_tty_unthrottle(struct tty_struct *tty)
@@ -458,9 +605,10 @@ static void acm_tty_unthrottle(struct tty_struct *tty)
        struct acm *acm = tty->driver_data;
        if (!ACM_READY(acm))
                return;
+       spin_lock_bh(&acm->throttle_lock);
        acm->throttle = 0;
-       if (acm->readurb->status != -EINPROGRESS)
-               acm_read_bulk(acm->readurb, NULL);
+       spin_unlock_bh(&acm->throttle_lock);
+       tasklet_schedule(&acm->urb_task);
 }
 
 static void acm_tty_break_ctl(struct tty_struct *tty, int state)
@@ -517,7 +665,7 @@ static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int
        return -ENOIOCTLCMD;
 }
 
-static __u32 acm_tty_speed[] = {
+static const __u32 acm_tty_speed[] = {
        0, 50, 75, 110, 134, 150, 200, 300, 600,
        1200, 1800, 2400, 4800, 9600, 19200, 38400,
        57600, 115200, 230400, 460800, 500000, 576000,
@@ -525,40 +673,42 @@ static __u32 acm_tty_speed[] = {
        2500000, 3000000, 3500000, 4000000
 };
 
-static __u8 acm_tty_size[] = {
+static const __u8 acm_tty_size[] = {
        5, 6, 7, 8
 };
 
-static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_old)
+static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old)
 {
        struct acm *acm = tty->driver_data;
-       struct termios *termios = tty->termios;
-       struct acm_line newline;
+       struct ktermios *termios = tty->termios;
+       struct usb_cdc_line_coding newline;
        int newctrl = acm->ctrlout;
 
        if (!ACM_READY(acm))
                return;
 
-       newline.speed = cpu_to_le32p(acm_tty_speed +
+       newline.dwDTERate = cpu_to_le32p(acm_tty_speed +
                (termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0));
-       newline.stopbits = termios->c_cflag & CSTOPB ? 2 : 0;
-       newline.parity = termios->c_cflag & PARENB ?
+       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;
-       newline.databits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
+       newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
 
        acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
 
-       if (!newline.speed) {
-               newline.speed = acm->line.speed;
+       if (!newline.dwDTERate) {
+               newline.dwDTERate = acm->line.dwDTERate;
                newctrl &= ~ACM_CTRL_DTR;
        } else  newctrl |=  ACM_CTRL_DTR;
 
        if (newctrl != acm->ctrlout)
                acm_set_control(acm, acm->ctrlout = newctrl);
 
-       if (memcmp(&acm->line, &newline, sizeof(struct acm_line))) {
-               memcpy(&acm->line, &newline, sizeof(struct acm_line));
-               dbg("set line: %d %d %d %d", newline.speed, newline.stopbits, newline.parity, newline.databits);
+       if (memcmp(&acm->line, &newline, sizeof newline)) {
+               memcpy(&acm->line, &newline, sizeof newline);
+               dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate),
+                       newline.bCharFormat, newline.bParityType,
+                       newline.bDataBits);
                acm_set_line(acm, &acm->line);
        }
 }
@@ -567,190 +717,356 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_
  * USB probe and disconnect routines.
  */
 
+/* Little helper: write buffers free */
+static void acm_write_buffers_free(struct acm *acm)
+{
+       int i;
+       struct acm_wb *wb;
+
+       for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) {
+               usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah);
+       }
+}
+
+/* Little helper: write buffers allocate */
+static int acm_write_buffers_alloc(struct acm *acm)
+{
+       int i;
+       struct acm_wb *wb;
+
+       for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) {
+               wb->buf = usb_buffer_alloc(acm->dev, acm->writesize, GFP_KERNEL,
+                   &wb->dmah);
+               if (!wb->buf) {
+                       while (i != 0) {
+                               --i;
+                               --wb;
+                               usb_buffer_free(acm->dev, acm->writesize,
+                                   wb->buf, wb->dmah);
+                       }
+                       return -ENOMEM;
+               }
+       }
+       return 0;
+}
+
 static int acm_probe (struct usb_interface *intf,
                      const struct usb_device_id *id)
 {
-       struct usb_device *dev;
+       struct usb_cdc_union_desc *union_header = NULL;
+       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_device *usb_dev = interface_to_usbdev(intf);
        struct acm *acm;
-       struct usb_host_config *cfacm;
-       struct usb_interface *data = NULL;
-       struct usb_host_interface *ifcom, *ifdata = NULL;
-       struct usb_endpoint_descriptor *epctrl = NULL;
-       struct usb_endpoint_descriptor *epread = NULL;
-       struct usb_endpoint_descriptor *epwrite = NULL;
-       int readsize, ctrlsize, minor, j;
-       unsigned char *buf;
-
-       dev = interface_to_usbdev (intf);
-
-                       cfacm = dev->actconfig;
+       int minor;
+       int ctrlsize,readsize;
+       u8 *buf;
+       u8 ac_management_function = 0;
+       u8 call_management_function = 0;
+       int call_interface_num = -1;
+       int data_interface_num;
+       unsigned long quirks;
+       int num_rx_buf;
+       int i;
+
+       /* normal quirks */
+       quirks = (unsigned long)id->driver_info;
+       num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;
+
+       /* handle quirks deadly to normal probing*/
+       if (quirks == NO_UNION_NORMAL) {
+               data_interface = usb_ifnum_to_if(usb_dev, 1);
+               control_interface = usb_ifnum_to_if(usb_dev, 0);
+               goto skip_normal_probe;
+       }
        
-                       /* We know we're probe()d with the control interface. */
-                       ifcom = intf->cur_altsetting;
-
-                       /* ACM doesn't guarantee the data interface is
-                        * adjacent to the control interface, or that if one
-                        * is there it's not for call management ... so find
-                        * it
-                        */
-                       for (j = 0; j < cfacm->desc.bNumInterfaces; j++) {
-                               ifdata = cfacm->interface[j]->cur_altsetting;
-                               data = cfacm->interface[j];
-
-                               if (ifdata->desc.bInterfaceClass == 10 &&
-                                   ifdata->desc.bNumEndpoints == 2) {
-                                       epctrl = &ifcom->endpoint[0].desc;
-                                       epread = &ifdata->endpoint[0].desc;
-                                       epwrite = &ifdata->endpoint[1].desc;
-
-                                       if ((epctrl->bEndpointAddress & 0x80) != 0x80 ||
-                                           (epctrl->bmAttributes & 3) != 3 ||
-                                           (epread->bmAttributes & 3) != 2 || 
-                                           (epwrite->bmAttributes & 3) != 2 ||
-                                           ((epread->bEndpointAddress & 0x80) ^ (epwrite->bEndpointAddress & 0x80)) != 0x80) 
-                                               goto next_interface;
-
-                                       if ((epread->bEndpointAddress & 0x80) != 0x80) {
-                                               epread = &ifdata->endpoint[1].desc;
-                                               epwrite = &ifdata->endpoint[0].desc;
-                                       }
-                                       dbg("found data interface at %d\n", j);
-                                       break;
-                               } else {
-next_interface:
-                                       ifdata = NULL;
-                                       data = NULL;
+       /* normal probing*/
+       if (!buffer) {
+               err("Wierd 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");
+                       buflen = intf->cur_altsetting->endpoint->extralen;
+                       buffer = intf->cur_altsetting->endpoint->extra;
+               } else {
+                       err("Zero length descriptor references\n");
+                       return -EINVAL;
+               }
+       }
+
+       while (buflen > 0) {
+               if (buffer [1] != USB_DT_CS_INTERFACE) {
+                       err("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: /* maybe somehow export */
+                               break; /* for now we ignore it */
+                       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;
                        }
+next_desc:
+               buflen -= buffer[0];
+               buffer += buffer[0];
+       }
 
-                       /* there's been a problem */
-                       if (!ifdata) {
-                               dbg("interface not found (%p)\n", ifdata);
-                               return -ENODEV;
+       if (!union_header) {
+               if (call_interface_num > 0) {
+                       dev_dbg(&intf->dev,"No union descriptor, using call management descriptor");
+                       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");
+                       return -ENODEV;
+               }
+       } 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");
+                       return -ENODEV;
+               }
+       }
+       
+       if (data_interface_num != call_interface_num)
+               dev_dbg(&intf->dev,"Seperate call control interface. That is not fully supported.");
 
-                       }
+skip_normal_probe:
 
-                       for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++);
-                       if (acm_table[minor]) {
-                               err("no more free acm devices");
-                               return -ENODEV;
-                       }
+       /*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) {
+                       struct usb_interface *t;
+                       dev_dbg(&intf->dev,"Your device has switched interfaces.");
 
-                       if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) {
-                               err("out of memory");
-                               return -ENOMEM;
-                       }
-                       memset(acm, 0, sizeof(struct acm));
-
-                       ctrlsize = epctrl->wMaxPacketSize;
-                       readsize = epread->wMaxPacketSize;
-                       acm->writesize = epwrite->wMaxPacketSize;
-                       acm->control = intf;
-                       acm->data = data;
-                       acm->minor = minor;
-                       acm->dev = dev;
-
-                       acm->bh.func = acm_rx_tasklet;
-                       acm->bh.data = (unsigned long) acm;
-                       INIT_WORK(&acm->work, acm_softint, acm);
-
-                       if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) {
-                               err("out of memory");
-                               kfree(acm);
-                               return -ENOMEM;
-                       }
+                       t = control_interface;
+                       control_interface = data_interface;
+                       data_interface = t;
+               } else {
+                       return -EINVAL;
+               }
+       }
+       
+       if (usb_interface_claimed(data_interface)) { /* valid in this context */
+               dev_dbg(&intf->dev,"The data interface isn't available");
+               return -EBUSY;
+       }
 
-                       acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
-                       if (!acm->ctrlurb) {
-                               err("out of memory");
-                               kfree(acm);
-                               kfree(buf);
-                               return -ENOMEM;
-                       }
-                       acm->readurb = usb_alloc_urb(0, GFP_KERNEL);
-                       if (!acm->readurb) {
-                               err("out of memory");
-                               usb_free_urb(acm->ctrlurb);
-                               kfree(acm);
-                               kfree(buf);
-                               return -ENOMEM;
-                       }
-                       acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
-                       if (!acm->writeurb) {
-                               err("out of memory");
-                               usb_free_urb(acm->readurb);
-                               usb_free_urb(acm->ctrlurb);
-                               kfree(acm);
-                               kfree(buf);
-                               return -ENOMEM;
-                       }
 
-                       usb_fill_int_urb(acm->ctrlurb, dev, usb_rcvintpipe(dev, epctrl->bEndpointAddress),
-                               buf, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
+       if (data_interface->cur_altsetting->desc.bNumEndpoints < 2)
+               return -EINVAL;
+
+       epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
+       epread = &data_interface->cur_altsetting->endpoint[0].desc;
+       epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
 
-                       usb_fill_bulk_urb(acm->readurb, dev, usb_rcvbulkpipe(dev, epread->bEndpointAddress),
-                               buf += ctrlsize, readsize, acm_read_bulk, acm);
-                       acm->readurb->transfer_flags |= URB_NO_FSBR;
 
-                       usb_fill_bulk_urb(acm->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress),
-                               buf += readsize, acm->writesize, acm_write_bulk, acm);
-                       acm->writeurb->transfer_flags |= URB_NO_FSBR;
+       /* workaround for switched endpoints */
+       if (!usb_endpoint_dir_in(epread)) {
+               /* descriptors are swapped */
+               struct usb_endpoint_descriptor *t;
+               dev_dbg(&intf->dev,"The data interface has switched endpoints");
+               
+               t = epread;
+               epread = epwrite;
+               epwrite = t;
+       }
+       dbg("interfaces are valid");
+       for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++);
 
-                       dev_info(&intf->dev, "ttyACM%d: USB ACM device", minor);
+       if (minor == ACM_TTY_MINORS) {
+               err("no more free acm devices");
+               return -ENODEV;
+       }
 
-                       acm_set_control(acm, acm->ctrlout);
+       if (!(acm = kzalloc(sizeof(struct acm), GFP_KERNEL))) {
+               dev_dbg(&intf->dev, "out of memory (acm kzalloc)");
+               goto alloc_fail;
+       }
 
-                       acm->line.speed = cpu_to_le32(9600);
-                       acm->line.databits = 8;
-                       acm_set_line(acm, &acm->line);
+       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);
+       acm->control = control_interface;
+       acm->data = data_interface;
+       acm->minor = minor;
+       acm->dev = usb_dev;
+       acm->ctrl_caps = ac_management_function;
+       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);
+       spin_lock_init(&acm->throttle_lock);
+       spin_lock_init(&acm->write_lock);
+       spin_lock_init(&acm->read_lock);
+       acm->write_ready = 1;
+       acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
+
+       buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
+       if (!buf) {
+               dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)");
+               goto alloc_fail2;
+       }
+       acm->ctrl_buffer = buf;
 
-                       if ( (j = usb_driver_claim_interface(&acm_driver, data, acm)) != 0) {
-                               err("claim failed");
-                               usb_free_urb(acm->ctrlurb);
-                               usb_free_urb(acm->readurb);
-                               usb_free_urb(acm->writeurb);
-                               kfree(acm);
-                               kfree(buf);
-                               return j;
-                       } 
+       if (acm_write_buffers_alloc(acm) < 0) {
+               dev_dbg(&intf->dev, "out of memory (write buffer alloc)");
+               goto alloc_fail4;
+       }
 
-                       tty_register_device(acm_tty_driver, minor, &intf->dev);
+       acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!acm->ctrlurb) {
+               dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)");
+               goto alloc_fail5;
+       }
+       for (i = 0; i < num_rx_buf; i++) {
+               struct acm_ru *rcv = &(acm->ru[i]);
 
-                       acm_table[minor] = acm;
-                       usb_set_intfdata (intf, acm);
-                       return 0;
+               if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) {
+                       dev_dbg(&intf->dev, "out of memory (read urbs usb_alloc_urb)");
+                       goto alloc_fail7;
+               }
+
+               rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+               rcv->instance = acm;
+       }
+       for (i = 0; i < num_rx_buf; i++) {
+               struct acm_rb *buf = &(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)");
+                       goto alloc_fail7;
+               }
+       }
+       acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!acm->writeurb) {
+               dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)");
+               goto alloc_fail7;
+       }
+
+       usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
+                        acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
+       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);
+
+       acm->line.dwDTERate = cpu_to_le32(9600);
+       acm->line.bDataBits = 8;
+       acm_set_line(acm, &acm->line);
+
+       usb_driver_claim_interface(&acm_driver, data_interface, acm);
+
+       usb_get_intf(control_interface);
+       tty_register_device(acm_tty_driver, minor, &control_interface->dev);
+
+       acm_table[minor] = acm;
+       usb_set_intfdata (intf, acm);
+       return 0;
+
+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);
+       for (i = 0; i < num_rx_buf; i++)
+               usb_free_urb(acm->ru[i].urb);
+       usb_free_urb(acm->ctrlurb);
+alloc_fail5:
+       acm_write_buffers_free(acm);
+alloc_fail4:
+       usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
+alloc_fail2:
+       kfree(acm);
+alloc_fail:
+       return -ENOMEM;
 }
 
 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;
 
        if (!acm || !acm->dev) {
                dbg("disconnect on nonexisting interface");
                return;
        }
 
+       mutex_lock(&open_mutex);
+       if (!usb_get_intfdata(intf)) {
+               mutex_unlock(&open_mutex);
+               return;
+       }
        acm->dev = NULL;
-       usb_set_intfdata (intf, NULL);
+       usb_set_intfdata(acm->control, NULL);
+       usb_set_intfdata(acm->data, NULL);
 
-       usb_unlink_urb(acm->ctrlurb);
-       usb_unlink_urb(acm->readurb);
-       usb_unlink_urb(acm->writeurb);
+       tasklet_disable(&acm->urb_task);
 
-       kfree(acm->ctrlurb->transfer_buffer);
+       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);
 
-       usb_driver_release_interface(&acm_driver, acm->data);
+       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 */
+
+       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_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf);
 
        if (!acm->used) {
-               tty_unregister_device(acm_tty_driver, acm->minor);
-               acm_table[acm->minor] = NULL;
-               usb_free_urb(acm->ctrlurb);
-               usb_free_urb(acm->readurb);
-               usb_free_urb(acm->writeurb);
-               kfree(acm);
+               acm_tty_unregister(acm);
+               mutex_unlock(&open_mutex);
                return;
        }
 
+       mutex_unlock(&open_mutex);
+
        if (acm->tty)
                tty_hangup(acm->tty);
 }
@@ -760,22 +1076,43 @@ static void acm_disconnect(struct usb_interface *intf)
  */
 
 static struct usb_device_id acm_ids[] = {
+       /* quirky and broken devices */
+       { USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */
+       .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, 0x1608), /* ZyDAS 56K USB MODEM */
+       .driver_info = SINGLE_RX_URB, /* firmware bug */
+       },
+       { USB_DEVICE(0x0ace, 0x1611), /* ZyDAS 56K USB MODEM - new version */
+       .driver_info = SINGLE_RX_URB, /* firmware bug */
+       },
        /* control interfaces with various AT-command sets */
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 1) },
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 2) },
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 3) },
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 4) },
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 5) },
-       { USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 6) },
-
-       /* NOTE:  COMM/2/0xff is likely MSFT RNDIS ... NOT a modem!! */
+       { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+               USB_CDC_ACM_PROTO_AT_V25TER) },
+       { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+               USB_CDC_ACM_PROTO_AT_PCCA101) },
+       { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+               USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) },
+       { 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_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+               USB_CDC_ACM_PROTO_AT_CDMA) },
+
+       /* NOTE:  COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */
        { }
 };
 
 MODULE_DEVICE_TABLE (usb, acm_ids);
 
 static struct usb_driver acm_driver = {
-       .owner =        THIS_MODULE,
        .name =         "cdc_acm",
        .probe =        acm_probe,
        .disconnect =   acm_disconnect,
@@ -786,7 +1123,7 @@ static struct usb_driver acm_driver = {
  * TTY driver structures.
  */
 
-static struct tty_operations acm_ops = {
+static const struct tty_operations acm_ops = {
        .open =                 acm_tty_open,
        .close =                acm_tty_close,
        .write =                acm_tty_write,
@@ -814,12 +1151,11 @@ static int __init acm_init(void)
        acm_tty_driver->owner = THIS_MODULE,
        acm_tty_driver->driver_name = "acm",
        acm_tty_driver->name = "ttyACM",
-       acm_tty_driver->devfs_name = "usb/acm/",
        acm_tty_driver->major = ACM_TTY_MAJOR,
        acm_tty_driver->minor_start = 0,
        acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
        acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
-       acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
+       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;
        tty_set_operations(acm_tty_driver, &acm_ops);