Commit ae6a762d authored by Alexander Kochetkov's avatar Alexander Kochetkov Committed by Greg Kroah-Hartman

net: phy: fix auto-negotiation stall due to unavailable interrupt

[ Upstream commit f555f34f ]

The Ethernet link on an interrupt driven PHY was not coming up if the Ethernet
cable was plugged before the Ethernet interface was brought up.

The patch trigger PHY state machine to update link state if PHY was requested to
do auto-negotiation and auto-negotiation complete flag already set.

During power-up cycle the PHY do auto-negotiation, generate interrupt and set
auto-negotiation complete flag. Interrupt is handled by PHY state machine but
doesn't update link state because PHY is in PHY_READY state. After some time
MAC bring up, start and request PHY to do auto-negotiation. If there are no new
settings to advertise genphy_config_aneg() doesn't start PHY auto-negotiation.
PHY continue to stay in auto-negotiation complete state and doesn't fire
interrupt. At the same time PHY state machine expect that PHY started
auto-negotiation and is waiting for interrupt from PHY and it won't get it.

Fixes: 321beec5 ("net: phy: Use interrupts when available in NOLINK state")
Signed-off-by: default avatarAlexander Kochetkov <al.kochet@gmail.com>
Cc: stable <stable@vger.kernel.org> # v4.9+
Tested-by: default avatarRoger Quadros <rogerq@ti.com>
Tested-by: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 62817c31
...@@ -552,16 +552,18 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) ...@@ -552,16 +552,18 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
EXPORT_SYMBOL(phy_mii_ioctl); EXPORT_SYMBOL(phy_mii_ioctl);
/** /**
* phy_start_aneg - start auto-negotiation for this PHY device * phy_start_aneg_priv - start auto-negotiation for this PHY device
* @phydev: the phy_device struct * @phydev: the phy_device struct
* @sync: indicate whether we should wait for the workqueue cancelation
* *
* Description: Sanitizes the settings (if we're not autonegotiating * Description: Sanitizes the settings (if we're not autonegotiating
* them), and then calls the driver's config_aneg function. * them), and then calls the driver's config_aneg function.
* If the PHYCONTROL Layer is operating, we change the state to * If the PHYCONTROL Layer is operating, we change the state to
* reflect the beginning of Auto-negotiation or forcing. * reflect the beginning of Auto-negotiation or forcing.
*/ */
int phy_start_aneg(struct phy_device *phydev) static int phy_start_aneg_priv(struct phy_device *phydev, bool sync)
{ {
bool trigger = 0;
int err; int err;
mutex_lock(&phydev->lock); mutex_lock(&phydev->lock);
...@@ -586,10 +588,40 @@ int phy_start_aneg(struct phy_device *phydev) ...@@ -586,10 +588,40 @@ int phy_start_aneg(struct phy_device *phydev)
} }
} }
/* Re-schedule a PHY state machine to check PHY status because
* negotiation may already be done and aneg interrupt may not be
* generated.
*/
if (phy_interrupt_is_valid(phydev) && (phydev->state == PHY_AN)) {
err = phy_aneg_done(phydev);
if (err > 0) {
trigger = true;
err = 0;
}
}
out_unlock: out_unlock:
mutex_unlock(&phydev->lock); mutex_unlock(&phydev->lock);
if (trigger)
phy_trigger_machine(phydev, sync);
return err; return err;
} }
/**
* phy_start_aneg - start auto-negotiation for this PHY device
* @phydev: the phy_device struct
*
* Description: Sanitizes the settings (if we're not autonegotiating
* them), and then calls the driver's config_aneg function.
* If the PHYCONTROL Layer is operating, we change the state to
* reflect the beginning of Auto-negotiation or forcing.
*/
int phy_start_aneg(struct phy_device *phydev)
{
return phy_start_aneg_priv(phydev, true);
}
EXPORT_SYMBOL(phy_start_aneg); EXPORT_SYMBOL(phy_start_aneg);
/** /**
...@@ -617,7 +649,7 @@ void phy_start_machine(struct phy_device *phydev) ...@@ -617,7 +649,7 @@ void phy_start_machine(struct phy_device *phydev)
* state machine runs. * state machine runs.
*/ */
static void phy_trigger_machine(struct phy_device *phydev, bool sync) void phy_trigger_machine(struct phy_device *phydev, bool sync)
{ {
if (sync) if (sync)
cancel_delayed_work_sync(&phydev->state_queue); cancel_delayed_work_sync(&phydev->state_queue);
...@@ -1100,7 +1132,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1100,7 +1132,7 @@ void phy_state_machine(struct work_struct *work)
mutex_unlock(&phydev->lock); mutex_unlock(&phydev->lock);
if (needs_aneg) if (needs_aneg)
err = phy_start_aneg(phydev); err = phy_start_aneg_priv(phydev, false);
else if (do_suspend) else if (do_suspend)
phy_suspend(phydev); phy_suspend(phydev);
......
...@@ -806,6 +806,7 @@ void phy_change(struct work_struct *work); ...@@ -806,6 +806,7 @@ void phy_change(struct work_struct *work);
void phy_mac_interrupt(struct phy_device *phydev, int new_link); void phy_mac_interrupt(struct phy_device *phydev, int new_link);
void phy_start_machine(struct phy_device *phydev); void phy_start_machine(struct phy_device *phydev);
void phy_stop_machine(struct phy_device *phydev); void phy_stop_machine(struct phy_device *phydev);
void phy_trigger_machine(struct phy_device *phydev, bool sync);
int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd); int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd);
int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd); int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd);
int phy_ethtool_ksettings_get(struct phy_device *phydev, int phy_ethtool_ksettings_get(struct phy_device *phydev,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment