Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 2/2] net: dsa: mv88e6xxx: embedded PTP timestamp support
From: Luke Howard @ 2026-07-03  6:41 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6xxx-ptp-fixes-v1-0-0138581889a9@padl.com>

mv88e6xxx switches can support embedding PTP timestamps directly
in the frame, either as a trailer or at a configurable offset
(typically the reserved bytes in the PTP header).

Add support for this on the 88E6341 and 88E6352 switches, being
those on which I was able to verify this. Other switch chips may
also work. The arrival timestamp offsets are relative to the PTP
common header and will work for both L2 and L3 PTP packets; the
respective headers are skipped by the number of bytes set in the
ETJump and IPJump registers, which are initialized to sensible
defaults and are VLAN tag-aware.

(Note: the 6352 datasheet incorrectly states that ETJump and
IPJump are initialized to zero. They are initialized to 12 and
2 respectively; this is corrected in the 6341 data sheet.)

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c     |   3 +
 drivers/net/dsa/mv88e6xxx/chip.h     |   8 +++
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 122 +++++++++++++++++++++++++++++++----
 drivers/net/dsa/mv88e6xxx/hwtstamp.h |  14 ++++
 4 files changed, 135 insertions(+), 12 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..c3277c1f3d785 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -28,6 +28,7 @@
 #include <linux/of_mdio.h>
 #include <linux/platform_data/mv88e6xxx.h>
 #include <linux/property.h>
+#include <linux/ptp_classify.h>
 #include <linux/netdevice.h>
 #include <linux/gpio/consumer.h>
 #include <linux/phylink.h>
@@ -6370,6 +6371,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
 		.ops = &mv88e6341_ops,
+		.arr_ts_mode = offsetof(struct ptp_header, reserved2),
 	},
 
 	[MV88E6350] = {
@@ -6447,6 +6449,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
 		.ops = &mv88e6352_ops,
+		.arr_ts_mode = offsetof(struct ptp_header, reserved2),
 	},
 	[MV88E6361] = {
 		.prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6361,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..b6a90eba81c43 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -180,6 +180,14 @@ struct mv88e6xxx_info {
 	 * port 0, 1 means internal PHYs range starts at port 1, etc
 	 */
 	unsigned int internal_phys_offset;
+
+	/* Arrival Time Stamp Mode (ArrTSMode); see the ArrTSMode encoding in
+	 * hwtstamp.h. Zero (the default) leaves arrival time stamps in the
+	 * switch registers; non-zero embeds them in the frame, either appended
+	 * as a trailer or overwritten at that byte offset past the start of the
+	 * PTP common header.
+	 */
+	unsigned int arr_ts_mode;
 };
 
 struct mv88e6xxx_atu_entry {
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 57ff77496864f..341a9e33021d7 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -8,6 +8,8 @@
  *      Erik Hons <erik.hons@ni.com>
  *      Brandon Streiff <brandon.streiff@ni.com>
  *      Dane Wagner <dane.wagner@ni.com>
+ *
+ * Copyright (c) 2025 PADL Software Pty Ltd
  */
 
 #include "chip.h"
@@ -15,6 +17,7 @@
 #include "hwtstamp.h"
 #include "ptp.h"
 #include <linux/ptp_classify.h>
+#include <linux/unaligned.h>
 
 #define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
 
@@ -245,6 +248,63 @@ static int seq_match(struct sk_buff *skb, u16 ts_seqid)
 	return ts_seqid == ntohs(hdr->sequence_id);
 }
 
+static bool parse_embedded_ts(unsigned int arr_ts_mode,
+			      struct sk_buff *skb, u64 *ns)
+{
+	struct ptp_header *hdr;
+
+	*ns = 0;
+
+	/* APPEND means the switch appended the time stamp as a 4-byte trailer
+	 * (not all switches support this). Any other non-zero value is the byte
+	 * offset past the start of the PTP common header at which the switch
+	 * overwrote the time stamp in place (e.g. the reserved header bytes).
+	 */
+	if (arr_ts_mode == MV88E6XXX_PTP_ARR_TS_MODE_APPEND && skb->len >= 4) {
+		*ns = (u64)get_unaligned_be32(&skb->data[skb->len - 4]);
+		skb_trim(skb, skb->len - 4);
+	} else if (arr_ts_mode + 4 <= sizeof(*hdr)) {
+		hdr = ptp_parse_header(skb, SKB_PTP_TYPE(skb));
+		if (!hdr)
+			return false;
+
+		*ns = (u64)get_unaligned_be32((u8 *)hdr + arr_ts_mode);
+		memset((u8 *)hdr + arr_ts_mode, 0, 4);
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+static void mv88e6xxx_get_rxts_embedded(struct mv88e6xxx_chip *chip,
+					struct mv88e6xxx_port_hwtstamp *ps,
+					struct sk_buff *skb)
+{
+	struct sk_buff_head *rxq = &ps->rx_queue;
+	struct skb_shared_hwtstamps *shwt;
+	struct sk_buff_head received;
+	unsigned long flags;
+	u64 ns;
+
+	__skb_queue_head_init(&received);
+	spin_lock_irqsave(&rxq->lock, flags);
+	skb_queue_splice_tail_init(rxq, &received);
+	spin_unlock_irqrestore(&rxq->lock, flags);
+
+	for ( ; skb; skb = __skb_dequeue(&received)) {
+		if (parse_embedded_ts(chip->info->arr_ts_mode, skb, &ns)) {
+			mv88e6xxx_reg_lock(chip);
+			ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
+			mv88e6xxx_reg_unlock(chip);
+			shwt = skb_hwtstamps(skb);
+			memset(shwt, 0, sizeof(*shwt));
+			shwt->hwtstamp = ns_to_ktime(ns);
+		}
+		netif_rx(skb);
+	}
+}
+
 static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
 			       struct mv88e6xxx_port_hwtstamp *ps,
 			       struct sk_buff *skb, u16 reg,
@@ -307,8 +367,21 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
 	const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
 	struct sk_buff *skb;
 
-	skb = skb_dequeue(&ps->rx_queue);
+	if (chip->info->arr_ts_mode) {
+		/* If arr_ts_mode is set, the timestamps are embedded in the
+		 * frames so a register read is not required. We still need a
+		 * work queue rather than processing inline because
+		 * timecounter_cyc2time() takes the global mutex and this
+		 * cannot be called from mv88e6xxx_port_rxtstamp().
+		 */
+		skb = skb_dequeue(&ps->rx_queue);
+		if (skb)
+			mv88e6xxx_get_rxts_embedded(chip, ps, skb);
 
+		return;
+	}
+
+	skb = skb_dequeue(&ps->rx_queue);
 	if (skb)
 		mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr0_sts_reg,
 				   &ps->rx_queue);
@@ -350,7 +423,7 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
 
 	SKB_PTP_TYPE(skb) = type;
 
-	if (is_pdelay_msg(hdr))
+	if (!chip->info->arr_ts_mode && is_pdelay_msg(hdr))
 		skb_queue_tail(&ps->rx_queue2, skb);
 	else
 		skb_queue_tail(&ps->rx_queue, skb);
@@ -530,14 +603,37 @@ int mv88e6165_global_enable(struct mv88e6xxx_chip *chip)
 
 int mv88e6352_hwtstamp_port_disable(struct mv88e6xxx_chip *chip, int port)
 {
-	return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
-					MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+	int err;
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+				       MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2, 0);
+	if (err)
+		return err;
+
+	return 0;
 }
 
 int mv88e6352_hwtstamp_port_enable(struct mv88e6xxx_chip *chip, int port)
 {
-	return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
-					MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+	int err;
+
+	if (chip->info->arr_ts_mode) {
+		err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG2,
+					       chip->info->arr_ts_mode << 8);
+		if (err)
+			return err;
+	}
+
+	err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
+				       MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
+	if (err)
+		return err;
+
+	return 0;
 }
 
 static int mv88e6xxx_hwtstamp_port_setup(struct mv88e6xxx_chip *chip, int port)
@@ -591,12 +687,14 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
 	if (err)
 		return err;
 
-	/* Use ARRIVAL1 for peer delay messages. */
-	err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
-				  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
-				  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
-	if (err)
-		return err;
+	if (!chip->info->arr_ts_mode) {
+		/* Use ARRIVAL1 for peer delay messages. */
+		err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
+					  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
+					  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
+		if (err)
+			return err;
+	}
 
 	/* 88E6341 devices default to timestamping at the PHY, but this has
 	 * a hardware issue that results in unreliable timestamps. Force
diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.h b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
index c359821d5a6ea..c25f53923e768 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.h
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.h
@@ -68,6 +68,20 @@
 #define MV88E6XXX_PORT_PTP_CFG2_DEP_IRQ_EN		0x0002
 #define MV88E6XXX_PORT_PTP_CFG2_ARR_IRQ_EN		0x0001
 
+/* Arrival Time Stamp Mode (ArrTSMode), CFG2 bits [15:8]: configures how the
+ * switch embeds the arrival time stamp (PTPArr0Time) into enabled PTP event
+ * frames.
+ *   0x00        frame modification disabled (time stamp read from registers)
+ *   0x01        append the 4-byte time stamp at the end of the frame,
+ *               growing the frame by four bytes
+ *   0x04..0xEF  overwrite the 4-byte time stamp in place, that many bytes past
+ *               the start of the PTP common header, without growing the frame
+ *               (offsetof(struct ptp_header, reserved2) targets the reserved
+ *               bytes of the header)
+ *   others      reserved
+ */
+#define MV88E6XXX_PTP_ARR_TS_MODE_APPEND		0x01
+
 /* Offset 0x03: PTP LED Configuration */
 #define MV88E6XXX_PORT_PTP_LED_CFG	0x03
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 0/2] net: dsa: mv88e6xxx: 6141/6341 workarounds
From: Luke Howard @ 2026-07-03  6:42 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard

This patch series addresses two issues I found with the 88E6341
switch, which likely also apply to the 88E6141 (which is the same
chip without AVB/TSN support).

The first was a genuine bug, which assumed the chip did not have
a dedicated ATU FID register (it does).

The other is an issue I noticed with hash collisions in the ATU.
I would appreciate some external testing before this commit is
reviewed, but I am including it as it was necessary for reliable
operation with multiple FIDs.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
Luke Howard (2):
      net: dsa: mv88e6xxx: write the ATU FID register on 88E6141/88E6341
      net: dsa: mv88e6xxx: use direct ATU hash on 88E6141/6341

 drivers/net/dsa/mv88e6xxx/chip.c        | 14 ++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h        |  1 +
 drivers/net/dsa/mv88e6xxx/global1.h     |  6 +++++-
 drivers/net/dsa/mv88e6xxx/global1_atu.c |  4 ++--
 4 files changed, 22 insertions(+), 3 deletions(-)
---
base-commit: 1c664ec4b9ea827b609d296921ed5bad8a40a158
change-id: 20260624-mv88e6x41-fixes-d4c84f955ebc

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


^ permalink raw reply

* [PATCH net-next 1/2] net: dsa: mv88e6xxx: write the ATU FID register on 88E6141/88E6341
From: Luke Howard @ 2026-07-03  6:42 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6x41-fixes-v1-0-fbd3a1bf8965@padl.com>

The existing code assumed the 88E6141/88E6341 did not have a dedicated
ATU FID register because of its database count (256), instead taking
the legacy path which resulted in the FID register never being set.

This resulted in every FDB entry being loaded into FID 0, breaking
VLAN aware bridging.

Fixes: a75961d0ebfd ("net: dsa: mv88e6xxx: Add support for ethernet switch 88E6341")
Assisted-by: Claude:claude-opus-4-8
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/global1_atu.c | 4 ++--
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 80b877c74513d..60d46680fb50e 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5863,6 +5863,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.family = MV88E6XXX_FAMILY_6341,
 		.name = "Marvell 88E6141",
 		.num_databases = 256,
+		.atu_fid_reg = true,
 		.num_macs = 2048,
 		.num_ports = 6,
 		.num_internal_phys = 5,
@@ -6350,6 +6351,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.family = MV88E6XXX_FAMILY_6341,
 		.name = "Marvell 88E6341",
 		.num_databases = 256,
