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 v4 13/16] onsemi: s2500: Add driver support for TS2500 MAC-PHY
Date: Sun, 07 Jun 2026 05:43:02 +0000 [thread overview]
Message-ID: <20260607054302.DDCC71F00893@smtp.kernel.org> (raw)
In-Reply-To: <20260605-s2500-mac-phy-support-v4-13-de0fbc13c6d8@onsemi.com>
Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] The PTP adjtime ioctl callback actively blocks the calling user-space thread for up to 3 minutes.
- [High] Hardware multicast filtering is inadvertently permanently disabled when 1 to 3 multicast addresses are configured.
- [High] Missing cleanup in the s2500_open() error path leaks the PHY and leaves a dangling ERR_PTR causing kernel panics.
- [High] A transient SPI error permanently terminates the background kthread and leads to a Use-After-Free.
- [High] Missing cleanup in device probe error paths leaks the OA TC6 framework and PTP resources.
- [High] Unprotected read-modify-write operations on the MAC_CTRL register lead to race conditions and corrupted device state.
- [High] The `target` timespec64 variable is used uninitialized if `ptp->gettimex64()` fails.
- [High] Missing kthread_should_stop() check after setting TASK_INTERRUPTIBLE causes up to a 5-second shutdown delay.
- [High] The driver iterates over the multicast address list locklessly in a background thread, risking list corruption and use-after-free.
- [Medium] The driver duplicates standard network statistics in its private ethtool -S interface and fails to implement ndo_get_stats64.
- [Medium] The driver advertises a max_adj of 100,000,000 ppb, but its adjfine implementation rejects values exceeding ~33.5 million ppb.
- [Medium] Software MAC address is updated even if the hardware update fails.
- [Medium] Concurrent adjtime and adjfine calls clobber the hardware frequency adjustments.
- [Medium] Multicast MAC filter registers are written in the wrong order.
--
commit 7da83aa832497d5fef58b67e53a49c036aa7948e
Author: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Subject: onsemi: s2500: Add driver support for TS2500 MAC-PHY
This commit introduces the driver for the onsemi S2500 10BASE-T1S MAC-PHY
Ethernet transceiver. It utilizes the Open Alliance TC6 framework to handle
SPI transactions, and includes standard network device operations, ethtool
capabilities, and PTP hardware timestamping with a custom phase adjustment
fallback loop.
> 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..85bd8ea50dd8c
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
[ ... ]
> +static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
> + "tx_bytes",
> + "tx_frames",
> + "tx_broadcast_frames",
> + "tx_multicast_frames",
[Severity: Medium]
Should standard network UAPI stats like tx_bytes and tx_frames be duplicated
here in the private ethtool strings?
According to the standard kernel networking guidelines, UAPI stats should be
implemented via the standard ndo_get_stats64 interface instead of adding
private UAPI strings in ethtool -S.
> 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..7aa3267db39b5
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
[ ... ]
> +static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,
> + u32 in_bits, bool clr)
> +{
> + u32 reg = S2500_REG_MAC_CTRL;
> + u32 rval = 0;
> + int ret;
> +
> + ret = oa_tc6_read_register_mms(priv->tc6, reg,
> + OA_TC6_PHY_C45_MAC_MMS1, &rval);
> + if (!ret) {
> + u32 wval = 0;
> +
> + if (clr)
> + wval = rval & ~in_bits;
> + else
> + wval = rval | in_bits;
> + if (rval != wval)
> + ret = oa_tc6_write_register_mms(priv->tc6, reg,
> + OA_TC6_PHY_C45_MAC_MMS1, wval);
> + }
> + return ret;
> +}
[Severity: High]
Does this read-modify-write operation need mutual exclusion?
s2500_mac_ctrl_modify_bits() performs a non-atomic read-modify-write on
the MAC_CTRL register over the SPI bus. Because SPI operations can sleep,
calling this concurrently from the kthread (e.g. s2500_rx_mode_update())
and ndo_stop() could overwrite the device state, potentially leaving the
hardware incorrectly enabled or disabled.
[ ... ]
> +static int s2500_set_multicast_mode(struct s2500_info *priv,
> + unsigned int rx_flags)
> +{
[ ... ]
> + } else {
> + struct netdev_hw_addr *ha;
> + u32 addrh, addrl;
> +
> + /* Disable multicast filter */
> + ret = s2500_mac_ctrl_modify_bits(priv,
> + S2500_MAC_CTRL_MCSF_BIT,
> + true);
> + if (ret)
> + return ret;
> +
> + /* Disable filters */
> + for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
> + ret = oa_tc6_write_register_mms(priv->tc6,
> + S2500_REG_MAC_ADDRFILTH(i),
> + OA_TC6_PHY_C45_MAC_MMS1, 0);
> + if (ret)
> + return ret;
> + }
> +
> + i = 1;
> + netdev_for_each_mc_addr(ha, priv->ndev) {
[Severity: High]
Is the multicast filter accidentally left disabled in this execution path?
When 1 to 3 multicast addresses are configured, s2500_mac_ctrl_modify_bits()
is called to disable the filter, but there appears to be no subsequent call
to clear this bit and re-enable the filter after the addresses are written.
This would leave the MAC unconditionally accepting all multicast traffic.
[Severity: High]
Can this list traversal race with user-space configuration?
Since this executes from a background kthread, traversing priv->ndev->mc.list
without holding netif_addr_lock_bh() risks list corruption or a
use-after-free if a user-space daemon simultaneously modifies the addresses.
> + if (i > S2500_N_MCAST_FILTERS)
> + break;
> +
> + addrh = S2500_MAC_ADDRFILT_EN_BIT |
> + get_unaligned_be16(ha->addr);
> + addrl = get_unaligned_be32(&ha->addr[2]);
> +
> + ret = oa_tc6_write_register_mms(priv->tc6,
> + S2500_REG_MAC_ADDRFILTH(i),
> + OA_TC6_PHY_C45_MAC_MMS1, addrh);
> + if (ret)
> + return ret;
> +
> + ret = oa_tc6_write_register_mms(priv->tc6,
> + S2500_REG_MAC_ADDRFILTL(i),
> + OA_TC6_PHY_C45_MAC_MMS1, addrl);
[Severity: Medium]
Should the lower register (FILTL) be written before the high register
(FILTH)?
Because FILTH contains the S2500_MAC_ADDRFILT_EN_BIT, writing it first
enables the filter using the new high bytes with the old, uninitialized
low bytes. This could cause a brief window of incorrect hardware filtering.
[ ... ]
> +static int s2500_set_mac_address(struct net_device *ndev, void *p)
> +{
> + struct sockaddr *addr = p;
> +
> + if (!is_valid_ether_addr(addr->sa_data))
> + return -EADDRNOTAVAIL;
> +
> + eth_hw_addr_set(ndev, addr->sa_data);
> + return s2500_set_mac_filter(ndev, addr->sa_data);
> +}
[Severity: Medium]
Should the software MAC address only be updated after the hardware update
succeeds?
Updating the address via eth_hw_addr_set() before s2500_set_mac_filter()
completes means a transient SPI failure will leave the software and hardware
MAC addresses permanently out of sync.
[ ... ]
> +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]
Does this break statement leave priv->thread dangling?
If a transient SPI error occurs during s2500_rx_mode_update(), the kthread
exits permanently, but priv->thread is never cleared. Later calls to
s2500_stop() or s2500_set_rx_mode() might attempt to dereference the
freed task_struct, causing a use-after-free.
[ ... ]
> + 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]
Can this sequence cause a deadlock or a delay when stopping the interface?
The thread sets TASK_INTERRUPTIBLE and then calls schedule_timeout() without
first checking kthread_should_stop(). If kthread_stop() is executed before
the state change, the wakeup is consumed early, and the thread will sleep
unconditionally for up to 5 seconds while the caller blocks on kthread_stop.
[ ... ]
> +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]
Does this error path properly handle a kthread creation failure?
If kthread_run() fails, priv->thread is left containing an ERR_PTR and the
PHY remains running. If s2500_set_rx_mode() is later called, it checks if
priv->thread exists and attempts a wakeup, which would trigger a kernel
panic when dereferencing the ERR_PTR.
[ ... ]
> +static int s2500_probe(struct spi_device *spi)
> +{
[ ... ]
> + ret = s2500_init(priv);
> + if (unlikely(ret)) {
> + dev_err(&spi->dev, "failed to s2500_init the device");
> + goto err_reg_read;
> + }
> +
> + /* 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 the S2500 device\n");
> + ret = -ENODEV;
> +
> + goto err_reg_read;
> + }
> + return 0;
> +
> +err_reg_read:
> + dev_err(&spi->dev, "could not initialize macphy");
> + return ret;
> +}
[Severity: High]
Are the OA TC6 and PTP resources being leaked in this error path?
If initialization steps like s2500_init() or register_netdev() fail, the
code branches to err_reg_read and returns without invoking oa_tc6_exit()
or oa_tc6_ptp_unregister(). Because these are not managed by devres, they
could leak permanently.
> 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..fd6617c7ac798
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
[ ... ]
> +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;
> + }
[ ... ]
> + ppm = (u64)scaled_ppm * 125;
> + do_div(ppm, 1024);
> + adj = (long)ppm;
> +
> + /* check overflow */
> + if (adj >= (1L << 28))
> + return -ERANGE;
[Severity: Medium]
Does this boundary check reject valid frequency adjustments?
In s2500_ptp_register(), the max_adj is advertised as 100,000,000 ppb (10%).
However, restricting adj to (1L << 28) mathematically restricts the maximum
scaled_ppm equivalent to roughly 33.5 million ppb. This mismatch causes
valid user-space adjustments to fail with -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: Medium]
Is a lock required here to prevent clobbering concurrent frequency updates?
This function writes directly to the hardware without acquiring
priv->ptp_adj_lock. A concurrent user-space request to adjfine could conflict
with the driver's own internal slewing loop in s2500_ptp_adjtime().
[ ... ]
> +static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
[ ... ]
> + while (remaining != 0 && max_iters--) {
> + s64 abs_delta = remaining > 0 ? remaining : -remaining;
> +
> + /* 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) {
> + memset(&sts, 0, sizeof(sts));
> + ret = ptp->gettimex64(ptp, &now, &sts);
> + if (!ret) {
> + struct timespec64 delta_ts;
> +
> + if (remaining >= 0) {
> + delta_ts = ns_to_timespec64(remaining);
> + target = timespec64_add(now, delta_ts);
> + } else {
> + delta_ts = ns_to_timespec64(-remaining);
> + target = timespec64_sub(now, delta_ts);
> + }
> + }
> +
> + if (target.tv_sec < 0 || target.tv_sec >= (1ULL << 32))
> + ret = -ERANGE;
> + else
> + ret = ptp->settime64(ptp, &target);
[Severity: High]
Can target be evaluated uninitialized if ptp->gettimex64() fails?
If gettimex64() encounters an SPI error and returns a non-zero status, the
initialization block is bypassed, but execution falls through to check
target.tv_sec. This may pass garbage data to ptp->settime64(), potentially
corrupting the hardware clock.
[ ... ]
> + /* 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 with -EINTR.
> + */
> + if (msleep_interruptible(period_ms)) {
[Severity: High]
Is it safe to block the caller for up to 3 minutes during phase adjustment?
Because adjtime is invoked synchronously by the clock_adjtime syscall,
sleeping here with msleep_interruptible() for up to 60 seconds per iteration
will cause the user-space daemon (e.g., ptp4l) to block entirely, preventing
it from processing other network events.
> + /* Clear adjfine */
> + ptp->adjfine(ptp, 0);
> + ret = -EINTR;
> + break;
> + }
> +
> + /* Clear adjfine and measure remaining offset */
> + ptp->adjfine(ptp, 0);
[Severity: Medium]
Does clearing adjfine here wipe out concurrent user-space frequency changes?
Unconditionally calling ptp->adjfine(ptp, 0) at the end of the sleep cycle
will overwrite any concurrent clock adjustments made by other user-space
tasks, disrupting synchronization.
> +
> + memset(&sts, 0, sizeof(sts));
> + ret = ptp->gettimex64(ptp, &now, &sts);
> + if (ret)
> + break;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com?part=13
next prev parent reply other threads:[~2026-06-07 5:43 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-06 5:42 [PATCH net-next v4 00/16] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 01/16] net: phy: Helper to read and write through C45 without lock Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 02/16] net: phy: Helper to modify PHY loopback mode only Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 03/16] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:42 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 04/16] net: phy: microchip_t1s: Use generic APIs for C45 read and write Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 05/16] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 06/16] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:42 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 07/16] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:42 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 08/16] net: ethernet: oa_tc6: Remove FCS size in RX frame Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:42 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 09/16] net: ethernet: oa_tc6: read, write interface with MMS option Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 10/16] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 11/16] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 12/16] net: phy: ncn26000: Support for loopback support Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 13/16] onsemi: s2500: Add driver support for TS2500 MAC-PHY Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot [this message]
2026-06-07 5:56 ` Randy Dunlap
2026-06-07 19:25 ` Selvamani Rajagopal
2026-06-06 5:42 ` [PATCH net-next v4 14/16] onsemi: s2500: Added selftest support to onsemi's S2500 driver Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot
2026-06-06 5:42 ` [PATCH net-next v4 15/16] dt-bindings: net: add onsemi's S2500 Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot
2026-06-08 7:52 ` Krzysztof Kozlowski
2026-06-08 7:53 ` Krzysztof Kozlowski
2026-06-06 5:42 ` [PATCH net-next v4 16/16] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay
2026-06-06 5:42 ` Selvamani Rajagopal
2026-06-07 5:43 ` sashiko-bot
2026-06-07 5:49 ` Randy Dunlap
2026-06-07 21:10 ` Selvamani Rajagopal
2026-06-08 5:10 ` Selvamani Rajagopal
2026-06-08 5:19 ` Selvamani Rajagopal
2026-06-08 7:22 ` Andrew Lunn
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=20260607054302.DDCC71F00893@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.