Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-block.git] / drivers / net / ethernet / arc / emac_main.c
index dbea8472bfb47ab6293d2cb9277b342c15ccaff6..abe1eabc017177ede3d1bdc7df6bec5a8ded86da 100644 (file)
 #include "emac.h"
 
 
+/**
+ * arc_emac_tx_avail - Return the number of available slots in the tx ring.
+ * @priv: Pointer to ARC EMAC private data structure.
+ *
+ * returns: the number of slots available for transmission in tx the ring.
+ */
+static inline int arc_emac_tx_avail(struct arc_emac_priv *priv)
+{
+       return (priv->txbd_dirty + TX_BD_NUM - priv->txbd_curr - 1) % TX_BD_NUM;
+}
+
 /**
  * arc_emac_adjust_link - Adjust the PHY link duplex.
  * @ndev:      Pointer to the net_device structure.
@@ -182,10 +193,15 @@ static void arc_emac_tx_clean(struct net_device *ndev)
                txbd->info = 0;
 
                *txbd_dirty = (*txbd_dirty + 1) % TX_BD_NUM;
-
-               if (netif_queue_stopped(ndev))
-                       netif_wake_queue(ndev);
        }
+
+       /* Ensure that txbd_dirty is visible to tx() before checking
+        * for queue stopped.
+        */
+       smp_mb();
+
+       if (netif_queue_stopped(ndev) && arc_emac_tx_avail(priv))
+               netif_wake_queue(ndev);
 }
 
 /**
@@ -300,7 +316,7 @@ static int arc_emac_poll(struct napi_struct *napi, int budget)
        work_done = arc_emac_rx(ndev, budget);
        if (work_done < budget) {
                napi_complete(napi);
-               arc_reg_or(priv, R_ENABLE, RXINT_MASK);
+               arc_reg_or(priv, R_ENABLE, RXINT_MASK | TXINT_MASK);
        }
 
        return work_done;
@@ -329,9 +345,9 @@ static irqreturn_t arc_emac_intr(int irq, void *dev_instance)
        /* Reset all flags except "MDIO complete" */
        arc_reg_set(priv, R_STATUS, status);
 
-       if (status & RXINT_MASK) {
+       if (status & (RXINT_MASK | TXINT_MASK)) {
                if (likely(napi_schedule_prep(&priv->napi))) {
-                       arc_reg_clr(priv, R_ENABLE, RXINT_MASK);
+                       arc_reg_clr(priv, R_ENABLE, RXINT_MASK | TXINT_MASK);
                        __napi_schedule(&priv->napi);
                }
        }
@@ -442,7 +458,7 @@ static int arc_emac_open(struct net_device *ndev)
        arc_reg_set(priv, R_TX_RING, (unsigned int)priv->txbd_dma);
 
        /* Enable interrupts */
-       arc_reg_set(priv, R_ENABLE, RXINT_MASK | ERR_MASK);
+       arc_reg_set(priv, R_ENABLE, RXINT_MASK | TXINT_MASK | ERR_MASK);
 
        /* Set CONTROL */
        arc_reg_set(priv, R_CTRL,
@@ -513,7 +529,7 @@ static int arc_emac_stop(struct net_device *ndev)
        netif_stop_queue(ndev);
 
        /* Disable interrupts */
-       arc_reg_clr(priv, R_ENABLE, RXINT_MASK | ERR_MASK);
+       arc_reg_clr(priv, R_ENABLE, RXINT_MASK | TXINT_MASK | ERR_MASK);
 
        /* Disable EMAC */
        arc_reg_clr(priv, R_CTRL, EN_MASK);
@@ -576,11 +592,9 @@ static int arc_emac_tx(struct sk_buff *skb, struct net_device *ndev)
 
        len = max_t(unsigned int, ETH_ZLEN, skb->len);
 
-       /* EMAC still holds this buffer in its possession.
-        * CPU must not modify this buffer descriptor
-        */
-       if (unlikely((le32_to_cpu(*info) & OWN_MASK) == FOR_EMAC)) {
+       if (unlikely(!arc_emac_tx_avail(priv))) {
                netif_stop_queue(ndev);
+               netdev_err(ndev, "BUG! Tx Ring full when queue awake!\n");
                return NETDEV_TX_BUSY;
        }
 
@@ -609,12 +623,19 @@ static int arc_emac_tx(struct sk_buff *skb, struct net_device *ndev)
        /* Increment index to point to the next BD */
        *txbd_curr = (*txbd_curr + 1) % TX_BD_NUM;
 
-       /* Get "info" of the next BD */
-       info = &priv->txbd[*txbd_curr].info;
+       /* Ensure that tx_clean() sees the new txbd_curr before
+        * checking the queue status. This prevents an unneeded wake
+        * of the queue in tx_clean().
+        */
+       smp_mb();
 
-       /* Check if if Tx BD ring is full - next BD is still owned by EMAC */
-       if (unlikely((le32_to_cpu(*info) & OWN_MASK) == FOR_EMAC))
+       if (!arc_emac_tx_avail(priv)) {
                netif_stop_queue(ndev);
+               /* Refresh tx_dirty */
+               smp_mb();
+               if (arc_emac_tx_avail(priv))
+                       netif_start_queue(ndev);
+       }
 
        arc_reg_set(priv, R_STATUS, TXPL_MASK);