+		.atu_fid_reg = true,
 		.num_macs = 2048,
 		.num_internal_phys = 5,
 		.num_ports = 6,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index e966e7c4cc5de..9f23d2a7165f0 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -131,6 +131,7 @@ struct mv88e6xxx_info {
 	u16 prod_num;
 	const char *name;
 	unsigned int num_databases;
+	bool atu_fid_reg;
 	unsigned int num_macs;
 	unsigned int num_ports;
 	unsigned int num_internal_phys;
diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c
index c47f068f56b32..aa5adc78607ca 100644
--- a/drivers/net/dsa/mv88e6xxx/global1_atu.c
+++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c
@@ -135,7 +135,7 @@ static int mv88e6xxx_g1_atu_op(struct mv88e6xxx_chip *chip, u16 fid, u16 op)
 	int err;
 
 	/* FID bits are dispatched all around gradually as more are supported */
-	if (mv88e6xxx_num_databases(chip) > 256) {
+	if (mv88e6xxx_num_databases(chip) > 256 || chip->info->atu_fid_reg) {
 		err = mv88e6xxx_g1_atu_fid_write(chip, fid);
 		if (err)
 			return err;
@@ -179,7 +179,7 @@ static int mv88e6xxx_g1_atu_fid_read(struct mv88e6xxx_chip *chip, u16 *fid)
 	u16 val = 0, upper = 0, op = 0;
 	int err = -EOPNOTSUPP;
 
-	if (mv88e6xxx_num_databases(chip) > 256) {
+	if (mv88e6xxx_num_databases(chip) > 256 || chip->info->atu_fid_reg) {
 		err = mv88e6xxx_g1_read(chip, MV88E6352_G1_ATU_FID, &val);
 		val &= 0xfff;
 		if (err)

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 2/2] net: dsa: mv88e6xxx: use direct ATU hash on 88E6141/6341
From: Luke Howard @ 2026-07-03  6:42 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6x41-fixes-v1-0-fbd3a1bf8965@padl.com>

The default 88E6341/88E6141 ATU hash algorithm appears to result
in frequent collisions, evicting permanent registrations (including
the broadcast address) out of the ATU.

This workaround disables hasing in the ATU control register. It may
have a performance impact (the data sheet notes this is for testing
only), but it also enables correctness, at least in local testing.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c    | 12 ++++++++++++
 drivers/net/dsa/mv88e6xxx/global1.h |  6 +++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 60d46680fb50e..53ed2b9e30979 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1699,6 +1699,18 @@ static int mv88e6xxx_atu_setup(struct mv88e6xxx_chip *chip)
 {
 	int err;
 
+	/* Avoid collisions on the 6341 family by disabling hashing. */
+	if (chip->info->family == MV88E6XXX_FAMILY_6341 &&
+	    chip->info->ops->atu_set_hash) {
+		u8 hash = MV88E6161_G1_ATU_CTL_HASH_DIRECT;
+
+		err = chip->info->ops->atu_set_hash(chip, hash);
+		if (err)
+			return err;
+
+		dev_info(chip->dev, "ATU hash mode set to 0x%x\n", hash);
+	}
+
 	err = mv88e6xxx_g1_atu_flush(chip, 0, true);
 	if (err)
 		return err;
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..e2a54eac64832 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -112,7 +112,11 @@
 /* Offset 0x0A: ATU Control Register */
 #define MV88E6XXX_G1_ATU_CTL		0x0a
 #define MV88E6XXX_G1_ATU_CTL_LEARN2ALL	0x0008
-#define MV88E6161_G1_ATU_CTL_HASH_MASK	0x0003
+#define MV88E6161_G1_ATU_CTL_HASH_MASK		0x0003
+#define MV88E6161_G1_ATU_CTL_HASH_RESERVED_0	0x0000
+#define MV88E6161_G1_ATU_CTL_HASH_DEFAULT	0x0001
+#define MV88E6161_G1_ATU_CTL_HASH_RESERVED_1	0x0002
+#define MV88E6161_G1_ATU_CTL_HASH_DIRECT	0x0003
 
 /* Offset 0x0B: ATU Operation Register */
 #define MV88E6XXX_G1_ATU_OP				0x0b

-- 
2.43.0


^ permalink raw reply related

* [PATCH RFC net-next v3] net: dsa: mv88e6xxx: MQPRIO support
From: Luke Howard @ 2026-07-03  6:44 UTC (permalink / raw)
  To: Jiri Pirko, Ivan Vecera, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Nikolay Aleksandrov,
	Ido Schimmel, Andrew Lunn, David Ahern, Shuah Khan, Andrew Lunn,
	Vladimir Oltean
  Cc: netdev, linux-kernel, bridge, linux-kselftest, Cedric Jehasse,
	Max Hunter, Kieran Tyrrell, Simon Gapp, Max Holtmann,
	Christoph Mellauner, Luke Howard

Add MQPRIO traffic class offload for the Marvell 6352 and 6390 families
of switches.

Three traffic classes are supported: legacy (TC0), low (TC1) and high
(TC2), corresponding to non-AVB, AVB Class B and AVB Class A traffic.
A single Ethernet frame priority may be mapped to each AVB class.

As the policy is per-switch, HW offload can only be enabled across
multiple ports if the policy matches. This is similar to configuration
of DCB ingress priority mappings on switches that only support global
configuration. Attempts to configure a different policy on an additional
port will return -EEXIST.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
This patch series introduces support for using MQPRIO to configure
egress queues, using the AVB frame priority to queue mapping which
is supported by some Marvell switches.

It does configure the isochronous pointer reservation and enable
the standard AVB mode on all ports. The former potentially belongs
in a local patch: discussion is welcome. For this reason, and also
Jakub's comments about technical debt [1], I have demoted this to
a RFC.

This is the only way to configure priority to queue mappings on
egress on Marvell switches (their configuration of _ingress_ mappings
is more flexible and, on the 6390, is supported per-port). Egress
queues can be set in TCAM entries, but my understanding is that
tc-flower offloading can only specify priority, not queue, mappings.

We are using it in conjunction with the since rejected support for
802.1Q Dynamic Reservation Entries to implement hardware offloaded
AVB (802.1BA) bridging. The Dynamic Reservation Entry extension to
the FDB could potentially be implemented using tc-flower in a manner
acceptable to upstream; I do not have the resources to investigate
this at this time.

[1] https://lore.kernel.org/all/20260615095112.1dca0977@kernel.org/
---
Changes in v3:
- dropped support for Dynamic Resevation Entries in the MDB. Although
  these are defined in the 802.1Q specification, it seemed unlikely that
  the Linux bridge would accept this change. I maintain the old patches
  at https://github.com/PADL/linux/tree/b4/mv88e6xxx-8021qat-mqprio.
- Link to v2: https://patch.msgid.link/20260602-mv88e6xxx-8021qat-mqprio-v2-0-72be14522e7c@padl.com

Changes in v2:
- dropped CBS implementation (this is provided separately by Cedric
  Jehasse's patch series, and is required for its definition of
  num_tx_queues and qav_info)
- added MQPRIO channel support
- admission control is configured using a bridge port flag rather than
  a device tree entry
- software bridge support for admission control
- Link to v1: https://lore.kernel.org/all/cover.1779841530.git.lukeh@padl.com/

To: Andrew Lunn <andrew@lunn.ch>
To: Vladimir Oltean <olteanv@gmail.com>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org
---
 drivers/net/dsa/mv88e6xxx/Makefile      |   3 +-
 drivers/net/dsa/mv88e6xxx/avb.c         | 189 +++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/avb.h         |  67 ++++++++++
 drivers/net/dsa/mv88e6xxx/chip.c        | 208 ++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h        |  45 +++++++
 drivers/net/dsa/mv88e6xxx/global1.h     |   1 +
 drivers/net/dsa/mv88e6xxx/global2.h     |   2 +
 drivers/net/dsa/mv88e6xxx/global2_avb.c | 121 +++++++++++++++++++
 8 files changed, 635 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index b0b08c6f159c6..6123b431e255e 100644
--- a/drivers/net/dsa/mv88e6xxx/Makefile
+++ b/drivers/net/dsa/mv88e6xxx/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o
-mv88e6xxx-objs := chip.o
+mv88e6xxx-objs := avb.o
+mv88e6xxx-objs += chip.o
 mv88e6xxx-objs += devlink.o
 mv88e6xxx-objs += global1.o
 mv88e6xxx-objs += global1_atu.o
diff --git a/drivers/net/dsa/mv88e6xxx/avb.c b/drivers/net/dsa/mv88e6xxx/avb.c
new file mode 100644
index 0000000000000..f02aa8a4ad458
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch AVB support
+ *
+ * Copyright (c) 2024-2026 PADL Software Pty Ltd
+ */
+
+#include "avb.h"
+#include "chip.h"
+#include "global1.h"
+#include "global2.h"
+#include "port.h"
+
+static int mv88e6xxx_qav_read(struct mv88e6xxx_chip *chip, int addr,
+			      u16 *data, int len)
+{
+	if (!chip->info->ops->avb_ops->qav_read)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->qav_read(chip, addr, data, len);
+}
+
+static int mv88e6xxx_qav_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
+{
+	if (!chip->info->ops->avb_ops->qav_write)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->qav_write(chip, addr, data);
+}
+
+static int mv88e6xxx_avb_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
+{
+	if (!chip->info->ops->avb_ops->avb_write)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->avb_write(chip, addr, data);
+}
+
+static int mv88e6xxx_port_avb_read(struct mv88e6xxx_chip *chip, int port,
+				   int addr, u16 *data, int len)
+{
+	if (!chip->info->ops->avb_ops->port_avb_read)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->port_avb_read(chip, port, addr,
+						       data, len);
+}
+
+static int mv88e6xxx_port_avb_write(struct mv88e6xxx_chip *chip, int port,
+				    int addr, u16 data)
+{
+	if (!chip->info->ops->avb_ops->port_avb_write)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->port_avb_write(chip, port, addr, data);
+}
+
+static int mv88e6xxx_qav_set_iso_ptr(struct mv88e6xxx_chip *chip, u16 threshold)
+{
+	u16 data;
+	int err;
+
+	err = mv88e6xxx_qav_read(chip, MV88E6XXX_QAV_CFG, &data, 1);
+	if (err)
+		return err;
+
+	data &= ~(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK);
+	data |= MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_SET(threshold);
+
+	return mv88e6xxx_qav_write(chip, MV88E6XXX_QAV_CFG, data);
+}
+
+static int mv88e6xxx_avb_set_port_avb_mode(struct mv88e6xxx_chip *chip,
+					   int port, u16 avb_mode)
+{
+	u16 data;
+	int err;
+
+	err = mv88e6xxx_port_avb_read(chip, port, MV88E6XXX_PORT_AVB_CFG,
+				      &data, 1);
+	if (err)
+		return err;
+
+	data &= ~(MV88E6XXX_PORT_AVB_CFG_AVB_MODE |
+		  MV88E6XXX_PORT_AVB_CFG_AVB_OVERRIDE |
+		  MV88E6XXX_PORT_AVB_CFG_AVB_TUNNEL);
+
+	data |= avb_mode & MV88E6XXX_PORT_AVB_CFG_AVB_MODE;
+	data |= MV88E6XXX_PORT_AVB_CFG_AVB_FILTER_BAD_AVB;
+
+	return mv88e6xxx_port_avb_write(chip, port, MV88E6XXX_PORT_AVB_CFG, data);
+}
+
+static u8 mv88e6xxx_mqprio_tc_fpri(const struct tc_mqprio_qopt *qopt, int tc)
+{
+	u8 fpri;
+
+	for (fpri = 0; fpri < IEEE_8021Q_MAX_PRIORITIES; fpri++)
+		if (qopt->prio_tc_map[fpri] == tc)
+			return fpri;
+
+	return 0;
+}
+
+u16 mv88e6xxx_avb_pri_map_to_reg(const struct tc_mqprio_qopt *qopt)
+{
+	u8 hi_fpri = mv88e6xxx_mqprio_tc_fpri(qopt, MV88E6XXX_AVB_TC_HI);
+	u8 lo_fpri = mv88e6xxx_mqprio_tc_fpri(qopt, MV88E6XXX_AVB_TC_LO);
+	u8 hi_qpri = qopt->offset[MV88E6XXX_AVB_TC_HI];
+	u8 lo_qpri = qopt->offset[MV88E6XXX_AVB_TC_LO];
+
+	return MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(hi_fpri) |
+	       MV88E6XXX_AVB_CFG_AVB_HI_QPRI_SET(hi_qpri) |
+	       MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(lo_fpri) |
+	       MV88E6XXX_AVB_CFG_AVB_LO_QPRI_SET(lo_qpri);
+}
+
+int mv88e6xxx_avb_enable(struct mv88e6xxx_chip *chip,
+			 struct tc_mqprio_qopt_offload *mqprio)
+{
+	const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+	int err, port;
+
+	if (!qav)
+		return -EOPNOTSUPP;
+
+	/* Reserve 64 (1 << 6) isochronous pointers per port for the AVB queues. */
+	err = mv88e6xxx_qav_set_iso_ptr(chip, mv88e6xxx_num_ports(chip) << 6);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+				  mv88e6xxx_avb_pri_map_to_reg(&mqprio->qopt));
+	if (err)
+		goto err_iso_ptr;
+
+	for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
+		if (!dsa_is_user_port(chip->ds, port))
+			continue;
+
+		err = mv88e6xxx_avb_set_port_avb_mode(chip, port,
+						      MV88E6XXX_PORT_AVB_CFG_AVB_MODE_STANDARD);
+		if (err)
+			goto err_port_mode;
+	}
+
+	return 0;
+
+err_port_mode:
+	while (--port >= 0) {
+		if (!dsa_is_user_port(chip->ds, port))
+			continue;
+
+		mv88e6xxx_avb_set_port_avb_mode(chip, port,
+						MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY);
+	}
+	mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB, qav->avb_pri_map);
+err_iso_ptr:
+	mv88e6xxx_qav_set_iso_ptr(chip, 0);
+
+	return err;
+}
+
+int mv88e6xxx_avb_disable(struct mv88e6xxx_chip *chip)
+{
+	const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+	int err, port;
+
+	if (!qav)
+		return -EOPNOTSUPP;
+
+	/* Return every user port to legacy. */
+	for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
+		if (!dsa_is_user_port(chip->ds, port))
+			continue;
+
+		err = mv88e6xxx_avb_set_port_avb_mode(chip, port,
+						      MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY);
+		if (err)
+			return err;
+	}
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB, qav->avb_pri_map);
+	if (err)
+		return err;
+
+	return mv88e6xxx_qav_set_iso_ptr(chip, 0);
+}
+
diff --git a/drivers/net/dsa/mv88e6xxx/avb.h b/drivers/net/dsa/mv88e6xxx/avb.h
new file mode 100644
index 0000000000000..f231824d4eac7
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Marvell 88E6xxx Switch AVB support
+ *
+ * Copyright (c) 2024-2026 PADL Software Pty Ltd
+ */
+
+#ifndef _MV88E6XXX_AVB_H
+#define _MV88E6XXX_AVB_H
+
+#include "chip.h"
+
+/* Global AVB registers */
+
+/* Offset 0x00: AVB Global Config */
+
+#define MV88E6XXX_AVB_CFG_AVB			0x00
+
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK	GENMASK(14, 12)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(x)	FIELD_GET(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, x)
+
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK	GENMASK(6, 4)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_GET(x)	FIELD_GET(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, x)
+
+#define MV88E6XXX_AVB_CFG_AVB_HI_QPRI_MASK	GENMASK(10, 8)
+#define MV88E6XXX_AVB_CFG_AVB_HI_QPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_HI_QPRI_MASK, p)
+
+#define MV88E6XXX_AVB_CFG_AVB_LO_QPRI_MASK	GENMASK(2, 0)
+#define MV88E6XXX_AVB_CFG_AVB_LO_QPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_LO_QPRI_MASK, p)
+
+/* Global Qav registers */
+#define MV88E6XXX_QAV_CFG			0x00
+
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK	GENMASK(9, 0)
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_GET(x)	FIELD_GET(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK, x)
+#define MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_SET(x)	FIELD_PREP(MV88E6XXX_QAV_CFG_GLOBAL_ISO_PTR_MASK, x)
+
+/* allow mgmt frames in isochronous pointer pool */
+#define MV88E6XXX_QAV_CFG_ADMIT_MGMT		0x8000
+
+/* Per-port AVB registers */
+
+/* Offset 0x00: AVB Port Config */
+#define MV88E6XXX_PORT_AVB_CFG				0x00
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE			GENMASK(15, 14)
+/* all frames legacy (non-AVB) unless overridden */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY		0x0000
+/* AVB frames indicated by priority */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_STANDARD	0x4000
+/* STANDARD && ATU has STATIC_AVB_NRL bit set */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_ENHANCED	0x8000
+/* ENHANCED && source port in destination port vector */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_MODE_SECURE		0xc000
+
+#define MV88E6XXX_PORT_AVB_CFG_AVB_OVERRIDE		0x2000
+#define MV88E6XXX_PORT_AVB_CFG_AVB_FILTER_BAD_AVB	0x1000
+#define MV88E6XXX_PORT_AVB_CFG_AVB_TUNNEL		0x0800
+#define MV88E6XXX_PORT_AVB_CFG_AVB_DISCARD_BAD		0x0400
+
+u16 mv88e6xxx_avb_pri_map_to_reg(const struct tc_mqprio_qopt *qopt);
+int mv88e6xxx_avb_enable(struct mv88e6xxx_chip *chip,
+			 struct tc_mqprio_qopt_offload *mqprio);
+int mv88e6xxx_avb_disable(struct mv88e6xxx_chip *chip);
+
+#endif /* _MV88E6XXX_AVB_H */
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index dc8f9a04ab5e5..2d619fb809c1e 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -34,6 +34,7 @@
 #include <net/dsa.h>
 #include <net/pkt_sched.h>
 
+#include "avb.h"
 #include "chip.h"
 #include "devlink.h"
 #include "global1.h"
@@ -5708,6 +5709,10 @@ static const struct mv88e6xxx_qav_info mv88e6352_qav_info = {
 	.rate_mask = GENMASK(14, 0),
 	.hilimit_mask = GENMASK(14, 0),
 	.queue_mask = GENMASK(3, 0),
+	/* legacy (all queues), lo (queue 1/2), hi (queue 2/3) */
+	.avb_queue_mask = { GENMASK(3, 0), GENMASK(2, 1), GENMASK(3, 2) },
+	/* HI FPri 5/QPri 3, LO FPri 4/QPri 2 */
+	.avb_pri_map = 0x5342,
 };
 
 static const struct mv88e6xxx_qav_info mv88e6341_qav_info = {
@@ -5715,6 +5720,10 @@ static const struct mv88e6xxx_qav_info mv88e6341_qav_info = {
 	.rate_mask = GENMASK(15, 0),
 	.hilimit_mask = GENMASK(13, 0),
 	.queue_mask = GENMASK(3, 0),
+	/* legacy (all queues), lo (queue 1/2), hi (queue 2/3) */
+	.avb_queue_mask = { GENMASK(3, 0), GENMASK(2, 1), GENMASK(3, 2) },
+	/* HI FPri 5/QPri 3, LO FPri 4/QPri 2 */
+	.avb_pri_map = 0x5342,
 };
 
 static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
@@ -5722,6 +5731,10 @@ static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
 	.rate_mask = GENMASK(15, 0),
 	.hilimit_mask = GENMASK(13, 0),
 	.queue_mask = GENMASK(7, 0),
+	/* AVB traffic allowed on all queues */
+	.avb_queue_mask = { GENMASK(7, 0), GENMASK(7, 0), GENMASK(7, 0) },
+	/* HI FPri 3/QPri 7, LO FPri 2/QPri 6  */
+	.avb_pri_map = 0x3726,
 };
 
 static const struct mv88e6xxx_info mv88e6xxx_table[] = {
@@ -7244,6 +7257,197 @@ static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
 	return err_sync ? : err_pvt;
 }
 
