summaryrefslogtreecommitdiff
path: root/drivers/usb/serial/generic.c
diff options
context:
space:
mode:
authorJohan Hovold <jhovold@gmail.com>2013-03-21 12:36:52 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-25 13:50:51 -0700
commit980373b7918b8023be6b7df03857f494ae124d0b (patch)
tree0859105788cf8495327e8727a29779b7029024d0 /drivers/usb/serial/generic.c
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>
Diffstat (limited to 'drivers/usb/serial/generic.c')
-rw-r--r--drivers/usb/serial/generic.c58
1 files changed, 58 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)
}
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)
{