Add generic TIOCMIWAIT implementation which correctly handles USB-device disconnects, 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 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 port modem-status-change wait queue on changes. Signed-off-by: Johan Hovold <jhovold@xxxxxxxxx> --- drivers/usb/serial/generic.c | 45 +++++++++++++++++++++++++++++++++++++++++ drivers/usb/serial/usb-serial.c | 2 ++ include/linux/usb/serial.h | 7 +++++++ 3 files changed, 54 insertions(+) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index aa71f6e..62685c5 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -418,6 +418,51 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty) } EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle); +static bool usb_serial_generic_msr_changed(struct usb_serial_port *port, + unsigned long arg, struct async_icount *cprev) +{ + struct async_icount cnow; + unsigned long flags; + bool ret; + + if (port->serial->disconnected) + 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->delta_msr_wait, + usb_serial_generic_msr_changed(port, arg, &cnow)); + + if (!ret && port->serial->disconnected) + 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/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index c17becb..394f80e 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -898,6 +898,7 @@ static int usb_serial_probe(struct usb_interface *interface, /* Keep this for private driver use for the moment but should probably go away */ INIT_WORK(&port->work, usb_serial_port_work); + init_waitqueue_head(&port->delta_msr_wait); serial->port[i] = port; port->dev.parent = &interface->dev; port->dev.driver = NULL; @@ -1097,6 +1098,7 @@ static void usb_serial_disconnect(struct usb_interface *interface) tty_kref_put(tty); } usb_serial_port_poison_urbs(port); + wake_up_interruptible_all(&port->delta_msr_wait); cancel_work_sync(&port->work); if (device_is_registered(&port->dev)) device_del(&port->dev); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 8cb74c0..21853d6 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -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,8 @@ * @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 + * @delta_msr_wait: wait queue for modem-status changes + * @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. @@ -108,6 +111,8 @@ struct usb_serial_port { unsigned long write_urbs_free; __u8 bulk_out_endpointAddress; + wait_queue_head_t delta_msr_wait; + struct async_icount icount; int tx_bytes; unsigned long flags; @@ -328,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, -- 1.8.1.5 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html