mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v4 0/2] net: dsa: mv88e6xxx: add support for credit based shaper
@ 2026-05-28 16:09 Cedric Jehasse via B4 Relay
  2026-05-28 16:09 ` [PATCH net-next v4 1/2] net: dsa: mv88e6xxx: use the hw tx queues Cedric Jehasse via B4 Relay
  2026-05-28 16:09 ` [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
  0 siblings, 2 replies; 9+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-05-28 16:09 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King
  Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
	Cedric Jehasse, Cedric Jehasse

Several of the switch families in this driver have switches with AVB
support. The switches with AVB support have support for Credit based
shaping. This series adds support for the 6341, 6352, 6390 and 6393
families.
The difference between the families is:
- total number of queues
- which queues support credit based shaping
- shaping granularity

Eg. setting up 20mbps credit based shaper on a 1GBit link:
tc qdisc add dev p8 parent root handle 100: mqprio \
    num_tc 8 \
    map 0 0 6 7 0 5 0 0 0 0 0 0 0 0 0 0 \
    queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \
    hw 0

tc qdisc replace dev p8 parent 100:8 cbs locredit -1470 hicredit 30 \
    sendslope -980000 idleslope 20000 offload 1

Signed-off-by: Cedric Jehasse <cedric.jehasse@luminex.be>
---
Changes in v4:
- add qav info for the 6341
- remove max_rate from mv88e6xxx_qav_info. It looks like the max rate
  mentioned in the docs is just the maximum rate that fits in the
  registers. This is already represented by rate_mask.
- remove validation on sendslope. This isn't used by the driver.
- use accessors for qav_write and set_scheduling
- Link to v3: https://lore.kernel.org/r/20260527-net-next-mv88e6xxx-cbs-v3-0-2f387eb5ff63@luminex.be

Changes in v3:
- clear cbs_active_queues bit when the rate is set to 0 on
  port_set_scheduling_mode failure
- wait for the MV88E6390_PORT_QUEUE_CTL_UPDATE bit to be cleared after
  writing the scheduling mode
- add num_tx_queues for mv88e6191 based on it's family, as nobody is
  able to find a datasheet for this device
- reverse christmas tree variable declarations
- fix egress queue selection for devices with 4 queues
- Link to v2: https://lore.kernel.org/r/20260526-net-next-mv88e6xxx-cbs-v2-0-13eb49c29b83@luminex.be

Changes in v2:
- tx_queues defined for all devices (which could be found). Thanks to Marek Behún.
- Link to v1: https://lore.kernel.org/r/20260522-net-next-mv88e6xxx-cbs-v1-0-c87a8e6bcc0c@luminex.be

---
Cedric Jehasse (2):
      net: dsa: mv88e6xxx: use the hw tx queues
      net: dsa: mv88e6xxx: add support for credit based shaper

 drivers/net/dsa/mv88e6xxx/chip.c        | 165 ++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h        |  21 ++++
 drivers/net/dsa/mv88e6xxx/global2.h     |   3 +
 drivers/net/dsa/mv88e6xxx/global2_avb.c |  21 ++++
 drivers/net/dsa/mv88e6xxx/port.c        |  63 ++++++++++++
 drivers/net/dsa/mv88e6xxx/port.h        |  20 ++++
 net/dsa/tag_dsa.c                       |  11 ++-
 7 files changed, 303 insertions(+), 1 deletion(-)
---
base-commit: 022bdd9c0d036863c4bacd1688b73c6be3001cee
change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68

Best regards,
-- 
Cedric Jehasse <cedric.jehasse@luminex.be>



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

* [PATCH net-next v4 1/2] net: dsa: mv88e6xxx: use the hw tx queues
  2026-05-28 16:09 [PATCH net-next v4 0/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
@ 2026-05-28 16:09 ` Cedric Jehasse via B4 Relay
  2026-05-28 16:09 ` [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
  1 sibling, 0 replies; 9+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-05-28 16:09 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King
  Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
	Cedric Jehasse, Cedric Jehasse

From: Cedric Jehasse <cedric.jehasse@luminex.be>

When transmitting fill in the PRI field in the dsa tag to select the
egress queue is sent to.
From the datasheets i've looked at these switches have 4 or 8 transmit
queues per port.
Note: skbs with skb->fw_offload_fwd_mark set use the DSA_CMD_FORWARD
tag. These are processed as normal ingress frames, meaning the queue
they end up in can still be altered by other switch config. eg. priority
overrides, tcam policies.
This isn't done for vlan tagged frames because this would overwrite the
PCP value in the vlan tag (The PRI field in the dsa
tag is used as the PCP value in the vlan tag).

Signed-off-by: Cedric Jehasse <cedric.jehasse@luminex.be>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 35 +++++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h |  1 +
 net/dsa/tag_dsa.c                | 11 ++++++++++-
 3 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 8ca5fd40df92..ffd4fa41b7c5 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3979,6 +3979,7 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
 
 	chip->ds = ds;
 	ds->user_mii_bus = mv88e6xxx_default_mdio_bus(chip);
+	ds->num_tx_queues = chip->info->num_tx_queues;
 
 	/* Since virtual bridges are mapped in the PVT, the number we support
 	 * depends on the physical switch topology. We need to let DSA figure
@@ -5689,6 +5690,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		 */
 		.num_ports = 7,
 		.num_internal_phys = 2,
+		.num_tx_queues = 4,
 		.invalid_port_mask = BIT(2) | BIT(3) | BIT(4),
 		.max_vid = 4095,
 		.port_base_addr = 0x8,
@@ -5711,6 +5713,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_databases = 64,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x08,
 		.phy_base_addr = 0x00,
@@ -5733,6 +5736,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 10,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5757,6 +5761,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 11,
 		.num_internal_phys = 0,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x10,
 		.phy_base_addr = 0x0,
@@ -5778,6 +5783,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 11,
 		.num_internal_phys = 8,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5803,6 +5809,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 1024,
 		.num_ports = 3,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5828,6 +5835,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 8,
 		.num_internal_phys = 0,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x10,
 		.phy_base_addr = 0x0,
@@ -5850,6 +5858,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 6,
 		.num_internal_phys = 5,
 		.num_gpio = 11,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5875,6 +5884,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 1024,
 		.num_ports = 6,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5901,6 +5911,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 6,
 		.num_internal_phys = 0,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5926,6 +5937,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5952,6 +5964,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 7,
 		.num_internal_phys = 5,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -5977,6 +5990,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6003,6 +6017,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 7,
 		.num_internal_phys = 5,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6028,6 +6043,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 10,
 		.num_internal_phys = 0,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x10,
 		.phy_base_addr = 0x0,
@@ -6051,6 +6067,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 9,
 		.num_gpio = 16,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6076,6 +6093,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 9,
 		.num_gpio = 16,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6100,6 +6118,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 16384,
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 9,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6125,6 +6144,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 8,
 		.internal_phys_offset = 1,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6151,6 +6171,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 8,
 		.num_tcam_entries = 256,
 		.internal_phys_offset = 1,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6181,6 +6202,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 7,
 		.num_internal_phys = 2,
 		.invalid_port_mask = BIT(2) | BIT(3) | BIT(4),
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x08,
 		.phy_base_addr = 0x00,
@@ -6205,6 +6227,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 7,
 		.num_internal_phys = 5,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6230,6 +6253,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_databases = 64,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.port_base_addr = 0x08,
 		.phy_base_addr = 0x00,
@@ -6254,6 +6278,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 9,
 		.num_gpio = 16,
 		.num_tcam_entries = 256,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6282,6 +6307,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 2,
 		.internal_phys_offset = 3,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6310,6 +6336,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 2,
 		.internal_phys_offset = 3,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6337,6 +6364,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 5,
 		.num_ports = 6,
 		.num_gpio = 11,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6363,6 +6391,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6388,6 +6417,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_macs = 8192,
 		.num_ports = 7,
 		.num_internal_phys = 5,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6414,6 +6444,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 7,
 		.num_internal_phys = 5,
 		.num_gpio = 15,
+		.num_tx_queues = 4,
 		.max_vid = 4095,
 		.max_sid = 63,
 		.port_base_addr = 0x10,
@@ -6442,6 +6473,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.invalid_port_mask = BIT(1) | BIT(2) | BIT(8),
 		.num_internal_phys = 5,
 		.internal_phys_offset = 3,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6468,6 +6500,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_internal_phys = 9,
 		.num_gpio = 16,
 		.num_tcam_entries = 256,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6495,6 +6528,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 9,
 		.num_gpio = 16,
+		.num_tx_queues = 8,
 		.max_vid = 8191,
 		.max_sid = 63,
 		.port_base_addr = 0x0,
@@ -6521,6 +6555,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.num_ports = 11,	/* 10 + Z80 */
 		.num_internal_phys = 8,
 		.num_tcam_entries = 256,
+		.num_tx_queues = 8,
 		.internal_phys_offset = 1,
 		.max_vid = 8191,
 		.max_sid = 63,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index cde71828e9d9..19d8eda19b78 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -136,6 +136,7 @@ struct mv88e6xxx_info {
 	unsigned int num_internal_phys;
 	unsigned int num_gpio;
 	unsigned int num_tcam_entries;
+	unsigned int num_tx_queues;
 	unsigned int max_vid;
 	unsigned int max_sid;
 	unsigned int port_base_addr;
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index 2a2c4fb61a65..cc27b2994c6b 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -179,8 +179,17 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
 			dsa_header[2] &= ~0x10;
 		}
 	} else {
+		u16 queue = skb_get_queue_mapping(skb) & 0x7;
 		u16 vid;
 
+		/* The PRI field is 3 bits. According to the documentation the
+		 * 2 highest bits specify the egress queue in From_CPU DSA
+		 * tagged frames. On devices with 8 queues it's possible to
+		 * send to the 8 queues, which means the 3 bits are used.
+		 */
+		if (dp->ds->num_tx_queues == 4)
+			queue <<= 1;
+
 		vid = br_dev ? MV88E6XXX_VID_BRIDGED : MV88E6XXX_VID_STANDALONE;
 
 		skb_push(skb, DSA_HLEN + extra);
@@ -191,7 +200,7 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
 
 		dsa_header[0] = (cmd << 6) | tag_dev;
 		dsa_header[1] = tag_port << 3;
-		dsa_header[2] = vid >> 8;
+		dsa_header[2] = (queue << 5) | vid >> 8;
 		dsa_header[3] = vid & 0xff;
 	}
 

-- 
2.43.0



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

* [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-28 16:09 [PATCH net-next v4 0/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
  2026-05-28 16:09 ` [PATCH net-next v4 1/2] net: dsa: mv88e6xxx: use the hw tx queues Cedric Jehasse via B4 Relay
@ 2026-05-28 16:09 ` Cedric Jehasse via B4 Relay
  2026-05-28 23:48   ` Luke Howard
  1 sibling, 1 reply; 9+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-05-28 16:09 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King
  Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
	Cedric Jehasse, Cedric Jehasse

From: Cedric Jehasse <cedric.jehasse@luminex.be>

Some of the chips supported by this driver have credit based shaper
support. Support is added for the 6341, 6352, 6390 and 6393 families.
This is configured using the Qav registers in the AVB register block.
There are small differences in the Qav registers between the chip
families (eg. the unit used for the rate and number of bits in the
registers). mv88e6xxx_qav_info is introduced to configure this per chip.

Eg. setting up 20mbps credit based shaper on a 1GBit link:
tc qdisc add dev p8 parent root handle 100: mqprio \
                num_tc 8 \
                map 0 0 6 7 0 5 0 0 0 0 0 0 0 0 0 0 \
                queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \
                hw 0

tc qdisc replace dev p8 parent 100:8 cbs locredit -1470 hicredit 30 \
    sendslope -980000 idleslope 20000 offload 1

Note: only idleslope and hicredit can be programmed in the switch
registers, other parameters won't affect settings.

Signed-off-by: Cedric Jehasse <cedric.jehasse@luminex.be>
---
 drivers/net/dsa/mv88e6xxx/chip.c        | 130 ++++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h        |  20 +++++
 drivers/net/dsa/mv88e6xxx/global2.h     |   3 +
 drivers/net/dsa/mv88e6xxx/global2_avb.c |  21 ++++++
 drivers/net/dsa/mv88e6xxx/port.c        |  63 ++++++++++++++++
 drivers/net/dsa/mv88e6xxx/port.h        |  20 +++++
 6 files changed, 257 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index ffd4fa41b7c5..5dc5961f37d6 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -32,6 +32,7 @@
 #include <linux/gpio/consumer.h>
 #include <linux/phylink.h>
 #include <net/dsa.h>
+#include <net/pkt_sched.h>
 
 #include "chip.h"
 #include "devlink.h"
@@ -5015,6 +5016,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
 	.port_set_ether_type = mv88e6351_port_set_ether_type,
 	.port_set_jumbo_size = mv88e6165_port_set_jumbo_size,
 	.port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting,
+	.port_set_scheduling_mode = mv88e6352_port_set_scheduling_mode,
 	.port_pause_limit = mv88e6097_port_pause_limit,
 	.port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
 	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
@@ -5288,6 +5290,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
 	.port_set_ether_type = mv88e6351_port_set_ether_type,
 	.port_set_jumbo_size = mv88e6165_port_set_jumbo_size,
 	.port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting,
+	.port_set_scheduling_mode = mv88e6352_port_set_scheduling_mode,
 	.port_pause_limit = mv88e6097_port_pause_limit,
 	.port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
 	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
@@ -5446,6 +5449,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
 	.port_set_ether_type = mv88e6351_port_set_ether_type,
 	.port_set_jumbo_size = mv88e6165_port_set_jumbo_size,
 	.port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting,
+	.port_set_scheduling_mode = mv88e6352_port_set_scheduling_mode,
 	.port_pause_limit = mv88e6097_port_pause_limit,
 	.port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
 	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
@@ -5515,6 +5519,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
 	.port_get_cmode = mv88e6352_port_get_cmode,
 	.port_set_cmode = mv88e6390_port_set_cmode,
 	.port_setup_message_port = mv88e6xxx_setup_message_port,
+	.port_set_scheduling_mode = mv88e6390_port_set_scheduling_mode,
 	.stats_snapshot = mv88e6390_g1_stats_snapshot,
 	.stats_set_histogram = mv88e6390_g1_stats_set_histogram,
 	.stats_get_sset_count = mv88e6320_stats_get_sset_count,
@@ -5580,6 +5585,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
 	.port_get_cmode = mv88e6352_port_get_cmode,
 	.port_set_cmode = mv88e6390x_port_set_cmode,
 	.port_setup_message_port = mv88e6xxx_setup_message_port,
+	.port_set_scheduling_mode = mv88e6390_port_set_scheduling_mode,
 	.stats_snapshot = mv88e6390_g1_stats_snapshot,
 	.stats_set_histogram = mv88e6390_g1_stats_set_histogram,
 	.stats_get_sset_count = mv88e6320_stats_get_sset_count,
@@ -5637,6 +5643,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.port_set_ether_type = mv88e6393x_port_set_ether_type,
 	.port_set_jumbo_size = mv88e6165_port_set_jumbo_size,
 	.port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting,
+	.port_set_scheduling_mode = mv88e6390_port_set_scheduling_mode,
 	.port_pause_limit = mv88e6390_port_pause_limit,
 	.port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,
 	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
@@ -5679,6 +5686,27 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.tcam_ops = &mv88e6393_tcam_ops,
 };
 
+static const struct mv88e6xxx_qav_info mv88e6352_qav_info = {
+	.rate_unit = 32,
+	.rate_mask = GENMASK(14, 0),
+	.hi_limit_mask = GENMASK(14, 0),
+	.queue_mask = GENMASK(3, 0),
+};
+
+static const struct mv88e6xxx_qav_info mv88e6341_qav_info = {
+	.rate_unit = 64,
+	.rate_mask = GENMASK(15, 0),
+	.hi_limit_mask = GENMASK(14, 0),
+	.queue_mask = GENMASK(3, 0),
+};
+
+static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
+	.rate_unit = 64,
+	.rate_mask = GENMASK(15, 0),
+	.hi_limit_mask = GENMASK(13, 0),
+	.queue_mask = GENMASK(7, 0),
+};
+
 static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 	[MV88E6020] = {
 		.prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6020,
@@ -6243,6 +6271,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.qav = &mv88e6352_qav_info,
 		.ops = &mv88e6240_ops,
 	},
 
@@ -6380,6 +6409,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.qav = &mv88e6341_qav_info,
 		.ops = &mv88e6341_ops,
 	},
 
@@ -6460,6 +6490,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_SUPPORTED,
 		.ptp_support = true,
+		.qav = &mv88e6352_qav_info,
 		.ops = &mv88e6352_ops,
 	},
 	[MV88E6361] = {
@@ -6517,6 +6548,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED,
 		.ptp_support = true,
+		.qav = &mv88e6390_qav_info,
 		.ops = &mv88e6390_ops,
 	},
 	[MV88E6390X] = {
@@ -6544,6 +6576,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.multi_chip = true,
 		.edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED,
 		.ptp_support = true,
+		.qav = &mv88e6390_qav_info,
 		.ops = &mv88e6390x_ops,
 	},
 
@@ -6572,6 +6605,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
 		.pvt = true,
 		.multi_chip = true,
 		.ptp_support = true,
+		.qav = &mv88e6390_qav_info,
 		.ops = &mv88e6393x_ops,
 	},
 };
@@ -7193,6 +7227,101 @@ static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
 	return err_sync ? : err_pvt;
 }
 
+static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
+				  struct tc_cbs_qopt_offload *cbs)
+{
+	const struct mv88e6xxx_avb_ops *avb_ops;
+	struct mv88e6xxx_chip *chip = ds->priv;
+	const struct mv88e6xxx_qav_info *qav;
+	const struct mv88e6xxx_ops *ops;
+	int hilimit_reg;
+	int rate_reg;
+	u8 queue_bit;
+	u16 hi_limit;
+	u16 rate = 0;
+	int err;
+
+	ops = chip->info->ops;
+	avb_ops = ops->avb_ops;
+	qav = chip->info->qav;
+
+	if (!qav || !avb_ops || !avb_ops->port_qav_write ||
+	    !ops->port_set_scheduling_mode)
+		return -EOPNOTSUPP;
+
+	if (!dsa_is_user_port(ds, port))
+		return -EOPNOTSUPP;
+
+	if (!(qav->queue_mask & BIT(cbs->queue)))
+		return -EOPNOTSUPP;
+
+	queue_bit = BIT(cbs->queue);
+	rate_reg = MV88E6XXX_PORT_QAV_CFG_RATE(cbs->queue);
+	hilimit_reg = MV88E6XXX_PORT_QAV_CFG_HI_LIMIT(cbs->queue);
+
+	if (cbs->enable) {
+		if (cbs->hicredit <= 0 ||
+		    cbs->hicredit > qav->hi_limit_mask)
+			return -ERANGE;
+
+		rate = DIV_ROUND_UP(cbs->idleslope, qav->rate_unit);
+		if (rate > qav->rate_mask)
+			return -ERANGE;
+		/* avoid using zero rate */
+		rate = max_t(u16, rate, 1);
+	}
+
+	mv88e6xxx_reg_lock(chip);
+
+	if (!cbs->enable) {
+		err = mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
+		if (err)
+			goto unlock;
+
+		if (!(chip->ports[port].cbs_active_queues & ~queue_bit)) {
+			err = mv88e6xxx_port_set_scheduling_mode(chip, port, 0);
+			if (err)
+				goto unlock;
+		}
+		chip->ports[port].cbs_active_queues &= ~queue_bit;
+		goto unlock;
+	}
+
+	hi_limit = cbs->hicredit & qav->hi_limit_mask;
+	err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hi_limit);
+	if (err)
+		goto unlock;
+
+	err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
+	if (err)
+		goto unlock;
+
+	err = mv88e6xxx_port_set_scheduling_mode(chip, port,
+						 chip->info->num_tx_queues - 1);
+	if (err) {
+		mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
+		chip->ports[port].cbs_active_queues &= ~queue_bit;
+		goto unlock;
+	}
+	chip->ports[port].cbs_active_queues |= queue_bit;
+
+unlock:
+	mv88e6xxx_reg_unlock(chip);
+
+	return err;
+}
+
+static int mv88e6xxx_port_setup_tc(struct dsa_switch *ds, int port,
+				   enum tc_setup_type type, void *type_data)
+{
+	switch (type) {
+	case TC_SETUP_QDISC_CBS:
+		return mv88e6xxx_setup_tc_cbs(ds, port, type_data);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
 static const struct phylink_mac_ops mv88e6xxx_phylink_mac_ops = {
 	.mac_select_pcs		= mv88e6xxx_mac_select_pcs,
 	.mac_prepare		= mv88e6xxx_mac_prepare,
@@ -7252,6 +7381,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
 	.port_hwtstamp_get	= mv88e6xxx_port_hwtstamp_get,
 	.port_txtstamp		= mv88e6xxx_port_txtstamp,
 	.port_rxtstamp		= mv88e6xxx_port_rxtstamp,
+	.port_setup_tc		= mv88e6xxx_port_setup_tc,
 	.cls_flower_add		= mv88e6xxx_cls_flower_add,
 	.cls_flower_del         = mv88e6xxx_cls_flower_del,
 	.get_ts_info		= mv88e6xxx_get_ts_info,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 19d8eda19b78..8768d599f8fc 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -125,6 +125,7 @@ enum mv88e6xxx_edsa_support {
 };
 
 struct mv88e6xxx_ops;
+struct mv88e6xxx_qav_info;
 
 struct mv88e6xxx_info {
 	enum mv88e6xxx_family family;
@@ -177,6 +178,9 @@ struct mv88e6xxx_info {
 	/* Supports PTP */
 	bool ptp_support;
 
+	/* 802.1Qav credit based shaping */
+	const struct mv88e6xxx_qav_info *qav;
+
 	/* Internal PHY start index. 0 means that internal PHYs range starts at
 	 * port 0, 1 means internal PHYs range starts at port 1, etc
 	 */
@@ -304,6 +308,9 @@ struct mv88e6xxx_port {
 
 	/* MacAuth Bypass control flag */
 	bool mab;
+
+	/* Queues with CBS currently enabled. */
+	u8 cbs_active_queues;
 };
 
 enum mv88e6xxx_region_id {
@@ -607,6 +614,8 @@ struct mv88e6xxx_ops {
 				   size_t size);
 
 	int (*port_egress_rate_limiting)(struct mv88e6xxx_chip *chip, int port);
+	int (*port_set_scheduling_mode)(struct mv88e6xxx_chip *chip, int port,
+					u8 mode);
 	int (*port_pause_limit)(struct mv88e6xxx_chip *chip, int port, u8 in,
 				u8 out);
 	int (*port_disable_learn_limit)(struct mv88e6xxx_chip *chip, int port);
@@ -764,6 +773,10 @@ 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 802.1Qav registers */
+	int (*port_qav_write)(struct mv88e6xxx_chip *chip, int port, int addr,
+			      u16 data);
 };
 
 struct mv88e6xxx_ptp_ops {
@@ -799,6 +812,13 @@ struct mv88e6xxx_tcam_ops {
 	int (*flush_tcam)(struct mv88e6xxx_chip *chip);
 };
 
+struct mv88e6xxx_qav_info {
+	u16 rate_unit; /* in kbps */
+	u16 rate_mask; /* QPri Rate valid bits mask */
+	u16 hi_limit_mask; /* Qpri Hi Limit bits mask*/
+	u8 queue_mask; /* supported queues bitmask */
+};
+
 static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
 {
 	return chip->info->max_sid > 0 &&
diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h
index 82f9b410de0b..f1cdc33dd418 100644
--- a/drivers/net/dsa/mv88e6xxx/global2.h
+++ b/drivers/net/dsa/mv88e6xxx/global2.h
@@ -186,6 +186,9 @@
 #define MV88E6352_G2_AVB_CMD_BLOCK_MASK		0x00e0
 #define MV88E6352_G2_AVB_CMD_ADDR_MASK		0x001f
 
+#define MV88E6XXX_PORT_QAV_CFG_RATE(queue)	(((queue) & 0x7) << 1)
+#define MV88E6XXX_PORT_QAV_CFG_HI_LIMIT(queue)	((((queue) & 0x7) << 1) + 1)
+
 /* 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 657783e043ff..6b54e275d21a 100644
--- a/drivers/net/dsa/mv88e6xxx/global2_avb.c
+++ b/drivers/net/dsa/mv88e6xxx/global2_avb.c
@@ -110,6 +110,15 @@ static int mv88e6352_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip,
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
 
+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) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 static int mv88e6352_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
 				     u16 *data, int len)
 {
@@ -149,6 +158,7 @@ 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_qav_write		= mv88e6352_g2_avb_port_qav_write,
 };
 
 static int mv88e6165_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
@@ -174,6 +184,7 @@ 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_qav_write		= mv88e6352_g2_avb_port_qav_write,
 };
 
 static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
@@ -197,6 +208,15 @@ static int mv88e6390_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip,
 	return mv88e6xxx_g2_avb_write(chip, writeop, data);
 }
 
+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) |
+		      (MV88E6352_G2_AVB_CMD_BLOCK_QAV << 5) | addr;
+
+	return mv88e6xxx_g2_avb_write(chip, writeop, data);
+}
+
 static int mv88e6390_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
 				     u16 *data, int len)
 {
@@ -236,4 +256,5 @@ 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_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 c90117d2dd83..23d1435db0d8 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1348,6 +1348,51 @@ int mv88e6097_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port)
 				    0x0001);
 }
 
+int mv88e6352_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode)
+{
+	u16 reg;
+	int err;
+
+	if (mode > 3)
+		return -EINVAL;
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_EGRESS_RATE_CTL2,
+				  &reg);
+	if (err)
+		return err;
+
+	reg &= ~MV88E6XXX_PORT_EGRESS_RATE_CTL2_SCHEDULE_MASK;
+	reg |= mode << MV88E6XXX_PORT_EGRESS_RATE_CTL2_SCHEDULE_SHIFT;
+
+	return mv88e6xxx_port_write(chip, port,
+				    MV88E6XXX_PORT_EGRESS_RATE_CTL2, reg);
+}
+
+int mv88e6390_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode)
+{
+	u16 reg;
+	int err;
+
+	if (mode > MV88E6390_PORT_QUEUE_CTL_SCHEDULE_MASK)
+		return -EINVAL;
+
+	reg = MV88E6390_PORT_QUEUE_CTL_UPDATE |
+	      (MV88E6390_PORT_QUEUE_CTL_SCHEDULE <<
+	       MV88E6390_PORT_QUEUE_CTL_PTR_SHIFT) |
+	      (mode & MV88E6390_PORT_QUEUE_CTL_SCHEDULE_MASK);
+
+	err = mv88e6xxx_port_write(chip, port, MV88E6390_PORT_QUEUE_CTL,
+				   reg);
+	if (err)
+		return err;
+
+	return mv88e6xxx_port_wait_bit(chip, port, MV88E6390_PORT_QUEUE_CTL,
+				       __bf_shf(MV88E6390_PORT_QUEUE_CTL_UPDATE)
+				       , 0);
+}
+
 /* Offset 0x0B: Port Association Vector */
 
 int mv88e6xxx_port_set_assoc_vector(struct mv88e6xxx_chip *chip, int port,
@@ -1761,3 +1806,21 @@ int mv88e6393x_port_set_policy(struct mv88e6xxx_chip *chip, int port,
 
 	return mv88e6393x_port_policy_write(chip, port, ptr, reg);
 }
+
+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);
+}
+
+int mv88e6xxx_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode)
+{
+	if (!chip->info->ops->port_set_scheduling_mode)
+		return -EOPNOTSUPP;
+
+	return chip->info->ops->port_set_scheduling_mode(chip, port, mode);
+}
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index f6041f91215e..cf8655a13729 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -241,6 +241,18 @@
 
 /* Offset 0x0A: Egress Rate Control 2 */
 #define MV88E6XXX_PORT_EGRESS_RATE_CTL2		0x0a
+#define MV88E6XXX_PORT_EGRESS_RATE_CTL2_SCHEDULE_MASK	0x3000
+#define MV88E6XXX_PORT_EGRESS_RATE_CTL2_SCHEDULE_SHIFT	12
+
+/* Offset 0x1C: Port Queue Control */
+#define MV88E6390_PORT_QUEUE_CTL			0x1c
+#define MV88E6390_PORT_QUEUE_CTL_UPDATE			0x8000
+#define MV88E6390_PORT_QUEUE_CTL_PTR_MASK		0x7f00
+#define MV88E6390_PORT_QUEUE_CTL_PTR_SHIFT		8
+#define MV88E6390_PORT_QUEUE_CTL_DATA_MASK		0x00ff
+#define MV88E6390_PORT_QUEUE_CTL_SCHEDULE		0x00
+#define MV88E6390_PORT_QUEUE_CTL_SCHEDULE_MASK		0x07
+
 
 /* Offset 0x0B: Port Association Vector */
 #define MV88E6XXX_PORT_ASSOC_VECTOR			0x0b
@@ -569,6 +581,10 @@ int mv88e6165_port_set_jumbo_size(struct mv88e6xxx_chip *chip, int port,
 				  size_t size);
 int mv88e6095_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
 int mv88e6097_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
+int mv88e6352_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode);
+int mv88e6390_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode);
 int mv88e6xxx_port_set_assoc_vector(struct mv88e6xxx_chip *chip, int port,
 				    u16 pav);
 int mv88e6097_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
@@ -613,5 +629,9 @@ int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
 			       int reg, u16 *val);
 
 int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port);
+int mv88e6xxx_port_qav_write(struct mv88e6xxx_chip *chip, int port, int addr,
+			     u16 data);
+int mv88e6xxx_port_set_scheduling_mode(struct mv88e6xxx_chip *chip, int port,
+				       u8 mode);
 
 #endif /* _MV88E6XXX_PORT_H */

-- 
2.43.0



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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-28 16:09 ` [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
@ 2026-05-28 23:48   ` Luke Howard
  2026-05-29 12:32     ` Cedric Jehasse
  0 siblings, 1 reply; 9+ messages in thread
From: Luke Howard @ 2026-05-28 23:48 UTC (permalink / raw)
  To: cedric.jehasse
  Cc: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King, netdev,
	linux-kernel, Marek Behún, Cedric Jehasse

Hi Cedric,

Looking good!

> +static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
> +  struct tc_cbs_qopt_offload *cbs)
> +{
> + const struct mv88e6xxx_avb_ops *avb_ops;
> + struct mv88e6xxx_chip *chip = ds->priv;
> + const struct mv88e6xxx_qav_info *qav;
> + const struct mv88e6xxx_ops *ops;
> + int hilimit_reg;
> + int rate_reg;
> + u8 queue_bit;
> + u16 hi_limit;
> + u16 rate = 0;
> + int err;
> +
> + ops = chip->info->ops;
> + avb_ops = ops->avb_ops;
> + qav = chip->info->qav;
> +
> + if (!qav || !avb_ops || !avb_ops->port_qav_write ||
> +    !ops->port_set_scheduling_mode)
> + return -EOPNOTSUPP;

Only the first check should be necessary because the wrapper functions return -EOPNOTSUPP in the case of a NULL function pointer.

> + if (!(chip->ports[port].cbs_active_queues & ~queue_bit)) {
> + err = mv88e6xxx_port_set_scheduling_mode(chip, port, 0);
> + if (err)
> + goto unlock;
> + }

Missing a newline here after closing brace.

> + chip->ports[port].cbs_active_queues &= ~queue_bit;
> + goto unlock;
> + }

The control flow might be clearer were goto unlock only taken in case of error. i.e. wrap the CBS enable case in an else block.

> +
> + hi_limit = cbs->hicredit & qav->hi_limit_mask;
> + err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hi_limit);
> + if (err)
> + goto unlock;
> +
> + err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
> + if (err)
> + goto unlock;
> +
> + err = mv88e6xxx_port_set_scheduling_mode(chip, port,
> + chip->info->num_tx_queues - 1);

For the 6020 (which you brought to my attention as only supporting CBS on half its four queues), should strict priority be enabled only on the CBS queues or on all queues? (I don’t know the answer, just asking.)

Cheers,
Luke


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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-28 23:48   ` Luke Howard
@ 2026-05-29 12:32     ` Cedric Jehasse
  2026-05-29 12:45       ` Luke Howard
  0 siblings, 1 reply; 9+ messages in thread
From: Cedric Jehasse @ 2026-05-29 12:32 UTC (permalink / raw)
  To: Luke Howard
  Cc: cedric.jehasse, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Russell King, netdev, linux-kernel, Marek Behún

On Fri, May 29, 2026 at 09:48:33AM +1000, Luke Howard wrote:
> > +static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
> > +  struct tc_cbs_qopt_offload *cbs)
> > +{
> > + const struct mv88e6xxx_avb_ops *avb_ops;
> > + struct mv88e6xxx_chip *chip = ds->priv;
> > + const struct mv88e6xxx_qav_info *qav;
> > + const struct mv88e6xxx_ops *ops;
> > + int hilimit_reg;
> > + int rate_reg;
> > + u8 queue_bit;
> > + u16 hi_limit;
> > + u16 rate = 0;
> > + int err;
> > +
> > + ops = chip->info->ops;
> > + avb_ops = ops->avb_ops;
> > + qav = chip->info->qav;
> > +
> > + if (!qav || !avb_ops || !avb_ops->port_qav_write ||
> > +    !ops->port_set_scheduling_mode)
> > + return -EOPNOTSUPP;
> 
> Only the first check should be necessary because the wrapper functions return -EOPNOTSUPP in the case of a NULL function pointer.
> 
That's not the same logic.
If one of the two function pointer is NULL, the other can still be executed
(Maybe not that likely as that's probably a mistake in the driver)

> > + if (!(chip->ports[port].cbs_active_queues & ~queue_bit)) {
> > + err = mv88e6xxx_port_set_scheduling_mode(chip, port, 0);
> > + if (err)
> > + goto unlock;
> > + }
> 
> Missing a newline here after closing brace.

Is that a personal preference, or a guideline i've missed?

> 
> > + chip->ports[port].cbs_active_queues &= ~queue_bit;
> > + goto unlock;
> > + }
> 
> The control flow might be clearer were goto unlock only taken in case of error. i.e. wrap the CBS enable case in an else block.
> 
I don't think it makes that much difference.
You've mentioned the felix driver before, that has similar flow :) (with a
return in the disable case isof goto)

> > +
> > + hi_limit = cbs->hicredit & qav->hi_limit_mask;
> > + err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hi_limit);
> > + if (err)
> > + goto unlock;
> > +
> > + err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
> > + if (err)
> > + goto unlock;
> > +
> > + err = mv88e6xxx_port_set_scheduling_mode(chip, port,
> > + chip->info->num_tx_queues - 1);
> 
> For the 6020 (which you brought to my attention as only supporting CBS on half its four queues), should strict priority be enabled only on the CBS queues or on all queues? (I don’t know the answer, just asking.)
> 
I hadn't thought about that. The 6020 has the same strict priority settings
registers as the other devices, it doesn't need special handling.


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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-29 12:32     ` Cedric Jehasse
@ 2026-05-29 12:45       ` Luke Howard
  2026-05-29 13:28         ` Andrew Lunn
  0 siblings, 1 reply; 9+ messages in thread
From: Luke Howard @ 2026-05-29 12:45 UTC (permalink / raw)
  To: Cedric Jehasse
  Cc: cedric.jehasse, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Russell King, netdev, linux-kernel, Marek Behún

Hi Cedric,

> On 29 May 2026, at 10:32 pm, Cedric Jehasse <cedric.jehasse@gmail.com> wrote:
> 
> On Fri, May 29, 2026 at 09:48:33AM +1000, Luke Howard wrote:
>>> +static int mv88e6xxx_setup_tc_cbs(struct dsa_switch *ds, int port,
>>> +  struct tc_cbs_qopt_offload *cbs)
>>> +{
>>> + const struct mv88e6xxx_avb_ops *avb_ops;
>>> + struct mv88e6xxx_chip *chip = ds->priv;
>>> + const struct mv88e6xxx_qav_info *qav;
>>> + const struct mv88e6xxx_ops *ops;
>>> + int hilimit_reg;
>>> + int rate_reg;
>>> + u8 queue_bit;
>>> + u16 hi_limit;
>>> + u16 rate = 0;
>>> + int err;
>>> +
>>> + ops = chip->info->ops;
>>> + avb_ops = ops->avb_ops;
>>> + qav = chip->info->qav;

These could be initialised at the top of the function, might save some lines…

>>> + if (!qav || !avb_ops || !avb_ops->port_qav_write ||
>>> +    !ops->port_set_scheduling_mode)
>>> + return -EOPNOTSUPP;
>> 
>> Only the first check should be necessary because the wrapper functions return -EOPNOTSUPP in the case of a NULL function pointer.
>> 
> That's not the same logic.
> If one of the two function pointer is NULL, the other can still be executed
> (Maybe not that likely as that's probably a mistake in the driver)

True, but agree also that it shouldn’t happen (would be a driver bug).

>>> + if (!(chip->ports[port].cbs_active_queues & ~queue_bit)) {
>>> + err = mv88e6xxx_port_set_scheduling_mode(chip, port, 0);
>>> + if (err)
>>> + goto unlock;
>>> + }
>> 
>> Missing a newline here after closing brace.
> 
> Is that a personal preference, or a guideline I’ve missed?

You’re right, I had just assumed it was because it’s the case in the rest of the driver (97% adherence according to Claude :))

BTW, I have a (as yet untested) MQPRIO patch rebased on your v4 CBS patch. In DCB mode, TCs represent AVB classes (as my previous patch did). In CHANNEL mode, they represent queues, so you can flexibly map priorities to queues, with the additional advantage that these mappings can be per-port on the 6390.

It’s potentially a questionable overloading of the MQPRIO modes but, it’s a starting point for discussion, and it avoids needing out-of-band configuration (e.g. command line, devlink, etc, although I am now using devlink to set the AVB mode rather than the device tree).

Cheers,
Luke

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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-29 12:45       ` Luke Howard
@ 2026-05-29 13:28         ` Andrew Lunn
  2026-05-29 14:32           ` Cedric Jehasse
  0 siblings, 1 reply; 9+ messages in thread
From: Andrew Lunn @ 2026-05-29 13:28 UTC (permalink / raw)
  To: Luke Howard
  Cc: Cedric Jehasse, cedric.jehasse, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Russell King, netdev, linux-kernel, Marek Behún

> > That's not the same logic.
> > If one of the two function pointer is NULL, the other can still be executed
> > (Maybe not that likely as that's probably a mistake in the driver)
> 
> True, but agree also that it shouldn’t happen (would be a driver bug).

However, a kernel splat because you have jumped through a NULL pointer
makes a good bug report. It is much more obvious what is wrong than a
report that somebody tried to configure TC, but nothing appeared to
happen.

	Andrew

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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-29 13:28         ` Andrew Lunn
@ 2026-05-29 14:32           ` Cedric Jehasse
  2026-05-29 14:38             ` Andrew Lunn
  0 siblings, 1 reply; 9+ messages in thread
From: Cedric Jehasse @ 2026-05-29 14:32 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Luke Howard, cedric.jehasse, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Russell King, netdev, linux-kernel, Marek Behún

On Fri, May 29, 2026 at 03:28:56PM +0200, Andrew Lunn wrote:
> > > That's not the same logic.
> > > If one of the two function pointer is NULL, the other can still be executed
> > > (Maybe not that likely as that's probably a mistake in the driver)
> > 
> > True, but agree also that it shouldn’t happen (would be a driver bug).
> 
> However, a kernel splat because you have jumped through a NULL pointer
> makes a good bug report. It is much more obvious what is wrong than a
> report that somebody tried to configure TC, but nothing appeared to
> happen.
>

Removing !avb_ops->port_qav_write || !ops->port_set_scheduling_mode won't make
it jump to a NULL pointer. mv88e6xxx_port_qav_write and
mv88e6xxx_port_set_scheduling_mode will still return -EOPNOTSUPP.
Do you prefer removing the NULL pointer checks in those functions?

Cedric

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

* Re: [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper
  2026-05-29 14:32           ` Cedric Jehasse
@ 2026-05-29 14:38             ` Andrew Lunn
  0 siblings, 0 replies; 9+ messages in thread
From: Andrew Lunn @ 2026-05-29 14:38 UTC (permalink / raw)
  To: Cedric Jehasse
  Cc: Luke Howard, cedric.jehasse, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Russell King, netdev, linux-kernel, Marek Behún

On Fri, May 29, 2026 at 04:32:15PM +0200, Cedric Jehasse wrote:
> On Fri, May 29, 2026 at 03:28:56PM +0200, Andrew Lunn wrote:
> > > > That's not the same logic.
> > > > If one of the two function pointer is NULL, the other can still be executed
> > > > (Maybe not that likely as that's probably a mistake in the driver)
> > > 
> > > True, but agree also that it shouldn’t happen (would be a driver bug).
> > 
> > However, a kernel splat because you have jumped through a NULL pointer
> > makes a good bug report. It is much more obvious what is wrong than a
> > report that somebody tried to configure TC, but nothing appeared to
> > happen.
> >
> 
> Removing !avb_ops->port_qav_write || !ops->port_set_scheduling_mode won't make
> it jump to a NULL pointer. mv88e6xxx_port_qav_write and
> mv88e6xxx_port_set_scheduling_mode will still return -EOPNOTSUPP.
> Do you prefer removing the NULL pointer checks in those functions?

No, leave them. Its just people have an aversion to kernel splats, but
they can actually be useful if they highlight a bug.

     Andrew

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

end of thread, other threads:[~2026-05-29 14:39 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-28 16:09 [PATCH net-next v4 0/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
2026-05-28 16:09 ` [PATCH net-next v4 1/2] net: dsa: mv88e6xxx: use the hw tx queues Cedric Jehasse via B4 Relay
2026-05-28 16:09 ` [PATCH net-next v4 2/2] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
2026-05-28 23:48   ` Luke Howard
2026-05-29 12:32     ` Cedric Jehasse
2026-05-29 12:45       ` Luke Howard
2026-05-29 13:28         ` Andrew Lunn
2026-05-29 14:32           ` Cedric Jehasse
2026-05-29 14:38             ` Andrew Lunn

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

all inboxes | Powered by JetHome®