Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Selvamani Rajagopal via B4 Relay"
	<devnull+Selvamani.Rajagopal.onsemi.com@kernel.org>
Cc: robh@kernel.org, devicetree@vger.kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH net-next v6 12/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY
Date: Tue, 30 Jun 2026 17:24:03 +0000	[thread overview]
Message-ID: <20260630172404.108871F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260629-s2500-mac-phy-support-v6-12-18ce79500371@onsemi.com>

Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider:
- [High] Lost wakeup race condition in the kthread wait loop causes `kthread_stop()` to stall for up to 5 seconds during interface teardown.
- [High] The PTP `adjtime` callback manually slews the clock synchronously, blocking the caller for up to 60 seconds.
- [High] Missing cleanup of `oa_tc6` state and PTP clock on driver probe failure leads to resource leaks and Use-After-Free vulnerabilities.
- [High] Unprotected iteration of the multicast address list in the background worker thread.
- [High] Missing error handling and resource cleanup in `s2500_open` leads to orphaned kthreads and active PHY states.
- [High] The PTP `max_adj` configuration causes a signed long overflow of the `scaled_ppm` parameter on 32-bit architectures.
- [High] Premature termination of the polling kthread on transient SPI errors leaves a dangling pointer, leading to a Use-After-Free.
- [High] Passing an uninitialized `struct timespec64` by value to `s2500_use_settime` triggers undefined behavior.
- [High] Data Race on the `S2500_REG_MAC_CTRL` register due to concurrent Read-Modify-Write cycles.
- [High] Concurrent calls to `s2500_ptp_adjfine()` clobber the long-running frequency slew in `s2500_ptp_adjtime()`, and `adjtime` destructively resets the baseline frequency.
- [High] Incorrect math in the PTP frequency slew calculation results in a 1,000x slower adjustment.
- [Medium] Standard networking statistics are improperly duplicated in driver-private `ethtool -S` strings instead of using the standard uAPI (`ndo_get_stats64`).
- [Medium] The driver fails to validate the constructed MAC address read from NVRAM.
- [Medium] Unhandled return value from `s2500_set_mac_filter` in `s2500_init` masks configuration failures.
- [Medium] The driver abuses `dev->platform_data` to store dynamically allocated runtime driver state.
--

commit 959b629541f81f549c8073520aa3f70fa8b692f6
Author: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Subject: onsemi: s2500: Add driver support for TS2500 MAC-PHY

This commit adds support for the onsemi S2500 Ethernet transceiver with
integrated MAC-PHY. It implements SPI communication, ethtool support,
and hardware timestamping utilizing the Open Alliance TC6 framework.

> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
> new file mode 100644
> index 0000000000000..7760cec578492
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c

[ ... ]

> @@ -0,0 +1,353 @@
> +static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
> +	"tx_bytes",
> +	"tx_frames",

[Severity: Medium]
Are these standard statistics being exposed as private ethtool counters?
The networking driver subsystem guidelines forbid duplicating standard 
counters in ethtool -S output. Exposing them here while omitting the 
standard ndo_get_stats64 callback prevents standard tools like 
"ip -s link show" from accessing them.

> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_main.c b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
> new file mode 100644
> index 0000000000000..687c98b69cc02
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c

[ ... ]

> @@ -0,0 +1,655 @@
> +static int s2500_read_mac_from_nvmem(struct s2500_info *priv)
> +{

[ ... ]

> +	addr[3] = FIELD_GET(S2500_VS_MACID1_BYTE3_MASK, mac1);
> +	addr[4] = FIELD_GET(S2500_VS_MACID0_BYTE4_MASK, mac0);
> +	addr[5] = FIELD_GET(S2500_VS_MACID0_BYTE5_MASK, mac0);
> +
> +	__dev_addr_set(priv->ndev, addr, ETH_ALEN);
> +	priv->ndev->addr_assign_type = NET_ADDR_PERM;
> +	return ret;
> +}

