aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/serial
diff options
context:
space:
mode:
authorSimon Arlott <simon@fire.lp0.eu>2012-03-26 18:27:59 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-04-09 18:41:40 -0400
commit876ae50d94b02f3f523aa451b45ec5fb9c25d221 (patch)
tree529f5a3acb7beec2ad733709326e067096f53a2d /drivers/usb/serial
parentfca5430d48d53eaf103498c33fd0d1984b9f448b (diff)
USB: ftdi_sio: fix race condition in TIOCMIWAIT, and abort of TIOCMIWAIT when the device is removed
There are two issues here, one is that the device is generating spurious very fast modem status line changes somewhere: CTS becomes high then low 18µs later: [121226.924373] ftdi_process_packet: prev rng=0 dsr=10 dcd=0 cts=6 [121226.924378] ftdi_process_packet: status=10 prev=00 diff=10 [121226.924382] ftdi_process_packet: now rng=0 dsr=10 dcd=0 cts=7 (wake_up_interruptible is called) [121226.924391] ftdi_process_packet: prev rng=0 dsr=10 dcd=0 cts=7 [121226.924394] ftdi_process_packet: status=00 prev=10 diff=10 [121226.924397] ftdi_process_packet: now rng=0 dsr=10 dcd=0 cts=8 (wake_up_interruptible is called) This wakes up the task in TIOCMIWAIT: [121226.924405] ftdi_ioctl: 19451 rng=0->0 dsr=10->10 dcd=0->0 cts=6->8 (wait from 20:51:46 returns and observes both changes) Which then calls TIOCMIWAIT again: 20:51:46.400239 ioctl(3, TIOCMIWAIT, 0x20) = 0 22:11:09.441818 ioctl(3, TIOCMGET, [TIOCM_DTR|TIOCM_RTS]) = 0 22:11:09.442812 ioctl(3, TIOCMIWAIT, 0x20) = -1 EIO (Input/output error) (the second wake_up_interruptible takes effect and an I/O error occurs) The other issue is that TIOCMIWAIT will wait forever (unless the task is interrupted) if the device is removed. This change removes the -EIO return that occurs if the counts don't appear to have changed. Multiple counts may have been processed as one or the waiting task may have started waiting after recording the current count. It adds a bool to indicate that the device has been removed so that TIOCMIWAIT doesn't wait forever, and wakes up any tasks so that they can return -EIO. Signed-off-by: Simon Arlott <simon@fire.lp0.eu> Cc: stable <stable@vger.kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/serial')
-rw-r--r--drivers/usb/serial/ftdi_sio.c12
1 files changed, 7 insertions, 5 deletions
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
index c02e4602d90a..02e7f2d32d52 100644
--- a/drivers/usb/serial/ftdi_sio.c
+++ b/drivers/usb/serial/ftdi_sio.c
@@ -76,6 +76,7 @@ struct ftdi_private {
76 struct async_icount icount; 76 struct async_icount icount;
77 wait_queue_head_t delta_msr_wait; /* Used for TIOCMIWAIT */ 77 wait_queue_head_t delta_msr_wait; /* Used for TIOCMIWAIT */
78 char prev_status; /* Used for TIOCMIWAIT */ 78 char prev_status; /* Used for TIOCMIWAIT */
79 bool dev_gone; /* Used to abort TIOCMIWAIT */
79 char transmit_empty; /* If transmitter is empty or not */ 80 char transmit_empty; /* If transmitter is empty or not */
80 struct usb_serial_port *port; 81 struct usb_serial_port *port;
81 __u16 interface; /* FT2232C, FT2232H or FT4232H port interface 82 __u16 interface; /* FT2232C, FT2232H or FT4232H port interface
@@ -1681,6 +1682,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
1681 init_waitqueue_head(&priv->delta_msr_wait); 1682 init_waitqueue_head(&priv->delta_msr_wait);
1682 1683
1683 priv->flags = ASYNC_LOW_LATENCY; 1684 priv->flags = ASYNC_LOW_LATENCY;
1685 priv->dev_gone = false;
1684 1686
1685 if (quirk && quirk->port_probe) 1687 if (quirk && quirk->port_probe)
1686 quirk->port_probe(priv); 1688 quirk->port_probe(priv);
@@ -1839,6 +1841,9 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port)
1839 1841
1840 dbg("%s", __func__); 1842 dbg("%s", __func__);
1841 1843
1844 priv->dev_gone = true;
1845 wake_up_interruptible_all(&priv->delta_msr_wait);
1846
1842 remove_sysfs_attrs(port); 1847 remove_sysfs_attrs(port);
1843 1848
1844 kref_put(&priv->kref, ftdi_sio_priv_release); 1849 kref_put(&priv->kref, ftdi_sio_priv_release);
@@ -2397,15 +2402,12 @@ static int ftdi_ioctl(struct tty_struct *tty,
2397 */ 2402 */
2398 case TIOCMIWAIT: 2403 case TIOCMIWAIT:
2399 cprev = priv->icount; 2404 cprev = priv->icount;
2400 while (1) { 2405 while (!priv->dev_gone) {
2401 interruptible_sleep_on(&priv->delta_msr_wait); 2406 interruptible_sleep_on(&priv->delta_msr_wait);
2402 /* see if a signal did it */ 2407 /* see if a signal did it */
2403 if (signal_pending(current)) 2408 if (signal_pending(current))
2404 return -ERESTARTSYS; 2409 return -ERESTARTSYS;
2405 cnow = priv->icount; 2410 cnow = priv->icount;
2406 if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
2407 cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
2408 return -EIO; /* no change => error */
2409 if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || 2411 if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
2410 ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || 2412 ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
2411 ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || 2413 ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
@@ -2414,7 +2416,7 @@ static int ftdi_ioctl(struct tty_struct *tty,
2414 } 2416 }
2415 cprev = cnow; 2417 cprev = cnow;
2416 } 2418 }
2417 /* not reached */ 2419 return -EIO;
2418 break; 2420 break;
2419 case TIOCSERGETLSR: 2421 case TIOCSERGETLSR:
2420 return get_lsr_info(port, (struct serial_struct __user *)arg); 2422 return get_lsr_info(port, (struct serial_struct __user *)arg);