+static int mv88e6xxx_tc_query_caps(struct tc_query_caps_base *base)
+{
+	switch (base->type) {
+	case TC_SETUP_QDISC_MQPRIO: {
+		struct tc_mqprio_caps *caps = base->caps;
+
+		caps->validate_queue_counts = true;
+
+		return 0;
+	}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mv88e6xxx_validate_tc_mqprio(const struct mv88e6xxx_chip *chip,
+					const struct tc_mqprio_qopt_offload *mqprio)
+{
+	const struct mv88e6xxx_qav_info *qav = chip->info->qav;
+	const struct tc_mqprio_qopt *qopt = &mqprio->qopt;
+	struct netlink_ext_ack *extack = mqprio->extack;
+	u8 avb_tc_set = 0;
+	int tc, fpri;
+
+	if (qopt->num_tc == 0)
+		return 0;
+
+	if (qopt->hw != TC_MQPRIO_HW_OFFLOAD_TCS) {
+		NL_SET_ERR_MSG_MOD(extack, "only full TC hardware offload is supported");
+		return -EOPNOTSUPP;
+	} else if (!qav || !chip->info->ops->avb_ops) {
+		NL_SET_ERR_MSG_MOD(extack, "chip does not support MQPRIO DCB offload");
+		return -EOPNOTSUPP;
+	} else if (mqprio->mode != TC_MQPRIO_MODE_DCB) {
+		NL_SET_ERR_MSG_MOD(extack, "only DCB mode is supported");
+		return -EOPNOTSUPP;
+	} else if (mqprio->shaper != TC_MQPRIO_SHAPER_DCB) {
+		NL_SET_ERR_MSG_MOD(extack, "only DCB shaper is supported for AVB mode");
+		return -EOPNOTSUPP;
+	} else if (mqprio->preemptible_tcs) {
+		NL_SET_ERR_MSG_MOD(extack, "frame preemption is not supported");
+		return -EOPNOTSUPP;
+	} else if (qopt->num_tc > MV88E6XXX_AVB_TC_MAX + 1) {
+		NL_SET_ERR_MSG_MOD(extack, "too many traffic classes for AVB mode");
+		return -EOPNOTSUPP;
+	}
+
+	/* Validate switch-side constraints on AVB traffic classes. TC0 queue
+	 * mapping can only be configured on ingress using the DCB PCP app.
+	 */
+	for (tc = MV88E6XXX_AVB_TC_LO; tc < qopt->num_tc; tc++) {
+		if (qopt->count[tc] != 1) {
+			NL_SET_ERR_MSG_FMT_MOD(extack, "only one queue supported for TC%d", tc);
+			return -EOPNOTSUPP;
+		} else if ((qav->avb_queue_mask[tc] & BIT(qopt->offset[tc])) == 0) {
+			NL_SET_ERR_MSG_FMT_MOD(extack, "queue %d not valid for TC%d",
+					       qopt->offset[tc], tc);
+			return -EOPNOTSUPP;
+		}
+	}
+
+	/* Each AVB traffic class must map exactly one frame priority */
+	for (fpri = 0; fpri < IEEE_8021Q_MAX_PRIORITIES; fpri++) {
+		tc = qopt->prio_tc_map[fpri];
+
+		if (tc == MV88E6XXX_AVB_TC_LEGACY)
+			continue;
+
+		if (avb_tc_set & BIT(tc)) {
+			NL_SET_ERR_MSG_FMT_MOD(extack,
+					       "only one frame priority can be mapped to TC%d", tc);
+			return -EOPNOTSUPP;
+		}
+
+		avb_tc_set |= BIT(tc);
+	}
+
+	if (avb_tc_set != GENMASK(MV88E6XXX_AVB_TC_HI, MV88E6XXX_AVB_TC_LO)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "both TC1 and TC2 must have 802.1p priorities assigned");
+		return -EOPNOTSUPP;
+	}
+
+	return qopt->num_tc;
+}
+
+static void mv88e6xxx_mqprio_update_policy(struct mv88e6xxx_tc_policy *pol,
+					   int port, int num_tc)
+{
+	if (num_tc) {
+		pol->tc_port_mask |= BIT(port);
+		pol->enabled = true;
+	} else {
+		pol->tc_port_mask &= ~BIT(port);
+		if (!pol->tc_port_mask)
+			pol->enabled = false;
+	}
+}
+
+static int mv88e6xxx_mqprio_netdev_set_tc(struct net_device *user,
+					  const struct tc_mqprio_qopt *qopt,
+					  int num_tc)
+{
+	int err, tc;
+
+	err = netdev_set_num_tc(user, num_tc);
+	if (err)
+		return err;
+
+	for (tc = 0; tc < num_tc; tc++) {
+		err = netdev_set_tc_queue(user, tc, qopt->count[tc],
+					  qopt->offset[tc]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int mv88e6xxx_setup_tc_mqprio(struct dsa_switch *ds, int port,
+				     struct tc_mqprio_qopt_offload *mqprio)
+{
+	struct netlink_ext_ack *extack = mqprio->extack;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct mv88e6xxx_tc_policy *pol;
+	struct net_device *user;
+	bool can_update_pol;
+	u16 avb_pri_map;
+	int num_tc, err;
+
+	if (!dsa_is_user_port(ds, port))
+		return -EINVAL;
+
+	num_tc = mv88e6xxx_validate_tc_mqprio(chip, mqprio);
+	if (num_tc < 0)
+		return num_tc;
+
+	avb_pri_map = num_tc ? mv88e6xxx_avb_pri_map_to_reg(&mqprio->qopt) : 0;
+
+	user = dsa_to_port(ds, port)->user;
+
+	mv88e6xxx_reg_lock(chip);
+
+	pol = &chip->tc_policy;
+
+	if (!num_tc && !(pol->tc_port_mask & BIT(port))) {
+		netdev_reset_tc(user);
+		mv88e6xxx_reg_unlock(chip);
+		return 0;
+	}
+
+	can_update_pol = !pol->tc_port_mask || pol->tc_port_mask == BIT(port);
+	if (!can_update_pol && num_tc && avb_pri_map != pol->avb_pri_map) {
+		NL_SET_ERR_MSG_MOD(extack, "only a single priority mapping supported per switch");
+		err = -EEXIST;
+		goto err_unlock;
+	}
+
+	err = mv88e6xxx_mqprio_netdev_set_tc(user, &mqprio->qopt, num_tc);
+	if (err)
+		goto err_reset_tc;
+
+	if (can_update_pol) {
+		err = num_tc ? mv88e6xxx_avb_enable(chip, mqprio)
+			     : mv88e6xxx_avb_disable(chip);
+		if (err) {
+			NL_SET_ERR_MSG_FMT_MOD(extack, "failed to %s AVB",
+					       num_tc ? "enable" : "disable");
+			goto err_reset_tc;
+		}
+	}
+
+	mv88e6xxx_mqprio_update_policy(pol, port, num_tc);
+
+	if (num_tc && can_update_pol)
+		pol->avb_pri_map = avb_pri_map;
+	else if (!pol->tc_port_mask)
+		pol->avb_pri_map = 0;
+
+	mv88e6xxx_reg_unlock(chip);
+
+	return 0;
+
+err_reset_tc:
+	netdev_reset_tc(user);
+err_unlock:
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
+
 static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
 				  struct tc_cbs_qopt_offload *cbs)
 {
@@ -7335,6 +7539,10 @@ static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
 				   enum tc_setup_type type, void *type_data)
 {
 	switch (type) {
+	case TC_QUERY_CAPS:
+		return mv88e6xxx_tc_query_caps(type_data);
+	case TC_SETUP_QDISC_MQPRIO:
+		return mv88e6xxx_setup_tc_mqprio(ds, port, type_data);
 	case TC_SETUP_QDISC_CBS:
 		return mv88e6xxx_setup_tc_cbs(ds, port, type_data);
 	default:
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index d42d839e636bc..5e7c1c6bfbb95 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -8,6 +8,7 @@
 #ifndef _MV88E6XXX_CHIP_H
 #define _MV88E6XXX_CHIP_H
 
+#include <linux/dcbnl.h> /* for IEEE_8021Q_MAX_PRIORITIES */
 #include <linux/idr.h>
 #include <linux/if_vlan.h>
 #include <linux/irq.h>
@@ -19,6 +20,7 @@
 #include <linux/ptp_clock_kernel.h>
 #include <linux/timecounter.h>
 #include <net/dsa.h>
+#include <net/pkt_sched.h>
 
 #define EDSA_HLEN		8
 #define MV88E6XXX_N_FID		4096
@@ -252,6 +254,30 @@ struct mv88e6xxx_port_hwtstamp {
 	struct kernel_hwtstamp_config tstamp_config;
 };
 
+/**
+ * enum mv88e6xxx_avb_tc - Traffic class values for AVB mode
+ * @MV88E6XXX_AVB_TC_LEGACY: Non-AVB traffic
+ * @MV88E6XXX_AVB_TC_LO:     Low priority AVB (Class B)
+ * @MV88E6XXX_AVB_TC_HI:     High priority AVB (Class A)
+ */
+enum mv88e6xxx_avb_tc {
+	MV88E6XXX_AVB_TC_LEGACY = 0,
+	MV88E6XXX_AVB_TC_LO = 1,
+	MV88E6XXX_AVB_TC_HI = 2,
+	MV88E6XXX_AVB_TC_MAX = MV88E6XXX_AVB_TC_HI,
+};
+
+struct mv88e6xxx_tc_policy {
+	/* AVB MQPRIO offload active on the switch */
+	bool enabled;
+
+	/* Ports with MQPRIO TC installed */
+	u16 tc_port_mask;
+
+	/* Switch-wide AVB FPri/QPri register value */
+	u16 avb_pri_map;
+};
+
 enum mv88e6xxx_policy_mapping {
 	MV88E6XXX_POLICY_MAPPING_DA,
 	MV88E6XXX_POLICY_MAPPING_SA,
@@ -463,6 +489,9 @@ struct mv88e6xxx_chip {
 	/* TCAM entries */
 	struct mv88e6xxx_tcam tcam;
 
+	/* Global MQPRIO traffic class configuration */
+	struct mv88e6xxx_tc_policy tc_policy;
+
 	/* Global2 scratch register config data3 */
 	u8 g2_scratch_config3;
 };
@@ -777,6 +806,20 @@ struct mv88e6xxx_avb_ops {
 	/* Access port-scoped 802.1Qav registers */
 	int (*port_qav_write)(struct mv88e6xxx_chip *chip, int port, int addr,
 			      u16 data);
+
+	/* Access global Class Shaping and Pacing registers */
+	int (*qav_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
+			int len);
+	int (*qav_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
+
+	/* Access port-scoped Audio Video Bridging registers */
+	int (*port_avb_read)(struct mv88e6xxx_chip *chip, int port, int addr,
+			     u16 *data, int len);
+	int (*port_avb_write)(struct mv88e6xxx_chip *chip, int port, int addr,
+			      u16 data);
+
+	/* Access global Audio Video Bridging registers */
+	int (*avb_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
 };
 
 struct mv88e6xxx_ptp_ops {
@@ -817,6 +860,8 @@ struct mv88e6xxx_qav_info {
 	u16 rate_mask; /* QPri Rate valid bits mask */
 	u16 hilimit_mask; /* QPri HiLimit bits mask*/
 	u8 queue_mask; /* supported queues bitmask */
+	u8 avb_queue_mask[MV88E6XXX_AVB_TC_MAX + 1]; /* AVB supported queues bitmask */
+	u16 avb_pri_map; /* default AVB FPri to QPri map */
 };
 
 static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..199a826d426f0 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -111,6 +111,7 @@
 
 /* Offset 0x0A: ATU Control Register */
 #define MV88E6XXX_G1_ATU_CTL		0x0a
+#define MV88E6XXX_G1_ATU_CTL_MAC_AVB	0x8000
 #define MV88E6XXX_G1_ATU_CTL_LEARN2ALL	0x0008
 #define MV88E6161_G1_ATU_CTL_HASH_MASK	0x0003
 
diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h
index df52ff3adc88c..ae361cca51274 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.h
+++ b/drivers/net/dsa/mv88e6xxx/global2.h
@@ -176,9 +176,11 @@
 #define MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL	0xe
 #define MV88E6165_G2_AVB_CMD_PORT_PTPGLOBAL	0xf
 #define MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL	0xf
+#define MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL	0xf
 #define MV88E6390_G2_AVB_CMD_PORT_MASK		0x1f00
 #define MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL	0x1e
 #define MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL	0x1f
+#define MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL	0x1f
 #define MV88E6352_G2_AVB_CMD_BLOCK_PTP		0
 #define MV88E6352_G2_AVB_CMD_BLOCK_AVB		1
 #define MV88E6352_G2_AVB_CMD_BLOCK_QAV		2
diff --git a/drivers/net/dsa/mv88e6xxx/global2_avb.c b/drivers/net/dsa/mv88e6xxx/global2_avb.c
index 6b54e275d21ab..fe1607bc78734 100644
--- a/drivers/net/dsa/mv88e6xxx/global2_avb.c
+++ b/drivers/net/dsa/mv88e6xxx/global2_avb.c
@@ -119,6 +119,27 @@ static int mv88e6352_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
 
+static int mv88e6352_g2_avb_port_avb_read(struct mv88e6xxx_chip *chip,
+					  int port, int addr, u16 *data,
+					  int len)
+{
+	u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
+				 MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
+		     (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6352_g2_avb_port_avb_write(struct mv88e6xxx_chip *chip,
+					   int port, int addr, u16 data)
+{
+	u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE | (port << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 static int mv88e6352_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
 				     u16 *data, int len)
 {
@@ -151,6 +172,38 @@ static int mv88e6352_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
 					addr, data);
 }
 
+static int mv88e6352_g2_avb_qav_read(struct mv88e6xxx_chip *chip, int addr,
+				     u16 *data, int len)
+{
+	u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
+				 MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
+		     (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		     (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6352_g2_avb_qav_write(struct mv88e6xxx_chip *chip, int addr,
+				      u16 data)
+{
+	u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE |
+		      (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6352_g2_avb_avb_write(struct mv88e6xxx_chip *chip, int addr,
+				      u16 data)
+{
+	u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE |
+		      (MV88E6352_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
 	.port_ptp_read		= mv88e6352_g2_avb_port_ptp_read,
 	.port_ptp_write		= mv88e6352_g2_avb_port_ptp_write,
@@ -159,6 +212,11 @@ const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
 	.tai_read		= mv88e6352_g2_avb_tai_read,
 	.tai_write		= mv88e6352_g2_avb_tai_write,
 	.port_qav_write		= mv88e6352_g2_avb_port_qav_write,
+	.qav_read		= mv88e6352_g2_avb_qav_read,
+	.qav_write		= mv88e6352_g2_avb_qav_write,
+	.port_avb_read		= mv88e6352_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6352_g2_avb_port_avb_write,
+	.avb_write		= mv88e6352_g2_avb_avb_write,
 };
 
 static int mv88e6165_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
@@ -185,6 +243,11 @@ const struct mv88e6xxx_avb_ops mv88e6165_avb_ops = {
 	.tai_read		= mv88e6165_g2_avb_tai_read,
 	.tai_write		= mv88e6165_g2_avb_tai_write,
 	.port_qav_write		= mv88e6352_g2_avb_port_qav_write,
+	.qav_read		= mv88e6352_g2_avb_qav_read,
+	.qav_write		= mv88e6352_g2_avb_qav_write,
+	.port_avb_read		= mv88e6352_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6352_g2_avb_port_avb_write,
+	.avb_write		= mv88e6352_g2_avb_avb_write,
 };
 
 static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
@@ -217,6 +280,27 @@ static int mv88e6390_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
 
+static int mv88e6390_g2_avb_port_avb_read(struct mv88e6xxx_chip *chip,
+					  int port, int addr, u16 *data,
+					  int len)
+{
+	u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
+				 MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
+		     (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6390_g2_avb_port_avb_write(struct mv88e6xxx_chip *chip,
+					   int port, int addr, u16 data)
+{
+	u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE | (port << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 static int mv88e6390_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
 				     u16 *data, int len)
 {
@@ -249,6 +333,38 @@ static int mv88e6390_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
 					addr, data);
 }
 
+static int mv88e6390_g2_avb_qav_read(struct mv88e6xxx_chip *chip, int addr,
+				     u16 *data, int len)
+{
+	u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
+				 MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
+		     (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		     (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6390_g2_avb_qav_write(struct mv88e6xxx_chip *chip, int addr,
+				      u16 data)
+{
+	u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE |
+		      (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6390_g2_avb_avb_write(struct mv88e6xxx_chip *chip, int addr,
+				      u16 data)
+{
+	u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE |
+		      (MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL << 8) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
 	.port_ptp_read		= mv88e6390_g2_avb_port_ptp_read,
 	.port_ptp_write		= mv88e6390_g2_avb_port_ptp_write,
@@ -257,4 +373,9 @@ const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
 	.tai_read		= mv88e6390_g2_avb_tai_read,
 	.tai_write		= mv88e6390_g2_avb_tai_write,
 	.port_qav_write		= mv88e6390_g2_avb_port_qav_write,
+	.qav_read		= mv88e6390_g2_avb_qav_read,
+	.qav_write		= mv88e6390_g2_avb_qav_write,
+	.port_avb_read		= mv88e6390_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6390_g2_avb_port_avb_write,
+	.avb_write		= mv88e6390_g2_avb_avb_write,
 };

---
base-commit: 3abbe30231441f1fbb3305e9854c56a34650af53
change-id: 20260602-mv88e6xxx-8021qat-mqprio-46fc466d70e1
prerequisite-change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68:v7
prerequisite-patch-id: 8ad59c43368d4639e0cabcc59a7f6e487560d3f7
prerequisite-patch-id: 22f1cda311b1826bd4151fc7d4cdf3d87ca519ed
prerequisite-change-id: 20260603-net-next-mv88e6xxx-pcp-prio-5752caa7926b:v1
prerequisite-patch-id: f324a0334ef91256d6f09562d978e9d6c32b7f3c
prerequisite-patch-id: 79c2b056ae68846b5e2950b9738cb83e73d2710e

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


^ permalink raw reply related

* [PATCH net-next 0/2] net: dsa: mv88e6xxx: add support for DCB DSCP config
From: Luke Howard @ 2026-07-03  6:46 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard

Add the DCB DSCP and apptrust applications for configuring DSCP to
queue mappings.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
Luke Howard (2):
      net: dsa: mv88e6xxx: add support for DCB DSCP app
      net: dsa: mv88e6xxx: add support for DCB apptrust app

 drivers/net/dsa/mv88e6xxx/chip.c    |   8 +
 drivers/net/dsa/mv88e6xxx/chip.h    |  20 ++
 drivers/net/dsa/mv88e6xxx/dcb.c     | 406 ++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/dcb.h     |  11 +
 drivers/net/dsa/mv88e6xxx/global1.h |   8 +
 drivers/net/dsa/mv88e6xxx/port.h    |  10 +
 6 files changed, 463 insertions(+)
---
base-commit: 3abbe30231441f1fbb3305e9854c56a34650af53
change-id: 20260607-mv88e6xxx-dscp-prio-map-2fe4d8b0a19c
prerequisite-change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68:v7
prerequisite-patch-id: 8ad59c43368d4639e0cabcc59a7f6e487560d3f7
prerequisite-patch-id: 22f1cda311b1826bd4151fc7d4cdf3d87ca519ed
prerequisite-change-id: 20260603-net-next-mv88e6xxx-pcp-prio-5752caa7926b:v1
prerequisite-patch-id: f324a0334ef91256d6f09562d978e9d6c32b7f3c
prerequisite-patch-id: 79c2b056ae68846b5e2950b9738cb83e73d2710e

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


^ permalink raw reply

* [PATCH net-next 1/2] net: dsa: mv88e6xxx: add support for DCB DSCP app
From: Luke Howard @ 2026-07-03  6:46 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6xxx-dscp-prio-map-v1-0-250e0ebcce83@padl.com>

Implement {get,add,del}_dscp_prio for Marvell switches that support it.
On switches that do not support setting the QPri independently of FPri,
FPri is set to the default for the DSCP value.

On 6352-class switches the DSCP-to-QPri mapping is a global table with no
per-entry "unmapped" state: every DSCP is always mapped, so the DCB app
table comes up fully populated and deleting an entry restores its
reset-default QPri rather than removing it.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c    |   6 +
 drivers/net/dsa/mv88e6xxx/chip.h    |  14 ++
 drivers/net/dsa/mv88e6xxx/dcb.c     | 294 ++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/dcb.h     |   6 +
 drivers/net/dsa/mv88e6xxx/global1.h |   8 +
 drivers/net/dsa/mv88e6xxx/port.h    |  10 ++
 6 files changed, 338 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index cefe6be2f0ce9..3bdf8c7b36612 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7434,6 +7434,9 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.port_get_pcp_prio	= mv88e6xxx_port_get_pcp_prio,
 	.port_add_pcp_prio	= mv88e6xxx_port_add_pcp_prio,
 	.port_del_pcp_prio	= mv88e6xxx_port_del_pcp_prio,
+	.port_get_dscp_prio	= mv88e6xxx_port_get_dscp_prio,
+	.port_add_dscp_prio	= mv88e6xxx_port_add_dscp_prio,
+	.port_del_dscp_prio	= mv88e6xxx_port_del_dscp_prio,
 	.port_setup_tc		= mv88e6xxx_port_setup_tc,
 	.cls_flower_add		= mv88e6xxx_cls_flower_add,
 	.cls_flower_del         = mv88e6xxx_cls_flower_del,
@@ -7471,6 +7474,9 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
 	ds->pcp_prio_mapping_is_global = dcb_ops &&
 					 dcb_ops->global_get_pcp_prio;
 
+	ds->dscp_prio_mapping_is_global = dcb_ops &&
+					  dcb_ops->global_get_dscp_prio;
+
 	/* Some chips support up to 32, but that requires enabling the
 	 * 5-bit port mode, which we do not support. 640k^W16 ought to
 	 * be enough for anyone.
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 8c4e8b4890907..d6265e925574f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -794,6 +794,20 @@ struct mv88e6xxx_dcb_ops {
 				 u8 pcp);
 	int (*port_set_pcp_prio)(struct mv88e6xxx_chip *chip, int port,
 				 u8 pcp, u8 prio);
+
+	/* Get/set the chip's global DSCP to queue priority (QPri) mapping */
+	int (*global_get_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp);
+	int (*global_set_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp,
+				    u8 prio);
+	int (*global_del_dscp_prio)(struct mv88e6xxx_chip *chip, u8 dscp);
+
+	/* Get/set a port's DSCP to queue priority (QPri) mapping */
+	int (*port_get_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+				  u8 dscp);
+	int (*port_set_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+				  u8 dscp, u8 prio);
+	int (*port_del_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
+				  u8 dscp);
 };
 
 struct mv88e6xxx_ptp_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.c b/drivers/net/dsa/mv88e6xxx/dcb.c
index 0a3099cdf3882..c26f43b27f09e 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.c
+++ b/drivers/net/dsa/mv88e6xxx/dcb.c
@@ -1,6 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // Copyright (c) 2026 Luminex Network Intelligence
 
+#include <linux/bitfield.h>
+
+#include <net/dscp.h>
+
 #include "chip.h"
 #include "dcb.h"
 #include "global1.h"
@@ -134,19 +138,201 @@ static int mv88e6393x_port_set_pcp_prio(struct mv88e6xxx_chip *chip, int port,
 	return mv88e639x_port_set_pcp_prio(chip, port, pcp, prio, true);
 }
 
+/* The DCB priority maps the DSCP to an egress queue priority (QPri); the frame
+ * priority (FPri) is left untouched.  These chips always map every DSCP, so
+ * there is no per-entry "unmapped" state on the global (6352) table: deleting
+ * an entry restores its reset-default QPri rather than removing it.
+ */
+
+/* G1 offset 0x19 is the IP Mapping Table only on these families; elsewhere it is
+ * the Core Tag Type register, so report DSCP mapping as unsupported there.
+ */
+static bool mv88e6352_has_ip_pri_map(struct mv88e6xxx_chip *chip)
+{
+	switch (chip->info->family) {
+	case MV88E6XXX_FAMILY_6320:
+	case MV88E6XXX_FAMILY_6341:
+	case MV88E6XXX_FAMILY_6352:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int mv88e6352_get_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp)
+{
+	u16 val;
+	int err;
+
+	if (!mv88e6352_has_ip_pri_map(chip))
+		return -EOPNOTSUPP;
+
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* load the table pointer (Update clear), then read the entry back */
+	err = mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP,
+				 FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK,
+					    dscp));
+	if (err)
+		return err;
+
+	err = mv88e6xxx_g1_read(chip, MV88E6352_G1_IP_PRI_MAP, &val);
+	if (err)
+		return err;
+
+	return FIELD_GET(MV88E6352_G1_IP_PRI_MAP_QPRI_MASK, val);
+}
+
+/* Update only the QPri of a DSCP entry, preserving the global UseIpFPri bit and
+ * the per-entry FPri so the frame priority handling is left untouched.
+ */
+static int mv88e6352_write_dscp_qpri(struct mv88e6xxx_chip *chip, u8 dscp,
+				     u8 prio)
+{
+	u16 val;
+	int err;
+
+	/* load the table pointer (Update clear), then read the entry back */
+	err = mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP,
+				 FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK,
+					    dscp));
+	if (err)
+		return err;
+
+	err = mv88e6xxx_g1_read(chip, MV88E6352_G1_IP_PRI_MAP, &val);
+	if (err)
+		return err;
+
+	val &= ~(MV88E6352_G1_IP_PRI_MAP_PTR_MASK |
+		 MV88E6352_G1_IP_PRI_MAP_QPRI_MASK);
+	val |= MV88E6352_G1_IP_PRI_MAP_UPDATE |
+	       FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_PTR_MASK, dscp) |
+	       FIELD_PREP(MV88E6352_G1_IP_PRI_MAP_QPRI_MASK, prio);
+
+	return mv88e6xxx_g1_write(chip, MV88E6352_G1_IP_PRI_MAP, val);
+}
+
+static int mv88e6352_set_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp,
+				   u8 prio)
+{
+	if (!mv88e6352_has_ip_pri_map(chip))
+		return -EOPNOTSUPP;
+
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* QPri range as for the PCP-to-priority mapping */
+	if (prio >= 4)
+		return -EINVAL;
+
+	return mv88e6352_write_dscp_qpri(chip, dscp, prio);
+}
+
+static int mv88e6352_del_dscp_prio(struct mv88e6xxx_chip *chip, u8 dscp)
+{
+	if (!mv88e6352_has_ip_pri_map(chip))
+		return -EOPNOTSUPP;
+
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* No per-entry disable exists, so restore the reset-default QPri, which
+	 * is the top two DSCP bits (DSCP >> 4: 0x00-0x0f->0, 0x10-0x1f->1,
+	 * 0x20-0x2f->2, 0x30-0x3f->3).
+	 */
+	return mv88e6352_write_dscp_qpri(chip, dscp, dscp >> 4);
+}
+
+static int mv88e6390_port_get_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+					u8 dscp)
+{
+	u16 val;
+	int err;
+
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* load the table pointer (Update clear), then read the entry back */
+	err = mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+				   FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+					      dscp));
+	if (err)
+		return err;
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6390_PORT_IP_PRI_MAP, &val);
+	if (err)
+		return err;
+
+	if (val & MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI)
+		return -EOPNOTSUPP;
+
+	return FIELD_GET(MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK, val);
+}
+
+static int mv88e6390_port_set_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+					u8 dscp, u8 prio)
+{
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* QPri range as for the PCP-to-priority mapping */
+	if (prio >= 8)
+		return -EINVAL;
+
+	/* This write defines the whole entry: enable and set the QPri, disable
+	 * the FPri override so frame priority is left unchanged, and leave
+	 * IP_YELLOW clear.  The entry is owned solely by this API, so a blind
+	 * write is sufficient and no read-modify-write is needed.
+	 */
+	return mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+				   MV88E6390_PORT_IP_PRI_MAP_UPDATE |
+				   MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI |
+				   FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+					      dscp) |
+				   FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK,
+					      prio));
+}
+
+static int mv88e6390_port_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+					u8 dscp)
+{
+	if (dscp >= DSCP_MAX)
+		return -EINVAL;
+
+	/* Disable both the QPri and FPri overrides so the DSCP falls back to the
+	 * port default; get_dscp_prio then reports the entry as removed.
+	 */
+	return mv88e6xxx_port_write(chip, port, MV88E6390_PORT_IP_PRI_MAP,
+				   MV88E6390_PORT_IP_PRI_MAP_UPDATE |
+				   MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI |
+				   MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI |
+				   FIELD_PREP(MV88E6390_PORT_IP_PRI_MAP_PTR_MASK,
+					      dscp));
+}
+
 const struct mv88e6xxx_dcb_ops mv88e6352_dcb_ops = {
 	.global_get_pcp_prio = mv88e6352_get_pcp_prio,
 	.global_set_pcp_prio = mv88e6352_set_pcp_prio,
+	.global_get_dscp_prio = mv88e6352_get_dscp_prio,
+	.global_set_dscp_prio = mv88e6352_set_dscp_prio,
+	.global_del_dscp_prio = mv88e6352_del_dscp_prio,
 };
 
 const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
 	.port_get_pcp_prio = mv88e6390_port_get_pcp_prio,
 	.port_set_pcp_prio = mv88e6390_port_set_pcp_prio,
+	.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
+	.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
+	.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
 };
 
 const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
 	.port_get_pcp_prio = mv88e6393x_port_get_pcp_prio,
 	.port_set_pcp_prio = mv88e6393x_port_set_pcp_prio,
+	.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
+	.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
+	.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
 };
 
 static int mv88e6xxx_dcb_get_pcp_prio(struct mv88e6xxx_chip *chip, int port,
@@ -227,3 +413,111 @@ int mv88e6xxx_port_del_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
 	mv88e6xxx_reg_unlock(chip);
 	return err;
 }
+
+static int mv88e6xxx_dcb_get_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+				       u8 dscp)
+{
+	const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+	if (!dcb_ops)
+		return -EOPNOTSUPP;
+
+	if (dcb_ops->port_get_dscp_prio)
+		return dcb_ops->port_get_dscp_prio(chip, port, dscp);
+
+	if (dcb_ops->global_get_dscp_prio)
+		return dcb_ops->global_get_dscp_prio(chip, dscp);
+
+	return -EOPNOTSUPP;
+}
+
+static int mv88e6xxx_dcb_set_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+				       u8 dscp, u8 prio)
+{
+	const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+	if (!dcb_ops)
+		return -EOPNOTSUPP;
+
+	if (dcb_ops->port_set_dscp_prio)
+		return dcb_ops->port_set_dscp_prio(chip, port, dscp, prio);
+
+	if (dcb_ops->global_set_dscp_prio)
+		return dcb_ops->global_set_dscp_prio(chip, dscp, prio);
+
+	return -EOPNOTSUPP;
+}
+
+static int mv88e6xxx_dcb_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
+				       u8 dscp)
+{
+	const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+
+	if (!dcb_ops)
+		return -EOPNOTSUPP;
+
+	if (dcb_ops->port_del_dscp_prio)
+		return dcb_ops->port_del_dscp_prio(chip, port, dscp);
+
+	if (dcb_ops->global_del_dscp_prio)
+		return dcb_ops->global_del_dscp_prio(chip, dscp);
+
+	return -EOPNOTSUPP;
+}
+
+int mv88e6xxx_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+	err = mv88e6xxx_dcb_get_dscp_prio(chip, port, dscp);
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
+
+int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+				 u8 prio)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	if (prio >= IEEE_8021Q_MAX_PRIORITIES)
+		return -EINVAL;
+
+	mv88e6xxx_reg_lock(chip);
+	err = mv88e6xxx_dcb_set_dscp_prio(chip, port, dscp, prio);
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
+
+int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+				 u8 prio)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	mv88e6xxx_reg_lock(chip);
+
+	err = mv88e6xxx_dcb_get_dscp_prio(chip, port, dscp);
+	if (err == -EOPNOTSUPP) {
+		/* No explicit mapping for this DSCP, so nothing to remove */
+		err = 0;
+		goto unlock;
+	}
+	if (err < 0)
+		goto unlock;
+	if (err != prio) {
+		/* Mapping was changed in the meantime; leave it alone */
+		err = 0;
+		goto unlock;
+	}
+
+	err = mv88e6xxx_dcb_del_dscp_prio(chip, port, dscp);
+
+unlock:
+	mv88e6xxx_reg_unlock(chip);
+	return err;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.h b/drivers/net/dsa/mv88e6xxx/dcb.h
index 7a2124ec23ba9..e1cf92bbd8729 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.h
+++ b/drivers/net/dsa/mv88e6xxx/dcb.h
@@ -18,4 +18,10 @@ int mv88e6xxx_port_add_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
 int mv88e6xxx_port_del_pcp_prio(struct dsa_switch *ds, int port, u8 pcp,
 				u8 prio);
 
+int mv88e6xxx_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp);
+int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+				 u8 prio);
+int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
+				 u8 prio);
+
 #endif /* _MV88E6XXX_DCB_H_ */
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..fdc9795fa5c6d 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -195,6 +195,14 @@
 /* Offset 0x19: Core Tag Type */
 #define MV88E6185_G1_CORE_TAG_TYPE	0x19
 
