diff mbox series

[net-next] page_pool: add a test module for page_pool

Message ID 20240909091913.987826-1-linyunsheng@huawei.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net-next] page_pool: add a test module for page_pool | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 16 this patch: 16
netdev/build_tools fail Errors and warnings before: 0 this patch: 7
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 17 this patch: 17
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api fail Found: 'module_param' was: 0 now: 8
netdev/check_selftest success net selftest script(s) already in Makefile
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 22 this patch: 22
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 82 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Yunsheng Lin Sept. 9, 2024, 9:19 a.m. UTC
The testing is done by ensuring that the page allocated from
the page_pool instance is pushed into a ptr_ring instance in
a kthread/napi binded to a specified cpu, and a kthread/napi
binded to a specified cpu will pop the page from the ptr_ring
and free it back to the page_pool.

Signed-off-by: Yunsheng Lin <linyunsheng@huawei.com>
---
 tools/testing/selftests/net/Makefile          |   3 +
 .../testing/selftests/net/page_pool/Makefile  |  18 +
 .../selftests/net/page_pool/page_pool_test.c  | 433 ++++++++++++++++++
 tools/testing/selftests/net/test_page_pool.sh | 175 +++++++
 4 files changed, 629 insertions(+)
 create mode 100644 tools/testing/selftests/net/page_pool/Makefile
 create mode 100644 tools/testing/selftests/net/page_pool/page_pool_test.c
 create mode 100755 tools/testing/selftests/net/test_page_pool.sh

Comments

Mina Almasry Sept. 9, 2024, 5:28 p.m. UTC | #1
On Mon, Sep 9, 2024 at 2:25 AM Yunsheng Lin <linyunsheng@huawei.com> wrote:
>
> The testing is done by ensuring that the page allocated from
> the page_pool instance is pushed into a ptr_ring instance in
> a kthread/napi binded to a specified cpu, and a kthread/napi
> binded to a specified cpu will pop the page from the ptr_ring
> and free it back to the page_pool.
>
> Signed-off-by: Yunsheng Lin <linyunsheng@huawei.com>

It seems this test is has a correctness part and a performance part.
For the performance test, Jesper has out of tree tests for the
page_pool:
https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/bench_page_pool_simple.c

I have these rebased on top of net-next and use them to verify devmem
& memory-provider performance:
https://github.com/mina/linux/commit/07fd1c04591395d15d83c07298b4d37f6b56157f

My preference here (for the performance part) is to upstream the
out-of-tree tests that Jesper (and probably others) are using, rather
than adding a new performance test that is not as battle-hardened.

--
Thanks,
Mina
Yunsheng Lin Sept. 10, 2024, 10:46 a.m. UTC | #2
On 2024/9/10 1:28, Mina Almasry wrote:
> On Mon, Sep 9, 2024 at 2:25 AM Yunsheng Lin <linyunsheng@huawei.com> wrote:
>>
>> The testing is done by ensuring that the page allocated from
>> the page_pool instance is pushed into a ptr_ring instance in
>> a kthread/napi binded to a specified cpu, and a kthread/napi
>> binded to a specified cpu will pop the page from the ptr_ring
>> and free it back to the page_pool.
>>
>> Signed-off-by: Yunsheng Lin <linyunsheng@huawei.com>
> 
> It seems this test is has a correctness part and a performance part.
> For the performance test, Jesper has out of tree tests for the
> page_pool:
> https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/bench_page_pool_simple.c
> 
> I have these rebased on top of net-next and use them to verify devmem
> & memory-provider performance:
> https://github.com/mina/linux/commit/07fd1c04591395d15d83c07298b4d37f6b56157f

Yes, I used that testing ko too when adding frag API support for
page_pool.

