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