From: Marek Vasut <marex@denx.de> Date: 2021-01-05 16:12:44
In case the PHY transitions to PHY_HALTED state in phy_stop(), the
link_change_notify callback is not triggered. That's because the
phydev->state = PHY_HALTED in phy_stop() is assigned first, and
phy_state_machine() is called afterward. For phy_state_machine(),
no state transition happens, because old_state = PHY_HALTED and
phy_dev->state = PHY_HALTED.
Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: David S. Miller <davem@davemloft.net>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/net/phy/phy.c | 10 ++++++++++
1 file changed, 10 insertions(+)
@@ -1004,6 +1004,7 @@ EXPORT_SYMBOL(phy_free_interrupt);voidphy_stop(structphy_device*phydev){structnet_device*dev=phydev->attached_dev;+enumphy_stateold_state;if(!phy_is_started(phydev)&&phydev->state!=PHY_DOWN){WARN(1,"called from state %s\n",
In case the PHY transitions to PHY_HALTED state in phy_stop(), the
link_change_notify callback is not triggered. That's because the
phydev->state = PHY_HALTED in phy_stop() is assigned first, and
phy_state_machine() is called afterward. For phy_state_machine(),
no state transition happens, because old_state = PHY_HALTED and
phy_dev->state = PHY_HALTED.
There are a few formal issues with this patch:
- It misses a net/net-next annotation. If it's meant to be a fix,
then the Fixes tag is missing. I just checked the existing
link_change_notify handlers and nobody is interested in state
transitions to PHY_HALTED. Therefore I think it's more of an
improvement. However AFAICS net-next is still closed.
- The maintainers should be in To: and the list(s) on cc.
- Seems that Russell and Jakub are missing as maintainers.
quoted hunk
Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: David S. Miller <davem@davemloft.net>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/net/phy/phy.c | 10 ++++++++++
1 file changed, 10 insertions(+)
@@ -1004,6 +1004,7 @@ EXPORT_SYMBOL(phy_free_interrupt);voidphy_stop(structphy_device*phydev){structnet_device*dev=phydev->attached_dev;+enumphy_stateold_state;if(!phy_is_started(phydev)&&phydev->state!=PHY_DOWN){WARN(1,"called from state %s\n",
This check shouldn't be needed because it shouldn't happen that
phy_stop() is called from status PHY_HALTED. In this case the
WARN() a few lines above would have fired already.
+ phydev_err(phydev, "PHY state change %s -> %s\n",
+ phy_state_to_str(old_state),
+ phy_state_to_str(phydev->state));
+ if (phydev->drv && phydev->drv->link_change_notify)
+ phydev->drv->link_change_notify(phydev);
Instead of duplicating this code it could be factored out into
a small helper that is used by phy_stop() and phy_state_machine().
This check shouldn't be needed because it shouldn't happen that
phy_stop() is called from status PHY_HALTED. In this case the
WARN() a few lines above would have fired already.
That is incorrect. If an error happens with the phy, phy_error() will
be called, which sets phydev->state = PHY_HALTED. If you then
subsequently take the interface down, phy_stop() will be called, but
phydev->state will be set to PHY_HALTED.
This is a long standing bug since you changed the code, and I think is
something I've reported previously, since I've definitely encountered
it.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 40Mbps down 10Mbps up. Decent connectivity at last!
This check shouldn't be needed because it shouldn't happen that
phy_stop() is called from status PHY_HALTED. In this case the
WARN() a few lines above would have fired already.
That is incorrect. If an error happens with the phy, phy_error() will
be called, which sets phydev->state = PHY_HALTED. If you then
subsequently take the interface down, phy_stop() will be called, but
phydev->state will be set to PHY_HALTED.
OK, so we have to fix the way phy_error() works. Still the check isn't
needed here. So far nobody is interested in transitions to PHY_HALTED,
so nothing is broken.
This is a long standing bug since you changed the code, and I think is
something I've reported previously, since I've definitely encountered
it.
IIRC there was a start of a discussion whether phy_error() is useful
at all regarding how it works as of today. A single failed MDIO access
(e.g. timeout) stops the state machine, when e.g. one missed PHY status
update in polling mode doesn't really cause any harm.
If we want to keep the functionality of phy_error(), then I'd say
a separate error phy state (e.g. PHY_ERROR) would make sense, as it
would make clear that the network was stopped due to an error.
In case the PHY transitions to PHY_HALTED state in phy_stop(), the
link_change_notify callback is not triggered. That's because the
phydev->state = PHY_HALTED in phy_stop() is assigned first, and
phy_state_machine() is called afterward. For phy_state_machine(),
no state transition happens, because old_state = PHY_HALTED and
phy_dev->state = PHY_HALTED.
Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: David S. Miller <davem@davemloft.net>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/net/phy/phy.c | 10 ++++++++++
1 file changed, 10 insertions(+)
@@ -1004,6 +1004,7 @@ EXPORT_SYMBOL(phy_free_interrupt);voidphy_stop(structphy_device*phydev){structnet_device*dev=phydev->attached_dev;+enumphy_stateold_state;if(!phy_is_started(phydev)&&phydev->state!=PHY_DOWN){WARN(1,"called from state %s\n",
Following is RFC as an additional idea. When requesting a new state
from outside the state machine, we could simply provide the old
state to the state machine in a new phy_device member.
Then we shouldn't have to touch phy_stop(), and maybe phy_error().
And it looks cleaner to me than duplicating code from the state
machine to functions like phy_stop().
---
drivers/net/phy/phy.c | 10 +++++++++-
drivers/net/phy/phy_device.c | 1 +
include/linux/phy.h | 3 +++
3 files changed, 13 insertions(+), 1 deletion(-)
@@ -1086,7 +1088,13 @@ void phy_state_machine(struct work_struct *work)mutex_lock(&phydev->lock);-old_state=phydev->state;+/* set if a new state is requested from outside the state machine */+if(phydev->old_state!=PHY_INVALID){+old_state=phydev->old_state;+phydev->old_state=PHY_INVALID;+}else{+old_state=phydev->state;+}switch(phydev->state){casePHY_DOWN:
@@ -566,6 +567,8 @@ struct phy_device {unsignedinterrupts:1;enumphy_statestate;+/* if a new state is requested from outside the state machine */+enumphy_stateold_state;u32dev_flags;