USB: serial: add generic TIOCMIWAIT implementation
authorJohan Hovold <jhovold@gmail.com>
Thu, 21 Mar 2013 11:36:52 +0000 (12:36 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 25 Mar 2013 20:50:51 +0000 (13:50 -0700)
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>
drivers/usb/serial/generic.c
include/linux/usb/serial.h

index aa71f6e..18bc74e 100644 (file)
@@ -418,6 +418,64 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty)
 }
 EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle);
 
+static bool usb_serial_generic_msr_changed(struct tty_struct *tty,
+                               unsigned long arg, struct async_icount *cprev)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       struct async_icount cnow;
+       unsigned long flags;
+       bool ret;
+
+       /*
+        * Use tty-port initialised flag to detect all hangups including the
+        * one generated at USB-device disconnect.
+        *
+        * FIXME: Remove hupping check once tty_port_hangup calls shutdown
+        *        (which clears the initialised flag) before wake up.
+        */
+       if (test_bit(TTY_HUPPING, &tty->flags))
+               return true;
+       if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags))
+               return true;
+
+       spin_lock_irqsave(&port->lock, flags);
+       cnow = port->icount;                            /* atomic copy*/
+       spin_unlock_irqrestore(&port->lock, flags);
+
+       ret =   ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) ||
+               ((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) ||
+               ((arg & TIOCM_CD)  && (cnow.dcd != cprev->dcd)) ||
+               ((arg & TIOCM_CTS) && (cnow.cts != cprev->cts));
+
+       *cprev = cnow;
+
+       return ret;
+}
+
+int usb_serial_generic_tiocmiwait(struct tty_struct *tty, unsigned long arg)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       struct async_icount cnow;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&port->lock, flags);
+       cnow = port->icount;                            /* atomic copy */
+       spin_unlock_irqrestore(&port->lock, flags);
+
+       ret = wait_event_interruptible(port->port.delta_msr_wait,
+                       usb_serial_generic_msr_changed(tty, arg, &cnow));
+       if (!ret) {
+               if (test_bit(TTY_HUPPING, &tty->flags))
+                       ret = -EIO;
+               if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags))
+                       ret = -EIO;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(usb_serial_generic_tiocmiwait);
+
 #ifdef CONFIG_MAGIC_SYSRQ
 int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch)
 {
index 9c8b53f..47c8d2c 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <linux/kref.h>
 #include <linux/mutex.h>
+#include <linux/serial.h>
 #include <linux/sysrq.h>
 #include <linux/kfifo.h>
 
@@ -61,6 +62,7 @@
  * @bulk_out_buffers: pointers to the bulk out buffers for this port
  * @write_urbs: pointers to the bulk out urbs for this port
  * @write_urbs_free: status bitmap the for bulk out urbs
+ * @icount: interrupt counters
  * @tx_bytes: number of bytes currently in host stack queues
  * @bulk_out_endpointAddress: endpoint address for the bulk out pipe for this
  *     port.
@@ -109,6 +111,7 @@ struct usb_serial_port {
        unsigned long           write_urbs_free;
        __u8                    bulk_out_endpointAddress;
 
+       struct async_icount     icount;
        int                     tx_bytes;
 
        unsigned long           flags;
@@ -330,6 +333,8 @@ extern void usb_serial_generic_read_bulk_callback(struct urb *urb);
 extern void usb_serial_generic_write_bulk_callback(struct urb *urb);
 extern void usb_serial_generic_throttle(struct tty_struct *tty);
 extern void usb_serial_generic_unthrottle(struct tty_struct *tty);
+extern int usb_serial_generic_tiocmiwait(struct tty_struct *tty,
+                                                       unsigned long arg);
 extern int usb_serial_generic_register(void);
 extern void usb_serial_generic_deregister(void);
 extern int usb_serial_generic_submit_read_urbs(struct usb_serial_port *port,