tty: serial: msm_serial: RX SW/FIFO mode fallback
authorLoic Poulain <loic.poulain@linaro.org>
Fri, 10 Jan 2020 08:58:04 +0000 (09:58 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 14 Jan 2020 13:07:51 +0000 (14:07 +0100)
During db410c stress test and when the system is low on memory,
the UART/console becomes unresponsive and never recover back.
This has been narrowed down to the msm_start_rx_dma which does
not manage error cases correctly (e.g. dma mapping failure),
indeed, when an error happens, dma transfer is simply discarded
and so never completed, leading to unconfigured RX path.

This patch fixes this issue by switching to SW/FIFO mode in case
of DMA issue. This mainly consists in resetting the receiver to
apply RX BAM/DMA disabling change and re-enabling the RX level
and stale interrupts (previously disabled for DMA transfers).

The DMA will be re-enabled once memory is available since the
SW/FIFO read function (msm_handle_rx_dm) retries to start dma
on completion.

Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Link: https://lore.kernel.org/r/1578646684-17379-1-git-send-email-loic.poulain@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/msm_serial.c

index 19e897dad5fed745697c9049b936e63cea2dcb8d..60a9c53fa7cbf9c03e9277bb5e580596170ea9e6 100644 (file)
@@ -606,7 +606,7 @@ static void msm_start_rx_dma(struct msm_port *msm_port)
                                   UARTDM_RX_SIZE, dma->dir);
        ret = dma_mapping_error(uart->dev, dma->phys);
        if (ret)
-               return;
+               goto sw_mode;
 
        dma->desc = dmaengine_prep_slave_single(dma->chan, dma->phys,
                                                UARTDM_RX_SIZE, DMA_DEV_TO_MEM,
@@ -657,6 +657,22 @@ static void msm_start_rx_dma(struct msm_port *msm_port)
        return;
 unmap:
        dma_unmap_single(uart->dev, dma->phys, UARTDM_RX_SIZE, dma->dir);
+
+sw_mode:
+       /*
+        * Switch from DMA to SW/FIFO mode. After clearing Rx BAM (UARTDM_DMEN),
+        * receiver must be reset.
+        */
+       msm_write(uart, UART_CR_CMD_RESET_RX, UART_CR);
+       msm_write(uart, UART_CR_RX_ENABLE, UART_CR);
+
+       msm_write(uart, UART_CR_CMD_RESET_STALE_INT, UART_CR);
+       msm_write(uart, 0xFFFFFF, UARTDM_DMRX);
+       msm_write(uart, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR);
+
+       /* Re-enable RX interrupts */
+       msm_port->imr |= (UART_IMR_RXLEV | UART_IMR_RXSTALE);
+       msm_write(uart, msm_port->imr, UART_IMR);
 }
 
 static void msm_stop_rx(struct uart_port *port)