The main issue I remembered was that it only support x86:(

> 
> My preference here (for the performance part) is to upstream the
> out-of-tree tests that Jesper (and probably others) are using, rather
> than adding a new performance test that is not as battle-hardened.

I looked through the out-of-tree tests again, it seems we can take the
best of them.
For Jesper' ko:
It seems we can do prefill as something that pp_fill_ptr_ring() does
in bench_page_pool_simple.c to avoid the noise from the page allocator.


For the ko in this patch:
It uses NAPI instead of tasklet mimicking the NAPI context, support
PP_FLAG_DMA_MAP flag testing, and return '-EAGAIN' in module_init()
to use perf stat for collecting and calculating performance data.

Is there other testcase or better practicing that we can learn from
Jesper' out of tree ko?

> 
> --
> Thanks,
> Mina
>
Jesper Dangaard Brouer Sept. 10, 2024, 11:27 a.m. UTC | #3
On 10/09/2024 12.46, Yunsheng Lin wrote:
> On 2024/9/10 1:28, Mina Almasry wrote:
>> On Mon, Sep 9, 2024 at 2:25 AM Yunsheng Lin <linyunsheng@huawei.com> wrote:
>>>
>>> The testing is done by ensuring that the page allocated from
>>> the page_pool instance is pushed into a ptr_ring instance in
>>> a kthread/napi binded to a specified cpu, and a kthread/napi
>>> binded to a specified cpu will pop the page from the ptr_ring
>>> and free it back to the page_pool.
>>>
>>> Signed-off-by: Yunsheng Lin <linyunsheng@huawei.com>
>>
>> It seems this test is has a correctness part and a performance part.
>> For the performance test, Jesper has out of tree tests for the
>> page_pool:
>> https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/bench_page_pool_simple.c
>>
>> I have these rebased on top of net-next and use them to verify devmem
>> & memory-provider performance:
>> https://github.com/mina/linux/commit/07fd1c04591395d15d83c07298b4d37f6b56157f
> 
> Yes, I used that testing ko too when adding frag API support for
> page_pool.
> 
> The main issue I remembered was that it only support x86:(
> 

Yes, because I've added ASM code for reading TSC counter in a very
precise manor. Given we run many iterations, then I don't think we
need this precise reading.  I guess it can simply be replaced with
get_cycles() or get_cycles64().  Then it should work on all archs.

The code already supports wall-clock time via ktime_get() (specifically
ktime_get_real_ts64()).


>>
>> My preference here (for the performance part) is to upstream the
>> out-of-tree tests that Jesper (and probably others) are using, rather
>> than adding a new performance test that is not as battle-hardened.
> 
> I looked through the out-of-tree tests again, it seems we can take the
> best of them.
> For Jesper' ko:
> It seems we can do prefill as something that pp_fill_ptr_ring() does
> in bench_page_pool_simple.c to avoid the noise from the page allocator.
> 
> 
> For the ko in this patch:
> It uses NAPI instead of tasklet mimicking the NAPI context, support
> PP_FLAG_DMA_MAP flag testing, and return '-EAGAIN' in module_init()
> to use perf stat for collecting and calculating performance data.
> 
My bench don't return minus-number on module load, because I used perf
record, and to see symbols decoded with perf report, I needed the module
to be loaded.

I started on reading the PMU counters[1] around the bench loop, it works
if enabling PMU counters yourself/manually, but I never finished that work.

  [1] 
https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/include/linux/time_bench.h#L195-L209


> Is there other testcase or better practicing that we can learn from
> Jesper' out of tree ko?
> 

I created a time_bench.c [2] module that other modules [3] can use to
easier reuse the benchmarking code in other modules.

  [2] 
https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/time_bench.c

  [3] 
https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/bench_page_pool_simple.c

--Jesper
Yunsheng Lin Sept. 11, 2024, 11:32 a.m. UTC | #4
On 2024/9/10 19:27, Jesper Dangaard Brouer wrote:
...

>>
>> The main issue I remembered was that it only support x86:(
>>
> 
> Yes, because I've added ASM code for reading TSC counter in a very
> precise manor. Given we run many iterations, then I don't think we
> need this precise reading.  I guess it can simply be replaced with
> get_cycles() or get_cycles64().  Then it should work on all archs.

Agreed.

> 
> The code already supports wall-clock time via ktime_get() (specifically
> ktime_get_real_ts64()).
> 
> 
>>>
>>> My preference here (for the performance part) is to upstream the
>>> out-of-tree tests that Jesper (and probably others) are using, rather
>>> than adding a new performance test that is not as battle-hardened.
>>
>> I looked through the out-of-tree tests again, it seems we can take the
>> best of them.
>> For Jesper' ko:
>> It seems we can do prefill as something that pp_fill_ptr_ring() does
>> in bench_page_pool_simple.c to avoid the noise from the page allocator.
>>
>>
>> For the ko in this patch:
>> It uses NAPI instead of tasklet mimicking the NAPI context, support
>> PP_FLAG_DMA_MAP flag testing, and return '-EAGAIN' in module_init()
>> to use perf stat for collecting and calculating performance data.
>>
> My bench don't return minus-number on module load, because I used perf
> record, and to see symbols decoded with perf report, I needed the module
> to be loaded.
> 
> I started on reading the PMU counters[1] around the bench loop, it works
> if enabling PMU counters yourself/manually, but I never finished that work.
> 
>  [1] https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/include/linux/time_bench.h#L195-L209
> 
> 
>> Is there other testcase or better practicing that we can learn from
>> Jesper' out of tree ko?
>>
> 
> I created a time_bench.c [2] module that other modules [3] can use to
> easier reuse the benchmarking code in other modules.
> 
>  [2] https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/time_bench.c
> 
>  [3] https://github.com/netoptimizer/prototype-kernel/blob/master/kernel/lib/bench_page_pool_simple.c

Will take a look at it, thanks.

> 
> --Jesper
diff mbox series

Patch

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 27362e40eb37..4d4ddd853ef8 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -6,6 +6,8 @@  CFLAGS += -I../../../../usr/include/ $(KHDR_INCLUDES)
 # Additional include paths needed by kselftest.h
 CFLAGS += -I../
 
+TEST_GEN_MODS_DIR := page_pool
+
 TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh \
 	      rtnetlink.sh xfrm_policy.sh test_blackhole_dev.sh
 TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
@@ -96,6 +98,7 @@  TEST_PROGS += fdb_flush.sh
 TEST_PROGS += fq_band_pktlimit.sh
 TEST_PROGS += vlan_hw_filter.sh
 TEST_PROGS += bpf_offload.py
+TEST_PROGS += test_page_pool.sh
 
 TEST_FILES := settings
 TEST_FILES += in_netns.sh lib.sh net_helper.sh setup_loopback.sh setup_veth.sh
diff --git a/tools/testing/selftests/net/page_pool/Makefile b/tools/testing/selftests/net/page_pool/Makefile
new file mode 100644
index 000000000000..4380a70d6391
--- /dev/null
+++ b/tools/testing/selftests/net/page_pool/Makefile
@@ -0,0 +1,18 @@ 
+PAGE_POOL_TEST_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= $(abspath $(PAGE_POOL_TEST_DIR)/../../../../..)
+
+ifeq ($(V),1)
+Q =
+else
+Q = @
+endif
+
+MODULES = page_pool_test.ko
+
+obj-m += page_pool_test.o
+
+all:
+	+$(Q)make -C $(KDIR) M=$(PAGE_POOL_TEST_DIR) modules
+
+clean:
+	+$(Q)make -C $(KDIR) M=$(PAGE_POOL_TEST_DIR) clean
diff --git a/tools/testing/selftests/net/page_pool/page_pool_test.c b/tools/testing/selftests/net/page_pool/page_pool_test.c
new file mode 100644
index 000000000000..475b64f21b78
--- /dev/null
+++ b/tools/testing/selftests/net/page_pool/page_pool_test.c
@@ -0,0 +1,433 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Test module for page_pool
+ *
+ * Copyright (C) 2024 Yunsheng Lin <linyunsheng@huawei.com>
+ */
+
+#include <linux/module.h>
+#include <linux/cpumask.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/ptr_ring.h>
+#include <linux/kthread.h>
+#include <net/page_pool/helpers.h>
+
+static struct ptr_ring ptr_ring;
+static int nr_objs = 512;
+static atomic_t nthreads;
+static struct completion wait;
+static struct page_pool *test_pool;
+static struct device *dev;
+static u64 dma_mask = DMA_BIT_MASK(64);
+
+static int nr_test = 2000000;
+module_param(nr_test, int, 0);
+MODULE_PARM_DESC(nr_test, "number of iterations to test");
+
+static bool test_frag;
+module_param(test_frag, bool, 0);
+MODULE_PARM_DESC(test_frag, "use frag API for testing");
+
+static bool test_dma;
+module_param(test_dma, bool, 0);
+MODULE_PARM_DESC(test_dma, "enable dma mapping for testing");
+
+static bool test_napi;
+module_param(test_napi, bool, 0);
+MODULE_PARM_DESC(test_napi, "use NAPI softirq for testing");
+
+static bool test_direct;
+module_param(test_direct, bool, 0);
+MODULE_PARM_DESC(test_direct, "enable direct recycle for testing");
+
+static int test_alloc_len = 2048;
+module_param(test_alloc_len, int, 0);
+MODULE_PARM_DESC(test_alloc_len, "alloc len for testing");
+
+static int test_push_cpu;
+module_param(test_push_cpu, int, 0);
+MODULE_PARM_DESC(test_push_cpu, "test cpu for pushing page");
+
+static int test_pop_cpu;
+module_param(test_pop_cpu, int, 0);
+MODULE_PARM_DESC(test_pop_cpu, "test cpu for popping page");
+
+static void page_pool_test_dev_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+static struct page_pool *page_pool_test_create(void)
+{
+	struct page_pool_params page_pool_params = {
+		.pool_size = nr_objs,
+		.flags = 0,
+		.nid = cpu_to_mem(test_push_cpu),
+	};
+	int ret;
+
+	if (test_dma) {
+		dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+		if (!dev)
+			return ERR_PTR(-ENOMEM);
+
+		dev->release = page_pool_test_dev_release;
+		dev->dma_mask = &dma_mask;
+		device_initialize(dev);
+
+		ret = dev_set_name(dev, "page_pool_dev");
+		if (ret) {
+			pr_err("page_pool_test dev_set_name() failed: %d\n",
+			       ret);
+			goto err_out;
+		}
+
+		ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+		if (ret) {
+			pr_err("page_pool_test set dma mask failed: %d\n",
+			       ret);
+			goto err_out;
+		}
+
+		ret = device_add(dev);
+		if (ret) {
+			pr_err("page_pool_test device_add() failed: %d\n", ret);
+			goto err_out;
+		}
+
+		page_pool_params.dev = dev;
+		page_pool_params.flags |= PP_FLAG_DMA_MAP;
+		page_pool_params.dma_dir = DMA_FROM_DEVICE;
+	}
+
+	return page_pool_create(&page_pool_params);
+err_out:
+	put_device(dev);
+	return ERR_PTR(ret);
+}
+
+static void page_pool_test_destroy(struct page_pool *pool)
+{
+	page_pool_destroy(pool);
+
+	if (test_dma) {
+		device_del(dev);
+		put_device(dev);
+	}
+}
+
+static int test_pushed;
+static int test_popped;
+static int page_pool_pop_thread(void *arg)
+{
+	struct ptr_ring *ring = arg;
+
+	pr_info("page_pool pop test thread begins on cpu %d\n",
+		smp_processor_id());
+
+	while (test_popped < nr_test) {
+		void *obj = __ptr_ring_consume(ring);
+
+		if (obj) {
+			test_popped++;
+			page_pool_put_full_page(test_pool, obj, false);
+		} else {
+			cond_resched();
+		}
+	}
+
+	if (atomic_dec_and_test(&nthreads))
+		complete(&wait);
+
+	pr_info("page_pool pop test thread exits on cpu %d\n",
+		smp_processor_id());
+
+	return 0;
+}
+
+static int page_pool_push_thread(void *arg)
+{
+	struct ptr_ring *ring = arg;
+
+	pr_info("page_pool push test thread begins on cpu %d\n",
+		smp_processor_id());
+
+	while (test_pushed < nr_test) {
+		struct page *page;
+		int ret;
+
+		if (test_frag) {
+			unsigned int offset;
+
+			page = page_pool_dev_alloc_frag(test_pool, &offset,
+							test_alloc_len);
+		} else {
+			page = page_pool_dev_alloc_pages(test_pool);
+		}
+
+		if (!page)
+			continue;
+
+		ret = __ptr_ring_produce(ring, page);
+		if (ret) {
+			page_pool_put_full_page(test_pool, page, true);
+			cond_resched();
+		} else {
+			test_pushed++;
+		}
+	}
+
+	pr_info("page_pool push test thread exits on cpu %d\n",
+		smp_processor_id());
+
+	if (atomic_dec_and_test(&nthreads))
+		complete(&wait);
+
+	return 0;
+}
+
+static int page_pool_push_poll(struct napi_struct *napi, int budget)
+{
+	static bool print = true;
+	int processed = 0;
+
+	if (unlikely(print)) {
+		pr_info("page_pool push test napi begins on cpu %d\n",
+			smp_processor_id());
+		print = false;
+	}
+
+	while (processed < budget && test_pushed < nr_test) {
+		struct page *page;
+		int ret;
+
+		if (test_frag) {
+			unsigned int offset;
+
+			page = page_pool_dev_alloc_frag(test_pool, &offset,
+							test_alloc_len);
+		} else {
+			page = page_pool_dev_alloc_pages(test_pool);
+		}
+
+		if (!page)
+			return budget;
+
+		ret = __ptr_ring_produce(&ptr_ring, page);
+		if (ret) {
+			page_pool_put_full_page(test_pool, page, true);
+			return budget;
+		}
+
+		processed++;
+		test_pushed++;
+	}
+
+	if (test_pushed < nr_test)
+		return budget;
+
+	pr_info("page_pool push test napi exits on cpu %d\n",
+		smp_processor_id());
+
+	napi_complete(napi);
+	if (atomic_dec_and_test(&nthreads))
+		complete(&wait);
+
+	return 0;
+}
+
+static int page_pool_pop_poll(struct napi_struct *napi, int budget)
+{
+	static bool print = true;
+	int processed = 0;
+
+	if (unlikely(print)) {
+		pr_info("page_pool pop test napi begins on cpu %d\n",
+			smp_processor_id());
+		print = false;
+	}
+
+	while (processed < budget && test_popped < nr_test) {
+		void *obj = __ptr_ring_consume(&ptr_ring);
+
+		if (obj) {
+			processed++;
+			test_popped++;
+			page_pool_put_full_page(test_pool, obj, test_direct);
+		} else {
+			return budget;
+		}
+	}
+
+	if (test_popped < nr_test)
+		return budget;
+
+	if (atomic_dec_and_test(&nthreads))
+		complete(&wait);
+
+	napi_complete(napi);
+	pr_info("page_pool pop test napi exits on cpu %d\n",
+		smp_processor_id());
+
+	return 0;
+}
+
+static int page_pool_create_test_thread(void)
+{
+	struct task_struct *tsk_push, *tsk_pop;
+
+	tsk_push = kthread_create_on_cpu(page_pool_push_thread, &ptr_ring,
+					 test_push_cpu, "page_pool_push");
+	if (IS_ERR(tsk_push))
+		return PTR_ERR(tsk_push);
+
+	tsk_pop = kthread_create_on_cpu(page_pool_pop_thread, &ptr_ring,
+					test_pop_cpu, "page_pool_pop");
+	if (IS_ERR(tsk_pop)) {
+		kthread_stop(tsk_push);
+		return PTR_ERR(tsk_pop);
+	}
+
+	wake_up_process(tsk_push);
+	wake_up_process(tsk_pop);
+
+	return 0;
+}
+
+static struct napi_struct *pop_napi, *push_napi;
+static struct net_device *netdev;
+static int page_pool_schedule_napi(void *arg)
+{
+	struct napi_struct *napi = arg;
+
+	napi_schedule_irqoff(napi);
+
+	return 0;
+}
+
+static int page_pool_create_test_napi(void)
+{
+	struct task_struct *push_tsk, *pop_tsk;
+	int ret;
+
+	netdev = alloc_etherdev(sizeof(struct napi_struct) * 2);
+	if (!netdev)
+		return -ENOMEM;
+
+	pop_napi = netdev_priv(netdev);
+	push_napi = pop_napi + 1;
+
+	netif_napi_add(netdev, push_napi, page_pool_push_poll);
+	netif_napi_add(netdev, pop_napi, page_pool_pop_poll);
+
+	napi_enable(push_napi);
+	napi_enable(pop_napi);
+
+	push_tsk = kthread_create_on_cpu(page_pool_schedule_napi, push_napi,
+					 test_push_cpu, "page_pool_push_napi");
+	if (IS_ERR(push_tsk)) {
+		ret = PTR_ERR(push_tsk);
+		goto err_alloc_etherdev;
+	}
+
+	pop_tsk = kthread_create_on_cpu(page_pool_schedule_napi, pop_napi,
+					test_pop_cpu, "page_pool_pop_napi");
+	if (IS_ERR(pop_tsk)) {
+		ret = PTR_ERR(pop_tsk);
+		goto err_push_thread;
+	}
+
+	wake_up_process(push_tsk);
+	wake_up_process(pop_tsk);
+	return 0;
+
+err_push_thread:
+	kthread_stop(push_tsk);
+err_alloc_etherdev:
+	free_netdev(netdev);
+	return ret;
+}
+
+static void page_pool_destroy_test_napi(void)
+{
+	napi_disable(push_napi);
+	napi_disable(pop_napi);
+
+	netif_napi_del(push_napi);
+	netif_napi_del(pop_napi);
+
+	free_netdev(netdev);
+}
+
+static int __init page_pool_test_init(void)
+{
+	ktime_t start;
+	u64 duration;
+	int ret;
+
+	if (test_alloc_len > PAGE_SIZE || test_alloc_len <= 0 ||
+	    !cpu_active(test_push_cpu) || !cpu_active(test_pop_cpu) ||
+	    (test_direct && (test_push_cpu != test_pop_cpu || !test_napi)))
+		return -EINVAL;
+
+	ret = ptr_ring_init(&ptr_ring, nr_objs, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	test_pool = page_pool_test_create();
+	if (IS_ERR(test_pool)) {
+		ret = PTR_ERR(test_pool);
+		goto err_ptr_ring_init;
+	}
+
+	atomic_set(&nthreads, 2);
+	init_completion(&wait);
+
+	if (test_napi)
+		ret = page_pool_create_test_napi();
+	else
+		ret = page_pool_create_test_thread();
+	if (ret)
+		goto err_pool_create;
+
+	start = ktime_get();
+	pr_info("waiting for test to complete\n");
+
+	while (!wait_for_completion_timeout(&wait, msecs_to_jiffies(20000)))
+		pr_info("page_pool_test progress: pushed = %d, popped = %d\n",
+			test_pushed, test_popped);
+
+	duration = (u64)ktime_us_delta(ktime_get(), start);
+	pr_info("%d of iterations for %s%s%s%s testing took: %lluus\n",
+		nr_test, test_napi ? "napi" : "thread",
+		test_direct ? " direct" : "", test_dma ? " dma" : "",
+		test_frag ? " frag" : "", duration);
+
+	ptr_ring_cleanup(&ptr_ring, NULL);
+	page_pool_test_destroy(test_pool);
+
+	if (test_napi)
+		page_pool_destroy_test_napi();
+
+	return -EAGAIN;
+
+err_pool_create:
+	page_pool_test_destroy(test_pool);
+err_ptr_ring_init:
+	ptr_ring_cleanup(&ptr_ring, NULL);
+	return ret;
+}
+
+static void __exit page_pool_test_exit(void)
+{
+}
+
+module_init(page_pool_test_init);
+module_exit(page_pool_test_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yunsheng Lin <linyunsheng@huawei.com>");
+MODULE_DESCRIPTION("Test module for page_pool");
diff --git a/tools/testing/selftests/net/test_page_pool.sh b/tools/testing/selftests/net/test_page_pool.sh
new file mode 100755
index 000000000000..b9b422f5449d
--- /dev/null
+++ b/tools/testing/selftests/net/test_page_pool.sh
@@ -0,0 +1,175 @@ 
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2024 Yunsheng Lin <linyunsheng@huawei.com>
+# Copyright (C) 2018 Uladzislau Rezki (Sony) <urezki@gmail.com>
+#
+# This is a test script for the kernel test driver to test the
+# correctness and performance of page_pool's implementation.
+# Therefore it is just a kernel module loader. You can specify
+# and pass different parameters in order to:
+#     a) analyse performance of page_pool;
+#     b) stressing and stability check of page_pool subsystem.
+
+DRIVER="./page_pool/page_pool_test.ko"
+CPU_LIST=$(grep -m 2 processor /proc/cpuinfo | cut -d ' ' -f 2)
+CPU_CNT=$(echo $CPU_LIST | wc -w)
+TEST_CPU_0=$(echo $CPU_LIST | awk '{print $1}')
+
+if [ $CPU_CNT -gt 1 ]; then
+	TEST_CPU_1=$(echo $CPU_LIST | awk '{print $2}')
+	NR_TEST=100000000
+else
+	TEST_CPU_1=$TEST_CPU_0
+	NR_TEST=1000000
+fi
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+#
+# Static templates for testing of page_pool APIs.
+# Also it is possible to pass any supported parameters manually.
+#
+SMOKE_0_PARAM="test_push_cpu=$TEST_CPU_0 test_pop_cpu=$TEST_CPU_0"
+SMOKE_1_PARAM="test_push_cpu=$TEST_CPU_0 test_pop_cpu=$TEST_CPU_1"
+NONFRAG_PARAM="$SMOKE_1_PARAM nr_test=$NR_TEST"
+FRAG_PARAM="$NONFRAG_PARAM test_alloc_len=2048 test_frag=1"
+NONFRAG_DMA_PARAM="$NONFRAG_PARAM test_dma=1"
+FRAG_DMA_PARAM="$FRAG_PARAM test_dma=1"
+NONFRAG_NAPI_PARAM="$NONFRAG_PARAM test_napi=1"
+FRAG_NAPI_PARAM="$FRAG_PARAM test_napi=1"
+NAPI_PARAM="$SMOKE_0_PARAM test_napi=1"
+NAPI_DIRECT_PARAM="$NAPI_PARAM test_direct=1"
+
+check_test_requirements()
+{
+	uid=$(id -u)
+	if [ $uid -ne 0 ]; then
+		echo "$0: Must be run as root"
+		exit $ksft_skip
+	fi
+
+	if ! which insmod > /dev/null 2>&1; then
+		echo "$0: You need insmod installed"
+		exit $ksft_skip
+	fi
+
+	if [ ! -f $DRIVER ]; then
+		echo "$0: You need to compile page_pool_test module"
+		exit $ksft_skip
+	fi
+}
+
+run_nonfrag_check()
+{
+	echo "Run performance tests to evaluate how fast nonaligned alloc API is."
+
+	insmod $DRIVER $NONFRAG_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_frag_check()
+{
+	echo "Run performance tests to evaluate how fast aligned alloc API is."
+
+	insmod $DRIVER $FRAG_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_nonfrag_dma_check()
+{
+	echo "Run performance tests to evaluate nonaligned alloc API with dma mapping."
+
+	insmod $DRIVER $NONFRAG_DMA_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_frag_dma_check()
+{
+	echo "Run performance tests to evaluate aligned alloc API with dma mapping."
+
+	insmod $DRIVER $FRAG_DMA_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_nonfrag_napi_check()
+{
+	echo "Run performance tests to evaluate nonaligned alloc API in NAPI testing mode."
+
+	insmod $DRIVER $NONFRAG_NAPI_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_frag_napi_check()
+{
+	echo "Run performance tests to evaluate aligned alloc API in NAPI testing mode."
+
+	insmod $DRIVER $FRAG_NAPI_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_napi_check()
+{
+	echo "Run performance in NAPI testing mode."
+
+	insmod $DRIVER $NAPI_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_napi_direct_check()
+{
+	echo "Run performance tests in NAPI and direct recycle testing mode."
+
+	insmod $DRIVER $NAPI_DIRECT_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_smoke_0_check()
+{
+	echo "Run smoke_0 test."
+
+	insmod $DRIVER $SMOKE_0_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+run_smoke_1_check()
+{
+	echo "Run smoke_1 test."
+
+	insmod $DRIVER $SMOKE_1_PARAM > /dev/null 2>&1
+	echo "Done."
+	echo "Check the kernel ring buffer to see the summary."
+}
+
+
+function run_test()
+{
+	run_smoke_0_check
+	run_napi_check
+	run_napi_direct_check
+
+	if [ $CPU_CNT -gt 1 ]; then
+		run_smoke_1_check
+		run_nonfrag_check
+		run_frag_check
+		run_nonfrag_dma_check
+		run_frag_dma_check
+		run_nonfrag_napi_check
+		run_frag_napi_check
+	fi
+}
+
+check_test_requirements
+run_test
+
+exit 0