1
0
Fork 0

serial: imx: fix a race condition in receive path

The main irq handler function starts by first masking disabled
interrupts in the status register values to ensure to only handle
enabled interrupts. This is important as when the RX path in the
hardware is disabled reading the RX fifo results in an external abort.

This checking must be done under the port lock, otherwise the following
can happen:

     CPU1                            | CPU2
                                     |
     irq triggers as there are chars |
     in the RX fifo                  |
				     | grab port lock
     imx_uart_int finds RRDY enabled |
     and calls imx_uart_rxint which  |
     has to wait for port lock       |
                                     | disable RX (e.g. because we're
                                     | using RS485 with !RX_DURING_TX)
                                     |
                                     | release port lock
     read from RX fifo with RX       |
     disabled => exception           |

So take the port lock only once in imx_uart_int() instead of in the
functions called from there.

Reported-by: Andre Renaud <arenaud@designa-electronics.com>
Cc: stable@vger.kernel.org
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Link: https://lore.kernel.org/r/20200121071702.20150-1-u.kleine-koenig@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
alistair/sensors
Uwe Kleine-König 2020-01-21 08:17:02 +01:00 committed by Greg Kroah-Hartman
parent 48d414a3f2
commit 101aa46bd2
1 changed files with 38 additions and 13 deletions

View File

@ -696,22 +696,33 @@ static void imx_uart_start_tx(struct uart_port *port)
}
}
static irqreturn_t imx_uart_rtsint(int irq, void *dev_id)
static irqreturn_t __imx_uart_rtsint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
u32 usr1;
spin_lock(&sport->port.lock);
imx_uart_writel(sport, USR1_RTSD, USR1);
usr1 = imx_uart_readl(sport, USR1) & USR1_RTSS;
uart_handle_cts_change(&sport->port, !!usr1);
wake_up_interruptible(&sport->port.state->port.delta_msr_wait);
spin_unlock(&sport->port.lock);
return IRQ_HANDLED;
}
static irqreturn_t imx_uart_rtsint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
irqreturn_t ret;
spin_lock(&sport->port.lock);
ret = __imx_uart_rtsint(irq, dev_id);
spin_unlock(&sport->port.lock);
return ret;
}
static irqreturn_t imx_uart_txint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
@ -722,14 +733,12 @@ static irqreturn_t imx_uart_txint(int irq, void *dev_id)
return IRQ_HANDLED;
}
static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
static irqreturn_t __imx_uart_rxint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
unsigned int rx, flg, ignored = 0;
struct tty_port *port = &sport->port.state->port;
spin_lock(&sport->port.lock);
while (imx_uart_readl(sport, USR2) & USR2_RDR) {
u32 usr2;
@ -786,11 +795,25 @@ static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
}
out:
spin_unlock(&sport->port.lock);
tty_flip_buffer_push(port);
return IRQ_HANDLED;
}
static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
irqreturn_t ret;
spin_lock(&sport->port.lock);
ret = __imx_uart_rxint(irq, dev_id);
spin_unlock(&sport->port.lock);
return ret;
}
static void imx_uart_clear_rx_errors(struct imx_port *sport);
/*
@ -849,6 +872,8 @@ static irqreturn_t imx_uart_int(int irq, void *dev_id)
unsigned int usr1, usr2, ucr1, ucr2, ucr3, ucr4;
irqreturn_t ret = IRQ_NONE;
spin_lock(&sport->port.lock);
usr1 = imx_uart_readl(sport, USR1);
usr2 = imx_uart_readl(sport, USR2);
ucr1 = imx_uart_readl(sport, UCR1);
@ -882,27 +907,25 @@ static irqreturn_t imx_uart_int(int irq, void *dev_id)
usr2 &= ~USR2_ORE;
if (usr1 & (USR1_RRDY | USR1_AGTIM)) {
imx_uart_rxint(irq, dev_id);
__imx_uart_rxint(irq, dev_id);
ret = IRQ_HANDLED;
}
if ((usr1 & USR1_TRDY) || (usr2 & USR2_TXDC)) {
imx_uart_txint(irq, dev_id);
imx_uart_transmit_buffer(sport);
ret = IRQ_HANDLED;
}
if (usr1 & USR1_DTRD) {
imx_uart_writel(sport, USR1_DTRD, USR1);
spin_lock(&sport->port.lock);
imx_uart_mctrl_check(sport);
spin_unlock(&sport->port.lock);
ret = IRQ_HANDLED;
}
if (usr1 & USR1_RTSD) {
imx_uart_rtsint(irq, dev_id);
__imx_uart_rtsint(irq, dev_id);
ret = IRQ_HANDLED;
}
@ -917,6 +940,8 @@ static irqreturn_t imx_uart_int(int irq, void *dev_id)
ret = IRQ_HANDLED;
}
spin_unlock(&sport->port.lock);
return ret;
}