From: Björn Töpel <bjorn.to...@intel.com>

Added a buff_poll implementation that do page recycling.

Signed-off-by: Björn Töpel <bjorn.to...@intel.com>
---
 drivers/net/ethernet/intel/i40e/buff_pool.c | 385 ++++++++++++++++++++++++++++
 drivers/net/ethernet/intel/i40e/buff_pool.h |   6 +
 2 files changed, 391 insertions(+)

diff --git a/drivers/net/ethernet/intel/i40e/buff_pool.c 
b/drivers/net/ethernet/intel/i40e/buff_pool.c
index 8c51f61ca71d..42b6cf5042e9 100644
--- a/drivers/net/ethernet/intel/i40e/buff_pool.c
+++ b/drivers/net/ethernet/intel/i40e/buff_pool.c
@@ -283,3 +283,388 @@ void i40e_buff_pool_destroy(struct buff_pool *pool)
        kfree(pool);
 }
 
+/* Recycling allocator */
+
+struct i40e_bpr_header {
+       dma_addr_t dma;
+#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
+       __u32 page_offset;
+#else
+       __u16 page_offset;
+#endif
+       __u16 pagecnt_bias;
+};
+
+struct i40e_bpr_pool {
+       unsigned int buff_tot_len;
+       unsigned int buff_len;
+       unsigned int headroom;
+       unsigned int pg_order;
+       unsigned int pg_size;
+       struct device *dev;
+       unsigned int head;
+       unsigned int tail;
+       unsigned int buffs_size_mask;
+       struct i40e_bpr_header *buffs[0];
+};
+
+#define I40E_BPRHDR_ALIGNED_SIZE ALIGN(sizeof(struct i40e_bpr_header), \
+                                      SMP_CACHE_BYTES)
+
+static int i40e_bpr_alloc(void *pool, unsigned long *handle)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+       struct i40e_bpr_header *hdr;
+       struct page *pg;
+       dma_addr_t dma;
+
+       if (impl->head != impl->tail) {
+               *handle = (unsigned long)impl->buffs[impl->head];
+               impl->head = (impl->head + 1) & impl->buffs_size_mask;
+
+               return 0;
+       }
+
+       pg = dev_alloc_pages(impl->pg_order);
+       if (unlikely(!pg))
+               return -ENOMEM;
+
+       dma = dma_map_page_attrs(impl->dev, pg, 0, impl->pg_size,
+                                DMA_FROM_DEVICE, I40E_RX_DMA_ATTR);
+
+       if (dma_mapping_error(impl->dev, dma)) {
+               __free_pages(pg, impl->pg_order);
+               return -ENOMEM;
+       }
+
+       hdr = (struct i40e_bpr_header *)page_address(pg);
+       hdr->dma = dma;
+       hdr->page_offset = I40E_BPRHDR_ALIGNED_SIZE;
+       hdr->pagecnt_bias = 1;
+
+       *handle = (unsigned long)hdr;
+
+       return 0;
+}
+
+static void i40e_bpr_free(void *pool, unsigned long handle)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+       struct i40e_bpr_header *hdr;
+       unsigned int tail;
+
+       hdr = (struct i40e_bpr_header *)handle;
+       tail = (impl->tail + 1) & impl->buffs_size_mask;
+       /* Is full? */
+       if (tail == impl->head) {
+               dma_unmap_page_attrs(impl->dev, hdr->dma, impl->pg_size,
+                                    DMA_FROM_DEVICE, I40E_RX_DMA_ATTR);
+               __page_frag_cache_drain(virt_to_head_page(hdr),
+                                       hdr->pagecnt_bias);
+       }
+
+       impl->buffs[impl->tail] = hdr;
+       impl->tail = tail;
+}
+
+static unsigned int i40e_bpr_buff_size(void *pool)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+
+       return impl->buff_len;
+}
+
+static unsigned int i40e_bpr_total_buff_size(void *pool)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+
+       return impl->buff_tot_len;
+}
+
+static unsigned int i40e_bpr_buff_headroom(void *pool)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+
+       return impl->headroom;
+}
+
+static unsigned int i40e_bpr_buff_truesize(void *pool)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+
+       return impl->buff_tot_len;
+}
+
+static void *i40e_bpr_buff_ptr(void *pool, unsigned long handle)
+{
+       struct i40e_bpr_header *hdr;
+
+       hdr = (struct i40e_bpr_header *)handle;
+
+       return ((void *)hdr) + hdr->page_offset;
+}
+
+static bool i40e_page_is_reusable(struct page *page)
+{
+       return (page_to_nid(page) == numa_mem_id()) &&
+               !page_is_pfmemalloc(page);
+}
+
+static bool i40e_can_reuse_page(struct i40e_bpr_header *hdr)
+{
+       unsigned int pagecnt_bias = hdr->pagecnt_bias;
+       struct page *page = virt_to_head_page(hdr);
+
+       if (unlikely(!i40e_page_is_reusable(page)))
+               return false;
+
+#if (PAGE_SIZE < 8192)
+       if (unlikely((page_count(page) - pagecnt_bias) > 1))
+               return false;
+#else
+#define I40E_LAST_OFFSET \
+       (PAGE_SIZE - I40E_RXBUFFER_3072 - I40E_BPRHDR_ALIGNED_SIZE)
+       if (hdr->page_offset > I40E_LAST_OFFSET)
+               return false;
+#endif
+
+       if (unlikely(!pagecnt_bias)) {
+               page_ref_add(page, USHRT_MAX);
+               hdr->pagecnt_bias = USHRT_MAX;
+       }
+
+       return true;
+}
+
+static int i40e_bpr_buff_convert_to_page(void *pool, unsigned long handle,
+                                        struct page **pg,
+                                        unsigned int *pg_off)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+       struct i40e_bpr_header *hdr;
+       unsigned int tail;
+
+       hdr = (struct i40e_bpr_header *)handle;
+
+       *pg = virt_to_page(hdr);
+       *pg_off = hdr->page_offset;
+
+#if (PAGE_SIZE < 8192)
+       hdr->page_offset ^= impl->buff_tot_len;
+#else
+       hdr->page_offset += impl->buff_tot_len;
+#endif
+       hdr->pagecnt_bias--;
+
+       tail = (impl->tail + 1) & impl->buffs_size_mask;
+       if (i40e_can_reuse_page(hdr) && tail != impl->head) {
+               impl->buffs[impl->tail] = hdr;
+               impl->tail = tail;
+
+               return 0;
+       }
+
+       dma_unmap_page_attrs(impl->dev, hdr->dma, impl->pg_size,
+                            DMA_FROM_DEVICE, I40E_RX_DMA_ATTR);
+       __page_frag_cache_drain(*pg, hdr->pagecnt_bias);
+       return 0;
+}
+
+static dma_addr_t i40e_bpr_buff_dma(void *pool,
+                                   unsigned long handle)
+{
+       struct i40e_bpr_header *hdr;
+
+       hdr = (struct i40e_bpr_header *)handle;
+
+       return hdr->dma + hdr->page_offset;
+}
+
+static void i40e_bpr_buff_dma_sync_cpu(void *pool,
+                                      unsigned long handle,
+                                      unsigned int off,
+                                      unsigned int size)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+       dma_addr_t dma;
+
+       dma = i40e_bpr_buff_dma(pool, handle);
+       dma_sync_single_range_for_cpu(impl->dev, dma, off, size,
+                                     DMA_FROM_DEVICE);
+}
+
+static void i40e_bpr_buff_dma_sync_dev(void *pool,
+                                      unsigned long handle,
+                                      unsigned int off,
+                                      unsigned int size)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool;
+       dma_addr_t dma;
+
+       dma = i40e_bpr_buff_dma(pool, handle);
+       dma_sync_single_range_for_device(impl->dev, dma, off, size,
+                                        DMA_FROM_DEVICE);
+}
+
+static void calc_buffer_size_less_8192(unsigned int mtu, bool reserve_headroom,
+                                      unsigned int *buff_tot_len,
+                                      unsigned int *buff_len,
+                                      unsigned int *headroom,
+                                      unsigned int *pg_order)
+{
+       *pg_order = 0;
+
+       if (!reserve_headroom) {
+               *buff_tot_len = (PAGE_SIZE - I40E_BPRHDR_ALIGNED_SIZE) / 2;
+               *buff_len = *buff_tot_len;
+               *headroom = 0;
+
+               return;
+       }
+
+       /* We're relying on page flipping, so make sure that a page
+        * (with the buff header removed) / 2 is large enough.
+        */
+       *buff_tot_len = (PAGE_SIZE - I40E_BPRHDR_ALIGNED_SIZE) / 2;
+       if ((NET_SKB_PAD + I40E_RXBUFFER_1536) <=
+           SKB_WITH_OVERHEAD(*buff_tot_len) && mtu <= ETH_DATA_LEN) {
+               *buff_len = I40E_RXBUFFER_1536;
+               *headroom = SKB_WITH_OVERHEAD(*buff_tot_len) - *buff_len;
+
+               return;
+       }
+
+       *pg_order = 1;
+       *buff_tot_len = ((PAGE_SIZE << 1) - I40E_BPRHDR_ALIGNED_SIZE) / 2;
+       *buff_len = I40E_RXBUFFER_3072;
+       *headroom = SKB_WITH_OVERHEAD(*buff_tot_len) - *buff_len;
+}
+
+static void calc_buffer_size_greater_8192(bool reserve_headroom,
+                                         unsigned int *buff_tot_len,
+                                         unsigned int *buff_len,
+                                         unsigned int *headroom,
+                                         unsigned int *pg_order)
+{
+       *pg_order = 0;
+
+       if (!reserve_headroom) {
+               *buff_tot_len = I40E_RXBUFFER_2048;
+               *buff_len = I40E_RXBUFFER_2048;
+               *headroom = 0;
+
+               return;
+       }
+
+       *buff_tot_len = I40E_RXBUFFER_3072;
+       *buff_len = SKB_WITH_OVERHEAD(*buff_tot_len) - NET_SKB_PAD;
+       *buff_len = (*buff_len / 128) * 128; /* 128B align */
+       *headroom = *buff_tot_len - *buff_len;
+}
+
+static void calc_buffer_size(unsigned int mtu, bool reserve_headroom,
+                            unsigned int *buff_tot_len,
+                            unsigned int *buff_len,
+                            unsigned int *headroom,
+                            unsigned int *pg_order)
+{
+       if (PAGE_SIZE < 8192) {
+               calc_buffer_size_less_8192(mtu, reserve_headroom,
+                                          buff_tot_len,
+                                          buff_len,
+                                          headroom,
+                                          pg_order);
+
+               return;
+       }
+
+       calc_buffer_size_greater_8192(reserve_headroom, buff_tot_len,
+                                     buff_len, headroom, pg_order);
+}
+
+struct buff_pool *i40e_buff_pool_recycle_create(unsigned int mtu,
+                                               bool reserve_headroom,
+                                               struct device *dev,
+                                               unsigned int pool_size)
+{
+       struct buff_pool_ops *pool_ops;
+       struct i40e_bpr_pool *impl;
+       struct buff_pool *pool;
+
+       if (!is_power_of_2(pool_size)) {
+               pr_err("%s pool_size (%u) is not power of 2\n", __func__, 
pool_size);
+
+               return NULL;
+       }
+
+       pool = kzalloc(sizeof(*pool), GFP_KERNEL);
+       if (!pool)
+               return NULL;
+
+       pool_ops = kzalloc(sizeof(*pool_ops), GFP_KERNEL);
+       if (!pool_ops) {
+               kfree(pool);
+               return NULL;
+       }
+
+       impl = kzalloc(sizeof(*impl) +
+                      pool_size * sizeof(struct i40e_bpr_header *),
+                      GFP_KERNEL);
+       if (!impl) {
+               kfree(pool_ops);
+               kfree(pool);
+               return NULL;
+       }
+
+       calc_buffer_size(mtu, reserve_headroom,
+                        &impl->buff_tot_len,
+                        &impl->buff_len,
+                        &impl->headroom,
+                        &impl->pg_order);
+
+       impl->buffs_size_mask = pool_size - 1;
+       impl->dev = dev;
+       impl->pg_size = PAGE_SIZE << impl->pg_order;
+
+       pool_ops->alloc = i40e_bpr_alloc;
+       pool_ops->free = i40e_bpr_free;
+       pool_ops->buff_size = i40e_bpr_buff_size;
+       pool_ops->total_buff_size = i40e_bpr_total_buff_size;
+       pool_ops->buff_headroom = i40e_bpr_buff_headroom;
+       pool_ops->buff_truesize = i40e_bpr_buff_truesize;
+       pool_ops->buff_ptr = i40e_bpr_buff_ptr;
+       pool_ops->buff_convert_to_page = i40e_bpr_buff_convert_to_page;
+       pool_ops->buff_dma = i40e_bpr_buff_dma;
+       pool_ops->buff_dma_sync_cpu = i40e_bpr_buff_dma_sync_cpu;
+       pool_ops->buff_dma_sync_dev = i40e_bpr_buff_dma_sync_dev;
+
+       pr_err("%s mtu=%u reserve=%d pool_size=%u buff_tot_len=%u buff_len=%u 
headroom=%u pg_order=%u pf_size=%u\n",
+              __func__,
+              mtu, (int)reserve_headroom, pool_size, impl->buff_tot_len,
+              impl->buff_len, impl->headroom, impl->pg_order, impl->pg_size);
+
+       pool->pool = impl;
+       pool->ops = pool_ops;
+
+       return pool;
+}
+
+void i40e_buff_pool_recycle_destroy(struct buff_pool *pool)
+{
+       struct i40e_bpr_pool *impl = (struct i40e_bpr_pool *)pool->pool;
+       struct i40e_bpr_header *hdr;
+
+       while (impl->head != impl->tail) {
+               hdr = impl->buffs[impl->head];
+               dma_unmap_page_attrs(impl->dev, hdr->dma, impl->pg_size,
+                                    DMA_FROM_DEVICE, I40E_RX_DMA_ATTR);
+               __page_frag_cache_drain(virt_to_head_page(hdr),
+                                       hdr->pagecnt_bias);
+               impl->head = (impl->head + 1) & impl->buffs_size_mask;
+       }
+
+       kfree(pool->ops);
+       kfree(pool->pool);
+       kfree(pool);
+}
+
diff --git a/drivers/net/ethernet/intel/i40e/buff_pool.h 
b/drivers/net/ethernet/intel/i40e/buff_pool.h
index 933881e14ac0..03897f5ebbff 100644
--- a/drivers/net/ethernet/intel/i40e/buff_pool.h
+++ b/drivers/net/ethernet/intel/i40e/buff_pool.h
@@ -66,5 +66,11 @@ void bpool_buff_dma_sync_dev(struct buff_pool *pool,
 struct buff_pool *i40e_buff_pool_create(struct device *dev);
 void i40e_buff_pool_destroy(struct buff_pool *pool);
 
+struct buff_pool *i40e_buff_pool_recycle_create(unsigned int mtu,
+                                               bool reserve_headroom,
+                                               struct device *dev,
+                                               unsigned int pool_size);
+void i40e_buff_pool_recycle_destroy(struct buff_pool *pool);
+
 #endif /* BUFF_POOL_H_ */
 
-- 
2.14.1

Reply via email to