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

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
Loading
Loading
Loading
Loading
+45 −127
Original line number Original line Diff line number Diff line
@@ -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)
	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)
	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)
 */
 */
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)


	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)
}
}
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)
	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)
			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)
			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);
+1 −18
Original line number Original line Diff line number Diff line
@@ -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);
 *
 *
 * 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);
 *   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 {
	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,