[Severity: Medium]
Is it possible for the hardware to yield an invalid MAC address here?
There is no is_valid_ether_addr() check before assigning the address 
to the netdev. If the NVRAM is unprogrammed or corrupted, this bypasses 
the random MAC generation fallback in s2500_probe() and registers an 
invalid address.

[ ... ]

> +static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,
> +				      u32 in_bits, bool clr)
> +{
> +	u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
> +	u32 reg = S2500_REG_MAC_CTRL;
> +	u32 rval = 0;
> +	u32 wval = 0;
> +	int ret;
> +
> +	ret = oa_tc6_read_register_mms(priv->tc6, reg, mms, &rval);
> +	if (ret)
> +		return ret;
> +
> +	if (clr)
> +		wval = rval & ~in_bits;
> +	else
> +		wval = rval | in_bits;
> +	if (rval != wval)
> +		ret = oa_tc6_write_register_mms(priv->tc6, reg,
> +						mms, wval);
> +	return ret;
> +}

[Severity: High]
Is this read-modify-write operation protected against concurrent execution?
It appears this function can be invoked simultaneously by the background 
kthread updating RX filters and userspace bringing the interface down 
via s2500_stop(), leading to lost register updates and an inconsistent 
interface state.

[ ... ]

> +static int s2500_init(struct s2500_info *priv)
> +{
> +	u32 val;
> +	int ret;

[ ... ]

> +	/* Program the source MAC address into the device */
> +	ret = s2500_set_mac_filter(priv->ndev, priv->ndev->dev_addr);
> +
> +	val = S2500_MAC_CTRL_ADRF_BIT | S2500_MAC_CTRL_FCSA_BIT;
> +
> +	return s2500_mac_ctrl_modify_bits(priv, val, false);
> +}

[Severity: Medium]
Does this mask configuration failures during initialization?
The ret variable is overwritten with the result of s2500_mac_ctrl_modify_bits()
without being validated. If s2500_set_mac_filter() encounters an SPI error,
it will fail silently.

[ ... ]

