]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
serial: qcom-geni: fix hard lockup on buffer flush
authorJohan Hovold <johan+linaro@kernel.org>
Thu, 4 Jul 2024 10:18:04 +0000 (12:18 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 4 Jul 2024 10:30:30 +0000 (12:30 +0200)
The Qualcomm GENI serial driver does not handle buffer flushing and used
to continue printing discarded characters when the circular buffer was
cleared. Since commit 1788cf6a91d9 ("tty: serial: switch from circ_buf
to kfifo") this instead results in a hard lockup due to
qcom_geni_serial_send_chunk_fifo() spinning indefinitely in the
interrupt handler.

This is easily triggered by interrupting a command such as dmesg in a
serial console but can also happen when stopping a serial getty on
reboot.

Implement the flush_buffer() callback and use it to cancel any active TX
command when the write buffer has been emptied.

Reported-by: Douglas Anderson <dianders@chromium.org>
Link: https://lore.kernel.org/lkml/20240610222515.3023730-1-dianders@chromium.org/
Fixes: 1788cf6a91d9 ("tty: serial: switch from circ_buf to kfifo")
Fixes: a1fee899e5be ("tty: serial: qcom_geni_serial: Fix softlock")
Cc: stable@vger.kernel.org # 5.0
Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
Link: https://lore.kernel.org/r/20240704101805.30612-3-johan+linaro@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/qcom_geni_serial.c

index a41360d34790261cb22f52937ad3fa9bb566d604..b2bbd2d79dbbf7356b57a35acd8529844a1cb6c0 100644 (file)
@@ -906,13 +906,17 @@ static void qcom_geni_serial_handle_tx_fifo(struct uart_port *uport,
        else
                pending = kfifo_len(&tport->xmit_fifo);
 
-       /* All data has been transmitted and acknowledged as received */
-       if (!pending && !status && done) {
+       /* All data has been transmitted or command has been cancelled */
+       if (!pending && done) {
                qcom_geni_serial_stop_tx_fifo(uport);
                goto out_write_wakeup;
        }
 
-       avail = port->tx_fifo_depth - (status & TX_FIFO_WC);
+       if (active)
+               avail = port->tx_fifo_depth - (status & TX_FIFO_WC);
+       else
+               avail = port->tx_fifo_depth;
+
        avail *= BYTES_PER_FIFO_WORD;
 
        chunk = min(avail, pending);
@@ -1091,6 +1095,11 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
        qcom_geni_serial_cancel_tx_cmd(uport);
 }
 
+static void qcom_geni_serial_flush_buffer(struct uart_port *uport)
+{
+       qcom_geni_serial_cancel_tx_cmd(uport);
+}
+
 static int qcom_geni_serial_port_setup(struct uart_port *uport)
 {
        struct qcom_geni_serial_port *port = to_dev_port(uport);
@@ -1547,6 +1556,7 @@ static const struct uart_ops qcom_geni_console_pops = {
        .request_port = qcom_geni_serial_request_port,
        .config_port = qcom_geni_serial_config_port,
        .shutdown = qcom_geni_serial_shutdown,
+       .flush_buffer = qcom_geni_serial_flush_buffer,
        .type = qcom_geni_serial_get_type,
        .set_mctrl = qcom_geni_serial_set_mctrl,
        .get_mctrl = qcom_geni_serial_get_mctrl,