+/* Offset 0x19: IP Mapping Table (6172/6176/6240/6320/6341/6352) */
+#define MV88E6352_G1_IP_PRI_MAP			0x19
+#define MV88E6352_G1_IP_PRI_MAP_UPDATE		0x8000
+#define MV88E6352_G1_IP_PRI_MAP_USE_IP_FPRI	0x4000
+#define MV88E6352_G1_IP_PRI_MAP_PTR_MASK	0x3f00
+#define MV88E6352_G1_IP_PRI_MAP_FPRI_MASK	0x0070
+#define MV88E6352_G1_IP_PRI_MAP_QPRI_MASK	0x0003
+
 /* Offset 0x1A: Monitor Control */
 #define MV88E6185_G1_MONITOR_CTL			0x1a
 #define MV88E6185_G1_MONITOR_CTL_INGRESS_DEST_MASK	0xf000
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index 56143fa534c16..230a99f404606 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -448,6 +448,16 @@
 /* Control for Special LED (Index 0x7 of LED Control on Port 2) */
 #define MV88E6XXX_PORT_LED_CONTROL_0x07_P2_PTP_ACT		0 /* bits 6:0 PTP Activity */
 
+/* Offset 0x17: IP Priority Mapping Table */
+#define MV88E6390_PORT_IP_PRI_MAP			0x17
+#define MV88E6390_PORT_IP_PRI_MAP_UPDATE		BIT(15)
+#define MV88E6390_PORT_IP_PRI_MAP_PTR_MASK		GENMASK(14, 9)
+#define MV88E6390_PORT_IP_PRI_MAP_IP_YELLOW		BIT(8)
+#define MV88E6390_PORT_IP_PRI_MAP_DIS_IP_QPRI		BIT(7)
+#define MV88E6390_PORT_IP_PRI_MAP_QPRI_MASK		GENMASK(6, 4)
+#define MV88E6390_PORT_IP_PRI_MAP_DIS_IP_FPRI		BIT(3)
+#define MV88E6390_PORT_IP_PRI_MAP_FPRI_MASK		GENMASK(2, 0)
+
 /* Offset 0x18: IEEE Priority Mapping Table */
 #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE			0x18
 #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE		0x8000

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 2/2] net: dsa: mv88e6xxx: add support for DCB apptrust app
From: Luke Howard @ 2026-07-03  6:46 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6xxx-dscp-prio-map-v1-0-250e0ebcce83@padl.com>

