tty: serial: imx: fix potential deadlock
authorSam Nobs <samuel.nobs@taitradio.com>
Mon, 9 Nov 2020 20:50:06 +0000 (09:50 +1300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Nov 2020 08:32:49 +0000 (09:32 +0100)
Enabling the lock dependency validator has revealed
that the way spinlocks are used in the IMX serial
port could result in a deadlock.

Specifically, imx_uart_int() acquires a spinlock
without disabling the interrupts, meaning that another
interrupt could come along and try to acquire the same
spinlock, potentially causing the two to wait for each
other indefinitely.

Use spin_lock_irqsave() instead to disable interrupts
upon acquisition of the spinlock.

Fixes: c974991d2620 ("tty:serial:imx: use spin_lock instead of spin_lock_irqsave in isr")
Reviewed-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Sam Nobs <samuel.nobs@taitradio.com>
Link: https://lore.kernel.org/r/1604955006-9363-1-git-send-email-samuel.nobs@taitradio.com
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/imx.c

index 1731d972886522e805addc3bc2f63ec5fcff6cbe..3c53a3c89959b66ffe3d9ad87aa3b77da8a62188 100644 (file)
@@ -942,8 +942,14 @@ static irqreturn_t imx_uart_int(int irq, void *dev_id)
        struct imx_port *sport = dev_id;
        unsigned int usr1, usr2, ucr1, ucr2, ucr3, ucr4;
        irqreturn_t ret = IRQ_NONE;
+       unsigned long flags = 0;
 
-       spin_lock(&sport->port.lock);
+       /*
+        * IRQs might not be disabled upon entering this interrupt handler,
+        * e.g. when interrupt handlers are forced to be threaded. To support
+        * this scenario as well, disable IRQs when acquiring the spinlock.
+        */
+       spin_lock_irqsave(&sport->port.lock, flags);
 
        usr1 = imx_uart_readl(sport, USR1);
        usr2 = imx_uart_readl(sport, USR2);
@@ -1013,7 +1019,7 @@ static irqreturn_t imx_uart_int(int irq, void *dev_id)
                ret = IRQ_HANDLED;
        }
 
-       spin_unlock(&sport->port.lock);
+       spin_unlock_irqrestore(&sport->port.lock, flags);
 
        return ret;
 }