Linux Serial subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Tapio Reijonen" <tapio.reijonen@vaisala.com>
Cc: linux-serial@vger.kernel.org
Subject: Re: [PATCH RESEND v2] serial: max310x: drive RTS in software when hardware delays are too short
Date: Mon, 14 Sep 2026 07:53:46 +0000	[thread overview]
Message-ID: <20260914075346.87FE01F00893@smtp.kernel.org> (raw)
In-Reply-To: <20260914-max310x-rs485-sw-delay-v2-1-9a7b681e64ce@vaisala.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Unprivileged Denial of Service (Hung Task Panic) via unbounded uninterruptible sleep.
- [High] Use-After-Free due to timer resurrection during teardown.
- [High] Illegal drop of port->lock in UART callbacks, breaking core serialization.
- [High] RS485 data loss via software state machine bypass.
- [High] Software RTS state and TX-break bits are wiped out by absolute LCR write.
- [High] Broken RS485 break signaling ignores transceiver polarity.
- [High] Resource leak and Use-After-Free of timers/workqueues on probe failure.
- [Medium] Spurious before-send delays corrupt the RS485 timing envelope.
--

commit baf4e55a904a787d7bc6f1d6391ae17076f84296
Author: Tapio Reijonen <tapio.reijonen@vaisala.com>
Subject: serial: max310x: drive RTS in software when hardware delays are too short

This patch adds a software-driven RTS path for the MAX310X serial driver
when hardware auto-RTS delays cannot accommodate the requested timing or
polarity. It implements an hrtimer and workqueue-based state machine to
handle RTS toggle timings.

Link: https://lore.kernel.org/r/20260709-max310x-rs485-sw-delay-v1-1-454ac10b937a@vaisala.com

> diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
> index 022502986c5fc..453c4aca2f2d9 100644
> --- a/drivers/tty/serial/max310x.c
> +++ b/drivers/tty/serial/max310x.c
[ ... ]
> @@ -787,8 +904,13 @@ static void max310x_handle_tx(struct uart_port *port)
[ ... ]
>  	/*
>  	 * It's a circ buffer -- wrap around.
>  	 * We could do that in one SPI transaction, but meh.
>  	 */
>  	while (!kfifo_is_empty(&tport->xmit_fifo)) {
>  		unsigned int txlen, to_send;
>  		unsigned char *tail;
>  
>  		/* Limit to space available in TX FIFO */
>  		txlen = max310x_port_read(port, MAX310X_TXFIFOLVL_REG);
>  		txlen = port->fifosize - txlen;
>  		if (!txlen)
>  			break;
>  
>  		to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen);
>  		max310x_batch_write(port, tail, to_send);
>  		uart_xmit_advance(port, to_send);
>  	}

[Severity: High]
Could this lead to data loss on the RS485 bus? If a hardware TXEMPTY
interrupt fires and schedules tx_work before rts_work has transitioned the
state machine to MAX310X_TX_SEND and asserted RTS, max310x_handle_tx() might
push data to the hardware FIFO while the transceiver is still in receive mode.