Implement port_{get,set}_apptrust for Marvell switches that support it.

This maps to the per-port USE_TAG / USE_IP / TAG_IF_BOTH bits in Port
Control 0, which the driver already initialises for every port (trust
both, IP taking precedence), so apptrust is available across the shared
mv88e6352_dcb_ops families, not only the 6352/6390/6393x.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c |   2 +
 drivers/net/dsa/mv88e6xxx/chip.h |   6 +++
 drivers/net/dsa/mv88e6xxx/dcb.c  | 112 +++++++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/dcb.h  |   5 ++
 4 files changed, 125 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 3bdf8c7b36612..6b85b2d0a982f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7437,6 +7437,8 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.port_get_dscp_prio	= mv88e6xxx_port_get_dscp_prio,
 	.port_add_dscp_prio	= mv88e6xxx_port_add_dscp_prio,
 	.port_del_dscp_prio	= mv88e6xxx_port_del_dscp_prio,
+	.port_get_apptrust	= mv88e6xxx_port_get_apptrust,
+	.port_set_apptrust	= mv88e6xxx_port_set_apptrust,
 	.port_setup_tc		= mv88e6xxx_port_setup_tc,
 	.cls_flower_add		= mv88e6xxx_cls_flower_add,
 	.cls_flower_del         = mv88e6xxx_cls_flower_del,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index d6265e925574f..e95be110dfa7b 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -808,6 +808,12 @@ struct mv88e6xxx_dcb_ops {
 				  u8 dscp, u8 prio);
 	int (*port_del_dscp_prio)(struct mv88e6xxx_chip *chip, int port,
 				  u8 dscp);
+
+	/* Get/set a port's trusted application priority sources */
+	int (*port_get_apptrust)(struct mv88e6xxx_chip *chip, int port,
+				 u8 *sel, int *nsel);
+	int (*port_set_apptrust)(struct mv88e6xxx_chip *chip, int port,
+				 const u8 *sel, int nsel);
 };
 
 struct mv88e6xxx_ptp_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.c b/drivers/net/dsa/mv88e6xxx/dcb.c
index c26f43b27f09e..08a08acb9bfad 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.c
+++ b/drivers/net/dsa/mv88e6xxx/dcb.c
@@ -311,12 +311,86 @@ static int mv88e6390_port_del_dscp_prio(struct mv88e6xxx_chip *chip, int port,
 					      dscp));
 }
 
+/* Application trust (apptrust).
+ *
+ * InitialPri (the UseTag/UseIP bits) selects which ingress values a port
+ * trusts for priority assignment; TagIfBoth breaks the tie when a frame
+ * matches both.  DCB lists selectors in increasing precedence, so the
+ * last-named selector is the most trusted.
+ */
+
+static int mv88e6xxx_set_apptrust(struct mv88e6xxx_chip *chip, int port,
+				  const u8 *sel, int nsel)
+{
+	bool use_tag = false, use_ip = false, tag_wins = false;
+	u16 reg;
+	int i, err;
+
+	for (i = 0; i < nsel; i++) {
+		switch (sel[i]) {
+		case DCB_APP_SEL_PCP:
+			use_tag = true;
+			tag_wins = true;
+			break;
+		case IEEE_8021QAZ_APP_SEL_DSCP:
+			use_ip = true;
+			tag_wins = false;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL0, &reg);
+	if (err)
+		return err;
+
+	reg &= ~(MV88E6185_PORT_CTL0_USE_TAG | MV88E6185_PORT_CTL0_USE_IP |
+		 MV88E6XXX_PORT_CTL0_TAG_IF_BOTH);
+	if (use_tag)
+		reg |= MV88E6185_PORT_CTL0_USE_TAG;
+	if (use_ip)
+		reg |= MV88E6185_PORT_CTL0_USE_IP;
+	if (use_tag && use_ip && tag_wins)
+		reg |= MV88E6XXX_PORT_CTL0_TAG_IF_BOTH;
+
+	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL0, reg);
+}
+
+static int mv88e6xxx_get_apptrust(struct mv88e6xxx_chip *chip, int port,
+				  u8 *sel, int *nsel)
+{
+	bool tag_wins;
+	u16 reg;
+	int err, n = 0;
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL0, &reg);
+	if (err)
+		return err;
+
+	/* Report in increasing precedence: the winner of TagIfBoth comes last */
+	tag_wins = reg & MV88E6XXX_PORT_CTL0_TAG_IF_BOTH;
+
+	if (!tag_wins && (reg & MV88E6185_PORT_CTL0_USE_TAG))
+		sel[n++] = DCB_APP_SEL_PCP;
+	if (reg & MV88E6185_PORT_CTL0_USE_IP)
+		sel[n++] = IEEE_8021QAZ_APP_SEL_DSCP;
+	if (tag_wins && (reg & MV88E6185_PORT_CTL0_USE_TAG))
+		sel[n++] = DCB_APP_SEL_PCP;
+
+	*nsel = n;
+
+	return 0;
+}
+
 const struct mv88e6xxx_dcb_ops mv88e6352_dcb_ops = {
 	.global_get_pcp_prio = mv88e6352_get_pcp_prio,
 	.global_set_pcp_prio = mv88e6352_set_pcp_prio,
 	.global_get_dscp_prio = mv88e6352_get_dscp_prio,
 	.global_set_dscp_prio = mv88e6352_set_dscp_prio,
 	.global_del_dscp_prio = mv88e6352_del_dscp_prio,
+	.port_get_apptrust = mv88e6xxx_get_apptrust,
+	.port_set_apptrust = mv88e6xxx_set_apptrust,
 };
 
 const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
@@ -325,6 +399,8 @@ const struct mv88e6xxx_dcb_ops mv88e6390_dcb_ops = {
 	.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
 	.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
 	.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
+	.port_get_apptrust = mv88e6xxx_get_apptrust,
+	.port_set_apptrust = mv88e6xxx_set_apptrust,
 };
 
 const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
@@ -333,6 +409,8 @@ const struct mv88e6xxx_dcb_ops mv88e6393x_dcb_ops = {
 	.port_get_dscp_prio = mv88e6390_port_get_dscp_prio,
 	.port_set_dscp_prio = mv88e6390_port_set_dscp_prio,
 	.port_del_dscp_prio = mv88e6390_port_del_dscp_prio,
+	.port_get_apptrust = mv88e6xxx_get_apptrust,
+	.port_set_apptrust = mv88e6xxx_set_apptrust,
 };
 
 static int mv88e6xxx_dcb_get_pcp_prio(struct mv88e6xxx_chip *chip, int port,
@@ -521,3 +599,37 @@ int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
 	mv88e6xxx_reg_unlock(chip);
 	return err;
 }
+
+int mv88e6xxx_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
+				int *nsel)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+	int err;
+
+	if (!dcb_ops || !dcb_ops->port_get_apptrust)
+		return -EOPNOTSUPP;
+
+	mv88e6xxx_reg_lock(chip);
+	err = dcb_ops->port_get_apptrust(chip, port, sel, nsel);
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
+
+int mv88e6xxx_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
+				int nsel)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	const struct mv88e6xxx_dcb_ops *dcb_ops = chip->info->ops->dcb_ops;
+	int err;
+
+	if (!dcb_ops || !dcb_ops->port_set_apptrust)
+		return -EOPNOTSUPP;
+
+	mv88e6xxx_reg_lock(chip);
+	err = dcb_ops->port_set_apptrust(chip, port, sel, nsel);
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/dcb.h b/drivers/net/dsa/mv88e6xxx/dcb.h
index e1cf92bbd8729..256d25d650014 100644
--- a/drivers/net/dsa/mv88e6xxx/dcb.h
+++ b/drivers/net/dsa/mv88e6xxx/dcb.h
@@ -24,4 +24,9 @@ int mv88e6xxx_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
 int mv88e6xxx_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp,
 				 u8 prio);
 
+int mv88e6xxx_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
+				int *nsel);
+int mv88e6xxx_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
+				int nsel);
+
 #endif /* _MV88E6XXX_DCB_H_ */

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 net-next] ipv4: fib: fix route re-dump in inet_dump_fib() on multi-batch dump
From: patchwork-bot+netdevbpf @ 2026-07-03  6:50 UTC (permalink / raw)
  To: Pengfei Zhang
  Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, netdev,
	linux-kernel, chenzhangqi, baohua, zhangpengfei16
In-Reply-To: <20260630084220.2711025-1-zhangfeionline@gmail.com>

Hello:

This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Tue, 30 Jun 2026 16:42:20 +0800 you wrote:
> inet_dump_fib() saves its progress in cb->args[1] as a positional
> index within the current hash chain.  Between batches, a concurrent
> fib_new_table() can insert a new table at the chain head, shifting
> all existing entries.  On resume the saved index lands on a different
> table, causing already-dumped tables to be re-dumped and the
> originally suspended table to restart from the beginning.
> 
> [...]

Here is the summary with links:
  - [v2,net-next] ipv4: fib: fix route re-dump in inet_dump_fib() on multi-batch dump
    https://git.kernel.org/netdev/net-next/c/2ed8d5c72488

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next 0/3] net: report multicast group user count
From: Paolo Abeni @ 2026-07-03  6:50 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Andrew Lunn, David Ahern, Donald Hunter,
	Eric Dumazet, Ido Schimmel, Jakub Kicinski, Shuah Khan,
	Simon Horman, linux-kernel, linux-kselftest, netdev
In-Reply-To: <20260630110207.37841-1-sigefriedhyy@gmail.com>

On 6/30/26 1:02 PM, Yuyang Huang wrote:
> RTM_GETMULTICAST reports IPv4 and IPv6 multicast group membership, but
> does not include the per-group user count. Userspace therefore still has
> to parse /proc/net/igmp and /proc/net/igmp6 to obtain the Users column.
> In particular, this prevents iproute2 from moving "ip maddr show"
> entirely from procfs to rtnetlink.
> 
> Add IFA_MC_USERS to carry the user count in RTM_GETMULTICAST dumps and
> RTM_NEWMULTICAST / RTM_DELMULTICAST notifications for both address
> families. Update the rt-addr YNL specification and extend the rtnetlink
> selftest to verify that two joins increase the reported count by two.

Will you take care of implementing the iproute2 bits, too?

Thanks,

Paolo


^ permalink raw reply

* [PATCH net-next 1/2] net: dsa: mv88e6xxx: use ARRIVAL1 counter for all peer delay messages
From: Luke Howard @ 2026-07-03  6:41 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard
In-Reply-To: <20260703-mv88e6xxx-ptp-fixes-v1-0-0138581889a9@padl.com>

mv88e6xxx switches have two arrival timestamp counters for timestamping
PTP event messages. This permits more than one arriving event message's
timestamp to be captured. This is useful for the case where event
messages from a grandmaster arrive at the same time as PDelayReq/PDelayResp
messages from a peer.

Previously only PDelayResp messages were assigned to the second arrival
counter; this patch does so for PDelayReq messages as well.

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

diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 6e6472a3b75ad..57ff77496864f 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -319,9 +319,16 @@ static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
 				   &ps->rx_queue2);
 }
 
-static int is_pdelay_resp(const struct ptp_header *hdr)
+static bool is_pdelay_msg(const struct ptp_header *hdr)
 {
-	return (hdr->tsmt & 0xf) == 3;
+	switch (ptp_get_msgtype(hdr, PTP_CLASS_V2)) {
+	case PTP_MSGTYPE_PDELAY_REQ:
+		fallthrough;
+	case PTP_MSGTYPE_PDELAY_RESP:
+		return true;
+	default:
+		return false;
+	}
 }
 
 bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
@@ -343,7 +350,7 @@ bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
 
 	SKB_PTP_TYPE(skb) = type;
 
-	if (is_pdelay_resp(hdr))
+	if (is_pdelay_msg(hdr))
 		skb_queue_tail(&ps->rx_queue2, skb);
 	else
 		skb_queue_tail(&ps->rx_queue, skb);
@@ -584,8 +591,9 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
 	if (err)
 		return err;
 
-	/* Use ARRIVAL1 for peer delay response messages. */
+	/* Use ARRIVAL1 for peer delay messages. */
 	err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
+				  MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ |
 				  MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
 	if (err)
 		return err;

-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 0/2] net: dsa: mv88e6xxx: various hwstamp fixes
From: Luke Howard @ 2026-07-03  6:41 UTC (permalink / raw)
  To: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Vivien Didelot, Gregory CLEMENT, Andrew Lunn,
	Richard Cochran
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, linux-kernel,
	Luke Howard

Two fixes for improving the reliably of hardware timestamp acquisition
on Marvell switches. In our tests this eliminated missed timestamps in
ptp4l.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
Luke Howard (2):
      net: dsa: mv88e6xxx: use ARRIVAL1 counter for all peer delay messages
      net: dsa: mv88e6xxx: embedded PTP timestamp support

 drivers/net/dsa/mv88e6xxx/chip.c     |   3 +
 drivers/net/dsa/mv88e6xxx/chip.h     |   8 +++
 drivers/net/dsa/mv88e6xxx/hwtstamp.c | 132 +++++++++++++++++++++++++++++++----
 drivers/net/dsa/mv88e6xxx/hwtstamp.h |  14 ++++
 4 files changed, 144 insertions(+), 13 deletions(-)
---
base-commit: b8ea7da314c2efcb9c2f559ed65b7a36c869d68e
change-id: 20260630-mv88e6xxx-ptp-fixes-1732570b8829

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


^ permalink raw reply

* Re: [PATCH net-next 0/3] net: report multicast group user count
From: Yuyang Huang @ 2026-07-03  6:52 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: David S. Miller, Andrew Lunn, David Ahern, Donald Hunter,
	Eric Dumazet, Ido Schimmel, Jakub Kicinski, Shuah Khan,
	Simon Horman, linux-kernel, linux-kselftest, netdev
In-Reply-To: <2a0033b6-9a54-4de3-b1a8-78d4bc815f56@redhat.com>

On Fri, Jul 3, 2026 at 3:50 PM Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 6/30/26 1:02 PM, Yuyang Huang wrote:
> > RTM_GETMULTICAST reports IPv4 and IPv6 multicast group membership, but
> > does not include the per-group user count. Userspace therefore still has
> > to parse /proc/net/igmp and /proc/net/igmp6 to obtain the Users column.
> > In particular, this prevents iproute2 from moving "ip maddr show"
> > entirely from procfs to rtnetlink.
> >
> > Add IFA_MC_USERS to carry the user count in RTM_GETMULTICAST dumps and
> > RTM_NEWMULTICAST / RTM_DELMULTICAST notifications for both address
> > families. Update the rt-addr YNL specification and extend the rtnetlink
> > selftest to verify that two joins increase the reported count by two.
>
> Will you take care of implementing the iproute2 bits, too?

Yes, I actually have something WIP for iproute2 and realized it cannot
be done due to the missing fields. That's why I sent out this patch
series first.

^ permalink raw reply

* [PATCH RFC net-next] net: bridge: add BRIDGE_VLAN_INFO_DYNAMIC flag
From: Luke Howard @ 2026-07-03  6:54 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Nikolay Aleksandrov, Ido Schimmel, Simon Horman
  Cc: Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
	Christoph Mellauner, Simon Gapp, netdev, bridge, linux-kernel,
	Luke Howard

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
---
 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 related

