mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 16:53:20 -04:00
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:
committed by
Greg Kroah-Hartman
parent
dcb2f7576c
commit
36672c8d7d
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user