Thread (13 messages) flat view 13 messages, 2 authors, 18h ago

Re: [PATCH net-next v11 4/4] net: pse-pd: realtek-pse-mcu: add UART transport

From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: 2026-08-06 08:59:19
Also in: linux-devicetree, lkml


On 06.08.26 03:11, Jakub Kicinski wrote:
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: pse-pd: realtek-pse-mcu: add UART transport

This adds a serdev (UART) transport for the Realtek PSE MCU core, with
send/recv callbacks that exchange the 12-byte frames and an asynchronous
receive_buf callback. The baud rate defaults to 19200 and can be overridden
with the "current-speed" property.
quoted
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
new file mode 100644
index 0000000000000..9baa17d8d31fa
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
@@ -0,0 +1,164 @@
[ ... ]
quoted
+/*
+ * No framing is done here: a glitched frame costs one transaction, then
+ * the next _send re-frames from rx_len 0. Resync works by returning count
+ * (not take), dropping any overflow so serdev keeps no leftover to bleed
+ * into the next frame.
+ */
+static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev,
+				     const u8 *buf, size_t count)
+{
+	struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev);
+	size_t take;
+
+	scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+		take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len);
+		if (take) {
+			memcpy(ctx->rx_buf + ctx->rx_len, buf, take);
+			ctx->rx_len += take;
+			if (ctx->rx_len == sizeof(ctx->rx_buf))
+				complete(&ctx->rx_done);
+		}
+	}
+
+	/* consume all to avoid desync/misalignment */
+	return count;
+}
[ ... ]
quoted
+static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+	struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+	int written;
+
+	/* clear any leftover rx state before transmitting */
+	scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+		reinit_completion(&ctx->rx_done);
+		ctx->rx_len = 0;
+	}
[ ... ]
quoted
+static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse,
+			       const struct rtpse_mcu_msg *req,
+			       struct rtpse_mcu_msg *resp)
+{
+	struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse);
+
+	if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT))
+		return -ETIMEDOUT;
+
+	scoped_guard(spinlock_irqsave, &ctx->rx_lock) {
+		if (ctx->rx_len != sizeof(*resp))
+			return -EIO;
+
+		memcpy(resp, ctx->rx_buf, sizeof(*resp));
+	}
+	return 0;
+}
[Severity: Medium]
Should rtpse_mcu_uart_recv() apply rtpse_mcu_resp_is_final() before
accepting a frame?  The req argument is unused here, so the first complete
12-byte frame becomes the transaction result no matter what it contains.

realtek-pse-mcu.h describes one of those frames as transient:

  /*
   * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them).
   * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient.
   */
  #define RTPSE_MCU_OPCODE_NOT_READY		0xff	/* -EAGAIN  */

and the I2C transport keeps polling until the acceptance predicate holds,
so a NOT_READY frame never escapes the transaction:

  drivers/net/pse-pd/realtek-pse-mcu-i2c.c:rtpse_mcu_i2c_smbus_recv() {
	for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
		...
		if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
			return 0;
	}
	return -ETIMEDOUT;
  }

With the UART transport, a NOT_READY frame is copied into resp and
rtpse_mcu_do_xfer() turns it into an error for the caller:

  drivers/net/pse-pd/realtek-pse-mcu-core.c:rtpse_mcu_do_xfer() {
	case RTPSE_MCU_OPCODE_NOT_READY:
		return -EAGAIN;
  }

Is any runtime caller prepared for that?  The only place -EAGAIN is retried
is the probe-time boot loop in rtpse_mcu_discover(); the pse_controller_ops
paths propagate it, so an ethtool PSE get/set would fail.

Note that "realtek,pse-mcu-gen1" is in rtpse_mcu_uart_of_match[], which is
the dialect documented as emitting these status opcodes.

Can the real reply also be lost in that case?  Once rx_len is 12 the
receive callback computes take == 0 and drops the following bytes; if they
instead arrive after the next rtpse_mcu_uart_send() has reset rx_len to 0
(the core only sleeps RTPSE_MCU_RESPONSE_MS = 25 ms between send and recv),
they are consumed as the next transaction's response and rejected by:

  if (resp->opcode != req->opcode ||
      resp->seq_num != req->seq_num ||
      resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1))
	return -EBADMSG;

failing a second consecutive operation.

Would it make sense to loop on wait_for_completion_timeout() against a
remaining-time budget until rtpse_mcu_resp_is_final(req, resp) holds?  As
written only the first frame is waited for, leaving most of the
RTPSE_MCU_RESPONSE_MAX_MS (1000 ms) receive budget unused.
Fixed in next version - uart_recv() now loops wait_for_completion_timeout()
against the remaining budget until resp_is_final(req, resp) holds, dropping
non-final frames (transient NOT_READY, or a stale frame from a prior
timeout), so it uses the full RESPONSE_MAX_MS and no longer surfaces a
transient frame as the transaction result.

Regards,
Jonas
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help