* Re: [PATCH net-next v2 0/2] tools: ynl: pyynl: pull the --family resolution logic into the lib
From: patchwork-bot+netdevbpf @ 2026-07-03  7:00 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms,
	donald.hunter, sdf, gal, jstancek, ast
In-Reply-To: <20260701021751.3234681-1-kuba@kernel.org>

Hello:

This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Tue, 30 Jun 2026 19:17:49 -0700 you wrote:
> When packaging YNL as a system level utility we added a --family
> argument which auto-resolves the full spec path from a well known
> path in /usr/share. Spelling out full YAML spec files is at this
> point only done in-tree, for example in the selftests which need
> the very latest YAML. But the selftests have their own wrapping
> classes for each family so test authors aren't really bothered
> by having to spell the paths out.
> 
> [...]

Here is the summary with links:
  - [net-next,v2,1/2] tools: ynl: pyynl: re-export the library API from the package root
    https://git.kernel.org/netdev/net-next/c/df87e5c4e94e
  - [net-next,v2,2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib
    https://git.kernel.org/netdev/net-next/c/10c90f1bba3a

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next 0/3] net: report multicast group user count
From: patchwork-bot+netdevbpf @ 2026-07-03  7:00 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: davem, andrew+netdev, dsahern, donald.hunter, edumazet, idosch,
	kuba, pabeni, shuah, horms, linux-kernel, linux-kselftest, netdev
In-Reply-To: <20260630110207.37841-1-sigefriedhyy@gmail.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Tue, 30 Jun 2026 20:02:04 +0900 you wrote:
> RTM_GETMULTICAST reports IPv4 and IPv6 multicast group membership, but
> does not include the per-group user count. Userspace therefore still has
> to parse /proc/net/igmp and /proc/net/igmp6 to obtain the Users column.
> In particular, this prevents iproute2 from moving "ip maddr show"
> entirely from procfs to rtnetlink.
> 
> Add IFA_MC_USERS to carry the user count in RTM_GETMULTICAST dumps and
> RTM_NEWMULTICAST / RTM_DELMULTICAST notifications for both address
> families. Update the rt-addr YNL specification and extend the rtnetlink
> selftest to verify that two joins increase the reported count by two.
> 
> [...]

Here is the summary with links:
  - [net-next,1/3] net: ipv4: report multicast group user count
    https://git.kernel.org/netdev/net-next/c/7cb8198761e6
  - [net-next,2/3] net: ipv6: report multicast group user count
    https://git.kernel.org/netdev/net-next/c/e1d0f3f08391
  - [net-next,3/3] selftests: net: check multicast group user count
    https://git.kernel.org/netdev/net-next/c/3373cb099e66

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* RE: [PATCH v2 net-next 02/14] net: enetc: extract common helpers for MAC hash filter configuration
From: Wei Fang (OSS) @ 2026-07-03  7:02 UTC (permalink / raw)
  To: sashiko-reviews@lists.linux.dev
  Cc: imx@lists.linux.dev, Frank.Li@kernel.org, network dev
In-Reply-To: <20260703032702.231E51F000E9@smtp.kernel.org>

> commit 0373b31ab4e7a0beb8461d5ceb05e09d7d5b61ab
> Author: Wei Fang <wei.fang@nxp.com>
> Date:   Thu Jul 2 02:57:02 2026 +0000
> 
> net: enetc: extract common helpers for MAC hash filter configuration
> 
> The patch extracts enetc_set_si_uc_hash_filter() and
> enetc_set_si_mc_hash_filter() into enetc_pf_common.c. This allows sharing
> the MAC hash filter configuration between ENETC v1 and v4 drivers, and
> removes redundant hardware-specific functions.
> 
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> > index a97d2e2dd07b7..db2a800a7aaf0 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> [ ... ]
> > @@ -135,7 +108,7 @@ static void enetc_sync_mac_filters(struct enetc_pf
> *pf)
> >  		if (em) {
> >  			int err;
> >
> > -			enetc_clear_mac_ht_flt(si, 0, UC);
> > +			enetc_set_si_uc_hash_filter(si, 0, 0);
> >
> >  			err = enetc_set_mac_flt_entry(si, pos, f->mac_addr,
> >  						      BIT(0));
> > @@ -147,11 +120,15 @@ static void enetc_sync_mac_filters(struct enetc_pf
> *pf)
> >  				 err);
> >  		}
> >
> > +		bitmap_to_arr64(&hash, f->mac_hash_table,
> > +				ENETC_MADDR_HASH_TBL_SZ);
> >  		/* hash table filter, clear EM filter for UC entries */
> > -		if (i == UC)
> > +		if (i == UC) {
> >  			enetc_clear_mac_flt_entry(si, pos);
> > -
> > -		enetc_set_mac_ht_flt(si, 0, i, *f->mac_hash_table);
> > +			enetc_set_si_uc_hash_filter(si, 0, hash);
> 
> [Severity: High]
> This is a pre-existing issue, but does this fallback path actually work for
> exact match MAC addresses?
> 
> In enetc_pf_set_rx_mode(), when configuring a single unicast MAC address,
> enetc_add_mac_addr_em_filter() is called, but the hash table population is
> explicitly skipped:
> 
> enetc_pf_set_rx_mode() {
> 	...
> 	em = (netdev_uc_count(ndev) == 1);
> 	netdev_for_each_uc_addr(ha, ndev) {
> 		if (em) {
> 			enetc_add_mac_addr_em_filter(filter, ha->addr);
> 			break;
> 		}
> 		enetc_add_mac_addr_ht_filter(filter, ha->addr);
> 	}
> 	...
> }
> 
> Because of this, mac_hash_table remains empty for the EM filter.
> 
> If enetc_set_mac_flt_entry() fails in enetc_sync_mac_filters(), the driver
> warns "fallback to HT filt" and falls through to this block. However, since
> the hash table was never populated, the derived hash is 0.
> 
> Doesn't this result in programming an empty hash filter, which will silently
> drop all packets for this intended MAC address instead of falling back to
> hash-based filtering?
> 

Yes, the code behavior is inconsistent with the comments, and this issue
will be resolved by adding a fix patch. The current patch does not require
modification.


^ permalink raw reply

* [syzbot] [mm?] INFO: rcu detected stall in unmap_region
From: syzbot @ 2026-07-03  7:03 UTC (permalink / raw)
  To: akpm, jannh, liam, linux-kernel, linux-mm, ljs, netdev, pfalcato,
	syzkaller-bugs, vbabka

Hello,

syzbot found the following issue on:

HEAD commit:    32f1c2bbb26a net: airoha: dma map xmit frags with skb_frag..
git tree:       net
console output: https://syzkaller.appspot.com/x/log.txt?x=116c2c0a580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=86ba763b42fa66a
dashboard link: https://syzkaller.appspot.com/bug?extid=2ad5ec205a38c46522b3
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
syz repro:      https://syzkaller.appspot.com/x/repro.syz?x=132f5861580000

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/7b7c3a22a8ed/disk-32f1c2bb.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/168b43c87305/vmlinux-32f1c2bb.xz
kernel image: https://storage.googleapis.com/syzbot-assets/70704720d284/bzImage-32f1c2bb.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+2ad5ec205a38c46522b3@syzkaller.appspotmail.com

rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: 	0-...!: (1 GPs behind) idle=6664/1/0x4000000000000000 softirq=17730/17732 fqs=2
rcu: 	(detected by 1, t=10502 jiffies, g=17101, q=1895 ncpus=2)
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 6010 Comm: modprobe Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:lock_release+0x5/0x3c0 kernel/locking/lockdep.c:5876
Code: e9 4f fe ff ff 41 bf 2f 00 00 00 e9 06 ff ff ff 0f 1f 44 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 55 <41> 57 41 56 41 55 41 54 53 48 83 ec 30 49 89 f5 49 89 fe 65 48 8b
RSP: 0018:ffffc90000007db0 EFLAGS: 00000082
RAX: 932ea09fa7cced00 RBX: 0000000000000092 RCX: 0000000000000002
RDX: 0000000000000001 RSI: ffffffff81b30e19 RDI: ffffffff9a73d4c8
RBP: ffff88807e29b300 R08: 0000000000000003 R09: 0000000000000004
R10: dffffc0000000000 R11: fffff52000000f98 R12: ffff8880b86281c0
R13: dffffc0000000000 R14: ffffffff9a73d4b0 R15: ffff8880b8628350
FS:  0000000000000000(0000) GS:ffff888125226000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffae81e84e8 CR3: 000000007ee52000 CR4: 00000000003526f0
Call Trace:
 <IRQ>
 __raw_spin_unlock_irqrestore include/linux/spinlock_api_smp.h:176 [inline]
 _raw_spin_unlock_irqrestore+0x1b/0x80 kernel/locking/spinlock.c:198
 debug_hrtimer_deactivate kernel/time/hrtimer.c:490 [inline]
 __run_hrtimer kernel/time/hrtimer.c:2000 [inline]
 __hrtimer_run_queues+0x239/0xa10 kernel/time/hrtimer.c:2096
 hrtimer_interrupt+0x448/0x910 kernel/time/hrtimer.c:2215
 local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1051 [inline]
 __sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1068
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:do_zap_pte_range mm/memory.c:1819 [inline]
RIP: 0010:zap_pte_range mm/memory.c:1934 [inline]
RIP: 0010:zap_pmd_range mm/memory.c:2020 [inline]
RIP: 0010:zap_pud_range mm/memory.c:2048 [inline]
RIP: 0010:zap_p4d_range mm/memory.c:2069 [inline]
RIP: 0010:__zap_vma_range+0xfbd/0x4f10 mm/memory.c:2109
Code: 4c 89 64 24 30 4c 89 e6 48 83 e6 9f 31 ff e8 3a 4a ad ff 4d 89 e7 49 83 e4 9f 75 2f 48 8b 44 24 10 4c 01 e8 48 83 f8 01 74 28 <e8> 3e 45 ad ff 48 83 c3 08 49 ff c5 4d 89 f4 eb a3 4d 89 ef e8 2a
RSP: 0018:ffffc90003287000 EFLAGS: 00000286
RAX: ffffffffffffff73 RBX: ffff888034e9d190 RCX: ffff88802dcf1f00
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc900032872f0 R08: ffff888078979803 R09: 1ffff1100f12f300
R10: dffffc0000000000 R11: ffffed100f12f301 R12: 0000000000000000
R13: 0000000000000032 R14: dffffc0000000000 R15: 0000000000000000
 unmap_vmas+0x390/0x550 mm/memory.c:2178
 unmap_region+0x208/0x330 mm/vma.c:488
 vms_clear_ptes mm/vma.c:1303 [inline]
 vms_clean_up_area mm/vma.c:1315 [inline]
 __mmap_setup mm/vma.c:2476 [inline]
 __mmap_region mm/vma.c:2756 [inline]
 mmap_region+0xc62/0x2310 mm/vma.c:2860
 do_mmap+0xc3b/0x10c0 mm/mmap.c:560
 vm_mmap_pgoff+0x272/0x4e0 mm/util.c:581
 ksys_mmap_pgoff+0x4dc/0x760 mm/mmap.c:606
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ffae8219242
Code: 08 00 04 00 00 eb e2 90 41 f7 c1 ff 0f 00 00 75 27 55 89 cd 53 48 89 fb 48 85 ff 74 33 41 89 ea 48 89 df b8 09 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 5e 5b 5d c3 0f 1f 00 c7 05 46 40 01 00 16 00
RSP: 002b:00007ffec64cd428 EFLAGS: 00000206 ORIG_RAX: 0000000000000009
RAX: ffffffffffffffda RBX: 00007ffae7f73000 RCX: 00007ffae8219242
RDX: 0000000000000005 RSI: 000000000014e000 RDI: 00007ffae7f73000
RBP: 0000000000000812 R08: 0000000000000000 R09: 0000000000028000
R10: 0000000000000812 R11: 0000000000000206 R12: 00007ffec64cd478
R13: 00007ffae81ed5f0 R14: 00007ffec64cdc60 R15: 00000fffd8c99a88
 </TASK>
rcu: rcu_preempt kthread starved for 10477 jiffies! g17101 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=1
rcu: 	Unless rcu_preempt kthread gets sufficient CPU time, OOM is now expected behavior.
rcu: RCU grace-period kthread stack dump:
task:rcu_preempt     state:R  running task     stack:28192 pid:16    tgid:16    ppid:2      task_flags:0x208040 flags:0x00080000
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x17d9/0x56c0 kernel/sched/core.c:7234
 __schedule_loop kernel/sched/core.c:7311 [inline]
 schedule+0x164/0x2b0 kernel/sched/core.c:7326
 schedule_timeout+0x152/0x2c0 kernel/time/sleep_timeout.c:99
 rcu_gp_fqs_loop+0x30c/0x11f0 kernel/rcu/tree.c:2123
 rcu_gp_kthread+0x9e/0x2b0 kernel/rcu/tree.c:2325
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
rcu: Stack dump where RCU GP kthread last ran:
CPU: 1 UID: 0 PID: 4992 Comm: udevd Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:csd_lock_wait kernel/smp.c:342 [inline]
RIP: 0010:smp_call_function_many_cond+0x10b0/0x14b0 kernel/smp.c:892
Code: c0 75 73 41 8b 1e 89 de 83 e6 01 31 ff e8 d8 1e 0c 00 83 e3 01 48 bb 00 00 00 00 00 fc ff df 75 07 e8 84 1a 0c 00 eb 37 f3 90 <41> 0f b6 04 1c 84 c0 75 10 41 f7 06 01 00 00 00 74 1e e8 69 1a 0c
RSP: 0018:ffffc90003837580 EFLAGS: 00000293
RAX: ffffffff81ba5717 RBX: dffffc0000000000 RCX: ffff88807e5a0000
RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
RBP: ffffc900038376a8 R08: ffffffff9032d2f7 R09: 1ffffffff2065a5e
R10: dffffc0000000000 R11: fffffbfff2065a5f R12: 1ffff110170c85fd
R13: ffff8880b873c448 R14: ffff8880b8642fe8 R15: 0000000000000000
FS:  00007fdd3255d880(0000) GS:ffff888125326000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00002c919703e000 CR3: 000000007e7f2000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 on_each_cpu_cond_mask+0x3f/0x80 kernel/smp.c:1057
 __flush_tlb_multi arch/x86/include/asm/paravirt.h:46 [inline]
 flush_tlb_multi arch/x86/mm/tlb.c:1361 [inline]
 flush_tlb_mm_range+0x5c4/0x1090 arch/x86/mm/tlb.c:1451
 dup_mmap+0x1786/0x1d90 mm/mmap.c:1905
 dup_mm kernel/fork.c:1538 [inline]
 copy_mm+0x11a/0x480 kernel/fork.c:1590
 copy_process+0x1e99/0x43f0 kernel/fork.c:2288
 kernel_clone+0x2d7/0x940 kernel/fork.c:2747
 __do_sys_clone kernel/fork.c:2888 [inline]
 __se_sys_clone kernel/fork.c:2872 [inline]
 __x64_sys_clone+0x1b6/0x230 kernel/fork.c:2872
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fdd31ef1636
Code: 89 df e8 6d e8 f6 ff 45 31 c0 31 d2 31 f6 64 48 8b 04 25 10 00 00 00 bf 11 00 20 01 4c 8d 90 d0 02 00 00 b8 38 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 52 89 c5 85 c0 75 31 64 48 8b 04 25 10 00 00
RSP: 002b:00007ffcd95f6670 EFLAGS: 00000246 ORIG_RAX: 0000000000000038
RAX: ffffffffffffffda RBX: 00007ffcd95f6678 RCX: 00007fdd31ef1636
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001200011
RBP: 0000564712dd5910 R08: 0000000000000000 R09: 0000564712ddd8e0
R10: 00007fdd3255db50 R11: 0000000000000246 R12: 00007ffcd95f6a30
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
 </TASK>


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* Re: [PATCH 2/3] arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
From: Nazle Asmade, Muhammad Nazim Amirul @ 2026-07-03  7:04 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: dinguyen@kernel.org, maxime.chevallier@bootlin.com,
	rmk+kernel@armlinux.org.uk, krzk+dt@kernel.org,
	conor+dt@kernel.org, robh@kernel.org, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	andrew+netdev@lunn.ch, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <d70a6795-e1a2-43e2-b523-0fc668324674@lunn.ch>

On 1/7/2026 8:47 pm, Andrew Lunn wrote:
>>> # There are a small number of cases where the MAC has hard coded
>>> # delays which cannot be disabled. The 'phy-mode' only describes the
>>> # PCB.  The inability to disable the delays in the MAC does not change
>>> # the meaning of 'phy-mode'. It does however mean that a 'phy-mode' of
>>> # 'rgmii' is now invalid, it cannot be supported, since both the PCB
>>> # and the MAC and PHY adding delays cannot result in a functional
>>> # link. Thus the MAC should report a fatal error for any modes which
>>> # cannot be supported. When the MAC implements the delay, it must
>>> # ensure that the PHY does not also implement the same delay. So it
>>> # must modify the phy-mode it passes to the PHY, removing the delay it
>>> # has added. Failure to remove the delay will result in a
>>> # non-functioning link.
>>>
>>>       Andrew
>>>
>>> ---
>>> pw-bot: cr
>> Hi Andrew,
>>
>> The delays are provided by the FPGA GMII-to-RGMII converter soft IP,
>> which is hardcoded in the FPGA bitstream and cannot be disabled or
>> modified from the driver side.
>>
>> Using phy-mode = "rgmii" is intentional here — it prevents the PHY from
>> adding its own internal delays on top, since the FPGA converter already
>> provides the full required delay. This is consistent with how all other
>> Agilex5 SoCDK board variants are described, as seen in commit
>> c5637e5ceb4b ("arm64: dts: socfpga: agilex5: Fix phy-mode to rgmii as HW
>> provides clock delay") already in Dinh Nguyen's tree, which applies the
>> same rationale across all Agilex5 boards.
> 
> I've become more insistent that designs get this correct. So i don't
> care too much about past systems. Many vendors are having to fix up
> their drivers and DT in order to make new boards consistent.
> 
> You can look at your system as the FPGA being the MAC, and the PHY is
> the PHY. The PCB is not providing the delay, the MAC is. This exactly
> fits the description above.
> 
>       Andrew
Hi Andrew,

Thank you for the clarification. We agree with your framework in 
principle, but would like to explain why phy-mode = "rgmii" is the 
appropriate description for this specific case.

After getting more information from hw team, for Agilex specific device, 
the RGMII timing delays on this board are provided by an FPGA delay 
chain (Input/Output Delay Chain primitives in the FPGA fabric). The 
reason for using the FPGA rather than the PHY is that the Marvell PHY on 
this board only supports 0ns or 2ns delay steps — too coarse to meet the 
RGMII timing requirements. The FPGA delay chain provides up to 63 steps 
of ~0.1ns precision, which the hardware team has tuned at design time to 
achieve correct signal timing.

This delay is fixed in the FPGA bitstream and is invisible to the Linux 
driver — the driver cannot read, modify, or even detect the delay value 
that was programmed. The driver has no way to know "the MAC is providing 
X nanoseconds of delay."

In this situation, the only meaningful action the driver can take is to 
instruct the PHY not to add its own delays — which phy-mode = "rgmii" 
already achieves correctly. Changing to phy-mode = "rgmii-id" and having 
the driver strip the delay before passing to the PHY would produce the 
same hardware behaviour (PHY adds zero delay), but would add driver 
complexity with no practical benefit, and would misrepresent the FPGA 
delay as a driver-managed MAC delay when it is actually a fixed, 
board-level hardware calibration.

Could you advise if you still prefer the rgmii-id approach given this 
constraint?

BR,
Nazim

^ permalink raw reply

* Re: [PATCH net-next v4 3/3] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
From: Paolo Abeni @ 2026-07-03  7:10 UTC (permalink / raw)
  To: github
  Cc: o.rempel, kory.maincent, andrew+netdev, hkallweit1, linux, davem,
	edumazet, kuba, pabeni, corey, jelonek.jonas, horms, netdev,
	linux-kernel
In-Reply-To: <20260630091125.3162481-4-github@szelinsky.de>

From: AI Reviewer <ai@example.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
Now I have all the information I need. Let me produce the report.

commit da23b745f9e416f319d1edba4054506a8dabbe37
Author: Corey Leavitt <corey@leavitt.info>
Subject: net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook

This patch moves ownership of phydev->psec from fwnode_mdio into the phy
subsystem via a PSE notifier, defers the final pse_control_put() from
phy_device_remove() to phy_device_release(), and adds a
phy_device_register_locked() variant for callers that already hold rtnl.

Link: https://lore.kernel.org/netdev/e00048dd-1ed3-40c3-9912-59bccf015ad5@gmail.com/

> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
> index 0615228459ef..f5febff4b00b 100644
> --- a/drivers/net/phy/phy_device.c
> +++ b/drivers/net/phy/phy_device.c
> @@ -223,8 +223,19 @@ static void phy_mdio_device_free(struct mdio_device *mdiodev)
>
>  static void phy_device_release(struct device *dev)
>  {
> +	struct phy_device *phydev = to_phy_device(dev);
> +
> +	/* bus_for_each_dev() holds get_device() across each iteration
> +	 * step, deferring this release callback until any in-flight PSE
> +	 * notifier walk has advanced past this phy. pse_control_put()
> +	 * takes pse_list_mutex, so this path must run in sleepable
> +	 * context.
> +	 */
> +	might_sleep();
> +	pse_control_put(phydev->psec);
> +
>  	fwnode_handle_put(dev->fwnode);
> -	kfree(to_phy_device(dev));
> +	kfree(phydev);
>  }

[High]
Can this combine with the PSE_UNREGISTERED walk to leave a use-after-free
window for phys that have already been device_del()'d but still have an
outstanding reference?

The notifier walks the bus klist:

  case PSE_UNREGISTERED:
      rtnl_lock();
      bus_for_each_dev(&mdio_bus_type, NULL, data,
                       phy_pse_detach_one);
      rtnl_unlock();

bus_for_each_dev() iterates subsys_private's klist_devices, and
device_del() -> bus_remove_device() -> klist_del() unlinks the device
from that klist. A phy that has been removed from the bus but is still
pinned (for example by a netdev that took get_device() via
phy_attach_direct()) is no longer reachable from this walk, so
phy_pse_detach_one() will not clear its phydev->psec.

> @@ -1152,8 +1299,6 @@ EXPORT_SYMBOL(phy_device_register);
>  void phy_device_remove(struct phy_device *phydev)
>  {
>  	unregister_mii_timestamper(phydev->mii_ts);
> -	pse_control_put(phydev->psec);
> -
>  	device_del(&phydev->mdio.dev);
>
>  	/* Assert the reset signal */

[High]
Previously phy_device_remove() performed pse_control_put() synchronously
before device_del(), so the put always ran while phydev->psec still
pointed at a live pcdev. After this change the put is deferred to
phy_device_release(), which only runs when the last device reference is
dropped.

Consider this sequence on two CPUs:

  CPU A (phy teardown)              CPU B (rmmod PSE driver)
  --------------------------------  -----------------------------------
  phy_detach()
    /* netdev still pins phydev */
  phy_device_remove()
    device_del()  /* off klist */
                                    pse_controller_unregister()
                                      blocking_notifier_call_chain(
                                        PSE_UNREGISTERED)
                                        phy_pse_notifier_event()
                                          bus_for_each_dev(mdio_bus_type,
                                            ..., phy_pse_detach_one)
                                          /* phydev not on klist,
                                           * phydev->psec not cleared */
                                      pse_release_pis()
                                        kfree(pcdev->pi)
  /* later, last ref dropped */
  phy_device_release()
    pse_control_put(phydev->psec)
      __pse_control_release()
        pcdev->pi[psec->id].admin_state_enabled
        /* UAF: pcdev->pi already freed */
        module_put(psec->pcdev->owner)

Does the commit message paragraph

  "The final pse_control_put() of phydev->psec moves from
   phy_device_remove() to phy_device_release(), so it runs only after
   every reference on the device -- including the bus-iterator
   references taken by bus_for_each_dev() in the notifier walk -- has
   been dropped."

cover the case where a phy is already off the bus klist before the
notifier fires? bus_for_each_dev() only holds get_device() on devices
it can reach via the klist, so a phy that has been device_del()'d but
is still pinned by some other holder (an attached netdev being the
typical case) is invisible to phy_pse_detach_one().

Similarly, when an MDIO bus is torn down (mdiobus_unregister() walks
its phys and calls phy_device_remove() on each), is there any guarantee
that no consumer is still holding a reference that would carry the
phy past device_del() and into a later phy_device_release()?

If this race is reachable, would it be safer to keep the
pse_control_put() in phy_device_remove() (or somewhere else that runs
before device_del() returns), so the put cannot outlive any
pcdev->pi[] that PSE_UNREGISTERED is about to free?
-- 
This is an AI-generated review.


^ permalink raw reply

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

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>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c

Changes in v4:
 - no changes

Changes in v5:
 - no changes

Changes in v6:
 - increase timeout to 600ms

Changes in v7:
 - no changes

Changes in v8:
 - no changes

Changes in v9:
 - rename genphy_c45_soft_reset to genphy_c45_pma_soft_reset
---
 drivers/net/phy/phy-c45.c | 22 ++++++++++++++++++++++
 include/linux/phy.h       |  1 +
 2 files changed, 23 insertions(+)

diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 126951741428..c1f817a59739 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -384,6 +384,28 @@ int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart)
 }
 EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg);
 
