Commit 8635c066 authored by Russell King (Oracle)'s avatar Russell King (Oracle) Committed by David S. Miller

net: phy: split locked and unlocked section of phy_state_machine()

Split out the locked and unlocked sections of phy_state_machine() into
two separate functions which can be called inside the phydev lock and
outside the phydev lock as appropriate, thus allowing us to combine
the locked regions in the caller of phy_state_machine() with the
locked region inside phy_state_machine().

This avoids unnecessarily dropping the phydev lock which may allow
races to occur.
Tested-by: default avatarJijie Shao <shaojijie@huawei.com>
Signed-off-by: default avatarRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Reviewed-by: default avatarFlorian Fainelli <florian.fainelli@broadcom.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent c398ef41
...@@ -1353,33 +1353,27 @@ void phy_free_interrupt(struct phy_device *phydev) ...@@ -1353,33 +1353,27 @@ void phy_free_interrupt(struct phy_device *phydev)
} }
EXPORT_SYMBOL(phy_free_interrupt); EXPORT_SYMBOL(phy_free_interrupt);
/** enum phy_state_work {
* phy_state_machine - Handle the state machine PHY_STATE_WORK_NONE,
* @work: work_struct that describes the work to be done PHY_STATE_WORK_ANEG,
*/ PHY_STATE_WORK_SUSPEND,
void phy_state_machine(struct work_struct *work) };
static enum phy_state_work _phy_state_machine(struct phy_device *phydev)
{ {
struct delayed_work *dwork = to_delayed_work(work); enum phy_state_work state_work = PHY_STATE_WORK_NONE;
struct phy_device *phydev =
container_of(dwork, struct phy_device, state_queue);
struct net_device *dev = phydev->attached_dev; struct net_device *dev = phydev->attached_dev;
bool needs_aneg = false, do_suspend = false; enum phy_state old_state = phydev->state;
enum phy_state old_state;
const void *func = NULL; const void *func = NULL;
bool finished = false; bool finished = false;
int err = 0; int err = 0;
mutex_lock(&phydev->lock);
old_state = phydev->state;
switch (phydev->state) { switch (phydev->state) {
case PHY_DOWN: case PHY_DOWN:
case PHY_READY: case PHY_READY:
break; break;
case PHY_UP: case PHY_UP:
needs_aneg = true; state_work = PHY_STATE_WORK_ANEG;
break; break;
case PHY_NOLINK: case PHY_NOLINK:
case PHY_RUNNING: case PHY_RUNNING:
...@@ -1391,7 +1385,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1391,7 +1385,7 @@ void phy_state_machine(struct work_struct *work)
if (err) { if (err) {
phy_abort_cable_test(phydev); phy_abort_cable_test(phydev);
netif_testing_off(dev); netif_testing_off(dev);
needs_aneg = true; state_work = PHY_STATE_WORK_ANEG;
phydev->state = PHY_UP; phydev->state = PHY_UP;
break; break;
} }
...@@ -1399,7 +1393,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1399,7 +1393,7 @@ void phy_state_machine(struct work_struct *work)
if (finished) { if (finished) {
ethnl_cable_test_finished(phydev); ethnl_cable_test_finished(phydev);
netif_testing_off(dev); netif_testing_off(dev);
needs_aneg = true; state_work = PHY_STATE_WORK_ANEG;
phydev->state = PHY_UP; phydev->state = PHY_UP;
} }
break; break;
...@@ -1409,19 +1403,17 @@ void phy_state_machine(struct work_struct *work) ...@@ -1409,19 +1403,17 @@ void phy_state_machine(struct work_struct *work)
phydev->link = 0; phydev->link = 0;
phy_link_down(phydev); phy_link_down(phydev);
} }
do_suspend = true; state_work = PHY_STATE_WORK_SUSPEND;
break; break;
} }
if (needs_aneg) { if (state_work == PHY_STATE_WORK_ANEG) {
err = _phy_start_aneg(phydev); err = _phy_start_aneg(phydev);
func = &_phy_start_aneg; func = &_phy_start_aneg;
} }
if (err == -ENODEV) { if (err == -ENODEV)
mutex_unlock(&phydev->lock); return state_work;
return;
}
if (err < 0) if (err < 0)
phy_error_precise(phydev, func, err); phy_error_precise(phydev, func, err);
...@@ -1438,12 +1430,36 @@ void phy_state_machine(struct work_struct *work) ...@@ -1438,12 +1430,36 @@ void phy_state_machine(struct work_struct *work)
*/ */
if (phy_polling_mode(phydev) && phy_is_started(phydev)) if (phy_polling_mode(phydev) && phy_is_started(phydev))
phy_queue_state_machine(phydev, PHY_STATE_TIME); phy_queue_state_machine(phydev, PHY_STATE_TIME);
mutex_unlock(&phydev->lock);
if (do_suspend) return state_work;
}
/* unlocked part of the PHY state machine */
static void _phy_state_machine_post_work(struct phy_device *phydev,
enum phy_state_work state_work)
{
if (state_work == PHY_STATE_WORK_SUSPEND)
phy_suspend(phydev); phy_suspend(phydev);
} }
/**
* phy_state_machine - Handle the state machine
* @work: work_struct that describes the work to be done
*/
void phy_state_machine(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct phy_device *phydev =
container_of(dwork, struct phy_device, state_queue);
enum phy_state_work state_work;
mutex_lock(&phydev->lock);
state_work = _phy_state_machine(phydev);
mutex_unlock(&phydev->lock);
_phy_state_machine_post_work(phydev, state_work);
}
/** /**
* phy_stop - Bring down the PHY link, and stop checking the status * phy_stop - Bring down the PHY link, and stop checking the status
* @phydev: target phy_device struct * @phydev: target phy_device struct
......
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