> +static int s2500_set_multicast_mode(struct s2500_info *priv,
> +				    unsigned int rx_flags)
> +{

[ ... ]

> +		/* Disable filters */
> +		for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
> +			addr = S2500_REG_MAC_ADDRFILTH(i);
> +			ret = oa_tc6_write_register_mms(priv->tc6,
> +							addr, mms, 0);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		i = 1;
> +		netdev_for_each_mc_addr(ha, priv->ndev) {
> +			if (i > S2500_N_MCAST_FILTERS)
> +				break;

[Severity: High]
Is it safe to iterate the multicast address list here without acquiring 
netif_addr_lock_bh()? Since this executes in the background worker thread,
concurrent modifications via dev_mc_add() or dev_mc_del() could cause 
a use-after-free and list corruption.

[ ... ]

> +static int s2500_thread_fun(void *data)
> +{
> +	struct s2500_info *priv = data;
> +	bool update_rx_mode = false;
> +	unsigned long flags;
> +	signed long tout;
> +	int ret = 0;
> +
> +	tout = priv->poll_jiff;
> +
> +	do {
> +		if (update_rx_mode) {
> +			ret = s2500_rx_mode_update(priv);
> +			if (unlikely(ret)) {
> +				dev_err(&priv->spi->dev,
> +					"Failed to set new RX mode");
> +				break;
> +			}
> +		}

[Severity: High]
Could this break statement cause a use-after-free?
By exiting the kthread loop on a transient SPI error, the thread will 
gracefully drop its task reference and its task_struct will be freed. 
However, priv->thread is left dangling and will be dereferenced later 
by kthread_stop() during interface teardown.

[ ... ]

> +		s2500_process_events(priv);
> +
> +		spin_lock_irqsave(&priv->lock, flags);
> +		__set_current_state(TASK_INTERRUPTIBLE);
> +
> +		update_rx_mode = priv->rx_flags_upd;
> +		ret = update_rx_mode;
> +
> +		spin_unlock_irqrestore(&priv->lock, flags);
> +
> +		if (!ret)
> +			tout = schedule_timeout(tout);
> +		else
> +			set_current_state(TASK_RUNNING);
> +	} while (!kthread_should_stop());
> +	return 0;
> +}

[Severity: High]
Does this sequence create a lost wakeup window?
Because the state is set to TASK_INTERRUPTIBLE and the thread sleeps
without a prior kthread_should_stop() check, if kthread_stop() sets 
the should_stop flag and wakes the thread right before __set_current_state(), 
the thread will go to sleep ignoring the stop request. This can stall 
s2500_stop() for up to 5 seconds.

[ ... ]

> +static int s2500_open(struct net_device *ndev)
> +{
> +	struct s2500_info *priv = netdev_priv(ndev);
> +	int ret = 0;
> +	u32 val;
> +
> +	dev_dbg(&ndev->dev, "%s", "s2500_open");
> +	phy_start(priv->ndev->phydev);
> +
> +	priv->thread = kthread_run(s2500_thread_fun, priv,
> +				   DRV_NAME "/%s:%d",
> +				   dev_name(&priv->spi->dev),
> +				   spi_get_chipselect(priv->spi, 0));
> +
> +	if (IS_ERR(priv->thread)) {
> +		ret = PTR_ERR(priv->thread);
> +	} else {
> +		val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;
> +		ret = s2500_mac_ctrl_modify_bits(priv, val, false);
> +
> +		netif_start_queue(priv->ndev);
> +	}
> +	return ret;
> +}

[Severity: High]
Is cleanup missing on the error path here?
If s2500_mac_ctrl_modify_bits() fails, the function returns an error 
but fails to call kthread_stop() or phy_stop(). Since ndo_open fails, 
the network stack will not invoke ndo_stop, permanently leaking the 
active PHY state and leaving the kthread running indefinitely.

[ ... ]

> +static const struct net_device_ops s2500_netdev_ops = {
> +	.ndo_open            = s2500_open,
> +	.ndo_stop            = s2500_stop,
> +	.ndo_start_xmit      = s2500_start_xmit,
> +	.ndo_set_mac_address = s2500_set_mac_address,
> +	.ndo_set_rx_mode     = s2500_set_rx_mode,
> +	.ndo_eth_ioctl       = s2500_ioctl,
> +	.ndo_hwtstamp_get    = s2500_hwtstamp_get,
> +	.ndo_hwtstamp_set    = s2500_hwtstamp_set,
> +};

[Severity: Medium]
This confirms the omission of the ndo_get_stats64 callback, which is 
required to expose standard networking statistics to userspace properly 
instead of duplicating them in ethtool -S.

[ ... ]

> +static int s2500_probe(struct spi_device *spi)
> +{
> +	struct device *dev = &spi->dev;
> +	struct net_device *ndev;
> +	struct s2500_info *priv;

[ ... ]

> +	spin_lock_init(&priv->lock);
> +	mutex_init(&priv->ptp_adj_lock);
> +	ndev->irq = spi->irq;
> +
> +	spi->dev.platform_data = priv;
> +	spi_set_drvdata(spi, priv);

[Severity: Medium]
Could the usage of platform_data here overwrite board-supplied configuration?
The platform_data field is designated for system setup code to pass downward 
static constraints. Storing dynamically allocated runtime state here should 
be avoided; spi_set_drvdata() is already being used and is the correct 
mechanism.

[ ... ]

> +	/* Configure PTP if the model supports it */
> +	ret = s2500_ptp_register(priv);
> +	if (unlikely(ret))
> +		goto err_reg_read;
> +
> +	ret = register_netdev(ndev);
> +	if (ret) {
> +		dev_err(&spi->dev, "failed to register netdev\n");
> +		ret = -ENODEV;
> +
> +		goto err_reg_read;
> +	}
> +	return 0;
> +
> +err_reg_read:
> +	dev_err(&spi->dev, "could not initialize macphy");
> +	return ret;
> +}

[Severity: High]
Does this error path leak resources?
If register_netdev() or s2500_ptp_register() fails, the code jumps to 
err_reg_read without calling oa_tc6_ptp_unregister() or oa_tc6_exit(). 
Because s2500_info is allocated via devm_, it will be freed on probe 
failure, leaving active components referencing freed memory.

> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
> new file mode 100644
> index 0000000000000..7da660cb5b190
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c

[ ... ]

> @@ -0,0 +1,250 @@
> +static int s2500_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
> +{
> +	struct s2500_info *priv = container_of(ptp, struct s2500_info,
> +					       ptp_clock_info);
> +	u32 sign_bit = 0;
> +	long adj;
> +	u32 val;
> +	u64 ppm;
> +
> +	if (scaled_ppm < 0) {
> +		/* split sign / mod */
> +		sign_bit = 1U << 31;
> +		scaled_ppm = ~scaled_ppm + 1;
> +	}

[ ... ]

> +	/* check overflow */
> +	if (adj >= (1L << 28))
> +		return -ERANGE;
> +
> +	val = (u32)adj | sign_bit;
> +	return oa_tc6_write_register_mms(priv->tc6, S2500_REG_VS_PTP_ADJ,
> +					 OA_TC6_PHY_C45_VS_MMS12, val);
> +}

[Severity: High]
Is this lockless hardware access safe?
Concurrent calls to s2500_ptp_adjfine() can silently clobber the long-running 
frequency slew currently being performed in s2500_ptp_adjtime().

[ ... ]

> +static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
> +	struct s2500_info *priv = container_of(ptp, struct s2500_info,
> +					       ptp_clock_info);
> +	struct ptp_system_timestamp sts;
> +	struct timespec64 target;
> +	unsigned int period_ms;
> +	struct timespec64 now;
> +	s64 scaled_ppm, rem;
> +	int max_iters = 3;

[ ... ]

> +	while (rem != 0 && max_iters--) {
> +		s64 abs_delta = rem > 0 ? rem : -rem;
> +
> +		/* If the adjustment is very large, more than 1 second,
> +		 * use settime to avoid very long slewing periods or
> +		 * excessive frequency offsets.
> +		 */
> +		if (abs_delta > 1000000000LL) {
> +			ret = s2500_use_settime(ptp, now, rem);
> +			rem = 0;
> +			break;
> +		}

[Severity: High]
Can this result in undefined behavior?
The "now" structure is declared but uninitialized here. It is passed 
by value to s2500_use_settime(), which copies the uninitialized stack memory.

> +
> +		/* Choose a slewing period depending on magnitude */
> +		if (abs_delta <= 1000000LL) /* <= 1ms */
> +			period_ms = 1000; /* 1 s */
> +		else if (abs_delta <= 100000000LL) /* <= 100ms */
> +			period_ms = 10000; /* 10 s */
> +		else
> +			period_ms = 60000; /* 60 s */
> +
> +		/* Compute current time and fixed target for this
> +		 * iteration
> +		 */
> +		memset(&sts, 0, sizeof(sts));
> +		ret = ptp->gettimex64(ptp, &now, &sts);
> +		if (ret)
> +			break;
> +
> +		if (rem >= 0)
> +			target = timespec64_add(now,
> +						ns_to_timespec64(rem));
> +		else
> +			target = timespec64_sub(now,
> +						ns_to_timespec64(-rem));
> +
> +		/* Compute scaled_ppm (Qx.16). scaled_ppm = ppm * 2^16
> +		 * ppm = (delta_seconds / period_seconds) * 1e6
> +		 * => scaled_ppm = delta_ns * 65536 / (period_ms * 1000)
> +		 */
> +		num = rem * 65536LL;
> +		den = (s64)period_ms * 1000LL;

[Severity: High]
Does this math correctly scale the frequency adjustment?
By dividing by (period_ms * 1000) instead of accurately scaling the values, 
the resulting scaled_ppm drastically shrinks the frequency adjustment 
instead of expanding it. This creates a result mathematically 1,000,000 
times too small, causing the slew to fail to reach the target time offset.

> +
> +		/* Integer division rounds toward zero; keep sign in
> +		 * numerator
> +		 */
> +		scaled_ppm = div_s64(num, den);
> +
> +		/* Apply frequency adjustment */
> +		ret = ptp->adjfine(ptp, (long)scaled_ppm);
> +		if (ret)
> +			break;
> +
> +		/* Sleep for the slew period (interruptible). If
> +		 * interrupted, clear the adjfine and return -EINTR.
> +		 */
> +		if (msleep_interruptible(period_ms)) {

[Severity: High]
Is it acceptable for adjtime to block synchronously?
The PTP subsystem expects adjtime callbacks to return immediately by 
stepping the clock or deferring logic. Selecting a sleep duration of 
up to 60 seconds and calling msleep_interruptible() will freeze 
userspace PTP daemons and break PI control loops.

> +			/* Clear adjfine */
> +			ptp->adjfine(ptp, 0);
> +			ret = -EINTR;
> +			break;
> +		}
> +
> +		/* Clear adjfine and measure remaining offset */
> +		ptp->adjfine(ptp, 0);

[Severity: High]
Could this destructively reset the baseline frequency?
By unconditionally resetting the frequency offset to 0 upon waking 
from sleep, any concurrent adjustments dynamically applied by the 
PTP daemon via clock_adjtime(ADJ_FREQUENCY) will be wiped out, 
preventing accurate clock synchronization.

[ ... ]

> +int s2500_ptp_register(struct s2500_info *priv)
> +{
> +	struct ptp_clock_info *info = &priv->ptp_clock_info;
> +	int ret;
> +
> +	info->max_adj = 100000000;
> +	info->owner = THIS_MODULE;

[Severity: High]
Will this maximum adjustment value overflow on 32-bit architectures?
A max_adj of 100,000,000 ppb will be multiplied by 65.536 by the PTP core 
to yield a scaled_ppm of ~6.55 billion. This exceeds the capacity of a 
signed 32-bit long, causing large positive frequency adjustments to overflow 
into negative values and apply an inverted slew.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com?part=12

  parent reply	other threads:[~2026-06-30 17:24 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-29 17:23 [PATCH net-next v6 00/15] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 01/15] net: phy: Helper to read and write through C45 without lock Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 02/15] net: phy: Helper to modify PHY loopback mode only Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 03/15] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
2026-06-30 17:24   ` sashiko-bot
2026-06-29 17:23 ` [PATCH net-next v6 04/15] net: phy: microchip_t1s: Use generic APIs for C45 read and write Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 05/15] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 06/15] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
2026-06-30 17:24   ` sashiko-bot
2026-07-01 19:33   ` Jerry.Ray
2026-07-01 21:59     ` Selvamani Rajagopal
2026-06-29 17:23 ` [PATCH net-next v6 07/15] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 08/15] net: ethernet: oa_tc6: read, write interface with MMS option Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 09/15] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 10/15] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` [PATCH net-next v6 11/15] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
2026-06-30 17:24   ` sashiko-bot
2026-06-29 17:23 ` [PATCH net-next v6 12/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY Selvamani Rajagopal via B4 Relay
2026-06-30 17:08   ` Uwe Kleine-König
2026-06-30 17:36     ` Selvamani Rajagopal
2026-06-30 17:24   ` sashiko-bot [this message]
2026-06-29 17:23 ` [PATCH net-next v6 13/15] onsemi: s2500: Added selftest support to onsemi's S2500 driver Selvamani Rajagopal via B4 Relay
2026-06-30 17:24   ` sashiko-bot
2026-06-29 17:23 ` [PATCH net-next v6 14/15] dt-bindings: net: add onsemi's S2500 Selvamani Rajagopal via B4 Relay
2026-06-30  6:29   ` Krzysztof Kozlowski
2026-06-30 15:09     ` Selvamani Rajagopal
2026-06-30 17:24   ` sashiko-bot
2026-06-29 17:23 ` [PATCH net-next v6 15/15] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay

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=20260630172404.108871F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=devnull+Selvamani.Rajagopal.onsemi.com@kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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