| From 57e9af7831dcf211c5c689c2a6f209f4abdf0bce Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= <ilpo.jarvinen@linux.intel.com> |
| Date: Mon, 30 Jan 2023 13:48:41 +0200 |
| Subject: [PATCH] serial: 8250_dma: Fix DMA Rx rearm race |
| Mime-version: 1.0 |
| Content-type: text/plain; charset=UTF-8 |
| Content-transfer-encoding: 8bit |
| Git-commit: 57e9af7831dcf211c5c689c2a6f209f4abdf0bce |
| Patch-mainline: v6.2-rc7 |
| References: git-fixes |
| |
| 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> |
| Acked-by: Takashi Iwai <tiwai@suse.de> |
| |
| |
| drivers/tty/serial/8250/8250_dma.c | 12 ++++++++++-- |
| 1 file 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 |
| |
| |
| @@ -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); |
| } |
| -- |
| 2.35.3 |
| |