Netdev List
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: mmyangfl@gmail.com
Cc: netdev@vger.kernel.org, andrew@lunn.ch, olteanv@gmail.com,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, linux@armlinux.org.uk,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next v3 2/6] net: dsa: motorcomm: Split MIB buffers
Date: Wed, 09 Sep 2026 15:16:13 +0000	[thread overview]
Message-ID: <178896697363.219967.5664786879390405949@kernel.org> (raw)
In-Reply-To: <20260906131414.2683362-3-mmyangfl@gmail.com>

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

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

- [Medium] Asymmetric use of the counted workqueue disable API: the new
  .port_teardown callback yt921x_dsa_port_teardown()…
- [Low] The descriptor-table comment above yt921x_mib_descs[] says 'Must
  agree with yt921x_mib', but after this patch `struct yt921x_mib`…

Pre-existing issues:
- [Medium] The software MIB shadow is not re-zeroed when the hardware MIB
  counters are cleared: yt921x_chip_setup() issues YT921X_MIB_CTRL_CLEAN…
- [Low] Unsynchronized 64-bit statistics: the MIB counters are written by
  yt921x_mib_read() from the poll worker under priv->reg_lock (plain…

--

net: dsa: motorcomm: Split MIB buffers

This moves the per-port MIB statistics, the derived rx/tx frame counters
and the MIB poll delayed_work out of struct yt921x_port into a new
struct yt921x_mib, allocated with devm_kzalloc() from .port_setup() only
for internal/external ports.  A new .port_teardown() callback replaces
the disable loop that used to live in yt921x_mdio_remove().

> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index ad5fe1ccb1d71..95041a2fb0ecc 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -549,9 +549,12 @@ static int yt921x_mib_read(struct yt921x_priv *priv, int port)
>  {
>  	struct yt921x_port *pp = &priv->ports[port];
>  	struct device *dev = to_device(priv);
> -	struct yt921x_mib_stats *mib = &pp->mib;
> +	struct yt921x_mib *pm = pp->mib;
> +	struct yt921x_mib_stats *mib;
>  	int res = 0;
>  
> +	mib = &pm->stats;
> +
>  	/* Reading of yt921x_port::mib is not protected by a lock and it's vain
>  	 * to keep its consistency, since we have to read registers one by one
>  	 * and there is no way to make a snapshot of MIB stats.

[Severity: Low]
Two comments look stale after the split.

The header comment above yt921x_mib_descs[] says "Must agree with
yt921x_mib", but struct yt921x_mib is now the wrapper holding the port
back pointer, the delayed work and the two derived counters.  The
descriptor offsets are applied to the flat counter layout instead:

	mib = &pm->stats;
	...
		u64 *valp = &((u64 *)mib)[i];

Should that comment name yt921x_mib_stats now?

The comment kept here still says "Reading of yt921x_port::mib is not
protected by a lock", although that member is only a pointer after this
patch and the lockless data lives in yt921x_mib::stats, rx_frames and
tx_frames.  This second half is removed later in the series by "net: dsa:
motorcomm: Fix MIB synchronization", but the yt921x_mib_descs comment
still reads "Must agree with yt921x_mib" at the end of the series.

[ ... ]

> @@ -786,7 +810,12 @@ yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
>  {
>  	struct yt921x_priv *priv = to_yt921x_priv(ds);
>  	struct yt921x_port *pp = &priv->ports[port];
> -	struct yt921x_mib_stats *mib = &pp->mib;
> +	struct yt921x_mib *pm = pp->mib;
> +	struct yt921x_mib_stats *mib;
> +
> +	if (!pm)
> +		return;
> +	mib = &pm->stats;
>  
>  	stats->rx_length_errors = mib->rx_undersize_errors +
>  				  mib->rx_fragment_errors;
> @@ -802,8 +831,8 @@ yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
>  	/* stats->tx_heartbeat_errors */
>  	stats->tx_window_errors = mib->tx_late_collisions;
>  
> -	stats->rx_packets = pp->rx_frames;
> -	stats->tx_packets = pp->tx_frames;
> +	stats->rx_packets = pm->rx_frames;
> +	stats->tx_packets = pm->tx_frames;
>  	stats->rx_bytes = mib->rx_good_bytes - ETH_FCS_LEN * stats->rx_packets;
>  	stats->tx_bytes = mib->tx_good_bytes - ETH_FCS_LEN * stats->tx_packets;

[Severity: Low]
This isn't a bug introduced by this patch, but can these u64 reads tear on
32-bit?  yt921x_mib_read() stores pm->rx_frames and pm->tx_frames with
plain assignments from the poll worker, and yt921x_dsa_get_stats64() reads
them with neither priv->reg_lock nor a u64_stats_sync retry loop, so a
reader can observe halves from two different updates.  The subtraction

	stats->rx_bytes = mib->rx_good_bytes - ETH_FCS_LEN * stats->rx_packets;

can then underflow.

The baseline had the same lockless read of pp->rx_frames/pp->tx_frames, and
later patches in this series ("net: dsa: motorcomm: Use u64_stats_t for MIB
stats" and "net: dsa: motorcomm: Fix MIB synchronization") wrap the writer
in u64_stats_update_begin()/end() and the reader in
u64_stats_fetch_begin()/u64_stats_fetch_retry(), so this is only noted for
completeness.

[ ... ]

> @@ -3988,7 +4026,9 @@ yt921x_phylink_mac_link_up(struct phylink_config *config,
>  		dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up",
>  			port, res);
>  
> -	schedule_delayed_work(&priv->ports[port].mib_read, 0);
> +	pm = priv->ports[port].mib;
> +	if (pm)
> +		schedule_delayed_work(&pm->work, 0);
>  }
>  
>  static void
> @@ -4108,11 +4148,35 @@ yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
>  	return DSA_TAG_PROTO_YT921X;
>  }
>  
> +static void yt921x_dsa_port_teardown(struct dsa_switch *ds, int port)
> +{
> +	struct yt921x_priv *priv = to_yt921x_priv(ds);
> +	struct yt921x_port *pp = &priv->ports[port];
> +	struct yt921x_mib *pm = pp->mib;
> +
> +	if (pm)
> +		disable_delayed_work_sync(&pm->work);
> +}
> +
>  static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port)
>  {
>  	struct yt921x_priv *priv = to_yt921x_priv(ds);
> +	struct yt921x_port *pp = &priv->ports[port];
> +	struct device *dev = to_device(priv);
> +	struct yt921x_mib *pm = pp->mib;
>  	int res;
>  
> +	if (!pm && (BIT(port) & (priv->info->internal_mask |
> +		    priv->info->external_mask))) {
> +		pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
> +		if (!pm)
> +			return -ENOMEM;
> +		pp->mib = pm;
> +
> +		pm->port = pp;
> +		INIT_DELAYED_WORK(&pm->work, yt921x_mib_poll);
> +	}
> +

[Severity: Medium]
Is the disable/enable pair here asymmetric?  yt921x_dsa_port_teardown()
calls disable_delayed_work_sync(&pm->work), but yt921x_dsa_port_setup() has
no matching enable_delayed_work(), and the "if (!pm && ...)" gate means an
already allocated pm (and its work item, including the disable counter) is
reused untouched.

The disable count is a plain counter in the work data, documented in
kernel/workqueue.c as:

 * Disable @work by incrementing its disable count and cancel it if currently
 * pending. As long as the disable count is non-zero, any attempt to queue @work
 * will fail and return %false.

with work_offqd_disable()/work_offqd_enable() incrementing/decrementing it
and enable_delayed_work() being the only decrement.  So after a single
teardown/setup cycle on a still-live switch:

yt921x_phylink_mac_link_up()
	schedule_delayed_work(&pm->work, 0);	/* returns false, forever */

yt921x_mib_poll() never runs again and the cached counters read by
yt921x_dsa_get_stats64() and the ethtool callbacks stay frozen, including
the 32-bit wrap accumulation done in yt921x_mib_read().

Two paths where DSA runs port_setup again for the same priv:

net/dsa/dsa.c:dsa_tree_setup_ports() {
	...
			err = dsa_port_setup(dp);
			if (err) {
				err = dsa_port_setup_as_unused(dp);
	...
}

dsa_port_setup() already ran the driver's port_teardown on its error path
(dsa_port_devlink_setup() calls dsa_port_devlink_teardown() when
devlink_port_register() fails), and then port_setup is called again.

In a multi-switch tree, dsa_switch_remove() -> dsa_tree_teardown() tears
down the ports of the surviving switches too, and a later re-probe runs
dsa_tree_setup() again, calling port_setup() for a port whose pp->mib is
already non-NULL.

Should port_setup() call enable_delayed_work(&pm->work) when it reuses an
existing pm?  As written, each cycle also bumps the disable depth
monotonically until it hits WORK_OFFQ_DISABLE_BITS and WARN_ONCE fires.

[Severity: Medium]
This is a pre-existing issue, but should the shadow counters be re-zeroed
here rather than only on first allocation?  yt921x_chip_setup() clears the
hardware MIB on every .setup():

	ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT;
	res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl);

while pm->stats is zeroed only by the devm_kzalloc() inside the
"if (!pm && ...)" branch.  dsa_switch_teardown() clears ds->setup, so a
second dsa_tree_setup() can call ds->ops->setup again for the same priv (for
example when a sibling switch in the tree is removed and re-probed).  The
retained shadow then holds pre-clean values while the hardware restarts at
zero, and the first read applies the wrap correction:

drivers/net/dsa/motorcomm/chip.c:yt921x_mib_read() {
	...
			val = (old_val & ~(u64)U32_MAX) | val0;
			if (val < old_val)
				val += 1ull << 32;
	...
}

adding a spurious 2^32 to every affected counter.  The baseline had the same
behaviour with the shadow embedded in priv.

[ ... ]

> diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
> index 805febb486838..5bf2d3409f4bb 100644
> --- a/drivers/net/dsa/motorcomm/chip.h
> +++ b/drivers/net/dsa/motorcomm/chip.h
> @@ -913,6 +913,15 @@ struct yt921x_mib_stats {
>  	u64 tx_oam;
>  };
>  
> +struct yt921x_mib {
> +	struct yt921x_port *port;
> +
> +	struct delayed_work work;
> +	struct yt921x_mib_stats stats;
> +	u64 rx_frames;
> +	u64 tx_frames;
> +};
> +

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260906131414.2683362-1-mmyangfl%40gmail.com

  reply	other threads:[~2026-09-09 15:16 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-06 13:14 [PATCH net-next v3 0/6] net: dsa: motorcomm: MIB fixup David Yang
2026-09-06 13:14 ` [PATCH net-next v3 1/6] net: dsa: motorcomm: Rename MIB stuffs David Yang
2026-09-09 15:16   ` netdev-bot+sashiko
2026-09-09 17:32     ` David Yang
2026-09-06 13:14 ` [PATCH net-next v3 2/6] net: dsa: motorcomm: Split MIB buffers David Yang
2026-09-09 15:16   ` netdev-bot+sashiko [this message]
2026-09-06 13:14 ` [PATCH net-next v3 3/6] net: dsa: motorcomm: Split MIB module David Yang
2026-09-09 15:16   ` netdev-bot+sashiko
2026-09-06 13:14 ` [PATCH net-next v3 4/6] net: dsa: motorcomm: Use u64_stats_t for MIB stats David Yang
2026-09-09 15:16   ` netdev-bot+sashiko
2026-09-06 13:14 ` [PATCH net-next v3 5/6] net: dsa: motorcomm: Fix MIB synchronization David Yang
2026-09-09 15:16   ` netdev-bot+sashiko
2026-09-09 17:45     ` David Yang
2026-09-06 13:14 ` [PATCH net-next v3 6/6] net: dsa: motorcomm: Use safe 64-bit counter reader David Yang
2026-09-09 15:16   ` netdev-bot+sashiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178896697363.219967.5664786879390405949@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=andrew@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=mmyangfl@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=olteanv@gmail.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox