Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft().
@ 2026-08-29  8:28 Lorenzo Bianconi
  2026-08-29  8:28 ` [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure Lorenzo Bianconi
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-08-29  8:28 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Furong Xu, Vladimir Oltean
  Cc: netdev, linux-stm32, linux-arm-kernel, Lorenzo Bianconi


---
Lorenzo Bianconi (2):
      net: stmmac: preserve real_num_tx_queues on mqprio setup failure
      net: stmmac: preserve FPE preemption class across qdisc replacement

 drivers/net/ethernet/stmicro/stmmac/stmmac.h    |  7 ++
 drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 97 +++++++++++++++++++------
 2 files changed, 80 insertions(+), 24 deletions(-)
---
base-commit: 2188569e7e1b0bc3f3b557dc97ab7a02befc11c8
change-id: 20260827-stmmac-fix-graft-overwrite-d3ec2e72d901

Best regards,
-- 
Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>


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

* [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure
  2026-08-29  8:28 [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
@ 2026-08-29  8:28 ` Lorenzo Bianconi
  2026-09-02 12:30   ` [net,1/2] " netdev-bot+sashiko
  2026-08-29  8:28 ` [PATCH net 2/2] net: stmmac: preserve FPE preemption class across qdisc replacement Lorenzo Bianconi
  2026-09-01  9:07 ` [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
  2 siblings, 1 reply; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-08-29  8:28 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Furong Xu, Vladimir Oltean
  Cc: netdev, linux-stm32, linux-arm-kernel, Lorenzo Bianconi

In tc_setup_dwmac510_mqprio(), if stmmac_fpe_map_preemption_class()
fails after the number of real TX queues has been set to
num_tx_queues, the error path calls stmmac_reset_tc_mqprio(), which resets
the number of real TX queues to priv->plat->tx_queues_to_use
(the maximum the platform supports).
This overwrites the value that was active before the mqprio offload was
attempted, which may have been lower than the platform maximum (for
example after a previous mqprio configuration reduced the queue count).

Save ndev->real_num_tx_queues before lowering it and restore it if the
FPE preemption-class mapping fails. Drop the use of
stmmac_reset_tc_mqprio() from the error path: the queue count is now
restored explicitly and the TC-to-queue mapping is restored to the
previously saved values.
Introduce stmmac_set_ndev_tcs utility routine.

Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio")
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 75 ++++++++++++++++++-------
 1 file changed, 55 insertions(+), 20 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
index 14cabe76e53e..be8ddda2aa76 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -1237,6 +1237,30 @@ static int tc_query_caps(struct stmmac_priv *priv,
 	}
 }
 
+static int stmmac_set_ndev_tcs(struct net_device *ndev, u8 ntc,
+			       struct netdev_tc_txq *tc_to_txq)
+{
+	int i, err;
+
+	netdev_reset_tc(ndev);
+	if (ntc == 1)
+		return 0;
+
+	err = netdev_set_num_tc(ndev, ntc);
+	if (err)
+		return err;
+
+	for (i = 0; i < ntc; i++) {
+		u16 count, offset;
+
+		count = tc_to_txq[i].count;
+		offset = tc_to_txq[i].offset;
+		netdev_set_tc_queue(ndev, i, count, offset);
+	}
+
+	return 0;
+}
+
 static void stmmac_reset_tc_mqprio(struct net_device *ndev,
 				   struct netlink_ext_ack *extack)
 {
@@ -1250,45 +1274,56 @@ static void stmmac_reset_tc_mqprio(struct net_device *ndev,
 static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
 				    struct tc_mqprio_qopt_offload *mqprio)
 {
+	unsigned int ndev_num_tx_queues, num_tx_queues = 0;
+	struct netdev_tc_txq ndev_tc_to_txq[TC_MAX_QUEUE];
+	struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE] = {};
 	struct netlink_ext_ack *extack = mqprio->extack;
 	struct tc_mqprio_qopt *qopt = &mqprio->qopt;
-	u32 offset, count, num_stack_tx_queues = 0;
 	struct net_device *ndev = priv->dev;
-	u32 num_tc = qopt->num_tc;
-	int err;
+	int i, err, ndev_ntc;
 
-	if (!num_tc) {
+	if (!qopt->num_tc) {
 		stmmac_reset_tc_mqprio(ndev, extack);
 		return 0;
 	}
 
-	err = netdev_set_num_tc(ndev, num_tc);
-	if (err)
-		return err;
-
-	for (u32 tc = 0; tc < num_tc; tc++) {
-		offset = qopt->offset[tc];
-		count = qopt->count[tc];
-		num_stack_tx_queues += count;
+	if (qopt->num_tc > ARRAY_SIZE(tc_to_txq))
+		return -EINVAL;
 
-		err = netdev_set_tc_queue(ndev, tc, count, offset);
-		if (err)
-			goto err_reset_tc;
+	/* save current tc values for reset */
+	ndev_ntc = netdev_get_num_tc(ndev) ? : 1;
+	for (i = 0; i < ARRAY_SIZE(ndev->tc_to_txq); i++)
+		ndev_tc_to_txq[i].combined =
+			READ_ONCE(ndev->tc_to_txq[i].combined);
+
+	for (i = 0; i < qopt->num_tc; i++) {
+		tc_to_txq[i] = (struct netdev_tc_txq) {
+			.count = qopt->count[i],
+			.offset = qopt->offset[i],
+		};
+		num_tx_queues += qopt->count[i];
 	}
 
-	err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues);
+	err = stmmac_set_ndev_tcs(ndev, qopt->num_tc, tc_to_txq);
+	if (err)
+		goto error_reset_tc;
+
+	ndev_num_tx_queues = ndev->real_num_tx_queues;
+	err = netif_set_real_num_tx_queues(ndev, num_tx_queues);
 	if (err)
-		goto err_reset_tc;
+		goto error_reset_tc;
 
 	err = stmmac_fpe_map_preemption_class(priv, ndev, extack,
 					      mqprio->preemptible_tcs);
 	if (err)
-		goto err_reset_tc;
+		goto error_reset_num_tx_queues;
 
 	return 0;
 
-err_reset_tc:
-	stmmac_reset_tc_mqprio(ndev, extack);
+error_reset_num_tx_queues:
+	netif_set_real_num_tx_queues(ndev, ndev_num_tx_queues);
+error_reset_tc:
+	stmmac_set_ndev_tcs(ndev, ndev_ntc, ndev_tc_to_txq);
 
 	return err;
 }

-- 
2.55.0


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

* [PATCH net 2/2] net: stmmac: preserve FPE preemption class across qdisc replacement
  2026-08-29  8:28 [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
  2026-08-29  8:28 ` [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure Lorenzo Bianconi
@ 2026-08-29  8:28 ` Lorenzo Bianconi
  2026-09-02 12:30   ` [net,2/2] " netdev-bot+sashiko
  2026-09-01  9:07 ` [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
  2 siblings, 1 reply; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-08-29  8:28 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Furong Xu, Vladimir Oltean
  Cc: netdev, linux-stm32, linux-arm-kernel, Lorenzo Bianconi

The kernel replaces a root qdisc by creating the new qdisc first and
destroying the old one only afterwards (qdisc_create() then
qdisc_graft() -> notify_and_destroy()). For a cross-kind replace this
means the teardown of the old qdisc runs after the offload of the new
one.

This breaks the FPE preemption-class mapping when replacing between
qdiscs that both program it. For example replacing taprio with mqprio:

  1. the mqprio offload (REPLACE) programs FPE_MTL_PREEMPTION_CLASS
     with mqprio->preemptible_tcs
  2. the old taprio is then torn down; tc_taprio_configure() runs the
     TAPRIO_CMD_DESTROY path and resets the preemption class to 0

Track which root qdisc policy is currently offloaded in a new
priv->qdisc_type field and only clear the FPE preemption class during
teardown if the qdisc being destroyed actually owns it.

Fixes: 15d8a407a547 ("net: stmmac: support fp parameter of tc-taprio")
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac.h    |  7 +++++++
 drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 22 ++++++++++++++++++----
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 7582fca63741..957317ec1461 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -258,6 +258,12 @@ struct stmmac_msi {
 	char int_name_tx_irq[MTL_MAX_TX_QUEUES][IFNAMSIZ + 18];
 };
 
+enum stmmac_qdisc_type {
+	STMMAC_QDISC_NONE = 0,
+	STMMAC_QDISC_MQPRIO,
+	STMMAC_QDISC_TAPRIO,
+};
+
 struct stmmac_priv {
 	/* Frequently used values are kept adjacent for cache effect */
 	u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
@@ -299,6 +305,7 @@ struct stmmac_priv {
 	/* Protect est parameters */
 	struct mutex est_lock;
 	struct stmmac_est *est;
+	enum stmmac_qdisc_type qdisc_type;
 	struct dma_features dma_cap;
 	struct stmmac_counters mmc;
 	int hw_cap_support;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
index be8ddda2aa76..7fe587b47d52 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -1103,6 +1103,8 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
 	if (ret)
 		goto disable;
 
+	priv->qdisc_type = STMMAC_QDISC_TAPRIO;
+
 	return 0;
 
 disable:
@@ -1120,7 +1122,11 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
 		mutex_unlock(&priv->est_lock);
 	}
 
-	stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
+	if (priv->qdisc_type == STMMAC_QDISC_TAPRIO &&
+	    qopt->cmd == TAPRIO_CMD_DESTROY) {
+		stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
+		priv->qdisc_type = STMMAC_QDISC_NONE;
+	}
 
 	return ret;
 }
@@ -1266,9 +1272,15 @@ static void stmmac_reset_tc_mqprio(struct net_device *ndev,
 {
 	struct stmmac_priv *priv = netdev_priv(ndev);
 
-	netdev_reset_tc(ndev);
-	netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use);
-	stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
+	if (priv->qdisc_type != STMMAC_QDISC_TAPRIO)
+		stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
+
+	if (priv->qdisc_type == STMMAC_QDISC_MQPRIO) {
+		netdev_reset_tc(ndev);
+		netif_set_real_num_tx_queues(ndev,
+					     priv->plat->tx_queues_to_use);
+		priv->qdisc_type = STMMAC_QDISC_NONE;
+	}
 }
 
 static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
@@ -1318,6 +1330,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
 	if (err)
 		goto error_reset_num_tx_queues;
 
+	priv->qdisc_type = STMMAC_QDISC_MQPRIO;
+
 	return 0;
 
 error_reset_num_tx_queues:

-- 
2.55.0


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

* Re: [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft().
  2026-08-29  8:28 [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
  2026-08-29  8:28 ` [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure Lorenzo Bianconi
  2026-08-29  8:28 ` [PATCH net 2/2] net: stmmac: preserve FPE preemption class across qdisc replacement Lorenzo Bianconi
@ 2026-09-01  9:07 ` Lorenzo Bianconi
  2 siblings, 0 replies; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-09-01  9:07 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Furong Xu, Vladimir Oltean
  Cc: netdev, linux-stm32, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 702 bytes --]

> 
> ---
> Lorenzo Bianconi (2):
>       net: stmmac: preserve real_num_tx_queues on mqprio setup failure
>       net: stmmac: preserve FPE preemption class across qdisc replacement
> 
>  drivers/net/ethernet/stmicro/stmmac/stmmac.h    |  7 ++
>  drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 97 +++++++++++++++++++------
>  2 files changed, 80 insertions(+), 24 deletions(-)
> ---
> base-commit: 2188569e7e1b0bc3f3b557dc97ab7a02befc11c8
> change-id: 20260827-stmmac-fix-graft-overwrite-d3ec2e72d901

Please drop this version, I will address some sashiko's comments in v2.

Regards,
Lorenzo

> 
> Best regards,
> -- 
> Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [net,1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure
  2026-08-29  8:28 ` [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure Lorenzo Bianconi
@ 2026-09-02 12:30   ` netdev-bot+sashiko
  0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 12:30 UTC (permalink / raw)
  To: lorenzo.bianconi
  Cc: maxime.chevallier, andrew+netdev, davem, edumazet, kuba, pabeni,
	mcoquelin.stm32, alexandre.torgue, 0x1207, olteanv, netdev,
	linux-stm32, linux-arm-kernel

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 2 · Medium: 1 · Low: 0

- [High] The error path this patch is built around cannot be taken:
  `stmmac_fpe_map_preemption_class()` is dispatched through…
- [High] The new helper `stmmac_set_ndev_tcs()`
  (drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c:1240) is a verbatim…
- [Medium] The commit message justifies dropping stmmac_reset_tc_mqprio()
  from the error path with 'the TC-to-queue mapping is restored to the…

--

net: stmmac: preserve real_num_tx_queues on mqprio setup failure

This reworks tc_setup_dwmac510_mqprio() so that the previous
real_num_tx_queues and TC-to-queue mapping are snapshotted and restored
if stmmac_fpe_map_preemption_class() fails, instead of falling back to
stmmac_reset_tc_mqprio().  A new stmmac_set_ndev_tcs() helper is added to
program and restore the netdev TC state.

A few questions below.

> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> index 14cabe76e53e..be8ddda2aa76 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> @@ -1237,6 +1237,30 @@ static int tc_query_caps(struct stmmac_priv *priv,
>  	}
>  }
>  
> +static int stmmac_set_ndev_tcs(struct net_device *ndev, u8 ntc,
> +			       struct netdev_tc_txq *tc_to_txq)
> +{
> +	int i, err;
> +
> +	netdev_reset_tc(ndev);
> +	if (ntc == 1)
> +		return 0;

[Severity: High]
Is the "ntc == 1" early return correct for stmmac?

This shortcut matches mlx5e_netdev_set_tcs() in
drivers/net/ethernet/mellanox/mlx5/core/en_main.c, where one TC means "no
TC differentiation" and mlx5 keeps its own queue/TC bookkeeping.  In
stmmac the netdev TC state is the only input to the FPE programming
callbacks.

For a legal single-TC offload request, for example

  tc qdisc replace dev eth0 root mqprio num_tc 1 map 0 0 0 0 0 0 0 0 \
      queues 4@0 fp P hw 1

mqprio_validate_qopt() accepts num_tc 1 and mqprio_fp_to_offload() sets
mqprio->preemptible_tcs = BIT(0).  stmmac_set_ndev_tcs(ndev, 1, tc_to_txq)
then leaves dev->num_tc == 0 and tc_to_txq[] zeroed, and
tc_setup_dwmac510_mqprio() still returns 0.

Before this patch the same request ran netdev_set_num_tc(ndev, 1) followed
by netdev_set_tc_queue(ndev, 0, count, offset).

Does that change the hardware programming?  dwmac5_fpe_map_preemption_class()
derives everything from netdev_get_num_tc():

drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c:dwmac5_fpe_map_preemption_class() {
	int num_tc = netdev_get_num_tc(ndev);
	...
	for (u32 tc = 0; tc < num_tc; tc++) {
		...
		if (pclass & BIT(tc))
			preemptible_txqs |= GENMASK(offset + count - 1, offset);
		...
	}
update_mapping:
	val = readl(priv->ioaddr + GMAC5_MTL_FPE_CTRL_STS);
	writel(u32_replace_bits(val, preemptible_txqs, FPE_MTL_PREEMPTION_CLASS),
	       priv->ioaddr + GMAC5_MTL_FPE_CTRL_STS);
}

With num_tc == 0 the loop body never runs, so FPE_MTL_PREEMPTION_CLASS is
written as 0 and the TC0 the user marked preemptible is never armed.  The
one-to-many SP-algorithm and equal-weight checks in the same loop are
skipped as well.

On XGMAC the effect is the opposite direction:

drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c:dwxgmac3_fpe_map_preemption_class() {
	if (!num_tc) {
		/* Restore default TC:Queue mapping */
		for (u32 i = 0; i < priv->plat->tx_queues_to_use; i++) {
			val = readl(priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
			writel(u32_replace_bits(val, i, XGMAC_Q2TCMAP),
			       priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
		}
	}

so the identity queue-to-TC map is reprogrammed even though the offload of
N queues into TC0 was reported as successful.  The netdev also ends up
reporting num_tc 0 to mqprio_dump() while the mqprio qdisc is installed.

> +
> +	err = netdev_set_num_tc(ndev, ntc);
> +	if (err)
> +		return err;
> +
> +	for (i = 0; i < ntc; i++) {
> +		u16 count, offset;
> +
> +		count = tc_to_txq[i].count;
> +		offset = tc_to_txq[i].offset;
> +		netdev_set_tc_queue(ndev, i, count, offset);
> +	}
> +
> +	return 0;
> +}
> +
>  static void stmmac_reset_tc_mqprio(struct net_device *ndev,
>  				   struct netlink_ext_ack *extack)
>  {

[ ... ]

> @@ -1250,45 +1274,56 @@ static void stmmac_reset_tc_mqprio(struct net_device *ndev,
>  static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
>  				    struct tc_mqprio_qopt_offload *mqprio)
>  {

[ ... ]

> +	/* save current tc values for reset */
> +	ndev_ntc = netdev_get_num_tc(ndev) ? : 1;
> +	for (i = 0; i < ARRAY_SIZE(ndev->tc_to_txq); i++)
> +		ndev_tc_to_txq[i].combined =
> +			READ_ONCE(ndev->tc_to_txq[i].combined);

[Severity: Medium]
The snapshot covers num_tc and tc_to_txq[], but not ndev->prio_tc_map[].
stmmac_set_ndev_tcs() starts with netdev_reset_tc(), which clears all
three:

net/core/dev.c:netdev_reset_tc() {
	WRITE_ONCE(dev->num_tc, 0);
	for (i = 0; i < TC_MAX_QUEUE; i++)
		WRITE_ONCE(dev->tc_to_txq[i].combined, 0);
	for (i = 0; i <= TC_BITMASK; i++)
		WRITE_ONCE(dev->prio_tc_map[i], 0);
}

The core only repopulates prio_tc_map after a successful driver callback:

net/sched/sch_mqprio.c:mqprio_init() {
	...
	/* Always use supplied priority mappings */
	for (i = 0; i < TC_BITMASK + 1; i++)
		netdev_set_prio_tc_map(dev, i, qopt->prio_tc_map[i]);
}

so on the error path the previously active priority-to-TC map is lost.
Should prio_tc_map[] be saved and restored too?

Also, the "? : 1" encoding collapses an existing num_tc == 1 into the
helper's reset case, so a device that legitimately had one TC ends up with
num_tc == 0 after a failed attempt.

Separately, the commit message says "the TC-to-queue mapping is restored to
the previously saved values".  Is the num_tc restore observable in the
scenario the message describes (a new mqprio configuration over a working
one)?  qdisc_create() calls ops->destroy() even when ops->init() failed,
and mqprio_destroy() takes the non-offload branch because priv->hw_offload
was never set:

net/sched/sch_mqprio.c:mqprio_destroy() {
	if (priv->hw_offload && dev->netdev_ops->ndo_setup_tc)
		mqprio_disable_offload(sch);
	else
		netdev_set_num_tc(dev, 0);
}

which zeroes dev->num_tc again right after the driver restored it.

> +
> +	for (i = 0; i < qopt->num_tc; i++) {
> +		tc_to_txq[i] = (struct netdev_tc_txq) {
> +			.count = qopt->count[i],
> +			.offset = qopt->offset[i],
> +		};
> +		num_tx_queues += qopt->count[i];
>  	}
>  
> -	err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues);
> +	err = stmmac_set_ndev_tcs(ndev, qopt->num_tc, tc_to_txq);
> +	if (err)
> +		goto error_reset_tc;
> +
> +	ndev_num_tx_queues = ndev->real_num_tx_queues;
> +	err = netif_set_real_num_tx_queues(ndev, num_tx_queues);
>  	if (err)
> -		goto err_reset_tc;
> +		goto error_reset_tc;
>  
>  	err = stmmac_fpe_map_preemption_class(priv, ndev, extack,
>  					      mqprio->preemptible_tcs);
>  	if (err)
> -		goto err_reset_tc;
> +		goto error_reset_num_tx_queues;
>  
>  	return 0;
>  
> -err_reset_tc:
> -	stmmac_reset_tc_mqprio(ndev, extack);
> +error_reset_num_tx_queues:
> +	netif_set_real_num_tx_queues(ndev, ndev_num_tx_queues);
> +error_reset_tc:
> +	stmmac_set_ndev_tcs(ndev, ndev_ntc, ndev_tc_to_txq);
>  
>  	return err;
>  }

[Severity: High]
Can err here ever be non-zero on a MAC that reaches this function?
stmmac_fpe_map_preemption_class() is dispatched through the void wrapper:

drivers/net/ethernet/stmicro/stmmac/hwif.h {
#define stmmac_do_void_callback(__priv, __module, __cname,  __arg0, __args...) \
({ \
	int __result = -EINVAL; \
	if ((__priv)->hw->__module && (__priv)->hw->__module->__cname) { \
		(__priv)->hw->__module->__cname((__arg0), ##__args); \
		__result = 0; \
	} \
	__result; \
})
...
#define stmmac_fpe_map_preemption_class(__priv, __args...) \
	stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args)
}

The op is declared as returning int in struct stmmac_ops, but the wrapper
calls it as a bare statement and forces 0 whenever the pointer is set.
Every ops table paired with .tc = &dwmac510_tc_ops in hwif.c (dwmac410_ops,
dwmac510_ops, dwxgmac210_ops, dwxlgmac2_ops) populates
.fpe_map_preemption_class, so err is always 0 and the new
error_reset_num_tx_queues label looks unreachable.

If so, does that mean the failure described in the commit message,

  "if stmmac_fpe_map_preemption_class() fails after the number of real TX
   queues has been set to num_tx_queues"

cannot currently happen, and the Fixes: 195e4f409a40 tag is pointing at a
path that is not taken?

The same wrapper also drops the real rejections in
dwmac5_fpe_map_preemption_class():

drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c:dwmac5_fpe_map_preemption_class() {
		if (priv->plat->tx_sched_algorithm == MTL_TX_ALGORITHM_SP) {
			NL_SET_ERR_MSG_MOD(extack, ALG_ERR_MSG);
			return -EINVAL;
		}
		...
			if (priv->plat->tx_queues_cfg[offset + i].weight !=
			    queue_weight) {
				NL_SET_ERR_MSG_FMT_MOD(extack, WEIGHT_ERR_MSG,
						       queue_weight, tc);
				return -EINVAL;
			}
}

Both returns happen before the GMAC5_MTL_FPE_CTRL_STS write, so the
preemption-class register keeps its previous value while the qdisc layer is
told the offload succeeded.  Would switching the wrapper to
stmmac_do_callback() be the prerequisite change here, so that this new
error path can actually run?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829-stmmac-fix-graft-overwrite-v1-0-a4f84c4c264e%40oss.qualcomm.com

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

* Re: [net,2/2] net: stmmac: preserve FPE preemption class across qdisc replacement
  2026-08-29  8:28 ` [PATCH net 2/2] net: stmmac: preserve FPE preemption class across qdisc replacement Lorenzo Bianconi
@ 2026-09-02 12:30   ` netdev-bot+sashiko
  0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 12:30 UTC (permalink / raw)
  To: lorenzo.bianconi
  Cc: maxime.chevallier, andrew+netdev, davem, edumazet, kuba, pabeni,
	mcoquelin.stm32, alexandre.torgue, 0x1207, olteanv, netdev,
	linux-stm32, linux-arm-kernel

Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:

Critical: 0 · High: 2 · Medium: 2 (1) · Low: 0

- [High] Incomplete fix: ownership of the FPE preemption class (and of
  the EST block) is tracked per qdisc *kind* (priv->qdisc_type) instead…
- [High] The patch also gates netdev_reset_tc() and the
  netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use)…
- [Medium] Reordering regression on XGMAC: in stmmac_reset_tc_mqprio()
  the stmmac_fpe_map_preemption_class(priv, ndev, extack, 0) call is now…
- [Medium] Lost error-path rollback in tc_taprio_configure(): the
  `disable:` tail now resets the FPE preemption class only when…

Pre-existing issues:
- [Medium] struct stmmac_ops::fpe_map_preemption_class is declared as
  returning int (drivers/net/ethernet/stmicro/stmmac/hwif.h:411-413),…

--

net: stmmac: preserve FPE preemption class across qdisc replacement

The patch adds a priv->qdisc_type field recording which root qdisc policy
(mqprio or taprio) is currently offloaded, and uses it to skip clearing the
FPE preemption class during teardown when the qdisc being destroyed is not
the one that programmed it. The mqprio reset path is additionally gated on
the same field.

A few questions about the new ownership tracking below.

> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
> index 7582fca63741..957317ec1461 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
> @@ -258,6 +258,12 @@ struct stmmac_msi {
>  	char int_name_tx_irq[MTL_MAX_TX_QUEUES][IFNAMSIZ + 18];
>  };
>  
> +enum stmmac_qdisc_type {
> +	STMMAC_QDISC_NONE = 0,
> +	STMMAC_QDISC_MQPRIO,
> +	STMMAC_QDISC_TAPRIO,
> +};
> +

[Severity: High]
Is tracking the qdisc kind enough to identify the owner here?  The create-
then-destroy ordering the commit message describes also happens for a
same-kind replace when a different handle is used.  In
net/sched/sch_api.c:__tc_modify_qdisc():

	if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {
		if (tcm->tcm_handle) {
			...
			q = qdisc_lookup(dev, tcm->tcm_handle);
			if (!q)
				goto create_n_graft;

so tc qdisc replace dev X root handle 8002: mqprio ... over an mqprio
installed at handle 8001: creates and offloads the new instance first and
destroys the old one afterwards, with both instances reporting the same
kind.

For mqprio -> mqprio, the new tc_setup_dwmac510_mqprio() stores
STMMAC_QDISC_MQPRIO, and the old instance's teardown then reaches
stmmac_reset_tc_mqprio() with qdisc_type == STMMAC_QDISC_MQPRIO, so it
clears the live qdisc's FPE class, calls netdev_reset_tc() and restores the
queue count, leaving qdisc_type == STMMAC_QDISC_NONE.

For taprio -> taprio, the new REPLACE stores STMMAC_QDISC_TAPRIO and the old
instance's TAPRIO_CMD_DESTROY satisfies the new guard, so
FPE_MTL_PREEMPTION_CLASS is cleared for the live qdisc.  The unconditional
disable: tail in tc_taprio_configure() also runs for that DESTROY:

	if (priv->est) {
		mutex_lock(&priv->est_lock);
		priv->est->enable = false;
		stmmac_est_configure(priv, priv, priv->est,
				     priv->plat->clk_ptp_rate);

which switches EST off (and zeroes the per-queue taprio stats) after the
replacing taprio has programmed its gate control list.  Would per-instance
identity (handle or a cookie/generation) be needed instead of the kind, and
should EST ownership be covered too?

>  struct stmmac_priv {
>  	/* Frequently used values are kept adjacent for cache effect */
>  	u32 tx_coal_frames[MTL_MAX_TX_QUEUES];

[ ... ]

> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> index be8ddda2aa76..7fe587b47d52 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c

[ ... ]

> @@ -1120,7 +1122,11 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
>  		mutex_unlock(&priv->est_lock);
>  	}
>  
> -	stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
> +	if (priv->qdisc_type == STMMAC_QDISC_TAPRIO &&
> +	    qopt->cmd == TAPRIO_CMD_DESTROY) {
> +		stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
> +		priv->qdisc_type = STMMAC_QDISC_NONE;
> +	}

[Severity: Medium]
Does the added cmd == TAPRIO_CMD_DESTROY term drop the rollback on the
error path?  The disable: label is also reached with cmd ==
TAPRIO_CMD_REPLACE:

	ret = stmmac_est_configure(priv, priv, priv->est,
				   priv->plat->clk_ptp_rate);
	mutex_unlock(&priv->est_lock);
	if (ret) {
		netdev_err(priv->dev, "failed to configure EST\n");
		goto disable;
	}

est_configure() in stmmac_est.c can return -EINVAL when ptp_rate is 0, and
-ETIMEDOUT via the readl_poll_timeout() inside est_write().  For a failing
REPLACE on an already offloaded taprio (qdisc_type == STMMAC_QDISC_TAPRIO),
the block now disables EST but leaves the previously programmed
FPE_MTL_PREEMPTION_CLASS mask in the hardware, while taprio_change() has
already installed the new num_tc/tc_to_txq mapping.  Before the patch that
mask was cleared unconditionally.

Would the ownership test alone (qdisc_type == STMMAC_QDISC_TAPRIO) be
sufficient for the case described in the commit message?

>  
>  	return ret;
>  }
> @@ -1266,9 +1272,15 @@ static void stmmac_reset_tc_mqprio(struct net_device *ndev,
>  {
>  	struct stmmac_priv *priv = netdev_priv(ndev);
>  
> -	netdev_reset_tc(ndev);
> -	netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use);
> -	stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
> +	if (priv->qdisc_type != STMMAC_QDISC_TAPRIO)
> +		stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
> +
> +	if (priv->qdisc_type == STMMAC_QDISC_MQPRIO) {
> +		netdev_reset_tc(ndev);
> +		netif_set_real_num_tx_queues(ndev,
> +					     priv->plat->tx_queues_to_use);
> +		priv->qdisc_type = STMMAC_QDISC_NONE;
> +	}
>  }

[Severity: High]
Can this leave real_num_tx_queues permanently reduced?  The commit message
only mentions gating the FPE preemption class, but the queue-count restore
is now gated too.

On an mqprio -> taprio root replace the taprio REPLACE offload runs first
and sets qdisc_type = STMMAC_QDISC_TAPRIO.  The old mqprio teardown
(mqprio_destroy() -> mqprio_disable_offload() with num_tc == 0 ->
stmmac_reset_tc_mqprio()) then takes neither branch, so the value programmed
earlier by tc_setup_dwmac510_mqprio() stays in place:

	ndev_num_tx_queues = ndev->real_num_tx_queues;
	err = netif_set_real_num_tx_queues(ndev, num_tx_queues);

where num_tx_queues is the sum of qopt->count[i], which
mqprio_validate_queue_counts() allows to be smaller than
plat->tx_queues_to_use.

Nothing appears to restore it afterwards: taprio_destroy() in
net/sched/sch_taprio.c only calls netdev_reset_tc(dev) and never touches
real_num_tx_queues, and the stmmac taprio DESTROY path only clears the FPE
class.  Recovery then requires an interface down/up so that
stmmac_hw_setup() re-runs netif_set_real_num_tx_queues().  Later tc
configurations would also be validated against the stale
dev->real_num_tx_queues.

Should the netdev TC/queue-count restore be kept independent of the FPE
ownership state, since the two resources have different owners?

[Severity: Medium]
Does the reordering here break the XGMAC default TXQ-to-TC restore?  The
FPE clear is now issued before netdev_reset_tc(ndev), while previously the
order was netdev_reset_tc() -> netif_set_real_num_tx_queues() -> FPE clear.

dwxgmac3_fpe_map_preemption_class() in stmmac_fpe.c restores the default
mapping only when the netdev already has no traffic classes:

	int num_tc = netdev_get_num_tc(ndev);

	if (!num_tc) {
		/* Restore default TC:Queue mapping */
		for (u32 i = 0; i < priv->plat->tx_queues_to_use; i++) {
			val = readl(priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
			writel(u32_replace_bits(val, i, XGMAC_Q2TCMAP),
			       priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
		}
	}

and the qdisc core does not clear dev->num_tc for an offloaded mqprio:

net/sched/sch_mqprio.c:mqprio_destroy() {
	if (priv->hw_offload && dev->netdev_ops->ndo_setup_tc)
		mqprio_disable_offload(sch);
	else
		netdev_set_num_tc(dev, 0);
}

So on tc qdisc del dev X root with an offloaded mqprio, num_tc is still
non-zero when the callback runs, the restore branch is skipped, and the MTL
Q2TCMAP keeps the grouping of the mqprio that is being torn down.  Could the
FPE clear stay after netdev_reset_tc()?

>  
>  static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
> @@ -1318,6 +1330,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
>  	if (err)
>  		goto error_reset_num_tx_queues;
>  
> +	priv->qdisc_type = STMMAC_QDISC_MQPRIO;
> +

[Severity: Medium]
This isn't a problem introduced by this patch, but can err here ever be
non-zero?  struct stmmac_ops declares

	int (*fpe_map_preemption_class)(struct net_device *ndev,
					struct netlink_ext_ack *extack,
					u32 pclass);

while the accessor in hwif.h goes through the void variant:

	#define stmmac_fpe_map_preemption_class(__priv, __args...) \
		stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args)

stmmac_do_void_callback() discards the callback result and evaluates to 0
whenever the pointer is set.  dwmac5_fpe_map_preemption_class() does return
an error before programming FPE_MTL_PREEMPTION_CLASS:

	if (priv->plat->tx_sched_algorithm == MTL_TX_ALGORITHM_SP) {
		NL_SET_ERR_MSG_MOD(extack, ALG_ERR_MSG);
		return -EINVAL;
	}

so with an SP scheduler (or mismatched TXQ weights inside a TC) the mapping
is rejected but reported as success.  With this patch, qdisc_type is then set
to STMMAC_QDISC_MQPRIO (and likewise to STMMAC_QDISC_TAPRIO in
tc_taprio_configure()) for a mapping that was never written to the hardware,
so the new ownership state describes hardware state that does not exist.
Would routing the accessor through stmmac_do_callback(), or making the ops
member void, be preferable before building ownership tracking on the return
value?

>  	return 0;
>  
>  error_reset_num_tx_queues:

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829-stmmac-fix-graft-overwrite-v1-0-a4f84c4c264e%40oss.qualcomm.com

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

end of thread, other threads:[~2026-09-02 12:30 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29  8:28 [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi
2026-08-29  8:28 ` [PATCH net 1/2] net: stmmac: preserve real_num_tx_queues on mqprio setup failure Lorenzo Bianconi
2026-09-02 12:30   ` [net,1/2] " netdev-bot+sashiko
2026-08-29  8:28 ` [PATCH net 2/2] net: stmmac: preserve FPE preemption class across qdisc replacement Lorenzo Bianconi
2026-09-02 12:30   ` [net,2/2] " netdev-bot+sashiko
2026-09-01  9:07 ` [PATCH net 0/2] net: stmmac: fix qdisc configuration overwrite during qdisc_graft() Lorenzo Bianconi

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