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)
PHY_STATE_STR(READY)
PHY_STATE_STR(PENDING)
PHY_STATE_STR(UP)
PHY_STATE_STR(AN)
PHY_STATE_STR(RUNNING)
PHY_STATE_STR(NOLINK)
PHY_STATE_STR(FORCING)
......@@ -62,6 +61,17 @@ static const char *phy_state_to_str(enum phy_state st)
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
......@@ -493,6 +503,34 @@ static int phy_config_aneg(struct phy_device *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
* @phydev: the phy_device struct
......@@ -504,7 +542,6 @@ static int phy_config_aneg(struct phy_device *phydev)
*/
int phy_start_aneg(struct phy_device *phydev)
{
bool trigger = 0;
int err;
if (!phydev->drv)
......@@ -524,32 +561,16 @@ int phy_start_aneg(struct phy_device *phydev)
if (phydev->state != PHY_HALTED) {
if (AUTONEG_ENABLE == phydev->autoneg) {
phydev->state = PHY_AN;
phydev->link_timeout = PHY_AN_TIMEOUT;
err = phy_check_link_status(phydev);
} else {
phydev->state = PHY_FORCING;
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:
mutex_unlock(&phydev->lock);
if (trigger)
phy_trigger_machine(phydev);
return err;
}
EXPORT_SYMBOL(phy_start_aneg);
......@@ -893,18 +914,6 @@ void phy_start(struct phy_device *phydev)
}
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
* @work: work_struct that describes the work to be done
......@@ -934,56 +943,15 @@ void phy_state_machine(struct work_struct *work)
case PHY_UP:
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;
case PHY_NOLINK:
case PHY_RUNNING:
if (!phy_polling_mode(phydev))
break;
err = phy_read_status(phydev);
if (err)
break;
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);
}
/* fall through */
case PHY_CHANGELINK:
case PHY_RESUMING:
err = phy_check_link_status(phydev);
break;
case PHY_FORCING:
err = genphy_update_link(phydev);
......@@ -999,32 +967,6 @@ void phy_state_machine(struct work_struct *work)
phy_link_down(phydev, false);
}
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:
if (phydev->link) {
phydev->link = 0;
......@@ -1032,30 +974,6 @@ void phy_state_machine(struct work_struct *work)
do_suspend = true;
}
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);
......
......@@ -178,7 +178,6 @@ static inline const char *phy_modes(phy_interface_t interface)
#define PHY_INIT_TIMEOUT 100000
#define PHY_STATE_TIME 1
#define PHY_FORCE_TIMEOUT 10
#define PHY_AN_TIMEOUT 10
#define PHY_MAX_ADDR 32
......@@ -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.
* Interrupts should be started here.
* - timer moves to AN
*
* 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.
* - timer moves to NOLINK or RUNNING
*
* NOLINK: PHY is up, but not currently plugged in.
* - If the timer notes that the link comes back, we move to RUNNING
* - config_aneg moves to AN
* - phy_stop moves to HALTED
*
* FORCING: PHY is being configured with forced settings
......@@ -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,
* which makes it every other second)
* - irq will set CHANGELINK
* - config_aneg will set AN
* - phy_stop moves to HALTED
*
* CHANGELINK: PHY experienced a change in link state
......@@ -353,7 +337,6 @@ enum phy_state {
PHY_READY,
PHY_PENDING,
PHY_UP,
PHY_AN,
PHY_RUNNING,
PHY_NOLINK,
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