summaryrefslogtreecommitdiff
path: root/drivers/tty
diff options
context:
space:
mode:
authorIlpo Järvinen <ilpo.jarvinen@linux.intel.com>2023-01-30 13:48:41 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2023-01-31 11:15:16 +0100
commit57e9af7831dcf211c5c689c2a6f209f4abdf0bce (patch)
tree79898d19654f8e471cb7f0856afdf141925a911c /drivers/tty
parent31352811e13dc2313f101b890fd4b1ce760b5fe7 (diff)
serial: 8250_dma: Fix DMA Rx rearm race
As DMA Rx can be completed from two places, it is possible that DMA Rx completes before DMA completion callback had a chance to complete it. Once the previous DMA Rx has been completed, a new one can be started on the next UART interrupt. The following race is possible (uart_unlock_and_check_sysrq_irqrestore() replaced with spin_unlock_irqrestore() for simplicity/clarity): CPU0 CPU1 dma_rx_complete() serial8250_handle_irq() spin_lock_irqsave(&port->lock) handle_rx_dma() serial8250_rx_dma_flush() __dma_rx_complete() dma->rx_running = 0 // Complete DMA Rx spin_unlock_irqrestore(&port->lock) serial8250_handle_irq() spin_lock_irqsave(&port->lock) handle_rx_dma() serial8250_rx_dma() dma->rx_running = 1 // Setup a new DMA Rx spin_unlock_irqrestore(&port->lock) spin_lock_irqsave(&port->lock) // sees dma->rx_running = 1 __dma_rx_complete() dma->rx_running = 0 // Incorrectly complete // running DMA Rx This race seems somewhat theoretical to occur for real but handle it correctly regardless. Check what is the DMA status before complething anything in __dma_rx_complete(). Reported-by: Gilles BULOZ <gilles.buloz@kontron.com> Tested-by: Gilles BULOZ <gilles.buloz@kontron.com> Fixes: 9ee4b83e51f7 ("serial: 8250: Add support for dmaengine") Cc: stable@vger.kernel.org Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Link: https://lore.kernel.org/r/20230130114841.25749-3-ilpo.jarvinen@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/serial/8250/8250_dma.c12
1 files changed, 10 insertions, 2 deletions
diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
index 5594883a96f8..7fa66501792d 100644
--- a/drivers/tty/serial/8250/8250_dma.c
+++ b/drivers/tty/serial/8250/8250_dma.c
@@ -43,15 +43,23 @@ static void __dma_rx_complete(struct uart_8250_port *p)
struct uart_8250_dma *dma = p->dma;
struct tty_port *tty_port = &p->port.state->port;
struct dma_tx_state state;
+ enum dma_status dma_status;
int count;
- dma->rx_running = 0;
- dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);
+ /*
+ * New DMA Rx can be started during the completion handler before it
+ * could acquire port's lock and it might still be ongoing. Don't to
+ * anything in such case.
+ */
+ dma_status = dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);
+ if (dma_status == DMA_IN_PROGRESS)
+ return;
count = dma->rx_size - state.residue;
tty_insert_flip_string(tty_port, dma->rx_buf, count);
p->port.icount.rx += count;
+ dma->rx_running = 0;
tty_flip_buffer_push(tty_port);
}