diff options
author | Thomas Abraham <thomas.abraham@linaro.org> | 2012-11-22 07:36:28 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-11-26 18:36:39 -0500 |
commit | c15c3747ee32b6969f3cfbc86dc94923e3742d0a (patch) | |
tree | 2c2f9b77abcaa8f116e6f87bf91208e6013d3fee /drivers/tty | |
parent | daa74a25cc7351a9fb01ba7611af5ef5df80ae4e (diff) |
serial: samsung: fix potential soft lockup during uart write
Certain tty line discipline implementations such slip and bluetooth hci invoke
the serial core uart_write() api in their write_wakeup callback. This leads to
a soft lockup with samsung serial driver since the uart port lock is taken in
the driver's interrupt handler and uart_write() attempts to take the same lock
again.
Fix this issue by releasing the uart port lock before the call to
uart_write_wakeup() in the tx handler. Also move the spin-lock/unlock sequence
from s3c64xx_serial_handle_irq() function into the tx and rx irq handlers so
that this change is applicable to s3c24xx platforms as well.
Reported-by: Kyungmin Park <kyungmin.park@samsung.com>
Reported-by: Hyeonkook Kim <hk619.kim@samsung.com>
Signed-off-by: Thomas Abraham <thomas.abraham@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/serial/samsung.c | 16 |
1 files changed, 12 insertions, 4 deletions
diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 82b48f60aa0c..9368c3e81cc8 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c | |||
@@ -223,8 +223,11 @@ s3c24xx_serial_rx_chars(int irq, void *dev_id) | |||
223 | struct uart_port *port = &ourport->port; | 223 | struct uart_port *port = &ourport->port; |
224 | struct tty_struct *tty = port->state->port.tty; | 224 | struct tty_struct *tty = port->state->port.tty; |
225 | unsigned int ufcon, ch, flag, ufstat, uerstat; | 225 | unsigned int ufcon, ch, flag, ufstat, uerstat; |
226 | unsigned long flags; | ||
226 | int max_count = 64; | 227 | int max_count = 64; |
227 | 228 | ||
229 | spin_lock_irqsave(&port->lock, flags); | ||
230 | |||
228 | while (max_count-- > 0) { | 231 | while (max_count-- > 0) { |
229 | ufcon = rd_regl(port, S3C2410_UFCON); | 232 | ufcon = rd_regl(port, S3C2410_UFCON); |
230 | ufstat = rd_regl(port, S3C2410_UFSTAT); | 233 | ufstat = rd_regl(port, S3C2410_UFSTAT); |
@@ -299,6 +302,7 @@ s3c24xx_serial_rx_chars(int irq, void *dev_id) | |||
299 | tty_flip_buffer_push(tty); | 302 | tty_flip_buffer_push(tty); |
300 | 303 | ||
301 | out: | 304 | out: |
305 | spin_unlock_irqrestore(&port->lock, flags); | ||
302 | return IRQ_HANDLED; | 306 | return IRQ_HANDLED; |
303 | } | 307 | } |
304 | 308 | ||
@@ -307,8 +311,11 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) | |||
307 | struct s3c24xx_uart_port *ourport = id; | 311 | struct s3c24xx_uart_port *ourport = id; |
308 | struct uart_port *port = &ourport->port; | 312 | struct uart_port *port = &ourport->port; |
309 | struct circ_buf *xmit = &port->state->xmit; | 313 | struct circ_buf *xmit = &port->state->xmit; |
314 | unsigned long flags; | ||
310 | int count = 256; | 315 | int count = 256; |
311 | 316 | ||
317 | spin_lock_irqsave(&port->lock, flags); | ||
318 | |||
312 | if (port->x_char) { | 319 | if (port->x_char) { |
313 | wr_regb(port, S3C2410_UTXH, port->x_char); | 320 | wr_regb(port, S3C2410_UTXH, port->x_char); |
314 | port->icount.tx++; | 321 | port->icount.tx++; |
@@ -336,13 +343,17 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) | |||
336 | port->icount.tx++; | 343 | port->icount.tx++; |
337 | } | 344 | } |
338 | 345 | ||
339 | if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) | 346 | if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { |
347 | spin_unlock(&port->lock); | ||
340 | uart_write_wakeup(port); | 348 | uart_write_wakeup(port); |
349 | spin_lock(&port->lock); | ||
350 | } | ||
341 | 351 | ||
342 | if (uart_circ_empty(xmit)) | 352 | if (uart_circ_empty(xmit)) |
343 | s3c24xx_serial_stop_tx(port); | 353 | s3c24xx_serial_stop_tx(port); |
344 | 354 | ||
345 | out: | 355 | out: |
356 | spin_unlock_irqrestore(&port->lock, flags); | ||
346 | return IRQ_HANDLED; | 357 | return IRQ_HANDLED; |
347 | } | 358 | } |
348 | 359 | ||
@@ -352,10 +363,8 @@ static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id) | |||
352 | struct s3c24xx_uart_port *ourport = id; | 363 | struct s3c24xx_uart_port *ourport = id; |
353 | struct uart_port *port = &ourport->port; | 364 | struct uart_port *port = &ourport->port; |
354 | unsigned int pend = rd_regl(port, S3C64XX_UINTP); | 365 | unsigned int pend = rd_regl(port, S3C64XX_UINTP); |
355 | unsigned long flags; | ||
356 | irqreturn_t ret = IRQ_HANDLED; | 366 | irqreturn_t ret = IRQ_HANDLED; |
357 | 367 | ||
358 | spin_lock_irqsave(&port->lock, flags); | ||
359 | if (pend & S3C64XX_UINTM_RXD_MSK) { | 368 | if (pend & S3C64XX_UINTM_RXD_MSK) { |
360 | ret = s3c24xx_serial_rx_chars(irq, id); | 369 | ret = s3c24xx_serial_rx_chars(irq, id); |
361 | wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK); | 370 | wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK); |
@@ -364,7 +373,6 @@ static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id) | |||
364 | ret = s3c24xx_serial_tx_chars(irq, id); | 373 | ret = s3c24xx_serial_tx_chars(irq, id); |
365 | wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK); | 374 | wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK); |
366 | } | 375 | } |
367 | spin_unlock_irqrestore(&port->lock, flags); | ||
368 | return ret; | 376 | return ret; |
369 | } | 377 | } |
370 | 378 | ||