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) { 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 @@ #include #include +#include #include #include @@ -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,