]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
3.8-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 23 Feb 2013 01:18:09 +0000 (17:18 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 23 Feb 2013 01:18:09 +0000 (17:18 -0800)
added patches:
serial_core-fix-type-definition-for-port_brcm_trumanage.patch
serial-imx-fix-recursive-locking-bug.patch
tty-prevent-deadlock-in-n_gsm-driver.patch
tty-set_termios-set_termiox-should-not-return-eintr.patch
usb-serial-fix-null-pointer-dereferences-on-disconnect.patch

queue-3.8/serial-imx-fix-recursive-locking-bug.patch [new file with mode: 0644]
queue-3.8/serial_core-fix-type-definition-for-port_brcm_trumanage.patch [new file with mode: 0644]
queue-3.8/series
queue-3.8/tty-prevent-deadlock-in-n_gsm-driver.patch [new file with mode: 0644]
queue-3.8/tty-set_termios-set_termiox-should-not-return-eintr.patch [new file with mode: 0644]
queue-3.8/usb-serial-fix-null-pointer-dereferences-on-disconnect.patch [new file with mode: 0644]

diff --git a/queue-3.8/serial-imx-fix-recursive-locking-bug.patch b/queue-3.8/serial-imx-fix-recursive-locking-bug.patch
new file mode 100644 (file)
index 0000000..326c5bb
--- /dev/null
@@ -0,0 +1,118 @@
+From 677fe555cbfb188af58cce105f4dae9505e58c31 Mon Sep 17 00:00:00 2001
+From: Thomas Gleixner <tglx@linutronix.de>
+Date: Thu, 14 Feb 2013 21:01:06 +0100
+Subject: serial: imx: Fix recursive locking bug
+
+From: Thomas Gleixner <tglx@linutronix.de>
+
+commit 677fe555cbfb188af58cce105f4dae9505e58c31 upstream.
+
+commit 9ec1882df2 (tty: serial: imx: console write routing is unsafe
+on SMP) introduced a recursive locking bug in imx_console_write().
+
+The callchain is:
+
+imx_rxint()
+  spin_lock_irqsave(&sport->port.lock,flags);
+  ...
+  uart_handle_sysrq_char();
+    sysrq_function();
+      printk();
+        imx_console_write();
+          spin_lock_irqsave(&sport->port.lock,flags); <--- DEAD
+
+The bad news is that the kernel debugging facilities can dectect the
+problem, but the printks never surface on the serial console for
+obvious reasons.
+
+There is a similar issue with oops_in_progress. If the kernel crashes
+we really don't want to be stuck on the lock and unable to tell what
+happened.
+
+In general most UP originated drivers miss these checks and nobody
+ever notices because CONFIG_PROVE_LOCKING seems to be still ignored by
+a large number of developers.
+
+The solution is to avoid locking in the sysrq case and trylock in the
+oops_in_progress case.
+
+This scheme is used in other drivers as well and it would be nice if
+we could move this to a common place, so the usual copy/paste/modify
+bugs can be avoided.
+
+Now there is another issue with this scheme:
+
+CPU0                    CPU1
+printk()
+                        rxint()
+                          sysrq_detection() -> sets port->sysrq
+                        return from interrupt
+  console_write()
+     if (port->sysrq)
+       avoid locking
+
+port->sysrq is reset with the next receive character. So as long as
+the port->sysrq is not reset and this can take an endless amount of
+time if after the break no futher receive character follows, all
+console writes happen unlocked.
+
+While the current writer is protected against other console writers by
+the console sem, it's unprotected against open/close or other
+operations which fiddle with the port. That's what the above mentioned
+commit tried to solve.
+
+That's an issue in all drivers which use that scheme and unfortunately
+there is no easy workaround. The only solution is to have a separate
+indicator port->sysrq_cpu. uart_handle_sysrq_char() then sets it to
+smp_processor_id() before calling into handle_sysrq() and resets it to
+-1 after that. Then change the locking check to:
+
+     if (port->sysrq_cpu == smp_processor_id())
+        locked = 0;
+     else if (oops_in_progress)
+         locked = spin_trylock_irqsave(port->lock, flags);
+     else
+        spin_lock_irqsave(port->lock, flags);
+
+That would force all other cpus into the spin_lock path. Problem
+solved, but that's way beyond the scope of this fix and really wants
+to be implemented in a common function which calls the uart specific
+write function to avoid another gazillion of hard to debug
+copy/paste/modify bugs.
+
+Reported-and-tested-by: Tim Sander <tim@krieglstein.org>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/tty/serial/imx.c |   11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+--- a/drivers/tty/serial/imx.c
++++ b/drivers/tty/serial/imx.c
+@@ -1213,8 +1213,14 @@ imx_console_write(struct console *co, co
+       struct imx_port_ucrs old_ucr;
+       unsigned int ucr1;
+       unsigned long flags;
++      int locked = 1;
+-      spin_lock_irqsave(&sport->port.lock, flags);
++      if (sport->port.sysrq)
++              locked = 0;
++      else if (oops_in_progress)
++              locked = spin_trylock_irqsave(&sport->port.lock, flags);
++      else
++              spin_lock_irqsave(&sport->port.lock, flags);
+       /*
+        *      First, save UCR1/2/3 and then disable interrupts
+@@ -1241,7 +1247,8 @@ imx_console_write(struct console *co, co
+       imx_port_ucrs_restore(&sport->port, &old_ucr);
+-      spin_unlock_irqrestore(&sport->port.lock, flags);
++      if (locked)
++              spin_unlock_irqrestore(&sport->port.lock, flags);
+ }
+ /*
diff --git a/queue-3.8/serial_core-fix-type-definition-for-port_brcm_trumanage.patch b/queue-3.8/serial_core-fix-type-definition-for-port_brcm_trumanage.patch
new file mode 100644 (file)
index 0000000..10d44d1
--- /dev/null
@@ -0,0 +1,31 @@
+From 85f024401bf80746ae08b7fd5809a9b16accf0b1 Mon Sep 17 00:00:00 2001
+From: Michael Chan <mchan@broadcom.com>
+Date: Tue, 29 Jan 2013 17:54:44 -0800
+Subject: serial_core: Fix type definition for PORT_BRCM_TRUMANAGE.
+
+From: Michael Chan <mchan@broadcom.com>
+
+commit 85f024401bf80746ae08b7fd5809a9b16accf0b1 upstream.
+
+It was mistakenly defined to be 24 instead of the next higher number 25.
+
+Reported-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
+Cc: Stephen Hurd <shurd@broadcom.com>
+Signed-off-by: Michael Chan <mchan@broadcom.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ include/uapi/linux/serial_core.h |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/uapi/linux/serial_core.h
++++ b/include/uapi/linux/serial_core.h
+@@ -50,7 +50,7 @@
+ #define PORT_LPC3220  22      /* NXP LPC32xx SoC "Standard" UART */
+ #define PORT_8250_CIR 23      /* CIR infrared port, has its own driver */
+ #define PORT_XR17V35X 24      /* Exar XR17V35x UARTs */
+-#define PORT_BRCM_TRUMANAGE   24
++#define PORT_BRCM_TRUMANAGE   25
+ #define PORT_MAX_8250 25      /* max port ID */
+ /*
index a1587e63bf526837a1dd6617b53cb0f746e15c6c..b2772513b1597eb2c5fa42db65c6e40b9db01a5b 100644 (file)
@@ -12,3 +12,8 @@ workqueue-un-gpl-function-delayed_work_timer_fn.patch
 alsa-ali5451-remove-irq-enabling-in-pointer-callback.patch
 alsa-rme32.c-irq-enabling-after-spin_lock_irq.patch
 alsa-aloop-fix-oops-while-pm-resume.patch
+tty-prevent-deadlock-in-n_gsm-driver.patch
+tty-set_termios-set_termiox-should-not-return-eintr.patch
+usb-serial-fix-null-pointer-dereferences-on-disconnect.patch
+serial-imx-fix-recursive-locking-bug.patch
+serial_core-fix-type-definition-for-port_brcm_trumanage.patch
diff --git a/queue-3.8/tty-prevent-deadlock-in-n_gsm-driver.patch b/queue-3.8/tty-prevent-deadlock-in-n_gsm-driver.patch
new file mode 100644 (file)
index 0000000..8a30f3f
--- /dev/null
@@ -0,0 +1,195 @@
+From 4d9b109060f690f5c835130ff54165ae157b3087 Mon Sep 17 00:00:00 2001
+From: Dirkjan Bussink <d.bussink@gmail.com>
+Date: Wed, 30 Jan 2013 11:44:50 +0100
+Subject: tty: Prevent deadlock in n_gsm driver
+
+From: Dirkjan Bussink <d.bussink@gmail.com>
+
+commit 4d9b109060f690f5c835130ff54165ae157b3087 upstream.
+
+This change fixes a deadlock when the multiplexer is closed while there
+are still client side ports open.
+
+When the multiplexer is closed and there are active tty's it tries to
+close them with tty_vhangup. This has a problem though, because
+tty_vhangup needs the tty_lock. This patch changes it to unlock the
+tty_lock before attempting the hangup and relocks afterwards. The
+additional call to tty_port_tty_set is needed because otherwise the
+port stays active because of the reference counter.
+
+This change also exposed another problem that other code paths don't
+expect that the multiplexer could have been closed. This patch also adds
+checks for these cases in the gsmtty_ class of function that could be
+called.
+
+The documentation explicitly states that "first close all virtual ports
+before closing the physical port" but we've found this to not always
+reality in our field situations. The GPRS / UTMS modem sometimes crashes
+and needs a power cycle in that case which means cleanly shutting down
+everything is not always possible. This change makes it much more robust
+for our situation where at least the system is recoverable with this patch
+and doesn't hang in a deadlock situation inside the kernel.
+
+The patch is against the long term support kernel (3.4.27) and should
+apply cleanly to more recent branches. Tested with a Telit GE864-QUADV2
+and Telit HE910 modem.
+
+Signed-off-by: Dirkjan Bussink <dirkjan.bussink@nedap.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/tty/n_gsm.c |   42 +++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 41 insertions(+), 1 deletion(-)
+
+--- a/drivers/tty/n_gsm.c
++++ b/drivers/tty/n_gsm.c
+@@ -1689,6 +1689,8 @@ static inline void dlci_put(struct gsm_d
+       tty_port_put(&dlci->port);
+ }
++static void gsm_destroy_network(struct gsm_dlci *dlci);
++
+ /**
+  *    gsm_dlci_release                -       release DLCI
+  *    @dlci: DLCI to destroy
+@@ -1702,9 +1704,19 @@ static void gsm_dlci_release(struct gsm_
+ {
+       struct tty_struct *tty = tty_port_tty_get(&dlci->port);
+       if (tty) {
++              mutex_lock(&dlci->mutex);
++              gsm_destroy_network(dlci);
++              mutex_unlock(&dlci->mutex);
++
++              /* tty_vhangup needs the tty_lock, so unlock and
++                 relock after doing the hangup. */
++              tty_unlock(tty);
+               tty_vhangup(tty);
++              tty_lock(tty);
++              tty_port_tty_set(&dlci->port, NULL);
+               tty_kref_put(tty);
+       }
++      dlci->state = DLCI_CLOSED;
+       dlci_put(dlci);
+ }
+@@ -2947,6 +2959,8 @@ static void gsmtty_close(struct tty_stru
+       if (dlci == NULL)
+               return;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       mutex_lock(&dlci->mutex);
+       gsm_destroy_network(dlci);
+       mutex_unlock(&dlci->mutex);
+@@ -2965,6 +2979,8 @@ out:
+ static void gsmtty_hangup(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       tty_port_hangup(&dlci->port);
+       gsm_dlci_begin_close(dlci);
+ }
+@@ -2972,9 +2988,12 @@ static void gsmtty_hangup(struct tty_str
+ static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf,
+                                                                   int len)
+ {
++      int sent;
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       /* Stuff the bytes into the fifo queue */
+-      int sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock);
++      sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock);
+       /* Need to kick the channel */
+       gsm_dlci_data_kick(dlci);
+       return sent;
+@@ -2983,18 +3002,24 @@ static int gsmtty_write(struct tty_struc
+ static int gsmtty_write_room(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       return TX_SIZE - kfifo_len(dlci->fifo);
+ }
+ static int gsmtty_chars_in_buffer(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       return kfifo_len(dlci->fifo);
+ }
+ static void gsmtty_flush_buffer(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       /* Caution needed: If we implement reliable transport classes
+          then the data being transmitted can't simply be junked once
+          it has first hit the stack. Until then we can just blow it
+@@ -3013,6 +3038,8 @@ static void gsmtty_wait_until_sent(struc
+ static int gsmtty_tiocmget(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       return dlci->modem_rx;
+ }
+@@ -3022,6 +3049,8 @@ static int gsmtty_tiocmset(struct tty_st
+       struct gsm_dlci *dlci = tty->driver_data;
+       unsigned int modem_tx = dlci->modem_tx;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       modem_tx &= ~clear;
+       modem_tx |= set;
+@@ -3040,6 +3069,8 @@ static int gsmtty_ioctl(struct tty_struc
+       struct gsm_netconfig nc;
+       int index;
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       switch (cmd) {
+       case GSMIOC_ENABLE_NET:
+               if (copy_from_user(&nc, (void __user *)arg, sizeof(nc)))
+@@ -3066,6 +3097,9 @@ static int gsmtty_ioctl(struct tty_struc
+ static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old)
+ {
++      struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       /* For the moment its fixed. In actual fact the speed information
+          for the virtual channel can be propogated in both directions by
+          the RPN control message. This however rapidly gets nasty as we
+@@ -3077,6 +3111,8 @@ static void gsmtty_set_termios(struct tt
+ static void gsmtty_throttle(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       if (tty->termios.c_cflag & CRTSCTS)
+               dlci->modem_tx &= ~TIOCM_DTR;
+       dlci->throttled = 1;
+@@ -3087,6 +3123,8 @@ static void gsmtty_throttle(struct tty_s
+ static void gsmtty_unthrottle(struct tty_struct *tty)
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
++      if (dlci->state == DLCI_CLOSED)
++              return;
+       if (tty->termios.c_cflag & CRTSCTS)
+               dlci->modem_tx |= TIOCM_DTR;
+       dlci->throttled = 0;
+@@ -3098,6 +3136,8 @@ static int gsmtty_break_ctl(struct tty_s
+ {
+       struct gsm_dlci *dlci = tty->driver_data;
+       int encode = 0; /* Off */
++      if (dlci->state == DLCI_CLOSED)
++              return -EINVAL;
+       if (state == -1)        /* "On indefinitely" - we can't encode this
+                                   properly */
diff --git a/queue-3.8/tty-set_termios-set_termiox-should-not-return-eintr.patch b/queue-3.8/tty-set_termios-set_termiox-should-not-return-eintr.patch
new file mode 100644 (file)
index 0000000..aaeef66
--- /dev/null
@@ -0,0 +1,77 @@
+From 183d95cdd834381c594d3aa801c1f9f9c0c54fa9 Mon Sep 17 00:00:00 2001
+From: Oleg Nesterov <oleg@redhat.com>
+Date: Tue, 29 Jan 2013 20:07:41 +0100
+Subject: tty: set_termios/set_termiox should not return -EINTR
+
+From: Oleg Nesterov <oleg@redhat.com>
+
+commit 183d95cdd834381c594d3aa801c1f9f9c0c54fa9 upstream.
+
+See https://bugzilla.redhat.com/show_bug.cgi?id=904907
+read command causes bash to abort with double free or corruption (out).
+
+A simple test-case from Roman:
+
+       // Compile the reproducer and send sigchld ti that process.
+       // EINTR occurs even if SA_RESTART flag is set.
+
+       void handler(int sig)
+       {
+       }
+
+       main()
+       {
+         struct sigaction act;
+         act.sa_handler = handler;
+         act.sa_flags = SA_RESTART;
+         sigaction (SIGCHLD, &act, 0);
+         struct termio ttp;
+         ioctl(0, TCGETA, &ttp);
+         while(1)
+         {
+           if (ioctl(0, TCSETAW, ttp) < 0)
+             {
+               if (errno == EINTR)
+               {
+                 fprintf(stderr, "BUG!"); return(1);
+               }
+             }
+         }
+       }
+
+Change set_termios/set_termiox to return -ERESTARTSYS to fix this
+particular problem.
+
+I didn't dare to change other EINTR's in drivers/tty/, but they look
+equally wrong.
+
+Reported-by: Roman Rakus <rrakus@redhat.com>
+Reported-by: Lingzhu Xiang <lxiang@redhat.com>
+Signed-off-by: Oleg Nesterov <oleg@redhat.com>
+Cc: Jiri Slaby <jslaby@suse.cz>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/tty/tty_ioctl.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/tty/tty_ioctl.c
++++ b/drivers/tty/tty_ioctl.c
+@@ -617,7 +617,7 @@ static int set_termios(struct tty_struct
+       if (opt & TERMIOS_WAIT) {
+               tty_wait_until_sent(tty, 0);
+               if (signal_pending(current))
+-                      return -EINTR;
++                      return -ERESTARTSYS;
+       }
+       tty_set_termios(tty, &tmp_termios);
+@@ -684,7 +684,7 @@ static int set_termiox(struct tty_struct
+       if (opt & TERMIOS_WAIT) {
+               tty_wait_until_sent(tty, 0);
+               if (signal_pending(current))
+-                      return -EINTR;
++                      return -ERESTARTSYS;
+       }
+       mutex_lock(&tty->termios_mutex);
diff --git a/queue-3.8/usb-serial-fix-null-pointer-dereferences-on-disconnect.patch b/queue-3.8/usb-serial-fix-null-pointer-dereferences-on-disconnect.patch
new file mode 100644 (file)
index 0000000..4f93ab2
--- /dev/null
@@ -0,0 +1,238 @@
+From b2ca699076573c94fee9a73cb0d8645383b602a0 Mon Sep 17 00:00:00 2001
+From: Johan Hovold <jhovold@gmail.com>
+Date: Wed, 13 Feb 2013 17:53:28 +0100
+Subject: USB: serial: fix null-pointer dereferences on disconnect
+
+From: Johan Hovold <jhovold@gmail.com>
+
+commit b2ca699076573c94fee9a73cb0d8645383b602a0 upstream.
+
+Make sure serial-driver dtr_rts is called with disc_mutex held after
+checking the disconnected flag.
+
+Due to a bug in the tty layer, dtr_rts may get called after a device has
+been disconnected and the tty-device unregistered. Some drivers have had
+individual checks for disconnect to make sure the disconnected interface
+was not accessed, but this should really be handled in usb-serial core
+(at least until the long-standing tty-bug has been fixed).
+
+Note that the problem has been made more acute with commit 0998d0631001
+("device-core: Ensure drvdata = NULL when no driver is bound") as the
+port data is now also NULL when dtr_rts is called resulting in further
+oopses.
+
+Reported-by: Chris Ruehl <chris.ruehl@gtsys.com.hk>
+Signed-off-by: Johan Hovold <jhovold@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/usb/serial/ftdi_sio.c   |   20 +++++++++-----------
+ drivers/usb/serial/mct_u232.c   |   22 +++++++++-------------
+ drivers/usb/serial/quatech2.c   |   18 ++++++++----------
+ drivers/usb/serial/sierra.c     |    8 +-------
+ drivers/usb/serial/ssu100.c     |   19 ++++++++-----------
+ drivers/usb/serial/usb-serial.c |   14 ++++++++++++--
+ drivers/usb/serial/usb_wwan.c   |    8 +++-----
+ 7 files changed, 50 insertions(+), 59 deletions(-)
+
+--- a/drivers/usb/serial/ftdi_sio.c
++++ b/drivers/usb/serial/ftdi_sio.c
+@@ -1886,24 +1886,22 @@ static void ftdi_dtr_rts(struct usb_seri
+ {
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+-      mutex_lock(&port->serial->disc_mutex);
+-      if (!port->serial->disconnected) {
+-              /* Disable flow control */
+-              if (!on && usb_control_msg(port->serial->dev,
++      /* Disable flow control */
++      if (!on) {
++              if (usb_control_msg(port->serial->dev,
+                           usb_sndctrlpipe(port->serial->dev, 0),
+                           FTDI_SIO_SET_FLOW_CTRL_REQUEST,
+                           FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
+                           0, priv->interface, NULL, 0,
+                           WDR_TIMEOUT) < 0) {
+-                          dev_err(&port->dev, "error from flowcontrol urb\n");
++                      dev_err(&port->dev, "error from flowcontrol urb\n");
+               }
+-              /* drop RTS and DTR */
+-              if (on)
+-                      set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+-              else
+-                      clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+       }
+-      mutex_unlock(&port->serial->disc_mutex);
++      /* drop RTS and DTR */
++      if (on)
++              set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
++      else
++              clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ }
+ /*
+--- a/drivers/usb/serial/mct_u232.c
++++ b/drivers/usb/serial/mct_u232.c
+@@ -499,19 +499,15 @@ static void mct_u232_dtr_rts(struct usb_
+       unsigned int control_state;
+       struct mct_u232_private *priv = usb_get_serial_port_data(port);
+-      mutex_lock(&port->serial->disc_mutex);
+-      if (!port->serial->disconnected) {
+-              /* drop DTR and RTS */
+-              spin_lock_irq(&priv->lock);
+-              if (on)
+-                      priv->control_state |= TIOCM_DTR | TIOCM_RTS;
+-              else
+-                      priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+-              control_state = priv->control_state;
+-              spin_unlock_irq(&priv->lock);
+-              mct_u232_set_modem_ctrl(port, control_state);
+-      }
+-      mutex_unlock(&port->serial->disc_mutex);
++      spin_lock_irq(&priv->lock);
++      if (on)
++              priv->control_state |= TIOCM_DTR | TIOCM_RTS;
++      else
++              priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
++      control_state = priv->control_state;
++      spin_unlock_irq(&priv->lock);
++
++      mct_u232_set_modem_ctrl(port, control_state);
+ }
+ static void mct_u232_close(struct usb_serial_port *port)
+--- a/drivers/usb/serial/quatech2.c
++++ b/drivers/usb/serial/quatech2.c
+@@ -945,19 +945,17 @@ static void qt2_dtr_rts(struct usb_seria
+       struct usb_device *dev = port->serial->dev;
+       struct qt2_port_private *port_priv = usb_get_serial_port_data(port);
+-      mutex_lock(&port->serial->disc_mutex);
+-      if (!port->serial->disconnected) {
+-              /* Disable flow control */
+-              if (!on && qt2_setregister(dev, port_priv->device_port,
++      /* Disable flow control */
++      if (!on) {
++              if (qt2_setregister(dev, port_priv->device_port,
+                                          UART_MCR, 0) < 0)
+                       dev_warn(&port->dev, "error from flowcontrol urb\n");
+-              /* drop RTS and DTR */
+-              if (on)
+-                      update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0);
+-              else
+-                      update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS);
+       }
+-      mutex_unlock(&port->serial->disc_mutex);
++      /* drop RTS and DTR */
++      if (on)
++              update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0);
++      else
++              update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS);
+ }
+ static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch)
+--- a/drivers/usb/serial/sierra.c
++++ b/drivers/usb/serial/sierra.c
+@@ -861,19 +861,13 @@ static int sierra_open(struct tty_struct
+ static void sierra_dtr_rts(struct usb_serial_port *port, int on)
+ {
+-      struct usb_serial *serial = port->serial;
+       struct sierra_port_private *portdata;
+       portdata = usb_get_serial_port_data(port);
+       portdata->rts_state = on;
+       portdata->dtr_state = on;
+-      if (serial->dev) {
+-              mutex_lock(&serial->disc_mutex);
+-              if (!serial->disconnected)
+-                      sierra_send_setup(port);
+-              mutex_unlock(&serial->disc_mutex);
+-      }
++      sierra_send_setup(port);
+ }
+ static int sierra_startup(struct usb_serial *serial)
+--- a/drivers/usb/serial/ssu100.c
++++ b/drivers/usb/serial/ssu100.c
+@@ -506,19 +506,16 @@ static void ssu100_dtr_rts(struct usb_se
+ {
+       struct usb_device *dev = port->serial->dev;
+-      mutex_lock(&port->serial->disc_mutex);
+-      if (!port->serial->disconnected) {
+-              /* Disable flow control */
+-              if (!on &&
+-                  ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
++      /* Disable flow control */
++      if (!on) {
++              if (ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
+                       dev_err(&port->dev, "error from flowcontrol urb\n");
+-              /* drop RTS and DTR */
+-              if (on)
+-                      set_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+-              else
+-                      clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+       }
+-      mutex_unlock(&port->serial->disc_mutex);
++      /* drop RTS and DTR */
++      if (on)
++              set_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
++      else
++              clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+ }
+ static void ssu100_update_msr(struct usb_serial_port *port, u8 msr)
+--- a/drivers/usb/serial/usb-serial.c
++++ b/drivers/usb/serial/usb-serial.c
+@@ -688,10 +688,20 @@ static int serial_carrier_raised(struct
+ static void serial_dtr_rts(struct tty_port *port, int on)
+ {
+       struct usb_serial_port *p = container_of(port, struct usb_serial_port, port);
+-      struct usb_serial_driver *drv = p->serial->type;
++      struct usb_serial *serial = p->serial;
++      struct usb_serial_driver *drv = serial->type;
+-      if (drv->dtr_rts)
++      if (!drv->dtr_rts)
++              return;
++      /*
++       * Work-around bug in the tty-layer which can result in dtr_rts
++       * being called after a disconnect (and tty_unregister_device
++       * has returned). Remove once bug has been squashed.
++       */
++      mutex_lock(&serial->disc_mutex);
++      if (!serial->disconnected)
+               drv->dtr_rts(p, on);
++      mutex_unlock(&serial->disc_mutex);
+ }
+ static const struct tty_port_operations serial_port_ops = {
+--- a/drivers/usb/serial/usb_wwan.c
++++ b/drivers/usb/serial/usb_wwan.c
+@@ -38,7 +38,6 @@
+ void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+ {
+-      struct usb_serial *serial = port->serial;
+       struct usb_wwan_port_private *portdata;
+       struct usb_wwan_intf_private *intfdata;
+@@ -48,12 +47,11 @@ void usb_wwan_dtr_rts(struct usb_serial_
+               return;
+       portdata = usb_get_serial_port_data(port);
+-      mutex_lock(&serial->disc_mutex);
++      /* FIXME: locking */
+       portdata->rts_state = on;
+       portdata->dtr_state = on;
+-      if (serial->dev)
+-              intfdata->send_setup(port);
+-      mutex_unlock(&serial->disc_mutex);
++
++      intfdata->send_setup(port);
+ }
+ EXPORT_SYMBOL(usb_wwan_dtr_rts);