aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hovold <jhovold@gmail.com>2013-03-21 07:36:52 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-25 16:50:51 -0400
commit980373b7918b8023be6b7df03857f494ae124d0b (patch)
tree0859105788cf8495327e8727a29779b7029024d0
parent143d9d961608b737d90a813deaaf91affb41c83c (diff)
USB: serial: add generic TIOCMIWAIT implementation
Add generic TIOCMIWAIT implementation which correctly handles hangup, USB-device disconnect, does not rely on the deprecated sleep_on functions and hence does not suffer from the races currently affecting several usb-serial drivers. This makes it much easier to add TIOCMIWAIT support to subdrivers as the tricky details related to hangup and disconnect (e.g. atomicity, that the private port data may have been freed when woken up, and waking up processes at disconnect) have been handled once and for all. To add support to a subdriver, simply set the tiocmiwait-port-operation field, update the port icount fields and wake up any process sleeping on the tty-port modem-status-change wait queue on changes. Note that the tty-port initialised flag can be used to detect disconnected as the port will be hung up as part of disconnect (and cannot be reactivated due to the disconnected flag). However, as the tty-port implementation currently wakes up processes before calling port shutdown, the tty-hupping flag must also be checked to detect hangup for now. Signed-off-by: Johan Hovold <jhovold@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/serial/generic.c58
-rw-r--r--include/linux/usb/serial.h5
2 files changed, 63 insertions, 0 deletions
diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
index aa71f6e72f61..18bc74e20fe1 100644
--- a/drivers/usb/serial/generic.c
+++ b/drivers/usb/serial/generic.c
@@ -418,6 +418,64 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty)
418} 418}
419EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle); 419EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle);
420 420
421static bool usb_serial_generic_msr_changed(struct tty_struct *tty,
422 unsigned long arg, struct async_icount *cprev)
423{
424 struct usb_serial_port *port = tty->driver_data;
425 struct async_icount cnow;
426 unsigned long flags;
427 bool ret;
428
429 /*
430 * Use tty-port initialised flag to detect all hangups including the
431 * one generated at USB-device disconnect.
432 *
433 * FIXME: Remove hupping check once tty_port_hangup calls shutdown
434 * (which clears the initialised flag) before wake up.
435 */
436 if (test_bit(TTY_HUPPING, &tty->flags))
437 return true;
438 if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags))
439 return true;
440
441 spin_lock_irqsave(&port->lock, flags);
442 cnow = port->icount; /* atomic copy*/
443 spin_unlock_irqrestore(&port->lock, flags);
444
445 ret = ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) ||
446 ((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) ||
447 ((arg & TIOCM_CD) && (cnow.dcd != cprev->dcd)) ||
448 ((arg & TIOCM_CTS) && (cnow.cts != cprev->cts));
449
450 *cprev = cnow;
451
452 return ret;
453}
454
455int usb_serial_generic_tiocmiwait(struct tty_struct *tty, unsigned long arg)
456{
457 struct usb_serial_port *port = tty->driver_data;
458 struct async_icount cnow;
459 unsigned long flags;
460 int ret;
461
462 spin_lock_irqsave(&port->lock, flags);
463 cnow = port->icount; /* atomic copy */
464 spin_unlock_irqrestore(&port->lock, flags);
465
466 ret = wait_event_interruptible(port->port.delta_msr_wait,
467 usb_serial_generic_msr_changed(tty, arg, &cnow));
468 if (!ret) {
469 if (test_bit(TTY_HUPPING, &tty->flags))
470 ret = -EIO;
471 if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags))
472 ret = -EIO;
473 }
474
475 return ret;
476}
477EXPORT_SYMBOL_GPL(usb_serial_generic_tiocmiwait);
478
421#ifdef CONFIG_MAGIC_SYSRQ 479#ifdef CONFIG_MAGIC_SYSRQ
422int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) 480int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch)
423{ 481{
diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h
index 9c8b53f80f48..47c8d2c506c8 100644
--- a/include/linux/usb/serial.h
+++ b/include/linux/usb/serial.h
@@ -15,6 +15,7 @@
15 15
16#include <linux/kref.h> 16#include <linux/kref.h>
17#include <linux/mutex.h> 17#include <linux/mutex.h>
18#include <linux/serial.h>
18#include <linux/sysrq.h> 19#include <linux/sysrq.h>
19#include <linux/kfifo.h> 20#include <linux/kfifo.h>
20 21
@@ -61,6 +62,7 @@
61 * @bulk_out_buffers: pointers to the bulk out buffers for this port 62 * @bulk_out_buffers: pointers to the bulk out buffers for this port
62 * @write_urbs: pointers to the bulk out urbs for this port 63 * @write_urbs: pointers to the bulk out urbs for this port
63 * @write_urbs_free: status bitmap the for bulk out urbs 64 * @write_urbs_free: status bitmap the for bulk out urbs
65 * @icount: interrupt counters
64 * @tx_bytes: number of bytes currently in host stack queues 66 * @tx_bytes: number of bytes currently in host stack queues
65 * @bulk_out_endpointAddress: endpoint address for the bulk out pipe for this 67 * @bulk_out_endpointAddress: endpoint address for the bulk out pipe for this
66 * port. 68 * port.
@@ -109,6 +111,7 @@ struct usb_serial_port {
109 unsigned long write_urbs_free; 111 unsigned long write_urbs_free;
110 __u8 bulk_out_endpointAddress; 112 __u8 bulk_out_endpointAddress;
111 113
114 struct async_icount icount;
112 int tx_bytes; 115 int tx_bytes;
113 116
114 unsigned long flags; 117 unsigned long flags;
@@ -330,6 +333,8 @@ extern void usb_serial_generic_read_bulk_callback(struct urb *urb);
330extern void usb_serial_generic_write_bulk_callback(struct urb *urb); 333extern void usb_serial_generic_write_bulk_callback(struct urb *urb);
331extern void usb_serial_generic_throttle(struct tty_struct *tty); 334extern void usb_serial_generic_throttle(struct tty_struct *tty);
332extern void usb_serial_generic_unthrottle(struct tty_struct *tty); 335extern void usb_serial_generic_unthrottle(struct tty_struct *tty);
336extern int usb_serial_generic_tiocmiwait(struct tty_struct *tty,
337 unsigned long arg);
333extern int usb_serial_generic_register(void); 338extern int usb_serial_generic_register(void);
334extern void usb_serial_generic_deregister(void); 339extern void usb_serial_generic_deregister(void);
335extern int usb_serial_generic_submit_read_urbs(struct usb_serial_port *port, 340extern int usb_serial_generic_submit_read_urbs(struct usb_serial_port *port,