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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.