* [PATCH net-next v8 0/3] net: dsa: mv88e6xxx: add support for credit based shaper
@ 2026-08-11 11:45 ` Cedric Jehasse
0 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
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 v8:
- add an extack into tc_cbs_qopt_offload and use it to report the reason
for rejection back to the user (Jakub)
- Link to v7: https://lore.kernel.org/r/20260609-net-next-mv88e6xxx-cbs-v7-0-7d5d732df545@luminex.be
Changes in v7:
- fix incorrect hilimit_mask for the 6341
- remove dead code in rollback (clearing a bit in cbs_active_queue which
is already 0)
- fix typo in commit message
- Link to v6: https://lore.kernel.org/r/20260604-net-next-mv88e6xxx-cbs-v6-0-b68c01142d1e@luminex.be
Changes in v6:
- consitently use hilimit naming isof a mixing hilimit and hi_limit
- Link to v5: https://lore.kernel.org/r/20260602-net-next-mv88e6xxx-cbs-v5-0-1969758d1fa3@luminex.be
Changes in v5:
- only set the scheduling mode when no queues are active
- use u32 for rate calculation to avoid truncation
- Link to v4: https://lore.kernel.org/r/20260528-net-next-mv88e6xxx-cbs-v4-0-8bd13b906457@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 (3):
net/sched: cbs: add extack to struct tc_cbs_qopt_offload
net: dsa: mv88e6xxx: use the hw tx queues
net: dsa: mv88e6xxx: add support for credit based shaper
drivers/net/dsa/mv88e6xxx/chip.c | 176 ++++++++++++++++++++++++++++++++
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 ++++
include/net/pkt_sched.h | 1 +
net/dsa/tag_dsa.c | 11 +-
net/sched/sch_cbs.c | 4 +-
9 files changed, 318 insertions(+), 2 deletions(-)
---
base-commit: 4e8f1e2c1e3b95fc86754e40c43d8483e01ff806
change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68
Best regards,
--
Cedric Jehasse <cedric.jehasse@luminex.be>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v8 0/3] net: dsa: mv88e6xxx: add support for credit based shaper
@ 2026-08-11 11:45 ` Cedric Jehasse
0 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
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 v8:
- add an extack into tc_cbs_qopt_offload and use it to report the reason
for rejection back to the user (Jakub)
- Link to v7: https://lore.kernel.org/r/20260609-net-next-mv88e6xxx-cbs-v7-0-7d5d732df545@luminex.be
Changes in v7:
- fix incorrect hilimit_mask for the 6341
- remove dead code in rollback (clearing a bit in cbs_active_queue which
is already 0)
- fix typo in commit message
- Link to v6: https://lore.kernel.org/r/20260604-net-next-mv88e6xxx-cbs-v6-0-b68c01142d1e@luminex.be
Changes in v6:
- consitently use hilimit naming isof a mixing hilimit and hi_limit
- Link to v5: https://lore.kernel.org/r/20260602-net-next-mv88e6xxx-cbs-v5-0-1969758d1fa3@luminex.be
Changes in v5:
- only set the scheduling mode when no queues are active
- use u32 for rate calculation to avoid truncation
- Link to v4: https://lore.kernel.org/r/20260528-net-next-mv88e6xxx-cbs-v4-0-8bd13b906457@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 (3):
net/sched: cbs: add extack to struct tc_cbs_qopt_offload
net: dsa: mv88e6xxx: use the hw tx queues
net: dsa: mv88e6xxx: add support for credit based shaper
drivers/net/dsa/mv88e6xxx/chip.c | 176 ++++++++++++++++++++++++++++++++
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 ++++
include/net/pkt_sched.h | 1 +
net/dsa/tag_dsa.c | 11 +-
net/sched/sch_cbs.c | 4 +-
9 files changed, 318 insertions(+), 2 deletions(-)
---
base-commit: 4e8f1e2c1e3b95fc86754e40c43d8483e01ff806
change-id: 20260430-net-next-mv88e6xxx-cbs-2121169caa68
Best regards,
--
Cedric Jehasse <cedric.jehasse@luminex.be>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v8 1/3] net/sched: cbs: add extack to struct tc_cbs_qopt_offload
2026-08-11 11:45 ` Cedric Jehasse
@ 2026-08-11 11:45 ` Cedric Jehasse
-1 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
Cedric Jehasse, Cedric Jehasse
From: Cedric Jehasse <cedric.jehasse@luminex.be>
To be able to report reason for rejection when cbs offloading fails.
Signed-off-by: Cedric Jehasse <cedric.jehasse@luminex.be>
---
include/net/pkt_sched.h | 1 +
net/sched/sch_cbs.c | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h
index 18a419cd9d949..d35c87004c279 100644
--- a/include/net/pkt_sched.h
+++ b/include/net/pkt_sched.h
@@ -150,6 +150,7 @@ struct tc_cbs_qopt_offload {
s32 locredit;
s32 idleslope;
s32 sendslope;
+ struct netlink_ext_ack *extack;
};
struct tc_etf_qopt_offload {
diff --git a/net/sched/sch_cbs.c b/net/sched/sch_cbs.c
index 1c93469c56e3d..87c2eda108c8c 100644
--- a/net/sched/sch_cbs.c
+++ b/net/sched/sch_cbs.c
@@ -307,10 +307,12 @@ static int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q,
cbs.locredit = opt->locredit;
cbs.idleslope = opt->idleslope;
cbs.sendslope = opt->sendslope;
+ cbs.extack = extack;
err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs);
if (err < 0) {
- NL_SET_ERR_MSG(extack, "Specified device failed to setup cbs hardware offload");
+ NL_SET_ERR_MSG_WEAK(extack,
+ "Specified device failed to setup cbs hardware offload");
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v8 1/3] net/sched: cbs: add extack to struct tc_cbs_qopt_offload
@ 2026-08-11 11:45 ` Cedric Jehasse
0 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
Cedric Jehasse, Cedric Jehasse
To be able to report reason for rejection when cbs offloading fails.
Signed-off-by: Cedric Jehasse <cedric.jehasse@luminex.be>
---
include/net/pkt_sched.h | 1 +
net/sched/sch_cbs.c | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h
index 18a419cd9d949..d35c87004c279 100644
--- a/include/net/pkt_sched.h
+++ b/include/net/pkt_sched.h
@@ -150,6 +150,7 @@ struct tc_cbs_qopt_offload {
s32 locredit;
s32 idleslope;
s32 sendslope;
+ struct netlink_ext_ack *extack;
};
struct tc_etf_qopt_offload {
diff --git a/net/sched/sch_cbs.c b/net/sched/sch_cbs.c
index 1c93469c56e3d..87c2eda108c8c 100644
--- a/net/sched/sch_cbs.c
+++ b/net/sched/sch_cbs.c
@@ -307,10 +307,12 @@ static int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q,
cbs.locredit = opt->locredit;
cbs.idleslope = opt->idleslope;
cbs.sendslope = opt->sendslope;
+ cbs.extack = extack;
err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs);
if (err < 0) {
- NL_SET_ERR_MSG(extack, "Specified device failed to setup cbs hardware offload");
+ NL_SET_ERR_MSG_WEAK(extack,
+ "Specified device failed to setup cbs hardware offload");
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v8 2/3] net: dsa: mv88e6xxx: use the hw tx queues
2026-08-11 11:45 ` Cedric Jehasse
@ 2026-08-11 11:45 ` Cedric Jehasse
-1 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
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->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 80b877c74513d..47e531f24e5f2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3990,6 +3990,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
@@ -5706,6 +5707,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,
@@ -5728,6 +5730,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,
@@ -5750,6 +5753,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,
@@ -5774,6 +5778,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,
@@ -5795,6 +5800,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,
@@ -5820,6 +5826,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,
@@ -5845,6 +5852,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,
@@ -5867,6 +5875,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,
@@ -5892,6 +5901,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,
@@ -5918,6 +5928,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,
@@ -5943,6 +5954,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,
@@ -5969,6 +5981,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,
@@ -5994,6 +6007,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,
@@ -6020,6 +6034,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,
@@ -6045,6 +6060,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,
@@ -6068,6 +6084,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,
@@ -6093,6 +6110,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,
@@ -6117,6 +6135,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,
@@ -6142,6 +6161,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,
@@ -6168,6 +6188,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,
@@ -6198,6 +6219,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,
@@ -6222,6 +6244,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,
@@ -6247,6 +6270,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,
@@ -6271,6 +6295,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,
@@ -6299,6 +6324,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,
@@ -6327,6 +6353,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,
@@ -6354,6 +6381,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,
@@ -6380,6 +6408,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,
@@ -6405,6 +6434,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,
@@ -6431,6 +6461,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,
@@ -6459,6 +6490,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,
@@ -6485,6 +6517,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,
@@ -6512,6 +6545,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,
@@ -6538,6 +6572,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 f4894bdc21ba8..3a1d79a117dca 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 d5ffee35fbb53..2bb71cd343e79 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 related [flat|nested] 10+ messages in thread
* [PATCH net-next v8 2/3] net: dsa: mv88e6xxx: use the hw tx queues
@ 2026-08-11 11:45 ` Cedric Jehasse
0 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
Cedric Jehasse, Cedric Jehasse
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->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 80b877c74513d..47e531f24e5f2 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3990,6 +3990,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
@@ -5706,6 +5707,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,
@@ -5728,6 +5730,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,
@@ -5750,6 +5753,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,
@@ -5774,6 +5778,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,
@@ -5795,6 +5800,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,
@@ -5820,6 +5826,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,
@@ -5845,6 +5852,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,
@@ -5867,6 +5875,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,
@@ -5892,6 +5901,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,
@@ -5918,6 +5928,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,
@@ -5943,6 +5954,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,
@@ -5969,6 +5981,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,
@@ -5994,6 +6007,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,
@@ -6020,6 +6034,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,
@@ -6045,6 +6060,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,
@@ -6068,6 +6084,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,
@@ -6093,6 +6110,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,
@@ -6117,6 +6135,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,
@@ -6142,6 +6161,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,
@@ -6168,6 +6188,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,
@@ -6198,6 +6219,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,
@@ -6222,6 +6244,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,
@@ -6247,6 +6270,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,
@@ -6271,6 +6295,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,
@@ -6299,6 +6324,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,
@@ -6327,6 +6353,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,
@@ -6354,6 +6381,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,
@@ -6380,6 +6408,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,
@@ -6405,6 +6434,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,
@@ -6431,6 +6461,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,
@@ -6459,6 +6490,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,
@@ -6485,6 +6517,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,
@@ -6512,6 +6545,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,
@@ -6538,6 +6572,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 f4894bdc21ba8..3a1d79a117dca 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 d5ffee35fbb53..2bb71cd343e79 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 related [flat|nested] 10+ messages in thread
* [PATCH net-next v8 3/3] net: dsa: mv88e6xxx: add support for credit based shaper
2026-08-11 11:45 ` Cedric Jehasse
@ 2026-08-11 11:45 ` Cedric Jehasse
-1 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse via B4 Relay @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
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 | 141 ++++++++++++++++++++++++++++++++
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, 268 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 47e531f24e5f2..a567ee9b6ce0d 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"
@@ -5024,6 +5025,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,
@@ -5307,6 +5309,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,
@@ -5465,6 +5468,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,
@@ -5534,6 +5538,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,
@@ -5598,6 +5603,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,
@@ -5654,6 +5660,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,
@@ -5696,6 +5703,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),
+ .hilimit_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),
+ .hilimit_mask = GENMASK(13, 0),
+ .queue_mask = GENMASK(3, 0),
+};
+
+static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
+ .rate_unit = 64,
+ .rate_mask = GENMASK(15, 0),
+ .hilimit_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,
@@ -6260,6 +6288,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,
},
@@ -6397,6 +6426,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,
},
@@ -6477,6 +6507,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] = {
@@ -6534,6 +6565,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] = {
@@ -6561,6 +6593,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,
},
@@ -6589,6 +6622,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.pvt = true,
.multi_chip = true,
.ptp_support = true,
+ .qav = &mv88e6390_qav_info,
.ops = &mv88e6393x_ops,
},
};
@@ -7210,6 +7244,112 @@ 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;
+ u32 rate = 0;
+ u16 hilimit;
+ 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))) {
+ NL_SET_ERR_MSG_MOD(cbs->extack, "CBS not supported on queue");
+ return -EOPNOTSUPP;
+ }
+
+ queue_bit = BIT(cbs->queue);
+ rate_reg = MV88E6XXX_PORT_QAV_CFG_RATE(cbs->queue);
+ hilimit_reg = MV88E6XXX_PORT_QAV_CFG_HILIMIT(cbs->queue);
+
+ if (cbs->enable) {
+ if (cbs->hicredit <= 0 ||
+ cbs->hicredit > qav->hilimit_mask) {
+ NL_SET_ERR_MSG_MOD(cbs->extack,
+ "hicredit out of range");
+ return -ERANGE;
+ }
+
+ rate = DIV_ROUND_UP(cbs->idleslope, qav->rate_unit);
+ if (rate > qav->rate_mask) {
+ NL_SET_ERR_MSG_MOD(cbs->extack,
+ "idleslope out of range");
+ 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;
+ }
+
+ hilimit = cbs->hicredit & qav->hilimit_mask;
+ err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hilimit);
+ if (err)
+ goto unlock;
+
+ err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
+ if (err)
+ goto unlock;
+
+ if (!chip->ports[port].cbs_active_queues) {
+ u8 sched_mode = chip->info->num_tx_queues - 1;
+
+ err = mv88e6xxx_port_set_scheduling_mode(chip, port,
+ sched_mode);
+ if (err) {
+ mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
+ 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,
@@ -7269,6 +7409,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 3a1d79a117dca..85f646efe75ee 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 {
@@ -606,6 +613,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);
@@ -763,6 +772,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 {
@@ -798,6 +811,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 hilimit_mask; /* QPri HiLimit 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 ff5adf7c9bc3a..df52ff3adc88c 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_HILIMIT(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 657783e043ff1..6b54e275d21ab 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 4f915fbee228c..541fee492f5ff 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1323,6 +1323,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,
+ ®);
+ 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,
@@ -1736,3 +1781,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 bd61ddf2b228a..010319cb8a25a 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
@@ -562,6 +574,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,
@@ -606,5 +622,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 related [flat|nested] 10+ messages in thread
* [PATCH net-next v8 3/3] net: dsa: mv88e6xxx: add support for credit based shaper
@ 2026-08-11 11:45 ` Cedric Jehasse
0 siblings, 0 replies; 10+ messages in thread
From: Cedric Jehasse @ 2026-08-11 11:45 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes
Cc: netdev, linux-kernel, Luke Howard, Marek Behún,
Cedric Jehasse, Cedric Jehasse
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 | 141 ++++++++++++++++++++++++++++++++
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, 268 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 47e531f24e5f2..a567ee9b6ce0d 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"
@@ -5024,6 +5025,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,
@@ -5307,6 +5309,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,
@@ -5465,6 +5468,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,
@@ -5534,6 +5538,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,
@@ -5598,6 +5603,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,
@@ -5654,6 +5660,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,
@@ -5696,6 +5703,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),
+ .hilimit_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),
+ .hilimit_mask = GENMASK(13, 0),
+ .queue_mask = GENMASK(3, 0),
+};
+
+static const struct mv88e6xxx_qav_info mv88e6390_qav_info = {
+ .rate_unit = 64,
+ .rate_mask = GENMASK(15, 0),
+ .hilimit_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,
@@ -6260,6 +6288,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,
},
@@ -6397,6 +6426,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,
},
@@ -6477,6 +6507,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] = {
@@ -6534,6 +6565,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] = {
@@ -6561,6 +6593,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,
},
@@ -6589,6 +6622,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.pvt = true,
.multi_chip = true,
.ptp_support = true,
+ .qav = &mv88e6390_qav_info,
.ops = &mv88e6393x_ops,
},
};
@@ -7210,6 +7244,112 @@ 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;
+ u32 rate = 0;
+ u16 hilimit;
+ 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))) {
+ NL_SET_ERR_MSG_MOD(cbs->extack, "CBS not supported on queue");
+ return -EOPNOTSUPP;
+ }
+
+ queue_bit = BIT(cbs->queue);
+ rate_reg = MV88E6XXX_PORT_QAV_CFG_RATE(cbs->queue);
+ hilimit_reg = MV88E6XXX_PORT_QAV_CFG_HILIMIT(cbs->queue);
+
+ if (cbs->enable) {
+ if (cbs->hicredit <= 0 ||
+ cbs->hicredit > qav->hilimit_mask) {
+ NL_SET_ERR_MSG_MOD(cbs->extack,
+ "hicredit out of range");
+ return -ERANGE;
+ }
+
+ rate = DIV_ROUND_UP(cbs->idleslope, qav->rate_unit);
+ if (rate > qav->rate_mask) {
+ NL_SET_ERR_MSG_MOD(cbs->extack,
+ "idleslope out of range");
+ 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;
+ }
+
+ hilimit = cbs->hicredit & qav->hilimit_mask;
+ err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hilimit);
+ if (err)
+ goto unlock;
+
+ err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
+ if (err)
+ goto unlock;
+
+ if (!chip->ports[port].cbs_active_queues) {
+ u8 sched_mode = chip->info->num_tx_queues - 1;
+
+ err = mv88e6xxx_port_set_scheduling_mode(chip, port,
+ sched_mode);
+ if (err) {
+ mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
+ 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,
@@ -7269,6 +7409,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 3a1d79a117dca..85f646efe75ee 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 {
@@ -606,6 +613,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);
@@ -763,6 +772,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 {
@@ -798,6 +811,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 hilimit_mask; /* QPri HiLimit 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 ff5adf7c9bc3a..df52ff3adc88c 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_HILIMIT(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 657783e043ff1..6b54e275d21ab 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 4f915fbee228c..541fee492f5ff 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1323,6 +1323,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,
+ ®);
+ 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,
@@ -1736,3 +1781,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 bd61ddf2b228a..010319cb8a25a 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
@@ -562,6 +574,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,
@@ -606,5 +622,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 related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v8 3/3] net: dsa: mv88e6xxx: add support for credit based shaper
2026-08-11 11:45 ` Cedric Jehasse
(?)
@ 2026-08-17 17:45 ` Jakub Kicinski
-1 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-08-17 17:45 UTC (permalink / raw)
To: cedric.jehasse
Cc: Jakub Kicinski, andrew, olteanv, davem, edumazet, pabeni, horms,
linux, jhs, jiri, vinicius.gomes, netdev, linux-kernel, lukeh,
kabel, cedric.jehasse
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: dsa: mv88e6xxx: add support for credit based shaper
This adds TC_SETUP_QDISC_CBS offload to mv88e6xxx by programming the Qav
rate and HiLimit registers in the AVB block, with the per-chip differences
described by a new mv88e6xxx_qav_info. The 6352 and 6390 style egress
scheduling mode is switched when the first CBS queue on a port is enabled.
> diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
> index 47e531f24e5f..a567ee9b6ce0 100644
> --- a/drivers/net/dsa/mv88e6xxx/chip.c
> +++ b/drivers/net/dsa/mv88e6xxx/chip.c
[ ... ]
> @@ -5654,6 +5660,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,
[Severity: Low]
The commit message says:
Support is added for the 6341, 6352, 6390 and 6393 families.
mv88e6xxx_setup_tc_cbs() gates the whole feature on chip->info->qav, and
the patch sets .qav on six product entries only: MV88E6240, MV88E6341,
MV88E6352, MV88E6390, MV88E6390X and MV88E6393X.
Other members of exactly those families still return -EOPNOTSUPP:
MV88E6141 (FAMILY_6341), MV88E6172 and MV88E6176 (FAMILY_6352),
MV88E6190, MV88E6190X, MV88E6191 and MV88E6290 (FAMILY_6390), and
MV88E6191X, MV88E6193X and MV88E6361 (FAMILY_6393). Should the commit
message name the individual chips instead of the families?
The mv88e6393x_ops table above is also shared by MV88E6191X, MV88E6193X
and MV88E6361, which do have mv88e6390_avb_ops but no .qav entry, so the
ops table now advertises .port_set_scheduling_mode for chips the info
table refuses. Is that combination intentional?
Related to this, mv88e6165_avb_ops in global2_avb.c gains:
.port_qav_write = mv88e6352_g2_avb_port_qav_write,
Its only users are mv88e6161_ops and mv88e6165_ops, and neither gets
.port_set_scheduling_mode, nor does any FAMILY_6165 chip entry get .qav.
Is that callback reachable from anywhere?
[ ... ]
> @@ -7210,6 +7244,112 @@ 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;
> + u32 rate = 0;
> + u16 hilimit;
> + 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))) {
> + NL_SET_ERR_MSG_MOD(cbs->extack, "CBS not supported on queue");
> + return -EOPNOTSUPP;
> + }
> +
> + queue_bit = BIT(cbs->queue);
> + rate_reg = MV88E6XXX_PORT_QAV_CFG_RATE(cbs->queue);
> + hilimit_reg = MV88E6XXX_PORT_QAV_CFG_HILIMIT(cbs->queue);
> +
> + if (cbs->enable) {
> + if (cbs->hicredit <= 0 ||
> + cbs->hicredit > qav->hilimit_mask) {
> + NL_SET_ERR_MSG_MOD(cbs->extack,
> + "hicredit out of range");
> + return -ERANGE;
> + }
> +
> + rate = DIV_ROUND_UP(cbs->idleslope, qav->rate_unit);
> + if (rate > qav->rate_mask) {
> + NL_SET_ERR_MSG_MOD(cbs->extack,
> + "idleslope out of range");
> + return -ERANGE;
> + }
> + /* avoid using zero rate */
> + rate = max_t(u16, rate, 1);
> + }
[Severity: Medium]
Can a negative idleslope reach the hardware here? cbs_change() and
cbs_enable_offload() in net/sched/sch_cbs.c copy the raw s32 tc_cbs_qopt
fields into struct tc_cbs_qopt_offload without any sign or range check, so
cbs->idleslope can be negative.
DIV_ROUND_UP expands to ((x) + (d) - 1) / (d) evaluated in int, so with
rate_unit == 64 and idleslope == -10:
rate = (-10 + 63) / 64; /* 0 */
...
rate = max_t(u16, rate, 1); /* 1 */
The rate > qav->rate_mask test passes and a shaper of one unit
(32 or 64 kbit/s) is programmed instead of an error being returned. For a
larger negative magnitude, say -1000, the quotient is -14, which as u32
exceeds rate_mask and -ERANGE is returned, so the same class of invalid
input is accepted or rejected depending only on magnitude.
For idleslope close to INT_MAX, does the addition inside DIV_ROUND_UP
overflow signed int before the bound check runs?
On the other side, cbs->hicredit <= 0 rejects hicredit == 0, which the
software cbs qdisc accepts. Is rejecting it with -ERANGE here intended?
> +
> + 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;
> + }
[Severity: Medium]
Can this leave chip->ports[port].cbs_active_queues permanently stale? The
queue's shaper has already been disabled by the rate_reg write, but if
mv88e6xxx_port_set_scheduling_mode() fails (for example an MDIO error or
an mv88e6xxx_g2_avb_wait() timeout), the goto unlock happens before:
chip->ports[port].cbs_active_queues &= ~queue_bit;
so the bit stays set. sch_cbs does not retry: cbs_disable_offload() only
does pr_warn("Couldn't disable CBS offload for queue %d\n", ...) and
cbs_change() then clears q->offload. Nothing else in the driver clears
cbs_active_queues.
Every later decision for that port is then wrong: an enable sees
cbs_active_queues != 0 and skips the scheduling mode programming, and a
disable of another queue sees !(active & ~queue_bit) as false and never
restores the port scheduling mode.
Is the enable-path rollback below complete for the same reason?
if (err) {
mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
goto unlock;
}
The HiLimit register stays programmed, the return value of the rollback
write is discarded, and the scheduling mode is not put back. In
mv88e6390_port_set_scheduling_mode() the register write happens before
the completion poll:
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);
so an error return does not mean the hardware mode is unchanged. Since
cbs_enable_offload() does not set q->offload on a negative return, will
any later call ever restore mode 0 on that port?
Also, on reconfiguration the HiLimit write happens before the Rate write,
so a failing rate write leaves an already active queue running its old
rate with the newly requested HiLimit while the operation is reported as
failed. Is that ordering deliberate?
As a side note, polling MV88E6390_PORT_QUEUE_CTL bit 15 is the only
UPDATE bit poll of its kind in this driver; MV88E6390_PORT_IEEE_PRIO_MAP_TABLE
and MV88E6393X_PORT_POLICY_MGMT_CTL are written without any wait. Does the
datasheet describe that bit as self-clearing on read?
> +
> + hilimit = cbs->hicredit & qav->hilimit_mask;
> + err = mv88e6xxx_port_qav_write(chip, port, hilimit_reg, hilimit);
> + if (err)
> + goto unlock;
> +
> + err = mv88e6xxx_port_qav_write(chip, port, rate_reg, rate);
> + if (err)
> + goto unlock;
> +
> + if (!chip->ports[port].cbs_active_queues) {
> + u8 sched_mode = chip->info->num_tx_queues - 1;
> +
> + err = mv88e6xxx_port_set_scheduling_mode(chip, port,
> + sched_mode);
> + if (err) {
> + mv88e6xxx_port_qav_write(chip, port, rate_reg, 0);
> + goto unlock;
> + }
> + }
> + chip->ports[port].cbs_active_queues |= queue_bit;
[Severity: Medium]
Enabling CBS on one queue reprograms the port wide egress scheduling mode
here. mv88e6xxx_port_set_scheduling_mode() writes
MV88E6XXX_PORT_EGRESS_RATE_CTL2 bits 13:12 on the 6352 and 6341 style
chips, or the Port Queue Control SCHEDULE pointer on the 6390 style
chips, both of which affect arbitration for every traffic class on that
port, including queues with no cbs qdisc.
The previous content of that field is never read back or cached; the patch
adds only a write callback (.port_qav_write) and no read helper, so the
disable path restores a hard coded 0:
err = mv88e6xxx_port_set_scheduling_mode(chip, port, 0);
Whatever the reset default, bootloader or EEPROM had programmed there is
lost until the switch is re-probed. Should the pre-CBS value be saved in
struct mv88e6xxx_port and restored instead?
The commit message says:
Note: only idleslope and hicredit can be programmed in the switch
registers, other parameters won't affect settings.
which reads as if the change is confined to the CBS queue's own shaper.
Could it mention the port wide scheduling mode change as well?
One more thing about the disable path condition:
if (!(chip->ports[port].cbs_active_queues & ~queue_bit)) {
this is also true when cbs_active_queues == 0, so a teardown for a port
that was never configured would force the port wide field to 0. Today
cbs_disable_offload() returns early when !q->offload, so it is not
reachable through sch_cbs, but should the code still require the queue bit
to be set before touching the port scheduling mode?
> +
> +unlock:
> + mv88e6xxx_reg_unlock(chip);
> +
> + return err;
> +}
[ ... ]
--
pw-bot: cr
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v8 2/3] net: dsa: mv88e6xxx: use the hw tx queues
2026-08-11 11:45 ` Cedric Jehasse
(?)
@ 2026-08-17 17:50 ` Jakub Kicinski
-1 siblings, 0 replies; 10+ messages in thread
From: Jakub Kicinski @ 2026-08-17 17:50 UTC (permalink / raw)
To: Cedric Jehasse via B4 Relay
Cc: cedric.jehasse, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Russell King,
Jamal Hadi Salim, Jiri Pirko, Vinicius Costa Gomes, netdev,
linux-kernel, Luke Howard, Marek Behún, Cedric Jehasse
On Tue, 11 Aug 2026 13:45:48 +0200 Cedric Jehasse via B4 Relay wrote:
> 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->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).
Pretending that a DSA device has multiple Tx queues _from the stack_
makes no sense. You should be offloading PRIO as the root qdisc,
like mlxsw does. Then the rate limiters should be attached as children
of PRIO.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-08-17 17:54 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 11:45 [PATCH net-next v8 0/3] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
2026-08-11 11:45 ` Cedric Jehasse
2026-08-11 11:45 ` [PATCH net-next v8 1/3] net/sched: cbs: add extack to struct tc_cbs_qopt_offload Cedric Jehasse via B4 Relay
2026-08-11 11:45 ` Cedric Jehasse
2026-08-11 11:45 ` [PATCH net-next v8 2/3] net: dsa: mv88e6xxx: use the hw tx queues Cedric Jehasse via B4 Relay
2026-08-11 11:45 ` Cedric Jehasse
2026-08-17 17:50 ` Jakub Kicinski
2026-08-11 11:45 ` [PATCH net-next v8 3/3] net: dsa: mv88e6xxx: add support for credit based shaper Cedric Jehasse via B4 Relay
2026-08-11 11:45 ` Cedric Jehasse
2026-08-17 17:45 ` Jakub Kicinski
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.