Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 14/17] net: dsa: mv88e6xxx: Time RMU operations and disable if slow
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

MII access to the switch generally has consistent latency. Sending and
receiving frames on the other hand has a big variance in
latency. Receiver interrupt coalescence can result in delays of
100us. As a result, the RMU can be slower than MDIO.

Measure the latency of the first 16 read requests performed using the
RMU, and compare it to the latency on an MDIO read. If the RMU is
slower than MDIO, do not use if for single register reads, writes and
waiting for bits to be set. Do however use it for retrieving MIB
values since a single RMU requests replaces a large number of MIDO
transactions.

Testing on ZII Devel C, which uses a FEC, and Marvel 370RD using
mvneta has show RMU is slower than MDIO. ZII RAP however, makes use of
bit banging MDIO and an IGB. IGB by default has a short interrupt
coalesce period, making RMU much faster than bit banging, by around 20
times.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.h |  6 +++++
 drivers/net/dsa/mv88e6xxx/rmu.c  | 58 ++++++++++++++++++++++++++++++++++++----
 2 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 7e483ba8c58d9..fdc87d02469b5 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -355,6 +355,8 @@ enum mv88e6xxx_rmu_state {
 	 */
 };
 
+#define MV88E6XXX_RMU_IS_SLOW	BIT(0)
+
 struct mv88e6xxx_chip {
 	const struct mv88e6xxx_info *info;
 
@@ -471,6 +473,10 @@ struct mv88e6xxx_chip {
 	struct net_device *rmu_conduit;
 	struct dsa_inband rmu_inband;
 	enum mv88e6xxx_rmu_state rmu_state;
+	u8 rmu_flags;
+	ktime_t rmu_read_latencies[16];
+	u32 rmu_samples;
+	ktime_t smi_read_latency;
 };
 
 #define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 88b02030a774f..5660372f41ad7 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -221,7 +221,8 @@ int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
 	int resp_len;
 	int ret = -1;
 
-	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+	    (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
 		return -EOPNOTSUPP;
 
 	resp_len = sizeof(resp);
@@ -249,6 +250,33 @@ int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
 	return 0;
 }
 
+static void mv88e6xxx_rmu_read_latency(struct mv88e6xxx_chip *chip,
+				       ktime_t latency)
+{
+	ktime_t average = 0;
+	int i;
+
+	if (chip->rmu_samples >= ARRAY_SIZE(chip->rmu_read_latencies))
+		return;
+
+	chip->rmu_read_latencies[chip->rmu_samples++] = latency;
+
+	if (chip->rmu_samples == ARRAY_SIZE(chip->rmu_read_latencies)) {
+		for (i = 0; i < ARRAY_SIZE(chip->rmu_read_latencies); i++)
+			average += chip->rmu_read_latencies[i];
+		average = average / ARRAY_SIZE(chip->rmu_read_latencies);
+
+		dev_dbg(chip->dev, "RMU %lldus, smi %lldus\n",
+			div_u64(average, 1000),
+			div_u64(chip->smi_read_latency, 1000));
+
+		if (chip->smi_read_latency < average)
+			chip->rmu_flags |= MV88E6XXX_RMU_IS_SLOW;
+
+		chip->rmu_samples = U32_MAX;
+	}
+}
+
 int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
 		       u16 *val)
 {
@@ -263,11 +291,15 @@ int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
 	};
 	struct mv88e6xxx_rmu_rw_resp resp;
 	int resp_len;
-	int ret = -1;
+	ktime_t start;
+	int ret;
 
-	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+	    (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
 		return -EOPNOTSUPP;
 
+	start = ktime_get();
+
 	resp_len = sizeof(resp);
 	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
 				    &resp, resp_len,
@@ -290,6 +322,8 @@ int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
 		return -EPROTO;
 	}
 
+	mv88e6xxx_rmu_read_latency(chip, ktime_get() - start);
+
 	*val = ntohs(resp.value);
 
 	return 0;
@@ -312,7 +346,8 @@ int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
 	int resp_len;
 	int ret = -1;
 
-	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED ||
+	    (chip->rmu_flags & MV88E6XXX_RMU_IS_SLOW))
 		return -EOPNOTSUPP;
 
 	resp_len = sizeof(resp);
@@ -389,8 +424,10 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 {
 	struct dsa_port *cpu_dp = conduit->dsa_ptr;
 	struct mv88e6xxx_chip *chip = ds->priv;
+	ktime_t start;
 	int port;
 	int ret;
+	u16 id;
 
 	port = dsa_towards_port(ds, cpu_dp->ds->index, cpu_dp->index);
 
@@ -414,13 +451,24 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 		if (ret < 0) {
 			dev_err(chip->dev, "RMU: initialization check failed %pe",
 				ERR_PTR(ret));
-			goto out;
+			goto out_disable;
 		}
+
+		start = ktime_get();
+		ret = mv88e6xxx_port_read(chip, 0, MV88E6XXX_PORT_SWITCH_ID,
+					  &id);
+		if (ret < 0) {
+			dev_err(chip->dev, "RMU: SMI latency read failed %pe",
+				ERR_PTR(ret));
+			goto out_disable;
+		}
+		chip->smi_read_latency = ktime_get() - start;
 		chip->rmu_state = MV88E6XXX_RMU_ENABLED;
 
 		dev_info(chip->dev, "RMU: enabled on port %d via conduit device %s",
 			 port, chip->rmu_conduit->name);
 	} else {
+out_disable:
 		if (chip->info->ops->rmu_disable)
 			chip->info->ops->rmu_disable(chip);
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 13/17] net: dsa: mv88e6xxx: Get some MIB stats via the RMU
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

It is possible to access the bank 0 and the port statistics using an
RMU request. Bank 1 stats are not available in this way. Issue an RMU
request to get the statistics which are available, and use the big
table of statistics to decode the reply and return statistics in a way
that is compatible with the MDIO method.

Where bank 1 stats cannot be retrieved via RMU, fall back to individual
register reads and writes.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c |  15 ++++--
 drivers/net/dsa/mv88e6xxx/chip.h |   4 ++
 drivers/net/dsa/mv88e6xxx/rmu.c  | 101 +++++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h  |  20 ++++++++
 4 files changed, 137 insertions(+), 3 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 6431d25f3cfa2..531a59093ef97 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1337,9 +1337,9 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 	return 1;
 }
 
-static size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
-				       const struct mv88e6xxx_hw_stat *stat,
-				       uint64_t *data)
+size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
+				const struct mv88e6xxx_hw_stat *stat,
+				uint64_t *data)
 {
 	int ret = 0;
 
@@ -1360,6 +1360,15 @@ static size_t mv88e6xxx_stats_get_stats(struct mv88e6xxx_chip *chip, int port,
 {
 	const struct mv88e6xxx_hw_stat *stat;
 	size_t i, j;
+	int err;
+
+	err = mv88e6xxx_rmu_stats(chip, port, data, mv88e6xxx_hw_stats,
+				  ARRAY_SIZE(mv88e6xxx_hw_stats));
+	if (err > 0)
+		return err;
+
+	if (!mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+		return err;
 
 	for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) {
 		stat = &mv88e6xxx_hw_stats[i];
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 07300c25cbd36..7e483ba8c58d9 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -905,4 +905,8 @@ int mv88e6xxx_vtu_walk(struct mv88e6xxx_chip *chip,
 				 void *priv),
 		       void *priv);
 
+size_t mv88e6xxx_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
+				const struct mv88e6xxx_hw_stat *stat,
+				uint64_t *data);
+
 #endif /* _MV88E6XXX_CHIP_H */
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 03c6f47814c85..88b02030a774f 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -10,6 +10,7 @@
 #include <net/dsa.h>
 #include "chip.h"
 #include "global1.h"
+#include "port.h"
 #include "rmu.h"
 
 static const u8 mv88e6xxx_rmu_dest_addr[ETH_ALEN] = {
@@ -105,6 +106,106 @@ static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
 				  resp, resp_len, timeout_ms);
 }
 
+int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
+			u64 *data,
+			const struct mv88e6xxx_hw_stat *hw_stats,
+			int num_hw_stats)
+{
+	__be16 req[] = {
+		MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+		MV88E6XXX_RMU_REQ_PAD,
+		MV88E6XXX_RMU_REQ_CODE_MIB,
+		htons(port),
+	};
+	const struct mv88e6xxx_hw_stat *stat;
+	struct mv88e6xxx_rmu_mib_resp resp;
+	int i, j, ret;
+	int resp_len;
+	u64 high;
+
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+		return -EOPNOTSUPP;
+
+	resp_len = sizeof(resp) - sizeof(resp.data);
+	if (chip->info->stats_type & STATS_TYPE_BANK0)
+		resp_len += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN;
+	if (chip->info->stats_type & STATS_TYPE_PORT)
+		resp_len += MV88E6XXX_RMU_STATS_TYPE_PORT_LEN;
+	else if (chip->info->stats_type & STATS_TYPE_BANK1)
+		resp_len += MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN;
+
+	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+				    &resp, resp_len,
+				    MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+	if (ret < 0) {
+		dev_dbg(chip->dev, "RMU: error for command MIB %pe\n",
+			ERR_PTR(ret));
+		return ret;
+	}
+
+	if (ret < resp_len) {
+		dev_err(chip->dev, "RMU: MIB returned wrong length: rx %d expecting %d\n",
+			ret, resp_len);
+		return -EPROTO;
+	}
+
+	if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_MIB) {
+		dev_err(chip->dev, "RMU: MIB returned wrong code %d\n",
+			be16_to_cpu(resp.rmu_header.code));
+		return -EPROTO;
+	}
+
+	for (i = 0, j = 0; i < num_hw_stats; i++) {
+		stat = &hw_stats[i];
+
+		if ((stat->type & chip->info->stats_type) == 0) {
+			/* Not available via RMU, use SMI (if available) */
+			j += mv88e6xxx_stats_get_stat(chip, port, stat, &data[j]);
+			continue;
+		}
+
+		if (stat->type & STATS_TYPE_PORT) {
+			__be16 *port = (__be16 *)resp.data;
+
+			if (chip->info->stats_type & STATS_TYPE_BANK0)
+				port += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN / 2;
+
+			switch (stat->reg) {
+			case MV88E6XXX_PORT_IN_DISCARD_LO:
+				data[j] = be16_to_cpu(port[0]) << 16;
+				data[j] |= be16_to_cpu(port[1]);
+				break;
+			case MV88E6XXX_PORT_IN_FILTERED:
+				data[j] = be16_to_cpu(port[3]);
+				break;
+			case MV88E6XXX_PORT_OUT_FILTERED:
+				data[j] = be16_to_cpu(port[5]);
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+
+		if (stat->type & (STATS_TYPE_BANK0 | STATS_TYPE_BANK1)) {
+			int reg = stat->reg;
+
+			if (stat->type & STATS_TYPE_BANK1 &&
+			    (chip->info->stats_type & STATS_TYPE_BANK0))
+				reg += MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN / 4;
+
+			data[j] = be32_to_cpu(resp.data[reg]);
+			if (stat->size == 8) {
+				high = be32_to_cpu(resp.data[reg + 1]);
+				data[j] |= (high << 32);
+			}
+		}
+
+		j++;
+	}
+
+	return j;
+}
+
 int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
 {
 	__be16 req[] = {
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 4b9df3119d517..0865e899cb340 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -17,6 +17,7 @@
 #define MV88E6XXX_RMU_REQ_FORMAT_SOHO		htons(0x0001)
 #define MV88E6XXX_RMU_REQ_PAD			htons(0x0000)
 #define MV88E6XXX_RMU_REQ_CODE_GET_ID		htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_MIB		htons(0x1020)
 #define MV88E6XXX_RMU_REQ_CODE_REG_RW		htons(0x2000)
 #define MV88E6XXX_RMU_REQ_DATA			htons(0x0000)
 
@@ -49,6 +50,7 @@
 #define MV88E6XXX_RMU_RESP_FORMAT_1		htons(0x0001)
 #define MV88E6XXX_RMU_RESP_FORMAT_2		htons(0x0002)
 #define MV88E6XXX_RMU_RESP_CODE_GOT_ID		htons(0x0000)
+#define MV88E6XXX_RMU_RESP_CODE_MIB		htons(0x1020)
 #define MV88E6XXX_RMU_RESP_CODE_REG_RW		htons(0x2000)
 
 struct mv88e6xxx_rmu_header {
@@ -65,6 +67,24 @@ struct mv88e6xxx_rmu_rw_resp {
 	__be16 end1;
 } __packed;
 
+/* number of RMU MIB statistics, by type, in octets */
+#define MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN	128
+#define MV88E6XXX_RMU_STATS_TYPE_PORT_LEN	12
+#define MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN	128
+#define MV88E6XXX_RMU_STATS_TYPE_MAX_LEN	(MV88E6XXX_RMU_STATS_TYPE_DATA0_LEN + \
+						 MV88E6XXX_RMU_STATS_TYPE_DATA1_LEN)
+
+struct mv88e6xxx_rmu_mib_resp {
+	struct mv88e6xxx_rmu_header rmu_header;
+	__be16 swport;
+	__be32 timestamp;
+	__be32 data[MV88E6XXX_RMU_STATS_TYPE_MAX_LEN / 4];
+} __packed;
+
+int mv88e6xxx_rmu_stats(struct mv88e6xxx_chip *chip, int port,
+			u64 *data,
+			const struct mv88e6xxx_hw_stat *hw_stats,
+			int num_hw_stats);
 int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg,
 			u16 val);
 int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 12/17] net: dsa: mv88e6xxx: Centralise common statistics check
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

With moving information about available statistics into the info
structure, the test becomes identical. Consolidate them into a single
test.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 5c0a1e2b0507d..6431d25f3cfa2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1303,9 +1303,6 @@ static size_t mv88e6095_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
-	if (!(stat->type & chip->info->stats_type))
-		return 0;
-
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
 	return 1;
@@ -1315,9 +1312,6 @@ static size_t mv88e6250_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
-	if (!(stat->type & chip->info->stats_type))
-		return 0;
-
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
 	return 1;
@@ -1327,9 +1321,6 @@ static size_t mv88e6320_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
-	if (!(stat->type & chip->info->stats_type))
-		return 0;
-
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
 					    MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
@@ -1340,9 +1331,6 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
-	if (!(stat->type & chip->info->stats_type))
-		return 0;
-
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
 					    MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10,
 					    0);

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 11/17] net: dsa: mv88e6xxx: Move available stats into info structure
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

Different families of switches have different statistics available.
This information is current hard coded into functions, however this
information will also soon be needed when getting statistics from the
RMU. Move it into the info structure.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 6431d25f3cfa2..5c0a1e2b0507d 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1303,6 +1303,9 @@ static size_t mv88e6095_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
+	if (!(stat->type & chip->info->stats_type))
+		return 0;
+
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
 	return 1;
@@ -1312,6 +1315,9 @@ static size_t mv88e6250_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
+	if (!(stat->type & chip->info->stats_type))
+		return 0;
+
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port, 0,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
 	return 1;
