aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy.c
diff options
context:
space:
mode:
authorMaciej W. Rozycki <macro@linux-mips.org>2006-10-03 11:18:35 -0400
committerJeff Garzik <jeff@garzik.org>2006-12-02 00:11:55 -0500
commit3c3070d713d798f7f9e7ee3614e49b47655d14d8 (patch)
tree232d881df29f8a14b73b08bf862739089b194e65 /drivers/net/phy/phy.c
parent13df29f69749a61b5209d52b71fcbf7300e5d6fb (diff)
[PATCH] 2.6.18: sb1250-mac: Phylib IRQ handling fixes
This patch fixes a couple of problems discovered with interrupt handling in the phylib core, namely: 1. The driver uses timer and workqueue calls, but does not include <linux/timer.h> nor <linux/workqueue.h>. 2. The driver uses schedule_work() for handling interrupts, but does not make sure any pending work scheduled thus has been completed before driver's structures get freed from memory. This is especially important as interrupts may keep arriving if the line is shared with another PHY. The solution is to ignore phy_interrupt() calls if the reported device has already been halted and calling flush_scheduled_work() from phy_stop_interrupts() (but guarded with current_is_keventd() in case the function has been called through keventd from the MAC device's close call to avoid a deadlock on the netlink lock). Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org> patch-mips-2.6.18-20060920-phy-irq-16 Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers/net/phy/phy.c')
-rw-r--r--drivers/net/phy/phy.c32
1 files changed, 26 insertions, 6 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 3af9fcf76c81..95f0419ba21e 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -7,6 +7,7 @@
7 * Author: Andy Fleming 7 * Author: Andy Fleming
8 * 8 *
9 * Copyright (c) 2004 Freescale Semiconductor, Inc. 9 * Copyright (c) 2004 Freescale Semiconductor, Inc.
10 * Copyright (c) 2006 Maciej W. Rozycki
10 * 11 *
11 * This program is free software; you can redistribute it and/or modify it 12 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the 13 * under the terms of the GNU General Public License as published by the
@@ -32,6 +33,8 @@
32#include <linux/mii.h> 33#include <linux/mii.h>
33#include <linux/ethtool.h> 34#include <linux/ethtool.h>
34#include <linux/phy.h> 35#include <linux/phy.h>
36#include <linux/timer.h>
37#include <linux/workqueue.h>
35 38
36#include <asm/io.h> 39#include <asm/io.h>
37#include <asm/irq.h> 40#include <asm/irq.h>
@@ -484,6 +487,9 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
484{ 487{
485 struct phy_device *phydev = phy_dat; 488 struct phy_device *phydev = phy_dat;
486 489
490 if (PHY_HALTED == phydev->state)
491 return IRQ_NONE; /* It can't be ours. */
492
487 /* The MDIO bus is not allowed to be written in interrupt 493 /* The MDIO bus is not allowed to be written in interrupt
488 * context, so we need to disable the irq here. A work 494 * context, so we need to disable the irq here. A work
489 * queue will write the PHY to disable and clear the 495 * queue will write the PHY to disable and clear the
@@ -577,6 +583,13 @@ int phy_stop_interrupts(struct phy_device *phydev)
577 if (err) 583 if (err)
578 phy_error(phydev); 584 phy_error(phydev);
579 585
586 /*
587 * Finish any pending work; we might have been scheduled
588 * to be called from keventd ourselves, though.
589 */
590 if (!current_is_keventd())
591 flush_scheduled_work();
592
580 free_irq(phydev->irq, phydev); 593 free_irq(phydev->irq, phydev);
581 594
582 return err; 595 return err;
@@ -603,7 +616,8 @@ static void phy_change(void *data)
603 enable_irq(phydev->irq); 616 enable_irq(phydev->irq);
604 617
605 /* Reenable interrupts */ 618 /* Reenable interrupts */
606 err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); 619 if (PHY_HALTED != phydev->state)
620 err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
607 621
608 if (err) 622 if (err)
609 goto irq_enable_err; 623 goto irq_enable_err;
@@ -624,18 +638,24 @@ void phy_stop(struct phy_device *phydev)
624 if (PHY_HALTED == phydev->state) 638 if (PHY_HALTED == phydev->state)
625 goto out_unlock; 639 goto out_unlock;
626 640
627 if (phydev->irq != PHY_POLL) { 641 phydev->state = PHY_HALTED;
628 /* Clear any pending interrupts */
629 phy_clear_interrupt(phydev);
630 642
643 if (phydev->irq != PHY_POLL) {
631 /* Disable PHY Interrupts */ 644 /* Disable PHY Interrupts */
632 phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); 645 phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
633 }
634 646
635 phydev->state = PHY_HALTED; 647 /* Clear any pending interrupts */
648 phy_clear_interrupt(phydev);
649 }
636 650
637out_unlock: 651out_unlock:
638 spin_unlock(&phydev->lock); 652 spin_unlock(&phydev->lock);
653
654 /*
655 * Cannot call flush_scheduled_work() here as desired because
656 * of rtnl_lock(), but PHY_HALTED shall guarantee phy_change()
657 * will not reenable interrupts.
658 */
639} 659}
640 660
641 661