#include "q_struct.h"
 #include "nicvf_queues.h"
 
-#define NICVF_PAGE_ORDER ((PAGE_SIZE <= 4096) ?  PAGE_ALLOC_COSTLY_ORDER : 0)
-
 static inline u64 nicvf_iova_to_phys(struct nicvf *nic, dma_addr_t dma_addr)
 {
        /* Translation is installed only when IOMMU is present */
        dmem->base = NULL;
 }
 
-/* Allocate buffer for packet reception
- * HW returns memory address where packet is DMA'ed but not a pointer
- * into RBDR ring, so save buffer address at the start of fragment and
- * align the start address to a cache aligned address
+/* Allocate a new page or recycle one if possible
+ *
+ * We cannot optimize dma mapping here, since
+ * 1. It's only one RBDR ring for 8 Rx queues.
+ * 2. CQE_RX gives address of the buffer where pkt has been DMA'ed
+ *    and not idx into RBDR ring, so can't refer to saved info.
+ * 3. There are multiple receive buffers per page
  */
-static inline int nicvf_alloc_rcv_buffer(struct nicvf *nic, gfp_t gfp,
-                                        u32 buf_len, u64 **rbuf)
+static struct pgcache *nicvf_alloc_page(struct nicvf *nic,
+                                       struct rbdr *rbdr, gfp_t gfp)
 {
-       int order = NICVF_PAGE_ORDER;
+       struct page *page = NULL;
+       struct pgcache *pgcache, *next;
+
+       /* Check if page is already allocated */
+       pgcache = &rbdr->pgcache[rbdr->pgidx];
+       page = pgcache->page;
+       /* Check if page can be recycled */
+       if (page && (page_ref_count(page) != 1))
+               page = NULL;
+
+       if (!page) {
+               page = alloc_pages(gfp | __GFP_COMP | __GFP_NOWARN, 0);
+               if (!page)
+                       return NULL;
+
+               this_cpu_inc(nic->pnicvf->drv_stats->page_alloc);
+
+               /* Check for space */
+               if (rbdr->pgalloc >= rbdr->pgcnt) {
+                       /* Page can still be used */
+                       nic->rb_page = page;
+                       return NULL;
+               }
+
+               /* Save the page in page cache */
+               pgcache->page = page;
+               rbdr->pgalloc++;
+       }
+
+       /* Take extra page reference for recycling */
+       page_ref_add(page, 1);
+
+       rbdr->pgidx++;
+       rbdr->pgidx &= (rbdr->pgcnt - 1);
+
+       /* Prefetch refcount of next page in page cache */
+       next = &rbdr->pgcache[rbdr->pgidx];
+       page = next->page;
+       if (page)
+               prefetch(&page->_refcount);
+
+       return pgcache;
+}
+
+/* Allocate buffer for packet reception */
+static inline int nicvf_alloc_rcv_buffer(struct nicvf *nic, struct rbdr *rbdr,
+                                        gfp_t gfp, u32 buf_len, u64 **rbuf)
+{
+       struct pgcache *pgcache = NULL;
 
        /* Check if request can be accomodated in previous allocated page */
        if (nic->rb_page &&
-           ((nic->rb_page_offset + buf_len) < (PAGE_SIZE << order))) {
+           ((nic->rb_page_offset + buf_len) <= PAGE_SIZE)) {
                nic->rb_pageref++;
                goto ret;
        }
 
        nicvf_get_page(nic);
+       nic->rb_page = NULL;
 
-       /* Allocate a new page */
-       nic->rb_page = alloc_pages(gfp | __GFP_COMP | __GFP_NOWARN,
-                                  order);
-       if (!nic->rb_page) {
+       /* Get new page, either recycled or new one */
+       pgcache = nicvf_alloc_page(nic, rbdr, gfp);
+       if (!pgcache && !nic->rb_page) {
                this_cpu_inc(nic->pnicvf->drv_stats->rcv_buffer_alloc_failures);
                return -ENOMEM;
        }
+
        nic->rb_page_offset = 0;
+       /* Check if it's recycled */
+       if (pgcache)
+               nic->rb_page = pgcache->page;
 ret:
        /* HW will ensure data coherency, CPU sync not required */
        *rbuf = (u64 *)((u64)dma_map_page_attrs(&nic->pdev->dev, nic->rb_page,
                                                DMA_ATTR_SKIP_CPU_SYNC));
        if (dma_mapping_error(&nic->pdev->dev, (dma_addr_t)*rbuf)) {
                if (!nic->rb_page_offset)
-                       __free_pages(nic->rb_page, order);
+                       __free_pages(nic->rb_page, 0);
                nic->rb_page = NULL;
                return -ENOMEM;
        }
        rbdr->head = 0;
        rbdr->tail = 0;
 
+       /* Initialize page recycling stuff.
+        *
+        * Can't use single buffer per page especially with 64K pages.
+        * On embedded platforms i.e 81xx/83xx available memory itself
+        * is low and minimum ring size of RBDR is 8K, that takes away
+        * lots of memory.
+        */
+       rbdr->pgcnt = ring_len / (PAGE_SIZE / buf_size);
+       rbdr->pgcnt = roundup_pow_of_two(rbdr->pgcnt);
+       rbdr->pgcache = kzalloc(sizeof(*rbdr->pgcache) *
+                               rbdr->pgcnt, GFP_KERNEL);
+       if (!rbdr->pgcache)
+               return -ENOMEM;
+       rbdr->pgidx = 0;
+       rbdr->pgalloc = 0;
+
        nic->rb_page = NULL;
        for (idx = 0; idx < ring_len; idx++) {
-               err = nicvf_alloc_rcv_buffer(nic, GFP_KERNEL, RCV_FRAG_LEN,
-                                            &rbuf);
+               err = nicvf_alloc_rcv_buffer(nic, rbdr, GFP_KERNEL,
+                                            RCV_FRAG_LEN, &rbuf);
                if (err) {
                        /* To free already allocated and mapped ones */
                        rbdr->tail = idx - 1;
 {
        int head, tail;
        u64 buf_addr, phys_addr;
+       struct pgcache *pgcache;
        struct rbdr_entry_t *desc;
 
        if (!rbdr)
        if (phys_addr)
                put_page(virt_to_page(phys_to_virt(phys_addr)));
 
+       /* Sync page cache info */
+       smp_rmb();
+
+       /* Release additional page references held for recycling */
+       head = 0;
+       while (head < rbdr->pgcnt) {
+               pgcache = &rbdr->pgcache[head];
+               if (pgcache->page && page_ref_count(pgcache->page) != 0)
+                       put_page(pgcache->page);
+               head++;
+       }
+
        /* Free RBDR ring */
        nicvf_free_q_desc_mem(nic, &rbdr->dmem);
 }
        else
                refill_rb_cnt = qs->rbdr_len - qcount - 1;
 
+       /* Sync page cache info */
+       smp_rmb();
+
        /* Start filling descs from tail */
        tail = nicvf_queue_reg_read(nic, NIC_QSET_RBDR_0_1_TAIL, rbdr_idx) >> 3;
        while (refill_rb_cnt) {
                tail++;
                tail &= (rbdr->dmem.q_len - 1);
 
-               if (nicvf_alloc_rcv_buffer(nic, gfp, RCV_FRAG_LEN, &rbuf))
+               if (nicvf_alloc_rcv_buffer(nic, rbdr, gfp, RCV_FRAG_LEN, &rbuf))
                        break;
 
                desc = GET_RBDR_DESC(rbdr, tail);