[PATCH v2] tty: serial: max3100: shut down timer before freeing port
Fan Wu <[email protected]> Sat, 1 Aug 2026 06:12:08 +0000
| Newsgroups | gmane.linux.kernel.stable,gmane.linux.serial,gmane.linux.kernel |
|---|---|
| Message-ID | <[email protected]> |
max3100_shutdown() stops the polling timer but returns early during
system suspend. If the SPI device is unbound before resume, the serial
core does not call max3100_shutdown() again, so max3100_remove() frees
the port while the timer remains armed. max3100_timeout() can then
access the freed port and re-arm the timer.
Add final timer teardown to max3100_remove() and use
timer_shutdown_sync() to prevent a racing callback from re-arming it.
Also drain the IRQ and workqueue before freeing the port.
Keep timer_delete_sync() in max3100_shutdown() so that a subsequent
open() can re-arm the timer.
Introduce an irq_registered flag to track whether the IRQ is registered,
independently of port->irq, so a failed request_irq() can be retried on
the next open().
Found by static analysis.
Fixes: 7831d56b0a35 ("tty: MAX3100")
Cc: [email protected] # 6.2+
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <[email protected]>
---
Changes since v1:
- Drop the shared drain helper; call timer_shutdown_sync() only in
max3100_remove(), keeping timer_delete_sync() in max3100_shutdown()
so a later open() can re-arm the timer.
- Track IRQ registration with a flag instead of clearing port->irq,
so a failed request_irq() can be retried on the next open().
v1: https://lore.kernel.org/all/[email protected]/
---
drivers/tty/serial/max3100.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c
index 44b745fa26c6..7bc3c5cfe886 100644
--- a/drivers/tty/serial/max3100.c
+++ b/drivers/tty/serial/max3100.c
@@ -107,6 +107,7 @@ struct max3100_port {
int force_end_work;
/* need to know we are suspending to avoid deadlock on workqueue */
int suspending;
+ bool irq_registered;
struct timer_list timer;
};
@@ -538,8 +539,10 @@ static void max3100_shutdown(struct uart_port *port)
destroy_workqueue(s->workqueue);
s->workqueue = NULL;
}
- if (port->irq)
+ if (s->irq_registered) {
free_irq(port->irq, s);
+ s->irq_registered = false;
+ }
/* set shutdown mode to save power */
max3100_sr(s, MAX3100_WC | MAX3100_SHDN, &rx);
@@ -575,12 +578,12 @@ static int max3100_startup(struct uart_port *port)
ret = request_irq(port->irq, max3100_irq, IRQF_TRIGGER_FALLING, "max3100", s);
if (ret < 0) {
dev_warn(&s->spi->dev, "cannot allocate irq %d\n", port->irq);
- port->irq = 0;
destroy_workqueue(s->workqueue);
s->workqueue = NULL;
return -EBUSY;
}
+ s->irq_registered = true;
s->conf_commit = 1;
max3100_dowork(s);
/* wait for clock to settle */
@@ -752,6 +755,17 @@ static void max3100_remove(struct spi_device *spi)
if (max3100s[i] == s) {
dev_dbg(&spi->dev, "%s: removing port %d\n", __func__, i);
uart_remove_one_port(&max3100_uart_driver, &max3100s[i]->port);
+
+ s->force_end_work = 1;
+ timer_shutdown_sync(&s->timer);
+ if (s->irq_registered) {
+ free_irq(s->port.irq, s);
+ s->irq_registered = false;
+ }
+ if (s->workqueue) {
+ destroy_workqueue(s->workqueue);
+ s->workqueue = NULL;
+ }
kfree(max3100s[i]);
max3100s[i] = NULL;
break;
--
2.34.1