aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2013-02-14 15:01:06 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-02-14 15:17:20 -0500
commit677fe555cbfb188af58cce105f4dae9505e58c31 (patch)
tree08ddf8713af473b97ee5a1088788fcbf652a07f8 /drivers/tty
parent5488c753530b7b08437df6115a2c2c6156c2f0f6 (diff)
serial: imx: Fix recursive locking bug
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> Cc: stable <stable@vger.kernel.org> # 3.6+ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/serial/imx.c11
1 files changed, 9 insertions, 2 deletions
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index a220f77ceab6..adf76117a733 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -1212,8 +1212,14 @@ imx_console_write(struct console *co, const char *s, unsigned int count)
1212 struct imx_port_ucrs old_ucr; 1212 struct imx_port_ucrs old_ucr;
1213 unsigned int ucr1; 1213 unsigned int ucr1;
1214 unsigned long flags; 1214 unsigned long flags;
1215 int locked = 1;
1215 1216
1216 spin_lock_irqsave(&sport->port.lock, flags); 1217 if (sport->port.sysrq)
1218 locked = 0;
1219 else if (oops_in_progress)
1220 locked = spin_trylock_irqsave(&sport->port.lock, flags);
1221 else
1222 spin_lock_irqsave(&sport->port.lock, flags);
1217 1223
1218 /* 1224 /*
1219 * First, save UCR1/2/3 and then disable interrupts 1225 * First, save UCR1/2/3 and then disable interrupts
@@ -1240,7 +1246,8 @@ imx_console_write(struct console *co, const char *s, unsigned int count)
1240 1246
1241 imx_port_ucrs_restore(&sport->port, &old_ucr); 1247 imx_port_ucrs_restore(&sport->port, &old_ucr);
1242 1248
1243 spin_unlock_irqrestore(&sport->port.lock, flags); 1249 if (locked)
1250 spin_unlock_irqrestore(&sport->port.lock, flags);
1244} 1251}
1245 1252
1246/* 1253/*