Skip to content

Commit 947cc4e

Browse files
jhovoldgregkh
authored andcommitted
serial: qcom-geni: fix soft lockup on sw flow control and suspend
The stop_tx() callback is used to implement software flow control and must not discard data as the Qualcomm GENI driver is currently doing when there is an active TX command. Cancelling an active command can also leave data in the hardware FIFO, which prevents the watermark interrupt from being enabled when TX is later restarted. This results in a soft lockup and is easily triggered by stopping TX using software flow control in a serial console but this can also happen after suspend. Fix this by only stopping any active command, and effectively clearing the hardware fifo, when shutting down the port. When TX is later restarted, a transfer command may need to be issued to discard any stale data that could prevent the watermark interrupt from firing. Fixes: c4f5287 ("tty: serial: msm_geni_serial: Add serial driver support for GENI based QUP") Cc: [email protected] # 4.17 Signed-off-by: Johan Hovold <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 1af2156 commit 947cc4e

File tree

1 file changed

+24
-9
lines changed

1 file changed

+24
-9
lines changed

drivers/tty/serial/qcom_geni_serial.c

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -649,29 +649,43 @@ static void qcom_geni_serial_start_tx_dma(struct uart_port *uport)
649649

650650
static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport)
651651
{
652+
unsigned char c;
652653
u32 irq_en;
653654

654-
if (qcom_geni_serial_main_active(uport) ||
655-
!qcom_geni_serial_tx_empty(uport))
656-
return;
655+
/*
656+
* Start a new transfer in case the previous command was cancelled and
657+
* left data in the FIFO which may prevent the watermark interrupt
658+
* from triggering. Note that the stale data is discarded.
659+
*/
660+
if (!qcom_geni_serial_main_active(uport) &&
661+
!qcom_geni_serial_tx_empty(uport)) {
662+
if (uart_fifo_out(uport, &c, 1) == 1) {
663+
writel(M_CMD_DONE_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
664+
qcom_geni_serial_setup_tx(uport, 1);
665+
writel(c, uport->membase + SE_GENI_TX_FIFOn);
666+
}
667+
}
657668

658669
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
659670
irq_en |= M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN;
660-
661671
writel(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
662672
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
663673
}
664674

665675
static void qcom_geni_serial_stop_tx_fifo(struct uart_port *uport)
666676
{
667677
u32 irq_en;
668-
struct qcom_geni_serial_port *port = to_dev_port(uport);
669678

670679
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
671680
irq_en &= ~(M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN);
672681
writel(0, uport->membase + SE_GENI_TX_WATERMARK_REG);
673682
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
674-
/* Possible stop tx is called multiple times. */
683+
}
684+
685+
static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport)
686+
{
687+
struct qcom_geni_serial_port *port = to_dev_port(uport);
688+
675689
if (!qcom_geni_serial_main_active(uport))
676690
return;
677691

@@ -684,6 +698,8 @@ static void qcom_geni_serial_stop_tx_fifo(struct uart_port *uport)
684698
writel(M_CMD_ABORT_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
685699
}
686700
writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
701+
702+
port->tx_remaining = 0;
687703
}
688704

689705
static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop)
@@ -1069,11 +1085,10 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
10691085
{
10701086
disable_irq(uport->irq);
10711087

1072-
if (uart_console(uport))
1073-
return;
1074-
10751088
qcom_geni_serial_stop_tx(uport);
10761089
qcom_geni_serial_stop_rx(uport);
1090+
1091+
qcom_geni_serial_cancel_tx_cmd(uport);
10771092
}
10781093

10791094
static int qcom_geni_serial_port_setup(struct uart_port *uport)

0 commit comments

Comments
 (0)