Commit e5fbab51 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

usb: cdc-acm: drain writes on close

Add a mechanism to let the write queue drain naturally before
closing the TTY, rather than always losing that data.  There
is a timeout, so it can't wait too long.

Provide missing locking inside acm_wb_is_avail(); it matters
more now.  Note, this presumes an earlier patch was applied,
removing a call to this routine where the lock was held.

Slightly improved diagnostics on write URB completion, so we
can tell when a write URB gets killed and, if so, how much
data it wrote first ... and so that I/O path is normally
silent (and can't much change timings).
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 934da463
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
*/ */
#undef DEBUG #undef DEBUG
#undef VERBOSE_DEBUG
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/errno.h> #include <linux/errno.h>
...@@ -70,6 +71,9 @@ ...@@ -70,6 +71,9 @@
#include "cdc-acm.h" #include "cdc-acm.h"
#define ACM_CLOSE_TIMEOUT 15 /* seconds to let writes drain */
/* /*
* Version Information * Version Information
*/ */
...@@ -85,6 +89,12 @@ static DEFINE_MUTEX(open_mutex); ...@@ -85,6 +89,12 @@ static DEFINE_MUTEX(open_mutex);
#define ACM_READY(acm) (acm && acm->dev && acm->used) #define ACM_READY(acm) (acm && acm->dev && acm->used)
#ifdef VERBOSE_DEBUG
#define verbose 1
#else
#define verbose 0
#endif
/* /*
* Functions for ACM control messages. * Functions for ACM control messages.
*/ */
...@@ -136,11 +146,14 @@ static int acm_wb_alloc(struct acm *acm) ...@@ -136,11 +146,14 @@ static int acm_wb_alloc(struct acm *acm)
static int acm_wb_is_avail(struct acm *acm) static int acm_wb_is_avail(struct acm *acm)
{ {
int i, n; int i, n;
unsigned long flags;
n = ACM_NW; n = ACM_NW;
spin_lock_irqsave(&acm->write_lock, flags);
for (i = 0; i < ACM_NW; i++) { for (i = 0; i < ACM_NW; i++) {
n -= acm->wb[i].use; n -= acm->wb[i].use;
} }
spin_unlock_irqrestore(&acm->write_lock, flags);
return n; return n;
} }
...@@ -467,22 +480,28 @@ static void acm_rx_tasklet(unsigned long _acm) ...@@ -467,22 +480,28 @@ static void acm_rx_tasklet(unsigned long _acm)
/* data interface wrote those outgoing bytes */ /* data interface wrote those outgoing bytes */
static void acm_write_bulk(struct urb *urb) static void acm_write_bulk(struct urb *urb)
{ {
struct acm *acm;
struct acm_wb *wb = urb->context; struct acm_wb *wb = urb->context;
struct acm *acm = wb->instance;
dbg("Entering acm_write_bulk with status %d", urb->status); if (verbose || urb->status
|| (urb->actual_length != urb->transfer_buffer_length))
dev_dbg(&acm->data->dev, "tx %d/%d bytes -- > %d\n",
urb->actual_length,
urb->transfer_buffer_length,
urb->status);
acm = wb->instance;
acm_write_done(acm, wb); acm_write_done(acm, wb);
if (ACM_READY(acm)) if (ACM_READY(acm))
schedule_work(&acm->work); schedule_work(&acm->work);
else
wake_up_interruptible(&acm->drain_wait);
} }
static void acm_softint(struct work_struct *work) static void acm_softint(struct work_struct *work)
{ {
struct acm *acm = container_of(work, struct acm, work); struct acm *acm = container_of(work, struct acm, work);
dbg("Entering acm_softint.");
dev_vdbg(&acm->data->dev, "tx work\n");
if (!ACM_READY(acm)) if (!ACM_READY(acm))
return; return;
tty_wakeup(acm->tty); tty_wakeup(acm->tty);
...@@ -603,6 +622,8 @@ static void acm_tty_unregister(struct acm *acm) ...@@ -603,6 +622,8 @@ static void acm_tty_unregister(struct acm *acm)
kfree(acm); kfree(acm);
} }
static int acm_tty_chars_in_buffer(struct tty_struct *tty);
static void acm_tty_close(struct tty_struct *tty, struct file *filp) static void acm_tty_close(struct tty_struct *tty, struct file *filp)
{ {
struct acm *acm = tty->driver_data; struct acm *acm = tty->driver_data;
...@@ -617,6 +638,13 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) ...@@ -617,6 +638,13 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
if (acm->dev) { if (acm->dev) {
usb_autopm_get_interface(acm->control); usb_autopm_get_interface(acm->control);
acm_set_control(acm, acm->ctrlout = 0); acm_set_control(acm, acm->ctrlout = 0);
/* try letting the last writes drain naturally */
wait_event_interruptible_timeout(acm->drain_wait,
(ACM_NW == acm_wb_is_avail(acm))
|| !acm->dev,
ACM_CLOSE_TIMEOUT * HZ);
usb_kill_urb(acm->ctrlurb); usb_kill_urb(acm->ctrlurb);
for (i = 0; i < ACM_NW; i++) for (i = 0; i < ACM_NW; i++)
usb_kill_urb(acm->wb[i].urb); usb_kill_urb(acm->wb[i].urb);
...@@ -1047,6 +1075,7 @@ static int acm_probe (struct usb_interface *intf, ...@@ -1047,6 +1075,7 @@ static int acm_probe (struct usb_interface *intf,
acm->urb_task.data = (unsigned long) acm; acm->urb_task.data = (unsigned long) acm;
INIT_WORK(&acm->work, acm_softint); INIT_WORK(&acm->work, acm_softint);
INIT_WORK(&acm->waker, acm_waker); INIT_WORK(&acm->waker, acm_waker);
init_waitqueue_head(&acm->drain_wait);
spin_lock_init(&acm->throttle_lock); spin_lock_init(&acm->throttle_lock);
spin_lock_init(&acm->write_lock); spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock); spin_lock_init(&acm->read_lock);
......
...@@ -113,6 +113,7 @@ struct acm { ...@@ -113,6 +113,7 @@ struct acm {
struct usb_cdc_line_coding line; /* bits, stop, parity */ struct usb_cdc_line_coding line; /* bits, stop, parity */
struct work_struct work; /* work queue entry for line discipline waking up */ struct work_struct work; /* work queue entry for line discipline waking up */
struct work_struct waker; struct work_struct waker;
wait_queue_head_t drain_wait; /* close processing */
struct tasklet_struct urb_task; /* rx processing */ struct tasklet_struct urb_task; /* rx processing */
spinlock_t throttle_lock; /* synchronize throtteling and read callback */ spinlock_t throttle_lock; /* synchronize throtteling and read callback */
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment