Commit 80b6265c authored by David S. Miller's avatar David S. Miller

Merge branch 'net-phy-improve-and-simplify-phylib-state-machine'

Heiner Kallweit says:

====================
net: phy: improve and simplify phylib state machine

This patch series is based on two axioms:

- During autoneg a PHY always reports the link being down

- Info in clause 22/45 registers doesn't allow to differentiate between
  these two states:
  1. Link is physically down
  2. A link partner is connected and PHY is autonegotiating
  In both cases "link up" and "aneg finished" bits aren't set.
  One consequence is that having separate states PHY_NOLINK and PHY_AN
  isn't needed.

By using these two axioms the state machine can be significantly
simplified.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5867b330 c8e977ba
...@@ -50,7 +50,6 @@ static const char *phy_state_to_str(enum phy_state st) ...@@ -50,7 +50,6 @@ static const char *phy_state_to_str(enum phy_state st)
PHY_STATE_STR(READY) PHY_STATE_STR(READY)
PHY_STATE_STR(PENDING) PHY_STATE_STR(PENDING)
PHY_STATE_STR(UP) PHY_STATE_STR(UP)
PHY_STATE_STR(AN)
PHY_STATE_STR(RUNNING) PHY_STATE_STR(RUNNING)
PHY_STATE_STR(NOLINK) PHY_STATE_STR(NOLINK)
PHY_STATE_STR(FORCING) PHY_STATE_STR(FORCING)
...@@ -62,6 +61,17 @@ static const char *phy_state_to_str(enum phy_state st) ...@@ -62,6 +61,17 @@ static const char *phy_state_to_str(enum phy_state st)
return NULL; return NULL;
} }
static void phy_link_up(struct phy_device *phydev)
{
phydev->phy_link_change(phydev, true, true);
phy_led_trigger_change_speed(phydev);
}
static void phy_link_down(struct phy_device *phydev, bool do_carrier)
{
phydev->phy_link_change(phydev, false, do_carrier);
phy_led_trigger_change_speed(phydev);
}
/** /**
* phy_print_status - Convenience function to print out the current phy status * phy_print_status - Convenience function to print out the current phy status
...@@ -493,6 +503,34 @@ static int phy_config_aneg(struct phy_device *phydev) ...@@ -493,6 +503,34 @@ static int phy_config_aneg(struct phy_device *phydev)
return genphy_config_aneg(phydev); return genphy_config_aneg(phydev);
} }
/**
* phy_check_link_status - check link status and set state accordingly
* @phydev: the phy_device struct
*
* Description: Check for link and whether autoneg was triggered / is running
* and set state accordingly
*/
static int phy_check_link_status(struct phy_device *phydev)
{
int err;
WARN_ON(!mutex_is_locked(&phydev->lock));
err = phy_read_status(phydev);
if (err)
return err;
if (phydev->link && phydev->state != PHY_RUNNING) {
phydev->state = PHY_RUNNING;
phy_link_up(phydev);
} else if (!phydev->link && phydev->state != PHY_NOLINK) {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, true);
}
return 0;
}
/** /**
* phy_start_aneg - start auto-negotiation for this PHY device * phy_start_aneg - start auto-negotiation for this PHY device
* @phydev: the phy_device struct * @phydev: the phy_device struct
...@@ -504,7 +542,6 @@ static int phy_config_aneg(struct phy_device *phydev) ...@@ -504,7 +542,6 @@ static int phy_config_aneg(struct phy_device *phydev)
*/ */
int phy_start_aneg(struct phy_device *phydev) int phy_start_aneg(struct phy_device *phydev)
{ {
bool trigger = 0;
int err; int err;
if (!phydev->drv) if (!phydev->drv)
...@@ -524,32 +561,16 @@ int phy_start_aneg(struct phy_device *phydev) ...@@ -524,32 +561,16 @@ int phy_start_aneg(struct phy_device *phydev)
if (phydev->state != PHY_HALTED) { if (phydev->state != PHY_HALTED) {
if (AUTONEG_ENABLE == phydev->autoneg) { if (AUTONEG_ENABLE == phydev->autoneg) {
phydev->state = PHY_AN; err = phy_check_link_status(phydev);
phydev->link_timeout = PHY_AN_TIMEOUT;
} else { } else {
phydev->state = PHY_FORCING; phydev->state = PHY_FORCING;
phydev->link_timeout = PHY_FORCE_TIMEOUT; phydev->link_timeout = PHY_FORCE_TIMEOUT;
} }
} }
/* 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_polling_mode(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);
return err; return err;
} }
EXPORT_SYMBOL(phy_start_aneg); EXPORT_SYMBOL(phy_start_aneg);
...@@ -893,18 +914,6 @@ void phy_start(struct phy_device *phydev) ...@@ -893,18 +914,6 @@ void phy_start(struct phy_device *phydev)
} }
EXPORT_SYMBOL(phy_start); EXPORT_SYMBOL(phy_start);
static void phy_link_up(struct phy_device *phydev)
{
phydev->phy_link_change(phydev, true, true);
phy_led_trigger_change_speed(phydev);
}
static void phy_link_down(struct phy_device *phydev, bool do_carrier)
{
phydev->phy_link_change(phydev, false, do_carrier);
phy_led_trigger_change_speed(phydev);
}
/** /**
* phy_state_machine - Handle the state machine * phy_state_machine - Handle the state machine
* @work: work_struct that describes the work to be done * @work: work_struct that describes the work to be done
...@@ -934,56 +943,15 @@ void phy_state_machine(struct work_struct *work) ...@@ -934,56 +943,15 @@ void phy_state_machine(struct work_struct *work)
case PHY_UP: case PHY_UP:
needs_aneg = true; needs_aneg = true;
phydev->link_timeout = PHY_AN_TIMEOUT;
break;
case PHY_AN:
err = phy_read_status(phydev);
if (err < 0)
break;
/* If the link is down, give up on negotiation for now */
if (!phydev->link) {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, true);
break;
}
/* Check if negotiation is done. Break if there's an error */
err = phy_aneg_done(phydev);
if (err < 0)
break;
/* If AN is done, we're running */
if (err > 0) {
phydev->state = PHY_RUNNING;
phy_link_up(phydev);
} else if (0 == phydev->link_timeout--)
needs_aneg = true;
break; break;
case PHY_NOLINK: case PHY_NOLINK:
case PHY_RUNNING:
if (!phy_polling_mode(phydev)) if (!phy_polling_mode(phydev))
break; break;
/* fall through */
err = phy_read_status(phydev); case PHY_CHANGELINK:
if (err) case PHY_RESUMING:
break; err = phy_check_link_status(phydev);
if (phydev->link) {
if (AUTONEG_ENABLE == phydev->autoneg) {
err = phy_aneg_done(phydev);
if (err < 0)
break;
if (!err) {
phydev->state = PHY_AN;
phydev->link_timeout = PHY_AN_TIMEOUT;
break;
}
}
phydev->state = PHY_RUNNING;
phy_link_up(phydev);
}
break; break;
case PHY_FORCING: case PHY_FORCING:
err = genphy_update_link(phydev); err = genphy_update_link(phydev);
...@@ -999,32 +967,6 @@ void phy_state_machine(struct work_struct *work) ...@@ -999,32 +967,6 @@ void phy_state_machine(struct work_struct *work)
phy_link_down(phydev, false); phy_link_down(phydev, false);
} }
break; break;
case PHY_RUNNING:
if (!phy_polling_mode(phydev))
break;
err = phy_read_status(phydev);
if (err)
break;
if (!phydev->link) {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, true);
}
break;
case PHY_CHANGELINK:
err = phy_read_status(phydev);
if (err)
break;
if (phydev->link) {
phydev->state = PHY_RUNNING;
phy_link_up(phydev);
} else {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, true);
}
break;
case PHY_HALTED: case PHY_HALTED:
if (phydev->link) { if (phydev->link) {
phydev->link = 0; phydev->link = 0;
...@@ -1032,30 +974,6 @@ void phy_state_machine(struct work_struct *work) ...@@ -1032,30 +974,6 @@ void phy_state_machine(struct work_struct *work)
do_suspend = true; do_suspend = true;
} }
break; break;
case PHY_RESUMING:
if (AUTONEG_ENABLE == phydev->autoneg) {
err = phy_aneg_done(phydev);
if (err < 0) {
break;
} else if (!err) {
phydev->state = PHY_AN;
phydev->link_timeout = PHY_AN_TIMEOUT;
break;
}
}
err = phy_read_status(phydev);
if (err)
break;
if (phydev->link) {
phydev->state = PHY_RUNNING;
phy_link_up(phydev);
} else {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, false);
}
break;
} }
mutex_unlock(&phydev->lock); mutex_unlock(&phydev->lock);
......
...@@ -178,7 +178,6 @@ static inline const char *phy_modes(phy_interface_t interface) ...@@ -178,7 +178,6 @@ static inline const char *phy_modes(phy_interface_t interface)
#define PHY_INIT_TIMEOUT 100000 #define PHY_INIT_TIMEOUT 100000
#define PHY_STATE_TIME 1 #define PHY_STATE_TIME 1
#define PHY_FORCE_TIMEOUT 10 #define PHY_FORCE_TIMEOUT 10
#define PHY_AN_TIMEOUT 10
#define PHY_MAX_ADDR 32 #define PHY_MAX_ADDR 32
...@@ -297,24 +296,10 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr); ...@@ -297,24 +296,10 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr);
* *
* UP: The PHY and attached device are ready to do work. * UP: The PHY and attached device are ready to do work.
* Interrupts should be started here. * Interrupts should be started here.
* - timer moves to AN * - timer moves to NOLINK or RUNNING
*
* AN: The PHY is currently negotiating the link state. Link is
* therefore down for now. phy_timer will set this state when it
* detects the state is UP. config_aneg will set this state
* whenever called with phydev->autoneg set to AUTONEG_ENABLE.
* - If autonegotiation finishes, but there's no link, it sets
* the state to NOLINK.
* - If aneg finishes with link, it sets the state to RUNNING,
* and calls adjust_link
* - If autonegotiation did not finish after an arbitrary amount
* of time, autonegotiation should be tried again if the PHY
* supports "magic" autonegotiation (back to AN)
* - If it didn't finish, and no magic_aneg, move to FORCING.
* *
* NOLINK: PHY is up, but not currently plugged in. * NOLINK: PHY is up, but not currently plugged in.
* - If the timer notes that the link comes back, we move to RUNNING * - If the timer notes that the link comes back, we move to RUNNING
* - config_aneg moves to AN
* - phy_stop moves to HALTED * - phy_stop moves to HALTED
* *
* FORCING: PHY is being configured with forced settings * FORCING: PHY is being configured with forced settings
...@@ -329,7 +314,6 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr); ...@@ -329,7 +314,6 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr);
* link state is polled every other cycle of this state machine, * link state is polled every other cycle of this state machine,
* which makes it every other second) * which makes it every other second)
* - irq will set CHANGELINK * - irq will set CHANGELINK
* - config_aneg will set AN
* - phy_stop moves to HALTED * - phy_stop moves to HALTED
* *
* CHANGELINK: PHY experienced a change in link state * CHANGELINK: PHY experienced a change in link state
...@@ -353,7 +337,6 @@ enum phy_state { ...@@ -353,7 +337,6 @@ enum phy_state {
PHY_READY, PHY_READY,
PHY_PENDING, PHY_PENDING,
PHY_UP, PHY_UP,
PHY_AN,
PHY_RUNNING, PHY_RUNNING,
PHY_NOLINK, PHY_NOLINK,
PHY_FORCING, PHY_FORCING,
......
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