Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: zjzhao <zjzhao@edatec.cn>
Cc: linux-serial@vger.kernel.org, robh@kernel.org,
	devicetree@vger.kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver
Date: Fri, 04 Sep 2026 07:35:17 +0000	[thread overview]
Message-ID: <20260904073517.6B8061F00A3E@smtp.kernel.org> (raw)
In-Reply-To: <20260904072027.67473-2-zjzhao@edatec.cn>

Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] The IDA line number is freed before the UART port is unregistered, allowing an ID reuse race condition.
- [High] Closing a single UART port unconditionally stops the shared polling loop, breaking all other active ports on the same SPI chip.
- [High] Ignored SPI read errors leave stack variables uninitialized, leading to undefined control flow and leakage of stale buffer data to user-space.
- [High] Ports are registered and exposed to user-space before the IRQ handler and polling infrastructure are initialized.
- [High] Unprotected access to serial core data structures (xmit_fifo, x_char, icount) without holding port->lock causes data races and severe kfifo corruption.
- [High] SPI transfer buffers are allocated on the stack, violating DMA-safety requirements and potentially causing memory corruption or panics.
- [High] Late devm IRQ teardown causes Use-After-Free of port->state and kworker task_struct if an interrupt fires during manual port removal.
- [High] Polling mode deadlocks the kworker thread permanently upon the first TX FIFO drain.
- [High] 64-bit division using the `/` operator will break kernel builds on 32-bit architectures.
- [Medium] The shared IRQ handler unconditionally returns IRQ_HANDLED, breaking shared interrupt semantics.
- [Medium] Standard termios CREAD (Receiver Enable) flag is incorrectly evaluated, failing to drop normal characters.
- [Medium] RX error flags from FSR are incorrectly applied to every character in the burst read.
- [Medium] wk2xxx_throttle() fails to disable the RX timeout interrupt, defeating flow control.
- [Medium] Incorrect implementation of unsupported RS485 delays.
--

commit fa4922eb42a208a55c46770ba645376285db8163
Author: zjzhao <zjzhao@edatec.cn>
Subject: serial: wk2xxx: Add WK2xxx SPI UART driver

This commit adds a new serial driver for the WK2xxx SPI to UART bridge ICs.
It supports up to four full-duplex UART channels over a single SPI slave
interface, with features like hardware flow control and RS485, using a
kthread worker for register accesses.

> diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c
> new file mode 100644
> index 0000000000000..184bef9b0fa81
> --- /dev/null
> +++ b/drivers/tty/serial/wk2xxx.c
[ ... ]
> +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val)
> +{
> +	u8 tx[2] = { WK2XXX_SPI_READ | addr, 0 };
> +	u8 rx[2] = { 0, 0 };
> +	int ret;
> +
> +	ret = wk2xxx_spi_transfer(s, tx, rx, sizeof(tx));

[Severity: High]
Is it safe to pass stack-allocated buffers to spi_sync() here and in the
fifo access functions?

Stack memory is typically not DMA-safe and can lack cacheline alignment,
potentially causing memory corruption or panics on cache-incoherent
architectures.

[ ... ]
> +static void wk2xxx_throttle(struct uart_port *port)
> +{
> +	unsigned long flags;
> +
> +	/* Stop draining the RX FIFO to apply back-pressure. */
> +	uart_port_lock_irqsave(port, &flags);
> +	wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT);
> +	uart_port_unlock_irqrestore(port, flags);
> +}

[Severity: Medium]
Does this sequence fully apply back-pressure?

The RX timeout interrupt (WK2XXX_SIER_RXOUT_IEN_BIT) remains enabled. If any
bytes remain in the hardware FIFO, the timeout interrupt will eventually fire
and push more characters, defeating the flow control request.

[ ... ]
> +static void wk2xxx_handle_tx(struct uart_port *port)
> +{
[ ... ]
> +	to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen);
> +	sent = to_send;
> +	while (to_send) {
> +		unsigned int chunk = min_t(unsigned int, to_send,
> +					   WK2XXX_MAX_SPI_LEN);
> +
> +		wk2xxx_fifo_write(s, portno, tail, chunk);
> +		tail += chunk;
> +		to_send -= chunk;
> +	}
> +	uart_xmit_advance(port, sent);

[Severity: High]
Does this lockless access to tport->xmit_fifo expose the buffer to races?

Because wk2xxx_fifo_write() can sleep, a concurrent flush operation could
acquire port->lock and reset the kfifo. If that happens, the subsequent
lockless uart_xmit_advance() could increment the fifo out index beyond the
in index, leading to underflows and heap corruption.

