Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2274d77c authored by Brad Mouring's avatar Brad Mouring Committed by Greg Kroah-Hartman
Browse files

net: phy: Tell caller result of phy_change()




[ Upstream commit a2c054a896b8ac794ddcfc7c92e2dc7ec4ed4ed5 ]

In 664fcf12 (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: default avatarBrad Mouring <brad.mouring@ni.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 42cf2a1e
Loading
Loading
Loading
Loading
+86 −87
Original line number Diff line number Diff line
@@ -614,6 +614,91 @@ static void phy_error(struct phy_device *phydev)
	phy_trigger_machine(phydev, false);
}

/**
 * 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
@@ -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);
}

/**
@@ -651,32 +734,6 @@ static int phy_enable_interrupts(struct phy_device *phydev)
	return phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
}

/**
 * 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
@@ -727,64 +784,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
+0 −1
Original line number Diff line number Diff line
@@ -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);