// 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_misc.h"
#include "bpf_experimental.h"
#include <bpf_arena_common.h>
#include "../test_kmods/bpf_testmod_kfunc.h"

struct {
	__uint(type, BPF_MAP_TYPE_ARENA);
	__uint(map_flags, BPF_F_MMAPABLE);
	/* page 0 hosts the arena global, page 1 is for allocations */
	__uint(max_entries, 2);
} arena SEC(".maps");

/*
 * Occupies page 0 so no allocation lands at arena offset 0, which the
 * nullable tests below must be able to tell apart from NULL.
 */
u64 __arena arena_pad;

/* volatile to force the scalar reloads below */
volatile u64 stash;

SEC("syscall")
__arch_x86_64
__success __retval(0)
int arena_arg_forms(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
	u64 __arena *val;
	u64 ret;

	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	if (!val)
		return 1;

	/* PTR_TO_ARENA argument */
	*val = 41;
	ret = bpf_kfunc_arena_arg_test((u64 *)val);
	if (ret != 41 || *val != 42)
		return 2;

	/* the low 32 bits as a scalar */
	stash = (u32)(u64)val;
	ret = bpf_kfunc_arena_arg_test((u64 *)stash);
	if (ret != 42 || *val != 43)
		return 3;

	/* the full user address as a scalar */
	stash = (u64)val;
	bpf_addr_space_cast(stash, 1, 0);
	ret = bpf_kfunc_arena_arg_test((u64 *)stash);
	if (ret != 43 || *val != 44)
		return 4;

	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
	return 0;
}

/*
 * Pin the rebase semantics using the capture kfuncs, which return the raw
 * argument value: __arena rebases unconditionally, so zero low 32 bits
 * arrive as the arena kernel base, while __arena__nullable turns them into
 * NULL.
 */
SEC("syscall")
__arch_x86_64
__success __retval(0)
int arena_arg_rebase(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
	u64 __arena *val;
	u64 base, off;

	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	if (!val)
		return 1;

	base = bpf_kfunc_arena_cap_test(NULL);
	if (!base)
		return 2;

	/* only the low 32 bits contribute */
	stash = 0xbadc0ffe00000000;
	if (bpf_kfunc_arena_cap_test((u64 *)stash) != base)
		return 3;

	off = (u32)(u64)val;
	if (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)
		return 4;

	if (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)
		return 5;

	stash = 0xbadc0ffe00000000;
	if (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)
		return 6;

	if (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)
		return 7;

	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
	return 0;
}

SEC("syscall")
__arch_x86_64
__success __retval(0)
int arena_args5(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
	u64 __arena *val;

	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	if (!val)
		return 1;

	val[0] = 1;
	val[1] = 2;
	val[2] = 4;
	val[3] = 8;
	val[4] = 16;

	if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
				       (u64 *)&val[2], (u64 *)&val[3],
				       (u64 *)&val[4]) != 31)
		return 2;
	if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
				       (u64 *)&val[2], (u64 *)&val[3], NULL) != 15)
		return 3;

	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
	return 0;
}

SEC("syscall")
__arch_x86_64
__success __retval(0)
int arena_arg_mixed(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
	u64 __arena *val;

	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	if (!val)
		return 1;

	val[0] = 7;
	val[1] = 5;

	if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], NULL) != 7)
		return 2;

	if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], (u64 *)&val[1]) != 12)
		return 3;

	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
	return 0;
}

/* kernel-side faults on unpopulated pages recover via the scratch page */
SEC("syscall")
__arch_x86_64
__success __retval(0)
int arena_arg_unpopulated(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
	u64 __arena *val;

	val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	if (!val)
		return 1;

	stash = (u64)val + PAGE_SIZE;
	bpf_kfunc_arena_arg_test((u64 *)stash);

	bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
	return 0;
}

SEC("syscall")
__arch_x86_64
__failure __msg("arena pointer requires a program with an associated arena")
int arena_arg_no_arena(void *ctx)
{
	bpf_kfunc_arena_arg_test((u64 *)1);
	return 0;
}

SEC("syscall")
__arch_x86_64
__failure __msg("is not a pointer to arena or scalar")
int arena_arg_bad_reg(void *ctx)
{
	u64 buf = 0;

	/* use the arena so the program passes the arena presence check */
	bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	bpf_kfunc_arena_arg_test(&buf);
	return 0;
}

#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
	defined(__BPF_FEATURE_STACK_ARGUMENT)
SEC("syscall")
__arch_x86_64
__failure __msg("arena pointer cannot be a stack argument")
int arena_arg_stack(void *ctx)
{
	bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
	bpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);
	return 0;
}
#else
SEC("syscall")
__arch_x86_64
__description("arena_arg_stack: not supported, dummy test")
__success
int arena_arg_stack(void *ctx)
{
	return 0;
}
#endif

char _license[] SEC("license") = "GPL";