blob: 9b646d134a45ecffda98031f1f2570e0ed0b231d [file]
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include "struct_ops_assoc.skel.h"
#include "struct_ops_assoc_reuse.skel.h"
#include "struct_ops_assoc_in_timer.skel.h"
static void test_st_ops_assoc(void)
{
struct struct_ops_assoc *skel = NULL;
int err, pid;
skel = struct_ops_assoc__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc__open"))
goto out;
/* cannot explicitly associate struct_ops program */
err = bpf_program__assoc_struct_ops(skel->progs.test_1_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(test_1_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)");
/* sys_enter_prog_a already associated with map_a */
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
skel->maps.st_ops_map_b, NULL);
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_b)");
err = struct_ops_assoc__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/* run tracing prog that calls .test_1 and checks return */
pid = getpid();
skel->bss->test_pid = pid;
sys_gettid();
skel->bss->test_pid = 0;
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
/* run syscall_prog that calls .test_1 and checks return */
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
out:
struct_ops_assoc__destroy(skel);
}
static void test_st_ops_assoc_reuse(void)
{
struct struct_ops_assoc_reuse *skel = NULL;
int err;
skel = struct_ops_assoc_reuse__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_reuse__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
skel->maps.st_ops_map_a, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
skel->maps.st_ops_map_b, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
err = struct_ops_assoc_reuse__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/* run syscall_prog that calls .test_1 and checks return */
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
out:
struct_ops_assoc_reuse__destroy(skel);
}
static void test_st_ops_assoc_in_timer(void)
{
struct struct_ops_assoc_in_timer *skel = NULL;
int err;
skel = struct_ops_assoc_in_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
skel->maps.st_ops_map, NULL);
ASSERT_OK(err, "bpf_program__assoc_struct_ops");
err = struct_ops_assoc_in_timer__attach(skel);
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
goto out;
/*
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again
* immediately.
*/
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
ASSERT_OK(err, "bpf_prog_test_run_opts");
/* Check the return of the kfunc after timer_cb runs */
while (!READ_ONCE(skel->bss->timer_cb_run))
sched_yield();
ASSERT_EQ(skel->bss->timer_test_1_ret, 1234, "skel->bss->timer_test_1_ret");
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
out:
struct_ops_assoc_in_timer__destroy(skel);
}
static void run_st_ops_assoc_in_timer_no_uref(int timer_ns, int wait_map_free,
int spin_loops)
{
struct struct_ops_assoc_in_timer *skel = NULL;
struct bpf_link *link;
int err;
skel = struct_ops_assoc_in_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
goto out;
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
skel->maps.st_ops_map, NULL);
if (!ASSERT_OK(err, "bpf_program__assoc_struct_ops"))
goto out;
link = bpf_map__attach_struct_ops(skel->maps.st_ops_map);
if (!ASSERT_OK_PTR(link, "bpf_map__attach_struct_ops"))
goto out;
/*
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again.
* timer_cb will run timer_ns after syscall_prog runs and, with wait_map_free, every
* timer_ns after that until the map is freed. With 500ms, it first runs when the
* user space no longer holds a reference to st_ops_map.
*/
skel->bss->timer_ns = timer_ns;
skel->bss->wait_map_free = wait_map_free;
skel->bss->spin_loops = spin_loops;
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
if (!ASSERT_OK(err, "bpf_prog_test_run_opts")) {
bpf_link__destroy(link);
goto out;
}
/* Detach and close struct_ops map to cause it to be freed */
bpf_link__destroy(link);
close(bpf_program__fd(skel->progs.syscall_prog));
close(bpf_map__fd(skel->maps.st_ops_map));
/* Check the return of the kfunc after timer_cb runs */
while (!READ_ONCE(skel->bss->timer_cb_run))
sched_yield();
ASSERT_EQ(skel->bss->timer_test_1_ret, -1, "skel->bss->timer_test_1_ret");
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
out:
struct_ops_assoc_in_timer__destroy(skel);
}
static void test_st_ops_assoc_in_timer_no_uref(void)
{
run_st_ops_assoc_in_timer_no_uref(500000000, 1, 0);
}
/*
* Keep calling .test_1 through the association from the timer callback, and
* stay in it for a while each time, while the map is being freed. The map
* must not be freed under a running .test_1.
*/
static void test_st_ops_assoc_in_timer_free(void)
{
run_st_ops_assoc_in_timer_no_uref(0, 1, 1 << 20);
}
void test_struct_ops_assoc(void)
{
if (test__start_subtest("st_ops_assoc"))
test_st_ops_assoc();
if (test__start_subtest("st_ops_assoc_reuse"))
test_st_ops_assoc_reuse();
if (test__start_subtest("st_ops_assoc_in_timer"))
test_st_ops_assoc_in_timer();
if (test__start_subtest("st_ops_assoc_in_timer_no_uref"))
test_st_ops_assoc_in_timer_no_uref();
if (test__start_subtest("st_ops_assoc_in_timer_free"))
test_st_ops_assoc_in_timer_free();
}