If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Signed-off-by: Brad Mouring <redacted>
---
drivers/net/phy/phy.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
From: Andrew Lunn <andrew@lunn.ch> Date: 2018-03-08 16:29:38
On Wed, Mar 07, 2018 at 04:50:42PM -0600, Brad Mouring wrote:
quoted hunk
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Signed-off-by: Brad Mouring <redacted>
---
drivers/net/phy/phy.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
On Thu, Mar 08, 2018 at 05:29:05PM +0100, Andrew Lunn wrote:
On Wed, Mar 07, 2018 at 04:50:42PM -0600, Brad Mouring wrote:
quoted
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Signed-off-by: Brad Mouring <redacted>
---
drivers/net/phy/phy.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
From: Sergei Shtylyov <hidden> Date: 2018-03-08 19:46:49
Hello!
On 03/08/2018 01:50 AM, Brad Mouring wrote:
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Hm, looking at kernel/irq/handle.c, all registered IRQ handlers are always
called regardless of their results. Care to explain?
@@ -632,6 +632,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)if(PHY_HALTED==phydev->state)returnIRQ_NONE;/* It can't be ours. */+if(phy_interrupt_is_valid(phydev)){
Always true in this context, no?
+ if (phydev->drv->did_interrupt &&
+ !phydev->drv->did_interrupt(phydev))
I don't think we can do this in the interrupt context as this function *will*
read from MDIO... I think that was the reason why IRQ handling is done in the
thread context...
From: Sergei Shtylyov <hidden> Date: 2018-03-08 19:58:05
On 03/08/2018 10:41 PM, Sergei Shtylyov wrote:
quoted
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Hm, looking at kernel/irq/handle.c, all registered IRQ handlers are always
called regardless of their results. Care to explain?
@@ -632,6 +632,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)if(PHY_HALTED==phydev->state)returnIRQ_NONE;/* It can't be ours. */+if(phy_interrupt_is_valid(phydev)){
Always true in this context, no?
quoted
+ if (phydev->drv->did_interrupt &&
+ !phydev->drv->did_interrupt(phydev))
I don't think we can do this in the interrupt context as this function *will*
read from MDIO... I think that was the reason why IRQ handling is done in the
thread context...
Ah, we're already in a thread context here! Forgot about it...
Thanks for the feedback, Sergei.
On Thu, Mar 08, 2018 at 10:41:04PM +0300, Sergei Shtylyov wrote:
Hello!
On 03/08/2018 01:50 AM, Brad Mouring wrote:
quoted
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Hm, looking at kernel/irq/handle.c, all registered IRQ handlers are always
called regardless of their results. Care to explain?
In the phy interrupt handler case, the phy_interrupt function is
registered as the threaded secondary, and irq_default_primary_handler
is being used as the primary (which will turn around and wake the
threaded handler). It seems that we wake the thread_fns in order, and
the first to report back HANDLED stops us from waking the next.
@@ -632,6 +632,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)if(PHY_HALTED==phydev->state)returnIRQ_NONE;/* It can't be ours. */+if(phy_interrupt_is_valid(phydev)){
Always true in this context, no?
Yes, already noted.
quoted
+ if (phydev->drv->did_interrupt &&
+ !phydev->drv->did_interrupt(phydev))
I don't think we can do this in the interrupt context as this function *will*
read from MDIO... I think that was the reason why IRQ handling is done in the
thread context...
phy_interrupt is the thread_fn here. We're not in interrupt context.
From: Sergei Shtylyov <hidden> Date: 2018-03-08 20:25:33
On 03/08/2018 01:50 AM, Brad Mouring wrote:
quoted hunk
If multiple phys share the same interrupt (e.g. a multi-phy chip),
the first device registered is the only one checked as phy_interrupt
will always return IRQ_HANDLED if the first phydev is not halted.
Move the interrupt check into phy_interrupt and, if it was not this
phydev, return IRQ_NONE to allow other devices on this irq a chance
to check if it was their interrupt.
Signed-off-by: Brad Mouring <redacted>
---
drivers/net/phy/phy.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
@@ -632,6 +632,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)if(PHY_HALTED==phydev->state)returnIRQ_NONE;/* It can't be ours. */+if(phy_interrupt_is_valid(phydev)){+if(phydev->drv->did_interrupt&&+!phydev->drv->did_interrupt(phydev))
Forgot to mention: DaveM prefers that continuation lines start under the 1st
column after ( on the broken up line. The way you did it this line blends with
the next one (they shouldn't).
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 <redacted>
---
drivers/net/phy/phy.c | 13 +++++++------
include/linux/phy.h | 1 -
2 files changed, 7 insertions(+), 7 deletions(-)
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 <redacted>
---
drivers/net/phy/phy.c | 145 +++++++++++++++++++++++++-------------------------
include/linux/phy.h | 1 -
2 files changed, 72 insertions(+), 74 deletions(-)
@@ -617,6 +617,77 @@ static void phy_error(struct phy_device *phydev)phy_trigger_machine(phydev,false);}+/**+*phy_disable_interrupts-DisablethePHYinterruptsfromthePHYside+*@phydev:targetphy_devicestruct+*/+staticintphy_disable_interrupts(structphy_device*phydev)+{+interr;++/* Disable PHY interrupts */+err=phy_config_interrupt(phydev,PHY_INTERRUPT_DISABLED);+if(err)+gotophy_err;++/* Clear the interrupt */+err=phy_clear_interrupt(phydev);+if(err)+gotophy_err;++return0;++phy_err:+phy_error(phydev);++returnerr;+}++/**+*phy_change-Calledbythephy_interrupttohandlePHYchanges+*@phydev:phy_devicestructthatinterrupted+*/+staticirqreturn_tphy_change(structphy_device*phydev)+{+if(phy_interrupt_is_valid(phydev)){+if(phydev->drv->did_interrupt&&+!phydev->drv->did_interrupt(phydev))+returnIRQ_NONE;++if(phydev->state==PHY_HALTED)+if(phy_disable_interrupts(phydev))+gotophy_err;+}++mutex_lock(&phydev->lock);+if((PHY_RUNNING==phydev->state)||(PHY_NOLINK==phydev->state))+phydev->state=PHY_CHANGELINK;+mutex_unlock(&phydev->lock);++/* reschedule state queue work to run as soon as possible */+phy_trigger_machine(phydev,true);++if(phy_interrupt_is_valid(phydev)&&phy_clear_interrupt(phydev))+gotophy_err;+returnIRQ_HANDLED;++phy_err:+phy_error(phydev);+returnIRQ_NONE;+}++/**+*phy_change_work-Scheduledbythephy_mac_interrupttohandlePHYchanges+*@work:work_structthatdescribestheworktobedone+*/+voidphy_change_work(structwork_struct*work)+{+structphy_device*phydev=+container_of(work,structphy_device,phy_queue);++phy_change(phydev);+}+/***phy_interrupt-PHYinterrupthandler*@irq:interruptline
@@ -632,9 +703,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)if(PHY_HALTED==phydev->state)returnIRQ_NONE;/* It can't be ours. */-phy_change(phydev);--returnIRQ_HANDLED;+returnphy_change(phydev);}/**
@@ -651,32 +720,6 @@ static int phy_enable_interrupts(struct phy_device *phydev)returnphy_config_interrupt(phydev,PHY_INTERRUPT_ENABLED);}-/**-*phy_disable_interrupts-DisablethePHYinterruptsfromthePHYside-*@phydev:targetphy_devicestruct-*/-staticintphy_disable_interrupts(structphy_device*phydev)-{-interr;--/* Disable PHY interrupts */-err=phy_config_interrupt(phydev,PHY_INTERRUPT_DISABLED);-if(err)-gotophy_err;--/* Clear the interrupt */-err=phy_clear_interrupt(phydev);-if(err)-gotophy_err;--return0;--phy_err:-phy_error(phydev);--returnerr;-}-/***phy_start_interrupts-requestandenableinterruptsforaPHYdevice*@phydev:targetphy_devicestruct
@@ -719,50 +762,6 @@ int phy_stop_interrupts(struct phy_device *phydev)}EXPORT_SYMBOL(phy_stop_interrupts);-/**-*phy_change-Calledbythephy_interrupttohandlePHYchanges-*@phydev:phy_devicestructthatinterrupted-*/-voidphy_change(structphy_device*phydev)-{-if(phy_interrupt_is_valid(phydev)){-if(phydev->drv->did_interrupt&&-!phydev->drv->did_interrupt(phydev))-return;--if(phydev->state==PHY_HALTED)-if(phy_disable_interrupts(phydev))-gotophy_err;-}--mutex_lock(&phydev->lock);-if((PHY_RUNNING==phydev->state)||(PHY_NOLINK==phydev->state))-phydev->state=PHY_CHANGELINK;-mutex_unlock(&phydev->lock);--/* reschedule state queue work to run as soon as possible */-phy_trigger_machine(phydev,true);--if(phy_interrupt_is_valid(phydev)&&phy_clear_interrupt(phydev))-gotophy_err;-return;--phy_err:-phy_error(phydev);-}--/**-*phy_change_work-Scheduledbythephy_mac_interrupttohandlePHYchanges-*@work:work_structthatdescribestheworktobedone-*/-voidphy_change_work(structwork_struct*work)-{-structphy_device*phydev=-container_of(work,structphy_device,phy_queue);--phy_change(phydev);-}-/***phy_stop-BringdownthePHYlink,andstopcheckingthestatus*@phydev:targetphy_devicestruct
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 <redacted>