Message ID | 20240906131336.23625-2-johan+linaro@kernel.org |
---|---|
State | New |
Headers | show |
Series | serial: qcom-geni: fix console corruption | expand |
Hi, On Fri, Sep 6, 2024 at 6:15 AM Johan Hovold <johan+linaro@kernel.org> wrote: > > The qcom_geni_serial_poll_bit() can be used to wait for events like > command completion and is supposed to wait for the time it takes to > clear a full fifo before timing out. > > As noted by Doug, the current implementation does not account for start, > stop and parity bits when determining the timeout. The helper also does > not currently account for the shift register and the two-word > intermediate transfer register. > > A too short timeout can specifically lead to lost characters when > waiting for a transfer to complete as the transfer is cancelled on > timeout. > > Instead of determining the poll timeout on every call, store the fifo > timeout when updating it in set_termios() and make sure to take the > shift and intermediate registers into account. Note that serial core has > already added a 20 ms margin to the fifo timeout. > > Also note that the current uart_fifo_timeout() interface does > unnecessary calculations on every call and did not exist in earlier > kernels so only store its result once. This facilitates backports too as > earlier kernels can derive the timeout from uport->timeout, which has > since been removed. > > Fixes: c4f528795d1a ("tty: serial: msm_geni_serial: Add serial driver support for GENI based QUP") > Cc: stable@vger.kernel.org # 4.17 > Reported-by: Douglas Anderson <dianders@chromium.org> > Tested-by: Nícolas F. R. A. Prado <nfraprado@collabora.com> > Signed-off-by: Johan Hovold <johan+linaro@kernel.org> > --- > drivers/tty/serial/qcom_geni_serial.c | 31 +++++++++++++++------------ > 1 file changed, 17 insertions(+), 14 deletions(-) Reviewed-by: Douglas Anderson <dianders@chromium.org>
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 69a632fefc41..309c0bddf26a 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -124,7 +124,7 @@ struct qcom_geni_serial_port { dma_addr_t tx_dma_addr; dma_addr_t rx_dma_addr; bool setup; - unsigned int baud; + unsigned long poll_timeout_us; unsigned long clk_rate; void *rx_buf; u32 loopback; @@ -270,22 +270,13 @@ static bool qcom_geni_serial_poll_bit(struct uart_port *uport, { u32 reg; struct qcom_geni_serial_port *port; - unsigned int baud; - unsigned int fifo_bits; unsigned long timeout_us = 20000; struct qcom_geni_private_data *private_data = uport->private_data; if (private_data->drv) { port = to_dev_port(uport); - baud = port->baud; - if (!baud) - baud = 115200; - fifo_bits = port->tx_fifo_depth * port->tx_fifo_width; - /* - * Total polling iterations based on FIFO worth of bytes to be - * sent at current baud. Add a little fluff to the wait. - */ - timeout_us = ((fifo_bits * USEC_PER_SEC) / baud) + 500; + if (port->poll_timeout_us) + timeout_us = port->poll_timeout_us; } /* @@ -1244,11 +1235,11 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, unsigned long clk_rate; u32 ver, sampling_rate; unsigned int avg_bw_core; + unsigned long timeout; qcom_geni_serial_stop_rx(uport); /* baud rate */ baud = uart_get_baud_rate(uport, termios, old, 300, 4000000); - port->baud = baud; sampling_rate = UART_OVERSAMPLING; /* Sampling rate is halved for IP versions >= 2.5 */ @@ -1326,9 +1317,21 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, else tx_trans_cfg |= UART_CTS_MASK; - if (baud) + if (baud) { uart_update_timeout(uport, termios->c_cflag, baud); + /* + * Make sure that qcom_geni_serial_poll_bitfield() waits for + * the FIFO, two-word intermediate transfer register and shift + * register to clear. + * + * Note that uart_fifo_timeout() also adds a 20 ms margin. + */ + timeout = jiffies_to_usecs(uart_fifo_timeout(uport)); + timeout += 3 * timeout / port->tx_fifo_depth; + WRITE_ONCE(port->poll_timeout_us, timeout); + } + if (!uart_console(uport)) writel(port->loopback, uport->membase + SE_UART_LOOPBACK_CFG);