@@ -1321,6 +1327,9 @@ static size_t mv88e6320_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
+	if (!(stat->type & chip->info->stats_type))
+		return 0;
+
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
 					    MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9,
 					    MV88E6XXX_G1_STATS_OP_HIST_RX);
@@ -1331,6 +1340,9 @@ static size_t mv88e6390_stats_get_stat(struct mv88e6xxx_chip *chip, int port,
 				       const struct mv88e6xxx_hw_stat *stat,
 				       uint64_t *data)
 {
+	if (!(stat->type & chip->info->stats_type))
+		return 0;
+
 	*data = _mv88e6xxx_get_ethtool_stat(chip, stat, port,
 					    MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10,
 					    0);

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 10/17] net: dsa: mv88e6xxx: Add RMU register read/write/wait for bit
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

Add support for reading a register, writing a register, and waiting
for a bit in a register to change, via the remote management unit.

If the RMU is not enabled, return -EOPNOTSUPP, and fall back to MDIO.
Additionally, if the operation times out, fall back to MDIO. Other
errors, such as protocol errors are however reported, rather than
falling back to MDIO.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c    |  18 +++--
 drivers/net/dsa/mv88e6xxx/global2.c |   1 +
 drivers/net/dsa/mv88e6xxx/rmu.c     | 139 ++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h     |  54 +++++++++++++-
 4 files changed, 207 insertions(+), 5 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 88cc2326ecc0c..6431d25f3cfa2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -65,7 +65,9 @@ int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val)
 
 	assert_reg_lock(chip);
 
-	err = mv88e6xxx_smi_read(chip, addr, reg, val);
+	err = mv88e6xxx_rmu_read(chip, addr, reg, val);
+	if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+		err = mv88e6xxx_smi_read(chip, addr, reg, val);
 	if (err)
 		return err;
 
@@ -81,7 +83,9 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
 
 	assert_reg_lock(chip);
 
-	err = mv88e6xxx_smi_write(chip, addr, reg, val);
+	err = mv88e6xxx_rmu_write(chip, addr, reg, val);
+	if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+		err = mv88e6xxx_smi_write(chip, addr, reg, val);
 	if (err)
 		return err;
 
@@ -131,8 +135,14 @@ int mv88e6xxx_wait_mask(struct mv88e6xxx_chip *chip, int addr, int reg,
 int mv88e6xxx_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
 		       int bit, int val)
 {
-	return mv88e6xxx_wait_mask(chip, addr, reg, BIT(bit),
-				   val ? BIT(bit) : 0x0000);
+	int err;
+
+	err = mv88e6xxx_rmu_wait_bit(chip, addr, reg, bit, val);
+
+	if (mv88e6xxx_rmu_can_mdio_fallback(chip, err))
+		err = mv88e6xxx_wait_mask(chip, addr, reg, BIT(bit),
+					  val ? BIT(bit) : 0x0000);
+	return err;
 }
 
 struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c
index 30a6ffa7817b0..834fdb3503f1c 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.c
+++ b/drivers/net/dsa/mv88e6xxx/global2.c
@@ -855,6 +855,7 @@ static void mv88e6097_watchdog_free(struct mv88e6xxx_chip *chip)
 static int mv88e6097_watchdog_setup(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g2_write(chip, MV88E6352_G2_WDOG_CTL,
+				  MV88E6352_G2_WDOG_CTL_RMU_TIMEOUT |
 				  MV88E6352_G2_WDOG_CTL_EGRESS_ENABLE |
 				  MV88E6352_G2_WDOG_CTL_QC_ENABLE |
 				  MV88E6352_G2_WDOG_CTL_SWRESET);
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 65e6bcf4ca938..03c6f47814c85 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -105,6 +105,145 @@ static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
 				  resp, resp_len, timeout_ms);
 }
 
+int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
+{
+	__be16 req[] = {
+		MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+		MV88E6XXX_RMU_REQ_PAD,
+		MV88E6XXX_RMU_REQ_CODE_REG_RW,
+		MV88E6XXX_RMU_REQ_RW_0_WRITE(addr, reg),
+		htons(val),
+		MV88E6XXX_RMU_REQ_RW_0_END,
+		MV88E6XXX_RMU_REQ_RW_1_END,
+	};
+	struct mv88e6xxx_rmu_header resp;
+	int resp_len;
+	int ret = -1;
+
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+		return -EOPNOTSUPP;
+
+	resp_len = sizeof(resp);
+	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+				    &resp, resp_len,
+				    MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+	if (ret < 0) {
+		dev_dbg(chip->dev, "RMU: error for command REQ_RW:WRITE %pe addr %d reg %d val %04x\n",
+			ERR_PTR(ret), addr, reg, val);
+		return ret;
+	}
+
+	if (ret < resp_len) {
+		dev_err(chip->dev, "RMU: write returned wrong length: rx %d expecting %d\n",
+			ret, resp_len);
+		return -EPROTO;
+	}
+
+	if (resp.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+		dev_err(chip->dev, "RMU: write returned wrong code %d\n",
+			be16_to_cpu(resp.code));
+		return -EPROTO;
+	}
+
+	return 0;
+}
+
+int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
+		       u16 *val)
+{
+	__be16 req[] = {
+		MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+		MV88E6XXX_RMU_REQ_PAD,
+		MV88E6XXX_RMU_REQ_CODE_REG_RW,
+		MV88E6XXX_RMU_REQ_RW_0_READ(addr, reg),
+		0,
+		MV88E6XXX_RMU_REQ_RW_0_END,
+		MV88E6XXX_RMU_REQ_RW_1_END,
+	};
+	struct mv88e6xxx_rmu_rw_resp resp;
+	int resp_len;
+	int ret = -1;
+
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+		return -EOPNOTSUPP;
+
+	resp_len = sizeof(resp);
+	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+				    &resp, resp_len,
+				    MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+	if (ret < 0) {
+		dev_dbg(chip->dev, "RMU: error for command REQ_RW:READ %pe addr %d reg %d\n",
+			ERR_PTR(ret), addr, reg);
+		return ret;
+	}
+
+	if (ret < resp_len) {
+		dev_err(chip->dev, "RMU: read returned wrong length: rx %d expecting %d\n",
+			ret, resp_len);
+		return -EPROTO;
+	}
+
+	if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+		dev_err(chip->dev, "RMU: read returned wrong code %d\n",
+			be16_to_cpu(resp.rmu_header.code));
+		return -EPROTO;
+	}
+
+	*val = ntohs(resp.value);
+
+	return 0;
+}
+
+int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
+			   int bit, int val)
+{
+	__be16 req[] = {
+		MV88E6XXX_RMU_REQ_FORMAT_SOHO,
+		MV88E6XXX_RMU_REQ_PAD,
+		MV88E6XXX_RMU_REQ_CODE_REG_RW,
+		val ? MV88E6XXX_RMU_REQ_RW_0_WAIT_1(addr, reg) :
+		MV88E6XXX_RMU_REQ_RW_0_WAIT_0(addr, reg),
+		htons((bit & 0xf) << 8),
+		MV88E6XXX_RMU_REQ_RW_0_END,
+		MV88E6XXX_RMU_REQ_RW_1_END,
+	};
+	struct mv88e6xxx_rmu_rw_resp resp;
+	int resp_len;
+	int ret = -1;
+
+	if (chip->rmu_state == MV88E6XXX_RMU_DISABLED)
+		return -EOPNOTSUPP;
+
+	resp_len = sizeof(resp);
+	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+				    &resp, resp_len,
+				    MV88E6XXX_RMU_WAIT_BIT_TIMEOUT_MS);
+	if (ret < 0) {
+		dev_dbg(chip->dev, "RMU: error for command REQ_RW:WAIT %pe\n",
+			ERR_PTR(ret));
+		return ret;
+	}
+
+	if (ret < resp_len) {
+		dev_err(chip->dev, "RMU: wait on bit returned wrong length: rx %d expecting %d\n",
+			ret, resp_len);
+		return -EPROTO;
+	}
+
+	if (resp.rmu_header.code != MV88E6XXX_RMU_RESP_CODE_REG_RW) {
+		dev_err(chip->dev, "RMU: wait on bit returned wrong code %d\n",
+			be16_to_cpu(resp.rmu_header.code));
+		return -EPROTO;
+	}
+
+	if ((ntohs(resp.value) & 0xff) == 0xff) {
+		dev_err(chip->dev, "RMU: wait on bit timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
 static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip)
 {
 	const __be16 req[4] = {
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index a81af5a1ad762..4b9df3119d517 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -11,17 +11,45 @@
 
 #define MV88E6XXX_RMU_WAIT_TIME_MS		20
 #define MV88E6XXX_RMU_REQUEST_TIMEOUT_MS	50
+#define MV88E6XXX_RMU_WAIT_BIT_TIMEOUT_MS	1000
 
 #define MV88E6XXX_RMU_REQ_FORMAT_GET_ID		htons(0x0000)
 #define MV88E6XXX_RMU_REQ_FORMAT_SOHO		htons(0x0001)
 #define MV88E6XXX_RMU_REQ_PAD			htons(0x0000)
 #define MV88E6XXX_RMU_REQ_CODE_GET_ID		htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_REG_RW		htons(0x2000)
 #define MV88E6XXX_RMU_REQ_DATA			htons(0x0000)
 
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_1	(0x7 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_0	(0x4 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_READ		(0x2 << 10)
+#define MV88E6XXX_RMU_REQ_RW_0_OP_WRITE		(0x1 << 10)
+
+#define MV88E6XXX_RMU_REQ_RW_0_READ(_addr_, _reg_)		 \
+	htons(MV88E6XXX_RMU_REQ_RW_0_OP_READ |			 \
+	      ((_addr_) << 5) |					 \
+	      (_reg_))
+#define MV88E6XXX_RMU_REQ_RW_0_WRITE(_addr_, _reg_)		 \
+	htons(MV88E6XXX_RMU_REQ_RW_0_OP_WRITE |			 \
+	      ((_addr_) << 5) |					 \
+	      (_reg_))
+
+#define MV88E6XXX_RMU_REQ_RW_0_WAIT_0(_addr_, _reg_)		   \
+	htons(MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_0 |		   \
+	      ((_addr_) << 5) |					   \
+	      (_reg_))
+#define MV88E6XXX_RMU_REQ_RW_0_WAIT_1(_addr_, _reg_)		   \
+	htons(MV88E6XXX_RMU_REQ_RW_0_OP_WAIT_1 |		   \
+	      ((_addr_) << 5) |					   \
+	      (_reg_))
+
+#define MV88E6XXX_RMU_REQ_RW_0_END		htons(0xffff)
+#define MV88E6XXX_RMU_REQ_RW_1_END		htons(0xffff)
+
 #define MV88E6XXX_RMU_RESP_FORMAT_1		htons(0x0001)
 #define MV88E6XXX_RMU_RESP_FORMAT_2		htons(0x0002)
-
 #define MV88E6XXX_RMU_RESP_CODE_GOT_ID		htons(0x0000)
+#define MV88E6XXX_RMU_RESP_CODE_REG_RW		htons(0x2000)
 
 struct mv88e6xxx_rmu_header {
 	__be16 format;
@@ -29,10 +57,34 @@ struct mv88e6xxx_rmu_header {
 	__be16 code;
 } __packed;
 
+struct mv88e6xxx_rmu_rw_resp {
+	struct mv88e6xxx_rmu_header rmu_header;
+	__be16 cmd;
+	__be16 value;
+	__be16 end0;
+	__be16 end1;
+} __packed;
+
+int mv88e6xxx_rmu_write(struct mv88e6xxx_chip *chip, int addr, int reg,
+			u16 val);
+int mv88e6xxx_rmu_read(struct mv88e6xxx_chip *chip, int addr, int reg,
+		       u16 *val);
+int mv88e6xxx_rmu_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
+			   int bit, int val);
 void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 					const struct net_device *conduit,
 					bool operational);
 void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
 				     struct sk_buff *skb,
 				     u8 seqno);
+
+/* RMU register access falls back to MDIO when the operation is unsupported or
+ * times out. @chip is unused today, but threading it through here lets a future
+ * RMU-only mode suppress the fallback without touching any of the call sites.
+ */
+static inline bool
+mv88e6xxx_rmu_can_mdio_fallback(struct mv88e6xxx_chip *chip, int err)
+{
+	return err == -EOPNOTSUPP || err == -ETIMEDOUT;
+}
 #endif /* _MV88E6XXX_RMU_H_ */

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 09/17] net: dsa: mv88e6xxx: Get Product ID when enabling RMU
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

When the RMU is enabled, it is recommended to get the product ID,
which identifies the switch. This is used as a test that communication
with the switch actually works and the RMU can be used. If successful,
mark the RMU usable.

This completes the general infrastructure to send a request and
process the response.

Co-developed: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.h |  10 +++
 drivers/net/dsa/mv88e6xxx/rmu.c  | 151 ++++++++++++++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx/rmu.h  |  11 +++
 3 files changed, 170 insertions(+), 2 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 940ae698659fc..07300c25cbd36 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -346,6 +346,15 @@ struct mv88e6xxx_tcam {
 	struct list_head entries;
 };
 
+enum mv88e6xxx_rmu_state {
+	MV88E6XXX_RMU_DISABLED = 0,
+	MV88E6XXX_RMU_ENABLED = 1,
+	/* Reserved: a future RMU-only mode (e.g. MV88E6XXX_RMU_ONLY_ENABLED)
+	 * may be added here to indicate a switch operating purely via RMU
+	 * without an MDIO bus.
+	 */
+};
+
 struct mv88e6xxx_chip {
 	const struct mv88e6xxx_info *info;
 
@@ -461,6 +470,7 @@ struct mv88e6xxx_chip {
 	/* Remote Management Unit state. */
 	struct net_device *rmu_conduit;
 	struct dsa_inband rmu_inband;
+	enum mv88e6xxx_rmu_state rmu_state;
 };
 
 #define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 49d67d512fb0f..65e6bcf4ca938 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -6,11 +6,143 @@
  *
  */
 
+#include <linux/dsa/mv88e6xxx.h>
 #include <net/dsa.h>
 #include "chip.h"
 #include "global1.h"
 #include "rmu.h"
 
+static const u8 mv88e6xxx_rmu_dest_addr[ETH_ALEN] = {
+	0x01, 0x50, 0x43, 0x00, 0x00, 0x00
+};
+
+static void mv88e6xxx_rmu_create_l2(struct dsa_switch *ds,
+				    struct net_device *conduit,
+				    struct sk_buff *skb,
+				    bool edsa)
+{
+	struct dsa_tagger_data *tagger_data = ds->tagger_data;
+	struct ethhdr *eth;
+	u8 *header;
+
+	/* Create RMU L2 header. */
+	header = skb_push(skb, 2);
+	/* Two bytes of EtherType, which is ignored by the switch */
+	header[0] = 0;
+	header[1] = 0;
+
+	/* Ask tagger to add {E}DSA header */
+	tagger_data->rmu_reg2frame(ds, skb);
+
+	/* Insert RMU MAC destination address */
+	eth = skb_push(skb, ETH_ALEN * 2);
+	memcpy(eth->h_dest, mv88e6xxx_rmu_dest_addr, ETH_ALEN);
+	ether_addr_copy(eth->h_source, conduit->dev_addr);
+	skb_reset_network_header(skb);
+}
+
+static void mv88e6xxx_rmu_fill_seqno(struct sk_buff *skb, u32 seqno, int offset)
+{
+	u8 *dsa_header = skb->data + offset;
+
+	dsa_header[3] = seqno;
+}
+
+/* 2 MAC address, 2 byte Ethertype, 2 bytes padding, to DSA header */
+static void mv88e6xxx_rmu_fill_seqno_edsa(struct sk_buff *skb, u32 seqno)
+{
+	mv88e6xxx_rmu_fill_seqno(skb, seqno, ETH_ALEN * 2 + 2 + 2);
+}
+
+/* 2 MAC address, to DSA header */
+static void mv88e6xxx_rmu_fill_seqno_dsa(struct sk_buff *skb, u32 seqno)
+{
+	mv88e6xxx_rmu_fill_seqno(skb, seqno, ETH_ALEN * 2);
+}
+
+static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip,
+				 const void *req, int req_len,
+				 void *resp, unsigned int resp_len,
+				 int timeout_ms)
+{
+	struct net_device *conduit;
+	struct sk_buff *skb;
+	unsigned char *data;
+	bool edsa;
+
+	/* Capture conduit once: it can be cleared concurrently when the
+	 * conduit goes down (e.g. during reboot). Also guard against the
+	 * conduit being torn down out from under us, in which case its
+	 * dev_addr may already have been released.
+	 */
+	conduit = READ_ONCE(chip->rmu_conduit);
+	if (unlikely(!conduit || !conduit->dev_addr)) {
+		dev_err_ratelimited(chip->dev, "RMU: conduit device unavailable");
+		return -EINVAL;
+	}
+
+	skb = dev_alloc_skb(req_len + EDSA_HLEN + 2 * ETH_ALEN + 2);
+	if (!skb)
+		return -ENOMEM;
+
+	/* Insert RMU request message */
+	data = skb_put(skb, req_len);
+	memcpy(data, req, req_len);
+
+	edsa = chip->tag_protocol == DSA_TAG_PROTO_EDSA;
+
+	if (!chip->ds->tagger_data) {
+		kfree_skb(skb);
+		return -EOPNOTSUPP; /* can happen on teardown */
+	}
+
+	mv88e6xxx_rmu_create_l2(chip->ds, conduit, skb, edsa);
+	skb->dev = conduit;
+
+	return dsa_inband_request(&chip->rmu_inband, skb,
+				  (edsa ? mv88e6xxx_rmu_fill_seqno_edsa :
+				   mv88e6xxx_rmu_fill_seqno_dsa),
+				  resp, resp_len, timeout_ms);
+}
+
+static int mv88e6xxx_rmu_get_id(struct mv88e6xxx_chip *chip)
+{
+	const __be16 req[4] = {
+		MV88E6XXX_RMU_REQ_FORMAT_GET_ID,
+		MV88E6XXX_RMU_REQ_PAD,
+		MV88E6XXX_RMU_REQ_CODE_GET_ID,
+		MV88E6XXX_RMU_REQ_DATA};
+	struct mv88e6xxx_rmu_header resp;
+	int resp_len;
+	int ret = -1;
+
+	resp_len = sizeof(resp);
+	ret = mv88e6xxx_rmu_request(chip, req, sizeof(req),
+				    &resp, resp_len,
+				    MV88E6XXX_RMU_REQUEST_TIMEOUT_MS);
+	if (ret < 0) {
+		dev_dbg(chip->dev, "RMU: error for command GET_ID %pe\n",
+			ERR_PTR(ret));
+		return ret;
+	}
+
+	if (ret < resp_len) {
+		dev_err(chip->dev, "RMU: GET_ID returned wrong length: rx %d expecting %d\n",
+			ret, resp_len);
+		return -EPROTO;
+	}
+
+	if (resp.code != MV88E6XXX_RMU_RESP_CODE_GOT_ID) {
+		dev_dbg(chip->dev, "RMU: GET_ID returned wrong code %d\n",
+			be16_to_cpu(resp.code));
+		return -EPROTO;
+	}
+
+	dev_dbg(chip->dev, "RMU: product ID %4x\n", be16_to_cpu(resp.prodnr));
+
+	return 0;
+}
+
 void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 					const struct net_device *conduit,
 					bool operational)
@@ -37,11 +169,22 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 
 		WRITE_ONCE(chip->rmu_conduit, (struct net_device *)conduit);
 
-		dev_dbg(chip->dev, "RMU: Enabled on port %d", port);
+		/* Get the device ID to prove that the RMU works */
+		ret = mv88e6xxx_rmu_get_id(chip);
+		if (ret < 0) {
+			dev_err(chip->dev, "RMU: initialization check failed %pe",
+				ERR_PTR(ret));
+			goto out;
+		}
+		chip->rmu_state = MV88E6XXX_RMU_ENABLED;
+
+		dev_info(chip->dev, "RMU: enabled on port %d via conduit device %s",
+			 port, chip->rmu_conduit->name);
 	} else {
 		if (chip->info->ops->rmu_disable)
 			chip->info->ops->rmu_disable(chip);
 
+		chip->rmu_state = MV88E6XXX_RMU_DISABLED;
 		WRITE_ONCE(chip->rmu_conduit, NULL);
 	}
 
