Re: [PATCH] serial: imx: Prevent TX buffer PIO write when a DMA has been started

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Ian,

On Fri, Jul 14, 2017 at 05:31:57PM +0100, Ian Jamison wrote:
> Function imx_transmit_buffer starts a TX DMA if DMA is enabled, since
> commit 91a1a909f921 ("serial: imx: Support sw flow control in DMA mode").
> It also carries on and attempts to write the same TX buffer using PIO.
> This results in TX data corruption and double-incrementing xmit->tail
> with the knock-on effect of tail passing head and a page of garbage
> being sent out.

Good catch, thank you.

> This seems to be triggered mostly when using RS485 half duplex on SMP
> systems, but is probably not limited to just those.
> 
> Tested locally on an i.MX6Q with an RS485 half duplex transceiver on
> UART3, and also by Clemens Gruber.
> 
> Tested-by: Clemens Gruber <clemens.gruber@xxxxxxxxxxxx>
> Signed-off-by: Ian Jamison <ian.dev@xxxxxxxxxx>
> ---
>  drivers/tty/serial/imx.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
> index 9e3162bf3bd1..f6c37ce457c1 100644
> --- a/drivers/tty/serial/imx.c
> +++ b/drivers/tty/serial/imx.c
> @@ -464,7 +464,7 @@ static inline void imx_transmit_buffer(struct imx_port *sport)
>  		}
>  	}
>  
> -	while (!uart_circ_empty(xmit) &&
> +	while (!uart_circ_empty(xmit) && !sport->dma_is_txing &&
>  	       !(readl(sport->port.membase + uts_reg(sport)) & UTS_TXFULL)) {
>  		/* send xmit->buf[xmit->tail]
>  		 * out the port here */

The bigger context looks as follows:

     1	static inline void imx_transmit_buffer(struct imx_port *sport)
     2	{
     3		if (sport->port.x_char) {
     4			writel(sport->port.x_char, sport->port.membase + URTX0);
     5			sport->port.icount.tx++;
     6			sport->port.x_char = 0;
     7			return;
     8		}
     9	
    10		if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {
    11			imx_stop_tx(&sport->port);
    12			return;
    13		}
    14	
    15		led_trigger_blink_oneshot(...)
    16	
    17		if (sport->dma_is_enabled) {
    18			/*
    19			 * We've just sent a X-char Ensure the TX DMA is enabled
    20			 * and the TX IRQ is disabled.
    21			 */
    22			temp = readl(sport->port.membase + UCR1);
    23			temp &= ~UCR1_TXMPTYEN;
    24			if (sport->dma_is_txing) {
    25				temp |= UCR1_TDMAEN;
    26				writel(temp, sport->port.membase + UCR1);
    27			} else {
    28				writel(temp, sport->port.membase + UCR1);
    29				imx_dma_tx(sport);
    30			}
    31		}
    32	
    33		while (!uart_circ_empty(xmit) &&
    34		       !(readl(sport->port.membase + uts_reg(sport)) & UTS_TXFULL)) {
    35			/* send xmit->buf[xmit->tail]
    36			 * out the port here */
    37			writel(xmit->buf[xmit->tail], sport->port.membase + URTX0);
    38			xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
    39			sport->port.icount.tx++;
    40		}
    41	
    42		if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
    43			uart_write_wakeup(&sport->port);
    44	
    45		if (uart_circ_empty(xmit))
    46			imx_stop_tx(&sport->port);
    47	}

It is obvious that Ian's finding is right. After DMA is started in the
block 18 - 30 the driver starts doing PIO.

I wonder why this problem didn't hit us harder, i.e. it was only
reported when doing rs485 on a multi-core machine. So in the more normal
case, the data is sent only once. Looking at the driver I don't see how
the data cannot be sent twice: imx_transmit_buffer is called with
port.lock taken and irqs off. Then dma is set up to send the pending
chars and kicked to eventually start sending. When imx_transmit_buffer
reaches line 33 the DMA part didn't update the pointers in the xmit
buffer yet because this is only done in the dma_tx_callback that grabs
the lock before. The only thing that could prevent the data going out
twice is that after PIO imx_stop_tx is called before the DMA sends out
the data.

I think the problem needs a different fix: If dma_is_enabled in line 17,
lines 33 to 46 should not be executed because dma_tx_callback also
implements the logic of lines 42 to 46.

Further I wonder about the x_char handling: What if dma is currently
active when the driver sends the x_char? Can the fifo be full?

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux