From: Paolo Abeni <pabeni@redhat.com>
To: Selvamani.Rajagopal@onsemi.com
Cc: andrew@lunn.ch, pier.beruto@onsemi.com, hkallweit1@gmail.com,
linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, andrew+netdev@lunn.ch,
parthiban.veerasooran@microchip.com,
selvamani.rajagopal@onsemi.com, richardcochran@gmail.com,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
horms@kernel.org, corbet@lwn.net, skhan@linuxfoundation.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org, linux-doc@vger.kernel.org,
jerry.ray@microchip.com
Subject: Re: [PATCH net-next v7 14/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY
Date: Tue, 21 Jul 2026 15:17:42 +0200 [thread overview]
Message-ID: <20260721131742.233773-1-pabeni@redhat.com> (raw)
In-Reply-To: <20260708-s2500-mac-phy-support-v7-14-478c877aa1a9@onsemi.com>
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
onsemi: s2500: Add driver support for TS2500 MAC-PHY
This patch adds a driver for onsemi's S2500 10BASE-T1S MAC-PHY, built on
top of the Open Alliance TC6 framework. It provides ndo_* callbacks,
ethtool statistics/regdump/RMON support, and a PTP clock implementation
including an in-driver adjtime that mixes adjfine-based slewing with
settime.
A few questions and observations below.
> 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 000000000000..7e96d9ad563f
> --- /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",
> + "tx_underflow_errors",
> + "tx_single_collisions",
> + "tx_multiple_collisions",
> + "tx_excessive_collisions",
> + "tx_deferred_frames",
> + "tx_carrier_sense_errors",
> + "rx_bytes",
> + "rx_frames",
> + "rx_broadcast_frames",
> + "rx_multicast_frames",
> + "rx_runts",
> + "rx_oversize_frames",
> + "rx_crc_errors",
> + "rx_symbol_errors",
> + "rx_alignment_errors",
> + "rx_busy_drops",
> + "rx_mismatch_drops",
> +};
[Medium]
Do these ethtool -S entries duplicate counters that have a standard uAPI?
tx_bytes, tx_frames, rx_bytes, rx_frames, rx_broadcast_frames,
rx_multicast_frames, rx_crc_errors, rx_alignment_errors (=rx_frame_errors),
tx_carrier_sense_errors (=tx_carrier_errors), tx_underflow_errors
(=tx_fifo_errors), tx_excessive_collisions (=tx_aborted_errors),
rx_oversize_frames and rx_runts (=rx_length_errors) all appear to fall
into rtnl_link_stats64 categories exposed via ip -s link show.
The driver also does not implement ndo_get_stats64, so those standard
counters are reported as zero. Would it make sense to route these
through ndo_get_stats64 and keep ethtool -S only for the truly
driver-private counters (rx_busy_drops, rx_mismatch_drops, symbol
errors, single/multiple collisions, deferred frames)?
Documentation/networking/statistics.rst describes the expected hierarchy.
[ ... ]
> +static void s2500_rmon_stats(struct net_device *ndev,
> + struct ethtool_rmon_stats *rmon_stats,
> + const struct ethtool_rmon_hist_range **ranges)
> +{
> + struct s2500_info *priv = netdev_priv(ndev);
> + u8 offset;
> + int i;
> +
> + s2500_update_stats(priv);
> +
> + memset(rmon_stats, 0, sizeof(*rmon_stats));
> + rmon_stats->src = ETHTOOL_MAC_STATS_SRC_AGGREGATE;
> + rmon_stats->undersize_pkts = priv->stats_data[S2500_MSTRUNTSERR_IDX];
> + rmon_stats->oversize_pkts = priv->stats_data[S2500_MSTRXTOOLONG_IDX];
> + rmon_stats->fragments = priv->stats_data[S2500_MSTFCSERRS_IDX];
> + rmon_stats->jabbers = priv->stats_data[S2500_MSTALIGNERRS_IDX];
[Medium]
Do fragments and jabbers here match the RFC 2819 / RMON MIB definitions?
RMON defines fragments as undersized frames with a bad FCS/alignment and
jabbers as oversized frames with a bad FCS/alignment, not the raw FCS or
alignment error counters.
If the hardware does not have dedicated fragment/jabber counters, would
leaving these at zero avoid producing misleading data when compared
across devices?
[ ... ]
> +static void s2500_get_regs(struct net_device *ndev,
> + struct ethtool_regs *regs, void *p)
> +{
[ ... ]
> + for (i = 0; i < S2500_NUM_REGS; i++) {
> + val = 0;
> + reg = s2500_reg_map[i].reg;
> + mms = s2500_reg_map[i].mms;
> + ret = oa_tc6_read_register_mms(priv->tc6, reg, mms,
> + &val);
> + if (ret)
> + continue;
> + *pbuff++ = cpu_to_be32(((u32)mms << 16) | reg);
> + *pbuff++ = cpu_to_be32(val);
> + }
> +}
[Medium]
Why is the regdump written as big-endian u32 values via cpu_to_be32()?
The ethtool -d convention (and every other in-tree driver's regdump) is
to write host-native u32 values. On little-endian hosts the current
code produces byte-swapped mms/reg headers and register values, which
generic decoders would misinterpret. Would writing the values without
the byte swap be more consistent with existing drivers?
[ ... ]
> +const struct ethtool_ops s2500_ethtool_ops = {
[ ... ]
> + .get_sset_count = s2500_get_sset_count,
> + .get_strings = s2500_get_strings,
[ ... ]
> + .self_test = net_selftest,
> +};
[Low]
Since .self_test = net_selftest is wired up here, should get_sset_count
also return net_selftest_get_count() for ETH_SS_TEST and should
get_strings populate the strings via net_selftest_get_strings()? Without
those, ethtool cannot enumerate the tests. The follow-up patch in the
series adds this handling, but each patch typically needs to be
self-contained.
> 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 000000000000..2d81b44f8c76
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
[ ... ]
> +static int s2500_init(struct s2500_info *priv)
> +{
[ ... ]
> + /* 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);
> +}
[Medium]
Is the return value of s2500_set_mac_filter() intentionally discarded
here? It is assigned to ret but then overwritten by
s2500_mac_ctrl_modify_bits(). If any of the four SPI writes inside
s2500_set_mac_filter() fails, the driver will still enable address
filtering with a mis-programmed FILT0 register, which would silently
drop all traffic. Should the error be propagated?
[ ... ]
> +/* Deferred function for applying RX mode flags in non-atomic context */
> +static int s2500_rx_mode_update(struct s2500_info *priv)
[ ... ]
> +static void s2500_set_rx_mode(struct net_device *ndev)
> +{
> + struct s2500_info *priv = netdev_priv(ndev);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + priv->rx_flags_upd = true;
> + priv->ndev_flags = ndev->flags;
> +
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + if (priv->thread)
> + wake_up_process(priv->thread);
> +}
[High, High]
Can priv->thread be dereferenced when it is an ERR_PTR or a freed task?
In s2500_open() on failure of kthread_run(), priv->thread is left as
ERR_PTR(-errno):
priv->thread = kthread_run(s2500_thread_fun, priv, ...);
if (IS_ERR(priv->thread)) {
ret = PTR_ERR(priv->thread);
} else {
...
}
return ret;
An ERR_PTR is non-NULL, so a later s2500_set_rx_mode() would pass the
if (priv->thread) check and hand the ERR_PTR to wake_up_process() as a
task_struct pointer. Would setting priv->thread = NULL on the failure
branch be safer?
There is also a window in s2500_stop():
kthread_stop(priv->thread);
priv->thread = NULL;
kthread_stop() internally does put_task_struct() before it returns, so
between the two statements priv->thread references freed memory.
s2500_set_rx_mode() reads priv->thread without holding priv->lock, and
ndo_set_rx_mode can be invoked from BH context via dev_mc_sync-like
paths rather than under rtnl. Would clearing priv->thread under
priv->lock before calling kthread_stop() (or performing the check under
priv->lock) close this race?
[ ... ]
> +static netdev_tx_t s2500_start_xmit(struct sk_buff *skb,
[ ... ]
> +static void s2500_process_events(struct s2500_info *priv)
> +{
> + u32 val;
> + int ret;
> +
> + if (!priv->event_pending)
> + return;
> +
> + priv->event_pending = false;
> +
> + ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
> + if (ret) {
> + dev_err(&priv->spi->dev, "Error reading ST0 register");
> + return;
> + }
> +}
[High]
Should this handler write back the observed ST0 bits to acknowledge
them?
The probe path treats ST0 as write-1-to-clear:
/* Acknowledge all IRQ status bits */
ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
if (!ret) {
u32 mask = S2500_STATUS0_MASK;
val &= mask;
oa_tc6_write_register(priv->tc6, S2500_REG_SPI_ST0, val);
}
At runtime the read value is discarded and never written back, so any
status bits latched later (TX FCS errors, buffer over/underflow, LOFE,
header errors, reset-complete, timestamp captures) would remain set
indefinitely. Does the device gate further status changes or IRQs when
bits are already asserted?
[ ... ]
> +static int s2500_thread_fun(void *data)
[ ... ]
> +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;
> +}
[Medium]
If s2500_mac_ctrl_modify_bits() fails, should netif_start_queue() still
be called and should ret still be returned as failure?
Right now the queue is started even when the MAC-CTRL write returned an
error, and the function returns the error to the network stack. Since
ndo_open returned failure, s2500_stop() will not be called by the stack
to unwind, leaving the kthread running and the queue started.
[Medium]
Is phy_start() balanced on every failure path from s2500_open()?
phy_start() is called unconditionally at the top. If kthread_run() or
s2500_mac_ctrl_modify_bits() fails, s2500_open returns an error without
a matching phy_stop(), and because ndo_open failed the stack will not
call s2500_stop(). Would a goto-based unwind be more appropriate?
[ ... ]
> +static int s2500_probe(struct spi_device *spi)
> +{
[ ... ]
> + spi->dev.platform_data = priv;
> + spi_set_drvdata(spi, priv);
[Low]
Is overwriting spi->dev.platform_data intentional?
platform_data is normally a channel for the platform layer to hand
pre-populated data to a driver; drivers overwriting it can clobber data
the DT/platform code placed there. spi_set_drvdata() is already called
on the next line, so s2500_remove() could use spi_get_drvdata() /
dev_get_drvdata() instead of reading spi->dev.platform_data.
[ ... ]
> + priv->tc6 = oa_tc6_init(spi, ndev);
> + if (!priv->tc6) {
> + dev_err(&spi->dev, "OA TC6 init failed");
> + return -ENODEV;
> + }
> + oa_tc6_set_vend1_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12);
> +
> + /* Clear RSTS, if set */
> + oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MIIMIRQS,
> + OA_TC6_PHY_C45_VS_MMS12, &val);
> + val &= MIIM_IRQ_STATUS_RSTS;
> + if (val != 0)
> + oa_tc6_write_register_mms(priv->tc6,
> + S2500_REG_VS_MIIMIRQS,
> + OA_TC6_PHY_C45_VS_MMS12,
> + MIIM_IRQ_STATUS_RSTS);
[Medium]
Is val guaranteed to be initialized before the val &= ... read here?
val is declared uninitialized above and oa_tc6_read_register_mms()'s
return value is discarded. If the underlying read returns early
(e.g. -EINVAL) or fails on the SPI bus, *value is not written and the
subsequent test acts on stack garbage, which could produce an
unintended write to MIIMIRQS or skip a required RSTS acknowledgement.
[ ... ]
> + 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 netdev\n");
> + ret = -ENODEV;
> +
> + goto err_reg_read;
> + }
> + return 0;
> +
> +err_reg_read:
> + dev_err(&spi->dev, "could not initialize macphy");
> + return ret;
> +}
[High]
Does the err_reg_read path leak the resources acquired earlier in probe?
oa_tc6_init() installs a threaded IRQ, initializes the PHY and enables
data transfer, and s2500_ptp_register() calls ptp_clock_register(),
which is not devm-managed. If register_netdev() fails after PTP
registration, the ptp_clock (with its posix_clock device, debugfs
entries and aux kworker) has no owner.
Would the error path benefit from oa_tc6_ptp_unregister() and
oa_tc6_exit() to unwind these in reverse order?
> +static void s2500_remove(struct spi_device *spi)
> +{
> + struct s2500_info *priv = spi->dev.platform_data;
> +
> + dev_dbg(&priv->ndev->dev, "%s", "s2500_remove");
> +
> + oa_tc6_ptp_unregister(priv->tc6);
> + unregister_netdev(priv->ndev);
> + oa_tc6_exit(priv->tc6);
> +}
[Medium]
Is the teardown order in s2500_remove() safe against concurrent user
API access?
While the netdev is still registered, userspace can still invoke
ndo_hwtstamp_get/set via SIOCSHWTSTAMP and the ethtool get_ts_info /
get_ts_stats callbacks, which reach into the OA-TC6 PTP state. Calling
oa_tc6_ptp_unregister() before unregister_netdev() looks like the
reverse of registration order (oa_tc6_init -> s2500_ptp_register ->
register_netdev). Would unregister_netdev() first (which quiesces
user-visible ops through rtnl) close this window?
> 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 000000000000..7da660cb5b19
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
[ ... ]
> adjtime callback is implemented using adjfine. If time
> delta is too big, bigger than 1 second, using adjtime
> would take long to reduce the delta. In those cases,
> settime callback is used to reduce the delta. Once delta
> becomes less than a second, it uses adjfine to reduce
> the drift further.
[Medium]
Does the implementation match this description?
For |delta| > 1s the code calls s2500_use_settime() which computes
target = now + rem and calls ptp->settime64(target) - a full step, not
a partial reduction down to below 1s followed by adjfine. The claimed
coarse-then-fine hybrid does not appear to be implemented.
Beyond the mismatch, is hiding a time step inside adjtime consistent
with the PTP uAPI? Step and slew are exposed as different ops so
userspace daemons like phc2sys / ptp4l can choose the policy and audit
time steps; conflating them inside a driver can confuse those consumers.
[ ... ]
> +static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
[ ... ]
> + if (mutex_lock_interruptible(&priv->ptp_adj_lock))
> + return -EINTR;
> +
> + /* Try to slew the clock using adjfine for better accuracy.
> + * For large adjustments fall back to setting time directly.
> + */
> + rem = delta;
> +
> + while (rem != 0 && max_iters--) {
[ ... ]
> + /* 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 */
[ ... ]
> + /* 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)) {
[ ... ]
> + mutex_unlock(&priv->ptp_adj_lock);
> + return ret;
> +}
[High]
Should priv->ptp_adj_lock be held across msleep_interruptible() for up
to ~180 seconds (3 iterations at 60s)?
adjtime is invoked from userspace via PTP_CLOCK_ADJTIME, and the PTP
core does not serialize adjfine/settime64/gettimex64 with adjtime.
Holding the mutex across long sleeps blocks the calling task and any
other adjtime caller for minutes.
Also, s2500_ptp_adjfine() and s2500_ptp_set_time64() do not take
ptp_adj_lock, so a concurrent userspace adjfine or settime64 can
clobber (or be clobbered by) the writes to S2500_REG_VS_PTP_ADJ and the
PTP SEC/NSEC registers that this loop performs.
More broadly, would it be preferable to expose only the primitive ops
(adjfine, settime64, adjphase where supported) and let userspace
daemons implement any coarse-then-fine discipline?
--
This is an AI-generated review.
next prev parent reply other threads:[~2026-07-21 13:18 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 17:12 [PATCH net-next v7 00/15] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 01/15] dt-bindings: net: add onsemi's S2500 Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 02/15] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 03/15] net: phy: Helper to read and write through C45 without lock Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 04/15] net: phy: Helper to modify PHY loopback mode only Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 05/15] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 06/15] net: phy: microchip_t1s: Use generic APIs for C45 read and write Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 07/15] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 08/15] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
2026-07-09 10:44 ` Vadim Fedorenko
2026-07-09 15:02 ` Selvamani Rajagopal
2026-07-21 13:17 ` Paolo Abeni
2026-07-08 17:12 ` [PATCH net-next v7 09/15] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 10/15] net: ethernet: oa_tc6: read, write interface with MMS option Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 11/15] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 12/15] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 13/15] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
2026-07-08 17:12 ` [PATCH net-next v7 14/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY Selvamani Rajagopal via B4 Relay
2026-07-21 13:17 ` Paolo Abeni [this message]
2026-07-08 17:12 ` [PATCH net-next v7 15/15] onsemi: s2500: Added selftest support to onsemi's S2500 driver 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=20260721131742.233773-1-pabeni@redhat.com \
--to=pabeni@redhat.com \
--cc=Selvamani.Rajagopal@onsemi.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=hkallweit1@gmail.com \
--cc=horms@kernel.org \
--cc=jerry.ray@microchip.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=parthiban.veerasooran@microchip.com \
--cc=pier.beruto@onsemi.com \
--cc=richardcochran@gmail.com \
--cc=robh@kernel.org \
--cc=skhan@linuxfoundation.org \
/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