| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ | |
| #define BPF_NO_KFUNC_PROTOTYPES | |
| #include <vmlinux.h> | |
| #include <bpf/bpf_helpers.h> | |
| #include "bpf_experimental.h" | |
| #include <bpf_arena_common.h> | |
| #include "../test_kmods/bpf_testmod.h" | |
| #include "../test_kmods/bpf_testmod_kfunc.h" | |
| char _license[] SEC("license") = "GPL"; | |
| struct { | |
| __uint(type, BPF_MAP_TYPE_ARENA); | |
| __uint(map_flags, BPF_F_MMAPABLE); | |
| /* page 0 hosts the arena globals, page 1 is for allocations */ | |
| __uint(max_entries, 2); | |
| } arena SEC(".maps"); | |
| /* also associates the callbacks with the arena */ | |
| u64 __arena arena_touch; | |
| /* raw value of the last __arena ctx argument, captured by test_arena_cb */ | |
| u64 __arena cb_ptr_val; | |
| SEC("struct_ops/test_arena") | |
| int test_arena_cb(unsigned long long *ctx) | |
| { | |
| u64 __arena *ptr = (u64 __arena *)ctx[0]; | |
| arena_touch++; | |
| cb_ptr_val = ctx[0]; | |
| *ptr += 1; | |
| return 0; | |
| } | |
| SEC("struct_ops/test_arena_nullable") | |
| int test_arena_nullable_cb(unsigned long long *ctx) | |
| { | |
| u64 __arena *ptr = (u64 __arena *)ctx[0]; | |
| arena_touch++; | |
| if (!ptr) | |
| return 0xbee; | |
| *ptr += 1; | |
| return 0; | |
| } | |
| SEC("struct_ops/test_arena_stack") | |
| int test_arena_stack_cb(unsigned long long *ctx) | |
| { | |
| u64 __arena *ptr = (u64 __arena *)ctx[8]; | |
| arena_touch++; | |
| /* pin the slot layout: the leading args fill ctx[0]..ctx[7] */ | |
| if (ctx[0] != 1 || ctx[7] != 8) | |
| return 0xbad; | |
| *ptr += 1; | |
| return 0; | |
| } | |
| SEC("struct_ops/test_arena_multislot") | |
| int test_arena_multislot_cb(unsigned long long *ctx) | |
| { | |
| u64 __arena *ptr = (u64 __arena *)ctx[2]; | |
| arena_touch++; | |
| /* | |
| * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument | |
| * one but slot two. Getting that wrong hands the callback a scalar. | |
| */ | |
| if (ctx[0] != 11 || ctx[1] != 22) | |
| return 0xbad; | |
| *ptr += 1; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct bpf_testmod_ops3 testmod_arena = { | |
| .test_arena = (void *)test_arena_cb, | |
| .test_arena_nullable = (void *)test_arena_nullable_cb, | |
| .test_arena_stack = (void *)test_arena_stack_cb, | |
| .test_arena_multislot = (void *)test_arena_multislot_cb, | |
| }; | |
| SEC("syscall") | |
| int trigger(void *ctx) | |
| { | |
| #if defined(__BPF_FEATURE_ADDR_SPACE_CAST) | |
| u64 __arena *val; | |
| int ret; | |
| val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0); | |
| if (!val) | |
| return 1; | |
| *val = 41; | |
| ret = bpf_testmod_ops3_call_test_arena((u64 *)val); | |
| if (ret) | |
| return 2; | |
| if (*val != 42) | |
| return 3; | |
| /* | |
| * The callback must have seen exactly (u32)(kaddr - kern_vm_start), | |
| * which is the arena offset of val with the upper 32 bits clear. | |
| */ | |
| if (cb_ptr_val != (u32)(u64)val) | |
| return 4; | |
| ret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val); | |
| if (ret) | |
| return 5; | |
| if (*val != 43) | |
| return 6; | |
| /* NULL survives the nullable kfunc and the trampoline as NULL */ | |
| ret = bpf_testmod_ops3_call_test_arena_nullable(NULL); | |
| if (ret != 0xbee) | |
| return 7; | |
| /* the arena pointer is stack-passed into the trampoline here */ | |
| ret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val); | |
| if (ret) | |
| return 8; | |
| if (*val != 44) | |
| return 9; | |
| /* a multi-slot arg precedes the arena pointer here */ | |
| ret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val); | |
| if (ret) | |
| return 10; | |
| if (*val != 45) | |
| return 11; | |
| bpf_arena_free_pages(&arena, (void __arena *)val, 1); | |
| #endif | |
| return 0; | |
| } |