> @@ -813,11 +935,48 @@ static void max310x_handle_tx(struct uart_port *port)
[ ... ]
> +static void max310x_delayed_start_tx(struct uart_port *port)
> +{
> +	struct max310x_one *one = to_max310x_port(port);
> +
> +	WRITE_ONCE(one->tx_state, MAX310X_TX_WAIT_BEFORE_SEND);
> +	one->cancel_tx_delay_tmr = false;
> +	schedule_work(&one->rts_work);
> +}
> +
> +/* called with port.lock taken and irqs off */
>  static void max310x_start_tx(struct uart_port *port)
>  {
>  	struct max310x_one *one = to_max310x_port(port);
>  
> -	schedule_work(&one->tx_work);
> +	if (one->sw_rts_during_tx) {
> +		/*
> +		 * The before- and after-send phases share one delay timer. If an
> +		 * after-send release is pending, cancel it before starting a new
> +		 * TX so the just-asserted RTS is not yanked; re-arming the timer
> +		 * for the before-send phase then supersedes the release.
> +		 */
> +		int res = 0;
> +
> +		if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_AFTER_SEND)
> +			res = hrtimer_try_to_cancel(&one->tx_delay_tmr);
> +		if (unlikely(res == -1)) {
> +			one->cancel_tx_delay_tmr = true;
> +			spin_unlock(&one->port.lock);
> +			hrtimer_cancel(&one->tx_delay_tmr);
> +			spin_lock(&one->port.lock);
> +		}

[Severity: High]
Is it safe to drop the port lock here? The UART core typically calls the
start_tx callback with the lock held and interrupts disabled. Dropping it
might break UART core serialization and allow concurrent operations to modify
the port state.

> +
> +		max310x_delayed_start_tx(port);

[Severity: Medium]
If the port is already actively transmitting (tx_state == MAX310X_TX_SEND),
does calling max310x_delayed_start_tx() unnecessarily revert the state back
to MAX310X_TX_WAIT_BEFORE_SEND and queue another spurious delay?

> @@ -927,15 +1086,101 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl)
[ ... ]
>  static void max310x_break_ctl(struct uart_port *port, int break_state)
>  {
> +	struct max310x_one *one = to_max310x_port(port);
> +
>  	max310x_port_update(port, MAX310X_LCR_REG,
>  			    MAX310X_LCR_TXBREAK_BIT,
>  			    break_state ? MAX310X_LCR_TXBREAK_BIT : 0);
> +
> +	if (!(port->rs485.flags & SER_RS485_ENABLED))
> +		return;
> +
> +	/*
> +	 * Drive RTS manually for the break duration. HW auto-RTS only asserts
> +	 * the transceiver while FIFO data is shifting out, and a break is not
> +	 * FIFO data, so on the HW path also disable auto-RTS for the break and
> +	 * restore it when the break ends.
> +	 */
> +	if (!one->sw_rts_during_tx)
> +		max310x_port_update(port, MAX310X_MODE1_REG,
> +				    MAX310X_MODE1_TRNSCVCTRL_BIT,
> +				    break_state ? 0 : MAX310X_MODE1_TRNSCVCTRL_BIT);
> +
> +	max310x_rts_ctl(port, break_state);
> +}

[Severity: High]
Does passing break_state directly to max310x_rts_ctl() handle active-low
transceivers correctly? It appears this forces RTS active-high unconditionally,
bypassing the SER_RS485_RTS_ON_SEND polarity checks used elsewhere in the
driver.