@@ -58,6 +201,8 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
 	struct net_device *conduit;
 	unsigned char *ethhdr;
 	u8 expected_seqno;
+	int resp_len;
+	int err = 0;
 
 	/* Check received destination MAC is the conduit MAC address */
 	conduit = READ_ONCE(chip->rmu_conduit);
@@ -86,13 +231,15 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
 	}
 
 	rmu_header = (struct mv88e6xxx_rmu_header *)(skb->data + 4);
+	resp_len = skb->len - 4;
 	if (rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_1 &&
 	    rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_2) {
 		dev_dbg_ratelimited(ds->dev, "RMU: invalid format: rx %d\n",
 				    be16_to_cpu(rmu_header->format));
-		goto drop;
+		err = -EPROTO;
 	}
 
+	dsa_inband_complete(&chip->rmu_inband, rmu_header, resp_len, err);
 drop:
 	return;
 }
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 69cd3c2636f3d..a81af5a1ad762 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -9,9 +9,20 @@
 #ifndef _MV88E6XXX_RMU_H_
 #define _MV88E6XXX_RMU_H_
 
+#define MV88E6XXX_RMU_WAIT_TIME_MS		20
+#define MV88E6XXX_RMU_REQUEST_TIMEOUT_MS	50
+
+#define MV88E6XXX_RMU_REQ_FORMAT_GET_ID		htons(0x0000)
+#define MV88E6XXX_RMU_REQ_FORMAT_SOHO		htons(0x0001)
+#define MV88E6XXX_RMU_REQ_PAD			htons(0x0000)
+#define MV88E6XXX_RMU_REQ_CODE_GET_ID		htons(0x0000)
+#define MV88E6XXX_RMU_REQ_DATA			htons(0x0000)
+
 #define MV88E6XXX_RMU_RESP_FORMAT_1		htons(0x0001)
 #define MV88E6XXX_RMU_RESP_FORMAT_2		htons(0x0002)
 
+#define MV88E6XXX_RMU_RESP_CODE_GOT_ID		htons(0x0000)
+
 struct mv88e6xxx_rmu_header {
 	__be16 format;
 	__be16 prodnr;

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 08/17] net: dsa: mv88e6xxx: Add sanity check of received RMU frame
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

The received RMU frame is expected to be addressed to the MAC address
of the master interface. The sequence number should also match. Two
formats codes are accepted. Add these basic sanity checks, and drop
the frame is they are incorrect.  To aid sequence number checking
introduce the use of the DSA inband helpers and its data structures.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c |  2 ++
 drivers/net/dsa/mv88e6xxx/chip.h |  1 +
 drivers/net/dsa/mv88e6xxx/rmu.c  | 42 ++++++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h  |  9 +++++++++
 4 files changed, 54 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index fe0d0eb8f0813..88cc2326ecc0c 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7462,6 +7462,8 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev)
 	if (err)
 		goto out_g1_atu_prob_irq;
 
+	dsa_inband_init(&chip->rmu_inband, U8_MAX);
+
 	err = mv88e6xxx_register_switch(chip);
 	if (err)
 		goto out_g1_vtu_prob_irq;
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 76958b588a908..940ae698659fc 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -460,6 +460,7 @@ struct mv88e6xxx_chip {
 
 	/* Remote Management Unit state. */
 	struct net_device *rmu_conduit;
+	struct dsa_inband rmu_inband;
 };
 
 #define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index bae0ca6564f7a..49d67d512fb0f 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -53,4 +53,46 @@ void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
 				     struct sk_buff *skb,
 				     u8 seqno)
 {
+	struct mv88e6xxx_rmu_header *rmu_header;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct net_device *conduit;
+	unsigned char *ethhdr;
+	u8 expected_seqno;
+
+	/* Check received destination MAC is the conduit MAC address */
+	conduit = READ_ONCE(chip->rmu_conduit);
+	if (!conduit)
+		goto drop;
+
+	ethhdr = skb_mac_header(skb);
+	if (!ether_addr_equal(conduit->dev_addr, ethhdr)) {
+		dev_dbg_ratelimited(ds->dev, "RMU: mismatched MAC address for request: rx %pM expecting %pM\n",
+				    ethhdr, conduit->dev_addr);
+		goto drop;
+	}
+
+	expected_seqno = dsa_inband_seqno(&chip->rmu_inband);
+	if (seqno != expected_seqno) {
+		dev_dbg_ratelimited(ds->dev, "RMU: mismatched seqno for request: rx %d expecting %d\n",
+				    seqno, expected_seqno);
+		goto drop;
+	}
+
+	if (skb->len < 4 + sizeof(*rmu_header)) {
+		dev_dbg_ratelimited(ds->dev, "RMU: response too short (%d bytes)\n",
+				    skb->len);
+		dsa_inband_complete(&chip->rmu_inband, NULL, 0, -ETIMEDOUT);
+		goto drop;
+	}
+
+	rmu_header = (struct mv88e6xxx_rmu_header *)(skb->data + 4);
+	if (rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_1 &&
+	    rmu_header->format != MV88E6XXX_RMU_RESP_FORMAT_2) {
+		dev_dbg_ratelimited(ds->dev, "RMU: invalid format: rx %d\n",
+				    be16_to_cpu(rmu_header->format));
+		goto drop;
+	}
+
+drop:
+	return;
 }
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index c60f32480e317..69cd3c2636f3d 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -9,6 +9,15 @@
 #ifndef _MV88E6XXX_RMU_H_
 #define _MV88E6XXX_RMU_H_
 
+#define MV88E6XXX_RMU_RESP_FORMAT_1		htons(0x0001)
+#define MV88E6XXX_RMU_RESP_FORMAT_2		htons(0x0002)
+
+struct mv88e6xxx_rmu_header {
+	__be16 format;
+	__be16 prodnr;
+	__be16 code;
+} __packed;
+
 void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 					const struct net_device *conduit,
 					bool operational);

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 07/17] net: dsa: mv88e6xxx: Add connect_tag_protocol handler
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

When the tagging protocol handler is attached to the DSA driver, it
can validate it is a supported tagging protocol. It can also setup
data shared between the tagger and the switch driver. Ensure a Mavell
DSA tagger is being connected, and setup the handler from RMU in the
shared data. At this point, the handler is just an empty stub, but
later patches will add the needed functionality.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 18 ++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.c  |  6 ++++++
 drivers/net/dsa/mv88e6xxx/rmu.h  |  3 +++
 3 files changed, 27 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 72c93cb84b7e1..fe0d0eb8f0813 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7183,6 +7183,23 @@ static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
 	return err_sync ? : err_pvt;
 }
 
+static int mv88e6xxx_connect_tag_protocol(struct dsa_switch *ds,
+					  enum dsa_tag_protocol proto)
+{
+	struct dsa_tagger_data *tagger_data = ds->tagger_data;
+
+	switch (proto) {
+	case DSA_TAG_PROTO_DSA:
+	case DSA_TAG_PROTO_EDSA:
+		tagger_data->rmu_frame2reg = mv88e6xxx_rmu_frame2reg_handler;
+		break;
+	default:
+		return -EPROTONOSUPPORT;
+	}
+
+	return 0;
+}
+
 static const struct phylink_mac_ops mv88e6xxx_phylink_mac_ops = {
 	.mac_select_pcs		= mv88e6xxx_mac_select_pcs,
 	.mac_prepare		= mv88e6xxx_mac_prepare,
@@ -7255,6 +7272,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.crosschip_lag_join	= mv88e6xxx_crosschip_lag_join,
 	.crosschip_lag_leave	= mv88e6xxx_crosschip_lag_leave,
 	.conduit_state_change	= mv88e6xxx_rmu_conduit_state_change,
+	.connect_tag_protocol	= mv88e6xxx_connect_tag_protocol,
 };
 
 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 64e93e2eaf550..bae0ca6564f7a 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -48,3 +48,9 @@ void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 out:
 	mv88e6xxx_reg_unlock(chip);
 }
+
+void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
+				     struct sk_buff *skb,
+				     u8 seqno)
+{
+}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index 71c4fc1d4113f..c60f32480e317 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -12,4 +12,7 @@
 void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
 					const struct net_device *conduit,
 					bool operational);
+void mv88e6xxx_rmu_frame2reg_handler(struct dsa_switch *ds,
+				     struct sk_buff *skb,
+				     u8 seqno);
 #endif /* _MV88E6XXX_RMU_H_ */

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 06/17] net: dsa: mv88e6xxx: Remove mv88e6xxx_rmu_setup()
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

Now that the RMU is enabled/disabled in the master state change
handler, remove the setup of the RMU in the mv88e6xxx_setup().

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index d6c6834cca2cb..72c93cb84b7e1 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1676,14 +1676,6 @@ static int mv88e6xxx_trunk_setup(struct mv88e6xxx_chip *chip)
 	return 0;
 }
 
-static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
-{
-	if (chip->info->ops->rmu_disable)
-		return chip->info->ops->rmu_disable(chip);
-
-	return 0;
-}
-
 static int mv88e6xxx_pot_setup(struct mv88e6xxx_chip *chip)
 {
 	if (chip->info->ops->pot_clear)
@@ -4080,10 +4072,6 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
 	if (err)
 		goto unlock;
 
-	err = mv88e6xxx_rmu_setup(chip);
-	if (err)
-		goto unlock;
-
 	err = mv88e6xxx_rsvd2cpu_setup(chip);
 	if (err)
 		goto unlock;

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 05/17] net: dsa: mv88e6xxx: Add conduit state change handler
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

When the conduit interface comes up, try to enable the RMU.  Failing to
enable the remote management unit is not fatal, not all chips support
it, and some boards have chips which do support RMU, but with wrong
port is connected to the CPU.

When the conduit interface goes down, disable the RMU.

Keep track of the conduit interface, so that RMU frames can be sent to
it.

