Skip to content

Commit

Permalink
ucc_geth: Fix hangs after switching from full to half duplex
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Anton Vorontsov authored and David S. Miller committed Sep 11, 2009
1 parent 7de8ee7 commit 864fdf8
Showing 1 changed file with 31 additions and 5 deletions.
36 changes: 31 additions & 5 deletions drivers/net/ucc_geth.c
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,25 @@ static int ugeth_disable(struct ucc_geth_private *ugeth, enum comm_dir mode)
return 0;
}

static void ugeth_quiesce(struct ucc_geth_private *ugeth)
{
/* Wait for and prevent any further xmits. */
netif_tx_disable(ugeth->ndev);

/* Disable the interrupt to avoid NAPI rescheduling. */
disable_irq(ugeth->ug_info->uf_info.irq);

/* Stop NAPI, and possibly wait for its completion. */
napi_disable(&ugeth->napi);
}

static void ugeth_activate(struct ucc_geth_private *ugeth)
{
napi_enable(&ugeth->napi);
enable_irq(ugeth->ug_info->uf_info.irq);
netif_tx_wake_all_queues(ugeth->ndev);
}

/* Called every time the controller might need to be made
* aware of new link state. The PHY code conveys this
* information through variables in the ugeth structure, and this
Expand All @@ -1574,14 +1593,11 @@ static void adjust_link(struct net_device *dev)
struct ucc_geth __iomem *ug_regs;
struct ucc_fast __iomem *uf_regs;
struct phy_device *phydev = ugeth->phydev;
unsigned long flags;
int new_state = 0;

ug_regs = ugeth->ug_regs;
uf_regs = ugeth->uccf->uf_regs;

spin_lock_irqsave(&ugeth->lock, flags);

if (phydev->link) {
u32 tempval = in_be32(&ug_regs->maccfg2);
u32 upsmr = in_be32(&uf_regs->upsmr);
Expand Down Expand Up @@ -1632,9 +1648,21 @@ static void adjust_link(struct net_device *dev)
ugeth->oldspeed = phydev->speed;
}

/*
* To change the MAC configuration we need to disable the
* controller. To do so, we have to either grab ugeth->lock,
* which is a bad idea since 'graceful stop' commands might
* take quite a while, or we can quiesce driver's activity.
*/
ugeth_quiesce(ugeth);
ugeth_disable(ugeth, COMM_DIR_RX_AND_TX);

out_be32(&ug_regs->maccfg2, tempval);
out_be32(&uf_regs->upsmr, upsmr);

ugeth_enable(ugeth, COMM_DIR_RX_AND_TX);
ugeth_activate(ugeth);

if (!ugeth->oldlink) {
new_state = 1;
ugeth->oldlink = 1;
Expand All @@ -1648,8 +1676,6 @@ static void adjust_link(struct net_device *dev)

if (new_state && netif_msg_link(ugeth))
phy_print_status(phydev);

spin_unlock_irqrestore(&ugeth->lock, flags);
}

/* Initialize TBI PHY interface for communicating with the
Expand Down

0 comments on commit 864fdf8

Please sign in to comment.