diff mbox series

[bpf-next,v1,4/4] selftests/bpf: Add a test for btf_type_tag "percpu"

Message ID 20220304191657.981240-5-haoluo@google.com (mailing list archive)
State Accepted
Commit 50c6b8a9aea2b8dc6c4ffb0cc502b94f7f57a0dc
Delegated to: BPF
Headers show
Series bpf: add __percpu tagging in vmlinux BTF | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 11 maintainers not CCed: mcoquelin.stm32@gmail.com linux-kselftest@vger.kernel.org linux-stm32@st-md-mailman.stormreply.com linux-arm-kernel@lists.infradead.org alexandre.torgue@foss.st.com john.fastabend@gmail.com kafai@fb.com songliubraving@fb.com shuah@kernel.org netdev@vger.kernel.org memxor@gmail.com
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next success VM_Test

Commit Message

Hao Luo March 4, 2022, 7:16 p.m. UTC
Add test for percpu btf_type_tag. Similar to the "user" tag, we test
the following cases:

 1. __percpu struct field.
 2. __percpu as function parameter.
 3. per_cpu_ptr() accepts dynamically allocated __percpu memory.

Because the test for "user" and the test for "percpu" are very similar,
a little bit of refactoring has been done in btf_tag.c. Basically, both
tests share the same function for loading vmlinux and module btf.

Example output from log:

 > ./test_progs -v -t btf_tag

 libbpf: prog 'test_percpu1': BPF program load failed: Permission denied
 libbpf: prog 'test_percpu1': -- BEGIN PROG LOAD LOG --
 ...
 ; g = arg->a;
 1: (61) r1 = *(u32 *)(r1 +0)
 R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
 ...
 test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
 #26/6 btf_tag/btf_type_tag_percpu_mod1:OK

 libbpf: prog 'test_percpu2': BPF program load failed: Permission denied
 libbpf: prog 'test_percpu2': -- BEGIN PROG LOAD LOG --
 ...
 ; g = arg->p->a;
 2: (61) r1 = *(u32 *)(r1 +0)
 R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
 ...
 test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
 #26/7 btf_tag/btf_type_tag_percpu_mod2:OK

 libbpf: prog 'test_percpu_load': BPF program load failed: Permission denied
 libbpf: prog 'test_percpu_load': -- BEGIN PROG LOAD LOG --
 ...
 ; g = (__u64)cgrp->rstat_cpu->updated_children;
 2: (79) r1 = *(u64 *)(r1 +48)
 R1 is ptr_cgroup_rstat_cpu access percpu memory: off=48
 ...
 test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_load 0 nsec
 #26/8 btf_tag/btf_type_tag_percpu_vmlinux_load:OK

 load_btfs:PASS:could not load vmlinux BTF 0 nsec
 test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu 0 nsec
 test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_helper 0 nsec
 #26/9 btf_tag/btf_type_tag_percpu_vmlinux_helper:OK

Signed-off-by: Hao Luo <haoluo@google.com>
---
 .../selftests/bpf/bpf_testmod/bpf_testmod.c   |  17 ++
 .../selftests/bpf/prog_tests/btf_tag.c        | 164 ++++++++++++++----
 .../selftests/bpf/progs/btf_type_tag_percpu.c |  66 +++++++
 3 files changed, 218 insertions(+), 29 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c

Comments