[ ... ]
> +static void wk2xxx_handle_rx(struct uart_port *port)
> +{
[ ... ]
> +	u8 fsr, rfcnt, lsr = 0, flag = TTY_NORMAL;
> +
> +	wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr);
[ ... ]
> +	wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt);

[Severity: High]
If the SPI transaction fails here, the return value is ignored.

Could this leave rfcnt uninitialized, using its stale value to dictate how
much memory is read? Since wk2xxx_fifo_read() can also fail silently, this
might push unmodified, stale heap data directly to user-space.

> +	/* Map the FIFO status register error flags to line status. */
> +	if (fsr & WK2XXX_FSR_ERR_MASK) {
[ ... ]
> +	}
> +
> +	port->icount.rx += rxlen;
> +
> +	for (i = 0; i < rxlen; ++i) {
> +		u8 ch = one->buf[i];
[ ... ]
> +		uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag);
> +	}

[Severity: Medium]
Does reading fsr once and applying the exact same lsr error flags to every
character in the loop incorrectly flag valid characters as corrupted?

A single corrupted byte in the hardware FIFO could cause the entire batch
to be marked with framing, parity, or overrun errors.

[ ... ]
> +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno)
> +{
> +	struct uart_port *port = &s->p[portno].port;
> +	u8 sifr, sier;
> +	bool rc = false;
> +
> +	wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr);
> +	wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier);

[Severity: High]
If the SPI transaction fails here, the return value is ignored.

Could this leave sifr and sier uninitialized, causing undefined behavior
and spurious interrupt handling based on stack garbage?

[ ... ]
> +static irqreturn_t wk2xxx_irq(int irq, void *dev_id)
> +{
[ ... ]
> +		for (i = 0; i < s->devtype->nr_uart; ++i)
> +			if (gifr & BIT(i))
> +				keep_polling |= wk2xxx_port_irq(s, i);
> +	} while (keep_polling);
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
If keep_polling evaluates to false on the first iteration, this function
unconditionally returns IRQ_HANDLED even though no device interrupt was
pending.

Will this mask unhandled interrupts and break semantics for other
devices sharing the IRQ line?

[ ... ]
> +static void wk2xxx_poll_proc(struct kthread_work *ws)
> +{
> +	struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port,
> +					     poll_work.work);
> +
> +	/* Reuse the IRQ handler; the interrupt ID is unused here. */
> +	wk2xxx_irq(0, s);
> +
> +	kthread_queue_delayed_work(&s->kworker, &s->poll_work,
> +				   msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS));
> +}

[Severity: High]
Can this lead to a permanent deadlock when the TX FIFO drains?

The wk2xxx_poll_proc runs on s->kworker and processes the loop until the
device is idle. When TX empties, wk2xxx_handle_tx attempts to disable the
interrupt by queuing reg_work via wk2xxx_ier_clear to the very same
s->kworker. Since the worker is already blocked waiting for the interrupt
to clear in the keep_polling loop, reg_work never runs, creating an
infinite loop.

[ ... ]
> +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios,
> +			       struct serial_rs485 *rs485)
> +{
[ ... ]
> +		if (rs485->delay_rts_after_send)
> +			return -EINVAL;

[ ... ]
> +static const struct serial_rs485 wk2xxx_rs485_supported = {
> +	.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
> +		 SER_RS485_RTS_AFTER_SEND,
> +	.delay_rts_before_send = 1,
> +	.delay_rts_after_send = 1,	/* Not supported but keep returning -EINVAL */
> +};

[Severity: Medium]
Is it intentional to advertise up to 1ms of delay_rts_after_send support to
the serial core, but then explicitly return -EINVAL in wk2xxx_config_rs485
when that delay is requested?

This mismatch causes valid user-space ioctls to fail unexpectedly.

[ ... ]
> +static unsigned int wk2xxx_tx_empty(struct uart_port *port)
> +{
> +	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
> +	u8 fsr;
> +
> +	wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr);
> +
> +	return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 :
> +		TIOCSER_TEMT;
> +}

[Severity: High]
Like the previous read functions, SPI failures here are ignored.

Will this leave fsr uninitialized and evaluate arbitrary stack garbage in
the ternary logic?

[ ... ]
> +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud,
> +				u8 *baud0, u8 *baud1, u8 *pres)
> +{
[ ... ]
> +	*pres = ((unsigned long long)(clk % (baud * 16)) * 100 / baud + 50) / 100;
> +}

[Severity: High]
Can this division cause link failures on 32-bit architectures?

