diff options
Diffstat (limited to 'drivers/net/phy/phy.c')
-rw-r--r-- | drivers/net/phy/phy.c | 54 |
1 files changed, 34 insertions, 20 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index cb230f44d6fc..9bc11773705b 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -7,7 +7,7 @@ * Author: Andy Fleming * * Copyright (c) 2004 Freescale Semiconductor, Inc. - * Copyright (c) 2006 Maciej W. Rozycki + * Copyright (c) 2006, 2007 Maciej W. Rozycki * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -35,6 +35,7 @@ #include <linux/timer.h> #include <linux/workqueue.h> +#include <asm/atomic.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> @@ -204,7 +205,7 @@ static const struct phy_setting settings[] = { }, }; -#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting)) +#define MAX_NUM_SETTINGS ARRAY_SIZE(settings) /** * phy_find_setting - find a PHY settings array entry that matches speed & duplex @@ -424,7 +425,7 @@ int phy_start_aneg(struct phy_device *phydev) { int err; - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); if (AUTONEG_DISABLE == phydev->autoneg) phy_sanitize_settings(phydev); @@ -445,7 +446,7 @@ int phy_start_aneg(struct phy_device *phydev) } out_unlock: - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); return err; } EXPORT_SYMBOL(phy_start_aneg); @@ -490,10 +491,10 @@ void phy_stop_machine(struct phy_device *phydev) { del_timer_sync(&phydev->phy_timer); - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); if (phydev->state > PHY_UP) phydev->state = PHY_UP; - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); phydev->adjust_state = NULL; } @@ -537,9 +538,9 @@ static void phy_force_reduction(struct phy_device *phydev) */ void phy_error(struct phy_device *phydev) { - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); phydev->state = PHY_HALTED; - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); } /** @@ -562,6 +563,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat) * queue will write the PHY to disable and clear the * interrupt, and then reenable the irq line. */ disable_irq_nosync(irq); + atomic_inc(&phydev->irq_disable); schedule_work(&phydev->phy_queue); @@ -632,6 +634,7 @@ int phy_start_interrupts(struct phy_device *phydev) INIT_WORK(&phydev->phy_queue, phy_change); + atomic_set(&phydev->irq_disable, 0); if (request_irq(phydev->irq, phy_interrupt, IRQF_SHARED, "phy_interrupt", @@ -662,13 +665,22 @@ int phy_stop_interrupts(struct phy_device *phydev) if (err) phy_error(phydev); + free_irq(phydev->irq, phydev); + /* - * Finish any pending work; we might have been scheduled to be called - * from keventd ourselves, but cancel_work_sync() handles that. + * Cannot call flush_scheduled_work() here as desired because + * of rtnl_lock(), but we do not really care about what would + * be done, except from enable_irq(), so cancel any work + * possibly pending and take care of the matter below. */ cancel_work_sync(&phydev->phy_queue); - - free_irq(phydev->irq, phydev); + /* + * If work indeed has been cancelled, disable_irq() will have + * been left unbalanced from phy_interrupt() and enable_irq() + * has to be called so that other devices on the line work. + */ + while (atomic_dec_return(&phydev->irq_disable) >= 0) + enable_irq(phydev->irq); return err; } @@ -690,11 +702,12 @@ static void phy_change(struct work_struct *work) if (err) goto phy_err; - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) phydev->state = PHY_CHANGELINK; - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); + atomic_dec(&phydev->irq_disable); enable_irq(phydev->irq); /* Reenable interrupts */ @@ -708,6 +721,7 @@ static void phy_change(struct work_struct *work) irq_enable_err: disable_irq(phydev->irq); + atomic_inc(&phydev->irq_disable); phy_err: phy_error(phydev); } @@ -718,13 +732,11 @@ phy_err: */ void phy_stop(struct phy_device *phydev) { - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); if (PHY_HALTED == phydev->state) goto out_unlock; - phydev->state = PHY_HALTED; - if (phydev->irq != PHY_POLL) { /* Disable PHY Interrupts */ phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); @@ -733,8 +745,10 @@ void phy_stop(struct phy_device *phydev) phy_clear_interrupt(phydev); } + phydev->state = PHY_HALTED; + out_unlock: - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); /* * Cannot call flush_scheduled_work() here as desired because @@ -782,7 +796,7 @@ static void phy_timer(unsigned long data) int needs_aneg = 0; int err = 0; - spin_lock(&phydev->lock); + spin_lock_bh(&phydev->lock); if (phydev->adjust_state) phydev->adjust_state(phydev->attached_dev); @@ -948,7 +962,7 @@ static void phy_timer(unsigned long data) break; } - spin_unlock(&phydev->lock); + spin_unlock_bh(&phydev->lock); if (needs_aneg) err = phy_start_aneg(phydev); |