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: Gilles BULOZ <gilles.buloz@kontron.com>
Tested-by: Gilles BULOZ <gilles.buloz@kontron.com>
Fixes: 9ee4b83e51
("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>
This commit is contained in:
parent
71d6b277c4
commit
fc8548c75c
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user