Co-developed: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/Makefile |  1 +
 drivers/net/dsa/mv88e6xxx/chip.c   |  2 ++
 drivers/net/dsa/mv88e6xxx/chip.h   |  3 +++
 drivers/net/dsa/mv88e6xxx/rmu.c    | 50 ++++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h    | 15 ++++++++++++
 5 files changed, 71 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index b0b08c6f159c6..1f3571061a550 100644
--- a/drivers/net/dsa/mv88e6xxx/Makefile
+++ b/drivers/net/dsa/mv88e6xxx/Makefile
@@ -17,6 +17,7 @@ mv88e6xxx-objs += phy.o
 mv88e6xxx-objs += port.o
 mv88e6xxx-objs += port_hidden.o
 mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
+mv88e6xxx-objs += rmu.o
 mv88e6xxx-objs += serdes.o
 mv88e6xxx-objs += smi.o
 mv88e6xxx-objs += switchdev.o
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index dbfbdb982fc00..d6c6834cca2cb 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -41,6 +41,7 @@
 #include "phy.h"
 #include "port.h"
 #include "ptp.h"
+#include "rmu.h"
 #include "serdes.h"
 #include "smi.h"
 #include "tcam.h"
@@ -7265,6 +7266,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.crosschip_lag_change	= mv88e6xxx_crosschip_lag_change,
 	.crosschip_lag_join	= mv88e6xxx_crosschip_lag_join,
 	.crosschip_lag_leave	= mv88e6xxx_crosschip_lag_leave,
+	.conduit_state_change	= mv88e6xxx_rmu_conduit_state_change,
 };
 
 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 12fdcb63252eb..76958b588a908 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -457,6 +457,9 @@ struct mv88e6xxx_chip {
 
 	/* Global2 scratch register config data3 */
 	u8 g2_scratch_config3;
+
+	/* Remote Management Unit state. */
+	struct net_device *rmu_conduit;
 };
 
 #define TCAM_MATCH_SIZE 96
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
new file mode 100644
index 0000000000000..64e93e2eaf550
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch Remote Management Unit Support
+ *
+ * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
+ *
+ */
+
+#include <net/dsa.h>
+#include "chip.h"
+#include "global1.h"
+#include "rmu.h"
+
+void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
+					const struct net_device *conduit,
+					bool operational)
+{
+	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int port;
+	int ret;
+
+	port = dsa_towards_port(ds, cpu_dp->ds->index, cpu_dp->index);
+
+	mv88e6xxx_reg_lock(chip);
+
+	if (operational && chip->info->ops->rmu_enable) {
+		ret = chip->info->ops->rmu_enable(chip, port);
+		if (ret == -EOPNOTSUPP) {
+			dev_info(chip->dev, "RMU: not usable on this board");
+			goto out;
+		} else if (ret < 0) {
+			dev_err(chip->dev, "RMU: unable to enable on port %d %pe",
+				port, ERR_PTR(ret));
+			goto out;
+		}
+
+		WRITE_ONCE(chip->rmu_conduit, (struct net_device *)conduit);
+
+		dev_dbg(chip->dev, "RMU: Enabled on port %d", port);
+	} else {
+		if (chip->info->ops->rmu_disable)
+			chip->info->ops->rmu_disable(chip);
+
+		WRITE_ONCE(chip->rmu_conduit, NULL);
+	}
+
+out:
+	mv88e6xxx_reg_unlock(chip);
+}
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
new file mode 100644
index 0000000000000..71c4fc1d4113f
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Marvell 88E6xxx Switch Remote Management Unit Support
+ *
+ * Copyright (c) 2022 Mattias Forsblad <mattias.forsblad@gmail.com>
+ *
+ */
+
+#ifndef _MV88E6XXX_RMU_H_
+#define _MV88E6XXX_RMU_H_
+
+void mv88e6xxx_rmu_conduit_state_change(struct dsa_switch *ds,
+					const struct net_device *conduit,
+					bool operational);
+#endif /* _MV88E6XXX_RMU_H_ */

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 04/17] net: dsa: mv88e6xxx: Add RMU enable/disable for select switches.
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Mattias Forsblad <mattias.forsblad@gmail.com>

Add RMU enable and disable functionality for some Marvell SOHO
switches.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/dsa/mv88e6xxx/chip.c    | 15 +++++++++
 drivers/net/dsa/mv88e6xxx/chip.h    |  1 +
 drivers/net/dsa/mv88e6xxx/global1.c | 64 +++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/global1.h |  3 ++
 4 files changed, 83 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index eada2f71cb7b3..dbfbdb982fc00 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4265,6 +4265,7 @@ static const struct mv88e6xxx_ops mv88e6085_ops = {
 	.ppu_disable = mv88e6185_g1_ppu_disable,
 	.reset = mv88e6185_g1_reset,
 	.rmu_disable = mv88e6085_g1_rmu_disable,
+	.rmu_enable = mv88e6085_g1_rmu_enable,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
 	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
 	.stu_getnext = mv88e6352_g1_stu_getnext,
@@ -4343,6 +4344,7 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {
 	.pot_clear = mv88e6xxx_g2_pot_clear,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6085_g1_rmu_disable,
+	.rmu_enable = mv88e6085_g1_rmu_enable,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
 	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
 	.phylink_get_caps = mv88e6095_phylink_get_caps,
@@ -4477,6 +4479,7 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -4670,6 +4673,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_reset,
 	.rmu_disable = mv88e6352_g1_rmu_disable,
+	.rmu_enable = mv88e6352_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -4774,6 +4778,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_reset,
 	.rmu_disable = mv88e6352_g1_rmu_disable,
+	.rmu_enable = mv88e6352_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -4869,6 +4874,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -4928,6 +4934,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -4985,6 +4992,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5047,6 +5055,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_reset,
 	.rmu_disable = mv88e6352_g1_rmu_disable,
+	.rmu_enable = mv88e6352_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -5150,6 +5159,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5330,6 +5340,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -5488,6 +5499,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_reset,
 	.rmu_disable = mv88e6352_g1_rmu_disable,
+	.rmu_enable = mv88e6352_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6352_g1_vtu_getnext,
@@ -5551,6 +5563,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5615,6 +5628,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
@@ -5682,6 +5696,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
 	.reset = mv88e6352_g1_reset,
 	.rmu_disable = mv88e6390_g1_rmu_disable,
+	.rmu_enable = mv88e6390_g1_rmu_enable,
 	.atu_get_hash = mv88e6165_g1_atu_get_hash,
 	.atu_set_hash = mv88e6165_g1_atu_set_hash,
 	.vtu_getnext = mv88e6390_g1_vtu_getnext,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..12fdcb63252eb 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -701,6 +701,7 @@ struct mv88e6xxx_ops {
 
 	/* Remote Management Unit operations */
 	int (*rmu_disable)(struct mv88e6xxx_chip *chip);
+	int (*rmu_enable)(struct mv88e6xxx_chip *chip, int port);
 
 	/* Precision Time Protocol operations */
 	const struct mv88e6xxx_ptp_ops *ptp_ops;
diff --git a/drivers/net/dsa/mv88e6xxx/global1.c b/drivers/net/dsa/mv88e6xxx/global1.c
index 9820cd5967574..ae0b6e5628184 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.c
+++ b/drivers/net/dsa/mv88e6xxx/global1.c
@@ -536,18 +536,82 @@ int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 				      MV88E6085_G1_CTL2_RM_ENABLE, 0);
 }
 
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port)
+{
+	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+
+	switch (port) {
+	case 9:
+		val = MV88E6085_G1_CTL2_RM_ENABLE;
+		break;
+	case 10:
+		val = MV88E6085_G1_CTL2_RM_ENABLE | MV88E6085_G1_CTL2_P10RM;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6085_G1_CTL2_P10RM |
+				      MV88E6085_G1_CTL2_RM_ENABLE, val);
+}
+
 int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
 				      MV88E6352_G1_CTL2_RMU_MODE_DISABLED);
 }
 
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port)
+{
+	int val = MV88E6352_G1_CTL2_RMU_MODE_DISABLED;
+
+	switch (port) {
+	case 4:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_4;
+		break;
+	case 5:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_5;
+		break;
+	case 6:
+		val = MV88E6352_G1_CTL2_RMU_MODE_PORT_6;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK, val);
+}
+
 int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_RMU_MODE_MASK,
 				      MV88E6390_G1_CTL2_RMU_MODE_DISABLED);
 }
 
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port)
+{
+	int val = MV88E6390_G1_CTL2_RMU_MODE_DISABLED;
+
+	switch (port) {
+	case 0:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_0;
+		break;
+	case 1:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_1;
+		break;
+	case 9:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_9;
+		break;
+	case 10:
+		val = MV88E6390_G1_CTL2_RMU_MODE_PORT_10;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_RMU_MODE_MASK, val);
+}
+
 int mv88e6390_g1_stats_set_histogram(struct mv88e6xxx_chip *chip)
 {
 	return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_HIST_MODE_MASK,
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..4624d1bdfc243 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -316,8 +316,11 @@ int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
 int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port);
 
 int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6085_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port);
 int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6352_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port);
 int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
+int mv88e6390_g1_rmu_enable(struct mv88e6xxx_chip *chip, int port);
 
 int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 03/17] net: dsa: tag_dsa: Add helper to add DSA header to RMU frame
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Andrew Lunn <andrew@lunn.ch>

A RMU frame sent to the switch needs a DSA header. Add a helper to the
tag driver to add such a header to an skbuff, and provide access to
the helper via the tagger data structure. This keeps most of the
details of the DSA header in the tag driver.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 include/linux/dsa/mv88e6xxx.h |  3 +++
 net/dsa/tag_dsa.c             | 24 ++++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/include/linux/dsa/mv88e6xxx.h b/include/linux/dsa/mv88e6xxx.h
index 1e9460d6f7782..74a885b3bc4cb 100644
--- a/include/linux/dsa/mv88e6xxx.h
+++ b/include/linux/dsa/mv88e6xxx.h
@@ -13,6 +13,9 @@ struct dsa_tagger_data {
 	void (*rmu_frame2reg)(struct dsa_switch *ds,
 			      struct sk_buff *skb,
 			      u8 seqno);
+	/* Add DSA header to frame to be sent to switch */
+	void (*rmu_reg2frame)(struct dsa_switch *ds,
+			      struct sk_buff *skb);
 };
 
 #define MV88E6XXX_VID_STANDALONE	0
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index 5a95873e87340..a23c3279c49bd 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -341,6 +341,29 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 	return skb;
 }
 
+static void dsa_rmu_reg2frame(struct dsa_switch *ds,
+			      struct sk_buff *skb)
+{
+	enum dsa_cmd cmd = DSA_CMD_FROM_CPU;
+	u8 tag_dev = ds->index;
+	u8 *dsa_header;
+
+	dsa_header = skb_push(skb, DSA_HLEN);
+	dsa_header[0] = (cmd << 6) | tag_dev;
+	dsa_header[1] = BIT(1) | 0x3e << 2;
+	dsa_header[2] = 6 << 5 | 0xf;
+	dsa_header[3] = 0; /* Sequence number is filled in later */
+
+	if (ds->dst->tag_ops->proto == DSA_TAG_PROTO_EDSA) {
+		u8 *edsa_header = skb_push(skb, 4);
+
+		edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
+		edsa_header[1] = ETH_P_EDSA & 0xff;
+		edsa_header[2] = 0x00;
+		edsa_header[3] = 0x00;
+	}
+}
+
 static int dsa_tag_connect(struct dsa_switch *ds)
 {
 	struct dsa_tagger_data *tagger_data;
@@ -349,6 +372,7 @@ static int dsa_tag_connect(struct dsa_switch *ds)
 	if (!tagger_data)
 		return -ENOMEM;
 
+	tagger_data->rmu_reg2frame = dsa_rmu_reg2frame;
 	ds->tagger_data = tagger_data;
 
 	return 0;

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 02/17] net: dsa: mv88e6xxx: Introduce Marvell dsa tagger data operation.
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

From: Mattias Forsblad <mattias.forsblad@gmail.com>

Support connecting Marvell dsa tagger to the switch driver frame2reg
function to decode received RMU frames.

(Note: during teardown or failed setup, tagger_data may be NULL when
a late FRAME2REG frame arrives.)

Signed-off-by: Luke Howard <lukeh@padl.com>
Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
 include/linux/dsa/mv88e6xxx.h |  8 ++++++++
 net/dsa/tag_dsa.c             | 43 ++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/include/linux/dsa/mv88e6xxx.h b/include/linux/dsa/mv88e6xxx.h
index 8c3d45eca46bd..1e9460d6f7782 100644
--- a/include/linux/dsa/mv88e6xxx.h
+++ b/include/linux/dsa/mv88e6xxx.h
@@ -6,6 +6,14 @@
 #define _NET_DSA_TAG_MV88E6XXX_H
 
 #include <linux/if_vlan.h>
