OSDN Git Service

Merge tag 'v4.4.214' into 10
[sagit-ice-cold/kernel_xiaomi_msm8998.git] / drivers / net / ethernet / natsemi / sonic.c
index 6679005..712be59 100644 (file)
@@ -50,6 +50,8 @@ static int sonic_open(struct net_device *dev)
        if (sonic_debug > 2)
                printk("sonic_open: initializing sonic driver.\n");
 
+       spin_lock_init(&lp->lock);
+
        for (i = 0; i < SONIC_NUM_RRS; i++) {
                struct sk_buff *skb = netdev_alloc_skb(dev, SONIC_RBSIZE + 2);
                if (skb == NULL) {
@@ -101,6 +103,24 @@ static int sonic_open(struct net_device *dev)
        return 0;
 }
 
+/* Wait for the SONIC to become idle. */
+static void sonic_quiesce(struct net_device *dev, u16 mask)
+{
+       struct sonic_local * __maybe_unused lp = netdev_priv(dev);
+       int i;
+       u16 bits;
+
+       for (i = 0; i < 1000; ++i) {
+               bits = SONIC_READ(SONIC_CMD) & mask;
+               if (!bits)
+                       return;
+               if (irqs_disabled() || in_interrupt())
+                       udelay(20);
+               else
+                       usleep_range(100, 200);
+       }
+       WARN_ONCE(1, "command deadline expired! 0x%04x\n", bits);
+}
 
 /*
  * Close the SONIC device
@@ -118,6 +138,9 @@ static int sonic_close(struct net_device *dev)
        /*
         * stop the SONIC, disable interrupts
         */
+       SONIC_WRITE(SONIC_CMD, SONIC_CR_RXDIS);
+       sonic_quiesce(dev, SONIC_CR_ALL);
+
        SONIC_WRITE(SONIC_IMR, 0);
        SONIC_WRITE(SONIC_ISR, 0x7fff);
        SONIC_WRITE(SONIC_CMD, SONIC_CR_RST);
@@ -157,6 +180,9 @@ static void sonic_tx_timeout(struct net_device *dev)
         * put the Sonic into software-reset mode and
         * disable all interrupts before releasing DMA buffers
         */
+       SONIC_WRITE(SONIC_CMD, SONIC_CR_RXDIS);
+       sonic_quiesce(dev, SONIC_CR_ALL);
+
        SONIC_WRITE(SONIC_IMR, 0);
        SONIC_WRITE(SONIC_ISR, 0x7fff);
        SONIC_WRITE(SONIC_CMD, SONIC_CR_RST);
@@ -194,8 +220,6 @@ static void sonic_tx_timeout(struct net_device *dev)
  *   wake the tx queue
  * Concurrently with all of this, the SONIC is potentially writing to
  * the status flags of the TDs.
- * Until some mutual exclusion is added, this code will not work with SMP. However,
- * MIPS Jazz machines and m68k Macs were all uni-processor machines.
  */
 
 static int sonic_send_packet(struct sk_buff *skb, struct net_device *dev)
@@ -203,7 +227,8 @@ static int sonic_send_packet(struct sk_buff *skb, struct net_device *dev)
        struct sonic_local *lp = netdev_priv(dev);
        dma_addr_t laddr;
        int length;
-       int entry = lp->next_tx;
+       int entry;
+       unsigned long flags;
 
        if (sonic_debug > 2)
                printk("sonic_send_packet: skb=%p, dev=%p\n", skb, dev);
@@ -226,6 +251,10 @@ static int sonic_send_packet(struct sk_buff *skb, struct net_device *dev)
                return NETDEV_TX_OK;
        }
 
+       spin_lock_irqsave(&lp->lock, flags);
+
+       entry = lp->next_tx;
+
        sonic_tda_put(dev, entry, SONIC_TD_STATUS, 0);       /* clear status */
        sonic_tda_put(dev, entry, SONIC_TD_FRAG_COUNT, 1);   /* single fragment */
        sonic_tda_put(dev, entry, SONIC_TD_PKTSIZE, length); /* length of packet */
@@ -235,10 +264,6 @@ static int sonic_send_packet(struct sk_buff *skb, struct net_device *dev)
        sonic_tda_put(dev, entry, SONIC_TD_LINK,
                sonic_tda_get(dev, entry, SONIC_TD_LINK) | SONIC_EOL);
 
-       /*
-        * Must set tx_skb[entry] only after clearing status, and
-        * before clearing EOL and before stopping queue
-        */
        wmb();
        lp->tx_len[entry] = length;
        lp->tx_laddr[entry] = laddr;
@@ -263,6 +288,8 @@ static int sonic_send_packet(struct sk_buff *skb, struct net_device *dev)
 
        SONIC_WRITE(SONIC_CMD, SONIC_CR_TXP);
 
+       spin_unlock_irqrestore(&lp->lock, flags);
+
        return NETDEV_TX_OK;
 }
 
@@ -275,9 +302,21 @@ static irqreturn_t sonic_interrupt(int irq, void *dev_id)
        struct net_device *dev = dev_id;
        struct sonic_local *lp = netdev_priv(dev);
        int status;