The code casts clk to a 64-bit unsigned long long and then divides by the
32-bit baud using the native / operator, which breaks 32-bit builds lacking
libgcc.

[ ... ]
> +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios,
> +			       const struct ktermios *old)
> +{
[ ... ]
> +	/* Set status ignore mask. */
> +	port->ignore_status_mask = 0;
> +	if (termios->c_iflag & IGNBRK)
> +		port->ignore_status_mask |= WK2XXX_LSR_BI_BIT;
> +	if (!(termios->c_cflag & CREAD))
> +		port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK;

[Severity: Medium]
Does this correctly filter out characters when the receiver is disabled via
CREAD?

By setting only error bits in the ignore mask, standard characters with a
successful line status (lsr == 0) will bypass the filter in
wk2xxx_handle_rx and continue being pushed to the TTY layer.

[ ... ]
> +static void wk2xxx_shutdown(struct uart_port *port)
> +{
[ ... ]
> +	if (s->polling)
> +		kthread_cancel_delayed_work_sync(&s->poll_work);

[Severity: High]
Since s->poll_work is shared across all UART ports on the SPI chip, won't
shutting down a single port permanently cancel the shared polling loop?

This appears to starve all other concurrent open ports on the same device
of TX and RX events.

[ ... ]
> +static int wk2xxx_probe(struct spi_device *spi)
> +{
[ ... ]
> +		/* Register port. */
> +		ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port);
> +		if (ret)
> +			goto out_ports;
> +
> +		port_registered[i] = true;
> +	}
> +
> +	if (spi->irq <= 0) {
[ ... ]
> +	ret = devm_request_threaded_irq(dev, spi->irq, NULL, wk2xxx_irq,

[Severity: High]
Does exposing the port to user-space before its interrupt handler is
requested create a race condition?

If the port is opened immediately after uart_add_one_port, wk2xxx_startup
will unmask IC hardware interrupts before the handler is actually registered,
potentially causing unhandled IRQ storms that disable the shared line.

[ ... ]
> +out_ports:
> +	for (i = 0; i < devtype->nr_uart; i++) {
> +		if (s->p[i].port.line < WK2XXX_MAX_DEVS)
> +			ida_free(&wk2xxx_lines, s->p[i].port.line);
> +		if (port_registered[i])
> +			uart_remove_one_port(&wk2xxx_uart, &s->p[i].port);
> +	}

[Severity: High]
Does freeing the IDA line before unregistering the port expose a race
condition?

Releasing the ID early could allow a concurrent probe to claim the same
line ID before uart_remove_one_port is fully processed, leading to
subsystem corruption.

[ ... ]
> +static void wk2xxx_remove(struct spi_device *spi)
> +{
> +	struct wk2xxx_port *s = dev_get_drvdata(&spi->dev);
> +	int i;
> +
> +	for (i = 0; i < s->devtype->nr_uart; i++) {
> +		ida_free(&wk2xxx_lines, s->p[i].port.line);
> +		uart_remove_one_port(&wk2xxx_uart, &s->p[i].port);
> +	}

[Severity: High]
Along with the IDA freeing issue mentioned above, is there a use-after-free
risk here if a hardware interrupt fires?

The IRQ was requested via the managed devm_request_threaded_irq API, so it
stays active until after this function returns and devres cleanup runs.
However, uart_remove_one_port frees the port state manually, meaning a late
interrupt could deference freed memory.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904072027.67473-1-zjzhao@edatec.cn?part=1

  reply	other threads:[~2026-09-04  7:35 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04  7:20 [PATCH 0/2] WK2xxx SPI to UART bridge driver zjzhao
2026-09-04  7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
2026-09-04  7:35   ` sashiko-bot [this message]
2026-09-04  8:17   ` Jiri Slaby
2026-09-04  9:32     ` zjzhao
2026-09-04  7:20 ` [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao
2026-09-04  7:28   ` sashiko-bot
2026-09-04  9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao
2026-09-04  9:33   ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
2026-09-04  9:53     ` sashiko-bot
2026-09-04  9:33   ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao
2026-09-04  9:40     ` sashiko-bot
2026-09-05  7:31     ` Krzysztof Kozlowski
2026-09-04 10:52   ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao
2026-09-04 10:52     ` [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
2026-09-04 11:10       ` sashiko-bot
2026-09-04 10:52     ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao
2026-09-04 11:12       ` sashiko-bot
2026-09-04 15:28       ` Conor Dooley
2026-09-05  7:32       ` Krzysztof Kozlowski

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=20260904073517.6B8061F00A3E@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=zjzhao@edatec.cn \
    /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