+#include <net/dsa.h>
+
+struct dsa_tagger_data {
+	/* DSA frame decoded to be from the RMU */
+	void (*rmu_frame2reg)(struct dsa_switch *ds,
+			      struct sk_buff *skb,
+			      u8 seqno);
+};
 
 #define MV88E6XXX_VID_STANDALONE	0
 #define MV88E6XXX_VID_BRIDGED		(VLAN_N_VID - 1)
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index d5ffee35fbb53..5a95873e87340 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -201,14 +201,17 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
 static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 				  u8 extra)
 {
+	struct dsa_tagger_data *tagger_data;
 	bool trap = false, trunk = false;
 	int source_device, source_port;
+	struct dsa_switch *ds;
 	enum dsa_code code;
 	enum dsa_cmd cmd;
 	u8 *dsa_header;
 
 	/* The ethertype field is part of the DSA header. */
 	dsa_header = dsa_etype_header_pos_rx(skb);
+	source_device = dsa_header[0] & 0x1f;
 
 	cmd = dsa_header[0] >> 6;
 	switch (cmd) {
@@ -220,12 +223,20 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 		code = (dsa_header[1] & 0x6) | ((dsa_header[2] >> 4) & 1);
 
 		switch (code) {
-		case DSA_CODE_FRAME2REG:
-			/* Remote management is not implemented yet,
-			 * drop.
-			 */
+		case DSA_CODE_FRAME2REG: {
+			u8 seqno = dsa_header[3];
+
+			ds = dsa_conduit_find_switch(dev, source_device);
+			if (!ds) {
+				kfree_skb(skb);
+				return NULL;
+			}
+			tagger_data = ds->tagger_data;
+			if (likely(tagger_data && tagger_data->rmu_frame2reg))
+				tagger_data->rmu_frame2reg(ds, skb, seqno);
 			kfree_skb(skb);
 			return NULL;
+		}
 		case DSA_CODE_ARP_MIRROR:
 		case DSA_CODE_POLICY_MIRROR:
 			/* Mark mirrored packets to notify any upper
@@ -256,7 +267,6 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 		return NULL;
 	}
 
-	source_device = dsa_header[0] & 0x1f;
 	source_port = (dsa_header[1] >> 3) & 0x1f;
 
 	if (trunk) {
@@ -331,6 +341,25 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 	return skb;
 }
 
+static int dsa_tag_connect(struct dsa_switch *ds)
+{
+	struct dsa_tagger_data *tagger_data;
+
+	tagger_data = kzalloc_obj(*tagger_data, GFP_KERNEL);
+	if (!tagger_data)
+		return -ENOMEM;
+
+	ds->tagger_data = tagger_data;
+
+	return 0;
+}
+
+static void dsa_tag_disconnect(struct dsa_switch *ds)
+{
+	kfree(ds->tagger_data);
+	ds->tagger_data = NULL;
+}
+
 #if IS_ENABLED(CONFIG_NET_DSA_TAG_DSA)
 
 static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev)
@@ -353,6 +382,8 @@ static const struct dsa_device_ops dsa_netdev_ops = {
 	.proto	  = DSA_TAG_PROTO_DSA,
 	.xmit	  = dsa_xmit,
 	.rcv	  = dsa_rcv,
+	.connect  = dsa_tag_connect,
+	.disconnect = dsa_tag_disconnect,
 	.needed_headroom = DSA_HLEN,
 };
 
@@ -397,6 +428,8 @@ static const struct dsa_device_ops edsa_netdev_ops = {
 	.proto	  = DSA_TAG_PROTO_EDSA,
 	.xmit	  = edsa_xmit,
 	.rcv	  = edsa_rcv,
+	.connect  = dsa_tag_connect,
+	.disconnect = dsa_tag_disconnect,
 	.needed_headroom = EDSA_HLEN,
 };
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 01/17] net: dsa: mv88e6xxx: use lockdep_assert_held() if CONFIG_LOCKDEP
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard
In-Reply-To: <20260703-net-next-mv88e6xxx-rmu-v1-0-991a27d78bca@padl.com>

If CONFIG_LOCKDEP is enabled, use lockdep_assert_held() rather than an
explicit check for the chip's register mutex.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..eada2f71cb7b3 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -48,10 +48,14 @@
 
 static void assert_reg_lock(struct mv88e6xxx_chip *chip)
 {
+#ifdef CONFIG_LOCKDEP
+	lockdep_assert_held(&chip->reg_lock);
+#else
 	if (unlikely(!mutex_is_locked(&chip->reg_lock))) {
 		dev_err(chip->dev, "Switch registers lock not held!\n");
 		dump_stack();
 	}
+#endif
 }
 
 int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val)

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 00/17] net: dsa: mv88e6xxx: support for Remote Management Unit
From: Luke Howard @ 2026-07-03  7:46 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli
  Cc: Cedric Jehasse, Max Holtmann, Max Hunter, Kieran Tyrrell,
	Ryan Wilkins, Mattias Forsblad, netdev, linux-kernel, Luke Howard

This patch set shephards a series of commits made by Andrew Lunn
and others to support remotely managing Marvell switches over
Ethernet (as opposed to MDIO) using the Remote Management Unit
(RMU).

This is the second of four patch sets: the remaining two add
support for remote management when MDIO is unavailable.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
Andrew Lunn (11):
      net: dsa: tag_dsa: Add helper to add DSA header to RMU frame
      net: dsa: mv88e6xxx: Add conduit state change handler
      net: dsa: mv88e6xxx: Remove mv88e6xxx_rmu_setup()
      net: dsa: mv88e6xxx: Add connect_tag_protocol handler
      net: dsa: mv88e6xxx: Add sanity check of received RMU frame
      net: dsa: mv88e6xxx: Get Product ID when enabling RMU
      net: dsa: mv88e6xxx: Add RMU register read/write/wait for bit
      net: dsa: mv88e6xxx: Move available stats into info structure
      net: dsa: mv88e6xxx: Centralise common statistics check
      net: dsa: mv88e6xxx: Get some MIB stats via the RMU
      net: dsa: mv88e6xxx: Time RMU operations and disable if slow

Luke Howard (4):
      net: dsa: mv88e6xxx: use lockdep_assert_held() if CONFIG_LOCKDEP
      net: dsa: mv88e6xxx: cancel PTP polling work on .shutdown
      net: dsa: mv88e6xxx: size ATU snapshot buffer and region by num_macs
      net: dsa: mv88e6xxx: RMU DUMP_ATU support in devlink

Mattias Forsblad (2):
      net: dsa: mv88e6xxx: Introduce Marvell dsa tagger data operation.
      net: dsa: mv88e6xxx: Add RMU enable/disable for select switches.

 drivers/net/dsa/mv88e6xxx/Makefile  |   1 +
 drivers/net/dsa/mv88e6xxx/chip.c    |  92 ++++--
 drivers/net/dsa/mv88e6xxx/chip.h    |  35 +++
 drivers/net/dsa/mv88e6xxx/devlink.c |  71 +++--
 drivers/net/dsa/mv88e6xxx/global1.c |  64 ++++
 drivers/net/dsa/mv88e6xxx/global1.h |   3 +
 drivers/net/dsa/mv88e6xxx/global2.c |   1 +
 drivers/net/dsa/mv88e6xxx/ptp.c     |  14 +-
 drivers/net/dsa/mv88e6xxx/ptp.h     |   5 +
 drivers/net/dsa/mv88e6xxx/rmu.c     | 611 ++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h     | 145 +++++++++
 include/linux/dsa/mv88e6xxx.h       |  11 +
 net/dsa/tag_dsa.c                   |  67 +++-
 13 files changed, 1073 insertions(+), 47 deletions(-)
---
base-commit: 165539241e210db8e962479c3c5072372576b618
change-id: 20260613-net-next-mv88e6xxx-rmu-2db17825ece0

Best regards,
--  
Luke Howard <lukeh@padl.com>


^ permalink raw reply

* [PATCH net v2 2/2] net: fman: add error cleanup path in fman_probe
From: ZhaoJinming @ 2026-07-03  7:43 UTC (permalink / raw)
  To: madalin.bucur, sean.anderson
  Cc: netdev, andrew+netdev, davem, edumazet, kuba, pabeni,
	igal.liberman, linux-kernel, ZhaoJinming
In-Reply-To: <20260703074324.907294-1-zhaojinming@uniontech.com>

fman_init() and devm_request_irq() failure paths in fman_probe()
do not free fman and its sub-resources (keygen, muram allocations,
state, cfg), causing memory leaks on probe failure.

Add fman_muram_finish() to properly tear down a MURAM partition
(gen_pool_destroy + iounmap + kfree), complementing the existing
fman_muram_init().

Add fman_free_resources() that releases all fman sub-resources
in the correct order:
- devm_free_irq() for any already-registered IRQ handlers
- kfree(fman->keygen)
- free_init_resources() for MURAM CAM/FIFO allocations
- kfree(fman->cfg)
- fman_muram_finish(fman->muram) for the MURAM management object
- kfree(fman->state)
- kfree(fman)

Use two goto labels in fman_probe():
- err_irq: main IRQ registered but err_irq failed -- free main IRQ
  then fall through to release resources
- err_no_irq: no IRQ registered -- just release resources

The IRQ handlers must be explicitly freed before kfree(fman) to
avoid a window where a shared-IRQ spurious firing could dereference
the freed dev_id.

Note: fman_config() is not changed -- it already frees fman
internally on all its error paths, so fman_probe() must not touch
fman after fman_config() fails.

v2:
- add explicit devm_free_irq() before kfree(fman) to eliminate
  a potential UAF window on the cleanup path
- add fman_muram_finish() for complete MURAM teardown
- add kfree(fman->cfg) to release config structure

Fixes: 414fd46e7762 ("fsl/fman: Add FMan support")
Signed-off-by: ZhaoJinming <zhaojinming@uniontech.com>
---
 drivers/net/ethernet/freescale/fman/fman.c    | 31 +++++++++++++++++--
 .../net/ethernet/freescale/fman/fman_muram.c  | 15 +++++++++
 .../net/ethernet/freescale/fman/fman_muram.h  |  2 ++
 3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c
index 6947f3bc7c87..752c0df0e17c 100644
--- a/drivers/net/ethernet/freescale/fman/fman.c
+++ b/drivers/net/ethernet/freescale/fman/fman.c
@@ -2806,6 +2806,24 @@ static struct fman *read_dts_node(struct platform_device *of_dev)
 	return ERR_PTR(err);
 }
 
+static void fman_free_resources(struct fman *fman, struct device *dev,
+				bool irq_registered)
+{
+	/* Free IRQs first while fman is still valid */
+	if (irq_registered) {
+		if (fman->dts_params.err_irq != 0)
+			devm_free_irq(dev, fman->dts_params.err_irq, fman);
+		devm_free_irq(dev, fman->dts_params.irq, fman);
+	}
+
+	kfree(fman->keygen);
+	free_init_resources(fman);
+	kfree(fman->cfg);
+	fman_muram_finish(fman->muram);
+	kfree(fman->state);
+	kfree(fman);
+}
+
 static int fman_probe(struct platform_device *of_dev)
 {
 	struct fman *fman;
@@ -2821,12 +2839,13 @@ static int fman_probe(struct platform_device *of_dev)
 	err = fman_config(fman);
 	if (err) {
 		dev_err(dev, "%s: FMan config failed\n", __func__);
+		/* fman_config() frees fman internally on all error paths */
 		return -EINVAL;
 	}
 
 	if (fman_init(fman) != 0) {
 		dev_err(dev, "%s: FMan init failed\n", __func__);
-		return -EINVAL;
+		goto err_no_irq;
 	}
 
 	/* Register IRQ handlers only after initialization is complete.
@@ -2844,7 +2863,7 @@ static int fman_probe(struct platform_device *of_dev)
 	if (err < 0) {
 		dev_err(dev, "%s: irq %d allocation failed (error = %d)\n",
 			__func__, fman->dts_params.irq, err);
-		return err;
+		goto err_no_irq;
 	}
 
 	if (fman->dts_params.err_irq != 0) {
@@ -2854,7 +2873,7 @@ static int fman_probe(struct platform_device *of_dev)
 		if (err < 0) {
 			dev_err(dev, "%s: irq %d allocation failed (error = %d)\n",
 				__func__, fman->dts_params.err_irq, err);
-			return err;
+			goto err_irq;
 		}
 	}
 
@@ -2883,6 +2902,12 @@ static int fman_probe(struct platform_device *of_dev)
 	dev_dbg(dev, "FMan%d probed\n", fman->dts_params.id);
 
 	return 0;
+
+err_irq:
+	devm_free_irq(dev, fman->dts_params.irq, fman);
+err_no_irq:
+	fman_free_resources(fman, dev, false);
+	return err ?: -EINVAL;
 }
 
 static const struct of_device_id fman_match[] = {
diff --git a/drivers/net/ethernet/freescale/fman/fman_muram.c b/drivers/net/ethernet/freescale/fman/fman_muram.c
index 6ac7c2b0cb19..6c2b4f7a02b8 100644
--- a/drivers/net/ethernet/freescale/fman/fman_muram.c
+++ b/drivers/net/ethernet/freescale/fman/fman_muram.c
@@ -129,3 +129,18 @@ void fman_muram_free_mem(struct muram_info *muram, unsigned long offset,
 
 	gen_pool_free(muram->pool, addr, size);
 }
+
+/**
+ * fman_muram_finish
+ * @muram:	FM-MURAM module pointer.
+ *
+ * Frees all resources associated with a MURAM partition.
+ */
+void fman_muram_finish(struct muram_info *muram)
+{
+	if (!muram)
+		return;
+	iounmap(muram->vbase);
+	gen_pool_destroy(muram->pool);
+	kfree(muram);
+}
diff --git a/drivers/net/ethernet/freescale/fman/fman_muram.h b/drivers/net/ethernet/freescale/fman/fman_muram.h
index 3643af61bae2..a5cb544c0f08 100644
--- a/drivers/net/ethernet/freescale/fman/fman_muram.h
+++ b/drivers/net/ethernet/freescale/fman/fman_muram.h
@@ -23,4 +23,6 @@ unsigned long fman_muram_alloc(struct muram_info *muram, size_t size);
 void fman_muram_free_mem(struct muram_info *muram, unsigned long offset,
 			 size_t size);
 
+void fman_muram_finish(struct muram_info *muram);
+
 #endif /* __FM_MURAM_EXT */
-- 
2.20.1


^ permalink raw reply related

* [PATCH net v2 1/2] net: fman: move IRQ registration after init to prevent NULL deref and UAF
From: ZhaoJinming @ 2026-07-03  7:43 UTC (permalink / raw)
  To: madalin.bucur, sean.anderson
  Cc: netdev, andrew+netdev, davem, edumazet, kuba, pabeni,
	igal.liberman, linux-kernel, ZhaoJinming

read_dts_node() registers shared interrupt handlers via
devm_request_irq() with fman as dev_id. Two bugs exist in the
current code:

1) Pre-init NULL dereference: at registration time fman is only
partially initialized -- kzalloc_obj() zero-initializes all fields,
so fman->cfg and fman->fpm_regs are NULL. The handlers check
is_init_done(fman->cfg) to guard against incomplete init, but
is_init_done(NULL) returns true (intended to mean cfg was freed
after successful init), so the guard is bypassed and fpm_regs is
dereferenced. If another device on the same shared IRQ line fires
during the window between devm_request_irq() and fman_init(), the
handler accesses NULL fpm_regs via ioread32be(), causing a crash.

2) Use-after-free on probe failure: fman is allocated with
kzalloc_obj() (not devm), so on error paths in read_dts_node()
(ioremap failure, of_platform_populate failure) and fman_config(),
kfree(fman) is called while the devm IRQ handlers remain
registered. The driver core's subsequent devres_release_all() frees
the IRQ handlers, but during the window between kfree(fman) and
devm_free_irq(), a shared-IRQ spurious firing will dereference
the already-freed fman.

A previous attempt to fix issue #1 with an irq_ready flag protected
by READ_ONCE()/WRITE_ONCE() is insufficient on weakly-ordered
architectures. READ_ONCE()/WRITE_ONCE() only prevent compiler
optimization; they do not provide the memory ordering guarantees
(e.g., smp_store_release/smp_load_acquire) needed to ensure that
writes to register pointers are visible to the IRQ handler before
it observes the flag as true.

Fix both issues by moving devm_request_irq() out of read_dts_node()
and into fman_probe(), after both fman_config() and fman_init()
have completed. This eliminates both race windows: by the time the
handlers are registered, all register pointers are initialized
(preventing the NULL dereference), and since fman is never freed
after this point, the use-after-free cannot occur either.

Add an 'irq' field to struct fman_dts_params so that the primary IRQ
number parsed in read_dts_node() is available to fman_probe().

This replaces the previous approach (irq_ready flag with READ_ONCE)
and also supersedes the separate UAF fix patch ("fsl_fman: fix
use-after-free on IRQF_SHARED handler after probe failure" v3),
as moving IRQ registration after init resolves both issues
in a single change.

v2:
- move devm_request_irq() to fman_probe() after init (replaces
  irq_ready + READ_ONCE approach from v1)
- supersede the separate UAF fix patch (v3), as this patch
  resolves both issues in a single change

Fixes: 414fd46e7762 ("fsl/fman: Add FMan support")
Link: https://lore.kernel.org/netdev/20260626162323.GE1310988@horms.kernel.org/
Signed-off-by: ZhaoJinming <zhaojinming@uniontech.com>
---
 drivers/net/ethernet/freescale/fman/fman.c | 52 +++++++++++++---------
 drivers/net/ethernet/freescale/fman/fman.h |  1 +
 2 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c
index 299bab043175..6947f3bc7c87 100644
--- a/drivers/net/ethernet/freescale/fman/fman.c
+++ b/drivers/net/ethernet/freescale/fman/fman.c
@@ -2695,7 +2695,7 @@ static struct fman *read_dts_node(struct platform_device *of_dev)
 	void __iomem *base_addr;
 	struct resource *res;
 	u32 val, range[2];
-	int err, irq;
+	int err;
 	struct clk *clk;
 	u32 clk_rate;
 
@@ -2717,7 +2717,7 @@ static struct fman *read_dts_node(struct platform_device *of_dev)
 	err = platform_get_irq(of_dev, 0);
 	if (err < 0)
 		goto fman_node_put;
-	irq = err;
+	fman->dts_params.irq = err;
 
 	/* Get the FM error interrupt */
 	err = platform_get_irq(of_dev, 1);
@@ -2773,25 +2773,6 @@ static struct fman *read_dts_node(struct platform_device *of_dev)
 
 	of_node_put(muram_node);
 
-	err = devm_request_irq(&of_dev->dev, irq, fman_irq, IRQF_SHARED,
-			       "fman", fman);
-	if (err < 0) {
-		dev_err(&of_dev->dev, "%s: irq %d allocation failed (error = %d)\n",
-			__func__, irq, err);
-		goto fman_free;
-	}
-
-	if (fman->dts_params.err_irq != 0) {
-		err = devm_request_irq(&of_dev->dev, fman->dts_params.err_irq,
-				       fman_err_irq, IRQF_SHARED,
-				       "fman-err", fman);
-		if (err < 0) {
-			dev_err(&of_dev->dev, "%s: irq %d allocation failed (error = %d)\n",
-				__func__, fman->dts_params.err_irq, err);
-			goto fman_free;
-		}
-	}
-
 	base_addr = devm_platform_get_and_ioremap_resource(of_dev, 0, &res);
 	if (IS_ERR(base_addr)) {
 		err = PTR_ERR(base_addr);
@@ -2848,6 +2829,35 @@ static int fman_probe(struct platform_device *of_dev)
 		return -EINVAL;
 	}
 
+	/* Register IRQ handlers only after initialization is complete.
+	 * This prevents two issues:
+	 * 1) Pre-init NULL dereference: is_init_done(NULL) returns true,
+	 *    so a shared-IRQ spurious firing before fpm_regs is set would
+	 *    dereference NULL.
+	 * 2) Use-after-free on probe failure: fman was kzalloc'd (not devm),
+	 *    so on error paths kfree(fman) ran before devm_free_irq, leaving
+	 *    a window where the handler could fire with a freed dev_id.
+	 * By registering here, both problems are eliminated.
+	 */
+	err = devm_request_irq(dev, fman->dts_params.irq, fman_irq,
+			       IRQF_SHARED, "fman", fman);
+	if (err < 0) {
+		dev_err(dev, "%s: irq %d allocation failed (error = %d)\n",
+			__func__, fman->dts_params.irq, err);
+		return err;
+	}
+
+	if (fman->dts_params.err_irq != 0) {
+		err = devm_request_irq(dev, fman->dts_params.err_irq,
+				       fman_err_irq, IRQF_SHARED,
+				       "fman-err", fman);
+		if (err < 0) {
+			dev_err(dev, "%s: irq %d allocation failed (error = %d)\n",
+				__func__, fman->dts_params.err_irq, err);
+			return err;
+		}
+	}
+
 	if (fman->dts_params.err_irq == 0) {
 		fman_set_exception(fman, FMAN_EX_DMA_BUS_ERROR, false);
 		fman_set_exception(fman, FMAN_EX_DMA_READ_ECC, false);
diff --git a/drivers/net/ethernet/freescale/fman/fman.h b/drivers/net/ethernet/freescale/fman/fman.h
index 74eb62eba0d7..630d57c3144c 100644
--- a/drivers/net/ethernet/freescale/fman/fman.h
+++ b/drivers/net/ethernet/freescale/fman/fman.h
@@ -286,6 +286,7 @@ struct fman_dts_params {
 	struct resource *res;                   /* FMan memory resource */
 	u8 id;                                  /* FMan ID */
 
+	int irq;                                /* FMan IRQ */
 	int err_irq;                            /* FMan Error IRQ */
 
 	u16 clk_freq;                           /* FMan clock freq (In Mhz) */
-- 
2.20.1


^ permalink raw reply related

* Re: [PATCH net-next v9 1/4] net: phy: c45: add genphy_c45_pma_soft_reset()
From: Maxime Chevallier @ 2026-07-03  7:43 UTC (permalink / raw)
  To: javen, andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb
  Cc: netdev, linux-kernel, daniel, vladimir.oltean
In-Reply-To: <20260703071330.1707-2-javen_xu@realsil.com.cn>

Hi Javen,

On 7/3/26 09:13, javen wrote:
> From: Javen Xu <javen_xu@realsil.com.cn>
> 
> Add a generic Clause 45 software reset helper. The helper sets the reset
> bit in the PMA/PMD control register and waits until the bit is cleared by
> hardware.
> 
> Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
> Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>

Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>

Thanks,

Maxime

^ permalink raw reply

* [RFC PATCH 3/3] coredump, net: remove `SOCK_COREDUMP`
From: John Ericson @ 2026-07-03  7:39 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
	Christian Brauner, David Rheinsberg, Andy Lutomirski,
	Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
	Günther Noack, Paul Moore, linux-security-module,
	linux-kernel
In-Reply-To: <20260703073948.2541875-1-John.Ericson@Obsidian.Systems>

From: John Ericson <mail@JohnEricson.me>

The utility of the refactors of the last two commits is demonstrated by
fixing this glaring layer violation: the unix socket implementation
knowing about the coredump socket caller.

Before, when the only way to connect to a socket was via the UAPI
`struct sockaddr_un`, the only way to implement the proper logic that
the kernel needs to resolve the coredump socket path was via hacking it
into the socket implementation.

In addition to being quite ugly, this layer violation is not great for
security. The intent is that `SOCK_COREDUMP` can only be used by the
kernel, and to be clear, I have no reason to believe this is not
correctly enforced today. But because of the many functions with flags
arguments, this is not a locally-enforced invariant. Some change, at
some point, could mess up, and allow user-provided flags to sneak in,
and this strikes me as a mistake waiting to happen. At that point, a
user could exploit this to connect to any socket it likes, bypassing
permission checks.

Now, with the two functions we've just previously factored out, we can
fix the layering. The custom path lookup logic lives with the coredump
caller, where it belongs. Once the right `struct path` is found
(actually just the inode is needed), the coredump caller can resolve a
`struct sock *` from it, and then directly connect to it. With this
change, `SOCK_COREDUMP` is no longer needed at all, and can be deleted.
The layer violation is gone, and the security footgun is gone with it.

As an added bonus, remove `flags` parameters from a number of internal
functions that no longer need them. They were just taking flags
parameters for the sake of `SOCK_COREDUMP`, and so with that gone, those
flag parameters are also no longer needed. If or when there is a new
flag that motivates them, they can be added back.

Tested that `coredump_socket_protocol_test` still passes.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@JohnEricson.me>
---
 fs/coredump.c                 | 47 ++++++++++++++++++++++++-----------
 include/linux/lsm_hook_defs.h |  3 +--
 include/linux/net.h           |  1 -
 include/linux/security.h      |  4 +--
 net/unix/af_unix.c            | 42 ++++++++++---------------------
 security/landlock/fs.c        |  7 +-----
 security/security.c           |  5 ++--
 7 files changed, 51 insertions(+), 58 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index e68a76ff92a3..e1452021218e 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -14,6 +14,7 @@
 #include <linux/perf_event.h>
 #include <linux/highmem.h>
 #include <linux/spinlock.h>
+#include <linux/cred.h>
 #include <linux/key.h>
 #include <linux/personality.h>
 #include <linux/binfmts.h>
@@ -21,6 +22,7 @@
 #include <linux/sort.h>
 #include <linux/sched/coredump.h>
 #include <linux/sched/signal.h>
+#include <linux/sched/task.h>
 #include <linux/sched/task_stack.h>
 #include <linux/utsname.h>
 #include <linux/pid_namespace.h>
@@ -50,7 +52,6 @@
 #include <net/net_namespace.h>
 #include <net/sock.h>
 #include <uapi/linux/pidfd.h>
-#include <uapi/linux/un.h>
 #include <uapi/linux/coredump.h>
 
 #include <linux/uaccess.h>
@@ -668,17 +669,10 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
 static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)
 {
 	struct file *file __free(fput) = NULL;
-	struct sockaddr_un addr = {
-		.sun_family = AF_UNIX,
-	};
-	ssize_t addr_len;
-	int retval;
+	struct path root, path;
 	struct socket *socket;
-
-	addr_len = strscpy(addr.sun_path, cn->corename);
-	if (addr_len < 0)
-		return false;
-	addr_len += offsetof(struct sockaddr_un, sun_path) + 1;
+	struct sock *sk;
+	int retval;
 
 	/*
 	 * It is possible that the userspace process which is supposed
@@ -710,14 +704,37 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
 	 */
 	pidfs_coredump(cprm);
 
-	retval = kernel_connect(socket, (struct sockaddr_unsized *)(&addr), addr_len,
-				O_NONBLOCK | SOCK_COREDUMP);
+	/*
+	 * Resolve the socket path relative to init's root and with kernel
+	 * credentials, and with symlinks, magic links and escaping the
+	 * root all forbidden, so the dumping process cannot use its own
+	 * filesystem view to redirect its core to an arbitrary socket.
+	 */
+	task_lock(&init_task);
+	get_fs_root(init_task.fs, &root);
+	task_unlock(&init_task);
+
+	scoped_with_kernel_creds()
+		retval = vfs_path_lookup(root.dentry, root.mnt, cn->corename,
+					 LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS |
+					 LOOKUP_NO_MAGICLINKS, &path);
+	path_put(&root);
+	if (retval)
+		return false;
+
+	/* Connect directly to the socket bound there, by fd not by name. */
+	sk = unix_lookup_bsd_path(&path, SOCK_STREAM);
+	path_put(&path);
+	if (IS_ERR(sk))
+		return false;
 
+	retval = kernel_unix_connect_direct(sk, socket, O_NONBLOCK);
+	sock_put(sk);
 	if (retval) {
 		if (retval == -EAGAIN)
-			coredump_report_failure("Coredump socket %s receive queue full", addr.sun_path);
+			coredump_report_failure("Coredump socket %s receive queue full", cn->corename);
 		else
-			coredump_report_failure("Coredump socket connection %s failed %d", addr.sun_path, retval);
+			coredump_report_failure("Coredump socket connection %s failed %d", cn->corename, retval);
 		return false;
 	}
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec207..3d6fbb6d2628 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -323,8 +323,7 @@ LSM_HOOK(int, 0, watch_key, struct key *key)
 #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
 
 #if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
-LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
-	 int flags)
+LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other)
 #endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
 
 #ifdef CONFIG_SECURITY_NETWORK
diff --git a/include/linux/net.h b/include/linux/net.h
index f268f395ce47..285cb67927f0 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -102,7 +102,6 @@ enum sock_type {
 #ifndef SOCK_NONBLOCK
 #define SOCK_NONBLOCK	O_NONBLOCK
 #endif
-#define SOCK_COREDUMP	O_NOCTTY
 
 /**
  * enum sock_shutdown_cmd - Shutdown types
diff --git a/include/linux/security.h b/include/linux/security.h
index 153e9043058f..9797bd29c916 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1957,10 +1957,10 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
 
 #if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
 
-int security_unix_find(const struct path *path, struct sock *other, int flags);
+int security_unix_find(const struct path *path, struct sock *other);
 
 #else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
-static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+static inline int security_unix_find(const struct path *path, struct sock *other)
 {
 	return 0;
 }
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index aa94da1f8c24..7cb537b404cc 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1220,7 +1220,7 @@ struct sock *unix_lookup_bsd_path(const struct path *path, int type)
 EXPORT_SYMBOL_GPL(unix_lookup_bsd_path);
 
 static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
-				  int type, int flags)
+				  int type)
 {
 	struct path path;
 	struct sock *sk;
@@ -1228,29 +1228,13 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 
 	unix_mkname_bsd(sunaddr, addr_len);
 
-	if (flags & SOCK_COREDUMP) {
-		struct path root;
-
-		task_lock(&init_task);
-		get_fs_root(init_task.fs, &root);
-		task_unlock(&init_task);
-
-		scoped_with_kernel_creds()
-			err = vfs_path_lookup(root.dentry, root.mnt, sunaddr->sun_path,
-					      LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS |
-					      LOOKUP_NO_MAGICLINKS, &path);
-		path_put(&root);
-		if (err)
-			goto fail;
-	} else {
-		err = kern_path(sunaddr->sun_path, LOOKUP_FOLLOW, &path);
-		if (err)
-			goto fail;
+	err = kern_path(sunaddr->sun_path, LOOKUP_FOLLOW, &path);
+	if (err)
+		goto fail;
 
-		err = path_permission(&path, MAY_WRITE);
-		if (err)
-			goto path_put;
-	}
+	err = path_permission(&path, MAY_WRITE);
+	if (err)
+		goto path_put;
 
 	sk = unix_lookup_bsd_path(&path, type);
 	if (IS_ERR(sk)) {
@@ -1258,7 +1242,7 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 		goto path_put;
 	}
 
-	err = security_unix_find(&path, sk, flags);
+	err = security_unix_find(&path, sk);
 	if (err)
 		goto sock_put;
 
@@ -1297,12 +1281,12 @@ static struct sock *unix_find_abstract(struct net *net,
 
 static struct sock *unix_find_other(struct net *net,
 				    struct sockaddr_un *sunaddr,
-				    int addr_len, int type, int flags)
+				    int addr_len, int type)
 {
 	struct sock *sk;
 
 	if (sunaddr->sun_path[0])
-		sk = unix_find_bsd(sunaddr, addr_len, type, flags);
+		sk = unix_find_bsd(sunaddr, addr_len, type);
 	else
 		sk = unix_find_abstract(net, sunaddr, addr_len, type);
 
@@ -1558,7 +1542,7 @@ static int unix_dgram_connect(struct socket *sock, struct sockaddr_unsized *addr
 		}
 
 restart:
-		other = unix_find_other(sock_net(sk), sunaddr, alen, sock->type, 0);
+		other = unix_find_other(sock_net(sk), sunaddr, alen, sock->type);
 		if (IS_ERR(other)) {
 			err = PTR_ERR(other);
 			goto out;
@@ -1897,7 +1881,7 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
 	 * unix_stream_connect_commit() means "retry": the peer had died,
 	 * or its backlog was full and we slept -- so re-resolve the name.
 	 */
-	other = unix_find_other(net, sunaddr, addr_len, sk->sk_type, flags);
+	other = unix_find_other(net, sunaddr, addr_len, sk->sk_type);
 	if (IS_ERR(other)) {
 		err = PTR_ERR(other);
 		goto out_free;
@@ -2330,7 +2314,7 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 	if (msg->msg_namelen) {
 lookup:
 		other = unix_find_other(sock_net(sk), msg->msg_name,
-					msg->msg_namelen, sk->sk_type, 0);
+					msg->msg_namelen, sk->sk_type);
 		if (IS_ERR(other)) {
 			err = PTR_ERR(other);
 			goto out_free;
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f7e5e4ef9eac..d77080438c01 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1641,8 +1641,7 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
 	}
 }
 
-static int hook_unix_find(const struct path *const path, struct sock *other,
-			  int flags)
+static int hook_unix_find(const struct path *const path, struct sock *other)
 {
 	const struct landlock_ruleset *dom_other;
 	const struct landlock_cred_security *subject;
@@ -1652,10 +1651,6 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
 		.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
 	};
 
-	/* Lookup for the purpose of saving coredumps is OK. */
-	if (unlikely(flags & SOCK_COREDUMP))
-		return 0;
-
 	subject = landlock_get_applicable_subject(current_cred(),
 						  fs_resolve_unix, NULL);
 
diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..fabb75c88254 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4839,16 +4839,15 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
  * security_unix_find() - Check if a named AF_UNIX socket can connect
  * @path: path of the socket being connected to
  * @other: peer sock
- * @flags: flags associated with the socket
  *
  * This hook is called to check permissions before connecting to a named
  * AF_UNIX socket. The caller does not hold any locks on @other.
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_unix_find(const struct path *path, struct sock *other, int flags)
+int security_unix_find(const struct path *path, struct sock *other)
 {
-	return call_int_hook(unix_find, path, other, flags);
+	return call_int_hook(unix_find, path, other);
 }
 EXPORT_SYMBOL(security_unix_find);
 
-- 
2.54.0


^ permalink raw reply related

* [RFC PATCH 2/3] af_unix: factor out kernel_unix_connect_direct()
From: John Ericson @ 2026-07-03  7:39 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
	Christian Brauner, David Rheinsberg, Andy Lutomirski,
	Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
	Günther Noack, Paul Moore, linux-security-module,
	linux-kernel
In-Reply-To: <20260703073948.2541875-1-John.Ericson@Obsidian.Systems>

From: John Ericson <mail@JohnEricson.me>

I was hoping this was going to be a simple matter of factoring out the
back half of `unix_stream_connect`. No such luck was had, because
actually instead of `unix_stream_connect` looking up the socket from the
VFS once, it does it repeatedly in the same loop that is used to deal
with full listening queues.

(This behavior is rather surprising to me, because it would allow a
deleted and recreated socket to be picked up on the next loop iteration.
But, I don't want to make any UAPI-visible changes in this patch series,
so I did not consider changing it.)

Seeing that this was going to be more complex, I instead factored out
three helpers (setup, commit, cleanup) on a state struct, so I could
reuse them both in the existing `unix_stream_connect` and also in the
new `kernel_unix_connect_direct`. This allows each caller to implement a
slightly different loop:

- resource management of `struct sock *other`:
  - `unix_stream_connect` acquires (and releases) it.
  - `kernel_unix_connect_direct` uses the caller-provided one.

- stale `other` behavior:
  - `unix_stream_connect` retries, because on the next iteration the
    socket may have been replaced by a fresh one.
  - `kernel_unix_connect_direct` fails, because no reacquisition means
    staleness is permanent.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@JohnEricson.me>
---
 include/net/af_unix.h |   1 +
 net/unix/af_unix.c    | 247 +++++++++++++++++++++++++++++++++---------
 2 files changed, 199 insertions(+), 49 deletions(-)

diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index fe4547508af1..7d810321efa3 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -15,6 +15,7 @@
 #if IS_ENABLED(CONFIG_UNIX)
 struct unix_sock *unix_get_socket(struct file *filp);
 struct sock *unix_lookup_bsd_path(const struct path *path, int type);
+int kernel_unix_connect_direct(struct sock *other, struct socket *sock, int flags);
 #else
 static inline struct unix_sock *unix_get_socket(struct file *filp)
 {
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 3270299238c4..aa94da1f8c24 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1649,44 +1649,60 @@ static long unix_wait_for_peer(struct sock *other, long timeo)
 	return timeo;
 }
 
-static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
-			       int addr_len, int flags)
-{
-	struct sockaddr_un *sunaddr = (struct sockaddr_un *)uaddr;
-	struct sock *sk = sock->sk, *newsk = NULL, *other = NULL;
-	struct unix_sock *u = unix_sk(sk), *newu, *otheru;
-	struct unix_peercred peercred = {};
-	struct net *net = sock_net(sk);
-	struct sk_buff *skb = NULL;
-	unsigned char state;
-	long timeo;
-	int err;
+/*
+ * The state a stream connect() builds up before it has a peer: the new
+ * sock and the connection-request skb handed to the listener, the
+ * connecting side's credentials and its send timeout.
+ *
+ * - Built once by unix_stream_connect_setup()
+ * - Used to finish connecting by unix_stream_connect_commit()
+ * - Cleaned up in the failure case by unix_stream_connect_cleanup()
+ */
+struct unix_connect_state {
+	struct sock		*newsk;
+	struct sk_buff		*skb;
+	struct unix_peercred	peercred;
+	long			timeo;
+};
 
-	err = unix_validate_addr(sunaddr, addr_len);
-	if (err)
-		goto out;
+/* Free a connect state that no connection consumed (i.e. on failure). */
+static void unix_stream_connect_cleanup(struct unix_connect_state *st)
+{
+	consume_skb(st->skb);
+	unix_release_sock(st->newsk, 0);
+	drop_peercred(&st->peercred);
+}
 
-	err = BPF_CGROUP_RUN_PROG_UNIX_CONNECT_LOCK(sk, uaddr, &addr_len);
-	if (err)
-		goto out;
+/*
+ * Build the state a stream connect needs before it looks for a peer:
+ * autobind if required, snapshot the send timeout, and allocate the new
+ * sock, the request skb and the peer credentials.  On failure nothing is
+ * left allocated in @st.
+ */
+static int unix_stream_connect_setup(struct socket *sock, int flags,
+				     struct unix_connect_state *st)
+{
+	struct sock *sk = sock->sk, *newsk;
+	struct sk_buff *skb;
+	int err;
 
-	if (unix_may_passcred(sk) && !READ_ONCE(u->addr)) {
+	if (unix_may_passcred(sk) && !READ_ONCE(unix_sk(sk)->addr)) {
 		err = unix_autobind(sk);
 		if (err)
-			goto out;
+			return err;
 	}
 
-	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
+	st->timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
 
-	err = prepare_peercred(&peercred);
+	err = prepare_peercred(&st->peercred);
 	if (err)
-		goto out;
+		return err;
 
 	/* create new sock for complete connection */
-	newsk = unix_create1(net, NULL, 0, sock->type);
+	newsk = unix_create1(sock_net(sk), NULL, 0, sock->type);
 	if (IS_ERR(newsk)) {
 		err = PTR_ERR(newsk);
-		goto out;
+		goto out_drop;
 	}
 
 	/* Allocate skb for sending to listening sock */
@@ -1696,21 +1712,56 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
 		goto out_free_sk;
 	}
 
-restart:
-	/*  Find listening sock. */
-	other = unix_find_other(net, sunaddr, addr_len, sk->sk_type, flags);
-	if (IS_ERR(other)) {
-		err = PTR_ERR(other);
-		goto out_free_skb;
-	}
+	st->newsk = newsk;
+	st->skb = skb;
+	return 0;
+
+out_free_sk:
+	unix_release_sock(newsk, 0);
+out_drop:
+	drop_peercred(&st->peercred);
+	return err;
+}
+
+/*
+ * Positive returns from unix_stream_connect_commit() ask the caller to
+ * try again.  They are distinct only for a caller with a fixed peer
+ * (kernel_unix_connect_direct()): a full backlog can be retried on the
+ * same peer, but a peer found dead cannot -- the by-name path must
+ * re-resolve it, and a fixed peer has no such recourse and fails.
+ */
+#define UNIX_CONNECT_STALE 1	/* peer was found dead */
+#define UNIX_CONNECT_FULL  2	/* backlog was full and we slept */
+
+/*
+ * Try to connect @sk to the listening peer @other, using the connect
+ * state @st built by unix_stream_connect_setup().  Takes and releases
+ * unix_state_lock(@other) itself.
+ *
+ * Returns 0 on success (@st->skb queued to @other, @st->newsk linked to
+ * @sk and @st->peercred consumed), a negative errno on terminal failure,
+ * or a positive value (UNIX_CONNECT_STALE / UNIX_CONNECT_FULL) asking the
+ * caller to re-obtain @other and call again -- because @other was found
+ * dead, or its backlog was full and we slept (updating @st->timeo)
+ * waiting for room.
+ */
+static int unix_stream_connect_commit(struct sock *sk, struct sock *other,
+				      struct unix_connect_state *st)
+{
+	struct sock *newsk = st->newsk;
+	struct sk_buff *skb = st->skb;
+	struct unix_peercred *peercred = &st->peercred;
+	long *timeo = &st->timeo;
+	struct unix_sock *newu, *otheru;
+	unsigned char state;
+	int err;
 
 	unix_state_lock(other);
 
-	/* Apparently VFS overslept socket death. Retry. */
+	/* Apparently VFS overslept socket death; ask the caller to retry. */
 	if (sock_flag(other, SOCK_DEAD)) {
 		unix_state_unlock(other);
-		sock_put(other);
-		goto restart;
+		return UNIX_CONNECT_STALE;
 	}
 
 	if (other->sk_state != TCP_LISTEN ||
@@ -1720,19 +1771,19 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
 	}
 
 	if (unix_recvq_full_lockless(other)) {
-		if (!timeo) {
+		if (!*timeo) {
 			err = -EAGAIN;
 			goto out_unlock;
 		}
 
-		timeo = unix_wait_for_peer(other, timeo);
-		sock_put(other);
+		/* unix_wait_for_peer() drops unix_state_lock(other). */
+		*timeo = unix_wait_for_peer(other, *timeo);
 
-		err = sock_intr_errno(timeo);
+		err = sock_intr_errno(*timeo);
 		if (signal_pending(current))
-			goto out_free_skb;
+			return err;
 
-		goto restart;
+		return UNIX_CONNECT_FULL;
 	}
 
 	/* self connect and simultaneous connect are eliminated
@@ -1765,7 +1816,7 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
 	newsk->sk_state = TCP_ESTABLISHED;
 	newsk->sk_type = sk->sk_type;
 	newsk->sk_scm_recv_flags = other->sk_scm_recv_flags;
-	init_peercred(newsk, &peercred);
+	init_peercred(newsk, peercred);
 
 	newu = unix_sk(newsk);
 	newu->listener = other;
@@ -1813,20 +1864,118 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
 	spin_unlock(&other->sk_receive_queue.lock);
 	unix_state_unlock(other);
 	READ_ONCE(other->sk_data_ready)(other);
-	sock_put(other);
 	return 0;
 
 out_unlock:
 	unix_state_unlock(other);
+	return err;
+}
+
+static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
+			       int addr_len, int flags)
+{
+	struct sockaddr_un *sunaddr = (struct sockaddr_un *)uaddr;
+	struct sock *sk = sock->sk, *other;
+	struct unix_connect_state st = {};
+	struct net *net = sock_net(sk);
+	int err;
+
+	err = unix_validate_addr(sunaddr, addr_len);
+	if (err)
+		return err;
+
+	err = BPF_CGROUP_RUN_PROG_UNIX_CONNECT_LOCK(sk, uaddr, &addr_len);
+	if (err)
+		return err;
+
+	err = unix_stream_connect_setup(sock, flags, &st);
+	if (err)
+		return err;
+
+restart:
+	/* Find the listening sock.  A positive return from
+	 * unix_stream_connect_commit() means "retry": the peer had died,
+	 * or its backlog was full and we slept -- so re-resolve the name.
+	 */
+	other = unix_find_other(net, sunaddr, addr_len, sk->sk_type, flags);
+	if (IS_ERR(other)) {
+		err = PTR_ERR(other);
+		goto out_free;
+	}
+
+	err = unix_stream_connect_commit(sk, other, &st);
 	sock_put(other);
-out_free_skb:
-	consume_skb(skb);
-out_free_sk:
-	unix_release_sock(newsk, 0);
-out:
-	drop_peercred(&peercred);
+	switch (err) {
+	case 0:
+		return 0;
+	case UNIX_CONNECT_FULL:
+		goto restart;
+	case UNIX_CONNECT_STALE:
+		/* A full backlog or a dead peer: re-resolve and try again. */
+		goto restart;
+	case INT_MIN ... -1:
+		/* terminal errno, propagate as-is */
+		break;
+	default:
+		/* commit() only returns 0, a retry code, or an errno */
+		WARN_ONCE(1, "unix_stream_connect_commit() returned %d\n", err);
+		err = -EINVAL;
+		break;
+	}
+out_free:
+	unix_stream_connect_cleanup(&st);
+	return err;
+}
+
+/**
+ * kernel_unix_connect_direct - connect a socket to a specific AF_UNIX sock
+ * @other: a held listening sock to connect to (e.g. from
+ *         unix_lookup_bsd_path())
+ * @sock: the connecting socket, created with sock_create_kern()
+ * @flags: connect flags; without O_NONBLOCK a full listen backlog on
+ *         @other is waited on, as for connect(2)
+ *
+ * Connects @sock to @other without any name lookup, address validation
+ * or path-based permission check.  For in-kernel callers that have
+ * already located the target under their own policy.  The caller
+ * retains its reference on @other.
+ */
+int kernel_unix_connect_direct(struct sock *other, struct socket *sock, int flags)
+{
+	struct sock *sk = sock->sk;
+	struct unix_connect_state st = {};
+	int err;
+
+	err = unix_stream_connect_setup(sock, flags, &st);
+	if (err)
+		return err;
+
+restart:
+	sock_hold(other);
+	err = unix_stream_connect_commit(sk, other, &st);
+	sock_put(other);
+	switch (err) {
+	case 0:
+		return 0;
+	case UNIX_CONNECT_FULL:
+		goto restart;
+	case UNIX_CONNECT_STALE:
+		/* The peer is fixed, so a dead one cannot be re-found. */
+		err = -ECONNREFUSED;
+		break;
+	case INT_MIN ... -1:
+		/* terminal errno, propagate as-is */
+		break;
+	default:
+		/* commit() only returns 0, a retry code, or an errno */
+		WARN_ONCE(1, "unix_stream_connect_commit() returned %d\n", err);
+		err = -EINVAL;
+		break;
+	}
+	unix_stream_connect_cleanup(&st);
 	return err;
 }
+EXPORT_SYMBOL_GPL(kernel_unix_connect_direct);
 
 static int unix_socketpair(struct socket *socka, struct socket *sockb)
 {
-- 
2.54.0


^ permalink raw reply related

* [RFC PATCH 1/3] af_unix: factor out unix_lookup_bsd_path()
From: John Ericson @ 2026-07-03  7:39 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
	Christian Brauner, David Rheinsberg, Andy Lutomirski,
	Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
	Günther Noack, Paul Moore, linux-security-module,
	linux-kernel
In-Reply-To: <20260703073948.2541875-1-John.Ericson@Obsidian.Systems>

From: John Ericson <mail@JohnEricson.me>

Split the inode -> sock mapping out of `unix_find_bsd()` into a new
helper, `unix_lookup_bsd_path()`: given an already-resolved `struct
path`, check it is a socket, look the bound socket up by inode, and
check its type, returning a held `struct sock` (or an `ERR_PTR`).

`unix_find_bsd()` keeps doing the path resolution, the `MAY_WRITE`
permission check, the `security_unix_find()` LSM hook and
`touch_atime()`, and calls the helper for the lookup.  No functional
change.

The function documentation anticipates (in an example) the way this will
be used later in the patch series.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@JohnEricson.me>
---
 include/net/af_unix.h |  1 +
 net/unix/af_unix.c    | 50 ++++++++++++++++++++++++++++++++-----------
 2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 34f53dde65ce..fe4547508af1 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -14,6 +14,7 @@
 
 #if IS_ENABLED(CONFIG_UNIX)
 struct unix_sock *unix_get_socket(struct file *filp);
+struct sock *unix_lookup_bsd_path(const struct path *path, int type);
 #else
 static inline struct unix_sock *unix_get_socket(struct file *filp)
 {
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..3270299238c4 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1185,10 +1185,43 @@ static int unix_release(struct socket *sock)
 	return 0;
 }
 
+/**
+ * unix_lookup_bsd_path - find the AF_UNIX socket bound at a resolved path
+ * @path: a path the caller has already resolved under its own policy
+ * @type: required socket type (SOCK_STREAM/SOCK_SEQPACKET/SOCK_DGRAM)
+ *
+ * Unlike the connect(2) lookup, this performs no path resolution and no
+ * DAC or LSM check of its own: the caller is responsible for having
+ * resolved @path with whatever policy is appropriate.  Used by kernel
+ * callers (e.g. coredump-to-socket) that must resolve the path under
+ * their own root and credentials rather than the current task's.
+ *
+ * Returns a held sock, or an ERR_PTR.
+ */
+struct sock *unix_lookup_bsd_path(const struct path *path, int type)
+{
+	struct inode *inode = d_backing_inode(path->dentry);
+	struct sock *sk;
+
+	if (!S_ISSOCK(inode->i_mode))
+		return ERR_PTR(-ECONNREFUSED);
+
+	sk = unix_find_socket_byinode(inode);
+	if (!sk)
+		return ERR_PTR(-ECONNREFUSED);
+
+	if (sk->sk_type != type) {
+		sock_put(sk);
+		return ERR_PTR(-EPROTOTYPE);
+	}
+
+	return sk;
+}
+EXPORT_SYMBOL_GPL(unix_lookup_bsd_path);
+
 static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 				  int type, int flags)
 {
-	struct inode *inode;
 	struct path path;
 	struct sock *sk;
 	int err;
@@ -1219,18 +1252,11 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 			goto path_put;
 	}
 
-	err = -ECONNREFUSED;
-	inode = d_backing_inode(path.dentry);
-	if (!S_ISSOCK(inode->i_mode))
+	sk = unix_lookup_bsd_path(&path, type);
+	if (IS_ERR(sk)) {
+		err = PTR_ERR(sk);
 		goto path_put;
-
-	sk = unix_find_socket_byinode(inode);
-	if (!sk)
-		goto path_put;
-
-	err = -EPROTOTYPE;
-	if (sk->sk_type != type)
-		goto sock_put;
+	}
 
 	err = security_unix_find(&path, sk, flags);
 	if (err)
-- 
2.54.0


^ permalink raw reply related

* [RFC PATCH 0/3] coredump, net: fix layer violation with direct connection
From: John Ericson @ 2026-07-03  7:39 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
	Christian Brauner, David Rheinsberg, Andy Lutomirski,
	Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
	Günther Noack, Paul Moore, linux-security-module,
	linux-kernel

From: John Ericson <mail@JohnEricson.me>

In https://lore.kernel.org/all/akWxrjOl4Up02Bvq@pop-os.localdomain/ Cong
Wang asked about doing things without new syscalls for my fd-based
connect idea. This got me investigating a few things, all of which I hope to
submit as patches.

This is the first one. I stumbled on `SOCK_COREDUMP` in `af_unix.c`, and
I realized that this was --- right in the part of the kernel I was
already looking at --- an excellent example of something that directly
connecting to a socket could do better. This is not because the
filesystem would never be involved (the interface in procfs still
specifies a path) but because the core dumper wants to resolve that path
differently than the usual way.

The first two commits are refactors that expose/create the necessary
functionality, and then the last commit actually does the untangling of
the unix socket implementation and the core dumper. See especially that
third commit message for details.

I hope this is a compelling use-case for you all, that does not touch
the UABI yet, but also does just the sort of thing that would be nice to
expose with a new syscall.

John

John Ericson (3):
  af_unix: factor out unix_lookup_bsd_path()
  af_unix: factor out kernel_unix_connect_direct()
  coredump, net: remove `SOCK_COREDUMP`

 fs/coredump.c                 |  47 +++--
 include/linux/lsm_hook_defs.h |   3 +-
 include/linux/net.h           |   1 -
 include/linux/security.h      |   4 +-
 include/net/af_unix.h         |   2 +
 net/unix/af_unix.c            | 335 +++++++++++++++++++++++++---------
 security/landlock/fs.c        |   7 +-
 security/security.c           |   5 +-
 8 files changed, 287 insertions(+), 117 deletions(-)

-- 
2.54.0


^ permalink raw reply

* [PATCH 1/1] net: usb: aqc111: fix set_mac_address return value for bonding
From: Hanson Wang @ 2026-07-03  7:39 UTC (permalink / raw)
  To: netdev; +Cc: linux-usb, oneukum, Hanson Wang

aqc111_set_mac_addr() returns the result of aqc111_write_cmd() on
success. That function wraps usb_control_msg(), which returns the
number of bytes transferred (6 for ETH_ALEN) rather than zero.

Bonding calls ndo_set_mac_address() when enslaving an interface and
treats any non-zero return value as failure.

Return 0 on success and propagate negative error codes on failure.

Signed-off-by: Hanson Wang <hanson.wang@ugreen.com>
---
 drivers/net/usb/aqc111.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/aqc111.c b/drivers/net/usb/aqc111.c
index dd53f413c38f..da5b74637ee2 100644
--- a/drivers/net/usb/aqc111.c
+++ b/drivers/net/usb/aqc111.c
@@ -471,8 +471,12 @@ static int aqc111_set_mac_addr(struct net_device *net, void *p)
 		return ret;
 
 	/* Set the MAC address */
-	return aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_NODE_ID, ETH_ALEN,
-				ETH_ALEN, net->dev_addr);
+	ret = aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_NODE_ID, ETH_ALEN,
+			        ETH_ALEN, net->dev_addr);
+	if (ret < 0)
+		return ret;
+
+	return 0;
 }
 
 static int aqc111_vlan_rx_kill_vid(struct net_device *net,
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH RFC net-next] net: bridge: add BRIDGE_VLAN_INFO_DYNAMIC flag
From: Nikolay Aleksandrov @ 2026-07-03  7:34 UTC (permalink / raw)
  To: Luke Howard, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Ido Schimmel, Simon Horman
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, bridge, linux-kernel
In-Reply-To: <20260703-vlan-dynamic-entry-v1-1-c2cbb134036c@padl.com>

On 03/07/2026 09:54, Luke Howard wrote:
> Dynamic VLAN entries as specified in IEEE Std 802.1Q-2022,
> Clause 8.8.5. These allow a user-space VLAN registration
> protocol such as MVRP to mark a bridge's VLAN entries as
> dynamic, so they can be distinguished from administratively
> configured static entries.
> 
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Luke Howard <lukeh@padl.com>
> ---
> This patch adds support for Dynamic VLAN Entries, as specified
> in 802.1Q. I have marked it as RFC as a similar patch to add
> support for Dynamic Reservation Entries to the FDB was rejected
> on the argument that the dynamic bit should be stored separately.
> 
> Our MVRP implementation [1] will use this flag if available.
> 
> [1] https://github.com/PADL/OpenSRP

Hi,
Do this tracking entirely in your app, it doesn't bring anything to the
kernel and just adding flags because it is convenient is not acceptable.
These VLANs are the same from kernel POV and this flag doesn't change
anything functionally.
Alternatively perhaps a proto field similar to routes could help you out to
distinguish who installed a VLAN entry, not sure if that would help your
user-space app though, and you'll have to add a new rt proto id that should
get accepted.

Cheers,
  Nik

> ---
>   include/uapi/linux/if_bridge.h |  1 +
>   net/bridge/br_netlink.c        |  6 ++++++
>   net/bridge/br_vlan.c           | 18 ++++++++++++++----
>   3 files changed, 21 insertions(+), 4 deletions(-)
> 
> diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
> index 21a700c02ef76..a3abbfe79694b 100644
> --- a/include/uapi/linux/if_bridge.h
> +++ b/include/uapi/linux/if_bridge.h
> @@ -134,6 +134,7 @@ enum {
>   #define BRIDGE_VLAN_INFO_RANGE_END	(1<<4) /* VLAN is end of vlan range */
>   #define BRIDGE_VLAN_INFO_BRENTRY	(1<<5) /* Global bridge VLAN entry */
>   #define BRIDGE_VLAN_INFO_ONLY_OPTS	(1<<6) /* Skip create/delete/flags */
> +#define BRIDGE_VLAN_INFO_DYNAMIC	(1<<7) /* 802.1Q Dynamic VLAN Registration Entry */
>   
>   struct bridge_vlan_info {
>   	__u16 flags;
> diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> index b2cd4e39326d0..90f2e103f53d5 100644
> --- a/net/bridge/br_netlink.c
> +++ b/net/bridge/br_netlink.c
> @@ -388,6 +388,9 @@ static int br_fill_ifvlaninfo_compressed(struct sk_buff *skb,
>   		if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
>   			flags |= BRIDGE_VLAN_INFO_UNTAGGED;
>   
> +		if (v->flags & BRIDGE_VLAN_INFO_DYNAMIC)
> +			flags |= BRIDGE_VLAN_INFO_DYNAMIC;
> +
>   		if (vid_range_start == 0) {
>   			goto initvars;
>   		} else if ((v->vid - vid_range_end) == 1 &&
> @@ -440,6 +443,9 @@ static int br_fill_ifvlaninfo(struct sk_buff *skb,
>   		if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
>   			vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
>   
> +		if (v->flags & BRIDGE_VLAN_INFO_DYNAMIC)
> +			vinfo.flags |= BRIDGE_VLAN_INFO_DYNAMIC;
> +
>   		if (nla_put(skb, IFLA_BRIDGE_VLAN_INFO,
>   			    sizeof(vinfo), &vinfo))
>   			goto nla_put_failure;
> diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
> index 5560afcaaca32..e6bc59e3a4c4f 100644
> --- a/net/bridge/br_vlan.c
> +++ b/net/bridge/br_vlan.c
> @@ -54,9 +54,11 @@ static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
>   	vg->pvid = 0;
>   }
>   
> -/* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.
> - * If @commit is false, return just whether the BRIDGE_VLAN_INFO_PVID and
> - * BRIDGE_VLAN_INFO_UNTAGGED bits of @flags would produce any change onto @v.
> +/* Update the BRIDGE_VLAN_INFO_PVID, BRIDGE_VLAN_INFO_UNTAGGED and
> + * BRIDGE_VLAN_INFO_DYNAMIC flags of @v.
> + * If @commit is false, return just whether the BRIDGE_VLAN_INFO_PVID,
> + * BRIDGE_VLAN_INFO_UNTAGGED and BRIDGE_VLAN_INFO_DYNAMIC bits of @flags
> + * would produce any change onto @v.
>    */
>   static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
>   				bool commit)
> @@ -71,7 +73,8 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
>   
>   	/* check if anything would be changed on commit */
>   	change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(vg->pvid != v->vid) ||
> -		 ((flags ^ v->flags) & BRIDGE_VLAN_INFO_UNTAGGED);
> +		 ((flags ^ v->flags) & (BRIDGE_VLAN_INFO_UNTAGGED |
> +					BRIDGE_VLAN_INFO_DYNAMIC));
>   
>   	if (!commit)
>   		goto out;
> @@ -86,6 +89,11 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
>   	else
>   		v->flags &= ~BRIDGE_VLAN_INFO_UNTAGGED;
>   
> +	if (flags & BRIDGE_VLAN_INFO_DYNAMIC)
> +		v->flags |= BRIDGE_VLAN_INFO_DYNAMIC;
> +	else
> +		v->flags &= ~BRIDGE_VLAN_INFO_DYNAMIC;
> +
>   out:
>   	return change;
>   }
> @@ -1874,6 +1882,8 @@ static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
>   		info.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
>   	if (flags & BRIDGE_VLAN_INFO_PVID)
>   		info.flags |= BRIDGE_VLAN_INFO_PVID;
> +	if (flags & BRIDGE_VLAN_INFO_DYNAMIC)
> +		info.flags |= BRIDGE_VLAN_INFO_DYNAMIC;
>   
>   	if (nla_put(skb, BRIDGE_VLANDB_ENTRY_INFO, sizeof(info), &info))
>   		goto out_err;
> 
> ---
> base-commit: 2bb62a85aff6d4c14a62a476dfabaada3c1cc014
> change-id: 20260703-vlan-dynamic-entry-56a028e8990e
> 
> Best regards,
> --
> Luke Howard <lukeh@padl.com>
> 


^ permalink raw reply

* Re: [PATCH net] bnge/bng_re: fix ring ID widths
From: Paolo Abeni @ 2026-07-03  7:33 UTC (permalink / raw)
  To: Vikas Gupta, davem, edumazet, kuba, andrew+netdev, horms
  Cc: netdev, linux-kernel, linux-rdma, leonro, jgg, bhargava.marreddy,
	rahul-rg.gupta, vsrama-krishna.nemani, rajashekar.hudumula,
	ajit.khaparde, Siva Reddy Kallam, Dharmender Garg,
	Yendapally Reddy Dhananjaya Reddy
In-Reply-To: <20260630101554.1221733-1-vikas.gupta@broadcom.com>

On 6/30/26 12:15 PM, Vikas Gupta wrote:
> diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> index 341c7f81ed09..bb0c79a1ee60 100644
> --- a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> +++ b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> @@ -184,7 +184,7 @@ struct bnge_ctx_mem_info {
>  struct bnge_ring_struct {
>  	struct bnge_ring_mem_info	ring_mem;
>  
> -	u16			fw_ring_id;
> +	u32			fw_ring_id;

Sashiko gemini has a few concerns about the id size increases:

https://sashiko.dev/#/patchset/20260630101554.1221733-1-vikas.gupta%40broadcom.com

please have a look.

/P


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox