From: sashiko-bot@kernel.org
To: "Tapio Reijonen" <tapio.reijonen@vaisala.com>
Cc: linux-serial@vger.kernel.org
Subject: Re: [PATCH v8 8/9] serial: max310x: drive RTS in software when hardware delays are too short
Date: Thu, 08 Oct 2026 10:03:15 +0000 [thread overview]
Message-ID: <sashiko-outbox-163982@kernel.org> (raw)
In-Reply-To: <20261008-max310x-rs485-sw-delay-v8-8-6ba881f81e1f@vaisala.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] tty: use-after-free in max310x_remove due to premature workqueue cancellation
- [High] tty: lockless kfifo check in max310x_delayed_stop_tx truncates RS485 transmission
- [High] tty: TOCTOU race in max310x_rts_work_proc decouples timer arming from RTS assertion
- [High] tty: unprotected tx_state read in max310x_set_rts_ctl_params drops RTS mid-transmission
- [Medium] tty: trailing hrtimer running state in max310x_delayed_stop_tx strands RTS
- [Medium] tty: TOCTOU in max310x_rts_work_proc clobbers break_ctl RTS assertion
--
commit 05eccd91a3e45c1a75cc25465960c12d3c83b68c
Author: Tapio Reijonen <tapio.reijonen@vaisala.com>
serial: max310x: drive RTS in software when hardware delays are too short
The commit adds a software-driven RTS path for max310x when requested RS485
delays exceed the hardware's 15 bit-time limit. It uses an hrtimer and a
workqueue to track before-send and after-send RTS envelope phases and
handle teardown interlocking.
> diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
> index c1fe4ad887839..5fdb9dfca6027 100644
> --- a/drivers/tty/serial/max310x.c
> +++ b/drivers/tty/serial/max310x.c
[ ... ]
> +static void max310x_rts_work_proc(struct work_struct *ws)
> +{
> + struct max310x_one *one = container_of(ws, struct max310x_one, rts_work);
> + struct uart_port *port = &one->port;
> + bool rts_on = READ_ONCE(one->tx_state) != MAX310X_TX_OFF ||
> + READ_ONCE(one->tx_break);
> +
> + max310x_rts_ctl(port, max310x_rts_level(port, rts_on));
[Severity: Medium]
Can this sequence clobber a concurrent break_ctl() RTS assertion?
A concurrent max310x_break_ctl() sets tx_break and manually asserts the RTS
pin. If this function evaluates tx_break as false locklessly, gets preempted
while break_ctl() asserts RTS, and then resumes, it could write its stale
'false' evaluation to the hardware and drop the RTS pin prematurely.
> +
> + guard(spinlock_irqsave)(&port->lock);
> + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_BEFORE_SEND &&
> + !one->cancel_tx_delay_tmr && !hrtimer_active(&one->tx_delay_tmr))
> + hrtimer_start(&one->tx_delay_tmr,
> + ms_to_ktime(port->rs485.delay_rts_before_send),
> + HRTIMER_MODE_REL);
> +}
[Severity: High]
Does this lockless read decouple the timer arming from the physical RTS pin
assertion?
If a concurrent start_tx() sets tx_state to MAX310X_TX_WAIT_BEFORE_SEND and
queues a new rts_work after the lockless evaluation of rts_on but before
taking port->lock, the timer could be armed while the pin is still
deasserted, shortening the setup delay.
[ ... ]
> +static void max310x_delayed_stop_tx(struct uart_port *port)
> +{
> + struct max310x_one *one = to_max310x_port(port);
> + unsigned int txlvl;
> +
> + if (READ_ONCE(one->tx_state) == MAX310X_TX_OFF)
> + return;
> +
> + /*
> + * Data still queued for transmission defers the hold: a refill is
> + * coming and the envelope is not over. Stopped output does not
> + * count - it drains nowhere, and the envelope must end.
> + */
> + if (!kfifo_is_empty(&port->state->port.xmit_fifo) &&
> + !uart_tx_stopped(port))
> + return;
> +
> + /*
> + * The kfifo can be empty while the chip TX FIFO is still draining, so arm
> + * the after-send hold only once the chip FIFO is empty too - the TX-empty
> + * interrupt re-invokes us then. Otherwise the hold starts early and RTS
> + * drops mid-character, clipping the last byte(s).
> + */
> + txlvl = max310x_port_read(port, MAX310X_TXFIFOLVL_REG);
> + if (txlvl)
> + return;
> +
> + /*
> + * Runs from tx_work without port->lock, so re-check the state under it:
> + * shutdown() may have cancelled the envelope meanwhile. Only
> + * MAX310X_TX_SEND may arm the hold.
> + */
> + guard(spinlock_irqsave)(&one->port.lock);
> + if (one->cancel_tx_delay_tmr ||
> + READ_ONCE(one->tx_state) != MAX310X_TX_SEND)
> + return;
[Severity: High]
Is it possible for the lockless checks performed earlier in this function
to evaluate as empty, but for new data to be queued concurrently before
acquiring port->lock?
If new data is queued by a concurrent start_tx(), the function will observe
tx_state is still MAX310X_TX_SEND without re-evaluating the FIFO, and arm
the after-send timer. This could drop the RTS pin mid-transmission when the
timer expires, truncating the RS485 frame.
> +
> + if (!hrtimer_active(&one->tx_delay_tmr)) {
[Severity: Medium]
Could this check strand the RTS pin asserted?
The before-send timer callback releases the port lock and returns
HRTIMER_NORESTART, but the hrtimer core retains the running state until the
callback fully returns.
If tx_work executes concurrently, drains the FIFO, and enters this path
before the hrtimer core clears the running state, this check evaluates to
true. This skips starting the after-send hold, leaving tx_state stuck in
MAX310X_TX_SEND and never releasing the RTS pin.
> + /*
> + * Add one character for the byte still in the shift register -
> + * TX-empty fires as it enters, not as it leaves.
> + */
> + ktime_t delay = us_to_ktime(one->char_time_us +
> + port->rs485.delay_rts_after_send *
> + USEC_PER_MSEC);
> +
> + WRITE_ONCE(one->tx_state, MAX310X_TX_WAIT_AFTER_SEND);
> + hrtimer_start(&one->tx_delay_tmr, delay, HRTIMER_MODE_REL);
> + }
> +}
[ ... ]
> @@ -957,25 +1165,35 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one)
> struct uart_port *port = &one->port;
> unsigned int setup = 0, hold = 0;
> u8 mode1 = 0, irda = 0;
> + bool sw_rts = false;
[ ... ]
> + if (port->rs485.flags & SER_RS485_ENABLED) {
> + if (sw_rts) {
> + /*
> + * Only settle RTS at idle when no transmission owns it.
> + * A reconfigure while one is in flight - rs_work runs
> + * on every TIOCSRS485 - would otherwise release the
> + * transceiver mid-character. A transfer the hardware
> + * path owns is invisible to tx_state - adopt it
> + * instead of settling. The reverse strand also
> + * exists: an envelope the hardware path inherited
> + * mid-envelope leaves tx_state set with nothing
> + * driving the pin.
> + */
> + if (READ_ONCE(one->tx_state) != MAX310X_TX_OFF ||
> + max310x_adopt_hw_tx(one)) {
> + /*
> + * The transfer may be running under auto-RTS:
> + * assert before auto-RTS is disabled below so
> + * the pin is never released under it, and arm
> + * the after-send hold - with the chip FIFO
> + * already drained no TX-empty interrupt
> + * arrives to arm it.
> + */
> + max310x_rts_ctl(port,
> + max310x_rts_level(port, true));
> + max310x_delayed_stop_tx(port);
> + } else {
> + max310x_rts_ctl(port,
> + max310x_rts_level(port, false));
> + /*
> + * serial_core calls set_termios() without
> + * port->lock, so an envelope may have started
> + * while the idle level was written and the
> + * settle can land after its RTS assert.
> + * rts_work re-derives the level from
> + * tx_state; requeue it to converge.
> + */
> + if (READ_ONCE(one->tx_state) != MAX310X_TX_OFF)
> + schedule_work(&one->rts_work);
> + }
[Severity: High]
Is there a race that could cause the RTS pin to drop mid-transmission?
Since tx_state is read locklessly, a concurrent start_tx() could initiate a
transmission and assert the RTS pin before this slow hardware write executes.
The mitigation to requeue rts_work only re-asserts the RTS pin after it
was already dropped, which would irreparably corrupt the active RS485 frame.
[ ... ]
> @@ -1676,6 +2058,8 @@ static void max310x_remove(struct device *dev)
> int i;
>
> for (i = 0; i < s->devtype->nr; i++) {
> + hrtimer_cancel(&s->p[i].tx_delay_tmr);
> + cancel_work_sync(&s->p[i].rts_work);
> cancel_work_sync(&s->p[i].tx_work);
> cancel_work_sync(&s->p[i].md_work);
> cancel_work_sync(&s->p[i].rs_work);
>
> if (test_and_clear_bit(s->p[i].port.line, max310x_lines))
> uart_remove_one_port(&max310x_uart, &s->p[i].port);
[Severity: Critical]
Can this sequence cause a use-after-free by cancelling background tasks
too early?
uart_remove_one_port() is called after these workqueues and timers are
cancelled. Because the TTY port is still fully registered and active during
this window, userspace operations or hardware interrupts could re-queue
these works.
Once max310x_remove() returns and the memory is freed, any re-queued
works would execute on freed memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-max310x-rs485-sw-delay-v8-0-6ba881f81e1f@vaisala.com?part=8
next prev parent reply other threads:[~2026-10-08 10:03 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-08 9:48 [PATCH v8 0/9] serial: max310x: RS485 delay and RTS fixes, software-timed delays Tapio Reijonen
2026-10-08 9:48 ` [PATCH v8 1/9] serial: max310x: don't clobber the TX break bit in set_termios Tapio Reijonen
2026-10-08 9:58 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 2/9] serial: max310x: fix breaks not reaching the wire in RS485 mode Tapio Reijonen
2026-10-08 10:02 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 3/9] serial: max310x: centralize the RS485 transceiver programming Tapio Reijonen
2026-10-08 10:04 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 4/9] serial: max310x: fix RS485 delays being programmed as bit-times Tapio Reijonen
2026-10-08 10:03 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 5/9] serial: max310x: don't power down the port mid-transmission on close Tapio Reijonen
2026-10-08 10:01 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 6/9] serial: max310x: support active-low RTS on the hardware path Tapio Reijonen
2026-10-08 10:00 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 7/9] serial: max310x: schedule tx_work directly from the IRQ handler Tapio Reijonen
2026-10-08 10:04 ` sashiko-bot
2026-10-08 9:48 ` [PATCH v8 8/9] serial: max310x: drive RTS in software when hardware delays are too short Tapio Reijonen
2026-10-08 10:03 ` sashiko-bot [this message]
2026-10-08 9:48 ` [PATCH v8 9/9] serial: max310x: don't transmit while an RS485 reconfigure is pending Tapio Reijonen
2026-10-08 10:03 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-163982@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tapio.reijonen@vaisala.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox