From: Matteo Croce <redacted>
This is a respin of [1]
This patchset shows the plans for allowing page_pool to handle and
maintain DMA map/unmap of the pages it serves to the driver. For this
to work a return hook in the network core is introduced.
The overall purpose is to simplify drivers, by providing a page
allocation API that does recycling, such that each driver doesn't have
to reinvent its own recycling scheme. Using page_pool in a driver
does not require implementing XDP support, but it makes it trivially
easy to do so. Instead of allocating buffers specifically for SKBs
we now allocate a generic buffer and either wrap it on an SKB
(via build_skb) or create an XDP frame.
The recycling code leverages the XDP recycle APIs.
The Marvell mvpp2 and mvneta drivers are used in this patchset to
demonstrate how to use the API, and tested on a MacchiatoBIN
and EspressoBIN boards respectively.
Please let this going in on a future -rc1 so to allow enough time
to have wider tests.
Note that this series depends on the change "mm: fix struct page layout
on 32-bit systems"[2] which is not yet in master.
v6 -> v7:
- refresh patches against net-next
- remove a redundant call to virt_to_head_page()
- update mvneta benchmarks
v5 -> v6
- preserve pfmemalloc bit when setting signature
- fix typo in mvneta
- rebase on next-next with the new cache
- don't clear the skb->pp_recycle in pskb_expand_head()
v4 -> v5:
- move the signature so it doesn't alias with page->mapping
- use an invalid pointer as magic
- incorporate Matthew Wilcox's changes for pfmemalloc pages
- move the __skb_frag_unref() changes to a preliminary patch
- refactor some cpp directives
- only attempt recycling if skb->head_frag
- clear skb->pp_recycle in pskb_expand_head()
v3 -> v4:
- store a pointer to page_pool instead of xdp_mem_info
- drop a patch which reduces xdp_mem_info size
- do the recycling in the page_pool code instead of xdp_return
- remove some unused headers include
- remove some useless forward declaration
v2 -> v3:
- added missing SOBs
- CCed the MM people
v1 -> v2:
- fix a commit message
- avoid setting pp_recycle multiple times on mvneta
- squash two patches to avoid breaking bisect
[1] https://lore.kernel.org/netdev/154413868810.21735.572808840657728172.stgit@firesoul/
[2] https://lore.kernel.org/linux-mm/20210510153211.1504886-1-willy@infradead.org/
Ilias Apalodimas (1):
page_pool: Allow drivers to hint on SKB recycling
Matteo Croce (4):
mm: add a signature in struct page
skbuff: add a parameter to __skb_frag_unref
mvpp2: recycle buffers
mvneta: recycle buffers
drivers/net/ethernet/marvell/mvneta.c | 11 +++---
.../net/ethernet/marvell/mvpp2/mvpp2_main.c | 2 +-
drivers/net/ethernet/marvell/sky2.c | 2 +-
drivers/net/ethernet/mellanox/mlx4/en_rx.c | 2 +-
include/linux/mm.h | 12 ++++---
include/linux/mm_types.h | 12 ++++++-
include/linux/poison.h | 3 ++
include/linux/skbuff.h | 34 ++++++++++++++++---
include/net/page_pool.h | 9 +++++
net/core/page_pool.c | 29 ++++++++++++++++
net/core/skbuff.c | 24 ++++++++++---
net/tls/tls_device.c | 2 +-
12 files changed, 119 insertions(+), 23 deletions(-)
--
2.31.1
From: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Up to now several high speed NICs have custom mechanisms of recycling
the allocated memory they use for their payloads.
Our page_pool API already has recycling capabilities that are always
used when we are running in 'XDP mode'. So let's tweak the API and the
kernel network stack slightly and allow the recycling to happen even
during the standard operation.
The API doesn't take into account 'split page' policies used by those
drivers currently, but can be extended once we have users for that.
The idea is to be able to intercept the packet on skb_release_data().
If it's a buffer coming from our page_pool API recycle it back to the
pool for further usage or just release the packet entirely.
To achieve that we introduce a bit in struct sk_buff (pp_recycle:1) and
a field in struct page (page->pp) to store the page_pool pointer.
Storing the information in page->pp allows us to recycle both SKBs and
their fragments.
We could have skipped the skb bit entirely, since identical information
can bederived from struct page. However, in an effort to affect the free path
as less as possible, reading a single bit in the skb which is already
in cache, is better that trying to derive identical information for the
page stored data.
The driver or page_pool has to take care of the sync operations on it's own
during the buffer recycling since the buffer is, after opting-in to the
recycling, never unmapped.
Since the gain on the drivers depends on the architecture, we are not
enabling recycling by default if the page_pool API is used on a driver.
In order to enable recycling the driver must call skb_mark_for_recycle()
to store the information we need for recycling in page->pp and
enabling the recycling bit, or page_pool_store_mem_info() for a fragment.
Co-developed-by: Jesper Dangaard Brouer <redacted>
Signed-off-by: Jesper Dangaard Brouer <redacted>
Co-developed-by: Matteo Croce <redacted>
Signed-off-by: Matteo Croce <redacted>
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
include/linux/skbuff.h | 28 +++++++++++++++++++++++++---
include/net/page_pool.h | 9 +++++++++
net/core/page_pool.c | 23 +++++++++++++++++++++++
net/core/skbuff.c | 24 ++++++++++++++++++++----
4 files changed, 77 insertions(+), 7 deletions(-)
@@ -40,6 +40,9 @@#if IS_ENABLED(CONFIG_NF_CONNTRACK)#include<linux/netfilter/nf_conntrack_common.h>#endif+#ifdef CONFIG_PAGE_POOL+#include<net/page_pool.h>+#endif/* The interface for checksum offload between the stack and networking drivers*isasfollows...
@@ -251,4 +253,11 @@ static inline void page_pool_ring_unlock(struct page_pool *pool)spin_unlock_bh(&pool->ring.producer_lock);}+/* Store mem_info on struct page and use it while recycling skb frags */+staticinline+voidpage_pool_store_mem_info(structpage*page,structpage_pool*pp)+{+page->pp=pp;+}+#endif /* _NET_PAGE_POOL_H */
@@ -628,3 +628,26 @@ void page_pool_update_nid(struct page_pool *pool, int new_nid)}}EXPORT_SYMBOL(page_pool_update_nid);++boolpage_pool_return_skb_page(void*data)+{+structpage_pool*pp;+structpage*page;++page=virt_to_head_page(data);+if(unlikely(page->pp_magic!=PP_SIGNATURE))+returnfalse;++pp=(structpage_pool*)page->pp;++/* Driver set this to memory recycling info. Reset it on recycle.+*Thiswill*not*workforNICusingasplit-pagememorymodel.+*Thepagewillbereturnedtothepoolhereregardlessofthe+*'flipped'fragmentbeinginuseornot.+*/+page->pp=NULL;+page_pool_put_full_page(pp,page,false);++returntrue;+}+EXPORT_SYMBOL(page_pool_return_skb_page);
@@ -3495,7 +3504,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)fragto=&skb_shinfo(tgt)->frags[merge];skb_frag_size_add(fragto,skb_frag_size(fragfrom));-__skb_frag_unref(fragfrom,false);+__skb_frag_unref(fragfrom,skb->pp_recycle);}/* Reposition in the original skb */
@@ -5285,6 +5294,13 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,if(skb_cloned(to))returnfalse;+/* The page pool signature of struct page will eventually figure out+*whichpagescanberecycledornotbutfornowlet'sprohibitslab+*allocatedandpage_poolallocatedSKBsfrombeingcoalesced.+*/+if(to->pp_recycle!=from->pp_recycle)+returnfalse;+if(len<=skb_tailroom(to)){if(len)BUG_ON(skb_copy_bits(from,0,skb_put(to,len),len));
From: Matteo Croce <redacted>
This is needed by the page_pool to avoid recycling a page not allocated
via page_pool.
The page->signature field is aliased to page->lru.next and
page->compound_head, but it can't be set by mistake because the
signature value is a bad pointer, and can't trigger a false positive
in PageTail() because the last bit is 0.
Co-developed-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: Matteo Croce <redacted>
---
include/linux/mm.h | 12 +++++++-----
include/linux/mm_types.h | 12 +++++++++++-
include/linux/poison.h | 3 +++
net/core/page_pool.c | 6 ++++++
4 files changed, 27 insertions(+), 6 deletions(-)
@@ -96,6 +96,13 @@ struct page {unsignedlongprivate;};struct{/* page_pool used by netstack */+/**+*@pp_magic:magicvaluetoavoidrecyclingnon+*page_poolallocatedpages.+*/+unsignedlongpp_magic;+structpage_pool*pp;+unsignedlong_pp_mapping_pad;/***@dma_addr:mightrequirea64-bitvalueon*32-bitarchitectures.
@@ -130,7 +137,10 @@ struct page {};};struct{/* Tail pages of compound page */-unsignedlongcompound_head;/* Bit zero is set */+/* Bit zero is set+*Bitoneifpfmemallocpage+*/+unsignedlongcompound_head;/* First tail page only */unsignedcharcompound_dtor;
@@ -17,6 +17,7 @@#include<linux/dma-mapping.h>#include<linux/page-flags.h>#include<linux/mm.h> /* for __put_page() */+#include<linux/poison.h>#include<trace/events/page_pool.h>
@@ -221,6 +222,8 @@ static struct page *__page_pool_alloc_page_order(struct page_pool *pool,returnNULL;}+page->pp_magic|=PP_SIGNATURE;+/* Track how many pages are held 'in-flight' */pool->pages_state_hold_cnt++;trace_page_pool_state_hold(pool,page,pool->pages_state_hold_cnt);
@@ -263,6 +266,7 @@ static struct page *__page_pool_alloc_pages_slow(struct page_pool *pool,put_page(page);continue;}+page->pp_magic|=PP_SIGNATURE;pool->alloc.cache[pool->alloc.count++]=page;/* Track how many pages are held 'in-flight' */pool->pages_state_hold_cnt++;
@@ -341,6 +345,8 @@ void page_pool_release_page(struct page_pool *pool, struct page *page)DMA_ATTR_SKIP_CPU_SYNC);page_pool_set_dma_addr(page,0);skip_dma_unmap:+page->pp_magic=0;+/* This may be the last page returned, releasing the pool, so*itisnotsafetoreferencepoolafterwards.*/
From: Matteo Croce <redacted>
This is a prerequisite patch, the next one is enabling recycling of
skbs and fragments. Add an extra argument on __skb_frag_unref() to
handle recycling, and update the current users of the function with that.
Signed-off-by: Matteo Croce <redacted>
---
drivers/net/ethernet/marvell/sky2.c | 2 +-
drivers/net/ethernet/mellanox/mlx4/en_rx.c | 2 +-
include/linux/skbuff.h | 8 +++++---
net/core/skbuff.c | 4 ++--
net/tls/tls_device.c | 2 +-
5 files changed, 10 insertions(+), 8 deletions(-)
@@ -3495,7 +3495,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)fragto=&skb_shinfo(tgt)->frags[merge];skb_frag_size_add(fragto,skb_frag_size(fragfrom));-__skb_frag_unref(fragfrom);+__skb_frag_unref(fragfrom,false);}/* Reposition in the original skb */
From: Matteo Croce <redacted>
Use the new recycling API for page_pool.
In a drop rate test, the packet rate increased by 10%,
from 296 Kpps to 326 Kpps.
perf top on a stock system shows:
Overhead Shared Object Symbol
23.66% [kernel] [k] __pi___inval_dcache_area
22.85% [mvneta] [k] mvneta_rx_swbm
7.54% [kernel] [k] kmem_cache_alloc
6.49% [kernel] [k] eth_type_trans
3.94% [kernel] [k] dev_gro_receive
3.91% [kernel] [k] __netif_receive_skb_core
3.91% [kernel] [k] kmem_cache_free
3.76% [kernel] [k] page_pool_release_page
3.56% [kernel] [k] free_unref_page
2.40% [kernel] [k] build_skb
1.49% [kernel] [k] skb_release_data
1.45% [kernel] [k] __alloc_pages_bulk
1.30% [kernel] [k] page_frag_free
And this is the same output with recycling enabled:
Overhead Shared Object Symbol
26.41% [kernel] [k] __pi___inval_dcache_area
25.00% [mvneta] [k] mvneta_rx_swbm
8.14% [kernel] [k] kmem_cache_alloc
6.84% [kernel] [k] eth_type_trans
4.44% [kernel] [k] __netif_receive_skb_core
4.38% [kernel] [k] kmem_cache_free
4.16% [kernel] [k] dev_gro_receive
3.21% [kernel] [k] page_pool_put_page
2.41% [kernel] [k] build_skb
1.82% [kernel] [k] skb_release_data
1.61% [kernel] [k] napi_gro_receive
1.25% [kernel] [k] page_pool_refill_alloc_cache
1.16% [kernel] [k] __netif_receive_skb_list_core
We can see that page_pool_release_page(), free_unref_page() and
__alloc_pages_bulk() are no longer on top of the list when receiving
traffic.
The test was done with mausezahn on the TX side with 64 byte raw
ethernet frames.
Signed-off-by: Matteo Croce <redacted>
---
drivers/net/ethernet/marvell/mvneta.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
From: Matthew Wilcox <willy@infradead.org> Date: 2021-06-04 18:58:28
On Fri, Jun 04, 2021 at 08:33:44PM +0200, Matteo Croce wrote:
Please let this going in on a future -rc1 so to allow enough time
to have wider tests.
Note that this series depends on the change "mm: fix struct page layout
on 32-bit systems"[2] which is not yet in master.
From: Matthew Wilcox <willy@infradead.org> Date: 2021-06-04 19:08:31
On Fri, Jun 04, 2021 at 08:33:45PM +0200, Matteo Croce wrote:
quoted hunk
@@ -130,7 +137,10 @@ struct page { }; }; struct { /* Tail pages of compound page */- unsigned long compound_head; /* Bit zero is set */+ /* Bit zero is set+ * Bit one if pfmemalloc page+ */+ unsigned long compound_head;
I would drop this hunk. Bit 1 is not used for this purpose in tail
pages; it's used for that purpose in head and base pages.
I suppose we could do something like ...
static inline void set_page_pfmemalloc(struct page *page)
{
- page->index = -1UL;
+ page->lru.next = (void *)2;
}
if it's causing confusion.
It feels weird to have a page here, convert it back to an address,
then convert it back to a head page in page_pool_return_skb_page().
How about passing 'page' here, calling compound_head() in
page_pool_return_skb_page() and calling virt_to_page() in skb_free_head()?
quoted hunk
@@ -251,4 +253,11 @@ static inline void page_pool_ring_unlock(struct page_pool *pool) spin_unlock_bh(&pool->ring.producer_lock); }+/* Store mem_info on struct page and use it while recycling skb frags */+static inline+void page_pool_store_mem_info(struct page *page, struct page_pool *pp)+{+ page->pp = pp;
@@ -628,3 +628,26 @@ void page_pool_update_nid(struct page_pool *pool, int new_nid)}}EXPORT_SYMBOL(page_pool_update_nid);++boolpage_pool_return_skb_page(void*data)+{+structpage_pool*pp;+structpage*page;++page=virt_to_head_page(data);+if(unlikely(page->pp_magic!=PP_SIGNATURE))+returnfalse;++pp=(structpage_pool*)page->pp;
You don't need the cast any more.
quoted hunk
+ /* Driver set this to memory recycling info. Reset it on recycle.
+ * This will *not* work for NIC using a split-page memory model.
+ * The page will be returned to the pool here regardless of the
+ * 'flipped' fragment being in use or not.
+ */
+ page->pp = NULL;
+ page_pool_put_full_page(pp, page, false);
+
+ return true;
+}
+EXPORT_SYMBOL(page_pool_return_skb_page);
put this in a header file:
static inline bool skb_pp_recycle(struct sk_buff *skb, void *data)
{
if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
return false;
return page_pool_return_skb_page(virt_to_page(data));
}
then this becomes:
if (skb->head_frag) {
if (skb_pp_recycle(skb, head))
return;
Does this driver only use order-0 pages? Should it be using
virt_to_head_page() here? or should skb_mark_for_recycle() call
compound_head() internally?
On Fri, Jun 4, 2021 at 9:08 PM Matthew Wilcox [off-list ref] wrote:
On Fri, Jun 04, 2021 at 08:33:45PM +0200, Matteo Croce wrote:
quoted
@@ -130,7 +137,10 @@ struct page { }; }; struct { /* Tail pages of compound page */- unsigned long compound_head; /* Bit zero is set */+ /* Bit zero is set+ * Bit one if pfmemalloc page+ */+ unsigned long compound_head;
I would drop this hunk. Bit 1 is not used for this purpose in tail
pages; it's used for that purpose in head and base pages.
I suppose we could do something like ...
static inline void set_page_pfmemalloc(struct page *page)
{
- page->index = -1UL;
+ page->lru.next = (void *)2;
}
if it's causing confusion.
If you prefer, ok for me.
Why not "(void *)BIT(1)"? Just to remark that it's a single bit and
not a magic like value?
--
per aspera ad upstream
From: Matthew Wilcox <willy@infradead.org> Date: 2021-06-05 14:32:45
On Sat, Jun 05, 2021 at 12:59:50AM +0200, Matteo Croce wrote:
On Fri, Jun 4, 2021 at 9:08 PM Matthew Wilcox [off-list ref] wrote:
quoted
On Fri, Jun 04, 2021 at 08:33:45PM +0200, Matteo Croce wrote:
quoted
@@ -130,7 +137,10 @@ struct page { }; }; struct { /* Tail pages of compound page */- unsigned long compound_head; /* Bit zero is set */+ /* Bit zero is set+ * Bit one if pfmemalloc page+ */+ unsigned long compound_head;
I would drop this hunk. Bit 1 is not used for this purpose in tail
pages; it's used for that purpose in head and base pages.
I suppose we could do something like ...
static inline void set_page_pfmemalloc(struct page *page)
{
- page->index = -1UL;
+ page->lru.next = (void *)2;
}
if it's causing confusion.
If you prefer, ok for me.
Why not "(void *)BIT(1)"? Just to remark that it's a single bit and
not a magic like value?
I don't have a strong preference. I'd use '2', but I wouldn't ask
BIT(1) to be changed.
On Sat, Jun 5, 2021 at 4:32 PM Matthew Wilcox [off-list ref] wrote:
On Sat, Jun 05, 2021 at 12:59:50AM +0200, Matteo Croce wrote:
quoted
On Fri, Jun 4, 2021 at 9:08 PM Matthew Wilcox [off-list ref] wrote:
quoted
On Fri, Jun 04, 2021 at 08:33:45PM +0200, Matteo Croce wrote:
quoted
@@ -130,7 +137,10 @@ struct page { }; }; struct { /* Tail pages of compound page */- unsigned long compound_head; /* Bit zero is set */+ /* Bit zero is set+ * Bit one if pfmemalloc page+ */+ unsigned long compound_head;
I would drop this hunk. Bit 1 is not used for this purpose in tail
pages; it's used for that purpose in head and base pages.
I suppose we could do something like ...
static inline void set_page_pfmemalloc(struct page *page)
{
- page->index = -1UL;
+ page->lru.next = (void *)2;
}
if it's causing confusion.
And change all the *_pfmemalloc functions to use page->lru.next like this?
static inline bool page_is_pfmemalloc(const struct page *page)
{
/*
- * Page index cannot be this large so this must be
- * a pfmemalloc page.
+ * This is not a tail page; compound_head of a head page is unused
+ * at return from the page allocator, and will be overwritten
+ * by callers who do not care whether the page came from the
+ * reserves.
*/
- return page->index == -1UL;
+ return (uintptr_t)page->lru.next & BIT(1);
}
/*
Does this driver only use order-0 pages? Should it be using
virt_to_head_page() here? or should skb_mark_for_recycle() call
compound_head() internally?
This driver uses only order-0 pages.
--
per aspera ad upstream
It feels weird to have a page here, convert it back to an address,
then convert it back to a head page in page_pool_return_skb_page().
How about passing 'page' here, calling compound_head() in
page_pool_return_skb_page() and calling virt_to_page() in skb_free_head()?
Sure, sounds reasonable.
quoted
@@ -251,4 +253,11 @@ static inline void page_pool_ring_unlock(struct page_pool *pool) spin_unlock_bh(&pool->ring.producer_lock); }+/* Store mem_info on struct page and use it while recycling skb frags */+static inline+void page_pool_store_mem_info(struct page *page, struct page_pool *pp)+{+ page->pp = pp;
@@ -628,3 +628,26 @@ void page_pool_update_nid(struct page_pool *pool, int new_nid)}}EXPORT_SYMBOL(page_pool_update_nid);++boolpage_pool_return_skb_page(void*data)+{+structpage_pool*pp;+structpage*page;++page=virt_to_head_page(data);+if(unlikely(page->pp_magic!=PP_SIGNATURE))+returnfalse;++pp=(structpage_pool*)page->pp;
You don't need the cast any more.
True
quoted
+ /* Driver set this to memory recycling info. Reset it on recycle.
+ * This will *not* work for NIC using a split-page memory model.
+ * The page will be returned to the pool here regardless of the
+ * 'flipped' fragment being in use or not.
+ */
+ page->pp = NULL;
+ page_pool_put_full_page(pp, page, false);
+
+ return true;
+}
+EXPORT_SYMBOL(page_pool_return_skb_page);
put this in a header file:
static inline bool skb_pp_recycle(struct sk_buff *skb, void *data)
{
if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
return false;
return page_pool_return_skb_page(virt_to_page(data));
}
then this becomes:
if (skb->head_frag) {
if (skb_pp_recycle(skb, head))
return;
static inline bool page_is_pfmemalloc(const struct page *page)
{
/*
- * Page index cannot be this large so this must be
- * a pfmemalloc page.
+ * This is not a tail page; compound_head of a head page is unused
+ * at return from the page allocator, and will be overwritten
+ * by callers who do not care whether the page came from the
+ * reserves.
*/
The comment doesn't make a lot of sense if we're switching to use
lru.next. How about:
/*
* lru.next has bit 1 set if the page is allocated from the
* pfmemalloc reserves. Callers may simply overwrite it if
* they do not need to preserve that information.
*/
static inline bool page_is_pfmemalloc(const struct page *page)
{
/*
- * Page index cannot be this large so this must be
- * a pfmemalloc page.
+ * This is not a tail page; compound_head of a head page is unused
+ * at return from the page allocator, and will be overwritten
+ * by callers who do not care whether the page came from the
+ * reserves.
*/
The comment doesn't make a lot of sense if we're switching to use
lru.next. How about:
/*
* lru.next has bit 1 set if the page is allocated from the
* pfmemalloc reserves. Callers may simply overwrite it if
* they do not need to preserve that information.
*/
It feels weird to have a page here, convert it back to an address,
then convert it back to a head page in page_pool_return_skb_page().
How about passing 'page' here, calling compound_head() in
page_pool_return_skb_page() and calling virt_to_page() in skb_free_head()?
I like it.
quoted
@@ -251,4 +253,11 @@ static inline void page_pool_ring_unlock(struct page_pool *pool) spin_unlock_bh(&pool->ring.producer_lock); }+/* Store mem_info on struct page and use it while recycling skb frags */+static inline+void page_pool_store_mem_info(struct page *page, struct page_pool *pp)+{+ page->pp = pp;
I'm not sure this wrapper needs to exist.
I admit that this wrapper was bigger in the previous versions, but
it's used by drivers which handle skb fragments (e.g. mvneta) to set
the pointer for each frag.
We can open code it, but it will be less straightforward.
@@ -628,3 +628,26 @@ void page_pool_update_nid(struct page_pool *pool, int new_nid)}}EXPORT_SYMBOL(page_pool_update_nid);++boolpage_pool_return_skb_page(void*data)+{+structpage_pool*pp;+structpage*page;++page=virt_to_head_page(data);+if(unlikely(page->pp_magic!=PP_SIGNATURE))+returnfalse;++pp=(structpage_pool*)page->pp;
You don't need the cast any more.
Right.
quoted
+ /* Driver set this to memory recycling info. Reset it on recycle.
+ * This will *not* work for NIC using a split-page memory model.
+ * The page will be returned to the pool here regardless of the
+ * 'flipped' fragment being in use or not.
+ */
+ page->pp = NULL;
+ page_pool_put_full_page(pp, page, false);
+
+ return true;
+}
+EXPORT_SYMBOL(page_pool_return_skb_page);
put this in a header file:
static inline bool skb_pp_recycle(struct sk_buff *skb, void *data)
{
if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
return false;
return page_pool_return_skb_page(virt_to_page(data));
}
then this becomes:
if (skb->head_frag) {
if (skb_pp_recycle(skb, head))
return;