> +static void max310x_set_termios(struct uart_port *port,
>  				struct ktermios *termios,
>  				const struct ktermios *old)
>  {
> +	unsigned int frame_bits = tty_get_frame_size(termios->c_cflag);
>  	unsigned int lcr = 0, flow = 0;
>  	int baud;

[Severity: High]
Just below here, max310x_set_termios() performs an absolute write to update
MAX310X_LCR_REG. Could this inadvertently clear the manual RTS
(MAX310X_LCR_RTS_BIT) or TX break (MAX310X_LCR_TXBREAK_BIT) bits if a
software RS485 envelope is actively transmitting while termios is changed?

> @@ -1042,38 +1287,55 @@ static void max310x_set_termios(struct uart_port *port,
[ ... ]
> +/* called with port.lock taken and irqs off */
>  static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios,
>  				struct serial_rs485 *rs485)
>  {
>  	struct max310x_one *one = to_max310x_port(port);
>  
> -	if ((rs485->delay_rts_before_send > 0x0f) ||
> -	    (rs485->delay_rts_after_send > 0x0f))
> -		return -ERANGE;
> +	rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U);
> +	rs485->delay_rts_after_send  = min(rs485->delay_rts_after_send,  100U);
> +
> +	/*
> +	 * Make sure no SW-timed RTS toggle survives an RS485 disable, even
> +	 * if the delay timer happens to be running right now.
> +	 */
> +	if (!(rs485->flags & SER_RS485_ENABLED)) {
> +		one->cancel_tx_delay_tmr = true;
> +		if (hrtimer_try_to_cancel(&one->tx_delay_tmr) == -1) {
> +			spin_unlock(&port->lock);
> +			hrtimer_cancel(&one->tx_delay_tmr);
> +			spin_lock(&port->lock);
> +		}

[Severity: High]
Is it safe to drop the port lock here as well? Like in max310x_start_tx(),
dropping the lock could allow concurrent operations to enter the critical
section while the caller expects exclusive access.

> @@ -1134,9 +1396,54 @@ static int max310x_startup(struct uart_port *port)
[ ... ]
>  static void max310x_shutdown(struct uart_port *port)
>  {
> +	struct max310x_one *one = to_max310x_port(port);
> +
> +	/*
> +	 * Drain any in-flight software-timed RTS envelope before the port is
> +	 * powered down, so the last character and its after-send hold complete
> +	 * - close() can reach shutdown with data still queued and a before-send
> +	 * delay pending. The loop ends when the envelope does (tx_state == OFF);
> +	 * the bound is just a worst-case safety cap. Then stop the timer and work
> +	 * so neither runs against a powered-off port.
> +	 */
> +	if (one->sw_rts_during_tx) {
> +		unsigned int loops = port->rs485.delay_rts_before_send +
> +			    port->rs485.delay_rts_after_send +
> +			    DIV_ROUND_UP_ULL((kfifo_len(&port->state->port.xmit_fifo) +
> +					      port->fifosize) *
> +					     ktime_to_us(one->one_character_duration),
> +					     USEC_PER_MSEC);
> +
> +		while (READ_ONCE(one->tx_state) != MAX310X_TX_OFF && loops-- > 0)
> +			fsleep(USEC_PER_MSEC);

[Severity: Critical]
Is there a risk of a hung task panic here? If a user configures a very low
baud rate (like 50 baud), this loop could execute an uninterruptible sleep
via fsleep() for a very long time (e.g., several minutes) while the FIFO
drains. Should this be interruptible, or bound by a shorter maximum delay?

> +
> +		one->cancel_tx_delay_tmr = true;
> +		hrtimer_cancel(&one->tx_delay_tmr);
> +		cancel_work_sync(&one->rts_work);
> +		WRITE_ONCE(one->tx_state, MAX310X_TX_OFF);

[Severity: High]
Since tx_work isn't synchronized during shutdown, could a pending tx_work
execute after max310x_shutdown() completes, call max310x_delayed_stop_tx(),
and unconditionally set one->cancel_tx_delay_tmr = false to re-arm the timer?
If this happens after device memory is freed, could it cause a use-after-free
panic when the timer fires?

> +	} else {
> +		/*
> +		 * HW auto-RTS path: the tty layer waits for tx_empty before
> +		 * close(), but tx_empty only reflects the chip TX FIFO - the
> +		 * last character may still be in the transmit shift register.
> +		 * Let the FIFO drain and the final character clock out before
> +		 * the port is powered down, otherwise close() truncates the last
> +		 * byte on the wire as the chip auto-RTS turnaround clips it.
> +		 */
> +		unsigned int loops = port->fifosize + 1;
> +
> +		while (!max310x_tx_empty(port) && loops-- > 0)
> +			fsleep(ktime_to_us(one->one_character_duration));

[Severity: Critical]
Similarly, could this fallback hardware auto-RTS path also cause an unbounded
uninterruptible sleep at very low baud rates, potentially exceeding the
hung task limit?

> @@ -1425,6 +1733,11 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
[ ... ]
>  		INIT_WORK(&s->p[i].md_work, max310x_md_proc);
>  		/* Initialize queue for changing RS485 mode */
>  		INIT_WORK(&s->p[i].rs_work, max310x_rs_proc);
> +		/* Initialize queue for software-driven RTS toggling */
> +		INIT_WORK(&s->p[i].rts_work, max310x_rts_work_proc);
> +		hrtimer_setup(&s->p[i].tx_delay_tmr, max310x_tmr_tx,
> +			      CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> +		s->p[i].tx_state = MAX310X_TX_OFF;
>  	}

[Severity: High]
If max310x_probe() fails for a subsequent port and jumps to the out_uart
error label, is it possible we leak the newly initialized tx_delay_tmr and
rts_work for already registered ports? It doesn't appear they are cancelled
in the probe error path before freeing memory.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260914-max310x-rs485-sw-delay-v2-1-9a7b681e64ce@vaisala.com?part=1

  reply	other threads:[~2026-09-14  7:53 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-14  7:37 [PATCH RESEND v2] serial: max310x: drive RTS in software when hardware delays are too short Tapio Reijonen
2026-09-14  7:53 ` sashiko-bot [this message]
2026-09-14  9:10 ` Tapio Reijonen

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=20260914075346.87FE01F00893@smtp.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