mt76: use a per rx queue page fragment cache
authorFelix Fietkau <nbd@nbd.name>
Fri, 13 Jul 2018 14:26:15 +0000 (16:26 +0200)
committerFelix Fietkau <nbd@nbd.name>
Wed, 19 Sep 2018 10:31:44 +0000 (12:31 +0200)
Using the NAPI or netdev frag cache along with other drivers can lead to
32 KiB pages being held for a long time, despite only being used for
very few page fragments.

This can happen if the driver grabs one or two fragments for rx ring
refill, while other drivers use (and free up) the remaining fragments.
The 32 KiB higher-order page can only be freed once all users have freed
their fragments.

Depending on the traffic patterns, this can waste a lot of memory and
look a lot like a memory leak.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
drivers/net/wireless/mediatek/mt76/dma.c
drivers/net/wireless/mediatek/mt76/mt76.h
drivers/net/wireless/mediatek/mt76/usb.c

index c51da2205b938b87d5fb4b5c4ef0c15c1a5adaa4..f7fbd70164031165325d0eeb5f2067332828393e 100644 (file)
@@ -322,19 +322,13 @@ mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, bool napi)
        int len = SKB_WITH_OVERHEAD(q->buf_size);
        int offset = q->buf_offset;
        int idx;
-       void *(*alloc)(unsigned int fragsz);
-
-       if (napi)
-               alloc = napi_alloc_frag;
-       else
-               alloc = netdev_alloc_frag;
 
        spin_lock_bh(&q->lock);
 
        while (q->queued < q->ndesc - 1) {
                struct mt76_queue_buf qbuf;
 
-               buf = alloc(q->buf_size);
+               buf = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
                if (!buf)
                        break;
 
@@ -361,6 +355,7 @@ mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, bool napi)
 static void
 mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
 {
+       struct page *page;
        void *buf;
        bool more;
 
@@ -373,6 +368,13 @@ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
                skb_free_frag(buf);
        } while (1);
        spin_unlock_bh(&q->lock);
+
+       if (!q->rx_page.va)
+               return;
+
+       page = virt_to_page(q->rx_page.va);
+       __page_frag_cache_drain(page, q->rx_page.pagecnt_bias);
+       memset(&q->rx_page, 0, sizeof(q->rx_page));
 }
 
 static void
index 56983213093fc6a34727239da0b812d36dc1d517..dbda49243a105aed0ae42e1351ffc9db658375dc 100644 (file)
@@ -121,6 +121,7 @@ struct mt76_queue {
 
        dma_addr_t desc_dma;
        struct sk_buff *rx_head;
+       struct page_frag_cache rx_page;
 };
 
 struct mt76_mcu_ops {
index 16a9682d54524dea531cc831afacc03dd5be46bf..be43e2941dc488a3e1544f30d2592456edb94606 100644 (file)
@@ -275,6 +275,7 @@ static int
 mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76u_buf *buf,
                 int nsgs, int len, int sglen)
 {
+       struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
        struct urb *urb = buf->urb;
        int i;
 
@@ -283,7 +284,7 @@ mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76u_buf *buf,
                void *data;
                int offset;
 
-               data = netdev_alloc_frag(len);
+               data = page_frag_alloc(&q->rx_page, q->buf_size, GFP_ATOMIC);
                if (!data)
                        break;
 
@@ -550,10 +551,18 @@ static int mt76u_alloc_rx(struct mt76_dev *dev)
 static void mt76u_free_rx(struct mt76_dev *dev)
 {
        struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN];
+       struct page *page;
        int i;
 
        for (i = 0; i < q->ndesc; i++)
                mt76u_buf_free(&q->entry[i].ubuf);
+
+       if (!q->rx_page.va)
+               return;
+
+       page = virt_to_page(q->rx_page.va);
+       __page_frag_cache_drain(page, q->rx_page.pagecnt_bias);
+       memset(&q->rx_page, 0, sizeof(q->rx_page));
 }
 
 static void mt76u_stop_rx(struct mt76_dev *dev)