diff options
author | Darren Hart <dvhart@linux.intel.com> | 2012-06-19 17:00:18 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-26 16:37:02 -0400 |
commit | 2588aba002d14e938c2f56d299ecf3e7ce1302a5 (patch) | |
tree | 872f6a087ce3615b5c8df9f75d255a4d55b713a1 /drivers/tty | |
parent | a7b12929be6cc55eab2dac3330fa9f5984e12dda (diff) |
pch_uart: Add eg20t_port lock field, avoid recursive spinlocks
pch_uart_interrupt() takes priv->port.lock which leads to two recursive
spinlock calls if low_latency==1 or CONFIG_PREEMPT_RT_FULL=y (one
otherwise):
pch_uart_interrupt
spin_lock_irqsave(priv->port.lock, flags)
case PCH_UART_IID_RDR_TO (data ready)
handle_rx_to
push_rx
tty_port_tty_get
spin_lock_irqsave(&port->lock, flags) <--- already hold this lock
...
tty_flip_buffer_push
...
flush_to_ldisc
spin_lock_irqsave(&tty->buf.lock)
spin_lock_irqsave(&tty->buf.lock)
disc->ops->receive_buf(tty, char_buf)
n_tty_receive_buf
tty->ops->flush_chars()
uart_flush_chars
uart_start
spin_lock_irqsave(&port->lock) <--- already hold this lock
Avoid this by using a dedicated lock to protect the eg20t_port structure
and IO access to its membase. This is more consistent with the 8250
driver. Ensure priv->lock is always take prior to priv->port.lock when
taken at the same time.
V2: Remove inadvertent whitespace change.
V3: Account for oops_in_progress for the private lock in
pch_console_write().
Note: Like the 8250 driver, if a printk is introduced anywhere inside
the pch_console_write() critical section, the kernel will hang
on a recursive spinlock on the private lock. The oops case is
handled by using a trylock in the oops_in_progress case.
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
CC: Tomoya MORINAGA <tomoya.rohm@gmail.com>
CC: Feng Tang <feng.tang@intel.com>
CC: Alexander Stein <alexander.stein@systec-electronic.com>
Acked-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/serial/pch_uart.c | 38 |
1 files changed, 26 insertions, 12 deletions
diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c index 4fdec6a6b758..d291518a58a4 100644 --- a/drivers/tty/serial/pch_uart.c +++ b/drivers/tty/serial/pch_uart.c | |||
@@ -253,6 +253,9 @@ struct eg20t_port { | |||
253 | dma_addr_t rx_buf_dma; | 253 | dma_addr_t rx_buf_dma; |
254 | 254 | ||
255 | struct dentry *debugfs; | 255 | struct dentry *debugfs; |
256 | |||
257 | /* protect the eg20t_port private structure and io access to membase */ | ||
258 | spinlock_t lock; | ||
256 | }; | 259 | }; |
257 | 260 | ||
258 | /** | 261 | /** |
@@ -1058,7 +1061,7 @@ static irqreturn_t pch_uart_interrupt(int irq, void *dev_id) | |||
1058 | int next = 1; | 1061 | int next = 1; |
1059 | u8 msr; | 1062 | u8 msr; |
1060 | 1063 | ||
1061 | spin_lock_irqsave(&priv->port.lock, flags); | 1064 | spin_lock_irqsave(&priv->lock, flags); |
1062 | handled = 0; | 1065 | handled = 0; |
1063 | while (next) { | 1066 | while (next) { |
1064 | iid = pch_uart_hal_get_iid(priv); | 1067 | iid = pch_uart_hal_get_iid(priv); |
@@ -1116,7 +1119,7 @@ static irqreturn_t pch_uart_interrupt(int irq, void *dev_id) | |||
1116 | handled |= (unsigned int)ret; | 1119 | handled |= (unsigned int)ret; |
1117 | } | 1120 | } |
1118 | 1121 | ||
1119 | spin_unlock_irqrestore(&priv->port.lock, flags); | 1122 | spin_unlock_irqrestore(&priv->lock, flags); |
1120 | return IRQ_RETVAL(handled); | 1123 | return IRQ_RETVAL(handled); |
1121 | } | 1124 | } |
1122 | 1125 | ||
@@ -1226,9 +1229,9 @@ static void pch_uart_break_ctl(struct uart_port *port, int ctl) | |||
1226 | unsigned long flags; | 1229 | unsigned long flags; |
1227 | 1230 | ||
1228 | priv = container_of(port, struct eg20t_port, port); | 1231 | priv = container_of(port, struct eg20t_port, port); |
1229 | spin_lock_irqsave(&port->lock, flags); | 1232 | spin_lock_irqsave(&priv->lock, flags); |
1230 | pch_uart_hal_set_break(priv, ctl); | 1233 | pch_uart_hal_set_break(priv, ctl); |
1231 | spin_unlock_irqrestore(&port->lock, flags); | 1234 | spin_unlock_irqrestore(&priv->lock, flags); |
1232 | } | 1235 | } |
1233 | 1236 | ||
1234 | /* Grab any interrupt resources and initialise any low level driver state. */ | 1237 | /* Grab any interrupt resources and initialise any low level driver state. */ |
@@ -1376,7 +1379,8 @@ static void pch_uart_set_termios(struct uart_port *port, | |||
1376 | 1379 | ||
1377 | baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); | 1380 | baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); |
1378 | 1381 | ||
1379 | spin_lock_irqsave(&port->lock, flags); | 1382 | spin_lock_irqsave(&priv->lock, flags); |
1383 | spin_lock(&port->lock); | ||
1380 | 1384 | ||
1381 | uart_update_timeout(port, termios->c_cflag, baud); | 1385 | uart_update_timeout(port, termios->c_cflag, baud); |
1382 | rtn = pch_uart_hal_set_line(priv, baud, parity, bits, stb); | 1386 | rtn = pch_uart_hal_set_line(priv, baud, parity, bits, stb); |
@@ -1389,7 +1393,8 @@ static void pch_uart_set_termios(struct uart_port *port, | |||
1389 | tty_termios_encode_baud_rate(termios, baud, baud); | 1393 | tty_termios_encode_baud_rate(termios, baud, baud); |
1390 | 1394 | ||
1391 | out: | 1395 | out: |
1392 | spin_unlock_irqrestore(&port->lock, flags); | 1396 | spin_unlock(&port->lock); |
1397 | spin_unlock_irqrestore(&priv->lock, flags); | ||
1393 | } | 1398 | } |
1394 | 1399 | ||
1395 | static const char *pch_uart_type(struct uart_port *port) | 1400 | static const char *pch_uart_type(struct uart_port *port) |
@@ -1538,8 +1543,9 @@ pch_console_write(struct console *co, const char *s, unsigned int count) | |||
1538 | { | 1543 | { |
1539 | struct eg20t_port *priv; | 1544 | struct eg20t_port *priv; |
1540 | unsigned long flags; | 1545 | unsigned long flags; |
1546 | int priv_locked = 1; | ||
1547 | int port_locked = 1; | ||
1541 | u8 ier; | 1548 | u8 ier; |
1542 | int locked = 1; | ||
1543 | 1549 | ||
1544 | priv = pch_uart_ports[co->index]; | 1550 | priv = pch_uart_ports[co->index]; |
1545 | 1551 | ||
@@ -1547,12 +1553,16 @@ pch_console_write(struct console *co, const char *s, unsigned int count) | |||
1547 | 1553 | ||
1548 | local_irq_save(flags); | 1554 | local_irq_save(flags); |
1549 | if (priv->port.sysrq) { | 1555 | if (priv->port.sysrq) { |
1550 | /* serial8250_handle_port() already took the lock */ | 1556 | spin_lock(&priv->lock); |
1551 | locked = 0; | 1557 | /* serial8250_handle_port() already took the port lock */ |
1558 | port_locked = 0; | ||
1552 | } else if (oops_in_progress) { | 1559 | } else if (oops_in_progress) { |
1553 | locked = spin_trylock(&priv->port.lock); | 1560 | priv_locked = spin_trylock(&priv->lock); |
1554 | } else | 1561 | port_locked = spin_trylock(&priv->port.lock); |
1562 | } else { | ||
1563 | spin_lock(&priv->lock); | ||
1555 | spin_lock(&priv->port.lock); | 1564 | spin_lock(&priv->port.lock); |
1565 | } | ||
1556 | 1566 | ||
1557 | /* | 1567 | /* |
1558 | * First save the IER then disable the interrupts | 1568 | * First save the IER then disable the interrupts |
@@ -1570,8 +1580,10 @@ pch_console_write(struct console *co, const char *s, unsigned int count) | |||
1570 | wait_for_xmitr(priv, BOTH_EMPTY); | 1580 | wait_for_xmitr(priv, BOTH_EMPTY); |
1571 | iowrite8(ier, priv->membase + UART_IER); | 1581 | iowrite8(ier, priv->membase + UART_IER); |
1572 | 1582 | ||
1573 | if (locked) | 1583 | if (port_locked) |
1574 | spin_unlock(&priv->port.lock); | 1584 | spin_unlock(&priv->port.lock); |
1585 | if (priv_locked) | ||
1586 | spin_unlock(&priv->lock); | ||
1575 | local_irq_restore(flags); | 1587 | local_irq_restore(flags); |
1576 | } | 1588 | } |
1577 | 1589 | ||
@@ -1669,6 +1681,8 @@ static struct eg20t_port *pch_uart_init_port(struct pci_dev *pdev, | |||
1669 | pci_enable_msi(pdev); | 1681 | pci_enable_msi(pdev); |
1670 | pci_set_master(pdev); | 1682 | pci_set_master(pdev); |
1671 | 1683 | ||
1684 | spin_lock_init(&priv->lock); | ||
1685 | |||
1672 | iobase = pci_resource_start(pdev, 0); | 1686 | iobase = pci_resource_start(pdev, 0); |
1673 | mapbase = pci_resource_start(pdev, 1); | 1687 | mapbase = pci_resource_start(pdev, 1); |
1674 | priv->mapbase = mapbase; | 1688 | priv->mapbase = mapbase; |