diff options
author | Stephen Hemminger <shemminger@osdl.org> | 2006-01-17 13:43:20 -0800 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2006-01-17 19:27:30 -0500 |
commit | 302d12522a36790858ce93b69ebf2220f9e5173a (patch) | |
tree | 29b049801298aff6e889f9c15bcd5963cb33aebc /drivers/net/sky2.c | |
parent | 28bd181a8e4abf1126de56f0934ba7c910276475 (diff) | |
download | lwn-302d12522a36790858ce93b69ebf2220f9e5173a.tar.gz lwn-302d12522a36790858ce93b69ebf2220f9e5173a.zip |
[PATCH] sky2: more conservative transmit locking
Be more careful about transmit locking, this solves a possible race
between tx_complete and transmit, that would cause a tx timeout.
Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: Jeff Garzik <jgarzik@pobox.com>
Diffstat (limited to 'drivers/net/sky2.c')
-rw-r--r-- | drivers/net/sky2.c | 43 |
1 files changed, 31 insertions, 12 deletions
diff --git a/drivers/net/sky2.c b/drivers/net/sky2.c index f03bf79ca1f5..0c7665e465db 100644 --- a/drivers/net/sky2.c +++ b/drivers/net/sky2.c @@ -889,13 +889,13 @@ static void sky2_vlan_rx_register(struct net_device *dev, struct vlan_group *grp struct sky2_hw *hw = sky2->hw; u16 port = sky2->port; - spin_lock(&sky2->tx_lock); + spin_lock_bh(&sky2->tx_lock); sky2_write32(hw, SK_REG(port, RX_GMF_CTRL_T), RX_VLAN_STRIP_ON); sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T), TX_VLAN_TAG_ON); sky2->vlgrp = grp; - spin_unlock(&sky2->tx_lock); + spin_unlock_bh(&sky2->tx_lock); } static void sky2_vlan_rx_kill_vid(struct net_device *dev, unsigned short vid) @@ -904,14 +904,14 @@ static void sky2_vlan_rx_kill_vid(struct net_device *dev, unsigned short vid) struct sky2_hw *hw = sky2->hw; u16 port = sky2->port; - spin_lock(&sky2->tx_lock); + spin_lock_bh(&sky2->tx_lock); sky2_write32(hw, SK_REG(port, RX_GMF_CTRL_T), RX_VLAN_STRIP_OFF); sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T), TX_VLAN_TAG_OFF); if (sky2->vlgrp) sky2->vlgrp->vlan_devices[vid] = NULL; - spin_unlock(&sky2->tx_lock); + spin_unlock_bh(&sky2->tx_lock); } #endif @@ -1116,6 +1116,10 @@ static int sky2_xmit_frame(struct sk_buff *skb, struct net_device *dev) u16 mss; u8 ctrl; + /* No BH disabling for tx_lock here. We are running in BH disabled + * context and TX reclaim runs via poll inside of a software + * interrupt, and no related locks in IRQ processing. + */ if (!spin_trylock(&sky2->tx_lock)) return NETDEV_TX_LOCKED; @@ -1308,17 +1312,17 @@ static void sky2_tx_complete(struct sky2_port *sky2, u16 done) dev_kfree_skb_any(skb); } - spin_lock(&sky2->tx_lock); sky2->tx_cons = put; if (netif_queue_stopped(dev) && tx_avail(sky2) > MAX_SKB_TX_LE) netif_wake_queue(dev); - spin_unlock(&sky2->tx_lock); } /* Cleanup all untransmitted buffers, assume transmitter not running */ static void sky2_tx_clean(struct sky2_port *sky2) { + spin_lock_bh(&sky2->tx_lock); sky2_tx_complete(sky2, sky2->tx_prod); + spin_unlock_bh(&sky2->tx_lock); } /* Network shutdown */ @@ -1608,28 +1612,40 @@ out: local_irq_enable(); } + +/* Transmit timeout is only called if we are running, carries is up + * and tx queue is full (stopped). + */ static void sky2_tx_timeout(struct net_device *dev) { struct sky2_port *sky2 = netdev_priv(dev); struct sky2_hw *hw = sky2->hw; unsigned txq = txqaddr[sky2->port]; + u16 ridx; + + /* Maybe we just missed an status interrupt */ + spin_lock(&sky2->tx_lock); + ridx = sky2_read16(hw, + sky2->port == 0 ? STAT_TXA1_RIDX : STAT_TXA2_RIDX); + sky2_tx_complete(sky2, ridx); + spin_unlock(&sky2->tx_lock); + + if (!netif_queue_stopped(dev)) { + if (net_ratelimit()) + pr_info(PFX "transmit interrupt missed? recovered\n"); + return; + } if (netif_msg_timer(sky2)) printk(KERN_ERR PFX "%s: tx timeout\n", dev->name); - netif_stop_queue(dev); - sky2_write32(hw, Q_ADDR(txq, Q_CSR), BMU_STOP); - sky2_read32(hw, Q_ADDR(txq, Q_CSR)); - sky2_write32(hw, Y2_QADDR(txq, PREF_UNIT_CTRL), PREF_UNIT_RST_SET); sky2_tx_clean(sky2); sky2_qset(hw, txq); sky2_prefetch_init(hw, txq, sky2->tx_le_map, TX_RING_SIZE - 1); - - netif_wake_queue(dev); } @@ -1798,7 +1814,10 @@ static void sky2_tx_check(struct sky2_hw *hw, int port, u16 last) struct net_device *dev = hw->dev[port]; if (dev && netif_running(dev)) { struct sky2_port *sky2 = netdev_priv(dev); + + spin_lock(&sky2->tx_lock); sky2_tx_complete(sky2, last); + spin_unlock(&sky2->tx_lock); } } } |