diff options
author | Anton Vorontsov <avorontsov@ru.mvista.com> | 2009-09-10 07:48:12 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2009-09-11 15:54:45 -0400 |
commit | 864fdf884e82bacbe8ca5e93bd43393a61d2e2b4 (patch) | |
tree | 53a45c507d77eb8cc33cd9226ebeac947119ede3 /drivers/net/ucc_geth.c | |
parent | 7de8ee787e8e10adaf5635bffab4ee19a7558afb (diff) |
ucc_geth: Fix hangs after switching from full to half duplex
MPC8360 QE UCC ethernet controllers hang when changing link duplex
under a load (a bit of NFS activity is enough).
PHY: mdio@e0102120:00 - Link is Up - 1000/Full
sh-3.00# ethtool -s eth0 speed 100 duplex half autoneg off
PHY: mdio@e0102120:00 - Link is Down
PHY: mdio@e0102120:00 - Link is Up - 100/Half
NETDEV WATCHDOG: eth0 (ucc_geth): transmit queue 0 timed out
------------[ cut here ]------------
Badness at c01fcbd0 [verbose debug info unavailable]
NIP: c01fcbd0 LR: c01fcbd0 CTR: c0194e44
...
The cure is to disable the controller before changing speed/duplex
and enable it afterwards.
Though, disabling the controller might take quite a while, so we
better not grab any spinlocks in adjust_link(). Instead, we quiesce
the driver's activity, and only then disable the controller.
Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ucc_geth.c')
-rw-r--r-- | drivers/net/ucc_geth.c | 36 |
1 files changed, 31 insertions, 5 deletions
diff --git a/drivers/net/ucc_geth.c b/drivers/net/ucc_geth.c index a39368a46352..4469f2451a6f 100644 --- a/drivers/net/ucc_geth.c +++ b/drivers/net/ucc_geth.c | |||
@@ -1561,6 +1561,25 @@ static int ugeth_disable(struct ucc_geth_private *ugeth, enum comm_dir mode) | |||
1561 | return 0; | 1561 | return 0; |
1562 | } | 1562 | } |
1563 | 1563 | ||
1564 | static void ugeth_quiesce(struct ucc_geth_private *ugeth) | ||
1565 | { | ||
1566 | /* Wait for and prevent any further xmits. */ | ||
1567 | netif_tx_disable(ugeth->ndev); | ||
1568 | |||
1569 | /* Disable the interrupt to avoid NAPI rescheduling. */ | ||
1570 | disable_irq(ugeth->ug_info->uf_info.irq); | ||
1571 | |||
1572 | /* Stop NAPI, and possibly wait for its completion. */ | ||
1573 | napi_disable(&ugeth->napi); | ||
1574 | } | ||
1575 | |||
1576 | static void ugeth_activate(struct ucc_geth_private *ugeth) | ||
1577 | { | ||
1578 | napi_enable(&ugeth->napi); | ||
1579 | enable_irq(ugeth->ug_info->uf_info.irq); | ||
1580 | netif_tx_wake_all_queues(ugeth->ndev); | ||
1581 | } | ||
1582 | |||
1564 | /* Called every time the controller might need to be made | 1583 | /* Called every time the controller might need to be made |
1565 | * aware of new link state. The PHY code conveys this | 1584 | * aware of new link state. The PHY code conveys this |
1566 | * information through variables in the ugeth structure, and this | 1585 | * information through variables in the ugeth structure, and this |
@@ -1574,14 +1593,11 @@ static void adjust_link(struct net_device *dev) | |||
1574 | struct ucc_geth __iomem *ug_regs; | 1593 | struct ucc_geth __iomem *ug_regs; |
1575 | struct ucc_fast __iomem *uf_regs; | 1594 | struct ucc_fast __iomem *uf_regs; |
1576 | struct phy_device *phydev = ugeth->phydev; | 1595 | struct phy_device *phydev = ugeth->phydev; |
1577 | unsigned long flags; | ||
1578 | int new_state = 0; | 1596 | int new_state = 0; |
1579 | 1597 | ||
1580 | ug_regs = ugeth->ug_regs; | 1598 | ug_regs = ugeth->ug_regs; |
1581 | uf_regs = ugeth->uccf->uf_regs; | 1599 | uf_regs = ugeth->uccf->uf_regs; |
1582 | 1600 | ||
1583 | spin_lock_irqsave(&ugeth->lock, flags); | ||
1584 | |||
1585 | if (phydev->link) { | 1601 | if (phydev->link) { |
1586 | u32 tempval = in_be32(&ug_regs->maccfg2); | 1602 | u32 tempval = in_be32(&ug_regs->maccfg2); |
1587 | u32 upsmr = in_be32(&uf_regs->upsmr); | 1603 | u32 upsmr = in_be32(&uf_regs->upsmr); |
@@ -1632,9 +1648,21 @@ static void adjust_link(struct net_device *dev) | |||
1632 | ugeth->oldspeed = phydev->speed; | 1648 | ugeth->oldspeed = phydev->speed; |
1633 | } | 1649 | } |
1634 | 1650 | ||
1651 | /* | ||
1652 | * To change the MAC configuration we need to disable the | ||
1653 | * controller. To do so, we have to either grab ugeth->lock, | ||
1654 | * which is a bad idea since 'graceful stop' commands might | ||
1655 | * take quite a while, or we can quiesce driver's activity. | ||
1656 | */ | ||
1657 | ugeth_quiesce(ugeth); | ||
1658 | ugeth_disable(ugeth, COMM_DIR_RX_AND_TX); | ||
1659 | |||
1635 | out_be32(&ug_regs->maccfg2, tempval); | 1660 | out_be32(&ug_regs->maccfg2, tempval); |
1636 | out_be32(&uf_regs->upsmr, upsmr); | 1661 | out_be32(&uf_regs->upsmr, upsmr); |
1637 | 1662 | ||
1663 | ugeth_enable(ugeth, COMM_DIR_RX_AND_TX); | ||
1664 | ugeth_activate(ugeth); | ||
1665 | |||
1638 | if (!ugeth->oldlink) { | 1666 | if (!ugeth->oldlink) { |
1639 | new_state = 1; | 1667 | new_state = 1; |
1640 | ugeth->oldlink = 1; | 1668 | ugeth->oldlink = 1; |
@@ -1648,8 +1676,6 @@ static void adjust_link(struct net_device *dev) | |||
1648 | 1676 | ||
1649 | if (new_state && netif_msg_link(ugeth)) | 1677 | if (new_state && netif_msg_link(ugeth)) |
1650 | phy_print_status(phydev); | 1678 | phy_print_status(phydev); |
1651 | |||
1652 | spin_unlock_irqrestore(&ugeth->lock, flags); | ||
1653 | } | 1679 | } |
1654 | 1680 | ||
1655 | /* Initialize TBI PHY interface for communicating with the | 1681 | /* Initialize TBI PHY interface for communicating with the |