* Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2004 Oliver Neukum <oliver@neukum.name>
* Copyright (c) 2005 David Kubicek <dave@awk.cz>
+ * Copyright (c) 2011 Johan Hovold <jhovold@gmail.com>
*
* USB Abstract Control Model driver for USB modems and ISDN adapters
*
* Sponsored by SuSE
*
- * ChangeLog:
- * 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.13 - added termios, added hangup
- * v0.14 - sized down struct acm
- * v0.15 - fixed flow control again - characters could be lost
- * v0.16 - added code for modems with swapped data and control interfaces
- * v0.17 - added new style probing
- * v0.18 - fixed new style probing for devices with more configurations
- * v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan)
- * v0.20 - switched to probing on interface (rather than device) class
- * v0.21 - revert to probing on device for devices with multiple configs
- * 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
- * v0.26 - multiple write urbs, writesize increased
- */
-
-/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
#include "cdc-acm.h"
-#define ACM_CLOSE_TIMEOUT 15 /* seconds to let writes drain */
-
-/*
- * Version Information
- */
-#define DRIVER_VERSION "v0.26"
-#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek"
+#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek, Johan Hovold"
#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
static struct usb_driver acm_driver;
static struct tty_driver *acm_tty_driver;
static struct acm *acm_table[ACM_TTY_MINORS];
-static DEFINE_MUTEX(open_mutex);
+static DEFINE_MUTEX(acm_table_lock);
-#define ACM_READY(acm) (acm && acm->dev && acm->port.count)
+/*
+ * acm_table accessors
+ */
-static const struct tty_port_operations acm_port_ops = {
-};
+/*
+ * Look up an ACM structure by index. If found and not disconnected, increment
+ * its refcount and return it with its mutex held.
+ */
+static struct acm *acm_get_by_index(unsigned index)
+{
+ struct acm *acm;
-#ifdef VERBOSE_DEBUG
-#define verbose 1
-#else
-#define verbose 0
-#endif
+ mutex_lock(&acm_table_lock);
+ acm = acm_table[index];
+ if (acm) {
+ mutex_lock(&acm->mutex);
+ if (acm->disconnected) {
+ mutex_unlock(&acm->mutex);
+ acm = NULL;
+ } else {
+ tty_port_get(&acm->port);
+ mutex_unlock(&acm->mutex);
+ }
+ }
+ mutex_unlock(&acm_table_lock);
+ return acm;
+}
+
+/*
+ * Try to find an available minor number and if found, associate it with 'acm'.
+ */
+static int acm_alloc_minor(struct acm *acm)
+{
+ int minor;
+
+ mutex_lock(&acm_table_lock);
+ for (minor = 0; minor < ACM_TTY_MINORS; minor++) {
+ if (!acm_table[minor]) {
+ acm_table[minor] = acm;
+ break;
+ }
+ }
+ mutex_unlock(&acm_table_lock);
+
+ return minor;
+}
+
+/* Release the minor number associated with 'acm'. */
+static void acm_release_minor(struct acm *acm)
+{
+ mutex_lock(&acm_table_lock);
+ acm_table[acm->minor] = NULL;
+ mutex_unlock(&acm_table_lock);
+}
/*
* Functions for ACM control messages.
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);
+ dev_dbg(&acm->control->dev,
+ "%s - rq 0x%02x, val %#x, len %#x, result %d\n",
+ __func__, request, value, len, retval);
return retval < 0 ? retval : 0;
}
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);
+ dev_err(&acm->data->dev,
+ "%s - usb_submit_urb(write bulk) failed: %d\n",
+ __func__, rc);
acm_write_done(acm, wb);
}
return rc;
unsigned long flags;
struct acm_wb *wb = &acm->wb[wbn];
int rc;
+#ifdef CONFIG_PM
+ struct urb *res;
+#endif
spin_lock_irqsave(&acm->write_lock, flags);
if (!acm->dev) {
return -ENODEV;
}
- dbg("%s susp_count: %d", __func__, acm->susp_count);
+ dev_vdbg(&acm->data->dev, "%s - susp_count %d\n", __func__,
+ acm->susp_count);
usb_autopm_get_interface_async(acm->control);
if (acm->susp_count) {
+#ifdef CONFIG_PM
+ 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;
+ usb_anchor_urb(wb->urb, &acm->deferred);
+#else
if (!acm->delayed_wb)
acm->delayed_wb = wb;
else
usb_autopm_put_interface_async(acm->control);
+#endif
spin_unlock_irqrestore(&acm->write_lock, flags);
return 0; /* A white lie */
}
usb_mark_last_busy(acm->dev);
-
+#ifdef CONFIG_PM
+ while ((res = usb_get_from_anchor(&acm->deferred))) {
+ /* decrement ref count*/
+ usb_put_urb(res);
+ rc = usb_submit_urb(res, GFP_ATOMIC);
+ if (rc < 0) {
+ dbg("usb_submit_urb(pending request) failed: %d", rc);
+ usb_unanchor_urb(res);
+ acm_write_done(acm, res->context);
+ }
+ }
+#endif
rc = acm_start_wb(acm, wb);
spin_unlock_irqrestore(&acm->write_lock, flags);
{
struct acm *acm = urb->context;
struct usb_cdc_notification *dr = urb->transfer_buffer;
- struct tty_struct *tty;
unsigned char *data;
int newctrl;
int retval;
case -ENOENT:
case -ESHUTDOWN:
/* this urb is terminated, clean up */
- dbg("%s - urb shutting down with status: %d", __func__, status);
+ dev_dbg(&acm->control->dev,
+ "%s - urb shutting down with status: %d\n",
+ __func__, status);
return;
default:
- dbg("%s - nonzero urb status received: %d", __func__, status);
+ dev_dbg(&acm->control->dev,
+ "%s - nonzero urb status received: %d\n",
+ __func__, status);
goto exit;
}
- if (!ACM_READY(acm))
- goto exit;
+ usb_mark_last_busy(acm->dev);
data = (unsigned char *)(dr + 1);
switch (dr->bNotificationType) {
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
- dbg("%s network", dr->wValue ?
- "connected to" : "disconnected from");
+ dev_dbg(&acm->control->dev, "%s - network connection: %d\n",
+ __func__, dr->wValue);
break;
case USB_CDC_NOTIFY_SERIAL_STATE:
- tty = tty_port_tty_get(&acm->port);
newctrl = get_unaligned_le16(data);
- if (tty) {
- if (!acm->clocal &&
- (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
- dbg("calling hangup");
- tty_hangup(tty);
- }
- tty_kref_put(tty);
+ if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
+ dev_dbg(&acm->control->dev, "%s - calling hangup\n",
+ __func__);
+ tty_port_tty_hangup(&acm->port, false);
}
acm->ctrlin = newctrl;
- dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c",
+ dev_dbg(&acm->control->dev,
+ "%s - input control lines: dcd%c dsr%c break%c "
+ "ring%c framing%c parity%c overrun%c\n",
+ __func__,
acm->ctrlin & ACM_CTRL_DCD ? '+' : '-',
acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
acm->ctrlin & ACM_CTRL_BRK ? '+' : '-',
break;
default:
- dbg("unknown notification %d received: index %d len %d data0 %d data1 %d",
+ dev_dbg(&acm->control->dev,
+ "%s - unknown notification %d received: index %d "
+ "len %d data0 %d data1 %d\n",
+ __func__,
dr->bNotificationType, dr->wIndex,
dr->wLength, data[0], data[1]);
break;
}
exit:
- usb_mark_last_busy(acm->dev);
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
- dev_err(&urb->dev->dev, "%s - usb_submit_urb failed with "
- "result %d", __func__, retval);
+ dev_err(&acm->control->dev, "%s - usb_submit_urb failed: %d\n",
+ __func__, retval);
}
-/* data interface returns incoming bytes, or we got unthrottled */
-static void acm_read_bulk(struct urb *urb)
+static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags)
{
- struct acm_rb *buf;
- struct acm_ru *rcv = urb->context;
- struct acm *acm = rcv->instance;
- int status = urb->status;
+ int res;
- dbg("Entering acm_read_bulk with status %d", status);
+ if (!test_and_clear_bit(index, &acm->read_urbs_free))
+ return 0;
- if (!ACM_READY(acm)) {
- dev_dbg(&acm->data->dev, "Aborting, acm not ready");
- return;
+ dev_vdbg(&acm->data->dev, "%s - urb %d\n", __func__, index);
+
+ res = usb_submit_urb(acm->read_urbs[index], mem_flags);
+ if (res) {
+ if (res != -EPERM) {
+ dev_err(&acm->data->dev,
+ "%s - usb_submit_urb failed: %d\n",
+ __func__, res);
+ }
+ set_bit(index, &acm->read_urbs_free);
+ return res;
}
- usb_mark_last_busy(acm->dev);
- if (status)
- dev_dbg(&acm->data->dev, "bulk rx status %d\n", status);
+ return 0;
+}
- buf = rcv->buffer;
- buf->size = urb->actual_length;
+static int acm_submit_read_urbs(struct acm *acm, gfp_t mem_flags)
+{
+ int res;
+ int i;
- 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);
- } 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 */
+ for (i = 0; i < acm->rx_buflimit; ++i) {
+ res = acm_submit_read_urb(acm, i, mem_flags);
+ if (res)
+ return res;
}
- if (likely(!acm->susp_count))
- tasklet_schedule(&acm->urb_task);
+
+ return 0;
}
-static void acm_rx_tasklet(unsigned long _acm)
+static void acm_process_read_urb(struct acm *acm, struct urb *urb)
{
- struct acm *acm = (void *)_acm;
- struct acm_rb *buf;
- struct tty_struct *tty;
- struct acm_ru *rcv;
+ if (!urb->actual_length)
+ return;
+
+ tty_insert_flip_string(&acm->port, urb->transfer_buffer,
+ urb->actual_length);
+ tty_flip_buffer_push(&acm->port);
+}
+
+static void acm_read_bulk_callback(struct urb *urb)
+{
+ struct acm_rb *rb = urb->context;
+ struct acm *acm = rb->instance;
unsigned long flags;
- unsigned char throttled;
- dbg("Entering acm_rx_tasklet");
+ dev_vdbg(&acm->data->dev, "%s - urb %d, len %d\n", __func__,
+ rb->index, urb->actual_length);
+ set_bit(rb->index, &acm->read_urbs_free);
- if (!ACM_READY(acm)) {
- dbg("acm_rx_tasklet: ACM not ready");
+ if (!acm->dev) {
+ dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__);
return;
}
+ usb_mark_last_busy(acm->dev);
- spin_lock_irqsave(&acm->throttle_lock, flags);
- throttled = acm->throttle;
- spin_unlock_irqrestore(&acm->throttle_lock, flags);
- if (throttled) {
- dbg("acm_rx_tasklet: throttled");
+ if (urb->status) {
+ dev_dbg(&acm->data->dev, "%s - non-zero urb status: %d\n",
+ __func__, urb->status);
return;
}
+ acm_process_read_urb(acm, urb);
- tty = tty_port_tty_get(&acm->port);
-
-next_buffer:
+ /* throttle device if requested by tty */
spin_lock_irqsave(&acm->read_lock, flags);
- if (list_empty(&acm->filled_read_bufs)) {
+ acm->throttled = acm->throttle_req;
+ if (!acm->throttled && !acm->susp_count) {
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);
-
- if (tty) {
- 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);
- } 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);
- list_add(&buf->list, &acm->spare_read_bufs);
- spin_unlock_irqrestore(&acm->read_lock, flags);
- 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;
- }
- rcv = list_entry(acm->spare_read_urbs.next,
- struct acm_ru, list);
- list_del(&rcv->list);
+ acm_submit_read_urb(acm, rb->index, GFP_ATOMIC);
+ } else {
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;
-
- 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;
-
- /* 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);
- 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 */
struct acm *acm = wb->instance;
unsigned long flags;
- if (verbose || urb->status
- || (urb->actual_length != urb->transfer_buffer_length))
- dev_dbg(&acm->data->dev, "tx %d/%d bytes -- > %d\n",
+ if (urb->status || (urb->actual_length != urb->transfer_buffer_length))
+ dev_vdbg(&acm->data->dev, "%s - len %d/%d, status %d\n",
+ __func__,
urb->actual_length,
urb->transfer_buffer_length,
urb->status);
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);
+ schedule_work(&acm->work);
}
static void acm_softint(struct work_struct *work)
{
struct acm *acm = container_of(work, struct acm, work);
- struct tty_struct *tty;
- dev_vdbg(&acm->data->dev, "tx work\n");
- if (!ACM_READY(acm))
- return;
- tty = tty_port_tty_get(&acm->port);
- tty_wakeup(tty);
- tty_kref_put(tty);
+ dev_vdbg(&acm->data->dev, "%s\n", __func__);
+
+ tty_port_tty_wakeup(&acm->port);
}
/*
* TTY handlers
*/
-static int acm_tty_open(struct tty_struct *tty, struct file *filp)
+static int acm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct acm *acm;
- int rv = -ENODEV;
- int i;
- dbg("Entering acm_tty_open.");
+ int retval;
- mutex_lock(&open_mutex);
+ dev_dbg(tty->dev, "%s\n", __func__);
- acm = acm_table[tty->index];
- if (!acm || !acm->dev)
- goto out;
- else
- rv = 0;
+ acm = acm_get_by_index(tty->index);
+ if (!acm)
+ return -ENODEV;
- set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
+ retval = tty_standard_install(driver, tty);
+ if (retval)
+ goto error_init_termios;
tty->driver_data = acm;
- tty_port_tty_set(&acm->port, tty);
- if (usb_autopm_get_interface(acm->control) < 0)
- goto early_bail;
- else
- acm->control->needs_remote_wakeup = 1;
+ return 0;
+
+error_init_termios:
+ tty_port_put(&acm->port);
+ return retval;
+}
+
+static int acm_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct acm *acm = tty->driver_data;
+
+ dev_dbg(tty->dev, "%s\n", __func__);
+
+ return tty_port_open(&acm->port, tty, filp);
+}
+
+static int acm_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+ struct acm *acm = container_of(port, struct acm, port);
+ int retval = -ENODEV;
+
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
mutex_lock(&acm->mutex);
- if (acm->port.count++) {
- mutex_unlock(&acm->mutex);
- usb_autopm_put_interface(acm->control);
- goto out;
- }
+ if (acm->disconnected)
+ goto disconnected;
+
+ retval = usb_autopm_get_interface(acm->control);
+ if (retval)
+ goto error_get_interface;
+
+ /*
+ * FIXME: Why do we need this? Allocating 64K of physically contiguous
+ * memory is really nasty...
+ */
+ set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
+ acm->control->needs_remote_wakeup = 0;
+
+ if (acm_submit_read_urbs(acm, GFP_KERNEL))
+ goto error_submit_urb;
acm->ctrlurb->dev = acm->dev;
if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
- dbg("usb_submit_urb(ctrl irq) failed");
- goto bail_out;
+ dev_err(&acm->control->dev,
+ "%s - usb_submit_urb(ctrl irq) failed\n", __func__);
+ goto error_submit_urb;
}
- if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) &&
+ acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS;
+ if (acm_set_control(acm, acm->ctrlout) < 0 &&
(acm->ctrl_caps & USB_CDC_CAP_LINE))
- goto full_bailout;
+ goto error_set_control;
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++)
- 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);
-
- acm->throttle = 0;
+ /*
+ * Unthrottle device in case the TTY was closed while throttled.
+ */
+ spin_lock_irq(&acm->read_lock);
+ acm->throttled = 0;
+ acm->throttle_req = 0;
+ spin_unlock_irq(&acm->read_lock);
- set_bit(ASYNCB_INITIALIZED, &acm->port.flags);
- rv = tty_port_block_til_ready(&acm->port, tty, filp);
- tasklet_schedule(&acm->urb_task);
+ if (acm_submit_read_urbs(acm, GFP_KERNEL))
+ goto error_submit_read_urbs;
mutex_unlock(&acm->mutex);
-out:
- mutex_unlock(&open_mutex);
- return rv;
-full_bailout:
+ return 0;
+
+error_submit_read_urbs:
+ acm->ctrlout = 0;
+ acm_set_control(acm, acm->ctrlout);
+error_set_control:
usb_kill_urb(acm->ctrlurb);
-bail_out:
- acm->port.count--;
- mutex_unlock(&acm->mutex);
+error_submit_urb:
usb_autopm_put_interface(acm->control);
-early_bail:
- mutex_unlock(&open_mutex);
- tty_port_tty_set(&acm->port, NULL);
- return -EIO;
+error_get_interface:
+disconnected:
+ mutex_unlock(&acm->mutex);
+ return retval;
}
-static void acm_tty_unregister(struct acm *acm)
+static void acm_port_destruct(struct tty_port *port)
{
- int i, nr;
+ struct acm *acm = container_of(port, struct acm, port);
- nr = acm->rx_buflimit;
- tty_unregister_device(acm_tty_driver, acm->minor);
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
+
+ acm_release_minor(acm);
usb_put_intf(acm->control);
- acm_table[acm->minor] = NULL;
- usb_free_urb(acm->ctrlurb);
- 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)
+static void acm_port_shutdown(struct tty_port *port)
{
- int i, nr = acm->rx_buflimit;
- mutex_lock(&open_mutex);
- if (acm->dev) {
+ struct acm *acm = container_of(port, struct acm, port);
+ int i;
+
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
+
+ mutex_lock(&acm->mutex);
+ if (!acm->disconnected) {
usb_autopm_get_interface(acm->control);
acm_set_control(acm, acm->ctrlout = 0);
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);
+ for (i = 0; i < acm->rx_buflimit; i++)
+ usb_kill_urb(acm->read_urbs[i]);
acm->control->needs_remote_wakeup = 0;
usb_autopm_put_interface(acm->control);
}
- mutex_unlock(&open_mutex);
+ mutex_unlock(&acm->mutex);
+}
+
+static void acm_tty_cleanup(struct tty_struct *tty)
+{
+ struct acm *acm = tty->driver_data;
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
+ tty_port_put(&acm->port);
}
static void acm_tty_hangup(struct tty_struct *tty)
{
struct acm *acm = tty->driver_data;
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
tty_port_hangup(&acm->port);
- acm_port_down(acm);
}
static void acm_tty_close(struct tty_struct *tty, struct file *filp)
{
struct acm *acm = tty->driver_data;
-
- /* Perform the closing process and see if we need to do the hardware
- shutdown */
- if (!acm)
- return;
- 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;
- }
- acm_port_down(acm);
- tty_port_close_end(&acm->port, tty);
- tty_port_tty_set(&acm->port, NULL);
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
+ tty_port_close(&acm->port, tty, filp);
}
static int acm_tty_write(struct tty_struct *tty,
int wbn;
struct acm_wb *wb;
- dbg("Entering acm_tty_write to write %d bytes,", count);
-
- if (!ACM_READY(acm))
- return -EINVAL;
if (!count)
return 0;
+ dev_vdbg(&acm->data->dev, "%s - count %d\n", __func__, count);
+
spin_lock_irqsave(&acm->write_lock, flags);
wbn = acm_wb_alloc(acm);
if (wbn < 0) {
wb = &acm->wb[wbn];
count = (count > acm->writesize) ? acm->writesize : count;
- dbg("Get %d bytes...", count);
+ dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count);
memcpy(wb->buf, buf, count);
wb->len = count;
spin_unlock_irqrestore(&acm->write_lock, flags);
static int acm_tty_write_room(struct tty_struct *tty)
{
struct acm *acm = tty->driver_data;
- if (!ACM_READY(acm))
- return -EINVAL;
/*
* Do not let the line discipline to know that we have a reserve,
* or it might get too enthusiastic.
static int acm_tty_chars_in_buffer(struct tty_struct *tty)
{
struct acm *acm = tty->driver_data;
- if (!ACM_READY(acm))
+ /*
+ * if the device was unplugged then any remaining characters fell out
+ * of the connector ;)
+ */
+ if (acm->disconnected)
return 0;
/*
* This is inaccurate (overcounts), but it works.
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);
+
+ spin_lock_irq(&acm->read_lock);
+ acm->throttle_req = 1;
+ spin_unlock_irq(&acm->read_lock);
}
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;
- spin_unlock_bh(&acm->throttle_lock);
- tasklet_schedule(&acm->urb_task);
+ unsigned int was_throttled;
+
+ spin_lock_irq(&acm->read_lock);
+ was_throttled = acm->throttled;
+ acm->throttled = 0;
+ acm->throttle_req = 0;
+ spin_unlock_irq(&acm->read_lock);
+
+ if (was_throttled)
+ acm_submit_read_urbs(acm, GFP_KERNEL);
}
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 -EINVAL;
+
retval = acm_send_break(acm, state ? 0xffff : 0);
if (retval < 0)
- dbg("send break failed");
+ dev_dbg(&acm->control->dev, "%s - send break failed\n",
+ __func__);
return retval;
}
{
struct acm *acm = tty->driver_data;
- if (!ACM_READY(acm))
- return -EINVAL;
-
return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) |
(acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) |
(acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) |
struct acm *acm = tty->driver_data;
unsigned int newctrl;
- if (!ACM_READY(acm))
- return -EINVAL;
-
newctrl = acm->ctrlout;
set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) |
(set & TIOCM_RTS ? ACM_CTRL_RTS : 0);
return acm_set_control(acm, acm->ctrlout = newctrl);
}
-static int acm_tty_ioctl(struct tty_struct *tty,
- unsigned int cmd, unsigned long arg)
+static int get_serial_info(struct acm *acm, struct serial_struct __user *info)
{
- struct acm *acm = tty->driver_data;
+ struct serial_struct tmp;
- if (!ACM_READY(acm))
+ if (!info)
return -EINVAL;
- return -ENOIOCTLCMD;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.flags = ASYNC_LOW_LATENCY;
+ tmp.xmit_fifo_size = acm->writesize;
+ tmp.baud_base = le32_to_cpu(acm->line.dwDTERate);
+ tmp.close_delay = acm->port.close_delay / 10;
+ tmp.closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+ ASYNC_CLOSING_WAIT_NONE :
+ acm->port.closing_wait / 10;
+
+ if (copy_to_user(info, &tmp, sizeof(tmp)))
+ return -EFAULT;
+ else
+ return 0;
}
-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,
- 921600, 1000000, 1152000, 1500000, 2000000,
- 2500000, 3000000, 3500000, 4000000
-};
+static int set_serial_info(struct acm *acm,
+ struct serial_struct __user *newinfo)
+{
+ struct serial_struct new_serial;
+ unsigned int closing_wait, close_delay;
+ int retval = 0;
-static const __u8 acm_tty_size[] = {
- 5, 6, 7, 8
-};
+ if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
+ return -EFAULT;
+
+ close_delay = new_serial.close_delay * 10;
+ closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+ ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
+
+ mutex_lock(&acm->port.mutex);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((close_delay != acm->port.close_delay) ||
+ (closing_wait != acm->port.closing_wait))
+ retval = -EPERM;
+ else
+ retval = -EOPNOTSUPP;
+ } else {
+ acm->port.close_delay = close_delay;
+ acm->port.closing_wait = closing_wait;
+ }
+
+ mutex_unlock(&acm->port.mutex);
+ return retval;
+}
+
+static int acm_tty_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct acm *acm = tty->driver_data;
+ int rv = -ENOIOCTLCMD;
+
+ switch (cmd) {
+ case TIOCGSERIAL: /* gets serial port data */
+ rv = get_serial_info(acm, (struct serial_struct __user *) arg);
+ break;
+ case TIOCSSERIAL:
+ rv = set_serial_info(acm, (struct serial_struct __user *) arg);
+ break;
+ }
+
+ return rv;
+}
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;
+ struct ktermios *termios = &tty->termios;
struct usb_cdc_line_coding newline;
int newctrl = acm->ctrlout;
- if (!ACM_READY(acm))
- return;
-
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;
- newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ newline.bDataBits = 5;
+ break;
+ case CS6:
+ newline.bDataBits = 6;
+ break;
+ case CS7:
+ newline.bDataBits = 7;
+ break;
+ case CS8:
+ default:
+ newline.bDataBits = 8;
+ break;
+ }
/* FIXME: Needs to clear unsupported bits in the termios */
acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
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),
+ dev_dbg(&acm->control->dev, "%s - set line: %d %d %d %d\n",
+ __func__,
+ le32_to_cpu(newline.dwDTERate),
newline.bCharFormat, newline.bParityType,
newline.bDataBits);
acm_set_line(acm, &acm->line);
}
}
+static const struct tty_port_operations acm_port_ops = {
+ .shutdown = acm_port_shutdown,
+ .activate = acm_port_activate,
+ .destruct = acm_port_destruct,
+};
+
/*
* USB probe and disconnect routines.
*/
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;
+ int i;
- for (i = 0; i < n; i++)
+ for (i = 0; i < acm->rx_buflimit; i++)
usb_free_coherent(usb_dev, acm->readsize,
- acm->rb[i].base, acm->rb[i].dma);
+ acm->read_buffers[i].base, acm->read_buffers[i].dma);
}
/* Little helper: write buffers allocate */
u8 ac_management_function = 0;
u8 call_management_function = 0;
int call_interface_num = -1;
- int data_interface_num;
+ int data_interface_num = -1;
unsigned long quirks;
int num_rx_buf;
int i;
int combined_interfaces = 0;
+ struct device *tty_dev;
+ int rv = -ENOMEM;
/* normal quirks */
quirks = (unsigned long)id->driver_info;
+
+ if (quirks & IGNORE_DEVICE)
+ return -ENODEV;
+
num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;
/* handle quirks deadly to normal probing*/
- if (quirks == NO_UNION_NORMAL) {
+ 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;
case USB_CDC_CALL_MANAGEMENT_TYPE:
call_management_function = buffer[3];
call_interface_num = buffer[4];
- if ( (quirks & NOT_A_MODEM) == 0 && (call_management_function & 3) != 3)
+ if ((quirks & NOT_A_MODEM) == 0 && (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:
if (!union_header) {
if (call_interface_num > 0) {
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));
+ /* quirks for Droids MuIn LCD */
+ if (quirks & NO_DATA_INTERFACE)
+ data_interface = usb_ifnum_to_if(usb_dev, 0);
+ else
+ data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num));
control_interface = intf;
} else {
if (intf->cur_altsetting->desc.bNumEndpoints != 3) {
}
- if (data_interface->cur_altsetting->desc.bNumEndpoints < 2)
+ if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||
+ control_interface->cur_altsetting->desc.bNumEndpoints == 0)
return -EINVAL;
epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
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) {
- dev_err(&intf->dev, "no more free acm devices\n");
- return -ENODEV;
- }
+ dev_dbg(&intf->dev, "interfaces are valid\n");
acm = kzalloc(sizeof(struct acm), GFP_KERNEL);
if (acm == NULL) {
- dev_dbg(&intf->dev, "out of memory (acm kzalloc)\n");
+ dev_err(&intf->dev, "out of memory (acm kzalloc)\n");
goto alloc_fail;
}
- ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize);
- readsize = le16_to_cpu(epread->wMaxPacketSize) *
+ minor = acm_alloc_minor(acm);
+ if (minor == ACM_TTY_MINORS) {
+ dev_err(&intf->dev, "no more free acm devices\n");
+ kfree(acm);
+ return -ENODEV;
+ }
+
+ ctrlsize = usb_endpoint_maxp(epctrl);
+ readsize = usb_endpoint_maxp(epread) *
(quirks == SINGLE_RX_URB ? 1 : 2);
acm->combined_interfaces = combined_interfaces;
- acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20;
+ acm->writesize = usb_endpoint_maxp(epwrite) * 20;
acm->control = control_interface;
acm->data = data_interface;
acm->minor = minor;
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_waitqueue_head(&acm->drain_wait);
- spin_lock_init(&acm->throttle_lock);
+ init_usb_anchor(&acm->deferred);
spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock);
mutex_init(&acm->mutex);
acm->is_int_ep = usb_endpoint_xfer_int(epread);
if (acm->is_int_ep)
acm->bInterval = epread->bInterval;
+ if (quirks & NO_HANGUP_IN_RESET_RESUME)
+ acm->no_hangup_in_reset_resume = 1;
tty_port_init(&acm->port);
acm->port.ops = &acm_port_ops;
buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
if (!buf) {
- dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)\n");
+ dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n");
goto alloc_fail2;
}
acm->ctrl_buffer = buf;
if (acm_write_buffers_alloc(acm) < 0) {
- dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n");
+ dev_err(&intf->dev, "out of memory (write buffer alloc)\n");
goto alloc_fail4;
}
acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
if (!acm->ctrlurb) {
- dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
+ dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
goto alloc_fail5;
}
for (i = 0; i < num_rx_buf; i++) {
- struct acm_ru *rcv = &(acm->ru[i]);
+ struct acm_rb *rb = &(acm->read_buffers[i]);
+ struct urb *urb;
- 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");
+ rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,
+ &rb->dma);
+ if (!rb->base) {
+ dev_err(&intf->dev, "out of memory "
+ "(read bufs usb_alloc_coherent)\n");
goto alloc_fail6;
}
+ rb->index = i;
+ rb->instance = acm;
- rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
- rcv->instance = acm;
- }
- for (i = 0; i < num_rx_buf; i++) {
- struct acm_rb *rb = &(acm->rb[i]);
-
- rb->base = usb_alloc_coherent(acm->dev, readsize,
- GFP_KERNEL, &rb->dma);
- if (!rb->base) {
- dev_dbg(&intf->dev,
- "out of memory (read bufs usb_alloc_coherent)\n");
- goto alloc_fail7;
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dev_err(&intf->dev,
+ "out of memory (read urbs usb_alloc_urb)\n");
+ goto alloc_fail6;
}
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_dma = rb->dma;
+ if (acm->is_int_ep) {
+ usb_fill_int_urb(urb, acm->dev,
+ acm->rx_endpoint,
+ rb->base,
+ acm->readsize,
+ acm_read_bulk_callback, rb,
+ acm->bInterval);
+ } else {
+ usb_fill_bulk_urb(urb, acm->dev,
+ acm->rx_endpoint,
+ rb->base,
+ acm->readsize,
+ acm_read_bulk_callback, rb);
+ }
+
+ acm->read_urbs[i] = urb;
+ __set_bit(i, &acm->read_urbs_free);
}
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_fail8;
+ dev_err(&intf->dev,
+ "out of memory (write urbs usb_alloc_urb)\n");
+ goto alloc_fail7;
}
if (usb_endpoint_xfer_int(epwrite))
usb_fill_int_urb(snd->urb, usb_dev,
- usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
+ usb_sndintpipe(usb_dev, epwrite->bEndpointAddress),
NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);
else
usb_fill_bulk_urb(snd->urb, usb_dev,
i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
if (i < 0)
- goto alloc_fail8;
+ goto alloc_fail7;
if (cfd) { /* export the country data */
acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);
i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);
if (i < 0) {
kfree(acm->country_codes);
+ acm->country_codes = NULL;
+ acm->country_code_size = 0;
goto skip_countries;
}
if (i < 0) {
device_remove_file(&intf->dev, &dev_attr_wCountryCodes);
kfree(acm->country_codes);
+ acm->country_codes = NULL;
+ acm->country_code_size = 0;
goto skip_countries;
}
}
usb_set_intfdata(data_interface, acm);
usb_get_intf(control_interface);
- tty_register_device(acm_tty_driver, minor, &control_interface->dev);
-
- acm_table[minor] = acm;
+ tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,
+ &control_interface->dev);
+ if (IS_ERR(tty_dev)) {
+ rv = PTR_ERR(tty_dev);
+ goto alloc_fail8;
+ }
return 0;
alloc_fail8:
+ if (acm->country_codes) {
+ device_remove_file(&acm->control->dev,
+ &dev_attr_wCountryCodes);
+ device_remove_file(&acm->control->dev,
+ &dev_attr_iCountryCodeRelDate);
+ }
+ device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);
+alloc_fail7:
+ usb_set_intfdata(intf, NULL);
for (i = 0; i < ACM_NW; i++)
usb_free_urb(acm->wb[i].urb);
-alloc_fail7:
- acm_read_buffers_free(acm);
alloc_fail6:
for (i = 0; i < num_rx_buf; i++)
- usb_free_urb(acm->ru[i].urb);
+ usb_free_urb(acm->read_urbs[i]);
+ acm_read_buffers_free(acm);
usb_free_urb(acm->ctrlurb);
alloc_fail5:
acm_write_buffers_free(acm);
alloc_fail4:
usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
alloc_fail2:
+ acm_release_minor(acm);
kfree(acm);
alloc_fail:
- return -ENOMEM;
+ return rv;
}
static void stop_data_traffic(struct acm *acm)
{
int i;
- dbg("Entering stop_data_traffic");
- tasklet_disable(&acm->urb_task);
+ if (!acm) {
+ pr_err("%s: !acm\n", __func__);
+ return;
+ }
+
+ dev_dbg(&acm->control->dev, "%s\n", __func__);
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);
+ usb_kill_urb(acm->read_urbs[i]);
cancel_work_sync(&acm->work);
}
struct acm *acm = usb_get_intfdata(intf);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct tty_struct *tty;
+ struct urb *res;
+ int i;
+
+ dev_dbg(&intf->dev, "%s\n", __func__);
/* sibling interface is already cleaning up */
if (!acm)
return;
- mutex_lock(&open_mutex);
+ mutex_lock(&acm->mutex);
+ acm->disconnected = true;
if (acm->country_codes) {
device_remove_file(&acm->control->dev,
&dev_attr_wCountryCodes);
&dev_attr_iCountryCodeRelDate);
}
device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);
- acm->dev = NULL;
usb_set_intfdata(acm->control, NULL);
usb_set_intfdata(acm->data, NULL);
+ mutex_unlock(&acm->mutex);
+
+ tty = tty_port_tty_get(&acm->port);
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
stop_data_traffic(acm);
+ tty_unregister_device(acm_tty_driver, acm->minor);
+
+ /* decrement ref count of anchored urbs */
+ while ((res = usb_get_from_anchor(&acm->deferred)))
+ usb_put_urb(res);
+ usb_free_urb(acm->ctrlurb);
+ for (i = 0; i < ACM_NW; i++)
+ usb_free_urb(acm->wb[i].urb);
+ for (i = 0; i < acm->rx_buflimit; i++)
+ usb_free_urb(acm->read_urbs[i]);
acm_write_buffers_free(acm);
- usb_free_coherent(usb_dev, acm->ctrlsize, acm->ctrl_buffer,
- acm->ctrl_dma);
+ usb_free_coherent(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
acm_read_buffers_free(acm);
if (!acm->combined_interfaces)
usb_driver_release_interface(&acm_driver, intf == acm->control ?
acm->data : acm->control);
- 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);
- }
+ tty_port_put(&acm->port);
}
#ifdef CONFIG_PM
struct acm *acm = usb_get_intfdata(intf);
int cnt;
- if (message.event & PM_EVENT_AUTO) {
+ if (!acm) {
+ pr_err("%s: !acm\n", __func__);
+ return -ENODEV;
+ }
+
+ if (PMSG_IS_AUTO(message)) {
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);
+ spin_lock_irq(&acm->write_lock);
+ b = acm->transmitting;
+ spin_unlock_irq(&acm->write_lock);
if (b)
return -EBUSY;
}
if (cnt)
return 0;
- /*
- we treat opened interfaces differently,
- we must guard against open
- */
- mutex_lock(&acm->mutex);
- if (acm->port.count)
+ if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags))
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);
- struct acm_wb *wb;
int rv = 0;
int cnt;
+#ifdef CONFIG_PM
+ struct urb *res;
+#else
+ struct acm_wb *wb;
+#endif
+
+ if (!acm) {
+ pr_err("%s: !acm\n", __func__);
+ return -ENODEV;
+ }
spin_lock_irq(&acm->read_lock);
- acm->susp_count -= 1;
- cnt = acm->susp_count;
+ if (acm->susp_count > 0) {
+ acm->susp_count -= 1;
+ cnt = acm->susp_count;
+ } else {
+ spin_unlock_irq(&acm->read_lock);
+ return 0;
+ }
spin_unlock_irq(&acm->read_lock);
if (cnt)
return 0;
- mutex_lock(&acm->mutex);
- if (acm->port.count) {
+ if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) {
rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO);
-
spin_lock_irq(&acm->write_lock);
+#ifdef CONFIG_PM
+ while ((res = usb_get_from_anchor(&acm->deferred))) {
+ /* decrement ref count*/
+ usb_put_urb(res);
+ rv = usb_submit_urb(res, GFP_ATOMIC);
+ if (rv < 0) {
+ dbg("usb_submit_urb(pending request)"
+ " failed: %d", rv);
+ usb_unanchor_urb(res);
+ acm_write_done(acm, res->context);
+ }
+ }
+ spin_unlock_irq(&acm->write_lock);
+#else
if (acm->delayed_wb) {
wb = acm->delayed_wb;
acm->delayed_wb = NULL;
} else {
spin_unlock_irq(&acm->write_lock);
}
+#endif
/*
* delayed error checking because we must
if (rv < 0)
goto err_out;
- tasklet_schedule(&acm->urb_task);
+ rv = acm_submit_read_urbs(acm, GFP_NOIO);
}
err_out:
- mutex_unlock(&acm->mutex);
return rv;
}
static int acm_reset_resume(struct usb_interface *intf)
{
struct acm *acm = usb_get_intfdata(intf);
- struct tty_struct *tty;
- mutex_lock(&acm->mutex);
- if (acm->port.count) {
+ if (!acm) {
+ pr_err("%s: !acm\n", __func__);
+ return -ENODEV;
+ }
+
+ if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) {
tty = tty_port_tty_get(&acm->port);
if (tty) {
- tty_hangup(tty);
+ if (!acm->no_hangup_in_reset_resume)
+ tty_hangup(tty);
tty_kref_put(tty);
}
}
- mutex_unlock(&acm->mutex);
+
return acm_resume(intf);
}
},
{ USB_DEVICE(0x22b8, 0x6425), /* Motorola MOTOMAGX phones */
},
+ /* Motorola H24 HSPA module: */
+ { USB_DEVICE(0x22b8, 0x2d91) }, /* modem */
+ { USB_DEVICE(0x22b8, 0x2d92) }, /* modem + diagnostics */
+ { USB_DEVICE(0x22b8, 0x2d93) }, /* modem + AT port */
+ { USB_DEVICE(0x22b8, 0x2d95) }, /* modem + AT port + diagnostics */
+ { USB_DEVICE(0x22b8, 0x2d96) }, /* modem + NMEA */
+ { USB_DEVICE(0x22b8, 0x2d97) }, /* modem + diagnostics + NMEA */
+ { USB_DEVICE(0x22b8, 0x2d99) }, /* modem + AT port + NMEA */
+ { USB_DEVICE(0x22b8, 0x2d9a) }, /* modem + AT port + diagnostics + NMEA */
+
{ USB_DEVICE(0x0572, 0x1329), /* Hummingbird huc56s (Conexant) */
.driver_info = NO_UNION_NORMAL, /* union descriptor misplaced on
data interface instead of
Maybe we should define a new
quirk for this. */
},
+ { USB_DEVICE(0x0572, 0x1340), /* Conexant CX93010-2x UCMxx */
+ .driver_info = NO_UNION_NORMAL,
+ },
+ { USB_DEVICE(0x05f9, 0x4002), /* PSC Scanning, Magellan 800i */
+ .driver_info = NO_UNION_NORMAL,
+ },
{ USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */
.driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */
},
{ USB_DEVICE(0x1576, 0x03b1), /* Maretron USB100 */
.driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */
},
+ { USB_DEVICE(0x1519, 0x0020),
+ .driver_info = NO_UNION_NORMAL | NO_HANGUP_IN_RESET_RESUME, /* has no union descriptor */
+ },
/* Nokia S60 phones expose two ACM channels. The first is
* a modem and is picked up by the standard AT-command
{ NOKIA_PCSUITE_ACM_INFO(0x0154), }, /* Nokia 5800 XpressMusic */
{ NOKIA_PCSUITE_ACM_INFO(0x04ce), }, /* Nokia E90 */
{ NOKIA_PCSUITE_ACM_INFO(0x01d4), }, /* Nokia E55 */
+ { NOKIA_PCSUITE_ACM_INFO(0x0302), }, /* Nokia N8 */
+ { NOKIA_PCSUITE_ACM_INFO(0x0335), }, /* Nokia E7 */
+ { NOKIA_PCSUITE_ACM_INFO(0x03cd), }, /* Nokia C7 */
{ SAMSUNG_PCSUITE_ACM_INFO(0x6651), }, /* Samsung GTi8510 (INNOV8) */
+ /* Support for Owen devices */
+ { USB_DEVICE(0x03eb, 0x0030), }, /* Owen SI30 */
+
/* NOTE: non-Nokia COMM/ACM/0xff is likely MSFT RNDIS... NOT a modem! */
/* Support Lego NXT using pbLua firmware */
.driver_info = NOT_A_MODEM,
},
+ /* Support for Droids MuIn LCD */
+ { USB_DEVICE(0x04d8, 0x000b),
+ .driver_info = NO_DATA_INTERFACE,
+ },
+
+#if IS_ENABLED(CONFIG_INPUT_IMS_PCU)
+ { USB_DEVICE(0x04d8, 0x0082), /* Application mode */
+ .driver_info = IGNORE_DEVICE,
+ },
+ { USB_DEVICE(0x04d8, 0x0083), /* Bootloader mode */
+ .driver_info = IGNORE_DEVICE,
+ },
+#endif
+
+ /* Exclude XMM6260 boot rom (not running modem software yet) */
+ { USB_DEVICE(0x058b, 0x0041),
+ .driver_info = IGNORE_DEVICE,
+ },
+
+ /* Icera 450 */
+ { USB_DEVICE(0x1983, 0x0321),
+ .driver_info = NO_HANGUP_IN_RESET_RESUME,
+ },
+
/* control interfaces without any protocol set */
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
USB_CDC_PROTO_NONE) },
#ifdef CONFIG_PM
.supports_autosuspend = 1,
#endif
+ .disable_hub_initiated_lpm = 1,
};
/*
*/
static const struct tty_operations acm_ops = {
+ .install = acm_tty_install,
.open = acm_tty_open,
.close = acm_tty_close,
+ .cleanup = acm_tty_cleanup,
.hangup = acm_tty_hangup,
.write = acm_tty_write,
.write_room = acm_tty_write_room,
acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
if (!acm_tty_driver)
return -ENOMEM;
- acm_tty_driver->owner = THIS_MODULE,
acm_tty_driver->driver_name = "acm",
acm_tty_driver->name = "ttyACM",
acm_tty_driver->major = ACM_TTY_MAJOR,
return retval;
}
- printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
- DRIVER_DESC "\n");
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
return 0;
}