* Re: [PATCH net v2 2/2] vsock/test: add test for small packets under pressure
From: Bobby Eshleman @ 2026-07-09 19:48 UTC (permalink / raw)
To: Stefano Garzarella
Cc: netdev, Jason Wang, Xuan Zhuo, Eric Dumazet, Eugenio Pérez,
Simon Horman, Stefan Hajnoczi, David S. Miller, linux-kernel,
Michael S. Tsirkin, kvm, Paolo Abeni, virtualization,
Jakub Kicinski, Jason Wang
In-Reply-To: <20260708102904.50732-3-sgarzare@redhat.com>
On Wed, Jul 08, 2026 at 12:29:04PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
>
> Add a test that sends 2 MB of data using randomly sized small packets
> (129-512 bytes) over a SOCK_STREAM connection. Packets above
> GOOD_COPY_LEN (128) bypass the in-place coalescing in recv_enqueue(),
> forcing each one into its own skb.
>
> Without receive queue collapsing, the per-skb overhead eventually
> exceeds buf_alloc and the connection is reset. The test verifies
> that all data arrives and that content integrity is preserved.
>
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
> ---
> tools/testing/vsock/vsock_test.c | 87 ++++++++++++++++++++++++++++++++
> 1 file changed, 87 insertions(+)
>
> diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
> index 76be0e4a7f0e..b4ff9f946565 100644
> --- a/tools/testing/vsock/vsock_test.c
> +++ b/tools/testing/vsock/vsock_test.c
> @@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts)
> close(fd);
> }
>
> +/* Test that many small packets don't cause a connection reset under pressure
> + * and that data integrity is preserved. Packet sizes vary randomly between
> + * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing
> + * in recv_enqueue, forcing each one into its own skb. Without receive queue
> + * collapsing, the per-skb overhead eventually exceeds buf_alloc and the
> + * connection is reset.
> + */
> +#define COLLAPSE_PKT_MIN 129
> +#define COLLAPSE_PKT_MAX 512
> +#define COLLAPSE_TOTAL (2 * 1024 * 1024)
> +
> +static void test_stream_collapse_client(const struct test_opts *opts)
> +{
> + unsigned char *data;
> + unsigned long hash;
> + size_t offset = 0;
> + int i, fd;
> +
> + data = malloc(COLLAPSE_TOTAL);
> + if (!data) {
> + perror("malloc");
> + exit(EXIT_FAILURE);
> + }
> +
> + for (i = 0; i < COLLAPSE_TOTAL; i++)
> + data[i] = rand() & 0xff;
> +
> + fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
> + if (fd < 0) {
> + perror("connect");
> + exit(EXIT_FAILURE);
> + }
> +
> + while (offset < COLLAPSE_TOTAL) {
> + size_t pkt_size = COLLAPSE_PKT_MIN +
> + rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1);
> +
> + pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset);
> +
> + send_buf(fd, data + offset, pkt_size, 0, pkt_size);
> + offset += pkt_size;
> + }
> +
> + hash = hash_djb2(data, COLLAPSE_TOTAL);
> + control_writeulong(hash);
> +
> + free(data);
> + close(fd);
> +}
> +
> +static void test_stream_collapse_server(const struct test_opts *opts)
> +{
> + unsigned long hash, remote_hash;
> + unsigned char *data;
> + int fd;
> +
> + data = malloc(COLLAPSE_TOTAL);
> + if (!data) {
> + perror("malloc");
> + exit(EXIT_FAILURE);
> + }
> +
> + fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
> + if (fd < 0) {
> + perror("accept");
> + exit(EXIT_FAILURE);
> + }
> +
> + recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL);
> +
> + hash = hash_djb2(data, COLLAPSE_TOTAL);
> + remote_hash = control_readulong();
> + if (hash != remote_hash) {
> + fprintf(stderr, "hash mismatch: local %lu remote %lu\n",
> + hash, remote_hash);
> + exit(EXIT_FAILURE);
> + }
> +
> + free(data);
> + close(fd);
> +}
> +
> static struct test_case test_cases[] = {
> {
> .name = "SOCK_STREAM connection reset",
> @@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = {
> .run_client = test_stream_msg_peek_client,
> .run_server = test_stream_peek_after_recv_server,
> },
> + {
> + .name = "SOCK_STREAM small packets backpressure",
> + .run_client = test_stream_collapse_client,
> + .run_server = test_stream_collapse_server,
> + },
> {},
> };
>
> --
> 2.55.0
>
Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply
* Re: [PATCH net-next v4 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
From: Johan Alvarado @ 2026-07-09 19:44 UTC (permalink / raw)
To: Mieczyslaw Nalewaj
Cc: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux, luizluca, maxime.chevallier, netdev, linux-kernel
In-Reply-To: <276c436f-696c-4301-831e-6a79e4756905@yahoo.com>
On 7/9/2026 5:02 PM, Mieczyslaw Nalewaj wrote:
> Performance degradation without setting these registers can only be
> observed in HSGMII mode with simultaneous load on multiple LAN ports.
>
> This was noticed during work on adding support for the Mercusys MR85X
> router with DSA to OpenWrt: https://github.com/openwrt/openwrt/pull/19445#issuecomment-4505613294
Thanks, that is exactly the measurement I was hoping for. The ~1020
Mbit/s combined ceiling reported there with multiple clients sits
right where the 0x1FFFF reset default (~1.048 Gbps of wire rate)
would put it, and the jump to ~2 Gbit/s once the limiters are raised
settles the open question: the reset default is a real cap, and the
"disable" wording in the vendor comment does not match the silicon.
It also confirms why neither of us could see it with a single client:
one 1G port never reaches the limit.
I have updated v5 accordingly: the code comment and commit message
now state the cap as observed instead of hedging between the two
readings, and patch 2 carries a Link to that GitHub comment as the
reference for the measurement. I will send the series shortly.
Best regards,
Johan
^ permalink raw reply
* [PATCH net-next v6 4/4] net: pse-pd: realtek-pse-mcu: add UART transport
From: Jonas Jelonek @ 2026-07-09 19:41 UTC (permalink / raw)
To: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork,
Jonas Jelonek
In-Reply-To: <20260709194125.2784507-1-jelonek.jonas@gmail.com>
Add the serdev (UART) transport for the Realtek PSE MCU core. It registers
the MCU as a serdev device and provides the send/recv callbacks the core
uses to exchange the 12-byte frames, receiving asynchronously via the
serdev receive_buf callback.
The baud rate defaults to 19200 and can be overridden per board with the
"current-speed" property.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
---
drivers/net/pse-pd/Kconfig | 11 ++
drivers/net/pse-pd/Makefile | 1 +
drivers/net/pse-pd/realtek-pse-mcu-uart.c | 155 ++++++++++++++++++++++
3 files changed, 167 insertions(+)
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-uart.c
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 6d14c8832e8b..a0f2ae668c67 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -30,6 +30,17 @@ config PSE_REALTEK_MCU_I2C
PSE silicon is not accessed directly. To compile this driver as a
module, choose M here: the module will be called realtek-pse-mcu-i2c.
+config PSE_REALTEK_MCU_UART
+ tristate "Realtek PSE MCU driver (UART transport)"
+ depends on SERIAL_DEV_BUS
+ select PSE_REALTEK_MCU
+ help
+ Driver for the microcontroller (MCU) that fronts the PSE
+ hardware on various Realtek-based managed switches, attached
+ via UART. The MCU exposes a message-based protocol; the actual PSE
+ silicon is not accessed directly. To compile this driver as a
+ module, choose M here: the module will be called realtek-pse-mcu-uart.
+
config PSE_REGULATOR
tristate "Regulator based PSE controller"
help
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index ef869bba5ed9..9cca5900fe34 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
+obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
obj-$(CONFIG_PSE_SI3474) += si3474.o
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 000000000000..91cadb5569ab
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/serdev.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_UART_BAUD_DEFAULT 19200
+#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100)
+#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS)
+
+struct rtpse_mcu_uart {
+ struct rtpse_mcu_ctrl pse;
+ struct serdev_device *serdev;
+ struct completion rx_done;
+ spinlock_t rx_lock; /* protects rx_buf and rx_len */
+ size_t rx_len;
+ u8 rx_buf[RTPSE_MCU_MSG_SIZE];
+};
+
+#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse)
+
+/*
+ * 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;
+}
+
+static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = {
+ .receive_buf = rtpse_mcu_uart_receive,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+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;
+ }
+
+ written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req),
+ RTPSE_MCU_UART_TX_TIMEOUT);
+ if (written < 0)
+ return written;
+ if (written != sizeof(*req))
+ return -EIO;
+
+ return 0;
+}
+
+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;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = {
+ .send = rtpse_mcu_uart_send,
+ .recv = rtpse_mcu_uart_recv,
+};
+
+static int rtpse_mcu_uart_probe(struct serdev_device *serdev)
+{
+ u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT;
+ struct device *dev = &serdev->dev;
+ struct rtpse_mcu_uart *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->serdev = serdev;
+ ctx->pse.dev = dev;
+ ctx->pse.pcdev.owner = THIS_MODULE;
+ ctx->pse.transport = &rtpse_mcu_uart_transport_ops;
+ init_completion(&ctx->rx_done);
+ spin_lock_init(&ctx->rx_lock);
+
+ serdev_device_set_drvdata(serdev, ctx);
+ serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops);
+
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to open serdev\n");
+
+ fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed);
+ serdev_device_set_baudrate(serdev, speed);
+ serdev_device_set_flow_control(serdev, false);
+ serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+
+ return rtpse_mcu_register(&ctx->pse);
+}
+
+static const struct of_device_id rtpse_mcu_uart_of_match[] = {
+ { .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data },
+ { .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match);
+
+static struct serdev_device_driver rtpse_mcu_uart_driver = {
+ .driver = {
+ .name = "realtek-pse-mcu-uart",
+ .of_match_table = rtpse_mcu_uart_of_match,
+ },
+ .probe = rtpse_mcu_uart_probe,
+};
+module_serdev_device_driver(rtpse_mcu_uart_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 3/4] net: pse-pd: realtek-pse-mcu: add I2C transport
From: Jonas Jelonek @ 2026-07-09 19:41 UTC (permalink / raw)
To: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork,
Jonas Jelonek
In-Reply-To: <20260709194125.2784507-1-jelonek.jonas@gmail.com>
Add the I2C/SMBus transport for the Realtek PSE MCU core. It registers
the MCU on an I2C bus and provides the send/recv callbacks the core
uses to exchange the 12-byte frames.
The MCU firmware expects one of two framings on the I2C bus, and which one
is part of the compatible: '-smbus' (reads carry a leading command byte
and a repeated start) or raw '-i2c' (bare block writes and reads). The
match data flags the raw-I2C case; SMBus is the default.
Because i2c_master_send()/i2c_master_recv() may DMA, the raw-I2C path
bounces each frame through a heap buffer rather than the core's stack
buffers; the SMBus path is unaffected.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
---
drivers/net/pse-pd/Kconfig | 11 ++
drivers/net/pse-pd/Makefile | 1 +
drivers/net/pse-pd/realtek-pse-mcu-i2c.c | 171 +++++++++++++++++++++++
3 files changed, 183 insertions(+)
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-i2c.c
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 3b0c245a2bc7..6d14c8832e8b 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -19,6 +19,17 @@ config PSE_REALTEK_MCU
Shared core for the Realtek PSE MCU driver. This is selected
automatically by the transport options below.
+config PSE_REALTEK_MCU_I2C
+ tristate "Realtek PSE MCU driver (I2C transport)"
+ depends on I2C
+ select PSE_REALTEK_MCU
+ help
+ Driver for the microcontroller (MCU) that fronts the PSE
+ hardware on various Realtek-based managed switches, attached
+ via I2C/SMBus. The MCU exposes a message-based protocol; the actual
+ PSE silicon is not accessed directly. To compile this driver as a
+ module, choose M here: the module will be called realtek-pse-mcu-i2c.
+
config PSE_REGULATOR
tristate "Regulator based PSE controller"
help
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index bf35e2a5b110..ef869bba5ed9 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
+obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
obj-$(CONFIG_PSE_SI3474) += si3474.o
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
new file mode 100644
index 000000000000..5717684c0cbd
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "realtek-pse-mcu.h"
+
+/*
+ * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so
+ * the response is normally ready on the very first read. For commands the
+ * MCU produces more slowly, keep polling at the typical response cadence
+ * up to the worst-case ceiling.
+ */
+#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS
+#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)
+
+static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+
+ /* Send opcode as SMBus command byte; remaining 11 bytes as block data */
+ return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1,
+ (u8 *)req + 1);
+}
+
+static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ int tries, ret;
+
+ for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+ if (tries > 0)
+ msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+ /* MCU needs 0x00 as command byte for read */
+ ret = i2c_smbus_read_i2c_block_data(client, 0x00,
+ RTPSE_MCU_MSG_SIZE,
+ (u8 *)resp);
+ if (ret < 0)
+ return ret;
+ if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
+ return 0;
+ }
+
+ return -ETIMEDOUT;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {
+ .send = rtpse_mcu_i2c_smbus_send,
+ .recv = rtpse_mcu_i2c_smbus_recv,
+};
+
+static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ void *buf;
+ int ret;
+
+ /* i2c_master_send() may DMA, so the buffer must not be on the stack. */
+ buf = kmemdup(req, RTPSE_MCU_MSG_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = i2c_master_send(client, buf, RTPSE_MCU_MSG_SIZE);
+ kfree(buf);
+ if (ret < 0)
+ return ret;
+ return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;
+}
+
+static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ struct i2c_client *client = to_i2c_client(pse->dev);
+ int tries, ret;
+ u8 *buf;
+
+ /* i2c_master_recv() may DMA, so read into an off-stack buffer. */
+ buf = kmalloc(RTPSE_MCU_MSG_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
+ if (tries > 0)
+ msleep(RTPSE_MCU_I2C_RETRY_MS);
+
+ ret = i2c_master_recv(client, buf, RTPSE_MCU_MSG_SIZE);
+ if (ret < 0)
+ goto out;
+ if (ret == RTPSE_MCU_MSG_SIZE) {
+ memcpy(resp, buf, RTPSE_MCU_MSG_SIZE);
+ if (rtpse_mcu_resp_is_final(req, resp)) {
+ ret = 0;
+ goto out;
+ }
+ }
+ }
+ ret = -ETIMEDOUT;
+out:
+ kfree(buf);
+ return ret;
+}
+
+static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {
+ .send = rtpse_mcu_i2c_native_send,
+ .recv = rtpse_mcu_i2c_native_recv,
+};
+
+static int rtpse_mcu_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct rtpse_mcu_match_data *match;
+ struct rtpse_mcu_ctrl *pse;
+ bool use_native;
+
+ match = device_get_match_data(dev);
+ if (!match)
+ return dev_err_probe(dev, -ENODEV, "missing match data\n");
+
+ /* The framing (raw I2C vs SMBus) is carried by the match data. */
+ use_native = match->native_i2c;
+
+ if (use_native) {
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "plain-I2C MCU protocol requires I2C-capable adapter\n");
+ } else {
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "SMBus MCU protocol requires SMBus I2C-block support\n");
+ }
+
+ pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);
+ if (!pse)
+ return -ENOMEM;
+
+ pse->dev = dev;
+ pse->pcdev.owner = THIS_MODULE;
+ pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops;
+
+ return rtpse_mcu_register(pse);
+}
+
+static const struct of_device_id rtpse_mcu_i2c_of_match[] = {
+ { .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data },
+ { .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data },
+ { .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);
+
+static struct i2c_driver rtpse_mcu_i2c_driver = {
+ .driver = {
+ .name = "realtek-pse-mcu-i2c",
+ .of_match_table = rtpse_mcu_i2c_of_match,
+ },
+ .probe = rtpse_mcu_i2c_probe,
+};
+module_i2c_driver(rtpse_mcu_i2c_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 2/4] net: pse-pd: add Realtek PSE MCU core
From: Jonas Jelonek @ 2026-07-09 19:41 UTC (permalink / raw)
To: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork,
Jonas Jelonek
In-Reply-To: <20260709194125.2784507-1-jelonek.jonas@gmail.com>
A range of PoE switches use a small microcontroller on the PCB to front
the actual PSE silicon. The host CPU talks to that MCU over I2C/SMBus or
UART using a fixed 12-byte request/response protocol with a trailing
checksum; the PSE chips are managed by the MCU and are not accessed
directly. Two generations of the protocol exist - both Realtek's -
diverging in opcode numbering and a few response layouts; the driver
handles this with a per-dialect opcode table and parser hooks for the
responses that differ, selected by the compatible. The specific PSE chip
behind the MCU is detected at runtime and only influences per-chip
constants (power scaling and the per-port cap).
This core module implements the protocol, message framing, the dialect
machinery and the pse_controller_ops glue, and exports a registration
helper for transport modules. The I2C and UART transports that drive it
follow in the next patches; the core (PSE_REALTEK_MCU) is selected
automatically by those transports and is not user-selectable on its own.
The realtek-pse-mcu-* files and PSE_REALTEK_MCU* symbols match the
realtek,pse-mcu-* compatibles (see the binding for the naming rationale).
The two protocol generations - gen1 on older Broadcom-PSE boards, gen2 on
Realtek's own PSE silicon - are both Realtek's, handled by the same shared
core, each selecting its dialect via the compatible.
Power budgeting is left to the MCU firmware; the driver advertises
PSE_BUDGET_EVAL_STRAT_DYNAMIC (controller-managed budget) accordingly.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
---
MAINTAINERS | 7 +
drivers/net/pse-pd/Kconfig | 6 +
drivers/net/pse-pd/Makefile | 1 +
drivers/net/pse-pd/realtek-pse-mcu-core.c | 993 ++++++++++++++++++++++
drivers/net/pse-pd/realtek-pse-mcu.h | 90 ++
5 files changed, 1097 insertions(+)
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-core.c
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99..3b21e46be68a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22716,6 +22716,13 @@ S: Maintained
F: include/sound/rt*.h
F: sound/soc/codecs/rt*
+REALTEK PSE MCU DRIVER
+M: Jonas Jelonek <jelonek.jonas@gmail.com>
+L: netdev@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
+F: drivers/net/pse-pd/realtek-pse-mcu*
+
REALTEK OTTO WATCHDOG
M: Sander Vanheule <sander@svanheule.net>
L: linux-watchdog@vger.kernel.org
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index 7ef29657ee5d..3b0c245a2bc7 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -13,6 +13,12 @@ menuconfig PSE_CONTROLLER
if PSE_CONTROLLER
+config PSE_REALTEK_MCU
+ tristate
+ help
+ Shared core for the Realtek PSE MCU driver. This is selected
+ automatically by the transport options below.
+
config PSE_REGULATOR
tristate "Regulator based PSE controller"
help
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index cc78f7ea7f5f..bf35e2a5b110 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
+obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
obj-$(CONFIG_PSE_SI3474) += si3474.o
diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c
new file mode 100644
index 000000000000..0a5bb784360a
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c
@@ -0,0 +1,993 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for the microcontroller (MCU) fronting PSE silicon on various
+ * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame
+ * management protocol; this driver covers two generations of the
+ * protocol via a per-dialect opcode table and response parsers.
+ *
+ * Many PoE switch designs put a dedicated microcontroller in front of the
+ * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or
+ * UART, and the MCU in turn manages the PSE chips on the board. The MCU
+ * speaks a small message-based protocol. The PSE chips themselves are not
+ * accessed directly; everything goes through MCU commands.
+ *
+ * This driver targets that architecture for the Realtek-family protocol.
+ * Two generations are supported: Gen1 being used on older switches where
+ * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with
+ * Realtek PSE silicon. The two share frame format and a sum-mod-256
+ * checksum but diverge on opcode numbers and on a few response layouts;
+ * this is handled by the per-dialect opcode table and parser hooks.
+ *
+ * Out of scope: PSE chips that are interfaced directly from the host
+ * without a management MCU, MCU designs that speak an unrelated protocol
+ * family, and "dumb PSE" modes where no host control is wired up at all.
+ *
+ * This core module implements the protocol, decoding/encoding of MCU
+ * responses, and the pse_controller_ops integration. Transport modules
+ * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv
+ * callbacks.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/regulator/consumer.h>
+#include <linux/unaligned.h>
+
+#include "realtek-pse-mcu.h"
+
+#define RTPSE_MCU_DEVICE_ID_RTL8238B 0x0138
+#define RTPSE_MCU_DEVICE_ID_RTL8239 0x0039
+#define RTPSE_MCU_DEVICE_ID_RTL8239C 0x0139
+#define RTPSE_MCU_DEVICE_ID_BCM59111 0xe111
+#define RTPSE_MCU_DEVICE_ID_BCM59121 0xe121
+
+#define RTPSE_MCU_PORT_STS_DISABLED 0x00
+#define RTPSE_MCU_PORT_STS_SEARCHING 0x01
+#define RTPSE_MCU_PORT_STS_DELIVERING 0x02
+#define RTPSE_MCU_PORT_STS_TEST 0x03 /* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_FAULT 0x04
+#define RTPSE_MCU_PORT_STS_OTHER_FAULT 0x05 /* Gen1-only; reserved on Gen2 */
+#define RTPSE_MCU_PORT_STS_REQUESTING 0x06
+
+/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */
+#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER 0x02
+
+#define RTPSE_MCU_MAX_PORTS 48
+#define RTPSE_MCU_PORT_MAX_PRIORITY 3
+
+enum rtpse_mcu_cmd {
+ RTPSE_MCU_CMD_SET_GLOBAL_STATE,
+ RTPSE_MCU_CMD_GET_SYSTEM_INFO,
+ RTPSE_MCU_CMD_GET_EXT_CONFIG,
+
+ RTPSE_MCU_CMD_PORT_ENABLE,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ RTPSE_MCU_CMD_PORT_SET_PRIORITY,
+ RTPSE_MCU_CMD_PORT_GET_STATUS,
+ RTPSE_MCU_CMD_PORT_GET_POWER_STATS,
+ RTPSE_MCU_CMD_PORT_GET_CONFIG,
+ RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG,
+
+ RTPSE_MCU_NUM_CMDS,
+};
+
+struct rtpse_mcu_opcode {
+ u8 op;
+ bool valid;
+};
+
+/* Shorthand for the designated-initializer entries in dialect opcode tables. */
+#define RTPSE_MCU_OP(opc) { .op = (opc), .valid = true }
+
+/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */
+
+struct rtpse_mcu_info {
+ u8 max_ports;
+ bool system_enable;
+ u16 device_id;
+ u8 sw_ver;
+ u8 mcu_type;
+ u8 config_status;
+ u8 ext_ver;
+};
+
+struct rtpse_mcu_ext_config {
+ u8 uvlo;
+ u8 ovlo;
+ bool prealloc_enable;
+ u8 num_of_pses;
+};
+
+struct rtpse_mcu_port_status {
+ u8 sts1;
+ u8 sts2;
+ u8 sts3;
+};
+
+struct rtpse_mcu_port_measurement {
+ u16 voltage_raw; /* 64.45mV/LSB */
+ u16 current_raw; /* 1mA/LSB */
+ u16 temperature_raw; /* T(mC) = 1250 * (220 - raw) */
+ u16 power_raw; /* 100mW/LSB */
+};
+
+struct rtpse_mcu_port_config {
+ bool enable;
+};
+
+struct rtpse_mcu_port_ext_config {
+ u8 max_power;
+ u8 priority;
+};
+
+struct rtpse_mcu_dialect {
+ struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS];
+
+ /*
+ * Response parsers for the fields that differ between dialects; each
+ * dialect supplies its own. Other responses share one layout and are
+ * decoded directly - a dialect that diverges there must add a hook,
+ * as a mismatched layout cannot be detected (the checksum still passes).
+ */
+ void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info);
+ int (*parse_port_class)(const struct rtpse_mcu_port_status *status);
+ const char *(*mcu_type_str)(unsigned int mcu_type);
+};
+
+struct rtpse_mcu_chip_info {
+ const char *name;
+ u16 device_id;
+ u32 max_mW_per_port;
+ enum rtpse_mcu_cmd pw_set_cmd; /* command used by set_pw_limit */
+ u32 pw_set_lsb_mW; /* LSB of pw_set_cmd value, in mW */
+ u32 pw_read_lsb_mW; /* LSB of ext_config.max_power read-back, in mW */
+};
+
+static const struct rtpse_mcu_chip_info rtl8238b_info = {
+ .device_id = RTPSE_MCU_DEVICE_ID_RTL8238B,
+ .max_mW_per_port = 30000,
+ .name = "RTL8238B",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239_info = {
+ .device_id = RTPSE_MCU_DEVICE_ID_RTL8239,
+ .max_mW_per_port = 90000,
+ .name = "RTL8239",
+ .pw_read_lsb_mW = 400,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ .pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info rtl8239c_info = {
+ .device_id = RTPSE_MCU_DEVICE_ID_RTL8239C,
+ .max_mW_per_port = 90000,
+ .name = "RTL8239C",
+ .pw_read_lsb_mW = 400,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT,
+ .pw_set_lsb_mW = 400,
+};
+
+static const struct rtpse_mcu_chip_info bcm59111_info = {
+ .device_id = RTPSE_MCU_DEVICE_ID_BCM59111,
+ .max_mW_per_port = 30000,
+ .name = "BCM59111",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+static const struct rtpse_mcu_chip_info bcm59121_info = {
+ .device_id = RTPSE_MCU_DEVICE_ID_BCM59121,
+ /*
+ * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at
+ * and the BCM dialect has only the 8-bit/0.2W set command (<=51W);
+ * cap at the 30W the hardware actually offers.
+ */
+ .max_mW_per_port = 30000,
+ .name = "BCM59121",
+ .pw_read_lsb_mW = 200,
+ .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT,
+ .pw_set_lsb_mW = 200,
+};
+
+/* Helpers and basic functions */
+
+static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev)
+{
+ return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev);
+}
+
+static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode)
+{
+ memset(msg, 0xff, sizeof(*msg));
+ msg->opcode = opcode;
+}
+
+static u8 rtpse_mcu_checksum(const u8 *buf, size_t len)
+{
+ u8 sum = 0;
+
+ while (len--)
+ sum += *buf++;
+ return sum;
+}
+
+static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp)
+{
+ int ret;
+
+ req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1);
+
+ scoped_guard(mutex, &pse->mutex) {
+ ret = pse->transport->send(pse, req);
+ if (ret)
+ return ret;
+
+ /*
+ * The MCU needs a fixed amount of time between receiving a request
+ * and having the response ready, regardless of how the bytes get to
+ * us. Pace the transaction here so each transport can keep its recv
+ * path simple: a single bounded wait rather than a generic retry.
+ */
+ msleep(RTPSE_MCU_RESPONSE_MS);
+
+ memset(resp, 0, sizeof(*resp));
+ ret = pse->transport->recv(pse, req, resp);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Explicit MCU error opcodes (observed on the BCM dialect; harmless
+ * to check for RTL too). Catch these before the generic opcode/CRC
+ * mismatch path so callers see a meaningful errno.
+ */
+ switch (resp->opcode) {
+ case RTPSE_MCU_OPCODE_INCOMPLETE:
+ return -EBADE;
+ case RTPSE_MCU_OPCODE_BAD_CSUM:
+ return -EBADMSG;
+ case RTPSE_MCU_OPCODE_NOT_READY:
+ return -EAGAIN;
+ }
+
+ 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;
+
+ return 0;
+}
+
+static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode,
+ struct rtpse_mcu_msg *resp)
+{
+ struct rtpse_mcu_msg req;
+ int ret;
+
+ rtpse_mcu_msg_init(&req, opcode);
+ req.payload[0] = port;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, resp);
+ if (ret)
+ return ret;
+
+ if (resp->payload[0] != port)
+ return -EIO;
+
+ return 0;
+}
+
+static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg)
+{
+ struct rtpse_mcu_msg req, resp;
+ int ret;
+
+ rtpse_mcu_msg_init(&req, opcode);
+ req.payload[0] = port;
+ req.payload[1] = arg;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ if (resp.payload[0] != port || resp.payload[1] != 0)
+ return -EIO;
+
+ return 0;
+}
+
+/* Global operations */
+
+static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ pse->dialect->parse_system_info(resp.payload, info);
+ return 0;
+}
+
+static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ config->uvlo = resp.payload[0];
+ config->ovlo = resp.payload[5];
+ config->prealloc_enable = (resp.payload[1] == 0x1);
+ config->num_of_pses = resp.payload[6];
+
+ return 0;
+}
+
+static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable)
+{
+ struct rtpse_mcu_msg req, resp;
+ const struct rtpse_mcu_opcode *opc;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ rtpse_mcu_msg_init(&req, opc->op);
+ req.payload[0] = enable ? 0x1 : 0x0;
+
+ ret = rtpse_mcu_do_xfer(pse, &req, &resp);
+ if (ret)
+ return ret;
+
+ return (resp.payload[0] == 0x0) ? 0 : -EIO;
+}
+
+/* Port operations */
+
+static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_status *status)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ status->sts1 = resp.payload[1];
+ status->sts2 = resp.payload[2];
+ status->sts3 = resp.payload[3];
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_measurement *measurement)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]);
+ measurement->current_raw = get_unaligned_be16(&resp.payload[3]);
+ measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]);
+ measurement->power_raw = get_unaligned_be16(&resp.payload[7]);
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_config *config)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ config->enable = (resp.payload[1] == 1);
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port,
+ struct rtpse_mcu_port_ext_config *config)
+{
+ const struct rtpse_mcu_opcode *opc;
+ struct rtpse_mcu_msg resp;
+ int ret;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ ret = rtpse_mcu_port_query(pse, port, opc->op, &resp);
+ if (ret)
+ return ret;
+
+ config->max_power = resp.payload[3];
+ config->priority = resp.payload[4];
+
+ return 0;
+}
+
+static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable)
+{
+ const struct rtpse_mcu_opcode *opc;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0);
+}
+
+/* PSE controller ops */
+
+static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ struct pse_admin_state *admin_state)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED :
+ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+ return 0;
+}
+
+static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_status *pw_status)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_status status;
+ int ret;
+
+ ret = rtpse_mcu_port_get_status(pse, id, &status);
+ if (ret)
+ return ret;
+
+ switch (status.sts1) {
+ case RTPSE_MCU_PORT_STS_DISABLED:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+ break;
+ case RTPSE_MCU_PORT_STS_SEARCHING:
+ case RTPSE_MCU_PORT_STS_REQUESTING:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+ break;
+ case RTPSE_MCU_PORT_STS_DELIVERING:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ break;
+ case RTPSE_MCU_PORT_STS_TEST:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST;
+ break;
+ case RTPSE_MCU_PORT_STS_FAULT:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+ break;
+ case RTPSE_MCU_PORT_STS_OTHER_FAULT:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT;
+ break;
+ default:
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_status status;
+ int ret;
+
+ ret = rtpse_mcu_port_get_status(pse, id, &status);
+ if (ret)
+ return ret;
+
+ /*
+ * sts2 holds the class only in the operational states below. In a
+ * fault/test state (or a code reserved on the current dialect) it
+ * does not, so there is no class to report and we return 0. That is
+ * indistinguishable from a real class-0 PD; userspace disambiguates
+ * via the power status.
+ */
+ switch (status.sts1) {
+ case RTPSE_MCU_PORT_STS_DISABLED:
+ case RTPSE_MCU_PORT_STS_SEARCHING:
+ case RTPSE_MCU_PORT_STS_DELIVERING:
+ case RTPSE_MCU_PORT_STS_REQUESTING:
+ return pse->dialect->parse_port_class(&status);
+ default:
+ return 0;
+ }
+}
+
+static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_measurement measurement;
+ int ret;
+
+ ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+ if (ret)
+ return ret;
+
+ /* 100mW per LSB */
+ return measurement.power_raw * 100U;
+}
+
+static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_measurement measurement;
+ int ret;
+ u32 uV;
+
+ ret = rtpse_mcu_port_get_measurement(pse, id, &measurement);
+ if (ret)
+ return ret;
+
+ /* 64.45mV per LSB */
+ uV = measurement.voltage_raw * 64450U;
+ return min_t(u32, uV, INT_MAX);
+}
+
+static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id)
+{
+ return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true);
+}
+
+static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id)
+{
+ return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false);
+}
+
+static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_ext_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ return config.max_power * pse->chip->pw_read_lsb_mW;
+}
+
+static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW)
+{
+ const struct rtpse_mcu_opcode *type_opc, *val_opc;
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ const struct rtpse_mcu_chip_info *chip = pse->chip;
+ u8 prg_val;
+ int ret;
+
+ if (max_mW < 0 || max_mW > chip->max_mW_per_port)
+ return -ERANGE;
+
+ type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE];
+ val_opc = &pse->dialect->opcode[chip->pw_set_cmd];
+ /* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */
+ if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW)
+ return -EOPNOTSUPP;
+
+ /*
+ * Switch the port to user-defined limit mode first, then program the
+ * limit value. If the second cmd fails, the port is left in
+ * user-defined mode but with the previous limit value; the next
+ * successful set_pw_limit call recovers it.
+ */
+ ret = rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER);
+ if (ret)
+ return ret;
+
+ prg_val = min_t(unsigned int, max_mW / chip->pw_set_lsb_mW, U8_MAX);
+
+ return rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val);
+}
+
+static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+ struct pse_pw_limit_ranges *out)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct ethtool_c33_pse_pw_limit_range *range;
+
+ range = kzalloc_obj(*range);
+ if (!range)
+ return -ENOMEM;
+
+ range[0].min = 0;
+ range[0].max = pse->chip->max_mW_per_port;
+
+ out->c33_pw_limit_ranges = range;
+ return 1;
+}
+
+static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ struct rtpse_mcu_port_ext_config config;
+ int ret;
+
+ ret = rtpse_mcu_port_get_ext_config(pse, id, &config);
+ if (ret)
+ return ret;
+
+ return config.priority;
+}
+
+static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio)
+{
+ struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev);
+ const struct rtpse_mcu_opcode *opc;
+
+ if (prio > RTPSE_MCU_PORT_MAX_PRIORITY)
+ return -ERANGE;
+
+ opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY];
+ if (!opc->valid)
+ return -EOPNOTSUPP;
+
+ return rtpse_mcu_port_cmd(pse, id, opc->op, prio);
+}
+
+static const struct pse_controller_ops rtpse_mcu_ops = {
+ .pi_get_admin_state = rtpse_mcu_port_get_admin_state,
+ .pi_get_pw_status = rtpse_mcu_port_get_pw_status,
+ .pi_get_pw_class = rtpse_mcu_port_get_pw_class,
+ .pi_get_actual_pw = rtpse_mcu_port_get_actual_pw,
+ .pi_enable = rtpse_mcu_port_enable,
+ .pi_disable = rtpse_mcu_port_disable,
+ .pi_get_voltage = rtpse_mcu_port_get_voltage,
+ .pi_get_pw_limit = rtpse_mcu_port_get_pw_limit,
+ .pi_set_pw_limit = rtpse_mcu_port_set_pw_limit,
+ .pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges,
+ .pi_get_prio = rtpse_mcu_port_get_prio,
+ .pi_set_prio = rtpse_mcu_port_set_prio,
+};
+
+static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info)
+{
+ struct rtpse_mcu_ext_config ext_config;
+ unsigned long deadline;
+ int ret;
+
+ /*
+ * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address
+ * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a
+ * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a
+ * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail
+ * immediately.
+ */
+ deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS);
+ do {
+ ret = rtpse_mcu_get_info(pse, info);
+ if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO &&
+ ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE)
+ break;
+ msleep(RTPSE_MCU_BOOT_RETRY_MS);
+ } while (time_before(jiffies, deadline));
+ if (ret)
+ return dev_err_probe(pse->dev, ret, "failed to read MCU info\n");
+
+ switch (info->device_id) {
+ case RTPSE_MCU_DEVICE_ID_RTL8238B:
+ pse->chip = &rtl8238b_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_RTL8239:
+ pse->chip = &rtl8239_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_RTL8239C:
+ pse->chip = &rtl8239c_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_BCM59111:
+ pse->chip = &bcm59111_info;
+ break;
+ case RTPSE_MCU_DEVICE_ID_BCM59121:
+ pse->chip = &bcm59121_info;
+ break;
+ default:
+ return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n",
+ info->device_id);
+ }
+
+ if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS)
+ return dev_err_probe(pse->dev, -EINVAL,
+ "MCU reports invalid port count %u\n", info->max_ports);
+
+ ret = rtpse_mcu_get_ext_config(pse, &ext_config);
+ if (ret)
+ return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n");
+
+ dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n",
+ pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name,
+ info->device_id, info->max_ports, ext_config.num_of_pses);
+ return 0;
+}
+
+static void rtpse_mcu_regulator_disable(void *data)
+{
+ regulator_disable(data);
+}
+
+static void rtpse_mcu_global_disable(void *data)
+{
+ struct rtpse_mcu_ctrl *pse = data;
+
+ rtpse_mcu_set_global_state(pse, false);
+}
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse)
+{
+ const struct rtpse_mcu_match_data *match;
+ struct gpio_desc *enable_gpio;
+ struct rtpse_mcu_info info;
+ int ret;
+
+ BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE);
+
+ ret = devm_mutex_init(pse->dev, &pse->mutex);
+ if (ret)
+ return ret;
+
+ match = device_get_match_data(pse->dev);
+ if (!match)
+ return dev_err_probe(pse->dev, -ENODEV, "missing match data\n");
+ pse->dialect = match->dialect;
+
+ /*
+ * Catch a dialect that forgot to set one of the required hooks at
+ * probe time, rather than NULL-deref'ing later from a fast path.
+ */
+ if (!pse->dialect ||
+ !pse->dialect->parse_system_info ||
+ !pse->dialect->parse_port_class ||
+ !pse->dialect->mcu_type_str)
+ return dev_err_probe(pse->dev, -EINVAL,
+ "dialect for chip is incomplete\n");
+
+ pse->poe_supply = devm_regulator_get(pse->dev, "power");
+ if (IS_ERR(pse->poe_supply))
+ return dev_err_probe(pse->dev, PTR_ERR(pse->poe_supply),
+ "failed to get PoE supply\n");
+
+ enable_gpio = devm_gpiod_get_optional(pse->dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(enable_gpio))
+ return dev_err_probe(pse->dev, PTR_ERR(enable_gpio),
+ "failed to get enable gpio\n");
+
+ ret = rtpse_mcu_discover(pse, &info);
+ if (ret)
+ return ret;
+
+ ret = regulator_enable(pse->poe_supply);
+ if (ret)
+ return dev_err_probe(pse->dev, ret, "failed to enable PoE supply\n");
+
+ ret = devm_add_action_or_reset(pse->dev, rtpse_mcu_regulator_disable, pse->poe_supply);
+ if (ret)
+ return ret;
+
+ if (!info.system_enable) {
+ ret = rtpse_mcu_set_global_state(pse, true);
+ /* Dialects without a global-state concept (e.g. BCM) return
+ * -EOPNOTSUPP; treat that as "no separate enable required".
+ */
+ if (ret && ret != -EOPNOTSUPP)
+ return dev_err_probe(pse->dev, ret,
+ "failed to enable PSE system\n");
+ if (!ret) {
+ ret = devm_add_action_or_reset(pse->dev,
+ rtpse_mcu_global_disable, pse);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /*
+ * Depending on the MCU firmware configuration (which might be different
+ * for every board), it isn't known whether the PoE subsystem is active or
+ * inactive by default. At this stage, the PSE chips might already deliver
+ * power to PDs without any explicit enable.
+ */
+
+ /* pcdev.owner is set by the transport, so the registered controller
+ * pins the transport module that owns the live device, not the core.
+ */
+ pse->pcdev.ops = &rtpse_mcu_ops;
+ pse->pcdev.dev = pse->dev;
+ pse->pcdev.types = ETHTOOL_PSE_C33;
+ pse->pcdev.nr_lines = info.max_ports;
+ pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY;
+ pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
+
+ return devm_pse_controller_register(pse->dev, &pse->pcdev);
+}
+EXPORT_SYMBOL_GPL(rtpse_mcu_register);
+
+static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+ info->max_ports = payload[1];
+ info->system_enable = (payload[2] == 0x1);
+ info->device_id = get_unaligned_be16(&payload[3]);
+ info->sw_ver = payload[5];
+ info->mcu_type = payload[6];
+ info->config_status = payload[7];
+ info->ext_ver = payload[8];
+}
+
+static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+ /* Class lives in the upper nibble of sts2. */
+ return FIELD_GET(GENMASK(7, 4), status->sts2);
+}
+
+static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type)
+{
+ switch (mcu_type) {
+ case 0x00: return "GigaDevice GD32F310";
+ case 0x01: return "GigaDevice GD32F230";
+ case 0x02: return "GigaDevice GD32F303";
+ case 0x03: return "GigaDevice GD32F103";
+ case 0x04: return "GigaDevice GD32E103";
+ case 0x10: return "Nuvoton M0516";
+ case 0x11: return "Nuvoton M0564";
+ case 0x12: return "Nuvoton NUC029";
+ default: return "unknown";
+ }
+}
+
+static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info)
+{
+ info->max_ports = payload[1];
+ /* BCM has no explicit system_enable byte; the closest analog is the
+ * "remote enable" bit in the system-status flags at payload[7].
+ */
+ info->system_enable = !!(payload[7] & BIT(2));
+ info->device_id = get_unaligned_be16(&payload[3]);
+ info->sw_ver = payload[5];
+ info->mcu_type = payload[6];
+ info->config_status = payload[7];
+ info->ext_ver = payload[8];
+}
+
+static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status)
+{
+ /* BCM puts the detected class in payload[3] (== sts3) directly.
+ * Mask to the low nibble; class is 0..8 and any high bits would be
+ * noise.
+ */
+ return status->sts3 & 0x0f;
+}
+
+static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type)
+{
+ switch (mcu_type) {
+ case 0x00: return "ST Micro ST32F100";
+ case 0x01: return "Nuvoton M05xx LAN";
+ case 0x02: return "ST Micro STF030C8";
+ case 0x03: return "Nuvoton M058SAN";
+ case 0x04: return "Nuvoton NUC122";
+ default: return "unknown";
+ }
+}
+
+/* Map each logical command the core issues to its per-dialect opcode. */
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = {
+ .parse_system_info = rtpse_mcu_gen2_parse_system_info,
+ .parse_port_class = rtpse_mcu_gen2_parse_port_class,
+ .mcu_type_str = rtpse_mcu_gen2_mcu_type_str,
+ .opcode = {
+ [RTPSE_MCU_CMD_SET_GLOBAL_STATE] = RTPSE_MCU_OP(0x00),
+ [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40),
+ [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a),
+
+ [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x13),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14),
+ [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x15),
+ [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x42),
+ [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x44),
+ [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x48),
+ [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x49),
+ },
+};
+
+static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = {
+ .parse_system_info = rtpse_mcu_gen1_parse_system_info,
+ .parse_port_class = rtpse_mcu_gen1_parse_port_class,
+ .mcu_type_str = rtpse_mcu_gen1_mcu_type_str,
+ .opcode = {
+ [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20),
+ [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b),
+
+ [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15),
+ [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x16),
+ [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x1a),
+ [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x21),
+ [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x30),
+ [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x25),
+ [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x26),
+ },
+};
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = {
+ .dialect = &rtpse_mcu_dialect_gen1,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data);
+
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = {
+ .dialect = &rtpse_mcu_dialect_gen2,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data);
+
+/* Same dialect as gen2, but the MCU expects raw-I2C framing. */
+const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = {
+ .dialect = &rtpse_mcu_dialect_gen2,
+ .native_i2c = true,
+};
+EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data);
+
+MODULE_DESCRIPTION("Realtek PSE MCU driver (core)");
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h
new file mode 100644
index 000000000000..39a6da2c8f37
--- /dev/null
+++ b/drivers/net/pse-pd/realtek-pse-mcu.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _REALTEK_PSE_MCU_H
+#define _REALTEK_PSE_MCU_H
+
+#include <linux/mutex.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/types.h>
+
+/*
+ * Time the MCU itself needs between accepting a request and having a
+ * response ready. These are properties of the MCU firmware, not of the
+ * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS
+ * and both transports size their per-transaction recv ceiling from
+ * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as
+ * needing up to ~1s to produce a reply.
+ */
+#define RTPSE_MCU_RESPONSE_MS 25
+#define RTPSE_MCU_RESPONSE_MAX_MS 1000
+
+/*
+ * Total time to keep retrying the first MCU read at probe, and the pause
+ * between attempts. Right after enable-gpios is asserted the MCU may not
+ * answer on the bus yet; give it a bounded window to come up before
+ * declaring the probe failed.
+ */
+#define RTPSE_MCU_BOOT_TIMEOUT_MS 3000
+#define RTPSE_MCU_BOOT_RETRY_MS 100
+
+#define RTPSE_MCU_MSG_SIZE 12
+
+struct rtpse_mcu_msg {
+ u8 opcode;
+ u8 seq_num;
+ u8 payload[9];
+ u8 checksum;
+} __packed;
+
+/*
+ * MCU status opcodes (seen on the BCM dialect; RTL never emits them).
+ * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient.
+ */
+#define RTPSE_MCU_OPCODE_INCOMPLETE 0xfd /* -EBADE */
+#define RTPSE_MCU_OPCODE_BAD_CSUM 0xfe /* -EBADMSG */
+#define RTPSE_MCU_OPCODE_NOT_READY 0xff /* -EAGAIN */
+
+/* A polling transport can stop here: the matching reply, or a terminal error. */
+static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req,
+ const struct rtpse_mcu_msg *resp)
+{
+ return resp->opcode == req->opcode ||
+ resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE ||
+ resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM;
+}
+
+/* Opaque to transports; defined in realtek-pse-core.c. */
+struct rtpse_mcu_dialect;
+struct rtpse_mcu_chip_info;
+struct rtpse_mcu_ctrl;
+
+/* Per-compatible match data (the of_match .data). */
+struct rtpse_mcu_match_data {
+ const struct rtpse_mcu_dialect *dialect;
+ bool native_i2c; /* raw-I2C framing (vs SMBus); I2C transport only */
+};
+
+struct rtpse_mcu_transport_ops {
+ int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req);
+ int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
+ struct rtpse_mcu_msg *resp);
+};
+
+struct rtpse_mcu_ctrl {
+ struct device *dev;
+ struct pse_controller_dev pcdev;
+ struct mutex mutex; /* serializes MCU request/response transactions */
+ const struct rtpse_mcu_dialect *dialect;
+ const struct rtpse_mcu_chip_info *chip;
+ const struct rtpse_mcu_transport_ops *transport;
+
+ struct regulator *poe_supply;
+};
+
+int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse);
+
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data;
+extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data;
+
+#endif
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 1/4] dt-bindings: net: pse-pd: add bindings for Realtek PSE MCU
From: Jonas Jelonek @ 2026-07-09 19:41 UTC (permalink / raw)
To: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork,
Jonas Jelonek
In-Reply-To: <20260709194125.2784507-1-jelonek.jonas@gmail.com>
Add a binding for the microcontroller (MCU) that fronts the PSE silicon
on a range of managed switches. The host talks only to the MCU, over
I2C/SMBus or UART, using a fixed message-based protocol; the PSE chips
behind it never appear on the bus.
The device is the MCU together with its Realtek firmware: the firmware
and its host protocol are what the binding describes, not the
general-purpose microcontroller they run on. The PSE silicon behind the
MCU (Realtek or Broadcom) is reported by the MCU and detected at runtime,
so it is not described here - hence the 'realtek' vendor prefix.
Two protocol generations exist, both Realtek's, selected by the
compatible: gen1 on older boards (fronting Broadcom PSE silicon) and gen2,
the altered protocol used with Realtek's own PSE silicon. On an I2C
attachment the framing the MCU firmware expects is part of the compatible
as well - '-smbus' or raw '-i2c'; a UART attachment carries no framing
suffix, as the transport is given by the parent serial node.
Each board additionally carries a device-specific compatible that falls
back to one of the protocol compatibles above. Drivers bind on the
protocol compatible; the device-specific string identifies the board and
reserves a place for a future per-board quirk without having to retrofit
device trees already in the field.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
---
.../bindings/net/pse-pd/realtek,pse-mcu.yaml | 176 ++++++++++++++++++
1 file changed, 176 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
diff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
new file mode 100644
index 000000000000..733306193db5
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
@@ -0,0 +1,176 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek PSE MCU
+
+maintainers:
+ - Jonas Jelonek <jelonek.jonas@gmail.com>
+
+description: |
+ A microcontroller (MCU) that manages the PSE (Power Sourcing Equipment)
+ hardware on a range of managed PoE switches. The host CPU talks only to
+ this MCU - over I2C/SMBus or UART - using a small message-based protocol;
+ the PSE silicon it drives sits behind the MCU and is never accessed
+ directly. For example, on the Zyxel GS1900-10HP the SoC reaches the MCU
+ over UART, and the MCU manages the on-board PSE chip.
+
+ This binding describes the MCU together with its Realtek firmware: the
+ firmware and its host protocol, which are stable across boards. The
+ microcontroller silicon is a general-purpose part that varies, and the
+ PSE silicon behind the MCU (Realtek RTL823x/RTL8239* or Broadcom
+ BCM59xxx) is reported by the MCU and detected at runtime - neither is
+ named here.
+
+ Two protocol generations exist, both Realtek's:
+ gen1 older boards, where the MCU fronts Broadcom PSE silicon
+ gen2 the altered protocol used with Realtek's own PSE silicon
+
+ On an I2C attachment the framing the MCU firmware expects is part of the
+ compatible: '-smbus' (reads carry a leading command byte and a repeated
+ start) or '-i2c' (bare block writes and reads). A UART attachment carries
+ no framing suffix; the transport is given by the parent 'serial' node.
+
+ Each board additionally carries a device-specific compatible that falls
+ back to one of the protocol compatibles above. Drivers bind on the
+ protocol compatible; the device-specific string identifies the board and
+ reserves a place for a future per-board quirk without having to retrofit
+ device trees already in the field.
+
+properties:
+ compatible:
+ oneOf:
+ # UART
+ - items:
+ - enum:
+ - zyxel,gs1900-10hp-a1-pse
+ - const: realtek,pse-mcu-gen1
+
+ # I2C, SMBus framing
+ - items:
+ - enum:
+ - zyxel,gs1920-24hp-v2-pse
+ - const: realtek,pse-mcu-gen1-smbus
+
+ # UART
+ - items:
+ - enum:
+ - zyxel,gs1900-10hp-b1-pse
+ - zyxel,xmg1915-10ep-pse
+ - const: realtek,pse-mcu-gen2
+
+ # I2C, SMBus framing
+ - items:
+ - enum:
+ - zyxel,xs1930-12hp-pse
+ - const: realtek,pse-mcu-gen2-smbus
+
+ # I2C, raw framing
+ - items:
+ - enum:
+ - linksys,lgs328mpc-v2-pse
+ - const: realtek,pse-mcu-gen2-i2c
+
+ reg:
+ maxItems: 1
+
+ power-supply:
+ description: Regulator supplying the PoE power rail.
+
+ enable-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+
+allOf:
+ - $ref: pse-controller.yaml#
+ - $ref: /schemas/serial/serial-peripheral-props.yaml#
+ # A '-smbus'/'-i2c' compatible is an I2C attachment: it has 'reg' and
+ # cannot carry serial bus properties. A bare gen compatible is a UART
+ # attachment: no 'reg', the transport comes from the parent serial node.
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - realtek,pse-mcu-gen1-smbus
+ - realtek,pse-mcu-gen2-smbus
+ - realtek,pse-mcu-gen2-i2c
+ then:
+ required:
+ - reg
+ properties:
+ current-speed: false
+ max-speed: false
+ else:
+ properties:
+ reg: false
+
+unevaluatedProperties: false
+
+examples:
+ # SMBus-framed I2C attachment
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-pse@20 {
+ compatible = "zyxel,xs1930-12hp-pse", "realtek,pse-mcu-gen2-smbus";
+ reg = <0x20>;
+
+ pse-pis {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pse-pi@0 {
+ reg = <0>;
+ #pse-cells = <0>;
+ };
+ };
+ };
+ };
+
+ # Raw-I2C-framed attachment
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-pse@20 {
+ compatible = "linksys,lgs328mpc-v2-pse", "realtek,pse-mcu-gen2-i2c";
+ reg = <0x20>;
+
+ pse-pis {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pse-pi@0 {
+ reg = <0>;
+ #pse-cells = <0>;
+ };
+ };
+ };
+ };
+
+ # UART attachment
+ - |
+ serial {
+ ethernet-pse {
+ compatible = "zyxel,gs1900-10hp-a1-pse", "realtek,pse-mcu-gen1";
+ current-speed = <19200>;
+
+ pse-pis {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pse-pi@0 {
+ reg = <0>;
+ #pse-cells = <0>;
+ };
+ };
+ };
+ };
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 0/4] net: pse-pd: add Realtek PSE MCU support
From: Jonas Jelonek @ 2026-07-09 19:41 UTC (permalink / raw)
To: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: netdev, devicetree, linux-kernel, Daniel Golle, Bjørn Mork,
Jonas Jelonek
This series adds a PSE-PD driver for the microcontroller (MCU) that
fronts the PSE silicon on a range of managed switches, together with its
DT binding.
Hardware model
==============
These boards do not expose the PSE chips to the host directly. A small
microcontroller sits on an I2C/SMBus or UART bus and manages one or more
PSE chips behind it; the host CPU only ever talks to that MCU, using a
fixed 12-byte request/response protocol with a trailing checksum. The
PSE silicon never appears on the bus.
Two generations of the protocol exist, both Realtek's: an older one on
boards with Broadcom PSE silicon (BCM59111, BCM59121) and a newer one
used with Realtek's own PSE silicon (RTL8238B, RTL8239, RTL8239C). They
diverge in opcode numbering and a few response layouts; the driver
abstracts that behind a per-dialect opcode table and parser hooks,
selected by the compatible. The specific PSE chip behind the MCU is
detected at runtime and only influences per-chip constants (power scaling
and the per-port cap).
The compatibles
===============
The protocol compatibles name two generations of the Realtek protocol,
with the I2C framing folded in:
realtek,pse-mcu-gen1 gen1, UART
realtek,pse-mcu-gen1-smbus gen1, I2C/SMBus
realtek,pse-mcu-gen2 gen2, UART
realtek,pse-mcu-gen2-smbus gen2, I2C/SMBus
realtek,pse-mcu-gen2-i2c gen2, raw I2C
and each board carries a device-specific compatible that falls back to one
of these, e.g.
compatible = "zyxel,xs1930-12hp-pse", "realtek,pse-mcu-gen2-smbus";
The naming is the part most likely to raise questions, so the reasoning up
front (the binding documents it too):
- The node describes the MCU together with its Realtek firmware, not a
PSE chip and not the microcontroller silicon. The PSE chips sit behind
the MCU, never appear on the bus, and are reported by the MCU and
detected at runtime; the microcontroller itself is a general-purpose
part (GigaDevice, Nuvoton, ...) that varies across boards. What is
fixed and Realtek's is the firmware and its host protocol - hence the
'realtek' prefix.
- gen1 and gen2 are two generations of that protocol, both Realtek's:
gen1 on older boards fronting Broadcom PSE silicon, gen2 the altered
protocol used once Realtek shipped their own PSE silicon. The
generation is fixed per board and is all the driver needs at DT-parse
time, so the compatible encodes it.
- On I2C the MCU firmware expects one of two framings - SMBus or raw
I2C - which is a genuine programming-model difference, so it is part
of the compatible ('-smbus' / '-i2c'). A UART attachment carries no
framing suffix; the transport is given structurally by the parent
'serial' node.
- Each board additionally carries a device-specific compatible that
falls back to the protocol one. The driver only ever binds on the
protocol compatible; the device-specific string keeps the binding
specific and reserves a place for a future per-board quirk without
having to retrofit device trees already deployed in the field.
Testing
=======
- Linksys LGS328MPCv2 (RTL8238B, I2C)
- Zyxel GS1900-10HP A1 (BCM59121, UART)
- Zyxel GS1900-10HP B1 (RTL8238B, UART)
- Zyxel GS1920-24HPv2 (BCM59121, SMBus)
- Zyxel XMG1915-10EP (RTL8239C, UART)
- Zyxel XS1930-12HP (RTL8239, SMBus)
---
v5 -> v6:
- dt-bindings: reworked the compatibles per DT-maintainer feedback
- name the two protocol generations -gen1 / -gen2 (both Realtek's)
instead of the -rtk / -brcm dialect suffix (Conor)
- encode the I2C framing in the compatible (-smbus / raw -i2c) and
drop the realtek,i2c-protocol property (Rob)
- add device-specific (switch) compatibles that fall back to the
protocol compatibles, with the board↔protocol pairing enforced in
the schema (Conor)
- rewrite the description accordingly
- driver: track the binding rework
- match on realtek,pse-mcu-gen{1,2}[-smbus|-i2c]; the I2C transport
selects SMBus-vs-raw framing from a native_i2c match-data flag
instead of reading the property (drops rtpse_mcu_needs_i2c_proto)
- rename the internal dialect and parser symbols rtk/brcm → gen1/gen2
(chip identifiers like RTL8238B/BCM59121 kept)
- i2c: DMA-safe raw-I2C path — bounce each frame through a heap buffer,
since i2c_master_send()/i2c_master_recv() may DMA and the core's
frame buffers are on the stack (SMBus and UART paths unaffected)
(sashiko-nipa)
- includes: drop unused linux/mod_devicetable.h (core) (Uwe)
- includes: drop unused linux/delay.h (uart); add
linux/regulator/consumer.h (core) and linux/slab.h + linux/string.h
(i2c)
- commit messages — update the binding, core, and I2C messages to
match (generations, framing-in-compatible, DMA note)
v5: https://lore.kernel.org/netdev/20260706112425.3149226-1-jelonek.jonas@gmail.com/
v4 -> v5:
- split the single driver patch into three — core / I2C transport / UART
transport. Binding stays patch 1, unchanged in shape. (Paolo)
Please give guidance on how to if I should split more.
- core: set_pw_limit: guard divide-by-zero on pw_set_lsb_mW; cap the
programmed value with U8_MAX instead of a bare 0xff; prg_val is now u8.
(Oleksij, Sashiko)
- core: discover: also retry transient boot-time frames (-EBADMSG / -EBADE)
within the bounded window, not just silence/NAK/not-ready (Sashiko).
- core: pw_status: report Broadcom 0x3 → TEST and 0x5 → OTHERFAULT
(new STS_TEST/STS_OTHER_FAULT); pw_class comment corrected (0x3/0x5
aren't "other fault" on RTL; class-0-vs-fault note). (Sashiko)
- core: dropped unused decoded fields — function_mode, cls_type,
disconnect_type, pair_type, inrush_mode, limit_type, chip_addr,
channel. (Oleksij)
- core: removed forward declarations by moving the response structs above
the dialect struct. (Oleksij)
- core: get_pw_limit_ranges: reverse-Christmas-tree local ordering.
(Oleksij)
- core: dialect comment clarified (only divergent responses are hooked);
commit message "parser hooks" tightened to "…for the responses that
differ." (Sashiko)
- core: made parse_system_info hook void, both implementations return
hardcoded 0. (Paolo)
- core: dropped GFP_KERNEL from kzalloc_obj. (Paolo)
- core: dropped unneeded u32 cast
- kept probe dev_info() for now deliberately, due to different opinions
on whether a probe might print or not
- NOT included Acked-by from Oleksij, due to several changes
v4: https://lore.kernel.org/netdev/20260630105651.756058-1-jelonek.jonas@gmail.com/
v3 -> v4:
- move owner setting from core to transport, mitigating possible
use-after-free (Sashiko)
- resend because net-next was still closed
v3: https://lore.kernel.org/netdev/20260628222705.4052815-1-jelonek.jonas@gmail.com/
v2 -> v3:
- dt-bindings: using brcm instead of bcm for Broadcom
- rename the driver files and Kconfig symbols to realtek-pse-mcu-* /
PSE_REALTEK_MCU* for consistency with the realtek,pse-mcu-* compatibles
- rename driver-internal prefix from 'rtpse_' to 'rtpse_mcu' to
emphasize this targets the MCU-centric setup (and leaves room open
for eventual directly addressable PSE chips)
- rework the vendor-prefix rationale (binding + commit message): the
prefix names the protocol/firmware owner (Realtek documents the protocol
and supplies the firmware), and -rtk/-brcm select the Realtek or Broadcom
protocol dialect
- core: reject zeroed/echo-mismatched responses via the echoed seq_num
(a BCM PORT_ENABLE on port 0 was otherwise accepted from an all-zero
frame)
- core: enable the PoE supply before global-enabling the MCU, and roll
back the global enable on probe failure or driver removal
- core: drop inline from helpers (flagged by automated check)
- uart: update the completion under rx_lock too, so a late frame can no
longer make the next transaction fail spuriously with -EIO
v2: https://lore.kernel.org/netdev/20260612132944.460646-1-jelonek.jonas@gmail.com/
v1 -> v2:
- all points flagged by Sashiko addressed:
- uart: drop frame overflow (return count, not the stored length) so
serdev retains no leftover bytes that would misalign the next response
- uart: guard rx_buf/rx_len with a spinlock to close a data race between
the async receive_buf callback and send/recv
- i2c: return terminal MCU error opcodes (0xfd/0xfe) to the core
immediately instead of polling to the 1 s timeout
- core: cap BCM59121 at 30 W (802.3at) — the basic 8-bit set command
can't program the advertised 60 W (it silently clamped to 51 W)
v1: https://lore.kernel.org/netdev/20260608205758.1830521-1-jelonek.jonas@gmail.com/
---
Jonas Jelonek (4):
dt-bindings: net: pse-pd: add bindings for Realtek PSE MCU
net: pse-pd: add Realtek PSE MCU core
net: pse-pd: realtek-pse-mcu: add I2C transport
net: pse-pd: realtek-pse-mcu: add UART transport
.../bindings/net/pse-pd/realtek,pse-mcu.yaml | 176 ++++
MAINTAINERS | 7 +
drivers/net/pse-pd/Kconfig | 28 +
drivers/net/pse-pd/Makefile | 3 +
drivers/net/pse-pd/realtek-pse-mcu-core.c | 993 ++++++++++++++++++
drivers/net/pse-pd/realtek-pse-mcu-i2c.c | 171 +++
drivers/net/pse-pd/realtek-pse-mcu-uart.c | 155 +++
drivers/net/pse-pd/realtek-pse-mcu.h | 90 ++
8 files changed, 1623 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-core.c
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-i2c.c
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-uart.c
create mode 100644 drivers/net/pse-pd/realtek-pse-mcu.h
base-commit: 6d86ce0da0d5631721c142ea9bb5499cc129b347
--
2.51.0
^ permalink raw reply
* Re: (subset) [PATCH v9 00/14] firmware: qcom: Add OP-TEE PAS service support
From: Bjorn Andersson @ 2026-07-09 19:32 UTC (permalink / raw)
To: konradybcio, Sumit Garg
Cc: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc, robh, krzk+dt,
conor+dt, robin.clark, sean, akhilpo, lumag, abhinav.kumar,
jesszhan0024, marijn.suijten, airlied, simona, vikash.garodia,
bod, mchehab, elder, andrew+netdev, davem, edumazet, kuba, pabeni,
jjohnson, mathieu.poirier, trilokkumar.soni, mukesh.ojha,
pavan.kondeti, jorge.ramirez, tonyh, vignesh.viswanathan,
srinivas.kandagatla, amirreza.zarrabi, jenswi, op-tee, apurupa,
skare, linux-kernel, Sumit Garg
In-Reply-To: <20260702115835.167602-1-sumit.garg@kernel.org>
On Thu, 02 Jul 2026 17:28:16 +0530, Sumit Garg wrote:
> From: Sumit Garg <sumit.garg@oss.qualcomm.com>
>
> Qcom platforms has the legacy of using non-standard SCM calls
> splintered over the various kernel drivers. These SCM calls aren't
> compliant with the standard SMC calling conventions which is a
> prerequisite to enable migration to the FF-A specifications from Arm.
>
> [...]
Applied, thanks!
[01/14] firmware: qcom: Add a generic PAS service
commit: 08314e7c2c38b9ae6a5e01c58ed10a950859404d
[02/14] firmware: qcom_scm: Migrate to generic PAS service
commit: 5c1a2975d23c51c01aca51945d0f10a4ee4c9020
[03/14] firmware: qcom: Add a PAS TEE service
commit: b6f7978da0c4d26fe465aa6634f5a0b48f900de0
[14/14] MAINTAINERS: Add maintainer entry for Qualcomm PAS TZ service
commit: 6701259025d49139131a0eb2257659a066dcca22
This is available as an immutable branch, for other subsystems to pull at:
https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git 20260702115835.167602-2-sumit.garg@kernel.org
[04/14] remoteproc: qcom_q6v5_pas: Switch over to generic PAS TZ APIs
commit: 254030af0d81b12b7624d9ce85c6bdd3171629c6
[05/14] remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs
commit: f3b1357673ddb37ae8b9a8fe44df73cbd2a519c5
[06/14] remoteproc: qcom_wcnss: Switch to generic PAS TZ APIs
commit: ea3b5245f5deba916320b32a8e6510a74c034c17
[07/14] remoteproc: qcom: Select QCOM_PAS generic service
commit: c4383254ac7a529736577e304176a10371c2ee0b
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
* Re: [PATCH v2] dt-bindings: net: microchip,lan78xx: convert to DT schema
From: Rob Herring (Arm) @ 2026-07-09 19:31 UTC (permalink / raw)
To: Mikhail Lukianchikov
Cc: davem, pabeni, kuba, Rengarajan.S, edumazet, andrew+netdev,
devicetree, linux-kernel, netdev, conor+dt, krzk+dt
In-Reply-To: <20260709181724.24682-2-avermoal@gmail.com>
On Fri, 10 Jul 2026 00:17:25 +0600, Mikhail Lukianchikov wrote:
> Convert the Microchip LAN78xx family (LAN7800, LAN7801, LAN7850) binding
> documentation from plain text to DT schema.
>
> The conversion adds proper validation for the 'microchip,led-modes'
> property inside the MDIO node and updates the MAINTAINERS entry.
>
> Signed-off-by: Mikhail Lukianchikov <avermoal@gmail.com>
> ---
> Changes in v2:
> - Rename file to microchip,lan7800.yaml.
> - Keeps only one maintainer.
> - Code style fixed.
> - Example fixed, remove usb-port@1 node.
> - Move patternProperties for PHY inside mdio node to fix validation. (suggested by sashiko-bot)
>
> Link to v1: https://lore.kernel.org/netdev/20260707165840.107409-1-avermoal@gmail.com
>
> .../bindings/net/microchip,lan7800.yaml | 105 ++++++++++++++++++
> .../bindings/net/microchip,lan78xx.txt | 53 ---------
> MAINTAINERS | 3 +-
> 3 files changed, 106 insertions(+), 55 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/net/microchip,lan7800.yaml
> delete mode 100644 Documentation/devicetree/bindings/net/microchip,lan78xx.txt
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan7800.example.dtb: /: 'compatible' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan7800.example.dtb: /: 'model' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan7800.example.dtb: /: '#address-cells' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan7800.example.dtb: /: '#size-cells' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
Documentation/devicetree/bindings/net/microchip,lan7800.example.dtb: /usb: failed to match any schema with compatible: ['usb-host']
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260709181724.24682-2-avermoal@gmail.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* [PATCH net v3] mptcp: only set DATA_FIN when a mapping is present
From: Michael Bommarito @ 2026-07-09 19:19 UTC (permalink / raw)
To: Matthieu Baerts, Mat Martineau
Cc: Geliang Tang, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Gang Yan, mptcp, netdev, linux-kernel
mptcp_get_options() clears only the status group of struct
mptcp_options_received; data_seq, subflow_seq and data_len are filled in
by mptcp_parse_option() exclusively inside the DSS mapping block, which
runs only when the DSS M (mapping present) bit is set.
A peer can send a DSS option with the DATA_FIN flag set but the mapping
bit clear. The parser then records mp_opt->data_fin while leaving
data_len and data_seq uninitialized. For a zero-length segment
mptcp_incoming_options() evaluates
if (mp_opt.data_fin && mp_opt.data_len == 1 &&
mptcp_update_rcv_data_fin(msk, mp_opt.data_seq, mp_opt.dsn64))
which reads the uninitialized data_len and data_seq; KMSAN reports an
uninit-value in mptcp_incoming_options(). The stale data_seq can also be
fed into the receive-side DATA_FIN sequence tracking.
Record the DATA_FIN flag only when the DSS option carries a mapping, so
data_fin is never set without data_seq and data_len also being present.
data_fin is part of the status group that mptcp_get_options() clears up
front, so on the no-map path it stays zero and the zero-length DATA_FIN
branch is simply skipped. A DATA_FIN is always transmitted together with
a mapping (mptcp_write_data_fin() sets use_map along with data_seq and
data_len), so legitimate DATA_FIN handling is unaffected.
Move the pr_debug() that logs the parsed DSS flags below the mapping
block, so it reports the final data_fin value instead of the stale one
it would otherwise print before the assignment.
Fixes: 43b54c6ee382 ("mptcp: Use full MPTCP-level disconnect state machine")
Suggested-by: Paolo Abeni <pabeni@redhat.com>
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
v3:
- Move the pr_debug() DSS flag log below the mapping block so it prints
the final data_fin value (Gang Yan). data_fin is now assigned only
inside the use_map block, so logging it earlier would report a stale
value. No functional change to the fix.
v2: adopt Paolo Abeni's suggested approach - do not set mp_opt->data_fin
at all unless a mapping is present, rather than gating the consumer in
mptcp_incoming_options() (v1). data_fin then defaults to the value
mptcp_get_options() already clears it to (0) on the no-map path, so
the uninitialized data_len/data_seq are never read.
Link to v1: https://lore.kernel.org/all/20260617215725.1116295-1-michael.bommarito@gmail.com/
Link to v2: https://lore.kernel.org/all/20260707171730.2679013-1-michael.bommarito@gmail.com/
net/mptcp/options.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/net/mptcp/options.c b/net/mptcp/options.c
index dff3fd5d3b559..1b74ca5b6a595 100644
--- a/net/mptcp/options.c
+++ b/net/mptcp/options.c
@@ -157,17 +157,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
ptr++;
flags = (*ptr++) & MPTCP_DSS_FLAG_MASK;
- mp_opt->data_fin = (flags & MPTCP_DSS_DATA_FIN) != 0;
mp_opt->dsn64 = (flags & MPTCP_DSS_DSN64) != 0;
mp_opt->use_map = (flags & MPTCP_DSS_HAS_MAP) != 0;
mp_opt->ack64 = (flags & MPTCP_DSS_ACK64) != 0;
mp_opt->use_ack = (flags & MPTCP_DSS_HAS_ACK);
- pr_debug("data_fin=%d dsn64=%d use_map=%d ack64=%d use_ack=%d\n",
- mp_opt->data_fin, mp_opt->dsn64,
- mp_opt->use_map, mp_opt->ack64,
- mp_opt->use_ack);
-
expected_opsize = TCPOLEN_MPTCP_DSS_BASE;
if (mp_opt->use_ack) {
@@ -178,12 +172,18 @@ static void mptcp_parse_option(const struct sk_buff *skb,
}
if (mp_opt->use_map) {
+ mp_opt->data_fin = (flags & MPTCP_DSS_DATA_FIN) != 0;
if (mp_opt->dsn64)
expected_opsize += TCPOLEN_MPTCP_DSS_MAP64;
else
expected_opsize += TCPOLEN_MPTCP_DSS_MAP32;
}
+ pr_debug("data_fin=%d dsn64=%d use_map=%d ack64=%d use_ack=%d\n",
+ mp_opt->data_fin, mp_opt->dsn64,
+ mp_opt->use_map, mp_opt->ack64,
+ mp_opt->use_ack);
+
/* Always parse any csum presence combination, we will enforce
* RFC 8684 Section 3.3.0 checks later in subflow_data_ready
*/
--
2.53.0
^ permalink raw reply related
* [PATCH net] net/af_iucv: fix NULL deref in afiucv_hs_callback_syn()
From: Hidayath Khan @ 2026-07-09 19:17 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni
Cc: horms, linux-s390, netdev, linux-kernel, wintera, twinkler,
heiko.carstens, gor, agordeev, borntraeger, svens, hidayath
afiucv_hs_callback_syn() allocates the child socket with GFP_ATOMIC.
If the allocation fails, nsk is NULL.
The connection-refused path is entered when the listen state check
fails, the accept backlog is full, or nsk is NULL. The code
unconditionally calls iucv_sock_kill(nsk) in that path.
iucv_sock_kill() does not accept a NULL socket pointer and immediately
dereferences sk via sock_flag(sk, SOCK_ZAPPED). When nsk is NULL,
calling iucv_sock_kill(nsk) results in a NULL pointer dereference.
Only call iucv_sock_kill() when a child socket was successfully
allocated.
Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport")
Cc: stable@vger.kernel.org
Reviewed-by: Alexandra Winter <wintera@linux.ibm.com>
Signed-off-by: Hidayath Khan <hidayath@linux.ibm.com>
---
net/iucv/af_iucv.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
index fed240b453bd..f5b1ec44b6ae 100644
--- a/net/iucv/af_iucv.c
+++ b/net/iucv/af_iucv.c
@@ -1872,7 +1872,8 @@ static int afiucv_hs_callback_syn(struct sock *sk, struct sk_buff *skb)
afiucv_swap_src_dest(skb);
trans_hdr->flags = AF_IUCV_FLAG_SYN | AF_IUCV_FLAG_FIN;
err = dev_queue_xmit(skb);
- iucv_sock_kill(nsk);
+ if (nsk)
+ iucv_sock_kill(nsk);
bh_unlock_sock(sk);
goto out;
}
base-commit: 262b2eac463d880a664cf92af1107b4f9d84ad37
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net v2 1/2] vsock/virtio: collapse receive queue under memory pressure
From: Bobby Eshleman @ 2026-07-09 19:07 UTC (permalink / raw)
To: Stefano Garzarella
Cc: netdev, Jason Wang, Xuan Zhuo, Eric Dumazet, Eugenio Pérez,
Simon Horman, Stefan Hajnoczi, David S. Miller, linux-kernel,
Michael S. Tsirkin, kvm, Paolo Abeni, virtualization,
Jakub Kicinski, Jason Wang, stable, Brien Oberstein
In-Reply-To: <20260708102904.50732-2-sgarzare@redhat.com>
On Wed, Jul 08, 2026 at 12:29:03PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
>
> When many small packets accumulate in the receive queue, the skb overhead
> can exceed buf_alloc even while the payload is within bounds. This causes
> virtio_transport_inc_rx_pkt() to reject packets, leading to connection
> resets during large transfers under backpressure.
>
> The issue was reported by Brien, who has a reproducer, but it is also
> easily reproducible with iperf-vsock [1] using a small packet size:
>
> iperf3 --vsock -c $CID -l 129
>
> which fails immediately without this patch but with commit 059b7dbd20a6
> ("vsock/virtio: fix potential unbounded skb queue").
>
> Inspired by TCP's tcp_collapse() which solves a similar problem, add
> virtio_transport_collapse_rx_queue() that walks the receive queue and
> re-copies data into compact linear skbs to reduce the overhead.
>
> The collapse is triggered proactively from when the number of skb queued
> is close to exceeding the overhead budget.
>
> A pre-scan counts the eligible bytes to size each allocation precisely,
> avoiding waste for isolated small packets. Partially consumed skbs are
> kept as-is to preserve buf_used/fwd_cnt accounting, EOM-marked skbs to
> maintain SEQPACKET message boundaries, and skbs already larger than the
> collapse target because they already have a good data-to-overhead ratio.
>
> Walking a large queue may take a significant amount of time and cache
> misses, causing traffic burstiness. To limit this, the collapse stops
> once enough room is freed for this packet and the next one, but may
> opportunistically free more to fill each collapsed skb to capacity.
>
> [1] https://github.com/stefano-garzarella/iperf-vsock
>
> Fixes: 059b7dbd20a6 ("vsock/virtio: fix potential unbounded skb queue")
> Cc: stable@vger.kernel.org
> Reported-by: Brien Oberstein <brienpub@gmail.com>
> Closes: https://lore.kernel.org/netdev/618701dd023e$063de350$12b9a9f0$@gmail.com/
> Tested-by: Brien Oberstein <brienpub@gmail.com>
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
> ---
> v2:
> - defined MAX_COLLAPSE_LEN macro instead of using a variable [Paolo]
> - added a threshold to avoid walking all the queue while collapsing
> [Paolo]
> - collapsed the queue before calling virtio_transport_inc_rx_pkt().
> While working on the threshold, I figured out that the check I was
> introducing can also be used to proactively trigger the collapse, so I
> moved the call to virtio_transport_collapse_rx_queue() before acquiring
> the rx_lock to have also a better diff to simplify backports
> - improved code readability (removed `out` label, `keep` initialization,
> etc.) [Paolo + other small stuff]
> - Brien kindly retested this version as well (thank you so much)
> ---
> net/vmw_vsock/virtio_transport_common.c | 165 +++++++++++++++++++++++-
> 1 file changed, 164 insertions(+), 1 deletion(-)
>
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 09475007165b..8becad81279c 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -26,6 +26,13 @@
> /* Threshold for detecting small packets to copy */
> #define GOOD_COPY_LEN 128
>
> +/* Max payload that can be collapsed into a single linear skb, using the same
> + * allocation threshold as virtio_vsock_alloc_skb() to avoid adding pressure
> + * on the page allocator.
> + */
> +#define MAX_COLLAPSE_LEN \
> + SKB_MAX_ORDER(VIRTIO_VSOCK_SKB_HEADROOM, PAGE_ALLOC_COSTLY_ORDER)
> +
> static void virtio_transport_cancel_close_work(struct vsock_sock *vsk,
> bool cancel_timeout);
> static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs);
> @@ -420,6 +427,145 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
> return ret;
> }
>
> +static bool virtio_transport_can_collapse(struct sk_buff *skb)
> +{
> + /* skbs that are partially consumed, mark a SEQPACKET message boundary,
> + * or are already large enough should not be collapsed: they either
> + * need special accounting, carry protocol state, or already have a
> + * good data-to-overhead ratio.
> + */
> + if (VIRTIO_VSOCK_SKB_CB(skb)->offset)
> + return false;
> + if (le32_to_cpu(virtio_vsock_hdr(skb)->flags) & VIRTIO_VSOCK_SEQ_EOM)
> + return false;
> + if (skb->len >= MAX_COLLAPSE_LEN)
> + return false;
> + return true;
> +}
> +
> +/* Iterate through the packets in the queue starting from the current skb to
> + * count the number of bytes we can collapse.
> + */
> +static unsigned int
> +virtio_transport_collapse_size(struct sk_buff *skb, struct sk_buff_head *queue)
> +{
> + unsigned int target = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
> +
> + while ((skb = skb_peek_next(skb, queue)) &&
> + virtio_transport_can_collapse(skb)) {
> + unsigned int len = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
> +
> + if (len > MAX_COLLAPSE_LEN - target)
> + return target;
> +
> + target += len;
> + }
> +
> + return target;
> +}
> +
> +/* Called under lock_sock to compact the receive queue by merging small skbs.
> + * @min_to_free: minimum number of skbs to eliminate from the queue. May free
> + * more to fill each collapsed skb to capacity.
> + */
> +static void
> +virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
> + u32 min_to_free)
> +{
> + struct sk_buff *skb, *next_skb, *new_skb = NULL;
> + struct sk_buff_head new_queue;
> + u32 saved = 0;
> +
> + __skb_queue_head_init(&new_queue);
> +
> + skb_queue_walk_safe(&vvs->rx_queue, skb, next_skb) {
> + struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
> + u32 src_off = VIRTIO_VSOCK_SKB_CB(skb)->offset;
> + u32 src_len = skb->len - src_off;
> + bool keep;
> +
> + keep = !virtio_transport_can_collapse(skb);
> + if (keep) {
> + /* Finalize pending collapsed skb to preserve packet
> + * ordering.
> + */
> + if (new_skb) {
> + __skb_queue_tail(&new_queue, new_skb);
> + new_skb = NULL;
> + saved--;
> + }
> + goto next;
> + }
> +
> + /* Finalize if this packet won't fit in the remaining tailroom,
> + * so we can allocate a right-sized new_skb.
> + */
> + if (new_skb && src_len > skb_tailroom(new_skb)) {
> + __skb_queue_tail(&new_queue, new_skb);
> + new_skb = NULL;
> + saved--;
> + }
> +
> + if (!new_skb) {
> + unsigned int alloc_size;
> +
> + /* Check after finalizing to opportunistically fill
> + * each collapsed skb to capacity, merging more skbs
> + * than strictly required.
> + */
> + if (saved >= min_to_free)
> + break;
> +
> + alloc_size = virtio_transport_collapse_size(skb, &vvs->rx_queue);
> +
> + /* Only this skb's data is eligible, nothing to merge
> + * with. Keep as-is.
> + */
> + if (alloc_size <= src_len) {
> + keep = true;
> + goto next;
> + }
> +
> + new_skb = virtio_vsock_alloc_linear_skb(alloc_size +
> + VIRTIO_VSOCK_SKB_HEADROOM, GFP_KERNEL);
> + if (!new_skb)
> + break;
> +
> + memcpy(virtio_vsock_hdr(new_skb), hdr,
> + sizeof(struct virtio_vsock_hdr));
> + virtio_vsock_hdr(new_skb)->len = 0;
> + }
> +
> + /* Cannot fail since src_off/src_len are within bounds, but if
> + * it does, discard new_skb to avoid queuing corrupted data.
> + */
> + if (WARN_ON_ONCE(skb_copy_bits(skb, src_off,
> + skb_put(new_skb, src_len),
> + src_len))) {
> + kfree_skb(new_skb);
> + new_skb = NULL;
> + break;
> + }
> +
> + le32_add_cpu(&virtio_vsock_hdr(new_skb)->len, src_len);
> + virtio_vsock_hdr(new_skb)->flags |= hdr->flags;
> +
> +next:
> + __skb_unlink(skb, &vvs->rx_queue);
> + if (keep) {
> + __skb_queue_tail(&new_queue, skb);
> + } else {
> + consume_skb(skb);
> + saved++;
> + }
> + }
> +
> + if (new_skb)
> + __skb_queue_tail(&new_queue, new_skb);
> +
> + skb_queue_splice(&new_queue, &vvs->rx_queue);
> +}
> +
> static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
> u32 len)
> {
> @@ -1354,12 +1500,29 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,
> {
> struct virtio_vsock_sock *vvs = vsk->trans;
> bool can_enqueue, free_pkt = false;
> + u32 len, queue_max, queue_len;
> struct virtio_vsock_hdr *hdr;
> - u32 len;
>
> hdr = virtio_vsock_hdr(skb);
> len = le32_to_cpu(hdr->len);
>
> + /* virtio_transport_inc_rx_pkt() rejects packets when the per-skb
> + * overhead (skb_queue_len * SKB_TRUESIZE(0)) exceeds buf_alloc.
> + * Proactively collapse the queue before that happens.
> + * No rx_lock needed: lock_sock is held by caller, preventing
> + * concurrent enqueue or dequeue.
> + */
> + queue_max = vvs->buf_alloc / SKB_TRUESIZE(0);
> + queue_len = skb_queue_len(&vvs->rx_queue);
> + if (queue_len >= queue_max) {
> + /* Walking a large queue may take a significant amount of time
> + * and cache misses, causing traffic burstiness. Limit the
> + * collapse to freeing room for this packet and the next one.
> + * It may free more to fill each collapsed skb to capacity.
> + */
> + virtio_transport_collapse_rx_queue(vvs, queue_len + 2 - queue_max);
> + }
> +
> spin_lock_bh(&vvs->rx_lock);
>
> can_enqueue = virtio_transport_inc_rx_pkt(vvs, len);
The changes still look good to me.
Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>
Thanks,
Bobby
^ permalink raw reply
* [PATCH net v2] net: stmmac: intel: gate SerDes reconfig on rate
From: Markus Breitenberger @ 2026-07-09 19:03 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Choong Yong Liang, Markus Breitenberger, stable
From: Markus Breitenberger <bre@keba.com>
intel_mac_finish() is registered as the phylink mac_finish()
callback for the Elkhart Lake SGMII ports. phylink calls it at
the end of every major link reconfiguration, including the
initial one during probe.
The callback selects the PMC ModPHY LCPLL programming for the
requested MAC-side interface and then power-cycles the SerDes.
On Elkhart Lake that ModPHY is also used by the on-die AHCI
SATA PHY. Reapplying the programming during the initial
boot-time link-up disturbs the shared analog block while it is
still driving SATA, so the SATA link fails to train:
ata1: SATA link down (SStatus 1 SControl 300)
The disk carrying the root filesystem is never detected and the
system hangs at rootwait. Ethernet itself comes up normally,
which makes the failure look unrelated to the network driver.
Before mac_finish() runs, the legacy SerDes power-up path has
already programmed SERDES_GCR0 for the current interface. The
1G and 2.5G ModPHY tables selected by mac_finish() correspond
to the SerDes lane rate, so read that rate back from SERDES_GCR0
and skip the PMC reprogramming and SerDes power-cycle when it
already matches the selected interface.
This keeps the disruptive reprogramming out of the boot path
when the SerDes is configured correctly, while preserving the
previous behavior when a real SGMII/1000BASE-X to 2500BASE-X
rate change is needed. If the register read fails, reconfigure
as before.
Fixes: a42f6b3f1cc1 ("net: stmmac: configure SerDes according to the interface mode")
Cc: stable@vger.kernel.org
Assisted-by: GitHub-Copilot:claude-opus-4.8
Signed-off-by: Markus Breitenberger <bre@keba.com>
---
v2:
- Read the current SerDes lane rate from SERDES_GCR0 instead of
comparing against cached phy_interface state.
- Rework the commit message to clarify the SerDes power-up path and
the rate readback check.
- Keep the previous reconfiguration behavior if the SERDES_GCR0 read
fails.
v1: https://lore.kernel.org/netdev/20260706061954.94842-1-bre@breiti.cc/
.../net/ethernet/stmicro/stmmac/dwmac-intel.c | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
index b8d467ba6d72..fa0113597c97 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
@@ -525,6 +525,31 @@ static int intel_set_reg_access(const struct pmc_serdes_regs *regs, int max_regs
return ret;
}
+/* Return true if the SerDes lane rate must change to serve @interface.
+ * If the current rate cannot be determined, reconfigure as before.
+ */
+static bool intel_serdes_needs_reconfig(struct stmmac_priv *priv,
+ struct intel_priv_data *intel_priv,
+ phy_interface_t interface)
+{
+ u32 cur_rate, want_rate;
+ int data;
+
+ if (!intel_priv->mdio_adhoc_addr)
+ return true;
+
+ data = mdiobus_read(priv->mii, intel_priv->mdio_adhoc_addr,
+ SERDES_GCR0);
+ if (data < 0)
+ return true;
+
+ cur_rate = (data & SERDES_RATE_MASK) >> SERDES_RATE_PCIE_SHIFT;
+ want_rate = interface == PHY_INTERFACE_MODE_2500BASEX ?
+ SERDES_RATE_PCIE_GEN2 : SERDES_RATE_PCIE_GEN1;
+
+ return cur_rate != want_rate;
+}
+
static int intel_mac_finish(struct net_device *ndev,
void *intel_data,
unsigned int mode,
@@ -536,6 +561,9 @@ static int intel_mac_finish(struct net_device *ndev,
int max_regs = 0;
int ret = 0;
+ if (!intel_serdes_needs_reconfig(priv, intel_priv, interface))
+ return 0;
+
ret = intel_tsn_lane_is_available(ndev, intel_priv);
if (ret < 0) {
netdev_info(priv->dev, "No TSN lane available to set the registers.\n");
--
2.55.0
^ permalink raw reply related
* [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom()
From: Nnamdi Onyeyiri @ 2026-07-09 18:43 UTC (permalink / raw)
To: nnamdio, nnamdi.onyeyiri, john.fastabend, jakub, jiayuan.chen,
edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms
Cc: netdev, bpf
Hi,
We've encounted what appears to be a bug with bpf when invoking recvfrom() on
an ipv4 tcp socket that has been added to a sockmap. It results in unexpected
EAGAIN errors, that we've diagnosed as the result of spurious wakeups from
tcp_msg_wait_data().
This has been confirmed to still be present on the mainline kernel, and I have
written a reproducer at: https://github.com/Nnamdi/recvfrom_sockmap_eagain/
Attched is a patch that we found resolved this using kpatch locally. It just
causes spurious wakups to go round the loop again. I'd like to get a sense of
whether this is expected behaviour, or really is a bug, in which case, is this
the correct fix?
Thanks,
Nnamdi.
----8<----
From 0e0c342363b2e435297ab1feda402cde6ad54525 Mon Sep 17 00:00:00 2001
From: Nnamdi Onyeyiri <nnamdio@gmail.com>
Date: Thu, 9 Jul 2026 13:06:33 +0100
Subject: [PATCH] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup
recvfrom()/recv() are documented as only returning EAGAIN for blocking sockets
when they have a receive timeout configured. however, adding a blocking
ipv4 tcp socket without a receive timeout to a sockmap will cause EAGAIN errors
sporadically.
this happens when tcp_msg_wait_data() wakes spuriously (returning 0) in which
case, if no receive timeout is configured, we loop again instead of returning
-EAGAIN.
Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com>
---
net/ipv4/tcp_bpf.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index cc0bd73f3..38fd391ff 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -317,6 +317,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
}
if (data && !sk_psock_queue_empty(psock))
goto msg_bytes_ready;
+ if (!data && timeo == MAX_SCHEDULE_TIMEOUT)
+ goto msg_bytes_ready;
copied = -EAGAIN;
}
out:
@@ -390,6 +392,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
sk_psock_put(sk, psock);
return tcp_recvmsg(sk, msg, len, flags);
}
+ if (!data && timeo == MAX_SCHEDULE_TIMEOUT)
+ goto msg_bytes_ready;
copied = -EAGAIN;
}
ret = copied;
--
2.52.0
^ permalink raw reply related
* [PATCH v1 net 2/2] net: Call net_enable_timestamp() before failure in sk_clone().
From: Kuniyuki Iwashima @ 2026-07-09 18:31 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Willem de Bruijn
Cc: Simon Horman, Octavian Purdila, Daniel Borkmann,
Alexei Starovoitov, Martin KaFai Lau, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev, Sashiko
In-Reply-To: <20260709183315.965751-1-kuniyu@google.com>
When sk_clone() fails, sk_destruct() is called for the new socket.
If the parent socket has SK_FLAGS_TIMESTAMP in sk->sk_flags,
net_disable_timestamp() is called for the child socket even though
net_enable_timestamp() is not called for it.
Let's call net_enable_timestamp() before any failure path in
sk_clone().
Fixes: 704da560c0a0 ("tcp: update the netstamp_needed counter when cloning sockets")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/20260709032007.9E4D61F000E9@smtp.kernel.org/
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/core/sock.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/net/core/sock.c b/net/core/sock.c
index fc3ff0552d68..504d82a3aacd 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2546,6 +2546,9 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
+ if (sock_needs_netstamp(sk) && newsk->sk_flags & SK_FLAGS_TIMESTAMP)
+ net_enable_timestamp();
+
rcu_read_lock();
filter = rcu_dereference(sk->sk_filter);
if (filter != NULL)
@@ -2595,9 +2598,6 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
if (newsk->sk_prot->sockets_allocated)
sk_sockets_allocated_inc(newsk);
-
- if (sock_needs_netstamp(sk) && newsk->sk_flags & SK_FLAGS_TIMESTAMP)
- net_enable_timestamp();
out:
return newsk;
free:
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH v1 net 1/2] soreuseport: Clear sk_reuseport_cb before failure in sk_clone().
From: Kuniyuki Iwashima @ 2026-07-09 18:31 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Willem de Bruijn
Cc: Simon Horman, Octavian Purdila, Daniel Borkmann,
Alexei Starovoitov, Martin KaFai Lau, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev, Sashiko
In-Reply-To: <20260709183315.965751-1-kuniyu@google.com>
When sk_clone() fails, sk_destruct() is called for the new socket.
If the parent socket has sk->sk_reuseport_cb, the child will call
reuseport_detach_sock() for the reuseport group.
Let's clear sk->sk_reuseport_cb before any failure path in sk_clone().
Note that this was not a problem before the cited commit because
reuseport_detach_sock() did nothing if the socket was not found in
the reuseport array.
Fixes: 5dc4c4b7d4e8 ("bpf: Introduce BPF_MAP_TYPE_REUSEPORT_SOCKARRAY")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/20260709032007.9E4D61F000E9@smtp.kernel.org/
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/core/sock.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/core/sock.c b/net/core/sock.c
index 8a59bfaa8096..fc3ff0552d68 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2544,6 +2544,8 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
cgroup_sk_clone(&newsk->sk_cgrp_data);
+ RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
+
rcu_read_lock();
filter = rcu_dereference(sk->sk_filter);
if (filter != NULL)
@@ -2566,8 +2568,6 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
goto free;
}
- RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
-
if (bpf_sk_storage_clone(sk, newsk))
goto free;
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH v1 net 0/2] net: Fix two issues in sk_clone() error path.
From: Kuniyuki Iwashima @ 2026-07-09 18:31 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Willem de Bruijn
Cc: Simon Horman, Octavian Purdila, Daniel Borkmann,
Alexei Starovoitov, Martin KaFai Lau, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
Sashiko reported issues in the sk_clone() error path.
https://lore.kernel.org/bpf/20260709032007.9E4D61F000E9@smtp.kernel.org/
This series fixes them.
Note that Sashiko may point out the same issue for sk_bpf_storage,
but the fix was already merged in the bpf tree:
7cbd0c4cebe4 ("bpf: Fix UAF in sock clone early bailouts")
Kuniyuki Iwashima (2):
soreuseport: Clear sk_reuseport_cb before failure in sk_clone().
net: Call net_enable_timestamp() before failure in sk_clone().
net/core/sock.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply
* [PATCH v2] dt-bindings: net: microchip,lan78xx: convert to DT schema
From: Mikhail Lukianchikov @ 2026-07-09 18:17 UTC (permalink / raw)
To: Rengarajan.S
Cc: avermoal, andrew+netdev, conor+dt, davem, devicetree, edumazet,
krzk+dt, kuba, linux-kernel, netdev, pabeni, robh
Convert the Microchip LAN78xx family (LAN7800, LAN7801, LAN7850) binding
documentation from plain text to DT schema.
The conversion adds proper validation for the 'microchip,led-modes'
property inside the MDIO node and updates the MAINTAINERS entry.
Signed-off-by: Mikhail Lukianchikov <avermoal@gmail.com>
---
Changes in v2:
- Rename file to microchip,lan7800.yaml.
- Keeps only one maintainer.
- Code style fixed.
- Example fixed, remove usb-port@1 node.
- Move patternProperties for PHY inside mdio node to fix validation. (suggested by sashiko-bot)
Link to v1: https://lore.kernel.org/netdev/20260707165840.107409-1-avermoal@gmail.com
.../bindings/net/microchip,lan7800.yaml | 105 ++++++++++++++++++
.../bindings/net/microchip,lan78xx.txt | 53 ---------
MAINTAINERS | 3 +-
3 files changed, 106 insertions(+), 55 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/microchip,lan7800.yaml
delete mode 100644 Documentation/devicetree/bindings/net/microchip,lan78xx.txt
diff --git a/Documentation/devicetree/bindings/net/microchip,lan7800.yaml b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml
new file mode 100644
index 000000000000..289980c23181
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/microchip,lan7800.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip LAN7800/LAN7801/LAN7850 Gigabit Ethernet controller
+
+maintainers:
+ - Rengarajan Sundararajan <Rengarajan.S@microchip.com>
+
+description:
+ The LAN7800/LAN7801/LAN7850 devices are usually configured by
+ programming their OTP or with an external EEPROM, but some
+ platforms (e.g. Raspberry Pi 3 B+) have neither. The Device Tree
+ properties, if present, override the OTP and EEPROM.
+
+allOf:
+ - $ref: /schemas/usb/usb-device.yaml#
+ - $ref: /schemas/net/ethernet-controller.yaml#
+
+properties:
+ compatible:
+ enum:
+ - usb424,7800
+ - usb424,7801
+ - usb424,7850
+
+ reg:
+ maxItems: 1
+ description: USB port number
+
+ local-mac-address:
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ minItems: 6
+ maxItems: 6
+ description:
+ MAC address to use if not stored in OTP or EEPROM. If present,
+ overrides OTP/EEPROM.
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+
+ patternProperties:
+ "^ethernet-phy(@[0-9a-f]+)?$":
+ type: object
+ description: |
+ PHY node for the embedded or external PHY. The PHY address is
+ given by the 'reg' property.
+
+ properties:
+ reg:
+ maxItems: 1
+ description: PHY address.
+
+ microchip,led-modes:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 4
+ description:
+ Array of LED mode values for each of up to 4 LEDs.
+ Omitted LEDs are turned off. Allowed values are defined
+ in include/dt-bindings/net/microchip-lan78xx.h.
+
+ required:
+ - reg
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/net/microchip-lan78xx.h>
+ / {
+ usb {
+ compatible = "usb-host";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet@1 {
+ compatible = "usb424,7800";
+ reg = <1>;
+ local-mac-address = [00 11 22 33 44 55];
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ ethernet-phy@1 {
+ reg = <1>;
+ microchip,led-modes = <
+ LAN78XX_LINK_1000_ACTIVITY
+ LAN78XX_LINK_10_100_ACTIVITY
+ >;
+ };
+ };
+ };
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt
deleted file mode 100644
index 11a679530ae6..000000000000
--- a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-Microchip LAN78xx Gigabit Ethernet controller
-
-The LAN78XX devices are usually configured by programming their OTP or with
-an external EEPROM, but some platforms (e.g. Raspberry Pi 3 B+) have neither.
-The Device Tree properties, if present, override the OTP and EEPROM.
-
-Required properties:
-- compatible: Should be one of "usb424,7800", "usb424,7801" or "usb424,7850".
-
-The MAC address will be determined using the optional properties
-defined in ethernet.txt.
-
-Optional properties of the embedded PHY:
-- microchip,led-modes: a 0..4 element vector, with each element configuring
- the operating mode of an LED. Omitted LEDs are turned off. Allowed values
- are defined in "include/dt-bindings/net/microchip-lan78xx.h".
-
-Example:
-
-/* Based on the configuration for a Raspberry Pi 3 B+ */
-&usb {
- usb-port@1 {
- compatible = "usb424,2514";
- reg = <1>;
- #address-cells = <1>;
- #size-cells = <0>;
-
- usb-port@1 {
- compatible = "usb424,2514";
- reg = <1>;
- #address-cells = <1>;
- #size-cells = <0>;
-
- ethernet: ethernet@1 {
- compatible = "usb424,7800";
- reg = <1>;
- local-mac-address = [ 00 11 22 33 44 55 ];
-
- mdio {
- #address-cells = <0x1>;
- #size-cells = <0x0>;
- eth_phy: ethernet-phy@1 {
- reg = <1>;
- microchip,led-modes = <
- LAN78XX_LINK_1000_ACTIVITY
- LAN78XX_LINK_10_100_ACTIVITY
- >;
- };
- };
- };
- };
- };
-};
diff --git a/MAINTAINERS b/MAINTAINERS
index f37a81950e25..157a677a284f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27928,10 +27928,9 @@ F: drivers/usb/isp1760/*
USB LAN78XX ETHERNET DRIVER
M: Thangaraj Samynathan <Thangaraj.S@microchip.com>
M: Rengarajan Sundararajan <Rengarajan.S@microchip.com>
-M: UNGLinuxDriver@microchip.com
L: netdev@vger.kernel.org
S: Maintained
-F: Documentation/devicetree/bindings/net/microchip,lan78xx.txt
+F: Documentation/devicetree/bindings/net/microchip,lan7800.yaml
F: drivers/net/usb/lan78xx.*
F: include/dt-bindings/net/microchip-lan78xx.h
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net-next V5 6/6] net/mlx5: Apply devlink eswitch mode boot default on probe
From: Mark Bloch @ 2026-07-09 18:14 UTC (permalink / raw)
To: Jiri Pirko
Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <ak9un-lCDmhGI9tL@FV6GYCPJ69>
On 09/07/2026 12:52, Jiri Pirko wrote:
> Thu, Jul 09, 2026 at 08:00:19AM +0200, mbloch@nvidia.com wrote:
>>
>>
>> On 08/07/2026 11:34, Jiri Pirko wrote:
>>> Tue, Jul 07, 2026 at 07:45:27PM +0200, mbloch@nvidia.com wrote:
>>>> Apply devlink_eswitch_mode= boot defaults for mlx5 after the initial
>>>> probe finishes device initialization while holding the devlink instance
>>>> lock.
>>>>
>>>> At this point the devlink instance is registered and mlx5 can perform an
>>>> eswitch mode change. Calling devl_apply_default_esw_mode() also clears
>>>> any pending default apply work queued by devl_register(), so the queued
>>>> work will not apply the same default again.
>>>>
>>>> Keep this call in mlx5_init_one() rather than the lower-level
>>>> devl-locked init helper. That helper is also used by devlink reload, and
>>>> devlink core already applies the boot default after a successful
>>>> DRIVER_REINIT reload.
>>>>
>>>> Signed-off-by: Mark Bloch <mbloch@nvidia.com>
>>>> ---
>>>> drivers/net/ethernet/mellanox/mlx5/core/main.c | 13 +++++++++++++
>>>> 1 file changed, 13 insertions(+)
>>>>
>>>> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
>>>> index 643b4aac2033..0712efea74cc 100644
>>>> --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
>>>> +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
>>>> @@ -1392,6 +1392,17 @@ static void mlx5_unload(struct mlx5_core_dev *dev)
>>>> mlx5_free_bfreg(dev, &dev->priv.bfreg);
>>>> }
>>>>
>>>> +static void mlx5_devl_apply_default_esw_mode(struct mlx5_core_dev *dev)
>>>> +{
>>>> + struct devlink *devlink = priv_to_devlink(dev);
>>>> +
>>>> + if (!MLX5_ESWITCH_MANAGER(dev))
>>>> + return;
>>>> +
>>>> + devl_assert_locked(devlink);
>>>> + devl_apply_default_esw_mode(devlink);
>>>> +}
>>>> +
>>>> int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev)
>>>> {
>>>> bool light_probe = mlx5_dev_is_lightweight(dev);
>>>> @@ -1471,6 +1482,8 @@ int mlx5_init_one(struct mlx5_core_dev *dev)
>>>> err = mlx5_init_one_devl_locked(dev);
>>>> if (err)
>>>> devl_unregister(devlink);
>>>> + else
>>>> + mlx5_devl_apply_default_esw_mode(dev);
>>>
>>> I don't understand why this patch is needed at all. Just leave the job
>>> to the devlink core, no? That was the point to not pollute drivers with
>>> code like this. Is it some kind of leftover?
>>
>> It was discussed with Jakub here:
>> https://lore.kernel.org/all/20260611085440.4fe36bf2@kernel.org/
>>
>> The main reason is timing. If the default is applied only by devlink
>> core, it has to wait until the driver drops the devlink lock.
>
> I don't follow.
>
> <quote>
> devl_lock(devlink);
> if (dev->shd) {
> err = devl_nested_devlink_set(dev->shd, devlink);
> if (err)
> goto unlock;
> }
> devl_register(devlink);
> err = mlx5_init_one_devl_locked(dev);
> if (err)
> devl_unregister(devlink);
> unlock:
> devl_unlock(devlink);
> </quote>
>
> devlink lock is droped right after.
Earlier versions had the fw reset / recovery flows covered as well.
The regular probe/init in mlx5 need some changes in mlx5 so I can
move it earlier, left the API just to make sure we are aligned and
it could be used be a later patches.
Anyway, I guess I can drop this as well and continue with followup
patches just to keep forward progress.
>
>
>
>> For mlx5, that usually happens very late in the init sequence. I
>> wanted drivers to be able to apply the default as soon as the driver
>> is ready for it, because on NICs with a DPU the host PF can remain
>> stuck until the ECPF moves to switchdev.
>>
>> This API is also useful beyond the initial devlink registration path.
>> Follow-up patches will use it for driver controlled paths that are
>> not covered by the devlink core, such as recovery and FW reset.
>>
>> There is also a race window where userspace may take the devlink lock
>> before the core gets a chance to apply the default. Letting the driver
>> explicitly apply the default at the right point avoids that scenario.
>>
>> Thinking about this again, maybe the simpler approach is to apply the
>> default from devl_unlock(). That would avoid the whole workqueue
>> infra.
>>
>> I avoided doing that earlier because applying a default mode as a
>> side effect of devl_unlock() feels a bit odd. But compared to adding
>> dedicated workqueue handling maybe it is the lesser evil here.
>>
>> What do you think?
>
> I was under impression that you need the work to resolve nested locking.
> If not, drop it, sure.
I think that should be fine. Once the instance is registered and the
devlink lock is dropped, the driver should be ready for normal devlink
operations anyway, since a user could trigger the same path at that
point, so we just don't drop the lock and call the driver directly,
should work.
The only caveat I can think of is a driver holding some other lock
while dropping the devlink lock, and then trying to take that same
lock again from the mode-setting callback. But that sounds like a
driver bug rather than something devlink should work around.
I’ll drop the workqueue approach and rework this for v6 :)
Mark
>
>
>>
>> About the extra API, I still think it's useful and would like to keep
>> it if possible.
>>
>> Mark
>>
>>
>>>
>>>
>>>
>>>> unlock:
>>>> devl_unlock(devlink);
>>>> return err;
>>>> --
>>>> 2.43.0
>>>>
>>
^ permalink raw reply
* [PATCH net] vmxnet3: fix BUG_ON in vmxnet3_get_hdr_len() for Geneve packets
From: Harshaka Narayana @ 2026-07-09 18:11 UTC (permalink / raw)
To: davem, kuba, pabeni, netdev
Cc: ronak.doshi, bcm-kernel-feedback-list, andrew+netdev, edumazet,
linux-kernel, guolin.yang, Harshaka Narayana,
Sankararaman Jayaraman
In-Reply-To: <20260707165248.1859188-1-harshaka.narayana@broadcom.com>
vmxnet3_get_hdr_len() assumes gdesc->rcd.v4/v6/tcp always describe the
outer header, but for a Geneve-encapsulated packet the device can set
them based on the inner header instead, signalled by the
VMXNET3_RCD_HDR_INNER_SHIFT bit in the completion descriptor. Since the
function never skips the outer encapsulation, this mismatch triggers:
- BUG_ON(hdr.ipv4->protocol != IPPROTO_TCP), because the outer
protocol is UDP (Geneve), not TCP.
- BUG_ON(hdr.eth->h_proto != ...), when the tunnel's outer and inner
IP versions differ (e.g. outer IPv6/inner IPv4 or vice versa).
Check VMXNET3_RCD_HDR_INNER_SHIFT up front and bail out, since the
function cannot locate the inner header it would need to parse. Also
convert the remaining BUG_ON()s in this function to return 0
defensively.
Fixes: 45dac1d6ea04 ("vmxnet3: Changes for vmxnet3 adapter version 2 (fwd)")
Signed-off-by: Harshaka Narayana <harshaka.narayana@broadcom.com>
Signed-off-by: Ronak Doshi <ronak.doshi@broadcom.com>
Signed-off-by: Sankararaman Jayaraman <sankararaman.jayaraman@broadcom.com>
---
drivers/net/vmxnet3/vmxnet3_drv.c | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/drivers/net/vmxnet3/vmxnet3_drv.c b/drivers/net/vmxnet3/vmxnet3_drv.c
index 40522afc0532..dedf4082eb6c 100644
--- a/drivers/net/vmxnet3/vmxnet3_drv.c
+++ b/drivers/net/vmxnet3/vmxnet3_drv.c
@@ -1530,7 +1530,13 @@ vmxnet3_get_hdr_len(struct vmxnet3_adapter *adapter, struct sk_buff *skb,
struct ipv6hdr *ipv6;
struct tcphdr *tcp;
} hdr;
- BUG_ON(gdesc->rcd.tcp == 0);
+
+ /* v4/v6/tcp then describe the inner header, which we can't locate. */
+ if (le32_to_cpu(gdesc->dword[0]) & (1UL << VMXNET3_RCD_HDR_INNER_SHIFT))
+ return 0;
+
+ if (gdesc->rcd.tcp == 0)
+ return 0;
maplen = skb_headlen(skb);
if (unlikely(sizeof(struct iphdr) + sizeof(struct tcphdr) > maplen))
@@ -1544,15 +1550,21 @@ vmxnet3_get_hdr_len(struct vmxnet3_adapter *adapter, struct sk_buff *skb,
hdr.eth = eth_hdr(skb);
if (gdesc->rcd.v4) {
- BUG_ON(hdr.eth->h_proto != htons(ETH_P_IP) &&
- hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IP));
+ if (hdr.eth->h_proto != htons(ETH_P_IP) &&
+ hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IP))
+ return 0;
+
hdr.ptr += hlen;
- BUG_ON(hdr.ipv4->protocol != IPPROTO_TCP);
+ if (hdr.ipv4->protocol != IPPROTO_TCP)
+ return 0;
+
hlen = hdr.ipv4->ihl << 2;
hdr.ptr += hdr.ipv4->ihl << 2;
} else if (gdesc->rcd.v6) {
- BUG_ON(hdr.eth->h_proto != htons(ETH_P_IPV6) &&
- hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IPV6));
+ if (hdr.eth->h_proto != htons(ETH_P_IPV6) &&
+ hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IPV6))
+ return 0;
+
hdr.ptr += hlen;
/* Use an estimated value, since we also need to handle
* TSO case.
--
2.52.0
^ permalink raw reply related
* Re: [PATCH v2 08/18] dt-bindings: clock: mediatek: Add MT8189 clocks
From: Conor Dooley @ 2026-07-09 18:12 UTC (permalink / raw)
To: AngeloGioacchino Del Regno
Cc: Louis-Alexis Eyraud, Michael Turquette, Stephen Boyd,
Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, Chun-Jie Chen, Philipp Zabel, Edward-JW Yang,
Richard Cochran, kernel, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, netdev, Irving-CH Lin
In-Reply-To: <39ba7e37-bdd7-41ab-b72d-fed0bdbfc3b6@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 1518 bytes --]
On Thu, Jul 09, 2026 at 04:05:35PM +0200, AngeloGioacchino Del Regno wrote:
> On 7/9/26 15:42, Louis-Alexis Eyraud wrote:
> > Add dt schema and IDs for the clocks of MediaTek MT8189 SoC.
> > The MT8189 clock IP provide clock control for main system
> > (apmixedsys, topcksys and vlpcksys) and subsys (eg. peri, scp,
> > ufs...).
> >
> > Also, add compatible for frequency hopping and spread spectrum clock
> > functionality and reset controller header file for MT8189 UFS reset
> > controller support.
> >
> > Co-developed-by: Irving-CH Lin <irving-ch.lin@mediatek.com>
> > Signed-off-by: Irving-CH Lin <irving-ch.lin@mediatek.com>
> > Signed-off-by: Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
>
> Both the commit description and title are misleading, as in, you're not adding
> MT8189 clocks, but *both* clocks *and* resets.
>
> Fix it please.
>
> After which:
>
> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
And
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: changes-requested
Cheers,
Conor.
>
> > ---
> > .../bindings/clock/mediatek,mt8186-clock.yaml | 15 +
> > .../bindings/clock/mediatek,mt8186-fhctl.yaml | 1 +
> > .../bindings/clock/mediatek,mt8186-sys-clock.yaml | 5 +
> > include/dt-bindings/clock/mediatek,mt8189-clk.h | 433 +++++++++++++++++++++
> > include/dt-bindings/reset/mediatek,mt8189-resets.h | 17 +
> > 5 files changed, 471 insertions(+)
> >
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 07/18] dt-bindings: clock: mediatek: regroup MT8195 dt-bindings into MT8186
From: Conor Dooley @ 2026-07-09 18:10 UTC (permalink / raw)
To: Louis-Alexis Eyraud
Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Chun-Jie Chen, Philipp Zabel,
Edward-JW Yang, Richard Cochran, kernel, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260709-mt8189-clocks-system-base-v2-7-2926da3db6cf@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 06/18] dt-bindings: clock: mediatek: regroup MT8192 dt-bindings into MT8186
From: Conor Dooley @ 2026-07-09 18:10 UTC (permalink / raw)
To: Louis-Alexis Eyraud
Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Chun-Jie Chen, Philipp Zabel,
Edward-JW Yang, Richard Cochran, kernel, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260709-mt8189-clocks-system-base-v2-6-2926da3db6cf@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 05/18] dt-bindings: clock: mediatek: regroup MT8188 dt-bindings into MT8186
From: Conor Dooley @ 2026-07-09 18:10 UTC (permalink / raw)
To: Louis-Alexis Eyraud
Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Chun-Jie Chen, Philipp Zabel,
Edward-JW Yang, Richard Cochran, kernel, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260709-mt8189-clocks-system-base-v2-5-2926da3db6cf@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 04/18] dt-bindings: clock: mediatek: reorder MT8186 compatibles
From: Conor Dooley @ 2026-07-09 18:09 UTC (permalink / raw)
To: Louis-Alexis Eyraud
Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Chun-Jie Chen, Philipp Zabel,
Edward-JW Yang, Richard Cochran, kernel, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260709-mt8189-clocks-system-base-v2-4-2926da3db6cf@collabora.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox