diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-08-06 21:46:10 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-08-13 20:32:57 -0400 |
commit | e5fbab51b4219fbd1dab28666affe38a920b5f7e (patch) | |
tree | dc2d6ff5467f4eff183de712eb13403d35d13351 /drivers/usb | |
parent | 934da4635c2d05cef474e5243ef05df95b2ad264 (diff) |
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: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 39 | ||||
-rw-r--r-- | drivers/usb/class/cdc-acm.h | 1 |
2 files changed, 35 insertions, 5 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index a9dd28f446d8..efc4373ededb 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c | |||
@@ -51,6 +51,7 @@ | |||
51 | */ | 51 | */ |
52 | 52 | ||
53 | #undef DEBUG | 53 | #undef DEBUG |
54 | #undef VERBOSE_DEBUG | ||
54 | 55 | ||
55 | #include <linux/kernel.h> | 56 | #include <linux/kernel.h> |
56 | #include <linux/errno.h> | 57 | #include <linux/errno.h> |
@@ -70,6 +71,9 @@ | |||
70 | 71 | ||
71 | #include "cdc-acm.h" | 72 | #include "cdc-acm.h" |
72 | 73 | ||
74 | |||
75 | #define ACM_CLOSE_TIMEOUT 15 /* seconds to let writes drain */ | ||
76 | |||
73 | /* | 77 | /* |
74 | * Version Information | 78 | * Version Information |
75 | */ | 79 | */ |
@@ -85,6 +89,12 @@ static DEFINE_MUTEX(open_mutex); | |||
85 | 89 | ||
86 | #define ACM_READY(acm) (acm && acm->dev && acm->used) | 90 | #define ACM_READY(acm) (acm && acm->dev && acm->used) |
87 | 91 | ||
92 | #ifdef VERBOSE_DEBUG | ||
93 | #define verbose 1 | ||
94 | #else | ||
95 | #define verbose 0 | ||
96 | #endif | ||
97 | |||
88 | /* | 98 | /* |
89 | * Functions for ACM control messages. | 99 | * Functions for ACM control messages. |
90 | */ | 100 | */ |
@@ -136,11 +146,14 @@ static int acm_wb_alloc(struct acm *acm) | |||
136 | static int acm_wb_is_avail(struct acm *acm) | 146 | static int acm_wb_is_avail(struct acm *acm) |
137 | { | 147 | { |
138 | int i, n; | 148 | int i, n; |
149 | unsigned long flags; | ||
139 | 150 | ||
140 | n = ACM_NW; | 151 | n = ACM_NW; |
152 | spin_lock_irqsave(&acm->write_lock, flags); | ||
141 | for (i = 0; i < ACM_NW; i++) { | 153 | for (i = 0; i < ACM_NW; i++) { |
142 | n -= acm->wb[i].use; | 154 | n -= acm->wb[i].use; |
143 | } | 155 | } |
156 | spin_unlock_irqrestore(&acm->write_lock, flags); | ||
144 | return n; | 157 | return n; |
145 | } | 158 | } |
146 | 159 | ||
@@ -467,22 +480,28 @@ urbs: | |||
467 | /* data interface wrote those outgoing bytes */ | 480 | /* data interface wrote those outgoing bytes */ |
468 | static void acm_write_bulk(struct urb *urb) | 481 | static void acm_write_bulk(struct urb *urb) |
469 | { | 482 | { |
470 | struct acm *acm; | ||
471 | struct acm_wb *wb = urb->context; | 483 | struct acm_wb *wb = urb->context; |
484 | struct acm *acm = wb->instance; | ||
472 | 485 | ||
473 | dbg("Entering acm_write_bulk with status %d", urb->status); | 486 | if (verbose || urb->status |
487 | || (urb->actual_length != urb->transfer_buffer_length)) | ||
488 | dev_dbg(&acm->data->dev, "tx %d/%d bytes -- > %d\n", | ||
489 | urb->actual_length, | ||
490 | urb->transfer_buffer_length, | ||
491 | urb->status); | ||
474 | 492 | ||
475 | acm = wb->instance; | ||
476 | acm_write_done(acm, wb); | 493 | acm_write_done(acm, wb); |
477 | if (ACM_READY(acm)) | 494 | if (ACM_READY(acm)) |
478 | schedule_work(&acm->work); | 495 | schedule_work(&acm->work); |
496 | else | ||
497 | wake_up_interruptible(&acm->drain_wait); | ||
479 | } | 498 | } |
480 | 499 | ||
481 | static void acm_softint(struct work_struct *work) | 500 | static void acm_softint(struct work_struct *work) |
482 | { | 501 | { |
483 | struct acm *acm = container_of(work, struct acm, work); | 502 | struct acm *acm = container_of(work, struct acm, work); |
484 | dbg("Entering acm_softint."); | 503 | |
485 | 504 | dev_vdbg(&acm->data->dev, "tx work\n"); | |
486 | if (!ACM_READY(acm)) | 505 | if (!ACM_READY(acm)) |
487 | return; | 506 | return; |
488 | tty_wakeup(acm->tty); | 507 | tty_wakeup(acm->tty); |
@@ -603,6 +622,8 @@ static void acm_tty_unregister(struct acm *acm) | |||
603 | kfree(acm); | 622 | kfree(acm); |
604 | } | 623 | } |
605 | 624 | ||
625 | static int acm_tty_chars_in_buffer(struct tty_struct *tty); | ||
626 | |||
606 | static void acm_tty_close(struct tty_struct *tty, struct file *filp) | 627 | static void acm_tty_close(struct tty_struct *tty, struct file *filp) |
607 | { | 628 | { |
608 | struct acm *acm = tty->driver_data; | 629 | struct acm *acm = tty->driver_data; |
@@ -617,6 +638,13 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) | |||
617 | if (acm->dev) { | 638 | if (acm->dev) { |
618 | usb_autopm_get_interface(acm->control); | 639 | usb_autopm_get_interface(acm->control); |
619 | acm_set_control(acm, acm->ctrlout = 0); | 640 | acm_set_control(acm, acm->ctrlout = 0); |
641 | |||
642 | /* try letting the last writes drain naturally */ | ||
643 | wait_event_interruptible_timeout(acm->drain_wait, | ||
644 | (ACM_NW == acm_wb_is_avail(acm)) | ||
645 | || !acm->dev, | ||
646 | ACM_CLOSE_TIMEOUT * HZ); | ||
647 | |||
620 | usb_kill_urb(acm->ctrlurb); | 648 | usb_kill_urb(acm->ctrlurb); |
621 | for (i = 0; i < ACM_NW; i++) | 649 | for (i = 0; i < ACM_NW; i++) |
622 | usb_kill_urb(acm->wb[i].urb); | 650 | usb_kill_urb(acm->wb[i].urb); |
@@ -1047,6 +1075,7 @@ skip_normal_probe: | |||
1047 | acm->urb_task.data = (unsigned long) acm; | 1075 | acm->urb_task.data = (unsigned long) acm; |
1048 | INIT_WORK(&acm->work, acm_softint); | 1076 | INIT_WORK(&acm->work, acm_softint); |
1049 | INIT_WORK(&acm->waker, acm_waker); | 1077 | INIT_WORK(&acm->waker, acm_waker); |
1078 | init_waitqueue_head(&acm->drain_wait); | ||
1050 | spin_lock_init(&acm->throttle_lock); | 1079 | spin_lock_init(&acm->throttle_lock); |
1051 | spin_lock_init(&acm->write_lock); | 1080 | spin_lock_init(&acm->write_lock); |
1052 | spin_lock_init(&acm->read_lock); | 1081 | spin_lock_init(&acm->read_lock); |
diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 94266362ca68..1f95e7aa1b66 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h | |||
@@ -113,6 +113,7 @@ struct acm { | |||
113 | struct usb_cdc_line_coding line; /* bits, stop, parity */ | 113 | struct usb_cdc_line_coding line; /* bits, stop, parity */ |
114 | struct work_struct work; /* work queue entry for line discipline waking up */ | 114 | struct work_struct work; /* work queue entry for line discipline waking up */ |
115 | struct work_struct waker; | 115 | struct work_struct waker; |
116 | wait_queue_head_t drain_wait; /* close processing */ | ||
116 | struct tasklet_struct urb_task; /* rx processing */ | 117 | struct tasklet_struct urb_task; /* rx processing */ |
117 | spinlock_t throttle_lock; /* synchronize throtteling and read callback */ | 118 | spinlock_t throttle_lock; /* synchronize throtteling and read callback */ |
118 | unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ | 119 | unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ |