]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
serial: amba-pl011: cancel RS485 hrtimers after freeing IRQ
authorFan Wu <fanwu01@zju.edu.cn>
Fri, 31 Jul 2026 08:59:14 +0000 (08:59 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 3 Aug 2026 14:31:19 +0000 (16:31 +0200)
The RS485 trigger hrtimers are embedded in the devm-managed port and can
fire after it is freed. The IRQ handler can arm a timer, so free the IRQ
first and then cancel both timers.

Complete the RS485 stop without arming a timer, and cancel the timers
in remove() for the suspend-then-unbind path, where shutdown is not
called.

This issue was found by an in-house static analysis tool.

Fixes: 2c1fd53af21b ("serial: amba-pl011: Fix RTS handling in RS485 mode")
Cc: stable <stable@kernel.org>
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
Link: https://patch.msgid.link/20260731085915.326775-3-fanwu01@zju.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/amba-pl011.c

index d88c1242e6d880e543790714c90ae7ab9e3163c2..5232042fdd929c58fa7d364841b607ba7808018e 100644 (file)
@@ -1334,11 +1334,30 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
 #define pl011_dma_flush_buffer NULL
 #endif
 
-static void pl011_rs485_tx_stop(struct uart_amba_port *uap)
+static void pl011_rs485_tx_stop_now(struct uart_amba_port *uap)
 {
        struct uart_port *port = &uap->port;
        u32 cr;
 
+       cr = pl011_read(uap, REG_CR);
+
+       if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
+               cr &= ~UART011_CR_RTS;
+       else
+               cr |= UART011_CR_RTS;
+
+       /* Disable the transmitter and reenable the transceiver */
+       cr &= ~UART011_CR_TXE;
+       cr |= UART011_CR_RXE;
+       pl011_write(cr, uap, REG_CR);
+
+       uap->rs485_tx_state = OFF;
+}
+
+static void pl011_rs485_tx_stop(struct uart_amba_port *uap)
+{
+       struct uart_port *port = &uap->port;
+
        if (uap->rs485_tx_state == SEND)
                uap->rs485_tx_state = WAIT_AFTER_SEND;
 
@@ -1362,19 +1381,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap)
                hrtimer_try_to_cancel(&uap->trigger_start_tx);
        }
 
-       cr = pl011_read(uap, REG_CR);
-
-       if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
-               cr &= ~UART011_CR_RTS;
-       else
-               cr |= UART011_CR_RTS;
-
-       /* Disable the transmitter and reenable the transceiver */
-       cr &= ~UART011_CR_TXE;
-       cr |= UART011_CR_RXE;
-       pl011_write(cr, uap, REG_CR);
-
-       uap->rs485_tx_state = OFF;
+       pl011_rs485_tx_stop_now(uap);
 }
 
 static void pl011_stop_tx(struct uart_port *port)
@@ -2084,11 +2091,20 @@ static void pl011_shutdown(struct uart_port *port)
 
        pl011_dma_shutdown(uap);
 
-       if ((port->rs485.flags & SER_RS485_ENABLED && uap->rs485_tx_state != OFF))
-               pl011_rs485_tx_stop(uap);
-
        free_irq(uap->port.irq, uap);
 
+       /*
+        * free_irq() drains the UART interrupt handler, which can arm either
+        * timer.  Cancel the timers afterwards to drain their callbacks too.
+        */
+       hrtimer_cancel(&uap->trigger_start_tx);
+       hrtimer_cancel(&uap->trigger_stop_tx);
+
+       uart_port_lock_irq(port);
+       if (uap->rs485_tx_state != OFF)
+               pl011_rs485_tx_stop_now(uap);
+       uart_port_unlock_irq(port);
+
        pl011_disable_uart(uap);
 
        /*
@@ -3067,6 +3083,8 @@ static void pl011_remove(struct amba_device *dev)
        struct uart_amba_port *uap = amba_get_drvdata(dev);
 
        uart_remove_one_port(&amba_reg, &uap->port);
+       hrtimer_cancel(&uap->trigger_start_tx);
+       hrtimer_cancel(&uap->trigger_stop_tx);
        pl011_unregister_port(uap);
 }