net/sonic: Fix receive buffer replenishment
authorFinn Thain <fthain@telegraphics.com.au>
Wed, 22 Jan 2020 22:07:26 +0000 (09:07 +1100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 23 Jan 2020 20:24:37 +0000 (21:24 +0100)
As soon as the driver is finished with a receive buffer it allocs a new
one and overwrites the corresponding RRA entry with a new buffer pointer.

Problem is, the buffer pointer is split across two word-sized registers.
It can't be updated in one atomic store. So this operation races with the
chip while it stores received packets and advances its RRP register.
This could result in memory corruption by a DMA write.

Avoid this problem by adding buffers only at the location given by the
RWP register, in accordance with the National Semiconductor datasheet.

Re-factor this code into separate functions to calculate a RRA pointer
and to update the RWP.

Fixes: 2f74b9d37dfb ("[PATCH] macsonic/jazzsonic network drivers update")
Tested-by: Stan Johnson <userm57@yahoo.com>
Signed-off-by: Finn Thain <fthain@telegraphics.com.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/natsemi/sonic.c
drivers/net/ethernet/natsemi/sonic.h

index 9d2bddc4951b1474299ccab0d0f5e078432d8c52..26848acec63d574da2edf779db11439b1852f64a 100644 (file)
@@ -428,6 +428,59 @@ static int index_from_addr(struct sonic_local *lp, dma_addr_t addr,
        return -ENOENT;
 }
 
+/* Allocate and map a new skb to be used as a receive buffer. */
+static bool sonic_alloc_rb(struct net_device *dev, struct sonic_local *lp,
+                          struct sk_buff **new_skb, dma_addr_t *new_addr)
+{
+       *new_skb = netdev_alloc_skb(dev, SONIC_RBSIZE + 2);
+       if (!*new_skb)
+               return false;
+
+       if (SONIC_BUS_SCALE(lp->dma_bitmode) == 2)
+               skb_reserve(*new_skb, 2);
+
+       *new_addr = dma_map_single(lp->device, skb_put(*new_skb, SONIC_RBSIZE),
+                                  SONIC_RBSIZE, DMA_FROM_DEVICE);
+       if (!*new_addr) {
+               dev_kfree_skb(*new_skb);
+               *new_skb = NULL;
+               return false;
+       }
+
+       return true;
+}
+
+/* Place a new receive resource in the Receive Resource Area and update RWP. */
+static void sonic_update_rra(struct net_device *dev, struct sonic_local *lp,
+                            dma_addr_t old_addr, dma_addr_t new_addr)
+{
+       unsigned int entry = sonic_rr_entry(dev, SONIC_READ(SONIC_RWP));
+       unsigned int end = sonic_rr_entry(dev, SONIC_READ(SONIC_RRP));
+       u32 buf;
+
+       /* The resources in the range [RRP, RWP) belong to the SONIC. This loop
+        * scans the other resources in the RRA, those in the range [RWP, RRP).
+        */
+       do {
+               buf = (sonic_rra_get(dev, entry, SONIC_RR_BUFADR_H) << 16) |
+                     sonic_rra_get(dev, entry, SONIC_RR_BUFADR_L);
+
+               if (buf == old_addr)
+                       break;
+
+               entry = (entry + 1) & SONIC_RRS_MASK;
+       } while (entry != end);
+
+       WARN_ONCE(buf != old_addr, "failed to find resource!\n");
+
+       sonic_rra_put(dev, entry, SONIC_RR_BUFADR_H, new_addr >> 16);
+       sonic_rra_put(dev, entry, SONIC_RR_BUFADR_L, new_addr & 0xffff);
+
+       entry = (entry + 1) & SONIC_RRS_MASK;
+
+       SONIC_WRITE(SONIC_RWP, sonic_rr_addr(dev, entry));
+}
+
 /*
  * We have a good packet(s), pass it/them up the network stack.
  */
@@ -436,18 +489,15 @@ static void sonic_rx(struct net_device *dev)
        struct sonic_local *lp = netdev_priv(dev);
        int entry = lp->cur_rx;
        int prev_entry = lp->eol_rx;
+       bool rbe = false;
 
        while (sonic_rda_get(dev, entry, SONIC_RD_IN_USE) == 0) {
-               struct sk_buff *used_skb;
-               struct sk_buff *new_skb;
-               dma_addr_t new_laddr;
-               u16 bufadr_l;
-               u16 bufadr_h;
-               int pkt_len;
                u16 status = sonic_rda_get(dev, entry, SONIC_RD_STATUS);
 
                /* If the RD has LPKT set, the chip has finished with the RB */
                if ((status & SONIC_RCR_PRX) && (status & SONIC_RCR_LPKT)) {
+                       struct sk_buff *new_skb;
+                       dma_addr_t new_laddr;
                        u32 addr = (sonic_rda_get(dev, entry,
                                                  SONIC_RD_PKTPTR_H) << 16) |
                                   sonic_rda_get(dev, entry, SONIC_RD_PKTPTR_L);
@@ -458,55 +508,35 @@ static void sonic_rx(struct net_device *dev)
                                break;
                        }
 
-                       /* Malloc up new buffer. */
-                       new_skb = netdev_alloc_skb(dev, SONIC_RBSIZE + 2);
-                       if (new_skb == NULL) {
+                       if (sonic_alloc_rb(dev, lp, &new_skb, &new_laddr)) {
+                               struct sk_buff *used_skb = lp->rx_skb[i];
+                               int pkt_len;
+
+                               /* Pass the used buffer up the stack */
+                               dma_unmap_single(lp->device, addr, SONIC_RBSIZE,
+                                                DMA_FROM_DEVICE);
+
+                               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);
+                               netif_rx(used_skb);
+                               lp->stats.rx_packets++;
+                               lp->stats.rx_bytes += pkt_len;
+
+                               lp->rx_skb[i] = new_skb;
+                               lp->rx_laddr[i] = new_laddr;
+                       } else {
+                               /* Failed to obtain a new buffer so re-use it */
+                               new_laddr = addr;
                                lp->stats.rx_dropped++;
-                               break;
                        }
-                       /* provide 16 byte IP header alignment unless DMA requires otherwise */
-                       if(SONIC_BUS_SCALE(lp->dma_bitmode) == 2)
-                               skb_reserve(new_skb, 2);
-
-                       new_laddr = dma_map_single(lp->device, skb_put(new_skb, SONIC_RBSIZE),
-                                              SONIC_RBSIZE, DMA_FROM_DEVICE);
-                       if (!new_laddr) {
-                               dev_kfree_skb(new_skb);
-                               printk(KERN_ERR "%s: Failed to map rx buffer, dropping packet.\n", dev->name);
-                               lp->stats.rx_dropped++;
-                               break;
-                       }
-
-                       /* 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[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);
-                       netif_rx(used_skb);
-                       lp->stats.rx_packets++;
-                       lp->stats.rx_bytes += pkt_len;
-
-                       /* and insert the 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, i, SONIC_RR_BUFADR_L, bufadr_l);
-                       sonic_rra_put(dev, i, SONIC_RR_BUFADR_H, bufadr_h);
-                       /*
-                        * this was the last packet out of the current receive buffer
-                        * give the buffer back to the SONIC
+                       /* If RBE is already asserted when RWP advances then
+                        * it's safe to clear RBE after processing this packet.
                         */
-                       lp->cur_rwp += SIZEOF_SONIC_RR * SONIC_BUS_SCALE(lp->dma_bitmode);
-                       if (lp->cur_rwp >= lp->rra_end) lp->cur_rwp = lp->rra_laddr & 0xffff;
-                       SONIC_WRITE(SONIC_RWP, lp->cur_rwp);
-                       if (SONIC_READ(SONIC_ISR) & SONIC_INT_RBE) {
-                               netif_dbg(lp, rx_err, dev, "%s: rx buffer exhausted\n",
-                                         __func__);
-                               SONIC_WRITE(SONIC_ISR, SONIC_INT_RBE); /* clear the flag */
-                       }
+                       rbe = rbe || SONIC_READ(SONIC_ISR) & SONIC_INT_RBE;
+                       sonic_update_rra(dev, lp, addr, new_laddr);
                }
                /*
                 * give back the descriptor
@@ -528,6 +558,9 @@ static void sonic_rx(struct net_device *dev)
                              sonic_rda_get(dev, lp->eol_rx, SONIC_RD_LINK));
                lp->eol_rx = prev_entry;
        }
+
+       if (rbe)
+               SONIC_WRITE(SONIC_ISR, SONIC_INT_RBE);
        /*
         * If any worth-while packets have been received, netif_rx()
         * has done a mark_bh(NET_BH) for us and will work on them
@@ -642,15 +675,10 @@ static int sonic_init(struct net_device *dev)
        }
 
        /* initialize all RRA registers */
-       lp->rra_end = (lp->rra_laddr + SONIC_NUM_RRS * SIZEOF_SONIC_RR *
-                                       SONIC_BUS_SCALE(lp->dma_bitmode)) & 0xffff;
-       lp->cur_rwp = (lp->rra_laddr + (SONIC_NUM_RRS - 1) * SIZEOF_SONIC_RR *
-                                       SONIC_BUS_SCALE(lp->dma_bitmode)) & 0xffff;
-
-       SONIC_WRITE(SONIC_RSA, lp->rra_laddr & 0xffff);
-       SONIC_WRITE(SONIC_REA, lp->rra_end);
-       SONIC_WRITE(SONIC_RRP, lp->rra_laddr & 0xffff);
-       SONIC_WRITE(SONIC_RWP, lp->cur_rwp);
+       SONIC_WRITE(SONIC_RSA, sonic_rr_addr(dev, 0));
+       SONIC_WRITE(SONIC_REA, sonic_rr_addr(dev, SONIC_NUM_RRS));
+       SONIC_WRITE(SONIC_RRP, sonic_rr_addr(dev, 0));
+       SONIC_WRITE(SONIC_RWP, sonic_rr_addr(dev, SONIC_NUM_RRS - 1));
        SONIC_WRITE(SONIC_URRA, lp->rra_laddr >> 16);
        SONIC_WRITE(SONIC_EOBC, (SONIC_RBSIZE >> 1) - (lp->dma_bitmode ? 2 : 1));
 
index e6d47e45c5c2af217f80a0dd3647d43f07e7ddf9..cc2f7b4b77e359e91975521badf8dc3b6049e95b 100644 (file)
@@ -314,8 +314,6 @@ struct sonic_local {
        u32 rda_laddr;              /* logical DMA address of RDA */
        dma_addr_t rx_laddr[SONIC_NUM_RRS]; /* logical DMA addresses of rx skbuffs */
        dma_addr_t tx_laddr[SONIC_NUM_TDS]; /* logical DMA addresses of tx skbuffs */
-       unsigned int rra_end;
-       unsigned int cur_rwp;
        unsigned int cur_rx;
        unsigned int cur_tx;           /* first unacked transmit packet */
        unsigned int eol_rx;
@@ -450,6 +448,22 @@ static inline __u16 sonic_rra_get(struct net_device* dev, int entry,
                             (entry * SIZEOF_SONIC_RR) + offset);
 }
 
+static inline u16 sonic_rr_addr(struct net_device *dev, int entry)
+{
+       struct sonic_local *lp = netdev_priv(dev);
+
+       return lp->rra_laddr +
+              entry * SIZEOF_SONIC_RR * SONIC_BUS_SCALE(lp->dma_bitmode);
+}
+
+static inline u16 sonic_rr_entry(struct net_device *dev, u16 addr)
+{
+       struct sonic_local *lp = netdev_priv(dev);
+
+       return (addr - (u16)lp->rra_laddr) / (SIZEOF_SONIC_RR *
+                                             SONIC_BUS_SCALE(lp->dma_bitmode));
+}
+
 static const char version[] =
     "sonic.c:v0.92 20.9.98 tsbogend@alpha.franken.de\n";