aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Hunter <jonathanh@nvidia.com>2015-10-09 09:49:59 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-10-18 00:07:57 -0400
commit2a24bb28a315ea2579fbf13a99a69a10cf4c085e (patch)
treeb074103ae3391419236ae1a91beb4f604c1bdcaa
parent0abcc6df070687816b0ca0aefc3d64c62773063c (diff)
serial: tegra: Handle another RX race condition
Commit 853a699739fe ("serial: tegra: handle race condition on uart rx side") attempted to fix a race condition between the RX end of transmission interrupt and RX DMA completion callback. Despite this fix there is still another case where these two paths can race and result in duplicated data. The race condition is as follows: 1. DMA completion interrupt occurs and schedules tasklet to call DMA callback. 2. DMA callback for the UART driver starts to execute. This will copy the data from the DMA buffer and restart the DMA. This is done under uart port spinlock. 3. During the callback, UART interrupt is raised for end of receive. The UART ISR runs and waits to acquire port spinlock held by the DMA callback. 4. DMA callback gives up spinlock after copying the data, but before restarting DMA. 5. UART ISR acquires the spin lock and reads the same DMA buffer because DMA has not been restarted yet. The release of the spinlock during the DMA callback was introduced by commit 9b88748b362c ("tty: serial: tegra: drop uart_port->lock before calling tty_flip_buffer_push()") to fix a spinlock lock-up issue when calling tty_flip_buffer_push(). However, since then commit a9c3f68f3cd8 ("tty: Fix low_latency BUG") migrated tty_flip_buffer_push() to always use a workqueue, allowing tty_flip_buffer_push() to be called from within atomic sections. Therefore, we can remove the unlocking of the spinlock from the DMA callback and UART ISR and this will ensure that the race condition no longer occurs. Reported-by: Christopher Freeman <cfreeman@nvidia.com> Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/tty/serial/serial-tegra.c10
1 files changed, 2 insertions, 8 deletions
diff --git a/drivers/tty/serial/serial-tegra.c b/drivers/tty/serial/serial-tegra.c
index cf0133ae762d..38b49f447bd7 100644
--- a/drivers/tty/serial/serial-tegra.c
+++ b/drivers/tty/serial/serial-tegra.c
@@ -607,9 +607,7 @@ static void tegra_uart_rx_dma_complete(void *args)
607 607
608 tegra_uart_handle_rx_pio(tup, port); 608 tegra_uart_handle_rx_pio(tup, port);
609 if (tty) { 609 if (tty) {
610 spin_unlock_irqrestore(&u->lock, flags);
611 tty_flip_buffer_push(port); 610 tty_flip_buffer_push(port);
612 spin_lock_irqsave(&u->lock, flags);
613 tty_kref_put(tty); 611 tty_kref_put(tty);
614 } 612 }
615 tegra_uart_start_rx_dma(tup); 613 tegra_uart_start_rx_dma(tup);
@@ -622,13 +620,11 @@ done:
622 spin_unlock_irqrestore(&u->lock, flags); 620 spin_unlock_irqrestore(&u->lock, flags);
623} 621}
624 622
625static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup, 623static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup)
626 unsigned long *flags)
627{ 624{
628 struct dma_tx_state state; 625 struct dma_tx_state state;
629 struct tty_struct *tty = tty_port_tty_get(&tup->uport.state->port); 626 struct tty_struct *tty = tty_port_tty_get(&tup->uport.state->port);
630 struct tty_port *port = &tup->uport.state->port; 627 struct tty_port *port = &tup->uport.state->port;
631 struct uart_port *u = &tup->uport;
632 unsigned int count; 628 unsigned int count;
633 629
634 /* Deactivate flow control to stop sender */ 630 /* Deactivate flow control to stop sender */
@@ -645,9 +641,7 @@ static void tegra_uart_handle_rx_dma(struct tegra_uart_port *tup,
645 641
646 tegra_uart_handle_rx_pio(tup, port); 642 tegra_uart_handle_rx_pio(tup, port);
647 if (tty) { 643 if (tty) {
648 spin_unlock_irqrestore(&u->lock, *flags);
649 tty_flip_buffer_push(port); 644 tty_flip_buffer_push(port);
650 spin_lock_irqsave(&u->lock, *flags);
651 tty_kref_put(tty); 645 tty_kref_put(tty);
652 } 646 }
653 tegra_uart_start_rx_dma(tup); 647 tegra_uart_start_rx_dma(tup);
@@ -714,7 +708,7 @@ static irqreturn_t tegra_uart_isr(int irq, void *data)
714 iir = tegra_uart_read(tup, UART_IIR); 708 iir = tegra_uart_read(tup, UART_IIR);
715 if (iir & UART_IIR_NO_INT) { 709 if (iir & UART_IIR_NO_INT) {
716 if (is_rx_int) { 710 if (is_rx_int) {
717 tegra_uart_handle_rx_dma(tup, &flags); 711 tegra_uart_handle_rx_dma(tup);
718 if (tup->rx_in_progress) { 712 if (tup->rx_in_progress) {
719 ier = tup->ier_shadow; 713 ier = tup->ier_shadow;
720 ier |= (UART_IER_RLSI | UART_IER_RTOIE | 714 ier |= (UART_IER_RLSI | UART_IER_RTOIE |