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
next prev parent reply other threads:[~2026-06-30 17:24 UTC|newest]
Thread overview: 46+ 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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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-29 17:23 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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-29 17:23 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
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 ` Selvamani Rajagopal
2026-06-29 17:23 ` [PATCH net-next v6 11/15] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
2026-06-29 17:23 ` Selvamani Rajagopal
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-29 17:23 ` Selvamani Rajagopal
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-07-02 19:29 ` Julian Braha
2026-07-02 21:23 ` Selvamani Rajagopal
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-29 17:23 ` Selvamani Rajagopal
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-29 17:23 ` Selvamani Rajagopal
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
2026-06-29 17:23 ` Selvamani Rajagopal
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 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.