+/**
+ * genphy_c45_pma_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register
+ * @phydev: target phy_device struct
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int genphy_c45_pma_soft_reset(struct phy_device *phydev)
+{
+	int ret, val;
+
+	ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+			       MDIO_CTRL1_RESET);
+	if (ret < 0)
+		return ret;
+
+	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD,
+					 MDIO_CTRL1, val,
+					 !(val & MDIO_CTRL1_RESET),
+					 5000, 600000, true);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset);
+
 /**
  * genphy_c45_aneg_done - return auto-negotiation complete status
  * @phydev: target phy_device struct
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341b..d6bc19931fb8 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2309,6 +2309,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed);
 /* Clause 45 PHY */
 int genphy_c45_restart_aneg(struct phy_device *phydev);
 int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart);
+int genphy_c45_pma_soft_reset(struct phy_device *phydev);
 int genphy_c45_aneg_done(struct phy_device *phydev);
 int genphy_c45_read_link(struct phy_device *phydev);
 int genphy_c45_read_lpa(struct phy_device *phydev);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v9 3/4] net: phy: realtek: add support for RTL8261C_CG
From: javen @ 2026-07-03  7:13 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260703071330.1707-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is
0x001cc898.
This patch introduces a distinct family of handlers (probe, get_features,
config_aneg, read_status, config_intr, handle_interrupt).

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c
 - add kernel-doc about return value
 - add MASTER_SLAVE_CFG_MASTER_PREFERRED,
   MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
   MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg

Changes in v4:
 - no changes

Changes in v5:
 - remove genphy_c45_pma_setup_forced() for this is already done when
   calling genphy_c45_config_aneg()

Changes in v6:
 - when PHY_INTERRUPT_DISABLE, clear IMR and ISR
 - if AUTONEG_DISABLE, nothing need to do in rtl8261x_config_aneg
 - add rtl8261x_read_status, support 1G speed

Changes in v7:
 - remove RTL8261X_IMR and RTL8261X_ISR, duplicated definition
 - modify commit message
 - continue with default behavior when meet unknown sub_phy_id
 - change the internal order of rtl8261x_read_status
 - expand RTL8261X_INT_MASK_DEFAULT
 - add the handle for ADVERTISE_1000HALF in rtl8261x_config_aneg

Changes in v8:
 - no changes

Changes in v9:
 - get an status from genphy_c45_aneg_done()
---
 drivers/net/phy/realtek/realtek_main.c | 192 +++++++++++++++++++++++++
 1 file changed, 192 insertions(+)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 27268811f564..bd12f10843d9 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -141,6 +141,10 @@
 #define RTL8211F_PHYSICAL_ADDR_WORD1		17
 #define RTL8211F_PHYSICAL_ADDR_WORD2		18
 
+#define RTL8261X_EXT_ADDR_REG			0xa436
+#define RTL8261X_EXT_DATA_REG			0xa438
+#define RTL_8261X_SUB_PHY_ID_ADDR		0x801d
+
 #define RTL822X_VND1_SERDES_OPTION			0x697a
 #define RTL822X_VND1_SERDES_OPTION_MODE_MASK		GENMASK(5, 0)
 #define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII		0
@@ -251,6 +255,32 @@
 #define RTL_8221B_VM_CG				0x001cc84a
 #define RTL_8251B				0x001cc862
 #define RTL_8261C				0x001cc890
+#define RTL_8261C_CG				0x001cc898
+
+#define RTL8261C_CE_MODEL		0x00
+#define RTL8261X_INT_AUTONEG_ERROR	BIT(0)
+#define RTL8261X_INT_PAGE_RECV		BIT(2)
+#define RTL8261X_INT_AUTONEG_DONE	BIT(3)
+#define RTL8261X_INT_LINK_CHG		BIT(4)
+#define RTL8261X_INT_PHY_REG_ACCESS	BIT(5)
+#define RTL8261X_INT_PME		BIT(7)
+#define RTL8261X_INT_ALDPS_CHG		BIT(9)
+#define RTL8261X_INT_JABBER		BIT(10)
+
+#define RTL8261X_INT_MASK_DEFAULT	(RTL8261X_INT_AUTONEG_DONE | \
+					 RTL8261X_INT_LINK_CHG | \
+					 RTL8261X_INT_AUTONEG_ERROR | \
+					 RTL8261X_INT_JABBER)
+
+#define RTL8261X_INT_MASK_ALL		(RTL8261X_INT_AUTONEG_ERROR | \
+					 RTL8261X_INT_PAGE_RECV | \
+					 RTL8261X_INT_AUTONEG_DONE | \
+					 RTL8261X_INT_LINK_CHG | \
+					 RTL8261X_INT_PHY_REG_ACCESS | \
+					 RTL8261X_INT_PME | \
+					 RTL8261X_INT_ALDPS_CHG | \
+					 RTL8261X_INT_JABBER)
+
 
 /* RTL8211E and RTL8211F support up to three LEDs */
 #define RTL8211x_LED_COUNT			3
@@ -310,6 +340,156 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
 	return phy_restore_page(phydev, oldpage, ret);
 }
 
+static int rtl8261x_probe(struct phy_device *phydev)
+{
+	int sub_phy_id, ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
+			    RTL_8261X_SUB_PHY_ID_ADDR);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG);
+	if (ret < 0)
+		return ret;
+
+	sub_phy_id = (ret >> 8) & 0xff;
+
+	switch (sub_phy_id) {
+	case RTL8261C_CE_MODEL:
+		phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
+		break;
+
+	default:
+		phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_get_features(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_c45_pma_read_abilities(phydev);
+	if (ret)
+		return ret;
+	/*
+	 * Supplement Multi-Gig speeds that may not be automatically detected
+	 * RTL8261X supports 2.5G/5G in addition to standard 10G
+	 */
+	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+			 phydev->supported);
+
+	return 0;
+}
+
+static int rtl8261x_read_status(struct phy_device *phydev)
+{
+	int ret, val = 0;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		ret = genphy_c45_aneg_done(phydev);
+		if (ret < 0)
+			return ret;
+
+		if (ret) {
+			val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+					   RTL822X_VND2_C22_REG(MII_STAT1000));
+			if (val < 0)
+				return val;
+		}
+	}
+
+	mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+	ret = genphy_c45_read_status(phydev);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8261x_config_intr(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+		if (ret < 0)
+			return ret;
+
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER,
+				    RTL8261X_INT_MASK_DEFAULT);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0);
+		if (ret < 0)
+			return ret;
+
+		ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev)
+{
+	int irq_status;
+
+	irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+	if (irq_status < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	}
+
+	if (!(irq_status & RTL8261X_INT_MASK_ALL))
+		return IRQ_NONE;
+
+	if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE |
+	    RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER))
+		phy_trigger_machine(phydev);
+
+	return IRQ_HANDLED;
+}
+
+static int rtl8261x_config_aneg(struct phy_device *phydev)
+{
+	u16 adv_1g = 0;
+	int ret;
+
+	ret = genphy_c45_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (phydev->autoneg == AUTONEG_DISABLE)
+		return 0;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+			      phydev->advertising))
+		adv_1g = ADVERTISE_1000FULL;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+			      phydev->advertising))
+		adv_1g |= ADVERTISE_1000HALF;
+
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
+				     RTL822X_VND2_C22_REG(MII_CTRL1000),
+				     ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+				     adv_1g);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		return genphy_c45_restart_aneg(phydev);
+
+	return 0;
+}
+
 static int rtl821x_probe(struct phy_device *phydev)
 {
 	struct device *dev = &phydev->mdio.dev;
@@ -3001,6 +3181,18 @@ static struct phy_driver realtek_drvs[] = {
 		.resume		= genphy_resume,
 		.read_mmd	= genphy_read_mmd_unsupported,
 		.write_mmd	= genphy_write_mmd_unsupported,
+	}, {
+		PHY_ID_MATCH_EXACT(RTL_8261C_CG),
+		.name			= "Realtek RTL8261C 10Gbps PHY",
+		.probe			= rtl8261x_probe,
+		.get_features		= rtl8261x_get_features,
+		.config_aneg		= rtl8261x_config_aneg,
+		.read_status		= rtl8261x_read_status,
+		.config_intr		= rtl8261x_config_intr,
+		.handle_interrupt	= rtl8261x_handle_interrupt,
+		.soft_reset		= genphy_c45_pma_soft_reset,
+		.suspend		= genphy_c45_pma_suspend,
+		.resume			= genphy_c45_pma_resume,
 	},
 };
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v9 4/4] net: phy: realtek: load firmware for RTL8261C_CG
From: javen @ 2026-07-03  7:13 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260703071330.1707-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds support for loading firmware. Download some parameters
for RTL8261C_CG.

Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will not pad
 - reverse xmas tree for some definition
 - add explanation on rtl_phy_write_mmd_bits()

