xref: /linux/tools/testing/selftests/bpf/progs/struct_ops_arena.c (revision 5a8cd539ac19f7a68e68e1d25ef9ca2ff55b8500)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
3 
4 #define BPF_NO_KFUNC_PROTOTYPES
5 #include <vmlinux.h>
6 #include <bpf/bpf_helpers.h>
7 #include "bpf_experimental.h"
8 #include <bpf_arena_common.h>
9 #include "../test_kmods/bpf_testmod.h"
10 #include "../test_kmods/bpf_testmod_kfunc.h"
11 
12 char _license[] SEC("license") = "GPL";
13 
14 struct {
15 	__uint(type, BPF_MAP_TYPE_ARENA);
16 	__uint(map_flags, BPF_F_MMAPABLE);
17 	/* page 0 hosts the arena globals, page 1 is for allocations */
18 	__uint(max_entries, 2);
19 } arena SEC(".maps");
20 
21 /* also associates the callbacks with the arena */
22 u64 __arena arena_touch;
23 /* raw value of the last __arena ctx argument, captured by test_arena_cb */
24 u64 __arena cb_ptr_val;
25 
26 SEC("struct_ops/test_arena")
test_arena_cb(unsigned long long * ctx)27 int test_arena_cb(unsigned long long *ctx)
28 {
29 	u64 __arena *ptr = (u64 __arena *)ctx[0];
30 
31 	arena_touch++;
32 	cb_ptr_val = ctx[0];
33 	*ptr += 1;
34 	return 0;
35 }
36 
37 SEC("struct_ops/test_arena_nullable")
test_arena_nullable_cb(unsigned long long * ctx)38 int test_arena_nullable_cb(unsigned long long *ctx)
39 {
40 	u64 __arena *ptr = (u64 __arena *)ctx[0];
41 
42 	arena_touch++;
43 	if (!ptr)
44 		return 0xbee;
45 	*ptr += 1;
46 	return 0;
47 }
48 
49 SEC("struct_ops/test_arena_stack")
test_arena_stack_cb(unsigned long long * ctx)50 int test_arena_stack_cb(unsigned long long *ctx)
51 {
52 	u64 __arena *ptr = (u64 __arena *)ctx[8];
53 
54 	arena_touch++;
55 	/* pin the slot layout: the leading args fill ctx[0]..ctx[7] */
56 	if (ctx[0] != 1 || ctx[7] != 8)
57 		return 0xbad;
58 	*ptr += 1;
59 	return 0;
60 }
61 
62 SEC("struct_ops/test_arena_multislot")
test_arena_multislot_cb(unsigned long long * ctx)63 int test_arena_multislot_cb(unsigned long long *ctx)
64 {
65 	u64 __arena *ptr = (u64 __arena *)ctx[2];
66 
67 	arena_touch++;
68 	/*
69 	 * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument
70 	 * one but slot two. Getting that wrong hands the callback a scalar.
71 	 */
72 	if (ctx[0] != 11 || ctx[1] != 22)
73 		return 0xbad;
74 	*ptr += 1;
75 	return 0;
76 }
77 
78 SEC(".struct_ops.link")
79 struct bpf_testmod_ops3 testmod_arena = {
80 	.test_arena = (void *)test_arena_cb,
81 	.test_arena_nullable = (void *)test_arena_nullable_cb,
82 	.test_arena_stack = (void *)test_arena_stack_cb,
83 	.test_arena_multislot = (void *)test_arena_multislot_cb,
84 };
85 
86 SEC("syscall")
trigger(void * ctx)87 int trigger(void *ctx)
88 {
89 #if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
90 	u64 __arena *val;
91 	int ret;
92 
93 	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
94 	if (!val)
95 		return 1;
96 
97 	*val = 41;
98 	ret = bpf_testmod_ops3_call_test_arena((u64 *)val);
99 	if (ret)
100 		return 2;
101 	if (*val != 42)
102 		return 3;
103 
104 	/*
105 	 * The callback must have seen exactly (u32)(kaddr - kern_vm_start),
106 	 * which is the arena offset of val with the upper 32 bits clear.
107 	 */
108 	if (cb_ptr_val != (u32)(u64)val)
109 		return 4;
110 
111 	ret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val);
112 	if (ret)
113 		return 5;
114 	if (*val != 43)
115 		return 6;
116 
117 	/* NULL survives the nullable kfunc and the trampoline as NULL */
118 	ret = bpf_testmod_ops3_call_test_arena_nullable(NULL);
119 	if (ret != 0xbee)
120 		return 7;
121 
122 	/* the arena pointer is stack-passed into the trampoline here */
123 	ret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val);
124 	if (ret)
125 		return 8;
126 	if (*val != 44)
127 		return 9;
128 
129 	/* a multi-slot arg precedes the arena pointer here */
130 	ret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val);
131 	if (ret)
132 		return 10;
133 	if (*val != 45)
134 		return 11;
135 
136 	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
137 #endif
138 	return 0;
139 }
140