Re: [PATCH 1/1] serial: qcom-geni: fix TX DMA buffer flush

Praveen Talari <[email protected]> Thu, 30 Jul 2026 14:47:00 +0530
Newsgroups org.kernel.vger.linux-serial,org.kernel.vger.linux-arm-msm,org.kernel.vger.stable
Message-ID <[email protected]>
Hi

On 29-07-2026 23:11, jaseg wrote:
> From: Jan Sebastian Götte <[email protected]>
>
> When transmit flushing a qcom-geni UART during an ongoing TX DMA, the
> UART gets stuck infinitely repeating corrupted TX DMA frames.
>
> The DMA-mode uart_ops does not provide a flush_buffer callback, so an
> in-flight transfer can complete after serial core has reset the transmit
> kfifo, underflowing its length and resubmitting page-sized transfers
> indefinitely. Add one that stops the transfer and clears tx_remaining
> and tx_queued.
>
> The stop path was also broken: it unmapped the buffer while the serial
> engine could still read it, and never reset the TX DMA state machine.
> Cancel the main sequencer command first, then reset the state machine
> and wait for it before unmapping. Drop the early return so a pending
> mapping is also cleaned up when the main command is inactive.
>
> The bug can be triggered from userspace with a large write immediately
> followed by TCOFLUSH. A following tcdrain will hang forever. The bug was
> reproduced and this fix was validated on Arduino Uno Q (QRB2210)
> using /dev/ttyHS1.
>
> Assisted-by: Claude:claude-5-opus Codex:gpt-5
> Signed-off-by: Jan Sebastian Götte <[email protected]>
> Fixes: 2aaa43c70778 ("tty: serial: qcom-geni-serial: add support for serial engine DMA")
> Cc: [email protected]
> ---
>   drivers/tty/serial/qcom_geni_serial.c | 43 ++++++++++++++-------------
>   1 file changed, 22 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
> index 7ead87b4eb65..1e39122ec09f 100644
> --- a/drivers/tty/serial/qcom_geni_serial.c
> +++ b/drivers/tty/serial/qcom_geni_serial.c
> @@ -158,6 +158,7 @@ static const struct uart_ops qcom_geni_uart_pops;
>   static struct uart_driver qcom_geni_console_driver;
>   static struct uart_driver qcom_geni_uart_driver;
>   
> +static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport);
>   static void __qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport);
>   static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport);
>   static int qcom_geni_serial_port_setup(struct uart_port *uport);
> @@ -636,35 +637,34 @@ static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport)
>   	return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS);
>   }
>   
> +static void qcom_geni_serial_flush_buffer_dma(struct uart_port *uport)
> +{
> +	struct qcom_geni_serial_port *port = to_dev_port(uport);
> +
> +	qcom_geni_serial_stop_tx_dma(uport);
> +	port->tx_remaining = 0;
> +	port->tx_queued = 0;
> +}
> +
>   static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport)
>   {
>   	struct qcom_geni_serial_port *port = to_dev_port(uport);
> -	bool done;
>   
> -	if (!qcom_geni_serial_main_active(uport))
> -		return;
> +	if (qcom_geni_serial_main_active(uport))
> +		__qcom_geni_serial_cancel_tx_cmd(uport);
>   
>   	if (port->tx_dma_addr) {
> +		writel(1, uport->membase + SE_DMA_TX_FSM_RST);
> +		if (!qcom_geni_serial_poll_bit(uport, SE_DMA_TX_IRQ_STAT,
> +					       TX_RESET_DONE, true))
> +			dev_err_ratelimited(uport->dev, "TX DMA reset failed");
> +		writel(TX_RESET_DONE | TX_DMA_DONE,
> +		       uport->membase + SE_DMA_TX_IRQ_CLR);
> +
>   		geni_se_tx_dma_unprep(&port->se, port->tx_dma_addr,
>   				      port->tx_remaining);
>   		port->tx_dma_addr = 0;
> -		port->tx_remaining = 0;
>   	}
> -
> -	geni_se_cancel_m_cmd(&port->se);
> -
> -	done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
> -					 M_CMD_CANCEL_EN, true);
> -	if (!done) {
> -		geni_se_abort_m_cmd(&port->se);
> -		done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
> -						 M_CMD_ABORT_EN, true);
> -		if (!done)
> -			dev_err_ratelimited(uport->dev, "M_CMD_ABORT_EN not set");
> -		writel(M_CMD_ABORT_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
> -	}
> -
> -	writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
>   }
>   
>   static void qcom_geni_serial_start_tx_dma(struct uart_port *uport)
> @@ -1180,7 +1180,7 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
>   	uart_port_unlock_irq(uport);
>   }
>   
> -static void qcom_geni_serial_flush_buffer(struct uart_port *uport)
> +static void qcom_geni_serial_flush_buffer_fifo(struct uart_port *uport)
>   {
>   	qcom_geni_serial_cancel_tx_cmd(uport);
>   }
> @@ -1769,7 +1769,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,
> +	.flush_buffer = qcom_geni_serial_flush_buffer_fifo,
>   	.type = qcom_geni_serial_get_type,
>   	.set_mctrl = qcom_geni_serial_set_mctrl,
>   	.get_mctrl = qcom_geni_serial_get_mctrl,
> @@ -1792,6 +1792,7 @@ static const struct uart_ops qcom_geni_uart_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_dma,
>   	.type = qcom_geni_serial_get_type,
>   	.set_mctrl = qcom_geni_serial_set_mctrl,
>   	.get_mctrl = qcom_geni_serial_get_mctrl,

Reviewed-by: Praveen Talari <[email protected]>

Thanks,

Praveen Talari