net: phy: Tell caller result of phy_change()
authorBrad Mouring <brad.mouring@ni.com>
Thu, 8 Mar 2018 22:23:03 +0000 (16:23 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 31 Mar 2018 16:10:39 +0000 (18:10 +0200)
[ Upstream commit a2c054a896b8ac794ddcfc7c92e2dc7ec4ed4ed5 ]

In 664fcf123a30e (net: phy: Threaded interrupts allow some simplification)
the phy_interrupt system was changed to use a traditional threaded
interrupt scheme instead of a workqueue approach.

With this change, the phy status check moved into phy_change, which
did not report back to the caller whether or not the interrupt was
handled. This means that, in the case of a shared phy interrupt,
only the first phydev's interrupt registers are checked (since
phy_interrupt() would always return IRQ_HANDLED). This leads to
interrupt storms when it is a secondary device that's actually the
interrupt source.

Signed-off-by: Brad Mouring <brad.mouring@ni.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/net/phy/phy.c
include/linux/phy.h

index 39de77a..dba6d17 100644 (file)
@@ -615,6 +615,91 @@ static void phy_error(struct phy_device *phydev)
 }
 
 /**
+ * phy_disable_interrupts - Disable the PHY interrupts from the PHY side
+ * @phydev: target phy_device struct
+ */
+static int phy_disable_interrupts(struct phy_device *phydev)
+{
+       int err;
+
+       /* Disable PHY interrupts */
+       err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+       if (err)
+               goto phy_err;
+
+       /* Clear the interrupt */
+       err = phy_clear_interrupt(phydev);
+       if (err)
+               goto phy_err;
+
+       return 0;
+
+phy_err:
+       phy_error(phydev);
+
+       return err;
+}
+
+/**
+ * phy_change - Called by the phy_interrupt to handle PHY changes
+ * @phydev: phy_device struct that interrupted
+ */
+static irqreturn_t phy_change(struct phy_device *phydev)
+{
+       if (phy_interrupt_is_valid(phydev)) {
+               if (phydev->drv->did_interrupt &&
+                   !phydev->drv->did_interrupt(phydev))
+                       goto ignore;
+
+               if (phy_disable_interrupts(phydev))
+                       goto phy_err;
+       }
+
+       mutex_lock(&phydev->lock);
+       if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
+               phydev->state = PHY_CHANGELINK;
+       mutex_unlock(&phydev->lock);
+
+       if (phy_interrupt_is_valid(phydev)) {
+               atomic_dec(&phydev->irq_disable);
+               enable_irq(phydev->irq);
+
+               /* Reenable interrupts */
+               if (PHY_HALTED != phydev->state &&
+                   phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED))
+                       goto irq_enable_err;
+       }
+
+       /* reschedule state queue work to run as soon as possible */
+       phy_trigger_machine(phydev, true);
+       return IRQ_HANDLED;
+
+ignore:
+       atomic_dec(&phydev->irq_disable);
+       enable_irq(phydev->irq);
+       return IRQ_NONE;
+
+irq_enable_err:
+       disable_irq(phydev->irq);
+       atomic_inc(&phydev->irq_disable);
+phy_err:
+       phy_error(phydev);
+       return IRQ_NONE;
+}
+
+/**
+ * phy_change_work - Scheduled by the phy_mac_interrupt to handle PHY changes
+ * @work: work_struct that describes the work to be done
+ */
+void phy_change_work(struct work_struct *work)
+{
+       struct phy_device *phydev =
+               container_of(work, struct phy_device, phy_queue);
+
+       phy_change(phydev);
+}
+
+/**
  * phy_interrupt - PHY interrupt handler
  * @irq: interrupt line
  * @phy_dat: phy_device pointer
@@ -632,9 +717,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
        disable_irq_nosync(irq);
        atomic_inc(&phydev->irq_disable);
 
-       phy_change(phydev);
-
-       return IRQ_HANDLED;
+       return phy_change(phydev);
 }
 
 /**
@@ -652,32 +735,6 @@ static int phy_enable_interrupts(struct phy_device *phydev)
 }
 
 /**
- * phy_disable_interrupts - Disable the PHY interrupts from the PHY side
- * @phydev: target phy_device struct
- */
-static int phy_disable_interrupts(struct phy_device *phydev)
-{
-       int err;
-
-       /* Disable PHY interrupts */
-       err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
-       if (err)
-               goto phy_err;
-
-       /* Clear the interrupt */
-       err = phy_clear_interrupt(phydev);
-       if (err)
-               goto phy_err;
-
-       return 0;
-
-phy_err:
-       phy_error(phydev);
-
-       return err;
-}
-
-/**
  * phy_start_interrupts - request and enable interrupts for a PHY device
  * @phydev: target phy_device struct
  *
@@ -728,64 +785,6 @@ int phy_stop_interrupts(struct phy_device *phydev)
 EXPORT_SYMBOL(phy_stop_interrupts);
 
 /**
- * phy_change - Called by the phy_interrupt to handle PHY changes
- * @phydev: phy_device struct that interrupted
- */
-void phy_change(struct phy_device *phydev)
-{
-       if (phy_interrupt_is_valid(phydev)) {
-               if (phydev->drv->did_interrupt &&
-                   !phydev->drv->did_interrupt(phydev))
-                       goto ignore;
-
-               if (phy_disable_interrupts(phydev))
-                       goto phy_err;
-       }
-
-       mutex_lock(&phydev->lock);
-       if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
-               phydev->state = PHY_CHANGELINK;
-       mutex_unlock(&phydev->lock);
-
-       if (phy_interrupt_is_valid(phydev)) {
-               atomic_dec(&phydev->irq_disable);
-               enable_irq(phydev->irq);
-
-               /* Reenable interrupts */
-               if (PHY_HALTED != phydev->state &&
-                   phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED))
-                       goto irq_enable_err;
-       }
-
-       /* reschedule state queue work to run as soon as possible */
-       phy_trigger_machine(phydev, true);
-       return;
-
-ignore:
-       atomic_dec(&phydev->irq_disable);
-       enable_irq(phydev->irq);
-       return;
-
-irq_enable_err:
-       disable_irq(phydev->irq);
-       atomic_inc(&phydev->irq_disable);
-phy_err:
-       phy_error(phydev);
-}
-
-/**
- * phy_change_work - Scheduled by the phy_mac_interrupt to handle PHY changes
- * @work: work_struct that describes the work to be done
- */
-void phy_change_work(struct work_struct *work)
-{
-       struct phy_device *phydev =
-               container_of(work, struct phy_device, phy_queue);
-
-       phy_change(phydev);
-}
-
-/**
  * phy_stop - Bring down the PHY link, and stop checking the status
  * @phydev: target phy_device struct
  */
index 600076e..dca9e92 100644 (file)
@@ -895,7 +895,6 @@ int phy_driver_register(struct phy_driver *new_driver, struct module *owner);
 int phy_drivers_register(struct phy_driver *new_driver, int n,
                         struct module *owner);
 void phy_state_machine(struct work_struct *work);
-void phy_change(struct phy_device *phydev);
 void phy_change_work(struct work_struct *work);
 void phy_mac_interrupt(struct phy_device *phydev, int new_link);
 void phy_start_machine(struct phy_device *phydev);