Changes in v3:
 - add struct rtl8261x_priv

Changes in v4:
 - add struct device *dev

Changes in v5:
 - no changes

Changes in v6:
 - replace rtl_phy_write_mmd_bits with phy_modify_mmd, keep mdio lock
 - check msb and lsb at the beginning of rtl8261x_fw_execute_entry()
 - add comments on rtl8261x_config_init()

Changes in v7:
 - no changes

Changes in v8:
 - remove some phydev_err message in rtl8261x_fw_execute_entry() and
   rtl8261x_config_init()

Changes in v9:
 - no changes
---
 drivers/net/phy/realtek/realtek_main.c | 214 +++++++++++++++++++++++++
 1 file changed, 214 insertions(+)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index bd12f10843d9..68a2de0b2bd3 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -8,7 +8,9 @@
  * Copyright (c) 2004 Freescale Semiconductor, Inc.
  */
 #include <linux/bitops.h>
+#include <linux/crc32.h>
 #include <linux/ethtool_netlink.h>
+#include <linux/firmware.h>
 #include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/pm_wakeirq.h>
@@ -281,6 +283,43 @@
 					 RTL8261X_INT_ALDPS_CHG | \
 					 RTL8261X_INT_JABBER)
 
+#define FW_MAIN_MAGIC			0x52544C38
+#define FW_SUB_MAGIC_8261C		0x32363143
+#define RTL8261X_POLL_TIMEOUT_MS	100
+#define RTL8261X_MAX_MMD_DEV		31
+
+#define RTL8261C_CE_FW_NAME	"rtl_nic/rtl8261c.bin"
+MODULE_FIRMWARE(RTL8261C_CE_FW_NAME);
+
+enum rtl8261x_fw_op {
+	OP_WRITE = 0x00,	/* Write */
+	OP_POLL  = 0x02,	/* Polling */
+};
+
+struct rtl8261x_fw_header {
+	__le32 main_magic;	/* Main magic number */
+	__le32 sub_magic;	/* Sub magic number */
+	__le16 version_major;	/* Major version */
+	__le16 version_minor;	/* Minor version */
+	__le16 num_entries;	/* Number of entries */
+	__le16 reserved;	/* Reserved */
+	__le32 crc32;		/* CRC32 checksum */
+};
+
+struct rtl8261x_fw_entry {
+	__u8  type;		/* Operation type (OP_*) */
+	__u8  dev;		/* MMD device */
+	__le16 addr;		/* Register address */
+	__u8  msb;		/* MSB bit position */
+	__u8  lsb;		/* LSB bit position */
+	__le16 value;		/* Value to write/compare */
+	__le16 timeout_ms;	/* Poll timeout in milliseconds */
+	__u8  poll_set;		/* Poll until equal (1) or not equal (0) */
+	__u8  reserved;		/* Reserved */
+};
+
+#define FW_HEADER_SIZE		sizeof(struct rtl8261x_fw_header)
+#define FW_ENTRY_SIZE		sizeof(struct rtl8261x_fw_entry)
 
 /* RTL8211E and RTL8211F support up to three LEDs */
 #define RTL8211x_LED_COUNT			3
@@ -300,6 +339,11 @@ struct rtl821x_priv {
 	u16 iner;
 };
 
+struct rtl8261x_priv {
+	const char *fw_name;
+	bool fw_loaded;
+};
+
 static int rtl821x_read_page(struct phy_device *phydev)
 {
 	return __phy_read(phydev, RTL821x_PAGE_SELECT);
@@ -342,8 +386,16 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
 
 static int rtl8261x_probe(struct phy_device *phydev)
 {
+	struct device *dev = &phydev->mdio.dev;
+	struct rtl8261x_priv *priv;
 	int sub_phy_id, ret;
 
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
 			    RTL_8261X_SUB_PHY_ID_ADDR);
 	if (ret < 0)
@@ -357,6 +409,7 @@ static int rtl8261x_probe(struct phy_device *phydev)
 
 	switch (sub_phy_id) {
 	case RTL8261C_CE_MODEL:
+		priv->fw_name = RTL8261C_CE_FW_NAME;
 		phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
 		break;
 
@@ -413,6 +466,152 @@ static int rtl8261x_read_status(struct phy_device *phydev)
 	return 0;
 }
 
+static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw)
+{
+	const struct rtl8261x_fw_header *hdr;
+	u32 main_magic, sub_magic;
+	u32 calc_crc, file_crc;
+	size_t data_len;
+	u16 num_entries;
+
+	if (fw->size < FW_HEADER_SIZE) {
+		phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size);
+		return -EINVAL;
+	}
+
+	hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+	main_magic = le32_to_cpu(hdr->main_magic);
+	if (main_magic != FW_MAIN_MAGIC) {
+		phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic);
+		return -EINVAL;
+	}
+
+	sub_magic = le32_to_cpu(hdr->sub_magic);
+	if (sub_magic != FW_SUB_MAGIC_8261C) {
+		phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic);
+		return -EINVAL;
+	}
+
+	num_entries = le16_to_cpu(hdr->num_entries);
+	data_len = num_entries * FW_ENTRY_SIZE;
+
+	if (fw->size != sizeof(*hdr) + data_len) {
+		phydev_err(phydev, "Firmware size mismatch\n");
+		return -EINVAL;
+	}
+
+	calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0;
+	file_crc = le32_to_cpu(hdr->crc32);
+
+	if (calc_crc != file_crc) {
+		phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n",
+			   calc_crc, file_crc);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_fw_execute_entry(struct phy_device *phydev,
+				     const struct rtl8261x_fw_entry *entry)
+{
+	u16 addr, value, timeout_ms;
+	u8 dev, msb, lsb, poll_set;
+	u32 bits, expect_val;
+	int ret, val;
+
+	dev = entry->dev;
+	addr = le16_to_cpu(entry->addr);
+	msb = entry->msb;
+	lsb = entry->lsb;
+	value = le16_to_cpu(entry->value);
+	timeout_ms = le16_to_cpu(entry->timeout_ms);
+	poll_set = entry->poll_set;
+
+	if (timeout_ms == 0)
+		timeout_ms = RTL8261X_POLL_TIMEOUT_MS;
+
+	if (dev > RTL8261X_MAX_MMD_DEV) {
+		phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev);
+		return -EINVAL;
+	}
+
+	if (msb > 15 || lsb > msb) {
+		phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb);
+		return -EINVAL;
+	}
+
+	switch (entry->type) {
+	case OP_WRITE:
+		ret = phy_modify_mmd(phydev, dev, addr,
+				     GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb));
+		if (ret)
+			return ret;
+		break;
+
+	case OP_POLL:
+		bits = GENMASK(msb, lsb);
+		expect_val = (value << lsb) & bits;
+
+		if (poll_set)
+			ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+							(val & bits) == expect_val,
+							1000, timeout_ms * 1000, false);
+		else
+			ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+							(val & bits) != expect_val,
+							1000, timeout_ms * 1000, false);
+		if (ret)
+			return ret;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_fw_load(struct phy_device *phydev)
+{
+	struct rtl8261x_priv *priv = phydev->priv;
+	const struct rtl8261x_fw_entry *entry;
+	const struct rtl8261x_fw_header *hdr;
+	const struct firmware *fw;
+	int ret, i;
+
+	if (!priv->fw_name)
+		return 0;
+
+	ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev);
+	if (ret) {
+		phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret);
+		return ret;
+	}
+
+	ret = rtl8261x_verify_firmware(phydev, fw);
+	if (ret)
+		goto release_fw;
+
+	hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+	entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE);
+	for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) {
+		ret = rtl8261x_fw_execute_entry(phydev, entry);
+		if (ret) {
+			phydev_err(phydev, "Entry %d failed: %d\n", i, ret);
+			goto release_fw;
+		}
+	}
+
+	priv->fw_loaded = true;
+
+release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
 static int rtl8261x_config_intr(struct phy_device *phydev)
 {
 	int ret;
@@ -490,6 +689,20 @@ static int rtl8261x_config_aneg(struct phy_device *phydev)
 	return 0;
 }
 
+static int rtl8261x_config_init(struct phy_device *phydev)
+{
+	struct rtl8261x_priv *priv = phydev->priv;
+
+	/* The firmware parameters are preserved across IEEE soft resets and
+	 * suspend/resume cycles. Reloading is only necessary after a power
+	 * cycle or hard reset.
+	 */
+	if (priv->fw_name && !priv->fw_loaded)
+		return rtl8261x_fw_load(phydev);
+
+	return 0;
+}
+
 static int rtl821x_probe(struct phy_device *phydev)
 {
 	struct device *dev = &phydev->mdio.dev;
@@ -3185,6 +3398,7 @@ static struct phy_driver realtek_drvs[] = {
 		PHY_ID_MATCH_EXACT(RTL_8261C_CG),
 		.name			= "Realtek RTL8261C 10Gbps PHY",
 		.probe			= rtl8261x_probe,
+		.config_init		= rtl8261x_config_init,
 		.get_features		= rtl8261x_get_features,
 		.config_aneg		= rtl8261x_config_aneg,
 		.read_status		= rtl8261x_read_status,
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v9 2/4] net: phy: c45: add setup and read master/slave helpers
From: javen @ 2026-07-03  7:13 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260703071330.1707-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds two static helpers in drivers/net/phy/phy-c45.c to
configure and read back master-slave roles for non BASE-T1 Clause 45
PHYs via the 10GBASE-T AN control/status registers.
These helpers are wired into genphy_c45_config_aneg() and
genphy_c45_read_status(). This changes the observable ethtool output
for drivers using the generic c45 read path.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c
 - add kernel-doc about return value
 - add MASTER_SLAVE_CFG_MASTER_PREFERRED,
   MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
   MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg

Changes in v4:
 - no changes

Changes in v5:
 - move genphy_c45_an_setup_master_slave() to genphy_c45_config_aneg(),
   as that C22 does.

Changes in v6:
 - add colon in the function description
 - add genphy_c45_read_master_slave in read function

Changes in v7:
 - when phydev->link is down, just return UNKNOWN
 - modify commit message

Changes in v8:
 - no changes

Changes in v9:
 - no changes
---
 drivers/net/phy/phy-c45.c | 103 ++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/mdio.h |   5 ++
 2 files changed, 108 insertions(+)

diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index c1f817a59739..870920311f9a 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -406,6 +406,97 @@ int genphy_c45_pma_soft_reset(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset);
 
+/**
+ * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs
+ * @phydev: target phy_device struct
+ *
+ * Description: Configure the forced or preferred Master/Slave role
+ * 10GBASE-T control register (MMD 7, Register 0x0020) according to
+ * IEEE 802.3 standards.
+ *
+ * Return: negative errno code on failure, 0 if Master/Slave didn't change,
+ * or 1 if Master/Slave modes changed.
+ */
+static int genphy_c45_an_setup_master_slave(struct phy_device *phydev)
+{
+	u16 ctl = 0;
+
+	switch (phydev->master_slave_set) {
+	case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+		ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE;
+		break;
+	case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+		break;
+	case MASTER_SLAVE_CFG_MASTER_FORCE:
+		ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE;
+		break;
+	case MASTER_SLAVE_CFG_SLAVE_FORCE:
+		ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE;
+		break;
+	case MASTER_SLAVE_CFG_UNKNOWN:
+	case MASTER_SLAVE_CFG_UNSUPPORTED:
+		return 0;
+	default:
+		phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+		return -EOPNOTSUPP;
+	}
+
+	return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+				      MDIO_AN_10GBT_CTRL_MS_ENABLE |
+				      MDIO_AN_10GBT_CTRL_MS_VALUE |
+				      MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl);
+}
+
+/**
+ * genphy_c45_read_master_slave - read master/slave status
+ * @phydev: target phy_device struct
+ *
+ * Description: Read the Master/Slave configuration and status
+ * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021).
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int genphy_c45_read_master_slave(struct phy_device *phydev)
+{
+	int val;
+
+	phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+	phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) {
+		if (val & MDIO_AN_10GBT_CTRL_MS_VALUE)
+			phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+		else
+			phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+	} else {
+		if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE)
+			phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+		else
+			phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_10GBT_STAT_MS_FAULT) {
+		phydev->master_slave_state = MASTER_SLAVE_STATE_ERR;
+	} else if (phydev->link) {
+		if (val & MDIO_AN_10GBT_STAT_MS_RES)
+			phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+		else
+			phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+	} else {
+		phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+	}
+
+	return 0;
+}
+
 /**
  * genphy_c45_aneg_done - return auto-negotiation complete status
  * @phydev: target phy_device struct
@@ -1214,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev)
 			ret = genphy_c45_baset1_read_status(phydev);
 			if (ret < 0)
 				return ret;
+		} else {
+			ret = genphy_c45_read_master_slave(phydev);
+			if (ret < 0)
+				return ret;
 		}
 
 		phy_resolve_aneg_linkmode(phydev);
@@ -1247,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev)
 	if (ret > 0)
 		changed = true;
 
+	if (!genphy_c45_baset1_able(phydev)) {
+		ret = genphy_c45_an_setup_master_slave(phydev);
+		if (ret < 0)
+			return ret;
+		if (ret > 0)
+			changed = true;
+	}
+
 	return genphy_c45_check_and_restart_aneg(phydev, changed);
 }
 EXPORT_SYMBOL_GPL(genphy_c45_config_aneg);
diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h
index b2541c948fc1..06f4bc3c20c7 100644
--- a/include/uapi/linux/mdio.h
+++ b/include/uapi/linux/mdio.h
@@ -332,8 +332,13 @@
 #define MDIO_AN_10GBT_CTRL_ADV2_5G	0x0080	/* Advertise 2.5GBASE-T */
 #define MDIO_AN_10GBT_CTRL_ADV5G	0x0100	/* Advertise 5GBASE-T */
 #define MDIO_AN_10GBT_CTRL_ADV10G	0x1000	/* Advertise 10GBASE-T */
+#define MDIO_AN_10GBT_CTRL_MS_ENABLE	0x8000	/* Master/slave manual config enable */
+#define MDIO_AN_10GBT_CTRL_MS_VALUE	0x4000	/* Master/slave config value (1=Master) */
+#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE	0x2000	/* Master Preferred Type */
 
 /* AN 10GBASE-T status register. */
+#define MDIO_AN_10GBT_STAT_MS_FAULT	0x8000	/* Master/slave fault */
+#define MDIO_AN_10GBT_STAT_MS_RES	0x4000	/* Master/slave resolution (1=Master) */
 #define MDIO_AN_10GBT_STAT_LP2_5G	0x0020  /* LP is 2.5GBT capable */
 #define MDIO_AN_10GBT_STAT_LP5G		0x0040  /* LP is 5GBT capable */
 #define MDIO_AN_10GBT_STAT_LPTRR	0x0200	/* LP training reset req. */
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v9 0/4] Add support for RTL8261C_CG
From: javen @ 2026-07-03  7:13 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu

From: Javen Xu <javen_xu@realsil.com.cn>

Add support for RTL8261C_CG and add support for loading firmware.

Javen Xu (4):
  net: phy: c45: add genphy_c45_pma_soft_reset()
  net: phy: c45: add setup and read master/slave helpers
  net: phy: realtek: add support for RTL8261C_CG
  net: phy: realtek: load firmware for RTL8261C_CG

 drivers/net/phy/phy-c45.c              | 125 ++++++++
 drivers/net/phy/realtek/realtek_main.c | 406 +++++++++++++++++++++++++
 include/linux/phy.h                    |   1 +
 include/uapi/linux/mdio.h              |   5 +
 4 files changed, 537 insertions(+)

-- 
2.43.0


^ 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