Yonghong Song March 5, 2022, 9:20 p.m. UTC | #1
On 3/4/22 11:16 AM, Hao Luo wrote:
> Add test for percpu btf_type_tag. Similar to the "user" tag, we test
> the following cases:
> 
>   1. __percpu struct field.
>   2. __percpu as function parameter.
>   3. per_cpu_ptr() accepts dynamically allocated __percpu memory.
> 
> Because the test for "user" and the test for "percpu" are very similar,
> a little bit of refactoring has been done in btf_tag.c. Basically, both
> tests share the same function for loading vmlinux and module btf.
> 
> Example output from log:
> 
>   > ./test_progs -v -t btf_tag
> 
>   libbpf: prog 'test_percpu1': BPF program load failed: Permission denied
>   libbpf: prog 'test_percpu1': -- BEGIN PROG LOAD LOG --
>   ...
>   ; g = arg->a;
>   1: (61) r1 = *(u32 *)(r1 +0)
>   R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
>   ...
>   test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
>   #26/6 btf_tag/btf_type_tag_percpu_mod1:OK
> 
>   libbpf: prog 'test_percpu2': BPF program load failed: Permission denied
>   libbpf: prog 'test_percpu2': -- BEGIN PROG LOAD LOG --
>   ...
>   ; g = arg->p->a;
>   2: (61) r1 = *(u32 *)(r1 +0)
>   R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
>   ...
>   test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
>   #26/7 btf_tag/btf_type_tag_percpu_mod2:OK
> 
>   libbpf: prog 'test_percpu_load': BPF program load failed: Permission denied
>   libbpf: prog 'test_percpu_load': -- BEGIN PROG LOAD LOG --
>   ...
>   ; g = (__u64)cgrp->rstat_cpu->updated_children;
>   2: (79) r1 = *(u64 *)(r1 +48)
>   R1 is ptr_cgroup_rstat_cpu access percpu memory: off=48
>   ...
>   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_load 0 nsec
>   #26/8 btf_tag/btf_type_tag_percpu_vmlinux_load:OK
> 
>   load_btfs:PASS:could not load vmlinux BTF 0 nsec
>   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu 0 nsec
>   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_helper 0 nsec
>   #26/9 btf_tag/btf_type_tag_percpu_vmlinux_helper:OK
> 
> Signed-off-by: Hao Luo <haoluo@google.com>

With one nit below.

Acked-by: Yonghong Song <yhs@fb.com>

