All of lore.kernel.org
 help / color / mirror / Atom feed
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 = <&eth1_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

  reply	other threads:[~2026-09-03 13:08 UTC|newest]

Thread overview: 11+ 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-01 12:54   ` sashiko-bot
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 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.