From: Arthur Crepin Leblond <arthur@marmottus.net>
To: netdev-bot+sashiko@kernel.org
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, arnd@arndb.de,
netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [net-next,v6,3/3] w5100: detect carrier state using link status bit and optional interrupt
Date: Thu, 3 Sep 2026 15:08:20 +0200 [thread overview]
Message-ID: <20260903130820.GA21664@marmottus.net> (raw)
In-Reply-To: <178839696443.3394541.11998106403165239318@kernel.org>
On Thu, Sep 03, 2026 at 12:56:04AM +0000, netdev-bot+sashiko@kernel.org wrote:
>> +static int w5500_get_phycfgr_lnk(struct net_device *ndev)
>> +{
>> + struct w5100_priv *priv = netdev_priv(ndev);
>> + int ret = w5100_read(priv, W5500_PHYCFGR);
>> +
>> + if (ret < 0) {
>> + netif_err(priv, link, ndev,
>> + "failed to read link status: %d\n", ret);
>> + return 0;
>> + }
>> +
>> + return ret & PHYCFGR_LNK;
>> +}
>
>[Severity: Medium]
>
>Returning 0 on a failed read makes a bus error indistinguishable from a
>genuine LNK=0 for every caller. For the w5500, w5100_read() ends up in
>w5500_spi_read():
>
>drivers/net/ethernet/wiznet/w5100-spi.c:w5100_spi_read() {
> ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1);
>
> return ret ? ret : data;
>}
>
>so negative returns are reachable (spi_write_then_read() can fail with
>-ENOMEM on its internal buffer, or with any controller error).
>
>If that happens once inside w5100_open(), the else branch latches
>netif_carrier_off(); inside w5100_resume() it just skips netif_carrier_on()
>and the carrier stays off. After that, the only code that ever touches the
>carrier again is w5500_detect_link(), which runs on a LINKLED edge only, so
>if the cable is left plugged in does the interface stay carrier-down for the
>rest of its lifetime with no retry or re-read anywhere?
>
>Would returning the error to the callers, and leaving the carrier state
>unchanged on a failed read, be preferable?
Good point, I can propagate the error and handle it in the caller and
leave the carrier state unchanged.
>One more note on the diagnostic: priv->msg_enable is never initialised in
>w5100_probe() (the priv area comes from the zeroed alloc_etherdev()) and the
>only writer is w5100_set_msglevel(), so netif_msg_link(priv) is false by
>default and this netif_err() prints nothing unless the message level was
>raised beforehand.
I guess it would be up to the userland to decide if they want to have
error messages enabled?
>> @@ -659,6 +705,15 @@ static int w5100_open(struct net_device *ndev)
>> w5100_hw_start(priv);
>> napi_enable(&priv->napi);
>> netif_start_queue(ndev);
>> +
>> + if (priv->link_irq < 0 || w5100_get_link(ndev))
>> + netif_carrier_on(ndev);
>> + else
>> + netif_carrier_off(ndev);
>
>[Severity: Medium]
>
>Since || short-circuits, w5100_get_link() is not called at all when link_irq
>is negative and the carrier is unconditionally turned on. The ethtool
>callback added just above, however, keys on the chip id rather than on
>link_irq:
>
> if (priv->ops->chip_id == W5500)
> return w5500_get_phycfgr_lnk(ndev);
>
Again, good catch! One could setup the w5500 without the link IRQ but
still use the PHCFGR register for the carrier state.
>> +
>> + if (priv->link_irq >= 0)
>> + enable_irq(priv->link_irq);
>
>[Severity: Medium]
>
>The link bit is sampled and the carrier committed before the LINKLED source
>is armed here. The interrupt is requested with IRQF_NO_AUTOEN, so this
>first enable_irq() is what performs irq_startup(); can an edge that arrives
>between the PHYCFGR read (an SPI transfer) and enable_irq() be dropped
>rather than replayed by check_irq_resend()?
>
>w5100_resume() has the same order, right after w5100_hw_reset() /
>w5100_hw_start() where the PHY renegotiates, which looks like a spot where a
>link-up edge is likely to land in the window.
>
>Since w5500_detect_link() also writes the carrier from its own PHYCFGR
>sample, with no lock in common with open/resume, would swapping these two
>statements be sufficient on its own, or does the "sample plus carrier
>update" pair need serializing against the handler (or a re-sample after
>enable_irq())?
>
>If a transition is lost here, the carrier stays stale until the next
>physical link change.
Chances are probably low but I can enable the irq after the get_link
check.
>> @@ -820,14 +902,20 @@ void w5100_remove(struct device *dev)
>> struct net_device *ndev = dev_get_drvdata(dev);
>> struct w5100_priv *priv = netdev_priv(ndev);
>>
>> + if (priv->link_irq >= 0) {
>> + free_irq(priv->link_irq, ndev);
>> + priv->link_irq = -ENODEV;
>> + }
>> +
>> w5100_hw_reset(priv);
>> free_irq(priv->irq, ndev);
>>
>> + unregister_netdev(ndev);
>> +
>
>[Severity: Medium]
>
>Both interrupts are freed and the chip is reset here while the netdev is
>still registered and possibly up, and priv->link_irq = -ENODEV is used as
>the only guard against the other side. Driver unbind
>(device_release_driver() -> w5100_remove()) holds no rtnl, while
>w5100_open() / w5100_stop() run under rtnl, and both read priv->link_irq
>with no lock, barrier or READ_ONCE/WRITE_ONCE.
>
>Can this interleaving happen?
>
>CPU0 (unbind, no rtnl) CPU1 (ip link set dev down, rtnl)
>w5100_remove() w5100_stop()
> if (priv->link_irq >= 0) /* true */
>free_irq(priv->link_irq, ndev);
>priv->link_irq = -ENODEV;
> disable_irq(priv->link_irq);
>
>disable_irq() on a descriptor with no installed action leaves desc->depth
>unbalanced; after a re-bind, request_threaded_irq() with IRQF_NO_AUTOEN plus
>the single enable_irq() in w5100_open() would not bring the depth back to
>zero, so would link detection stay dead until reboot?
>
>The mirror case is w5100_open() calling enable_irq() on a just-freed link
>irq, and running w5100_hw_start() / w5100_get_link() against a chip that
>w5100_hw_reset() has already reset and whose main irq handler has already
>been released by free_irq(priv->irq, ndev). The window covers several
>sleeping SPI transfers plus a synchronize_irq(), so it is not narrow.
>
>Would calling unregister_netdev() first, before free_irq(priv->link_irq),
>free_irq(priv->irq) and w5100_hw_reset(), close this? It closes the device
>under rtnl and guarantees no further ndo_ or ethtool callbacks can start,
>which would also make the priv->link_irq = -ENODEV store unnecessary.
That should work, calling unregister_netdev earlier would disable ndo_ calls.
>> @@ -24,7 +24,11 @@ properties:
>> - wiznet,w5500
>>
>> interrupts:
>> - maxItems: 1
>> + minItems: 1
>> + items:
>> + - description: Main interrupt (INT pin on the device).
>> + - description:
>> + Optional interrupt for link status changes (LINKLED pin on w5500).
>
>[Severity: Low]
>
>This relaxes the count to two entries for all three compatibles, including
>wiznet,w5100 and wiznet,w5200, even though the second item's own description
>says the pin only exists on the w5500, and the driver only resolves index 1
>for that chip:
>
> priv->link_irq = ops->chip_id == W5500 ?
> fwnode_irq_get(dev_fwnode(dev), 1) : -ENODEV;
>
>Should this be expressed with an allOf / if-then block, so that
>
> if:
> properties:
> compatible:
> contains:
> enum:
> - wiznet,w5100
> - wiznet,w5200
>
>keeps interrupts at maxItems: 1, and only wiznet,w5500 allows maxItems: 2?
>As it stands a w5100/w5200 tree with a second interrupt passes
>dt_binding_check while the driver ignores the line.
I think the user should also be able to read the document and know
which hardware they are using? But yes, adding allOf with a condition
would be bullet proof.
>>
>> reg:
>> maxItems: 1
>> @@ -58,7 +62,8 @@ examples:
>> pinctrl-names = "default";
>> pinctrl-0 = <ð1_pins>;
>> interrupt-parent = <&gpio>;
>> - interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
>> + interrupts = <25 IRQ_TYPE_EDGE_FALLING>,
>> + <24 IRQ_TYPE_EDGE_BOTH>;
>
>[Severity: Low]
>
>This isn't an issue introduced by this series, but the example describes the
>main interrupt as IRQ_TYPE_EDGE_FALLING while the driver requests that same
>line with a hard-coded IRQF_TRIGGER_LOW in both paths:
>
> err = request_threaded_irq(priv->irq, NULL, w5100_interrupt,
> IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> dev_name(dev), ndev);
> ...
> err = request_irq(priv->irq, w5100_interrupt,
> IRQF_TRIGGER_LOW, dev_name(dev), ndev);
>
>A non-zero IRQF_TRIGGER_ mask makes __setup_irq() call __irq_set_trigger()
>and reprogram the controller, so the type from the device tree is
>discarded. The same example text came from the deleted
>Documentation/devicetree/bindings/net/wiznet,w5x00.txt. Since the file is
>being touched anyway, is it worth aligning the example with what the driver
>actually programs? The second entry is consistent, IRQ_TYPE_EDGE_BOTH
>matches IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING.
>
>> spi-max-frequency = <30000000>;
>> local-mac-address = [ a0 b0 c0 d0 e0 f0 ];
>> };
>
Right, the DT binding is not aligned with the implementation.
>--
>Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-wiznet-link-gpio-v6-0-abee6c5c966e%40marmottus.net
Thank you!
Arthur
next prev parent reply other threads:[~2026-09-03 13:08 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 12:53 [PATCH net-next v6 0/3] w5100: restore GPIO-based link detection Arthur Crepin Leblond
2026-08-31 12:53 ` [PATCH net-next v6 1/3] dt-bindings: net: wiznet,w5100: convert to DT schema Arthur Crepin Leblond
2026-09-03 0:56 ` [net-next,v6,1/3] " netdev-bot+sashiko
2026-08-31 12:53 ` [PATCH net-next v6 2/3] dt-bindings: net: wiznet,w5100: add link status interrupt Arthur Crepin Leblond
2026-09-01 9:58 ` Krzysztof Kozlowski
2026-09-03 0:56 ` [net-next,v6,2/3] " netdev-bot+sashiko
2026-08-31 12:53 ` [PATCH net-next v6 3/3] w5100: detect carrier state using link status bit and optional interrupt Arthur Crepin Leblond
2026-09-03 0:56 ` [net-next,v6,3/3] " netdev-bot+sashiko
2026-09-03 13:08 ` Arthur Crepin Leblond [this message]
2026-09-03 15:10 ` Arthur Crepin Leblond
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=20260903130820.GA21664@marmottus.net \
--to=arthur@marmottus.net \
--cc=andrew+netdev@lunn.ch \
--cc=arnd@arndb.de \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=robh@kernel.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