serial: amba-pl011: cancel RS485 hrtimers after freeing IRQ

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: 2c1fd53af2 ("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>
This commit is contained in:
Fan Wu
2026-07-31 08:59:14 +00:00
committed by Greg Kroah-Hartman
parent dcb2f7576c
commit 36672c8d7d

View 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);
}