+       unsigned long flags;
+
+       /* The lock has two purposes. Firstly, it synchronizes sonic_interrupt()
+        * with sonic_send_packet() so that the two functions can share state.
+        * Secondly, it makes sonic_interrupt() re-entrant, as that is required
+        * by macsonic which must use two IRQs with different priority levels.
+        */
+       spin_lock_irqsave(&lp->lock, flags);
+
+       status = SONIC_READ(SONIC_ISR) & SONIC_IMR_DEFAULT;
+       if (!status) {
+               spin_unlock_irqrestore(&lp->lock, flags);
 
-       if (!(status = SONIC_READ(SONIC_ISR) & SONIC_IMR_DEFAULT))
                return IRQ_NONE;
+       }
 
        do {
                if (status & SONIC_INT_PKTRX) {
@@ -292,11 +331,12 @@ static irqreturn_t sonic_interrupt(int irq, void *dev_id)
                        int td_status;
                        int freed_some = 0;
 
-                       /* At this point, cur_tx is the index of a TD that is one of:
-                        *   unallocated/freed                          (status set   & tx_skb[entry] clear)
-                        *   allocated and sent                         (status set   & tx_skb[entry] set  )
-                        *   allocated and not yet sent                 (status clear & tx_skb[entry] set  )
-                        *   still being allocated by sonic_send_packet (status clear & tx_skb[entry] clear)
+                       /* The state of a Transmit Descriptor may be inferred
+                        * from { tx_skb[entry], td_status } as follows.
+                        * { clear, clear } => the TD has never been used
+                        * { set,   clear } => the TD was handed to SONIC
+                        * { set,   set   } => the TD was handed back
+                        * { clear, set   } => the TD is available for re-use
                         */
 
                        if (sonic_debug > 2)
@@ -398,10 +438,30 @@ static irqreturn_t sonic_interrupt(int irq, void *dev_id)
                /* load CAM done */
                if (status & SONIC_INT_LCD)
                        SONIC_WRITE(SONIC_ISR, SONIC_INT_LCD); /* clear the interrupt */
-       } while((status = SONIC_READ(SONIC_ISR) & SONIC_IMR_DEFAULT));
+
+               status = SONIC_READ(SONIC_ISR) & SONIC_IMR_DEFAULT;
+       } while (status);
+
+       spin_unlock_irqrestore(&lp->lock, flags);
+
        return IRQ_HANDLED;
 }
 
+/* Return the array index corresponding to a given Receive Buffer pointer. */
+static int index_from_addr(struct sonic_local *lp, dma_addr_t addr,
+                          unsigned int last)
+{
+       unsigned int i = last;
+
+       do {
+               i = (i + 1) & SONIC_RRS_MASK;
+               if (addr == lp->rx_laddr[i])
+                       return i;
+       } while (i != last);
+
+       return -ENOENT;
+}
+
 /*
  * We have a good packet(s), pass it/them up the network stack.
  */
@@ -421,6 +481,16 @@ static void sonic_rx(struct net_device *dev)
 
                status = sonic_rda_get(dev, entry, SONIC_RD_STATUS);
                if (status & SONIC_RCR_PRX) {
+                       u32 addr = (sonic_rda_get(dev, entry,
+                                                 SONIC_RD_PKTPTR_H) << 16) |
+                                  sonic_rda_get(dev, entry, SONIC_RD_PKTPTR_L);
+                       int i = index_from_addr(lp, addr, entry);
+
+                       if (i < 0) {
+                               WARN_ONCE(1, "failed to find buffer!\n");
+                               break;
+                       }
+
                        /* Malloc up new buffer. */
                        new_skb = netdev_alloc_skb(dev, SONIC_RBSIZE + 2);
                        if (new_skb == NULL) {
@@ -442,7 +512,7 @@ static void sonic_rx(struct net_device *dev)
 
                        /* now we have a new skb to replace it, pass the used one up the stack */
                        dma_unmap_single(lp->device, lp->rx_laddr[entry], SONIC_RBSIZE, DMA_FROM_DEVICE);
-                       used_skb = lp->rx_skb[entry];
+                       used_skb = lp->rx_skb[i];
                        pkt_len = sonic_rda_get(dev, entry, SONIC_RD_PKTLEN);
                        skb_trim(used_skb, pkt_len);
                        used_skb->protocol = eth_type_trans(used_skb, dev);
@@ -451,13 +521,13 @@ static void sonic_rx(struct net_device *dev)
                        lp->stats.rx_bytes += pkt_len;
 
                        /* and insert the new skb */
-                       lp->rx_laddr[entry] = new_laddr;
-                       lp->rx_skb[entry] = new_skb;
+                       lp->rx_laddr[i] = new_laddr;
+                       lp->rx_skb[i] = new_skb;
 
                        bufadr_l = (unsigned long)new_laddr & 0xffff;
                        bufadr_h = (unsigned long)new_laddr >> 16;
-                       sonic_rra_put(dev, entry, SONIC_RR_BUFADR_L, bufadr_l);
-                       sonic_rra_put(dev, entry, SONIC_RR_BUFADR_H, bufadr_h);
+                       sonic_rra_put(dev, i, SONIC_RR_BUFADR_L, bufadr_l);
+                       sonic_rra_put(dev, i, SONIC_RR_BUFADR_H, bufadr_h);
                } else {
                        /* This should only happen, if we enable accepting broken packets. */
                        lp->stats.rx_errors++;
@@ -592,6 +662,7 @@ static int sonic_init(struct net_device *dev)
         */
        SONIC_WRITE(SONIC_CMD, 0);
        SONIC_WRITE(SONIC_CMD, SONIC_CR_RXDIS);
+       sonic_quiesce(dev, SONIC_CR_ALL);
 
        /*
         * initialize the receive resource area