> ---
>   .../selftests/bpf/bpf_testmod/bpf_testmod.c   |  17 ++
>   .../selftests/bpf/prog_tests/btf_tag.c        | 164 ++++++++++++++----
>   .../selftests/bpf/progs/btf_type_tag_percpu.c |  66 +++++++
>   3 files changed, 218 insertions(+), 29 deletions(-)
>   create mode 100644 tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c
> 
> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> index 27d63be47b95..17c211f3b924 100644
> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> @@ -33,6 +33,10 @@ struct bpf_testmod_btf_type_tag_2 {
>   	struct bpf_testmod_btf_type_tag_1 __user *p;
>   };
>   
> +struct bpf_testmod_btf_type_tag_3 {
> +	struct bpf_testmod_btf_type_tag_1 __percpu *p;
> +};
> +
>   noinline int
>   bpf_testmod_test_btf_type_tag_user_1(struct bpf_testmod_btf_type_tag_1 __user *arg) {
>   	BTF_TYPE_EMIT(func_proto_typedef);
> @@ -46,6 +50,19 @@ bpf_testmod_test_btf_type_tag_user_2(struct bpf_testmod_btf_type_tag_2 *arg) {
>   	return arg->p->a;
>   }
>   
> +noinline int
> +bpf_testmod_test_btf_type_tag_percpu_1(struct bpf_testmod_btf_type_tag_1 __percpu *arg) {
> +	BTF_TYPE_EMIT(func_proto_typedef);
> +	BTF_TYPE_EMIT(func_proto_typedef_nested1);
> +	BTF_TYPE_EMIT(func_proto_typedef_nested2);

Are these necessary? They have been defined in
bpf_testmod_test_btf_type_tag_user_1().

> +	return arg->a;
> +}
> +
[...]
Alexei Starovoitov March 6, 2022, 2:49 a.m. UTC | #2
On Sat, Mar 05, 2022 at 01:20:42PM -0800, Yonghong Song wrote:
> 
> On 3/4/22 11:16 AM, Hao Luo wrote:
> > Add test for percpu btf_type_tag. Similar to the "user" tag, we test
> > the following cases:
> > 
> >   1. __percpu struct field.
> >   2. __percpu as function parameter.
> >   3. per_cpu_ptr() accepts dynamically allocated __percpu memory.
> > 
> > Because the test for "user" and the test for "percpu" are very similar,
> > a little bit of refactoring has been done in btf_tag.c. Basically, both
> > tests share the same function for loading vmlinux and module btf.
> > 
> > Example output from log:
> > 
> >   > ./test_progs -v -t btf_tag
> > 
> >   libbpf: prog 'test_percpu1': BPF program load failed: Permission denied
> >   libbpf: prog 'test_percpu1': -- BEGIN PROG LOAD LOG --
> >   ...
> >   ; g = arg->a;
> >   1: (61) r1 = *(u32 *)(r1 +0)
> >   R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
> >   ...
> >   test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
> >   #26/6 btf_tag/btf_type_tag_percpu_mod1:OK
> > 
> >   libbpf: prog 'test_percpu2': BPF program load failed: Permission denied
> >   libbpf: prog 'test_percpu2': -- BEGIN PROG LOAD LOG --
> >   ...
> >   ; g = arg->p->a;
> >   2: (61) r1 = *(u32 *)(r1 +0)
> >   R1 is ptr_bpf_testmod_btf_type_tag_1 access percpu memory: off=0
> >   ...
> >   test_btf_type_tag_mod_percpu:PASS:btf_type_tag_percpu 0 nsec
> >   #26/7 btf_tag/btf_type_tag_percpu_mod2:OK
> > 
> >   libbpf: prog 'test_percpu_load': BPF program load failed: Permission denied
> >   libbpf: prog 'test_percpu_load': -- BEGIN PROG LOAD LOG --
> >   ...
> >   ; g = (__u64)cgrp->rstat_cpu->updated_children;
> >   2: (79) r1 = *(u64 *)(r1 +48)
> >   R1 is ptr_cgroup_rstat_cpu access percpu memory: off=48
> >   ...
> >   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_load 0 nsec
> >   #26/8 btf_tag/btf_type_tag_percpu_vmlinux_load:OK
> > 
> >   load_btfs:PASS:could not load vmlinux BTF 0 nsec
> >   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu 0 nsec
> >   test_btf_type_tag_vmlinux_percpu:PASS:btf_type_tag_percpu_helper 0 nsec
> >   #26/9 btf_tag/btf_type_tag_percpu_vmlinux_helper:OK
> > 
> > Signed-off-by: Hao Luo <haoluo@google.com>
> 
> With one nit below.
> 
> Acked-by: Yonghong Song <yhs@fb.com>
> 
> > ---
> >   .../selftests/bpf/bpf_testmod/bpf_testmod.c   |  17 ++
> >   .../selftests/bpf/prog_tests/btf_tag.c        | 164 ++++++++++++++----
> >   .../selftests/bpf/progs/btf_type_tag_percpu.c |  66 +++++++
> >   3 files changed, 218 insertions(+), 29 deletions(-)
> >   create mode 100644 tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c
> > 
> > diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> > index 27d63be47b95..17c211f3b924 100644
> > --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> > +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> > @@ -33,6 +33,10 @@ struct bpf_testmod_btf_type_tag_2 {
> >   	struct bpf_testmod_btf_type_tag_1 __user *p;
> >   };
> > +struct bpf_testmod_btf_type_tag_3 {
> > +	struct bpf_testmod_btf_type_tag_1 __percpu *p;
> > +};
> > +
> >   noinline int
> >   bpf_testmod_test_btf_type_tag_user_1(struct bpf_testmod_btf_type_tag_1 __user *arg) {
> >   	BTF_TYPE_EMIT(func_proto_typedef);
> > @@ -46,6 +50,19 @@ bpf_testmod_test_btf_type_tag_user_2(struct bpf_testmod_btf_type_tag_2 *arg) {
> >   	return arg->p->a;
> >   }
> > +noinline int
> > +bpf_testmod_test_btf_type_tag_percpu_1(struct bpf_testmod_btf_type_tag_1 __percpu *arg) {
> > +	BTF_TYPE_EMIT(func_proto_typedef);
> > +	BTF_TYPE_EMIT(func_proto_typedef_nested1);
> > +	BTF_TYPE_EMIT(func_proto_typedef_nested2);
> 
> Are these necessary? They have been defined in
> bpf_testmod_test_btf_type_tag_user_1().

Yonghong,
Thanks. Good catch. I've removed those while applying.

Hao,
Great work.
I really liked how patch 3 discovers MEM_PERCPU flag from
two sources: percpu_datasec and clang tag.
Hao Luo March 8, 2022, 1:41 a.m. UTC | #3
On Sat, Mar 5, 2022 at 6:49 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Sat, Mar 05, 2022 at 01:20:42PM -0800, Yonghong Song wrote:
> >
> > On 3/4/22 11:16 AM, Hao Luo wrote:
[...]
> > > ---
> > >   .../selftests/bpf/bpf_testmod/bpf_testmod.c   |  17 ++
> > >   .../selftests/bpf/prog_tests/btf_tag.c        | 164 ++++++++++++++----
> > >   .../selftests/bpf/progs/btf_type_tag_percpu.c |  66 +++++++
> > >   3 files changed, 218 insertions(+), 29 deletions(-)
> > >   create mode 100644 tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c
[...]
> > > +noinline int
> > > +bpf_testmod_test_btf_type_tag_percpu_1(struct bpf_testmod_btf_type_tag_1 __percpu *arg) {
> > > +   BTF_TYPE_EMIT(func_proto_typedef);
> > > +   BTF_TYPE_EMIT(func_proto_typedef_nested1);
> > > +   BTF_TYPE_EMIT(func_proto_typedef_nested2);
> >
> > Are these necessary? They have been defined in
> > bpf_testmod_test_btf_type_tag_user_1().
>
> Yonghong,
> Thanks. Good catch. I've removed those while applying.
>

Thanks Yonghong and Alexei. I wasn't sure their purpose and not sure
whether I should include them, so copy-pasted it.

> Hao,
> Great work.
> I really liked how patch 3 discovers MEM_PERCPU flag from
> two sources: percpu_datasec and clang tag.

Thanks Alexei! The BTF infrastructure together with clang tag is
really amazing! [thumbs up]
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index 27d63be47b95..17c211f3b924 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -33,6 +33,10 @@  struct bpf_testmod_btf_type_tag_2 {
 	struct bpf_testmod_btf_type_tag_1 __user *p;
 };
 
+struct bpf_testmod_btf_type_tag_3 {
+	struct bpf_testmod_btf_type_tag_1 __percpu *p;
+};
+
 noinline int
 bpf_testmod_test_btf_type_tag_user_1(struct bpf_testmod_btf_type_tag_1 __user *arg) {
 	BTF_TYPE_EMIT(func_proto_typedef);
@@ -46,6 +50,19 @@  bpf_testmod_test_btf_type_tag_user_2(struct bpf_testmod_btf_type_tag_2 *arg) {
 	return arg->p->a;
 }
 
+noinline int
+bpf_testmod_test_btf_type_tag_percpu_1(struct bpf_testmod_btf_type_tag_1 __percpu *arg) {
+	BTF_TYPE_EMIT(func_proto_typedef);
+	BTF_TYPE_EMIT(func_proto_typedef_nested1);
+	BTF_TYPE_EMIT(func_proto_typedef_nested2);
+	return arg->a;
+}
+
+noinline int
+bpf_testmod_test_btf_type_tag_percpu_2(struct bpf_testmod_btf_type_tag_3 *arg) {
+	return arg->p->a;
+}
+
 noinline int bpf_testmod_loop_test(int n)
 {
 	int i, sum = 0;
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_tag.c b/tools/testing/selftests/bpf/prog_tests/btf_tag.c
index f7560b54a6bb..071430cd54de 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_tag.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_tag.c
@@ -10,6 +10,7 @@  struct btf_type_tag_test {
 };
 #include "btf_type_tag.skel.h"
 #include "btf_type_tag_user.skel.h"
+#include "btf_type_tag_percpu.skel.h"
 
 static void test_btf_decl_tag(void)
 {
@@ -43,38 +44,81 @@  static void test_btf_type_tag(void)
 	btf_type_tag__destroy(skel);
 }
 
-static void test_btf_type_tag_mod_user(bool load_test_user1)
+/* loads vmlinux_btf as well as module_btf. If the caller passes NULL as
+ * module_btf, it will not load module btf.
+ *
+ * Returns 0 on success.
+ * Return -1 On error. In case of error, the loaded btf will be freed and the
+ * input parameters will be set to pointing to NULL.
+ */
+static int load_btfs(struct btf **vmlinux_btf, struct btf **module_btf,
+		     bool needs_vmlinux_tag)
 {
 	const char *module_name = "bpf_testmod";
-	struct btf *vmlinux_btf, *module_btf;
-	struct btf_type_tag_user *skel;
 	__s32 type_id;
-	int err;
 
 	if (!env.has_testmod) {
 		test__skip();
-		return;
+		return -1;
 	}
 
-	/* skip the test if the module does not have __user tags */
-	vmlinux_btf = btf__load_vmlinux_btf();
-	if (!ASSERT_OK_PTR(vmlinux_btf, "could not load vmlinux BTF"))
-		return;
+	*vmlinux_btf = btf__load_vmlinux_btf();
+	if (!ASSERT_OK_PTR(*vmlinux_btf, "could not load vmlinux BTF"))
+		return -1;
+
+	if (!needs_vmlinux_tag)
+		goto load_module_btf;
 
-	module_btf = btf__load_module_btf(module_name, vmlinux_btf);
-	if (!ASSERT_OK_PTR(module_btf, "could not load module BTF"))
+	/* skip the test if the vmlinux does not have __user tags */
+	type_id = btf__find_by_name_kind(*vmlinux_btf, "user", BTF_KIND_TYPE_TAG);
+	if (type_id <= 0) {
+		printf("%s:SKIP: btf_type_tag attribute not in vmlinux btf", __func__);
+		test__skip();
 		goto free_vmlinux_btf;
+	}
 
-	type_id = btf__find_by_name_kind(module_btf, "user", BTF_KIND_TYPE_TAG);
+load_module_btf:
+	/* skip loading module_btf, if not requested by caller */
+	if (!module_btf)
+		return 0;
+
+	*module_btf = btf__load_module_btf(module_name, *vmlinux_btf);
+	if (!ASSERT_OK_PTR(*module_btf, "could not load module BTF"))
+		goto free_vmlinux_btf;
+
+	/* skip the test if the module does not have __user tags */
+	type_id = btf__find_by_name_kind(*module_btf, "user", BTF_KIND_TYPE_TAG);
 	if (type_id <= 0) {
 		printf("%s:SKIP: btf_type_tag attribute not in %s", __func__, module_name);
 		test__skip();
 		goto free_module_btf;
 	}
 
+	return 0;
+
+free_module_btf:
+	btf__free(*module_btf);
+free_vmlinux_btf:
+	btf__free(*vmlinux_btf);
+
+	*vmlinux_btf = NULL;
+	if (module_btf)
+		*module_btf = NULL;
+	return -1;
+}
+
+static void test_btf_type_tag_mod_user(bool load_test_user1)
+{
+	struct btf *vmlinux_btf = NULL, *module_btf = NULL;
+	struct btf_type_tag_user *skel;
+	int err;
+
+	if (load_btfs(&vmlinux_btf, &module_btf, /*needs_vmlinux_tag=*/false))
+		return;
+
 	skel = btf_type_tag_user__open();
 	if (!ASSERT_OK_PTR(skel, "btf_type_tag_user"))
-		goto free_module_btf;
+		goto cleanup;
 
 	bpf_program__set_autoload(skel->progs.test_sys_getsockname, false);
 	if (load_test_user1)
@@ -87,34 +131,23 @@  static void test_btf_type_tag_mod_user(bool load_test_user1)
 
 	btf_type_tag_user__destroy(skel);
 
-free_module_btf:
+cleanup:
 	btf__free(module_btf);
-free_vmlinux_btf:
 	btf__free(vmlinux_btf);
 }
 
 static void test_btf_type_tag_vmlinux_user(void)
 {
 	struct btf_type_tag_user *skel;
-	struct btf *vmlinux_btf;
-	__s32 type_id;
+	struct btf *vmlinux_btf = NULL;
 	int err;
 
-	/* skip the test if the vmlinux does not have __user tags */
-	vmlinux_btf = btf__load_vmlinux_btf();
-	if (!ASSERT_OK_PTR(vmlinux_btf, "could not load vmlinux BTF"))
+	if (load_btfs(&vmlinux_btf, NULL, /*needs_vmlinux_tag=*/true))
 		return;
 
-	type_id = btf__find_by_name_kind(vmlinux_btf, "user", BTF_KIND_TYPE_TAG);
-	if (type_id <= 0) {
-		printf("%s:SKIP: btf_type_tag attribute not in vmlinux btf", __func__);
-		test__skip();
-		goto free_vmlinux_btf;
-	}
-
 	skel = btf_type_tag_user__open();
 	if (!ASSERT_OK_PTR(skel, "btf_type_tag_user"))
-		goto free_vmlinux_btf;
+		goto cleanup;
 
 	bpf_program__set_autoload(skel->progs.test_user2, false);
 	bpf_program__set_autoload(skel->progs.test_user1, false);
@@ -124,7 +157,70 @@  static void test_btf_type_tag_vmlinux_user(void)
 
 	btf_type_tag_user__destroy(skel);
 
-free_vmlinux_btf:
+cleanup:
+	btf__free(vmlinux_btf);
+}
+
+static void test_btf_type_tag_mod_percpu(bool load_test_percpu1)
+{
+	struct btf *vmlinux_btf, *module_btf;
+	struct btf_type_tag_percpu *skel;
+	int err;
+
+	if (load_btfs(&vmlinux_btf, &module_btf, /*needs_vmlinux_tag=*/false))
+		return;
+
+	skel = btf_type_tag_percpu__open();
+	if (!ASSERT_OK_PTR(skel, "btf_type_tag_percpu"))
+		goto cleanup;
+
+	bpf_program__set_autoload(skel->progs.test_percpu_load, false);
+	bpf_program__set_autoload(skel->progs.test_percpu_helper, false);
+	if (load_test_percpu1)
+		bpf_program__set_autoload(skel->progs.test_percpu2, false);
+	else
+		bpf_program__set_autoload(skel->progs.test_percpu1, false);
+
+	err = btf_type_tag_percpu__load(skel);
+	ASSERT_ERR(err, "btf_type_tag_percpu");
+
+	btf_type_tag_percpu__destroy(skel);
+
+cleanup:
+	btf__free(module_btf);
+	btf__free(vmlinux_btf);
+}
+
+static void test_btf_type_tag_vmlinux_percpu(bool load_test)
+{
+	struct btf_type_tag_percpu *skel;
+	struct btf *vmlinux_btf = NULL;
+	int err;
+
+	if (load_btfs(&vmlinux_btf, NULL, /*needs_vmlinux_tag=*/true))
+		return;
+
+	skel = btf_type_tag_percpu__open();
+	if (!ASSERT_OK_PTR(skel, "btf_type_tag_percpu"))
+		goto cleanup;
+
+	bpf_program__set_autoload(skel->progs.test_percpu2, false);
+	bpf_program__set_autoload(skel->progs.test_percpu1, false);
+	if (load_test) {
+		bpf_program__set_autoload(skel->progs.test_percpu_helper, false);
+
+		err = btf_type_tag_percpu__load(skel);
+		ASSERT_ERR(err, "btf_type_tag_percpu_load");
+	} else {
+		bpf_program__set_autoload(skel->progs.test_percpu_load, false);
+
+		err = btf_type_tag_percpu__load(skel);
+		ASSERT_OK(err, "btf_type_tag_percpu_helper");
+	}
+
+	btf_type_tag_percpu__destroy(skel);
+
+cleanup:
 	btf__free(vmlinux_btf);
 }
 
@@ -134,10 +230,20 @@  void test_btf_tag(void)
 		test_btf_decl_tag();
 	if (test__start_subtest("btf_type_tag"))
 		test_btf_type_tag();
+
 	if (test__start_subtest("btf_type_tag_user_mod1"))
 		test_btf_type_tag_mod_user(true);
 	if (test__start_subtest("btf_type_tag_user_mod2"))
 		test_btf_type_tag_mod_user(false);
 	if (test__start_subtest("btf_type_tag_sys_user_vmlinux"))
 		test_btf_type_tag_vmlinux_user();
+
+	if (test__start_subtest("btf_type_tag_percpu_mod1"))
+		test_btf_type_tag_mod_percpu(true);
+	if (test__start_subtest("btf_type_tag_percpu_mod2"))
+		test_btf_type_tag_mod_percpu(false);
+	if (test__start_subtest("btf_type_tag_percpu_vmlinux_load"))
+		test_btf_type_tag_vmlinux_percpu(true);
+	if (test__start_subtest("btf_type_tag_percpu_vmlinux_helper"))
+		test_btf_type_tag_vmlinux_percpu(false);
 }
diff --git a/tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c b/tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c
new file mode 100644
index 000000000000..8feddb8289cf
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf_type_tag_percpu.c
@@ -0,0 +1,66 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Google */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct bpf_testmod_btf_type_tag_1 {
+	int a;
+};
+
+struct bpf_testmod_btf_type_tag_2 {
+	struct bpf_testmod_btf_type_tag_1 *p;
+};
+
+__u64 g;
+
+SEC("fentry/bpf_testmod_test_btf_type_tag_percpu_1")
+int BPF_PROG(test_percpu1, struct bpf_testmod_btf_type_tag_1 *arg)
+{
+	g = arg->a;
+	return 0;
+}
+
+SEC("fentry/bpf_testmod_test_btf_type_tag_percpu_2")
+int BPF_PROG(test_percpu2, struct bpf_testmod_btf_type_tag_2 *arg)
+{
+	g = arg->p->a;
+	return 0;
+}
+
+/* trace_cgroup_mkdir(struct cgroup *cgrp, const char *path)
+ *
+ * struct cgroup_rstat_cpu {
+ *   ...
+ *   struct cgroup *updated_children;
+ *   ...
+ * };
+ *
+ * struct cgroup {
+ *   ...
+ *   struct cgroup_rstat_cpu __percpu *rstat_cpu;
+ *   ...
+ * };
+ */
+SEC("tp_btf/cgroup_mkdir")
+int BPF_PROG(test_percpu_load, struct cgroup *cgrp, const char *path)
+{
+	g = (__u64)cgrp->rstat_cpu->updated_children;
+	return 0;
+}
+
+SEC("tp_btf/cgroup_mkdir")
+int BPF_PROG(test_percpu_helper, struct cgroup *cgrp, const char *path)
+{
+	struct cgroup_rstat_cpu *rstat;
+	__u32 cpu;
+
+	cpu = bpf_get_smp_processor_id();
+	rstat = (struct cgroup_rstat_cpu *)bpf_per_cpu_ptr(cgrp->rstat_cpu, cpu);
+	if (rstat) {
+		/* READ_ONCE */
+		*(volatile int *)rstat;
+	}
+
+	return 0;
+}