imx: Race when disabling RX in IMX.6 UART in half duplex

Piotr Figiel p.figiel at camlintechnologies.com
Wed Feb 22 03:16:31 PST 2017


Hi,


On 22.02.2017 11:44, Sascha Hauer wrote:
> On Tue, Feb 21, 2017 at 09:48:54AM +0100, Piotr Figiel wrote:
>> Hi Baruch,
>>
>>
>> On 21.02.2017 09:18, Baruch Siach wrote:
>>> Hi Piotr,
>>>
>>> On Tue, Feb 21, 2017 at 09:00:39AM +0100, Piotr Figiel wrote:
>>>> On 20.02.2017 20:20, Baruch Siach wrote:
>>>>> On Mon, Feb 20, 2017 at 02:54:02PM +0100, Piotr Figiel wrote:
>>>>>>     I'm looking to find a correct way to disable RX in the I.MX6 UART during
>>>>>> TX-ing. I stumbled on the issue described below when working on
>>>>>> ~SER_RS485_RX_DURING_TX on custom 3.10 tree (branched from Freescale's/NXP's
>>>>>> BSP release) independently to what is now in the main line imx.c, but I see
>>>>>> now that the implementation in mainline also seem to have the same problem
>>>>>> I'm trying to solve now. I would like to request for comments to
>>>>>> confirm/deny whether the issue I raise here is valid and how to best
>>>>>> approach this.
>>>>>>
>>>>>>     I'm mostly concerned about the fact that the URXD register must not be
>>>>>> accessed (as documented in IMX.6 RM) when RX is disabled (RXEN=0). The issue
>>>>>> is that in the following sequence happening during imx_start_tx with
>>>>>> ~SER_RS485_RX_DURING_TX set:
>>>>>>
>>>>>> 1. Acquire spinlock, disable local interrupts (from serial_core.c),
>>>>>> 2. Disable RX receiver (RXEN=0 in UCR2),
>>>>>> 3. Release spinlock, reenable interrupts.
>>>>>>
>>>>>> The RX fifo may become not empty between #1 and #2. This will raise
>>>>>> interrupt which will be handled after re-enabling interrupts (after #3). ISR
>>>>>> in this case will check the status bit of the interrupt and fetch RX FIFO
>>>>>> contents, which I understand is forbidden by the documentation and may raise
>>>>>> error on the bus [1]. In addition disabling RX IRQ in this procedure, e.g.
>>>>>> after #1 doesn't seem to be enough, as the IRQ still may trigger after #1
>>>>>> and will need to be serviced.
>>>>> As I understand it, SER_RS485_RX_DURING_TX is for half-duplex RS-485 where
>>>>> only one device is allowed to transmit at any given time. Higher level code
>>>>> determines when any given device on the bus is allowed to transmit. If Rx FIFO
>>>>> becomes non-empty during Tx enable, you have a contention problem to solve.
>>>> That's correct, although I don't want bugs from the user-space or
>>>> misbehaving devices on RS-485 to cause errors on the AXI/AHB bus resulting
>>>> with possible crash/exception on the CPU core. The reason I write about this
>>>> is that the RM explicitly forbids accessing those registers in such case.
>>> The trivial solution might be to return early from imx_rxint() when RXEN=0.
>>> Would that be sufficient?
>> I'm not sure if it's best idea, I don't know if the UART will keep
>> triggering the interrupt if the source won't be cleared (FIFO drained), but
>> if it does we'll be irq flooded,
> And indeed that's what happens. The RX irq is acked by reading all
> characters from the FIFO. I had the same problem as you describe, see
> below for the patch I came up with (with the intention to mainline i
> t when I find the time). My "usecase" was to send a character while
> being flooded with received characters from the other side. That of
> course was only for testing purposes, but I could reliably crash
> the kernel with this setup.

Thanks for confirming this, this probably requires CVE, since user with 
tty access can crash mainline kernel by correctly timing a write 
(personally though I wasn't able to trigger this without putting delay 
in the code to trigger the race). Some comments regarding the 
implementation (thanks for sharing this btw):

> +static void imx_receiver_safe_disable(struct imx_port *sport)
> +{
> +	u32 ucr2, ucr1;
> +	int t = 50;
> +
> +	ucr1 = readl(sport->port.membase + UCR1);
> +	ucr1 &= ~UCR1_RRDYEN;
> +	writel(ucr1, sport->port.membase + UCR1);
> +
> +	ucr2 = readl(sport->port.membase + UCR2);
> +
> +	/*
> +	 * Disable the receiver and make sure that no characters are left
> +	 * in the FIFO. Otherwise an interrupt will be triggered even when
> +	 * UCR1_RRDYEN is cleared. We can't ack this interrupt because with
> +	 * disabled receiver we are not allowed to read from the FIFO.
> +	 */
> +	do {
> +		writel(ucr2 | UCR2_RXEN, sport->port.membase + UCR2);
> +
> +		while (readl(sport->port.membase + USR2) & USR2_RDR)
> +			readl(sport->port.membase + URXD0);
> +
> +		writel(ucr2 & ~UCR2_RXEN, sport->port.membase + UCR2);
> +
> +		if (!t--) {
> +			dev_err(sport->port.dev, "Failed to disable the receiver\n");
> +			return;
In this unlikely event perhaps an error should be returned so that the 
RXEN is not disabled in imx_start_tx() (i.e. I think it's better to 
ignore ~SER_RS485_RX_DURING_TX for a while than to crash).

> +		}
> +	} while (readl(sport->port.membase + USR2) & USR2_RDR);
> +}
> +
>   /*
>    * interrupts disabled on entry
>    */
> @@ -633,8 +669,10 @@ static void imx_start_tx(struct uart_port *port)
>   				imx_port_rts_active(sport, &temp);
>   			else
>   				imx_port_rts_inactive(sport, &temp);
> -			if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
> +			if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) {
> +				imx_receiver_safe_disable(sport);
>   				temp &= ~UCR2_RXEN;
> +			}
>   			writel(temp, port->membase + UCR2);
>   			sport->tx_state = WAIT_AFTER_RTS;
>   			sport->tx_state_next_change =

Other than that I think it's OK (it's also consistent with the procedure 
from my first post [1]).

Best regards, Piotr.

[1] 
http://lists.infradead.org/pipermail/linux-arm-kernel/2017-February/488957.html



More information about the linux-arm-kernel mailing list