All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav
@ 2025-09-27  7:07 Luke Howard
  2025-09-27  7:07 ` [RFC net-next 1/5] net: dsa: mv88e6xxx: add num_tx_queues to chip info structure Luke Howard
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

Add hardware offloaded 802.1Qav support to conforming Marvell switches.

Tested with 88E6341 and 88E6352 switch chips.

Luke Howard (5):
  net: dsa: mv88e6xxx: add num_tx_queues to chip info structure
  net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter
  net: dsa: mv88e6xxx: MQPRIO support
  net: dsa: mv88e6xxx: CBS support
  dt-bindings: net: dsa: mv88e6xxx: add mv88e6xxx-avb-mode property

 .../bindings/net/dsa/marvell,mv88e6xxx.yaml   |  25 +
 drivers/net/dsa/mv88e6xxx/Makefile            |   3 +-
 drivers/net/dsa/mv88e6xxx/avb.c               | 663 ++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/avb.h               | 244 +++++++
 drivers/net/dsa/mv88e6xxx/chip.c              | 308 +++++++-
 drivers/net/dsa/mv88e6xxx/chip.h              |  81 +++
 drivers/net/dsa/mv88e6xxx/global1.c           |   9 +-
 drivers/net/dsa/mv88e6xxx/global1.h           |  47 +-
 drivers/net/dsa/mv88e6xxx/global1_atu.c       |  17 +
 drivers/net/dsa/mv88e6xxx/global2.h           |  14 +-
 drivers/net/dsa/mv88e6xxx/global2_avb.c       | 205 +++++-
 drivers/net/dsa/mv88e6xxx/port.c              |   9 +
 drivers/net/dsa/mv88e6xxx/port.h              |   2 +
 include/linux/platform_data/mv88e6xxx.h       |   1 +
 14 files changed, 1607 insertions(+), 21 deletions(-)
 create mode 100644 drivers/net/dsa/mv88e6xxx/avb.c
 create mode 100644 drivers/net/dsa/mv88e6xxx/avb.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [RFC net-next 1/5] net: dsa: mv88e6xxx: add num_tx_queues to chip info structure
  2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
@ 2025-09-27  7:07 ` Luke Howard
  2025-09-27  7:07 ` [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter Luke Howard
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

In preparation for adding 802.1Qav support (FQTSS), add an element to
struct mv88e6xxx_info indicating the number of transmit queues supported
by each chip.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 15 +++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h |  3 +++
 2 files changed, 18 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index b4d48997bf467..42189aeb9aec0 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4099,6 +4099,8 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
 			goto unlock;
 	}
 
+	ds->num_tx_queues = chip->info->num_tx_queues;
+
 	err = mv88e6xxx_stats_setup(chip);
 	if (err)
 		goto unlock;
@@ -5863,6 +5865,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
+		.num_tx_queues = 4,
 		.ops = &mv88e6141_ops,
 	},
 
@@ -5965,6 +5968,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
+		.num_tx_queues = 4,
 		.ops = &mv88e6172_ops,
 	},
 
@@ -6016,6 +6020,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
+		.num_tx_queues = 4,
 		.ops = &mv88e6176_ops,
 	},
 
@@ -6063,6 +6068,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.atu_move_port_mask = 0x1f,
+		.num_tx_queues = 8,
 		.ops = &mv88e6190_ops,
 	},
 
@@ -6088,6 +6094,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.atu_move_port_mask = 0x1f,
 		.pvt = true,
 		.multi_chip = true,
+		.num_tx_queues = 8,
 		.ops = &mv88e6190x_ops,
 	},
 
@@ -6217,6 +6224,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.num_tx_queues = 4,
 		.ops = &mv88e6240_ops,
 	},
 
@@ -6264,6 +6272,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.ptp_support = true,
+		.num_tx_queues = 8,
 		.ops = &mv88e6290_ops,
 	},
 
@@ -6292,6 +6301,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.num_tx_queues = 4,
 		.ops = &mv88e6320_ops,
 	},
 
@@ -6320,6 +6330,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.num_tx_queues = 4,
 		.ops = &mv88e6321_ops,
 	},
 
@@ -6347,6 +6358,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.num_tx_queues = 4,
 		.ops = &mv88e6341_ops,
 	},
 
@@ -6424,6 +6436,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.num_tx_queues = 4,
 		.ops = &mv88e6352_ops,
 	},
 	[MV88E6361] = {
@@ -6477,6 +6490,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED,
 		.ptp_support = true,
+		.num_tx_queues = 8,
 		.ops = &mv88e6390_ops,
 	},
 	[MV88E6390X] = {
@@ -6503,6 +6517,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED,
 		.ptp_support = true,
+		.num_tx_queues = 8,
 		.ops = &mv88e6390x_ops,
 	},
 
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 2f211e55cb47b..b861486a7065e 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -174,6 +174,9 @@ struct mv88e6xxx_info {
 	/* Supports PTP */
 	bool ptp_support;
 
+	/* Number of 802.1Qav TX queues */
+	u8 num_tx_queues;
+
 	/* Internal PHY start index. 0 means that internal PHYs range starts at
 	 * port 0, 1 means internal PHYs range starts at port 1, etc
 	 */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter
  2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
  2025-09-27  7:07 ` [RFC net-next 1/5] net: dsa: mv88e6xxx: add num_tx_queues to chip info structure Luke Howard
@ 2025-09-27  7:07 ` Luke Howard
  2025-09-27 14:29   ` Andrew Lunn
  2025-09-27  7:07 ` [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support Luke Howard
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

Add accessors for the MACAVB bit, which controls whether certain ATU bits cause
the entry to be interpreted as AVB or NRL (non-rate-limiting) entries. This is
necessary on switches such as the 88E6352 and 88E6240 that support both AVB and
NRL ATU entries.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/global1.h     |  2 ++
 drivers/net/dsa/mv88e6xxx/global1_atu.c | 17 +++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 3dbb7a1b8fe11..74be4c485ab10 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -112,6 +112,7 @@
 /* Offset 0x0A: ATU Control Register */
 #define MV88E6XXX_G1_ATU_CTL		0x0a
 #define MV88E6XXX_G1_ATU_CTL_LEARN2ALL	0x0008
+#define MV88E6XXX_G1_ATU_CTL_MAC_AVB	0x8000
 #define MV88E6161_G1_ATU_CTL_HASH_MASK	0x0003
 
 /* Offset 0x0B: ATU Operation Register */
@@ -322,6 +323,7 @@ int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
 int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
 
 int mv88e6xxx_g1_atu_set_learn2all(struct mv88e6xxx_chip *chip, bool learn2all);
+int mv88e6xxx_g1_atu_set_mac_avb(struct mv88e6xxx_chip *chip, bool mac_avb);
 int mv88e6xxx_g1_atu_set_age_time(struct mv88e6xxx_chip *chip,
 				  unsigned int msecs);
 int mv88e6xxx_g1_atu_getnext(struct mv88e6xxx_chip *chip, u16 fid,
diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c
index c47f068f56b32..429a1ee44e47d 100644
--- a/drivers/net/dsa/mv88e6xxx/global1_atu.c
+++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c
@@ -41,6 +41,23 @@ int mv88e6xxx_g1_atu_set_learn2all(struct mv88e6xxx_chip *chip, bool learn2all)
 	return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL, val);
 }
 
+int mv88e6xxx_g1_atu_set_mac_avb(struct mv88e6xxx_chip *chip, bool mac_avb)
+{
+	u16 val;
+	int err;
+
+	err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val);
+	if (err)
+		return err;
+
+	if (mac_avb)
+		val |= MV88E6XXX_G1_ATU_CTL_MAC_AVB;
+	else
+		val &= ~MV88E6XXX_G1_ATU_CTL_MAC_AVB;
+
+	return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL, val);
+}
+
 int mv88e6xxx_g1_atu_set_age_time(struct mv88e6xxx_chip *chip,
 				  unsigned int msecs)
 {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
  2025-09-27  7:07 ` [RFC net-next 1/5] net: dsa: mv88e6xxx: add num_tx_queues to chip info structure Luke Howard
  2025-09-27  7:07 ` [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter Luke Howard
@ 2025-09-27  7:07 ` Luke Howard
  2025-09-27 15:12   ` Andrew Lunn
  2025-09-27 15:18   ` kernel test robot
  2025-09-27  7:07 ` [RFC net-next 4/5] net: dsa: mv88e6xxx: CBS support Luke Howard
  2025-09-27  7:07 ` [RFC net-next 5/5] dt-bindings: net: dsa: mv88e6xxx: add mv88e6xxx-avb-mode property Luke Howard
  4 siblings, 2 replies; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

Add support for MQPRIO TC 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 Class A.

A single Ethernet frame priority can be mapped to either AVB class.
"Legacy" (non-AVB) Ethernet frame priorities are distributed amongst the
remaining queues, per the MQPRIO policy.

Owing to hardware limitations, queue and frame priority policy is
per-switch, not per-port; HW offload can only be enabled across multiple
ports if the policy on each enabled port is the same.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/Makefile      |   3 +-
 drivers/net/dsa/mv88e6xxx/avb.c         | 550 ++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/avb.h         | 211 +++++++++
 drivers/net/dsa/mv88e6xxx/chip.c        | 266 +++++++++++-
 drivers/net/dsa/mv88e6xxx/chip.h        |  76 ++++
 drivers/net/dsa/mv88e6xxx/global1.c     |   9 +-
 drivers/net/dsa/mv88e6xxx/global1.h     |  45 +-
 drivers/net/dsa/mv88e6xxx/global2.h     |  14 +-
 drivers/net/dsa/mv88e6xxx/global2_avb.c | 205 ++++++++-
 drivers/net/dsa/mv88e6xxx/port.c        |   9 +
 drivers/net/dsa/mv88e6xxx/port.h        |   2 +
 include/linux/platform_data/mv88e6xxx.h |   1 +
 12 files changed, 1370 insertions(+), 21 deletions(-)
 create mode 100644 drivers/net/dsa/mv88e6xxx/avb.c
 create mode 100644 drivers/net/dsa/mv88e6xxx/avb.h

diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile
index dd961081d6313..5271bdebe171d 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..361e7ff821567
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.c
@@ -0,0 +1,550 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch AVB support
+ *
+ * Copyright (c) 2008 Marvell Semiconductor
+ *
+ * Copyright (c) 2024 PADL Software Pty Ltd
+ */
+
+#include <linux/dcbnl.h> /* for IEEE_8021Q_MAX_PRIORITIES */
+
+#include "avb.h"
+#include "chip.h"
+#include "global1.h"
+#include "global2.h"
+#include "port.h"
+
+/* AVB operation wrappers */
+
+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_avb_read(struct mv88e6xxx_chip *chip, int addr,
+			      u16 *data, int len)
+{
+	if (!chip->info->ops->avb_ops->avb_read)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->avb_read(chip, addr, data, len);
+}
+
+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);
+}
+
+/* 802.1Qav operation wrappers */
+
+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_port_qav_read(struct mv88e6xxx_chip *chip, int port,
+				   int addr, u16 *data, int len)
+{
+	if (!chip->info->ops->avb_ops->port_qav_read)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->port_qav_read(chip, port, addr, data, len);
+}
+
+static int mv88e6xxx_port_qav_write(struct mv88e6xxx_chip *chip, int port,
+				    int addr, u16 data)
+{
+	if (!chip->info->ops->avb_ops->port_qav_write)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->avb_ops->port_qav_write(chip, port, addr, data);
+}
+
+static int mv88e6xxx_tc_enable(struct mv88e6xxx_chip *chip,
+			       const struct mv88e6xxx_avb_tc_policy *policy)
+{
+	if (!chip->info->ops->tc_ops->tc_enable)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->tc_ops->tc_enable(chip, policy);
+}
+
+static int mv88e6xxx_tc_disable(struct mv88e6xxx_chip *chip)
+{
+	if (!chip->info->ops->tc_ops->tc_disable)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->tc_ops->tc_disable(chip);
+}
+
+/* MQPRIO helpers */
+
+/* Set the AVB global policy limit registers. Caller must acquired register
+ * lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param hilimit	Maximum frame size allowed for AVB Class A frames
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
+{
+	u16 data;
+	int err;
+
+	if (hilimit > MV88E6XXX_AVB_CFG_HI_LIMIT_MASK)
+		return -EINVAL;
+
+	err = mv88e6xxx_avb_read(chip, MV88E6XXX_AVB_CFG_HI_LIMIT, &data, 1);
+	if (err)
+		return err;
+
+	data &= ~(MV88E6XXX_AVB_CFG_HI_LIMIT_MASK);
+	data |= MV88E6XXX_AVB_CFG_HI_LIMIT_SET(hilimit);
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_HI_LIMIT, hilimit);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/* Set the AVB global policy OUI filter registers. Caller must acquire register
+ * lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param addr		The AVB OUI to load
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+static int mv88e6xxx_avb_set_oui(struct mv88e6xxx_chip *chip,
+				 const unsigned char *addr)
+{
+	u16 reg;
+	int err;
+
+	reg = (addr[0] << 8) | addr[1];
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_OUI_HI, reg);
+	if (err)
+		return err;
+
+	reg = (addr[2] << 8);
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_OUI_LO, reg);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/* Set the global isochronous queue pointer threshold. Caller must acquire
+ * register lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param threshold	Total number of pointers reserved for isochronous streams
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+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);
+
+	err = mv88e6xxx_qav_write(chip, MV88E6XXX_QAV_CFG, data);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/* Map the global AVB mode to a port AVB mode.
+ *
+ * @param chip		Marvell switch chip instance
+ *
+ * @return		A MV88E6XXX_PORT_AVB_CFG_AVB_MODE_XXX bitmask
+ */
+static u16 mv88e6xxx_avb_get_cfg_avb_mode(struct mv88e6xxx_chip *chip)
+{
+	u16 cfg;
+
+	switch (chip->avb_tc_policy.mode) {
+	case MV88E6XXX_AVB_MODE_STANDARD:
+		cfg = MV88E6XXX_PORT_AVB_CFG_AVB_MODE_STANDARD;
+		break;
+	case MV88E6XXX_AVB_MODE_ENHANCED:
+		cfg = MV88E6XXX_PORT_AVB_CFG_AVB_MODE_ENHANCED;
+		break;
+	case MV88E6XXX_AVB_MODE_SECURE:
+		cfg = MV88E6XXX_PORT_AVB_CFG_AVB_MODE_SECURE;
+		break;
+	default:
+		cfg = MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY;
+		break;
+	}
+
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_ENHANCED) {
+		cfg |= MV88E6XXX_PORT_AVB_CFG_AVB_FILTER_BAD_AVB |
+		       MV88E6XXX_PORT_AVB_CFG_AVB_DISCARD_BAD;
+	}
+
+	return cfg;
+}
+
+/* Enable or disable a port for AVB. Caller must acquire register lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param port		Switch port
+ * @param enable	If true, will enable AVB queues on this port.
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+static int mv88e6xxx_avb_set_port_avb_mode(struct mv88e6xxx_chip *chip,
+					   int port, bool enable)
+{
+	u16 cfg;
+
+	if (enable)
+		cfg = mv88e6xxx_avb_get_cfg_avb_mode(chip);
+	else
+		cfg = MV88E6XXX_PORT_AVB_CFG_AVB_MODE_LEGACY;
+
+	return mv88e6xxx_port_avb_write(chip, port, MV88E6XXX_PORT_AVB_CFG, cfg);
+}
+
+static int mv88e6xxx_avb_set_avb_mode(struct mv88e6xxx_chip *chip, bool enable)
+{
+	int port, err;
+
+	for (port = 0, err = 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, enable);
+		if (err)
+			break;
+	}
+
+	if (err)
+		return err;
+
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_SECURE) {
+		static const u8 zero_addr[ETH_ALEN] = {};
+		const u8 *addr;
+
+		addr = enable ? eth_maap_mcast_addr_base : zero_addr;
+
+		err = mv88e6xxx_avb_set_oui(chip, addr);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+int mv88e6xxx_avb_tc_enable(struct mv88e6xxx_chip *chip,
+			    const struct mv88e6xxx_avb_tc_policy *policy)
+{
+	int err;
+
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_ENHANCED) {
+		/* interpret AVB_NRL bits in ATU as AVB entries */
+		err = mv88e6xxx_g1_atu_set_mac_avb(chip, true);
+		if (err)
+			return err;
+	}
+
+	err = mv88e6xxx_qav_set_iso_ptr(chip, mv88e6xxx_num_ports(chip) << 6);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_tc_enable(chip, policy);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_avb_set_avb_mode(chip, true);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+int mv88e6xxx_avb_tc_disable(struct mv88e6xxx_chip *chip)
+{
+	int err;
+
+	err = mv88e6xxx_avb_set_avb_mode(chip, false);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_tc_disable(chip);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_qav_set_iso_ptr(chip, 0);
+	if (err)
+		return err;
+
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_ENHANCED) {
+		/* don't interpret AVB NRL bits in ATU as AVB entries */
+		err = mv88e6xxx_g1_atu_set_mac_avb(chip, false);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Assign FPri to QPri mappings for each traffic class
+ *
+ * @param chip		Marvell switch chip instance
+ * @param policy	AVB policy settings
+ * @param map		Callback for setting individual FPri:QPri mapping
+ * @param context	Opaque context passed to callback function
+ *
+ * @return		0 on success, or error returned by callback
+ */
+static int mv88e6xxx_qav_assign_qpri(struct mv88e6xxx_chip *chip,
+				     const struct mv88e6xxx_avb_tc_policy *policy,
+				     int (*map)(u8 fpri, u8 qpri, void *context),
+				     void *context)
+{
+	int tc0_qcount, tc0_base_qpri;
+	size_t tc0_fpri_per_qpri;
+	int err, fpri;
+
+	tc0_base_qpri = policy->map[MV88E6XXX_AVB_TC_LEGACY].qpri;
+	tc0_fpri_per_qpri =
+		DIV_ROUND_UP(IEEE_8021Q_MAX_PRIORITIES - 2,
+			     policy->map[MV88E6XXX_AVB_TC_LEGACY].count);
+
+	/* Match TC1/TC2 (AVB) FPri to QPri mappings to avoid needing to
+	 * configure legacy AVB registers, which map non-AVB frame FPri/QPris
+	 * to non-conflicting values.
+	 *
+	 * Distribute TC0 (non-AVB) queues amongst remaining FPris.
+	 */
+	for (fpri = 0, tc0_qcount = 0; fpri < IEEE_8021Q_MAX_PRIORITIES; fpri++) {
+		if (policy->map[MV88E6XXX_AVB_TC_LO].fpri == fpri) {
+			err = map(fpri, policy->map[MV88E6XXX_AVB_TC_LO].qpri, context);
+		} else if (policy->map[MV88E6XXX_AVB_TC_HI].fpri == fpri) {
+			err = map(fpri, policy->map[MV88E6XXX_AVB_TC_HI].qpri, context);
+		} else {
+			int qpri = tc0_base_qpri + (tc0_qcount++ / tc0_fpri_per_qpri);
+
+			err = map(fpri, qpri, context);
+		}
+
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+/* Family-specific 802.1Qav support */
+
+static inline u16 mv88e6352_avb_pri_map_to_reg(const struct mv88e6xxx_avb_priority_map map[])
+{
+	return MV88E6352_AVB_CFG_AVB_HI_FPRI_SET(map[MV88E6XXX_AVB_TC_HI].fpri) |
+		MV88E6352_AVB_CFG_AVB_HI_QPRI_SET(map[MV88E6XXX_AVB_TC_HI].qpri) |
+		MV88E6352_AVB_CFG_AVB_LO_FPRI_SET(map[MV88E6XXX_AVB_TC_LO].fpri) |
+		MV88E6352_AVB_CFG_AVB_LO_QPRI_SET(map[MV88E6XXX_AVB_TC_LO].qpri);
+}
+
+static int mv88e6352_qav_map_fpri_qpri(u8 fpri, u8 qpri, void *reg)
+{
+	mv88e6352_g1_ieee_pri_set(fpri, qpri, (u16 *)reg);
+	return 0;
+}
+
+static int mv88e6352_tc_enable(struct mv88e6xxx_chip *chip,
+			       const struct mv88e6xxx_avb_tc_policy *policy)
+{
+	u16 reg = 0;
+	int err;
+	int tc;
+
+	/* Validate TC to QPri mapping */
+	for (tc = MV88E6XXX_AVB_TC_LO; tc <= MV88E6XXX_AVB_TC_HI; tc++) {
+		if (policy->map[tc].qpri < MV88E6352_AVB_QUEUE_MIN(tc) ||
+		    policy->map[tc].qpri > MV88E6352_AVB_QUEUE_MAX(tc)) {
+			dev_err(chip->dev, "%s: bad QPri %d for TC %d\n",
+				__func__, policy->map[tc].qpri, tc);
+			return -EOPNOTSUPP;
+		}
+	}
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+				  mv88e6352_avb_pri_map_to_reg(policy->map));
+	if (err)
+		return err;
+
+	err = mv88e6xxx_qav_assign_qpri(chip, policy, mv88e6352_qav_map_fpri_qpri, &reg);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_g1_set_ieee_pri_map(chip, reg);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct mv88e6xxx_avb_priority_map
+mv88e6352_init_avb_pri_map[MV88E6XXX_AVB_TC_MAX + 1] = {
+	[MV88E6XXX_AVB_TC_LO] = {
+		/* VI, queue 2 */
+		.fpri = 0x4,
+		.qpri = 0x2
+	},
+	[MV88E6XXX_AVB_TC_HI] = {
+		/* VO, queue 3 */
+		.fpri = 0x5,
+		.qpri = 0x3
+	},
+};
+
+static int mv88e6352_tc_disable(struct mv88e6xxx_chip *chip)
+{
+	int err;
+
+	err = mv88e6250_g1_ieee_pri_map(chip);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+				  mv88e6352_avb_pri_map_to_reg(mv88e6352_init_avb_pri_map));
+	if (err)
+		return err;
+
+	return 0;
+}
+
+const struct mv88e6xxx_tc_ops mv88e6341_tc_ops = {
+	.tc_enable		= mv88e6352_tc_enable,
+	.tc_disable		= mv88e6352_tc_disable,
+};
+
+const struct mv88e6xxx_tc_ops mv88e6352_tc_ops = {
+	.tc_enable		= mv88e6352_tc_enable,
+	.tc_disable		= mv88e6352_tc_disable,
+};
+
+static inline u16 mv88e6390_avb_pri_map_to_reg(const struct mv88e6xxx_avb_priority_map map[])
+{
+	return MV88E6390_AVB_CFG_AVB_HI_FPRI_SET(map[MV88E6XXX_AVB_TC_HI].fpri) |
+		MV88E6390_AVB_CFG_AVB_HI_QPRI_SET(map[MV88E6XXX_AVB_TC_HI].qpri) |
+		MV88E6390_AVB_CFG_AVB_LO_FPRI_SET(map[MV88E6XXX_AVB_TC_LO].fpri) |
+		MV88E6390_AVB_CFG_AVB_LO_QPRI_SET(map[MV88E6XXX_AVB_TC_LO].qpri);
+}
+
+static int mv88e6390_qav_map_fpri_qpri(u8 fpri, u8 qpri, void *context)
+{
+	int err, port;
+	struct mv88e6xxx_chip *chip = (struct mv88e6xxx_chip *)context;
+
+	for (port = 0, err = 0; port < mv88e6xxx_num_ports(chip); port++) {
+		if (!dsa_is_user_port(chip->ds, port))
+			continue;
+
+		err = mv88e6390_port_set_ieeepmt_ingress_pcp(chip, port, fpri,
+							     fpri, qpri);
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+static int mv88e6390_tc_enable(struct mv88e6xxx_chip *chip,
+			       const struct mv88e6xxx_avb_tc_policy *policy)
+{
+	int err;
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+				  mv88e6390_avb_pri_map_to_reg(policy->map));
+	if (err)
+		return err;
+
+	err = mv88e6xxx_qav_assign_qpri(chip, policy, mv88e6390_qav_map_fpri_qpri, chip);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct mv88e6xxx_avb_priority_map
+mv88e6390_init_avb_pri_map[MV88E6XXX_AVB_TC_MAX + 1] = {
+	[MV88E6XXX_AVB_TC_LO] = {
+		/* EE, queue 6 */
+		.fpri = 0x2,
+		.qpri = 0x6
+	},
+	[MV88E6XXX_AVB_TC_HI] = {
+		/* CA, queue 7 */
+		.fpri = 0x3,
+		.qpri = 0x7
+	},
+};
+
+static int mv88e6390_tc_disable(struct mv88e6xxx_chip *chip)
+{
+	int err, port;
+
+	for (port = 0, err = 0; port < mv88e6xxx_num_ports(chip); port++) {
+		if (!dsa_is_user_port(chip->ds, port))
+			continue;
+
+		err = mv88e6390_port_tag_remap(chip, port);
+		if (err)
+			break;
+	}
+
+	err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_AVB,
+				  mv88e6390_avb_pri_map_to_reg(mv88e6390_init_avb_pri_map));
+	if (err)
+		return err;
+
+	return err;
+}
+
+const struct mv88e6xxx_tc_ops mv88e6390_tc_ops = {
+	.tc_enable		= mv88e6390_tc_enable,
+	.tc_disable		= mv88e6390_tc_disable,
+};
diff --git a/drivers/net/dsa/mv88e6xxx/avb.h b/drivers/net/dsa/mv88e6xxx/avb.h
new file mode 100644
index 0000000000000..d049e30c5c0e2
--- /dev/null
+++ b/drivers/net/dsa/mv88e6xxx/avb.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Marvell 88E6xxx Switch PTP support
+ *
+ * Copyright (c) 2008 Marvell Semiconductor
+ *
+ * Copyright (c) 2024 PADL Software Pty Ltd
+ */
+
+#ifndef _MV88E6XXX_AVB_H
+#define _MV88E6XXX_AVB_H
+
+#include "chip.h"
+
+/* The Marvell 6352 and 6390 families support the Credit Based Shaper defined
+ * in 802.1Qav. (The 6390 family also supports 802.1Qbv but that is presently
+ * unimplemented.)
+ *
+ * On ingress, frame priority tags (PCP for L2) are mapped to an internal frame
+ * priority, or FPri. This mapping is per-port on all switches that support
+ * AVB. The 6352 family has a per-switch mapping between FPri and QPri (the TX
+ * queue), whereas this mapping on the 6390 family is per-port. Both families
+ * support per-port CBS policies.
+ *
+ * In addition to traffic shaping, the Marvell switches also support a form of
+ * admission control, where true AVB frames are distinguished from other frames
+ * that share the same frame priority. This is done by flagging ATU entries
+ * with the ATU_DATA_STATE_{UC,MC}_STATIC_AVB_NRL flags. When the port is
+ * configured in Enhanced, rather than Standard, AVB mode, AVB frames will only
+ * be forwarded when the DA ATU entry has this bit set. Admission control would
+ * typically be managed by a user-space 802.1Q Stream Reservation Protocol
+ * (SRP) service. This, in combination with the global IsoPtrs register,
+ * ensures that AVB streams always have priority over other traffic. These
+ * features are necessary for Avnu certification.
+ *
+ * A final point is whether Linux TCs should be mapped to AVB classes or
+ * directly to queues. Enhanced AVB support above requires dedicated, global
+ * queues for Class A and B traffic, implying a mapping between TCs and AVB
+ * classes. Unfortunately this means that Marvell switches that support a
+ * larger number of TX queues (such as the 6390 family) must still funnel their
+ * MQPRIO policy through these three TCs. Further, this limits the 6390 family
+ * to per-switch MQPRIO policies whereas otherwise port-port policies could be
+ * supported.
+ *
+ * With that in mind, the current implementation has the following properties:
+ *
+ *	- there are only three traffic classes, hi (2), lo (1) and legacy (0),
+ *	  which correspond to AVB Class A, B, and non-AVB traffic
+ *
+ *	- only a single Ethernet frame priority can be mapped to either of the
+ *	  AVB traffic classes
+ *
+ *	- legacy Ethernet frame priorities are distributed amongst the
+ *	  remaining queues per the MQPRIO policy
+ *
+ *	- queue and frame priority policy is per-switch, not per-port, so
+ *	  HW offload can only be enabled across multiple ports if the policy
+ *	  on each port is the same
+ *
+ *	- on the 6352 family of switches, TC2 can only be in queue 2/3 and
+ *	  TC1 only in queue 1/2; this does not apply to the 6390 family
+ *
+ *	- because the Netlink API has no way to distinguish between FDB/MDB
+ *	  entries managed by SRP from those that are not, the
+ *	  "marvell,mv88e6xxx-avb-mode" device tree property controls whether
+ *	  a FDB or MDB entry is required in order for AVB frames to egress.
+ *	  To avoid breaking static IP MDB entries, only multicast addresses
+ *	  with OUI prefix of 91:e0:ff (IEEE 1722 Annex D) will have the AVB
+ *	  flag set on their ATU entry.
+ */
+
+/* Global AVB registers */
+
+/* Offset 0x00: AVB Global Config */
+
+#define MV88E6XXX_AVB_CFG_AVB			0x00
+#define MV88E6XXX_AVB_CFG_LEGACY		0x04
+
+/* Common AVB Global Config */
+
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK	GENMASK(14, 12)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(p)	FIELD_GET(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_HI_FPRI_MASK, p)
+
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK	GENMASK(6, 4)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_GET(p)	FIELD_GET(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, p)
+#define MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_AVB_LO_FPRI_MASK, p)
+
+#define MV88E6XXX_AVB_CFG_HI_LIMIT		0x08 /* max frame size for Class A */
+#define MV88E6XXX_AVB_CFG_HI_LIMIT_MASK		GENMASK(10, 0)
+#define MV88E6XXX_AVB_CFG_HI_LIMIT_GET(p)	FIELD_GET(MV88E6XXX_AVB_CFG_HI_LIMIT_MASK, p)
+#define MV88E6XXX_AVB_CFG_HI_LIMIT_SET(p)	FIELD_PREP(MV88E6XXX_AVB_CFG_HI_LIMIT_MASK, p)
+
+#define MV88E6XXX_AVB_CFG_OUI_HI		0x0C
+#define MV88E6XXX_AVB_CFG_OUI_LO		0x0D
+
+/* 6352 Family AVB Global Config (4 TX queues) */
+
+#define MV88E6352_AVB_CFG_AVB_HI_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(p)
+#define MV88E6352_AVB_CFG_AVB_HI_FPRI_SET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(p)
+
+#define MV88E6352_AVB_CFG_AVB_HI_QPRI_MASK	GENMASK(9, 8)
+#define MV88E6352_AVB_CFG_AVB_HI_QPRI_GET(p)	FIELD_GET(MV88E6352_AVB_CFG_AVB_HI_QPRI_MASK, p)
+#define MV88E6352_AVB_CFG_AVB_HI_QPRI_SET(p)	FIELD_PREP(MV88E6352_AVB_CFG_AVB_HI_QPRI_MASK, p)
+
+#define MV88E6352_AVB_CFG_AVB_LO_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_LO_FPRI_GET(p)
+#define MV88E6352_AVB_CFG_AVB_LO_FPRI_SET(p)	MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(p)
+
+#define MV88E6352_AVB_CFG_AVB_LO_QPRI_MASK	GENMASK(1, 0)
+#define MV88E6352_AVB_CFG_AVB_LO_QPRI_GET(p)	FIELD_GET(MV88E6352_AVB_CFG_AVB_LO_QPRI_MASK, p)
+#define MV88E6352_AVB_CFG_AVB_LO_QPRI_SET(p)	FIELD_PREP(MV88E6352_AVB_CFG_AVB_LO_QPRI_MASK, p)
+
+/* 6390 Family AVB Global Config (8 TX queues) */
+
+#define MV88E6390_AVB_CFG_AVB_HI_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(p)
+#define MV88E6390_AVB_CFG_AVB_HI_FPRI_SET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_SET(p)
+
+#define MV88E6390_AVB_CFG_AVB_HI_QPRI_MASK	GENMASK(10, 8)
+#define MV88E6390_AVB_CFG_AVB_HI_QPRI_GET(p)	FIELD_GET(MV88E6390_AVB_CFG_AVB_HI_QPRI_MASK, p)
+#define MV88E6390_AVB_CFG_AVB_HI_QPRI_SET(p)	FIELD_PREP(MV88E6390_AVB_CFG_AVB_HI_QPRI_MASK, p)
+
+#define MV88E6390_AVB_CFG_AVB_LO_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_LO_FPRI_GET(p)
+#define MV88E6390_AVB_CFG_AVB_LO_FPRI_SET(p)	MV88E6XXX_AVB_CFG_AVB_LO_FPRI_SET(p)
+
+#define MV88E6390_AVB_CFG_AVB_LO_QPRI_MASK	GENMASK(2, 0)
+#define MV88E6390_AVB_CFG_AVB_LO_QPRI_GET(p)	FIELD_GET(MV88E6390_AVB_CFG_AVB_LO_QPRI_MASK, p)
+#define MV88E6390_AVB_CFG_AVB_LO_QPRI_SET(p)	FIELD_PREP(MV88E6390_AVB_CFG_AVB_LO_QPRI_MASK, p)
+
+#define MV88E6352_AVB_QUEUE_MIN(tc)		(tc)
+#define MV88E6352_AVB_QUEUE_MAX(tc)		((tc) + 1)
+
+/* 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
+
+/* action is mv88e6xxx_policy_action */
+#define MV88E6XXX_PORT_AVB_CFG_AVB_HI_POLICY_MASK	GENMASK(3, 2)
+#define MV88E6XXX_PORT_AVB_CFG_AVB_HI_POLICY_GET(p)	\
+	FIELD_GET(MV88E6XXX_PORT_AVB_CFG_AVB_HI_POLICY_MASK, p)
+#define MV88E6XXX_PORT_AVB_CFG_AVB_HI_POLICY_SET(p)	\
+	FIELD_PREP(MV88E6XXX_PORT_AVB_CFG_AVB_HI_POLICY_MASK, p)
+
+#define MV88E6XXX_PORT_AVB_CFG_AVB_LO_POLICY_MASK	GENMASK(1, 0)
+#define MV88E6XXX_PORT_AVB_CFG_AVB_LO_POLICY_GET(p)	\
+	FIELD_GET(MV88E6XXX_PORT_AVB_CFG_AVB_LO_POLICY_MASK, p)
+#define MV88E6XXX_PORT_AVB_CFG_AVB_LO_POLICY_SET(p)	\
+	FIELD_PREP(MV88E6XXX_PORT_AVB_CFG_AVB_LO_POLICY_MASK, p)
+
+/* Per-family 802.1Qav operation tables */
+extern const struct mv88e6xxx_tc_ops mv88e6341_tc_ops;
+extern const struct mv88e6xxx_tc_ops mv88e6352_tc_ops;
+extern const struct mv88e6xxx_tc_ops mv88e6390_tc_ops;
+
+/* Set AVB queue priority policy. Caller must acquire register lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param policy	policy settings to apply
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+int mv88e6xxx_avb_tc_enable(struct mv88e6xxx_chip *chip,
+			    const struct mv88e6xxx_avb_tc_policy *policy);
+
+/* Clear AVB queue priority policy. Caller must acquire register lock.
+ *
+ * @param chip		Marvell switch chip instance
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+int mv88e6xxx_avb_tc_disable(struct mv88e6xxx_chip *chip);
+
+/* The MAAP address range is 91:E0:F0:00:00:00 thru 91:E0:F0:00:FF:FF
+ * (IEEE 1722 Annex D)
+ */
+static const u8 eth_maap_mcast_addr_base[ETH_ALEN] __aligned(2) = {
+	0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00
+};
+
+static inline bool ether_addr_is_maap_mcast(const u8 *addr)
+{
+	u8 mask[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 };
+
+	return ether_addr_equal_masked(addr, eth_maap_mcast_addr_base, mask);
+}
+
+#endif /* _MV88E6XXX_AVB_H */
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 42189aeb9aec0..6ba2179d1c4ee 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -11,6 +11,7 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/dcbnl.h>
 #include <linux/delay.h>
 #include <linux/dsa/mv88e6xxx.h>
 #include <linux/etherdevice.h>
@@ -33,6 +34,7 @@
 #include <linux/phylink.h>
 #include <net/dsa.h>
 
+#include "avb.h"
 #include "chip.h"
 #include "devlink.h"
 #include "global1.h"
@@ -2292,7 +2294,8 @@ static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
 		if (!entry.portvec)
 			entry.state = 0;
 	} else {
-		if (state == MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC)
+		if (state == MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC ||
+		    state == MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL)
 			entry.portvec = BIT(port);
 		else
 			entry.portvec |= BIT(port);
@@ -2875,11 +2878,17 @@ static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
 				  struct dsa_db db)
 {
 	struct mv88e6xxx_chip *chip = ds->priv;
+	u8 state;
 	int err;
 
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_ENHANCED &&
+	    chip->avb_tc_policy.port_mask & BIT(port))
+		state = MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL;
+	else
+		state = MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC;
+
 	mv88e6xxx_reg_lock(chip);
-	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
-					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
+	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, state);
 	if (err)
 		goto out;
 
@@ -2932,7 +2941,9 @@ static int mv88e6xxx_port_db_dump_fid(struct mv88e6xxx_chip *chip,
 			continue;
 
 		is_static = (addr.state ==
-			     MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
+			     MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC ||
+			     addr.state ==
+			     MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL);
 		err = cb(addr.mac, vid, is_static, data);
 		if (err)
 			return err;
@@ -4529,6 +4540,7 @@ static const struct mv88e6xxx_ops mv88e6161_ops = {
 	.ptp_ops = &mv88e6165_ptp_ops,
 	.phylink_get_caps = mv88e6185_phylink_get_caps,
 	.set_max_frame_size = mv88e6185_g1_set_max_frame_size,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6165_ops = {
@@ -4566,6 +4578,7 @@ static const struct mv88e6xxx_ops mv88e6165_ops = {
 	.avb_ops = &mv88e6165_avb_ops,
 	.ptp_ops = &mv88e6165_ptp_ops,
 	.phylink_get_caps = mv88e6185_phylink_get_caps,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6171_ops = {
@@ -4987,10 +5000,11 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
 	.serdes_get_stats = mv88e6390_serdes_get_stats,
 	.serdes_get_regs_len = mv88e6390_serdes_get_regs_len,
 	.serdes_get_regs = mv88e6390_serdes_get_regs,
-	.avb_ops = &mv88e6390_avb_ops,
+	.avb_ops = &mv88e6352_avb_ops,
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e6390_phylink_get_caps,
 	.pcs_ops = &mv88e6390_pcs_ops,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6240_ops = {
@@ -5052,6 +5066,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e6352_phylink_get_caps,
 	.pcs_ops = &mv88e6352_pcs_ops,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6250_ops = {
@@ -5097,6 +5112,7 @@ static const struct mv88e6xxx_ops mv88e6250_ops = {
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e6250_phylink_get_caps,
 	.set_max_frame_size = mv88e6185_g1_set_max_frame_size,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6290_ops = {
@@ -5158,6 +5174,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
 	.ptp_ops = &mv88e6390_ptp_ops,
 	.phylink_get_caps = mv88e6390_phylink_get_caps,
 	.pcs_ops = &mv88e6390_pcs_ops,
+	.tc_ops = &mv88e6390_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6320_ops = {
@@ -5211,6 +5228,7 @@ static const struct mv88e6xxx_ops mv88e6320_ops = {
 	.avb_ops = &mv88e6352_avb_ops,
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e632x_phylink_get_caps,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6321_ops = {
@@ -5263,6 +5281,7 @@ static const struct mv88e6xxx_ops mv88e6321_ops = {
 	.avb_ops = &mv88e6352_avb_ops,
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e632x_phylink_get_caps,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6341_ops = {
@@ -5328,6 +5347,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
 	.serdes_get_regs = mv88e6390_serdes_get_regs,
 	.phylink_get_caps = mv88e6341_phylink_get_caps,
 	.pcs_ops = &mv88e6390_pcs_ops,
+	.tc_ops = &mv88e6341_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6350_ops = {
@@ -5422,6 +5442,7 @@ static const struct mv88e6xxx_ops mv88e6351_ops = {
 	.avb_ops = &mv88e6352_avb_ops,
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e6351_phylink_get_caps,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6352_ops = {
@@ -5486,6 +5507,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
 	.serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude,
 	.phylink_get_caps = mv88e6352_phylink_get_caps,
 	.pcs_ops = &mv88e6352_pcs_ops,
+	.tc_ops = &mv88e6352_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6390_ops = {
@@ -5550,6 +5572,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
 	.serdes_get_regs = mv88e6390_serdes_get_regs,
 	.phylink_get_caps = mv88e6390_phylink_get_caps,
 	.pcs_ops = &mv88e6390_pcs_ops,
+	.tc_ops = &mv88e6390_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6390x_ops = {
@@ -5614,6 +5637,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
 	.ptp_ops = &mv88e6390_ptp_ops,
 	.phylink_get_caps = mv88e6390x_phylink_get_caps,
 	.pcs_ops = &mv88e6390_pcs_ops,
+	.tc_ops = &mv88e6390_tc_ops,
 };
 
 static const struct mv88e6xxx_ops mv88e6393x_ops = {
@@ -5677,6 +5701,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.ptp_ops = &mv88e6352_ptp_ops,
 	.phylink_get_caps = mv88e6393x_phylink_get_caps,
 	.pcs_ops = &mv88e6393x_pcs_ops,
+	.tc_ops = &mv88e6390_tc_ops,
 };
 
 static const struct mv88e6xxx_info mv88e6xxx_table[] = {
@@ -6697,11 +6722,24 @@ static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port,
 				  struct dsa_db db)
 {
 	struct mv88e6xxx_chip *chip = ds->priv;
+	u8 state;
 	int err;
 
+	/* In enhanced and secure modes, the switch will drop packets that end
+	 * up with an AVB frame priority but that do not have an ATU entry with
+	 * the AVB flag set. We make a slightly abstraction violating check for
+	 * MAAP multicast addresses (IEEE 1722 Annex D) to allow coexistence
+	 * with static IP MDB entries for packets without AVB frame priorities.
+	 */
+	if (chip->avb_tc_policy.mode >= MV88E6XXX_AVB_MODE_ENHANCED &&
+	    (chip->avb_tc_policy.port_mask & BIT(port)) &&
+	    ether_addr_is_maap_mcast(mdb->addr))
+		state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL;
+	else
+		state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC;
+
 	mv88e6xxx_reg_lock(chip);
-	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid,
-					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC);
+	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, state);
 	if (err)
 		goto out;
 
@@ -6797,6 +6835,193 @@ static void mv88e6xxx_port_mirror_del(struct dsa_switch *ds, int port,
 	mutex_unlock(&chip->reg_lock);
 }
 
+static int mv88e6xxx_qos_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_qos_validate_mqprio(const struct device *dev,
+					 const struct mv88e6xxx_chip *chip,
+					 int port,
+					 const struct tc_mqprio_qopt_offload *mqprio,
+					 struct mv88e6xxx_avb_tc_policy *tcpol)
+{
+	const struct tc_mqprio_qopt *qopt = &mqprio->qopt;
+	struct netlink_ext_ack *extack = mqprio->extack;
+	u8 avb_tcpol_set = 0;
+	int tc, pri;
+
+	memset(tcpol, 0, sizeof(*tcpol));
+
+	if (qopt->hw != TC_MQPRIO_HW_OFFLOAD_TCS) {
+		NL_SET_ERR_MSG(extack, "only full TC hardware offload is supported");
+		return -EOPNOTSUPP;
+	} else if (mqprio->shaper != TC_MQPRIO_SHAPER_DCB) {
+		NL_SET_ERR_MSG(extack, "only DCB shaper is supported");
+		return -EOPNOTSUPP;
+	} else if (qopt->num_tc > MV88E6XXX_AVB_TC_MAX + 1) {
+		NL_SET_ERR_MSG_FMT(extack, "too many traffic classes: %d", qopt->num_tc);
+		return -EOPNOTSUPP;
+	}
+
+	if (qopt->num_tc == 0)
+		return 0;
+
+	/* Validate and map TC to TX queue */
+	for (tc = MV88E6XXX_AVB_TC_LEGACY; tc < qopt->num_tc; tc++) {
+		if (qopt->offset[tc] + qopt->count[tc] > chip->info->num_tx_queues) {
+			NL_SET_ERR_MSG_FMT(extack, "queue %d out of range",
+					   qopt->offset[tc] + qopt->count[tc] - 1);
+			return -EOPNOTSUPP;
+		}
+
+		if (tc == MV88E6XXX_AVB_TC_LEGACY) {
+			tcpol->map[tc].count = qopt->count[tc];
+		} else if (qopt->count[tc] != 1) {
+			NL_SET_ERR_MSG_FMT(extack, "only one queue supported for TC %d", tc);
+			return -EOPNOTSUPP;
+		}
+
+		tcpol->map[tc].qpri = qopt->offset[tc];
+	}
+
+	/* Validate and map priority to TC */
+	for (pri = 0; pri < IEEE_8021Q_MAX_PRIORITIES; pri++) {
+		tc = qopt->prio_tc_map[pri];
+
+		if (tc == MV88E6XXX_AVB_TC_LEGACY)
+			continue;
+
+		if (avb_tcpol_set & BIT(tc)) {
+			NL_SET_ERR_MSG_FMT(extack,
+					   "only one frame priority can be mapped to TC %d", tc);
+			return -EOPNOTSUPP;
+		}
+
+		avb_tcpol_set |= BIT(tc);
+		tcpol->map[tc].fpri = pri;
+	}
+
+	if (avb_tcpol_set != GENMASK(MV88E6XXX_AVB_TC_HI, MV88E6XXX_AVB_TC_LO)) {
+		NL_SET_ERR_MSG(extack,
+			       "both hi and lo priority TCs must have 802.1p priorities");
+		return -EOPNOTSUPP;
+	}
+
+	return qopt->num_tc;
+}
+
+static int mv88e6xxx_qos_port_mqprio(struct dsa_switch *ds, int port,
+				     struct tc_mqprio_qopt_offload *mqprio)
+{
+	struct netlink_ext_ack *extack = mqprio->extack;
+	struct mv88e6xxx_avb_tc_policy tcpol;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	struct net_device *user;
+	int err, num_tc, tc;
+	bool can_update_pol;
+
+	if (!dsa_is_user_port(ds, port))
+		return -EINVAL;
+
+	num_tc = mv88e6xxx_qos_validate_mqprio(ds->dev, chip, port, mqprio, &tcpol);
+	if (num_tc < 0)
+		return num_tc;
+
+	/* Update the kernel's view of the priority mapping policy, then update the
+	 * switch's.
+	 */
+	user = dsa_to_port(ds, port)->user;
+
+	err = netdev_set_num_tc(user, num_tc);
+	if (err)
+		goto err_reset_tc;
+
+	for (tc = 0; tc < num_tc; tc++) {
+		const struct tc_mqprio_qopt *qopt = &mqprio->qopt;
+
+		err = netdev_set_tc_queue(user, tc, 1, qopt->offset[tc]);
+		if (err)
+			goto err_reset_tc;
+	}
+
+	mutex_lock(&chip->reg_lock);
+
+	/* Update the actual priority mapping policy iff no policy has been set or the
+	 * only referant is the requesting port. Silently allow matching updates from
+	 * other ports. All other updates are rejected with -EOPNOTSUPP.
+	 */
+	can_update_pol = chip->avb_tc_policy.port_mask == 0 ||
+			 (hweight16(chip->avb_tc_policy.port_mask) == 1 &&
+			  ffs(chip->avb_tc_policy.port_mask) == port);
+
+	if (!can_update_pol &&
+	    num_tc > 0 &&
+	    memcmp(&tcpol.map, &chip->avb_tc_policy.map, sizeof(tcpol.map)) != 0) {
+		NL_SET_ERR_MSG(extack, "only a single AVB queue policy is supported per switch");
+		err = -EOPNOTSUPP;
+		goto err_unlock;
+	}
+
+	if (can_update_pol) {
+		err = num_tc > 0 ? mv88e6xxx_avb_tc_enable(chip, &tcpol)
+				 : mv88e6xxx_avb_tc_disable(chip);
+		if (err) {
+			NL_SET_ERR_MSG_FMT(extack, "failed to %s AVB queue policy: %d",
+					   num_tc > 0 ? "enable" : "disable", err);
+			goto err_unlock;
+		}
+
+		memcpy(&chip->avb_tc_policy.map, &tcpol.map, sizeof(tcpol.map));
+	}
+
+	if (num_tc)
+		chip->avb_tc_policy.port_mask |= BIT(port);
+	else
+		chip->avb_tc_policy.port_mask &= ~BIT(port);
+
+	mutex_unlock(&chip->reg_lock);
+
+	return 0;
+
+err_unlock:
+	mutex_unlock(&chip->reg_lock);
+
+err_reset_tc:
+	netdev_reset_tc(user);
+
+	return err;
+}
+
+static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
+				   enum tc_setup_type type,
+				   void *type_data)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+
+	if (!chip->info->ops->tc_ops)
+		return -EOPNOTSUPP;
+
+	switch (type) {
+	case TC_QUERY_CAPS:
+		return mv88e6xxx_qos_query_caps(type_data);
+	case TC_SETUP_QDISC_MQPRIO:
+		return mv88e6xxx_qos_port_mqprio(ds, port, type_data);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
 static int mv88e6xxx_port_pre_bridge_flags(struct dsa_switch *ds, int port,
 					   struct switchdev_brport_flags flags,
 					   struct netlink_ext_ack *extack)
@@ -7216,6 +7441,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.port_mdb_del		= mv88e6xxx_port_mdb_del,
 	.port_mirror_add	= mv88e6xxx_port_mirror_add,
 	.port_mirror_del	= mv88e6xxx_port_mirror_del,
+	.port_setup_tc		= mv88e6xxx_port_setup_tc,
 	.crosschip_bridge_join	= mv88e6xxx_crosschip_bridge_join,
 	.crosschip_bridge_leave	= mv88e6xxx_crosschip_bridge_leave,
 	.port_hwtstamp_set	= mv88e6xxx_port_hwtstamp_set,
@@ -7281,6 +7507,28 @@ static const void *pdata_device_get_match_data(struct device *dev)
 	return NULL;
 }
 
+static int mv88e6xxx_get_avb_mode(struct mv88e6xxx_chip *chip,
+				  enum mv88e6xxx_avb_mode *modep)
+{
+	struct dsa_mv88e6xxx_pdata *pdata = chip->dev->platform_data;
+	struct device_node *np = chip->dev->of_node;
+	int mode = MV88E6XXX_AVB_MODE_STANDARD;
+
+	if (np)
+		of_property_read_u32(np, "marvell,mv88e6xxx-avb-mode", &mode);
+	else if (pdata)
+		mode = pdata->avb_mode;
+
+	if (mode < MV88E6XXX_AVB_MODE_STANDARD ||
+	    mode > MV88E6XXX_AVB_MODE_SECURE) {
+		dev_err(chip->dev, "Invalid AVB mode %d\n", mode);
+		return -EINVAL;
+	}
+
+	*modep = mode;
+	return 0;
+}
+
 /* There is no suspend to RAM support at DSA level yet, the switch configuration
  * would be lost after a power cycle so prevent it to be suspended.
  */
@@ -7376,6 +7624,10 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev)
 			chip->eeprom_len = pdata->eeprom_len;
 	}
 
+	err = mv88e6xxx_get_avb_mode(chip, &chip->avb_tc_policy.mode);
+	if (err)
+		goto out;
+
 	mv88e6xxx_reg_lock(chip);
 	err = mv88e6xxx_switch_reset(chip);
 	mv88e6xxx_reg_unlock(chip);
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index b861486a7065e..71e536fbd2d24 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -19,6 +19,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
@@ -213,6 +214,7 @@ struct mv88e6xxx_avb_ops;
 struct mv88e6xxx_ptp_ops;
 struct mv88e6xxx_pcs_ops;
 struct mv88e6xxx_cc_coeffs;
+struct mv88e6xxx_tc_ops;
 
 struct mv88e6xxx_irq {
 	u16 masked;
@@ -247,6 +249,46 @@ struct mv88e6xxx_port_hwtstamp {
 	struct kernel_hwtstamp_config tstamp_config;
 };
 
+enum mv88e6xxx_avb_mode {
+	MV88E6XXX_AVB_MODE_STANDARD = 0,
+	MV88E6XXX_AVB_MODE_ENHANCED,
+	MV88E6XXX_AVB_MODE_SECURE,
+};
+
+enum mv88e6xxx_avb_tc {
+	/* Non-AVB traffic */
+	MV88E6XXX_AVB_TC_LEGACY = 0,
+	/* Higher latency, low priority AVB flows (class B) */
+	MV88E6XXX_AVB_TC_LO = 1,
+	/* Low latency, high priority AVB flows (class A) */
+	MV88E6XXX_AVB_TC_HI = 2,
+	MV88E6XXX_AVB_TC_MAX = MV88E6XXX_AVB_TC_HI
+};
+
+struct mv88e6xxx_avb_priority_map {
+	union {
+		/* Number of queues, for MV88E6XXX_AVB_TC_LEGACY */
+		u8 count;
+
+		/* Frame priority, for MV88E6XXX_AVB_TC_LO/HI */
+		u8 fpri;
+	};
+
+	/* Queue priority*/
+	u8 qpri;
+};
+
+struct mv88e6xxx_avb_tc_policy {
+	/* AVB mode */
+	enum mv88e6xxx_avb_mode mode;
+
+	/* Ports participating in HW offload priority mapping */
+	u16 port_mask;
+
+	/* Map from 802.1p frame priority to queue */
+	struct mv88e6xxx_avb_priority_map map[MV88E6XXX_AVB_TC_MAX + 1];
+};
+
 enum mv88e6xxx_policy_mapping {
 	MV88E6XXX_POLICY_MAPPING_DA,
 	MV88E6XXX_POLICY_MAPPING_SA,
@@ -433,6 +475,9 @@ struct mv88e6xxx_chip {
 	int egress_dest_port;
 	int ingress_dest_port;
 
+	/* Global AVB queue policy resources */
+	struct mv88e6xxx_avb_tc_policy avb_tc_policy;
+
 	/* Per-port timestamping resources. */
 	struct mv88e6xxx_port_hwtstamp port_hwtstamp[DSA_MAX_PORTS];
 
@@ -685,6 +730,9 @@ struct mv88e6xxx_ops {
 
 	/* Max Frame Size */
 	int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu);
+
+	/* Traffic control / Qdisc operations */
+	const struct mv88e6xxx_tc_ops *tc_ops;
 };
 
 struct mv88e6xxx_irq_ops {
@@ -730,6 +778,28 @@ struct mv88e6xxx_avb_ops {
 	int (*tai_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
 			int len);
 	int (*tai_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_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
+			int len);
+	int (*avb_write)(struct mv88e6xxx_chip *chip, 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 Class Shaping and Pacing registers */
+	int (*port_qav_read)(struct mv88e6xxx_chip *chip, int port, int addr,
+			     u16 *data, int len);
+	int (*port_qav_write)(struct mv88e6xxx_chip *chip, int port, int addr,
+			      u16 data);
 };
 
 struct mv88e6xxx_ptp_ops {
@@ -759,6 +829,12 @@ struct mv88e6xxx_pcs_ops {
 
 };
 
+struct mv88e6xxx_tc_ops {
+	int (*tc_enable)(struct mv88e6xxx_chip *chip,
+			 const struct mv88e6xxx_avb_tc_policy *policy);
+	int (*tc_disable)(struct mv88e6xxx_chip *chip);
+};
+
 static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
 {
 	return chip->info->max_sid > 0 &&
diff --git a/drivers/net/dsa/mv88e6xxx/global1.c b/drivers/net/dsa/mv88e6xxx/global1.c
index 9820cd5967574..ce2c5cc3ea585 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.c
+++ b/drivers/net/dsa/mv88e6xxx/global1.c
@@ -356,16 +356,21 @@ int mv88e6085_g1_ip_pri_map(struct mv88e6xxx_chip *chip)
 
 /* Offset 0x18: IEEE-PRI Register */
 
+int mv88e6xxx_g1_set_ieee_pri_map(struct mv88e6xxx_chip *chip, u16 map)
+{
+	return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, map);
+}
+
 int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip)
 {
 	/* Reset the IEEE Tag priorities to defaults */
-	return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa41);
+	return mv88e6xxx_g1_set_ieee_pri_map(chip, 0xfa41);
 }
 
 int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip)
 {
 	/* Reset the IEEE Tag priorities to defaults */
-	return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa50);
+	return mv88e6xxx_g1_set_ieee_pri_map(chip, 0xfa50);
 }
 
 /* Offset 0x1a: Monitor Control */
diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
index 74be4c485ab10..3018901629fef 100644
--- a/drivers/net/dsa/mv88e6xxx/global1.h
+++ b/drivers/net/dsa/mv88e6xxx/global1.h
@@ -2,7 +2,7 @@
 /*
  * Marvell 88E6xxx Switch Global (1) Registers support
  *
- * Copyright (c) 2008 Marvell Semiconductor
+ * Copyright (c) 2008-2021 Marvell Semiconductor
  *
  * Copyright (c) 2016-2017 Savoir-faire Linux Inc.
  *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
@@ -62,6 +62,7 @@
 #define MV88E6185_G1_CTL1_MAX_FRAME_1632	0x0400
 #define MV88E6185_G1_CTL1_RELOAD_EEPROM		0x0200
 #define MV88E6393X_G1_CTL1_DEVICE2_EN		0x0200
+#define MV88E6XXX_G1_CTL1_AVB_EN		0x0100
 #define MV88E6XXX_G1_CTL1_DEVICE_EN		0x0080
 #define MV88E6XXX_G1_CTL1_STATS_DONE_EN		0x0040
 #define MV88E6XXX_G1_CTL1_VTU_PROBLEM_EN	0x0020
@@ -193,6 +194,47 @@
 /* Offset 0x18: IEEE-PRI Register */
 #define MV88E6XXX_G1_IEEE_PRI	0x18
 
+/* Switches supporting 4 TX queues pack FPri to QPri mappings into a single
+ * register value.
+ *
+ * Extract QPri for a given FPri from the provided register value.
+ *
+ * @param val	register value
+ * @param fpri	frame priority
+ *
+ * @return	queue priority
+ */
+static inline u16 mv88e6352_g1_ieee_pri_get(u16 val, u8 fpri)
+{
+	u16 mask;
+
+	fpri &= 0x7;
+	fpri <<= 1;
+	mask = GENMASK(fpri + 1, fpri);
+
+	return (val & mask) >> fpri;
+}
+
+/* Add a FPri to QPri mapping to the register value argument.
+ *
+ * @param fpri	frame priority
+ * @param qpri	queue priority
+ * @param reg	register value, should be 0 on first call
+ */
+static inline void mv88e6352_g1_ieee_pri_set(u8 fpri, u8 qpri, u16 *reg)
+{
+	u16 mask;
+
+	fpri &= 0x7;
+	fpri <<= 1;
+	mask = GENMASK(fpri + 1, fpri);
+
+	*reg &= ~(mask);
+	*reg |= (qpri & 0x3) << fpri;
+}
+
+#define MV88E6390_G1_IEEE_PRI_UPDATE	0x80
+
 /* Offset 0x19: Core Tag Type */
 #define MV88E6185_G1_CORE_TAG_TYPE	0x19
 
@@ -311,6 +353,7 @@ int mv88e6390_g1_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
 
 int mv88e6085_g1_ip_pri_map(struct mv88e6xxx_chip *chip);
 
+int mv88e6xxx_g1_set_ieee_pri_map(struct mv88e6xxx_chip *chip, u16 map);
 int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
 int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
 
diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h
index 82f9b410de0b8..7cebc2dc29db1 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.h
+++ b/drivers/net/dsa/mv88e6xxx/global2.h
@@ -176,15 +176,17 @@
 #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 MV88E6352_G2_AVB_CMD_BLOCK_PTP		0
-#define MV88E6352_G2_AVB_CMD_BLOCK_AVB		1
-#define MV88E6352_G2_AVB_CMD_BLOCK_QAV		2
-#define MV88E6352_G2_AVB_CMD_BLOCK_QVB		3
-#define MV88E6352_G2_AVB_CMD_BLOCK_MASK		0x00e0
-#define MV88E6352_G2_AVB_CMD_ADDR_MASK		0x001f
+#define MV88E6390_G2_AVB_CMD_PORT_AVBGLOBAL	0x1f
+#define MV88E6XXX_G2_AVB_CMD_BLOCK_PTP		0
+#define MV88E6XXX_G2_AVB_CMD_BLOCK_AVB		1
+#define MV88E6XXX_G2_AVB_CMD_BLOCK_QAV		2
+#define MV88E6XXX_G2_AVB_CMD_BLOCK_QVB		3
+#define MV88E6XXX_G2_AVB_CMD_BLOCK_MASK		0x00e0
+#define MV88E6XXX_G2_AVB_CMD_ADDR_MASK		0x001f
 
 /* Offset 0x17: AVB Data Register */
 #define MV88E6352_G2_AVB_DATA		0x17
diff --git a/drivers/net/dsa/mv88e6xxx/global2_avb.c b/drivers/net/dsa/mv88e6xxx/global2_avb.c
index 657783e043ff1..e9f75f7d3b3a2 100644
--- a/drivers/net/dsa/mv88e6xxx/global2_avb.c
+++ b/drivers/net/dsa/mv88e6xxx/global2_avb.c
@@ -14,6 +14,7 @@
 #include <linux/bitfield.h>
 
 #include "global2.h"
+#include "avb.h"
 
 /* Offset 0x16: AVB Command Register
  * Offset 0x17: AVB Data Register
@@ -95,7 +96,7 @@ static int mv88e6352_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
 {
 	u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
 				 MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
-		     (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) |
+		     (port << 8) | (MV88E6XXX_G2_AVB_CMD_BLOCK_PTP << 5) |
 		     addr;
 
 	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
@@ -105,7 +106,7 @@ static int mv88e6352_g2_avb_port_ptp_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_PTP << 5) | addr;
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_PTP << 5) | addr;
 
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
@@ -142,6 +143,92 @@ static int mv88e6352_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
 					addr, 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) | (MV88E6XXX_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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6352_g2_avb_avb_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) |
+		     (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, 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) |
+		     (MV88E6XXX_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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6352_g2_avb_port_qav_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) | (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6352_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
+					   int port, int addr, u16 data)
+{
+	u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE | (port << 8) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 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,
@@ -149,6 +236,14 @@ const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
 	.ptp_write		= mv88e6352_g2_avb_ptp_write,
 	.tai_read		= mv88e6352_g2_avb_tai_read,
 	.tai_write		= mv88e6352_g2_avb_tai_write,
+	.port_avb_read		= mv88e6352_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6352_g2_avb_port_avb_write,
+	.avb_read		= mv88e6352_g2_avb_avb_read,
+	.avb_write		= mv88e6352_g2_avb_avb_write,
+	.qav_read		= mv88e6352_g2_avb_qav_read,
+	.qav_write		= mv88e6352_g2_avb_qav_write,
+	.port_qav_read		= mv88e6352_g2_avb_port_qav_read,
+	.port_qav_write		= mv88e6352_g2_avb_port_qav_write,
 };
 
 static int mv88e6165_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
@@ -174,6 +269,14 @@ const struct mv88e6xxx_avb_ops mv88e6165_avb_ops = {
 	.ptp_write		= mv88e6352_g2_avb_ptp_write,
 	.tai_read		= mv88e6165_g2_avb_tai_read,
 	.tai_write		= mv88e6165_g2_avb_tai_write,
+	.port_avb_read		= mv88e6352_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6352_g2_avb_port_avb_write,
+	.avb_read		= mv88e6352_g2_avb_avb_read,
+	.avb_write		= mv88e6352_g2_avb_avb_write,
+	.qav_read		= mv88e6352_g2_avb_qav_read,
+	.qav_write		= mv88e6352_g2_avb_qav_write,
+	.port_qav_read		= mv88e6352_g2_avb_port_qav_read,
+	.port_qav_write		= mv88e6352_g2_avb_port_qav_write,
 };
 
 static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
@@ -182,7 +285,7 @@ static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
 {
 	u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
 				 MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
-		     (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) |
+		     (port << 8) | (MV88E6XXX_G2_AVB_CMD_BLOCK_PTP << 5) |
 		     addr;
 
 	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
@@ -192,7 +295,7 @@ static int mv88e6390_g2_avb_port_ptp_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_PTP << 5) | addr;
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_PTP << 5) | addr;
 
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
@@ -229,6 +332,92 @@ static int mv88e6390_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
 					addr, 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) | (MV88E6XXX_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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6390_g2_avb_avb_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) |
+		     (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_AVB << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, 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) |
+		     (MV88E6XXX_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) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
+static int mv88e6390_g2_avb_port_qav_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) | (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 5) |
+		     addr;
+
+	return mv88e6xxx_g2_avb_read(chip, readop, data, len);
+}
+
+static int mv88e6390_g2_avb_port_qav_write(struct mv88e6xxx_chip *chip,
+					   int port, int addr, u16 data)
+{
+	u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE | (port << 8) |
+		      (MV88E6XXX_G2_AVB_CMD_BLOCK_QAV << 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,
@@ -236,4 +425,12 @@ const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
 	.ptp_write		= mv88e6390_g2_avb_ptp_write,
 	.tai_read		= mv88e6390_g2_avb_tai_read,
 	.tai_write		= mv88e6390_g2_avb_tai_write,
+	.port_avb_read		= mv88e6390_g2_avb_port_avb_read,
+	.port_avb_write		= mv88e6390_g2_avb_port_avb_write,
+	.avb_read		= mv88e6390_g2_avb_avb_read,
+	.avb_write		= mv88e6390_g2_avb_avb_write,
+	.qav_read		= mv88e6390_g2_avb_qav_read,
+	.qav_write		= mv88e6390_g2_avb_qav_write,
+	.port_qav_read		= mv88e6390_g2_avb_port_qav_read,
+	.port_qav_write		= mv88e6390_g2_avb_port_qav_write,
 };
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index 66b1b72772810..db5cc28b8b701 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1605,6 +1605,15 @@ int mv88e6390_port_tag_remap(struct mv88e6xxx_chip *chip, int port)
 	return 0;
 }
 
+int mv88e6390_port_set_ieeepmt_ingress_pcp(struct mv88e6xxx_chip *chip, int port,
+					   u8 pcp, u8 fpri, u8 qpri)
+{
+	return mv88e6xxx_port_ieeepmt_write(chip, port,
+					    MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_INGRESS_PCP,
+					    pcp,
+					    (fpri | qpri << 4));
+}
+
 /* Offset 0x0E: Policy Control Register */
 
 static int
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index c1d2f99efb1c6..2d2dde9b45ede 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -527,6 +527,8 @@ int mv88e6xxx_port_set_8021q_mode(struct mv88e6xxx_chip *chip, int port,
 				  u16 mode);
 int mv88e6095_port_tag_remap(struct mv88e6xxx_chip *chip, int port);
 int mv88e6390_port_tag_remap(struct mv88e6xxx_chip *chip, int port);
+int mv88e6390_port_set_ieeepmt_ingress_pcp(struct mv88e6xxx_chip *chip, int port,
+					   u8 pcp, u8 fpri, u8 qpri);
 int mv88e6xxx_port_set_egress_mode(struct mv88e6xxx_chip *chip, int port,
 				   enum mv88e6xxx_egress_mode mode);
 int mv88e6085_port_set_frame_mode(struct mv88e6xxx_chip *chip, int port,
diff --git a/include/linux/platform_data/mv88e6xxx.h b/include/linux/platform_data/mv88e6xxx.h
index 21452a9365e1e..6366360fc15e1 100644
--- a/include/linux/platform_data/mv88e6xxx.h
+++ b/include/linux/platform_data/mv88e6xxx.h
@@ -14,6 +14,7 @@ struct dsa_mv88e6xxx_pdata {
 	struct net_device *netdev;
 	u32 eeprom_len;
 	int irq;
+	int avb_mode;
 };
 
 #endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC net-next 4/5] net: dsa: mv88e6xxx: CBS support
  2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
                   ` (2 preceding siblings ...)
  2025-09-27  7:07 ` [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support Luke Howard
@ 2025-09-27  7:07 ` Luke Howard
  2025-09-27  7:07 ` [RFC net-next 5/5] dt-bindings: net: dsa: mv88e6xxx: add mv88e6xxx-avb-mode property Luke Howard
  4 siblings, 0 replies; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

Add support for the 802.1Qav Credit Based Shaper (CBS) to Marvell switches
that support AVB. CBS policies can be configured per-port, but are subject
to the global policy limitations imposed by the Marvell MQPRIO
implementation.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 drivers/net/dsa/mv88e6xxx/avb.c  | 115 ++++++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx/avb.h  |  33 +++++++++
 drivers/net/dsa/mv88e6xxx/chip.c |  27 ++++++++
 drivers/net/dsa/mv88e6xxx/chip.h |   2 +
 4 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/avb.c b/drivers/net/dsa/mv88e6xxx/avb.c
index 361e7ff821567..513e0504735a6 100644
--- a/drivers/net/dsa/mv88e6xxx/avb.c
+++ b/drivers/net/dsa/mv88e6xxx/avb.c
@@ -107,7 +107,16 @@ static int mv88e6xxx_tc_disable(struct mv88e6xxx_chip *chip)
 	return chip->info->ops->tc_ops->tc_disable(chip);
 }
 
-/* MQPRIO helpers */
+/* MQPRIO and CBS helpers */
+
+int mv88e6xxx_qav_set_port_cbs_qopt(struct mv88e6xxx_chip *chip, int port,
+				    const struct tc_cbs_qopt_offload *cbs_qopt)
+{
+	if (!chip->info->ops->tc_ops->set_port_cbs_qopt)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->tc_ops->set_port_cbs_qopt(chip, port, cbs_qopt);
+}
 
 /* Set the AVB global policy limit registers. Caller must acquired register
  * lock.
@@ -330,6 +339,26 @@ int mv88e6xxx_avb_tc_disable(struct mv88e6xxx_chip *chip)
 	return 0;
 }
 
+static int mv88e6xxx_qav_set_port_config(struct mv88e6xxx_chip *chip, int port,
+					 int queue, u16 rate, u16 hilimit)
+{
+	int err;
+
+	err = mv88e6xxx_port_qav_write(chip, port,
+				       MV88E6XXX_PORT_QAV_CFG_RATE(queue),
+				       rate);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_port_qav_write(chip, port,
+				       MV88E6XXX_PORT_QAV_CFG_HI_LIMIT(queue),
+				       hilimit);
+	if (err)
+		return err;
+
+	return 0;
+}
+
 /* Assign FPri to QPri mappings for each traffic class
  *
  * @param chip		Marvell switch chip instance
@@ -456,14 +485,77 @@ static int mv88e6352_tc_disable(struct mv88e6xxx_chip *chip)
 	return 0;
 }
 
+static int mv88e6341_set_port_cbs_qopt(struct mv88e6xxx_chip *chip, int port,
+				       const struct tc_cbs_qopt_offload *cbs_qopt)
+{
+	u16 rate, hilimit;
+
+	if (cbs_qopt->enable) {
+		rate = DIV_ROUND_UP(cbs_qopt->idleslope, MV88E6341_AVB_CFG_RATE_UNITS);
+		rate = clamp_t(u16, rate, 1, MV88E6341_AVB_CFG_RATE_MASK);
+
+		hilimit = cbs_qopt->hicredit;
+		hilimit = clamp_t(u16, hilimit, 1, MV88E6341_AVB_CFG_HI_LIMIT_MASK);
+	} else {
+		rate = 0;
+		hilimit = MV88E6341_AVB_CFG_HI_LIMIT_MASK;
+	}
+
+	return mv88e6xxx_qav_set_port_config(chip, port, cbs_qopt->queue,
+					     rate, hilimit);
+}
+
 const struct mv88e6xxx_tc_ops mv88e6341_tc_ops = {
 	.tc_enable		= mv88e6352_tc_enable,
 	.tc_disable		= mv88e6352_tc_disable,
+	.set_port_cbs_qopt	= mv88e6341_set_port_cbs_qopt,
 };
 
+static int mv88e6352_set_port_cbs_qopt(struct mv88e6xxx_chip *chip, int port,
+				       const struct tc_cbs_qopt_offload *cbs_qopt)
+{
+	u16 rate, hilimit;
+	u16 cfg;
+	int err;
+
+	if (cbs_qopt->enable) {
+		rate = DIV_ROUND_UP(cbs_qopt->idleslope, MV88E6352_AVB_CFG_RATE_UNITS);
+		rate = clamp_t(u16, rate, 1, MV88E6352_AVB_CFG_RATE_MASK);
+
+		hilimit = cbs_qopt->hicredit;
+		hilimit = clamp_t(u16, hilimit, 1, MV88E6352_AVB_CFG_HI_LIMIT_MASK);
+	} else {
+		rate = 0;
+		hilimit = MV88E6352_AVB_CFG_HI_LIMIT_MASK;
+	}
+
+	err = mv88e6xxx_qav_set_port_config(chip, port, cbs_qopt->queue,
+					    rate, hilimit);
+	if (err)
+		return err;
+
+	/* Set undocumented enable register */
+
+	err = mv88e6xxx_port_qav_read(chip, port, MV88E6352_PORT_QAV_CFG, &cfg, 1);
+	if (err)
+		return err;
+
+	if (cbs_qopt->enable)
+		cfg |= MV88E6352_PORT_QAV_CFG_ENABLE;
+	else
+		cfg &= ~(MV88E6352_PORT_QAV_CFG_ENABLE);
+
+	err = mv88e6xxx_port_qav_write(chip, port, MV88E6352_PORT_QAV_CFG, cfg);
+	if (err)
+		return err;
+
+	return 0;
+}
+
 const struct mv88e6xxx_tc_ops mv88e6352_tc_ops = {
 	.tc_enable		= mv88e6352_tc_enable,
 	.tc_disable		= mv88e6352_tc_disable,
+	.set_port_cbs_qopt	= mv88e6352_set_port_cbs_qopt,
 };
 
 static inline u16 mv88e6390_avb_pri_map_to_reg(const struct mv88e6xxx_avb_priority_map map[])
@@ -544,7 +636,28 @@ static int mv88e6390_tc_disable(struct mv88e6xxx_chip *chip)
 	return err;
 }
 
+static int mv88e6390_set_port_cbs_qopt(struct mv88e6xxx_chip *chip, int port,
+				       const struct tc_cbs_qopt_offload *cbs_qopt)
+{
+	u16 rate, hilimit;
+
+	if (cbs_qopt->enable) {
+		rate = DIV_ROUND_UP(cbs_qopt->idleslope, MV88E6390_AVB_CFG_RATE_UNITS);
+		rate = clamp_t(u16, rate, 1, MV88E6390_AVB_CFG_RATE_MASK);
+
+		hilimit = cbs_qopt->hicredit;
+		hilimit = clamp_t(u16, hilimit, 1, MV88E6390_AVB_CFG_HI_LIMIT_MASK);
+	} else {
+		rate = 0;
+		hilimit = MV88E6390_AVB_CFG_HI_LIMIT_MASK;
+	}
+
+	return mv88e6xxx_qav_set_port_config(chip, port, cbs_qopt->queue,
+					     rate, hilimit);
+}
+
 const struct mv88e6xxx_tc_ops mv88e6390_tc_ops = {
 	.tc_enable		= mv88e6390_tc_enable,
 	.tc_disable		= mv88e6390_tc_disable,
+	.set_port_cbs_qopt	= mv88e6390_set_port_cbs_qopt,
 };
diff --git a/drivers/net/dsa/mv88e6xxx/avb.h b/drivers/net/dsa/mv88e6xxx/avb.h
index d049e30c5c0e2..b83db1e9878bf 100644
--- a/drivers/net/dsa/mv88e6xxx/avb.h
+++ b/drivers/net/dsa/mv88e6xxx/avb.h
@@ -94,6 +94,9 @@
 #define MV88E6XXX_AVB_CFG_OUI_HI		0x0C
 #define MV88E6XXX_AVB_CFG_OUI_LO		0x0D
 
+#define MV88E6XXX_PORT_QAV_CFG_RATE(queue)	(((queue) & 0x7) << 1)
+#define MV88E6XXX_PORT_QAV_CFG_HI_LIMIT(queue)	((((queue) & 0x7) << 1) + 1)
+
 /* 6352 Family AVB Global Config (4 TX queues) */
 
 #define MV88E6352_AVB_CFG_AVB_HI_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(p)
@@ -110,6 +113,18 @@
 #define MV88E6352_AVB_CFG_AVB_LO_QPRI_GET(p)	FIELD_GET(MV88E6352_AVB_CFG_AVB_LO_QPRI_MASK, p)
 #define MV88E6352_AVB_CFG_AVB_LO_QPRI_SET(p)	FIELD_PREP(MV88E6352_AVB_CFG_AVB_LO_QPRI_MASK, p)
 
+#define MV88E6352_AVB_CFG_RATE_UNITS		32 /* 32Kbps */
+#define MV88E6352_AVB_CFG_RATE_MASK		GENMASK(14, 0) /* 1Gbps */
+#define MV88E6352_AVB_CFG_HI_LIMIT_MASK		GENMASK(14, 0) /* 32k */
+
+#define MV88E6352_PORT_QAV_CFG			0x08
+#define MV88E6352_PORT_QAV_CFG_ENABLE		0x8000
+
+/* 6341 Family AVB Global Config (4 TX queues) */
+#define MV88E6341_AVB_CFG_RATE_UNITS		64 /* 64Kbps */
+#define MV88E6341_AVB_CFG_RATE_MASK		GENMASK(15, 0) /* 4Gbps */
+#define MV88E6341_AVB_CFG_HI_LIMIT_MASK		GENMASK(13, 0) /* 16k */
+
 /* 6390 Family AVB Global Config (8 TX queues) */
 
 #define MV88E6390_AVB_CFG_AVB_HI_FPRI_GET(p)	MV88E6XXX_AVB_CFG_AVB_HI_FPRI_GET(p)
@@ -126,6 +141,10 @@
 #define MV88E6390_AVB_CFG_AVB_LO_QPRI_GET(p)	FIELD_GET(MV88E6390_AVB_CFG_AVB_LO_QPRI_MASK, p)
 #define MV88E6390_AVB_CFG_AVB_LO_QPRI_SET(p)	FIELD_PREP(MV88E6390_AVB_CFG_AVB_LO_QPRI_MASK, p)
 
+#define MV88E6390_AVB_CFG_RATE_UNITS		64 /* 64Kbps */
+#define MV88E6390_AVB_CFG_RATE_MASK		GENMASK(15, 0) /* 4Gbps */
+#define MV88E6390_AVB_CFG_HI_LIMIT_MASK		GENMASK(13, 0) /* 16k */
+
 #define MV88E6352_AVB_QUEUE_MIN(tc)		(tc)
 #define MV88E6352_AVB_QUEUE_MAX(tc)		((tc) + 1)
 
@@ -194,6 +213,20 @@ int mv88e6xxx_avb_tc_enable(struct mv88e6xxx_chip *chip,
  */
 int mv88e6xxx_avb_tc_disable(struct mv88e6xxx_chip *chip);
 
+struct tc_cbs_qopt_offload;
+
+/* Set AVB credit based shaper policy. Caller must acquire register lock.
+ *
+ * @param chip		Marvell switch chip instance
+ * @param port		Switch port
+ * @param cbs_qopt	CBS policy to apply
+ *
+ * @return		0 on success, or a negative error value otherwise
+ */
+int mv88e6xxx_qav_set_port_cbs_qopt(struct mv88e6xxx_chip *chip,
+				    int port,
+				    const struct tc_cbs_qopt_offload *cbs_qopt);
+
 /* The MAAP address range is 91:E0:F0:00:00:00 thru 91:E0:F0:00:FF:FF
  * (IEEE 1722 Annex D)
  */
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 6ba2179d1c4ee..16f41604b932b 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -7003,6 +7003,31 @@ static int mv88e6xxx_qos_port_mqprio(struct dsa_switch *ds, int port,
 	return err;
 }
 
+static int mv88e6xxx_qos_port_cbs_set(struct dsa_switch *ds, int port,
+				      struct tc_cbs_qopt_offload *cbs_qopt)
+{
+	struct mv88e6xxx_chip *chip = ds->priv;
+	int err;
+
+	if (cbs_qopt->queue >= chip->info->num_tx_queues) {
+		dev_info(ds->dev, "p%d: invalid AVB queue %d\n", port, cbs_qopt->queue);
+		return -EINVAL;
+	}
+
+	mutex_lock(&chip->reg_lock);
+
+	err = mv88e6xxx_qav_set_port_cbs_qopt(chip, port, cbs_qopt);
+
+	mutex_unlock(&chip->reg_lock);
+
+	if (err) {
+		dev_info(ds->dev, "p%d: failed to %s AVB CBS policy: %d\n",
+			 port, cbs_qopt->enable ? "enable" : "disable", err);
+	}
+
+	return err;
+}
+
 static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
 				   enum tc_setup_type type,
 				   void *type_data)
@@ -7017,6 +7042,8 @@ static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
 		return mv88e6xxx_qos_query_caps(type_data);
 	case TC_SETUP_QDISC_MQPRIO:
 		return mv88e6xxx_qos_port_mqprio(ds, port, type_data);
+	case TC_SETUP_QDISC_CBS:
+		return mv88e6xxx_qos_port_cbs_set(ds, port, type_data);
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 71e536fbd2d24..6bbfb503b237f 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -833,6 +833,8 @@ struct mv88e6xxx_tc_ops {
 	int (*tc_enable)(struct mv88e6xxx_chip *chip,
 			 const struct mv88e6xxx_avb_tc_policy *policy);
 	int (*tc_disable)(struct mv88e6xxx_chip *chip);
+	int (*set_port_cbs_qopt)(struct mv88e6xxx_chip *chip, int port,
+				 const struct tc_cbs_qopt_offload *cbs_qopt);
 };
 
 static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC net-next 5/5] dt-bindings: net: dsa: mv88e6xxx: add mv88e6xxx-avb-mode property
  2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
                   ` (3 preceding siblings ...)
  2025-09-27  7:07 ` [RFC net-next 4/5] net: dsa: mv88e6xxx: CBS support Luke Howard
@ 2025-09-27  7:07 ` Luke Howard
  4 siblings, 0 replies; 13+ messages in thread
From: Luke Howard @ 2025-09-27  7:07 UTC (permalink / raw)
  To: netdev; +Cc: andrew, vladimir.oltean, kieran, jcschroeder, max, Luke Howard

Add the vendor-specific marvell,mv88e6xxx-avb-mode property for adding
stricter handling of frames with non-AVB frame priorities and destination
addresses.

Signed-off-by: Luke Howard <lukeh@padl.com>
---
 .../bindings/net/dsa/marvell,mv88e6xxx.yaml   | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/dsa/marvell,mv88e6xxx.yaml b/Documentation/devicetree/bindings/net/dsa/marvell,mv88e6xxx.yaml
index 19f15bdd1c976..33d0cf5f21d5b 100644
--- a/Documentation/devicetree/bindings/net/dsa/marvell,mv88e6xxx.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/marvell,mv88e6xxx.yaml
@@ -97,6 +97,31 @@ properties:
     required:
       - compatible
 
+  marvell,mv88e6xxx-avb-mode:
+    description: Marvell MV88E6xxx that support Audio Video Bridging/Time
+      Sensitive Networking (AVB/TSN) traffic prioritization can have ports
+      ports configured in one of several modes. These modes control the
+      handling of frames with non-AVB frame priorities and destination
+      addresses.
+    oneOf:
+      - enum:
+          - 0
+          - 1
+          - 2
+      - description: |
+        0: Standard Mode: frames with a priority that is mapped to an AVB
+           traffic class (TC) are considered as AVB frames. Others frames
+           are considered Legacy (non-AVB).
+        1: Enhanced: frames with a priority that is mapped to an AVB TC
+           and for which there is a static FDB or MDB entry are
+           considered as AVB frames. Those with an AVB TC but no FDB or
+	   MDB entry will be dropped. To avoid conflict with other multicast
+           protocols, only AVTP (91:e0:f0) destination addresses are
+           considered to be AVB multicast addresses.
+        2: Secure: as for Enhanced, but also require the FDB or MDB entry
+           to have its source port's bit set to one. In this mode, all
+	   frames with an AVTP destination address must be valid AVB frames.
+
 allOf:
   - $ref: dsa.yaml#/$defs/ethernet-ports
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter
  2025-09-27  7:07 ` [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter Luke Howard
@ 2025-09-27 14:29   ` Andrew Lunn
  0 siblings, 0 replies; 13+ messages in thread
From: Andrew Lunn @ 2025-09-27 14:29 UTC (permalink / raw)
  To: Luke Howard; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, max

On Sat, Sep 27, 2025 at 05:07:05PM +1000, Luke Howard wrote:
> Add accessors for the MACAVB bit, which controls whether certain ATU bits cause
> the entry to be interpreted as AVB or NRL (non-rate-limiting) entries. This is
> necessary on switches such as the 88E6352 and 88E6240 that support both AVB and
> NRL ATU entries.
> 
> Signed-off-by: Luke Howard <lukeh@padl.com>
> ---
>  drivers/net/dsa/mv88e6xxx/global1.h     |  2 ++
>  drivers/net/dsa/mv88e6xxx/global1_atu.c | 17 +++++++++++++++++
>  2 files changed, 19 insertions(+)
> 
> diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h
> index 3dbb7a1b8fe11..74be4c485ab10 100644
> --- a/drivers/net/dsa/mv88e6xxx/global1.h
> +++ b/drivers/net/dsa/mv88e6xxx/global1.h
> @@ -112,6 +112,7 @@
>  /* Offset 0x0A: ATU Control Register */
>  #define MV88E6XXX_G1_ATU_CTL		0x0a
>  #define MV88E6XXX_G1_ATU_CTL_LEARN2ALL	0x0008
> +#define MV88E6XXX_G1_ATU_CTL_MAC_AVB	0x8000
>  #define MV88E6161_G1_ATU_CTL_HASH_MASK	0x0003

nitpick: The sorting in this file suggests that they are sorted
highest bits to lowest bits. So MAC_AVB should be before LEARN2ALL.


    Andrew

---
pw-bot: cr

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27  7:07 ` [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support Luke Howard
@ 2025-09-27 15:12   ` Andrew Lunn
  2025-09-27 16:00     ` Luke Howard
  2025-09-27 15:18   ` kernel test robot
  1 sibling, 1 reply; 13+ messages in thread
From: Andrew Lunn @ 2025-09-27 15:12 UTC (permalink / raw)
  To: Luke Howard; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, max

> +/* MQPRIO helpers */
> +
> +/* Set the AVB global policy limit registers. Caller must acquired register
> + * lock.

No where else in this driver is this assumption about the register
lock documented. If you forget it, the low level read/write functions
will tell you. So i don't think it adds value.

> + *
> + * @param chip		Marvell switch chip instance
> + * @param hilimit	Maximum frame size allowed for AVB Class A frames
> + *
> + * @return		0 on success, or a negative error value otherwise
> + */

kerneldoc wants a : after return. 

> +static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
> +{
> +	u16 data;
> +	int err;
> +
> +	if (hilimit > MV88E6XXX_AVB_CFG_HI_LIMIT_MASK)
> +		return -EINVAL;

Does it make sense to check it against the MTU? Does it matter if it
is bigger than the MTU?

> +/* Set the AVB global policy OUI filter registers. Caller must acquire register
> + * lock.
> + *
> + * @param chip		Marvell switch chip instance
> + * @param addr		The AVB OUI to load
> + *
> + * @return		0 on success, or a negative error value otherwise
> + */
> +static int mv88e6xxx_avb_set_oui(struct mv88e6xxx_chip *chip,
> +				 const unsigned char *addr)

Maybe be a bit more specific with the documentation of addr. You pass
a 6 byte address, not a 3 byte OUI. So "The AVB OUI to load" is not
quite correct. It is more like, "Use the OUI from this MAC address."
I've not looked at the big picture, but i was woundering if it makes
more sense to pass an actual OUI? But that is not something we tend to
do in the kernel.

> +static inline u16 mv88e6352_avb_pri_map_to_reg(const struct mv88e6xxx_avb_priority_map map[])
> +{
> +	return MV88E6352_AVB_CFG_AVB_HI_FPRI_SET(map[MV88E6XXX_AVB_TC_HI].fpri) |
> +		MV88E6352_AVB_CFG_AVB_HI_QPRI_SET(map[MV88E6XXX_AVB_TC_HI].qpri) |
> +		MV88E6352_AVB_CFG_AVB_LO_FPRI_SET(map[MV88E6XXX_AVB_TC_LO].fpri) |
> +		MV88E6352_AVB_CFG_AVB_LO_QPRI_SET(map[MV88E6XXX_AVB_TC_LO].qpri);
> +}
> +
> +static int mv88e6352_qav_map_fpri_qpri(u8 fpri, u8 qpri, void *reg)
> +{
> +	mv88e6352_g1_ieee_pri_set(fpri, qpri, (u16 *)reg);
> +	return 0;

Blank line before the return please.

> + *	- because the Netlink API has no way to distinguish between FDB/MDB
> + *	  entries managed by SRP from those that are not, the
> + *	  "marvell,mv88e6xxx-avb-mode" device tree property controls whether
> + *	  a FDB or MDB entry is required in order for AVB frames to egress.

We probably need to think about this. What about other devices which
require this? Would it be better to extend the netlink API to pass
some sort of owner? If i remember correctly, routes passed by netlink
can indicate which daemon is responsible for it, quagga, zebra, bgp
etc.

> + *	  To avoid breaking static IP MDB entries, only multicast addresses
> + *	  with OUI prefix of 91:e0:ff (IEEE 1722 Annex D) will have the AVB
> + *	  flag set on their ATU entry.

This probably answers my question bellow.

> +/* The MAAP address range is 91:E0:F0:00:00:00 thru 91:E0:F0:00:FF:FF
> + * (IEEE 1722 Annex D)
> + */
> +static const u8 eth_maap_mcast_addr_base[ETH_ALEN] __aligned(2) = {
> +	0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00
> +};
> +
> +static inline bool ether_addr_is_maap_mcast(const u8 *addr)
> +{
> +	u8 mask[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 };
> +
> +	return ether_addr_equal_masked(addr, eth_maap_mcast_addr_base, mask);
> +}

Since these are part of a standard, i assume other drivers will need
it as well? These should probably be somewhere common.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27  7:07 ` [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support Luke Howard
  2025-09-27 15:12   ` Andrew Lunn
@ 2025-09-27 15:18   ` kernel test robot
  1 sibling, 0 replies; 13+ messages in thread
From: kernel test robot @ 2025-09-27 15:18 UTC (permalink / raw)
  To: Luke Howard; +Cc: oe-kbuild-all

Hi Luke,

[This is a private test report for your RFC patch.]
kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Luke-Howard/net-dsa-mv88e6xxx-add-num_tx_queues-to-chip-info-structure/20250927-155031
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250927070724.734933-4-lukeh%40padl.com
patch subject: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
config: x86_64-buildonly-randconfig-005-20250927 (https://download.01.org/0day-ci/archive/20250927/202509272325.MTS85ySX-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250927/202509272325.MTS85ySX-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202509272325.MTS85ySX-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/net/dsa/mv88e6xxx/avb.c:120:12: warning: 'mv88e6xxx_avb_set_hilimit' defined but not used [-Wunused-function]
     120 | static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
         |            ^~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/net/dsa/mv88e6xxx/avb.c:84:12: warning: 'mv88e6xxx_port_qav_write' defined but not used [-Wunused-function]
      84 | static int mv88e6xxx_port_qav_write(struct mv88e6xxx_chip *chip, int port,
         |            ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/net/dsa/mv88e6xxx/avb.c:75:12: warning: 'mv88e6xxx_port_qav_read' defined but not used [-Wunused-function]
      75 | static int mv88e6xxx_port_qav_read(struct mv88e6xxx_chip *chip, int port,
         |            ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/dsa/mv88e6xxx/avb.c:20:12: warning: 'mv88e6xxx_port_avb_read' defined but not used [-Wunused-function]
      20 | static int mv88e6xxx_port_avb_read(struct mv88e6xxx_chip *chip, int port,
         |            ^~~~~~~~~~~~~~~~~~~~~~~


vim +/mv88e6xxx_avb_set_hilimit +120 drivers/net/dsa/mv88e6xxx/avb.c

    19	
  > 20	static int mv88e6xxx_port_avb_read(struct mv88e6xxx_chip *chip, int port,
    21					   int addr, u16 *data, int len)
    22	{
    23		if (!chip->info->ops->avb_ops->port_avb_read)
    24			return -EOPNOTSUPP;
    25	
    26		return chip->info->ops->avb_ops->port_avb_read(chip, port, addr,
    27							       data, len);
    28	}
    29	
    30	static int mv88e6xxx_port_avb_write(struct mv88e6xxx_chip *chip, int port,
    31					    int addr, u16 data)
    32	{
    33		if (!chip->info->ops->avb_ops->port_avb_write)
    34			return -EOPNOTSUPP;
    35	
    36		return chip->info->ops->avb_ops->port_avb_write(chip, port, addr, data);
    37	}
    38	
    39	static int mv88e6xxx_avb_read(struct mv88e6xxx_chip *chip, int addr,
    40				      u16 *data, int len)
    41	{
    42		if (!chip->info->ops->avb_ops->avb_read)
    43			return -EOPNOTSUPP;
    44	
    45		return chip->info->ops->avb_ops->avb_read(chip, addr, data, len);
    46	}
    47	
    48	static int mv88e6xxx_avb_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
    49	{
    50		if (!chip->info->ops->avb_ops->avb_write)
    51			return -EOPNOTSUPP;
    52	
    53		return chip->info->ops->avb_ops->avb_write(chip, addr, data);
    54	}
    55	
    56	/* 802.1Qav operation wrappers */
    57	
    58	static int mv88e6xxx_qav_read(struct mv88e6xxx_chip *chip, int addr,
    59				      u16 *data, int len)
    60	{
    61		if (!chip->info->ops->avb_ops->qav_read)
    62			return -EOPNOTSUPP;
    63	
    64		return chip->info->ops->avb_ops->qav_read(chip, addr, data, len);
    65	}
    66	
    67	static int mv88e6xxx_qav_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
    68	{
    69		if (!chip->info->ops->avb_ops->qav_write)
    70			return -EOPNOTSUPP;
    71	
    72		return chip->info->ops->avb_ops->qav_write(chip, addr, data);
    73	}
    74	
    75	static int mv88e6xxx_port_qav_read(struct mv88e6xxx_chip *chip, int port,
    76					   int addr, u16 *data, int len)
    77	{
    78		if (!chip->info->ops->avb_ops->port_qav_read)
    79			return -EOPNOTSUPP;
    80	
    81		return chip->info->ops->avb_ops->port_qav_read(chip, port, addr, data, len);
    82	}
    83	
    84	static int mv88e6xxx_port_qav_write(struct mv88e6xxx_chip *chip, int port,
    85					    int addr, u16 data)
    86	{
    87		if (!chip->info->ops->avb_ops->port_qav_write)
    88			return -EOPNOTSUPP;
    89	
    90		return chip->info->ops->avb_ops->port_qav_write(chip, port, addr, data);
    91	}
    92	
    93	static int mv88e6xxx_tc_enable(struct mv88e6xxx_chip *chip,
    94				       const struct mv88e6xxx_avb_tc_policy *policy)
    95	{
    96		if (!chip->info->ops->tc_ops->tc_enable)
    97			return -EOPNOTSUPP;
    98	
    99		return chip->info->ops->tc_ops->tc_enable(chip, policy);
   100	}
   101	
   102	static int mv88e6xxx_tc_disable(struct mv88e6xxx_chip *chip)
   103	{
   104		if (!chip->info->ops->tc_ops->tc_disable)
   105			return -EOPNOTSUPP;
   106	
   107		return chip->info->ops->tc_ops->tc_disable(chip);
   108	}
   109	
   110	/* MQPRIO helpers */
   111	
   112	/* Set the AVB global policy limit registers. Caller must acquired register
   113	 * lock.
   114	 *
   115	 * @param chip		Marvell switch chip instance
   116	 * @param hilimit	Maximum frame size allowed for AVB Class A frames
   117	 *
   118	 * @return		0 on success, or a negative error value otherwise
   119	 */
 > 120	static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
   121	{
   122		u16 data;
   123		int err;
   124	
   125		if (hilimit > MV88E6XXX_AVB_CFG_HI_LIMIT_MASK)
   126			return -EINVAL;
   127	
   128		err = mv88e6xxx_avb_read(chip, MV88E6XXX_AVB_CFG_HI_LIMIT, &data, 1);
   129		if (err)
   130			return err;
   131	
   132		data &= ~(MV88E6XXX_AVB_CFG_HI_LIMIT_MASK);
   133		data |= MV88E6XXX_AVB_CFG_HI_LIMIT_SET(hilimit);
   134	
   135		err = mv88e6xxx_avb_write(chip, MV88E6XXX_AVB_CFG_HI_LIMIT, hilimit);
   136		if (err)
   137			return err;
   138	
   139		return 0;
   140	}
   141	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27 15:12   ` Andrew Lunn
@ 2025-09-27 16:00     ` Luke Howard
  2025-09-27 17:36       ` Andrew Lunn
  0 siblings, 1 reply; 13+ messages in thread
From: Luke Howard @ 2025-09-27 16:00 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, Max Hunter



> On 27 Sep 2025, at 16:12, Andrew Lunn <andrew@lunn.ch> wrote:
> 
>> +/* MQPRIO helpers */
>> +
>> +/* Set the AVB global policy limit registers. Caller must acquired register
>> + * lock.
> 
> No where else in this driver is this assumption about the register
> lock documented. If you forget it, the low level read/write functions
> will tell you. So i don't think it adds value.

Fixed.

> 
>> + *
>> + * @param chip Marvell switch chip instance
>> + * @param hilimit Maximum frame size allowed for AVB Class A frames
>> + *
>> + * @return 0 on success, or a negative error value otherwise
>> + */
> 
> kerneldoc wants a : after return.

Should also be for @param then? Seems fairly inconsistent on a brief survey of other drivers.

>> +static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
>> +{
>> + u16 data;
>> + int err;
>> +
>> + if (hilimit > MV88E6XXX_AVB_CFG_HI_LIMIT_MASK)
>> + return -EINVAL;
> 
> Does it make sense to check it against the MTU? Does it matter if it
> is bigger than the MTU?

I don’t think so; this is hicredit in tc-cbs(8).

>> +/* Set the AVB global policy OUI filter registers. Caller must acquire register
>> + * lock.
>> + *
>> + * @param chip Marvell switch chip instance
>> + * @param addr The AVB OUI to load
>> + *
>> + * @return 0 on success, or a negative error value otherwise
>> + */
>> +static int mv88e6xxx_avb_set_oui(struct mv88e6xxx_chip *chip,
>> + const unsigned char *addr)
> 
> Maybe be a bit more specific with the documentation of addr. You pass
> a 6 byte address, not a 3 byte OUI. So "The AVB OUI to load" is not
> quite correct. It is more like, "Use the OUI from this MAC address."
> I've not looked at the big picture, but i was woundering if it makes
> more sense to pass an actual OUI? But that is not something we tend to
> do in the kernel.

Passing in an addr lets us reuse eth_maap_mcast_addr_base. Agree the documentation could be clearer. Perhaps this extra restriction (requiring AVTP DAs in secure mode) is not necessary anyway.

>> +static inline u16 mv88e6352_avb_pri_map_to_reg(const struct mv88e6xxx_avb_priority_map map[])
>> +{
>> + return MV88E6352_AVB_CFG_AVB_HI_FPRI_SET(map[MV88E6XXX_AVB_TC_HI].fpri) |
>> + MV88E6352_AVB_CFG_AVB_HI_QPRI_SET(map[MV88E6XXX_AVB_TC_HI].qpri) |
>> + MV88E6352_AVB_CFG_AVB_LO_FPRI_SET(map[MV88E6XXX_AVB_TC_LO].fpri) |
>> + MV88E6352_AVB_CFG_AVB_LO_QPRI_SET(map[MV88E6XXX_AVB_TC_LO].qpri);
>> +}
>> +
>> +static int mv88e6352_qav_map_fpri_qpri(u8 fpri, u8 qpri, void *reg)
>> +{
>> + mv88e6352_g1_ieee_pri_set(fpri, qpri, (u16 *)reg);
>> + return 0;
> 
> Blank line before the return please.

Fixed.

>> + * - because the Netlink API has no way to distinguish between FDB/MDB
>> + *  entries managed by SRP from those that are not, the
>> + *  "marvell,mv88e6xxx-avb-mode" device tree property controls whether
>> + *  a FDB or MDB entry is required in order for AVB frames to egress.
> 
> We probably need to think about this. What about other devices which
> require this? Would it be better to extend the netlink API to pass
> some sort of owner? If i remember correctly, routes passed by netlink
> can indicate which daemon is responsible for it, quagga, zebra, bgp
> etc.

An additional flag to Netlink when adding a FDB or MDB entry would certainly be cleaner. I’m not sure what other devices do, I suspect some support similar functionality but do not have driver support, because most SRP implementations have managed the switch directly. (Mine [1] uses standard kernel interfaces.)

> Since these are part of a standard, i assume other drivers will need
> it as well? These should probably be somewhere common.

Yes, can put elsewhere. Or, if we have a suitable Netlink interface, they can be removed, as we won’t need heuristics to determine whether a MDB entry is for AVB. (This also likely takes care of mv88e6xxx_avb_set_oui.)

[1] github.com/PADL/OpenSRP

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27 16:00     ` Luke Howard
@ 2025-09-27 17:36       ` Andrew Lunn
  2025-09-27 18:14         ` Luke Howard
  0 siblings, 1 reply; 13+ messages in thread
From: Andrew Lunn @ 2025-09-27 17:36 UTC (permalink / raw)
  To: Luke Howard; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, Max Hunter

> > 
> >> + *
> >> + * @param chip Marvell switch chip instance
> >> + * @param hilimit Maximum frame size allowed for AVB Class A frames
> >> + *
> >> + * @return 0 on success, or a negative error value otherwise
> >> + */
> > 
> > kerneldoc wants a : after return.
> 
> Should also be for @param then? Seems fairly inconsistent on a brief survey of other drivers.
 
Ah, sorry. I missed you did not start the documentation correctly, so
it is not even considered kerneldoc.

You need

/**

to start the block.

https://docs.kernel.org/doc-guide/kernel-doc.html#how-to-format-kernel-doc-comments

> >> +static int mv88e6xxx_avb_set_hilimit(struct mv88e6xxx_chip *chip, u16 hilimit)
> >> +{
> >> + u16 data;
> >> + int err;
> >> +
> >> + if (hilimit > MV88E6XXX_AVB_CFG_HI_LIMIT_MASK)
> >> + return -EINVAL;
> > 
> > Does it make sense to check it against the MTU? Does it matter if it
> > is bigger than the MTU?
> 
> I don’t think so; this is hicredit in tc-cbs(8).

O.K, i've no real knowledge of AVB...

> >> + * - because the Netlink API has no way to distinguish between FDB/MDB
> >> + *  entries managed by SRP from those that are not, the
> >> + *  "marvell,mv88e6xxx-avb-mode" device tree property controls whether
> >> + *  a FDB or MDB entry is required in order for AVB frames to egress.
> > 
> > We probably need to think about this. What about other devices which
> > require this? Would it be better to extend the netlink API to pass
> > some sort of owner? If i remember correctly, routes passed by netlink
> > can indicate which daemon is responsible for it, quagga, zebra, bgp
> > etc.
> 

> An additional flag to Netlink when adding a FDB or MDB entry would
> certainly be cleaner. I’m not sure what other devices do, I suspect
> some support similar functionality but do not have driver support,
> because most SRP implementations have managed the switch
> directly. (Mine [1] uses standard kernel interfaces.)

Having an open user space side helps get such an extra attribute
added.

Would the kernel bridge have any use of this? It is normal to add a
feature to the kernel, and then offload it to hardware. 

	Andrew

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27 17:36       ` Andrew Lunn
@ 2025-09-27 18:14         ` Luke Howard
  2026-05-26  7:05           ` Luke Howard
  0 siblings, 1 reply; 13+ messages in thread
From: Luke Howard @ 2025-09-27 18:14 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, Max Hunter

> https://docs.kernel.org/doc-guide/kernel-doc.html#how-to-format-kernel-doc-comments

Oddly though very files in the kernel seem to follow this (unless I’m missing something obvious).

linux$ git grep @arg1|wc
     13      96    1009

Anyway, happy to change.

> Having an open user space side helps get such an extra attribute
> added.
> 
> Would the kernel bridge have any use of this? It is normal to add a
> feature to the kernel, and then offload it to hardware.

Yes, it should probably be implemented for the kernel bridge.

I guess we need a new attribute or flag when adding a MDB or FDB entry, and also a way to indicate that a TC is SRP-managed. The kernel bridge could then be modified to reject or reclassify packets that are classified into a TC with the SRP flag set, unless a MDB/FDB entry exists for the DA that also has that flag set.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support
  2025-09-27 18:14         ` Luke Howard
@ 2026-05-26  7:05           ` Luke Howard
  0 siblings, 0 replies; 13+ messages in thread
From: Luke Howard @ 2026-05-26  7:05 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: netdev, vladimir.oltean, kieran, jcschroeder, Max Hunter

Hi Andrew,

> On 28 Sep 2025, at 04:14, Luke Howard <lukeh@padl.com> wrote:
> 
> I guess we need a new attribute or flag when adding a MDB or FDB entry, and also a way to indicate that a TC is SRP-managed. The kernel bridge could then be modified to reject or reclassify packets that are classified into a TC with the SRP flag set, unless a MDB/FDB entry exists for the DA that also has that flag set.

I have looked into this once more (disclosure: this time, LLM-aided).

* adding a MDB entry can now take a nested MDBE_ATTR_FLAGS attribute; the only one defined is MDB_FLAGS_STREAM_RESERVED
* this is propagated to MDB_PG_FLAGS_STREAM_RESERVED in br_mdb_config’s pg_flags
* MDB_PG_FLAGS_STREAM_RESERVED is propagated, in turn, to SWITCHDEV_MDB_F_STREAM_RESERVED

I intentionally avoided FDB entries as they’re not typically used in AVB/TSN, and this would have involved more intrusive API changes (port_fdb_add takes just the address/VID as distinct parameters).

If mv88e6xxx_port_mdb_add() is called with SWITCHDEV_MDB_F_STREAM_RESERVED set, it adds the ATU entry as MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL instead of MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC. For a given destination MAC, the AVB_NRL state must match across ports (otherwise -EINVAL is returned).

For the software bridge, if a frame arrives with PCP 3 or 2 (Class A/B traffic per IEEE 802.1Q-2018 Annex L) and the destination MDB entry has at least one port group marked STREAM_RESERVED, the unflagged port groups are skipped and router ports are suppressed. MDB entries with no flagged port groups follow the existing br_multicast_flood() path unchanged. The PCP set is hard-coded for now as an example and probably needs a configuration knob.

Cheers,
Luke

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-05-26  7:05 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-27  7:07 [RFC net-next 0/5] net: dsa: mv88e6xxx: support for 802.1Qav Luke Howard
2025-09-27  7:07 ` [RFC net-next 1/5] net: dsa: mv88e6xxx: add num_tx_queues to chip info structure Luke Howard
2025-09-27  7:07 ` [RFC net-next 2/5] net: dsa: mv88e6xxx: add MV88E6XXX_G1_ATU_CTL_MAC_AVB setter Luke Howard
2025-09-27 14:29   ` Andrew Lunn
2025-09-27  7:07 ` [RFC net-next 3/5] net: dsa: mv88e6xxx: MQPRIO support Luke Howard
2025-09-27 15:12   ` Andrew Lunn
2025-09-27 16:00     ` Luke Howard
2025-09-27 17:36       ` Andrew Lunn
2025-09-27 18:14         ` Luke Howard
2026-05-26  7:05           ` Luke Howard
2025-09-27 15:18   ` kernel test robot
2025-09-27  7:07 ` [RFC net-next 4/5] net: dsa: mv88e6xxx: CBS support Luke Howard
2025-09-27  7:07 ` [RFC net-next 5/5] dt-bindings: net: dsa: mv88e6xxx: add mv88e6xxx-avb-mode property Luke Howard

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.