Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc8548c7 authored by Ilpo Järvinen's avatar Ilpo Järvinen Committed by Greg Kroah-Hartman
Browse files

serial: 8250_dma: Fix DMA Rx rearm race



commit 57e9af7831dcf211c5c689c2a6f209f4abdf0bce upstream.

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: default avatarGilles BULOZ <gilles.buloz@kontron.com>
Tested-by: default avatarGilles BULOZ <gilles.buloz@kontron.com>
Fixes: 9ee4b83e ("serial: 8250: Add support for dmaengine")
Cc: stable@vger.kernel.org
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20230130114841.25749-3-ilpo.jarvinen@linux.intel.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 71d6b277
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -46,15 +46,23 @@ static void __dma_rx_complete(void *param)
	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);
}