linux/tools/testing/selftests/bpf/prog_tests/free_timer.c
Hou Tao 0a5d2efa38 selftests/bpf: Add test case for the freeing of bpf_timer
The main purpose of the test is to demonstrate the lock problem for the
free of bpf_timer under PREEMPT_RT. When freeing a bpf_timer which is
running on other CPU in bpf_timer_cancel_and_free(), hrtimer_cancel()
will try to acquire a spin-lock (namely softirq_expiry_lock), however
the freeing procedure has already held a raw-spin-lock.

The test first creates two threads: one to start timers and the other to
free timers. The start-timers thread will start the timer and then wake
up the free-timers thread to free these timers when the starts complete.
After freeing, the free-timer thread will wake up the start-timer thread
to complete the current iteration. A loop of 10 iterations is used.

Signed-off-by: Hou Tao <houtao1@huawei.com>
Link: https://lore.kernel.org/r/20250117101816.2101857-6-houtao@huaweicloud.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2025-01-20 09:09:02 -08:00

165 lines
3.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <test_progs.h>
#include "free_timer.skel.h"
struct run_ctx {
struct bpf_program *start_prog;
struct bpf_program *overwrite_prog;
pthread_barrier_t notify;
int loop;
bool start;
bool stop;
};
static void start_threads(struct run_ctx *ctx)
{
ctx->start = true;
}
static void stop_threads(struct run_ctx *ctx)
{
ctx->stop = true;
/* Guarantee the order between ->stop and ->start */
__atomic_store_n(&ctx->start, true, __ATOMIC_RELEASE);
}
static int wait_for_start(struct run_ctx *ctx)
{
while (!__atomic_load_n(&ctx->start, __ATOMIC_ACQUIRE))
usleep(10);
return ctx->stop;
}
static void *overwrite_timer_fn(void *arg)
{
struct run_ctx *ctx = arg;
int loop, fd, err;
cpu_set_t cpuset;
long ret = 0;
/* Pin on CPU 0 */
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
/* Is the thread being stopped ? */
err = wait_for_start(ctx);
if (err)
return NULL;
fd = bpf_program__fd(ctx->overwrite_prog);
loop = ctx->loop;
while (loop-- > 0) {
LIBBPF_OPTS(bpf_test_run_opts, opts);
/* Wait for start thread to complete */
pthread_barrier_wait(&ctx->notify);
/* Overwrite timers */
err = bpf_prog_test_run_opts(fd, &opts);
if (err)
ret |= 1;
else if (opts.retval)
ret |= 2;
/* Notify start thread to start timers */
pthread_barrier_wait(&ctx->notify);
}
return (void *)ret;
}
static void *start_timer_fn(void *arg)
{
struct run_ctx *ctx = arg;
int loop, fd, err;
cpu_set_t cpuset;
long ret = 0;
/* Pin on CPU 1 */
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
/* Is the thread being stopped ? */
err = wait_for_start(ctx);
if (err)
return NULL;
fd = bpf_program__fd(ctx->start_prog);
loop = ctx->loop;
while (loop-- > 0) {
LIBBPF_OPTS(bpf_test_run_opts, opts);
/* Run the prog to start timer */
err = bpf_prog_test_run_opts(fd, &opts);
if (err)
ret |= 4;
else if (opts.retval)
ret |= 8;
/* Notify overwrite thread to do overwrite */
pthread_barrier_wait(&ctx->notify);
/* Wait for overwrite thread to complete */
pthread_barrier_wait(&ctx->notify);
}
return (void *)ret;
}
void test_free_timer(void)
{
struct free_timer *skel;
struct bpf_program *prog;
struct run_ctx ctx;
pthread_t tid[2];
void *ret;
int err;
skel = free_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_load"))
return;
memset(&ctx, 0, sizeof(ctx));
prog = bpf_object__find_program_by_name(skel->obj, "start_timer");
if (!ASSERT_OK_PTR(prog, "find start prog"))
goto out;
ctx.start_prog = prog;
prog = bpf_object__find_program_by_name(skel->obj, "overwrite_timer");
if (!ASSERT_OK_PTR(prog, "find overwrite prog"))
goto out;
ctx.overwrite_prog = prog;
pthread_barrier_init(&ctx.notify, NULL, 2);
ctx.loop = 10;
err = pthread_create(&tid[0], NULL, start_timer_fn, &ctx);
if (!ASSERT_OK(err, "create start_timer"))
goto out;
err = pthread_create(&tid[1], NULL, overwrite_timer_fn, &ctx);
if (!ASSERT_OK(err, "create overwrite_timer")) {
stop_threads(&ctx);
goto out;
}
start_threads(&ctx);
ret = NULL;
err = pthread_join(tid[0], &ret);
ASSERT_EQ(err | (long)ret, 0, "start_timer");
ret = NULL;
err = pthread_join(tid[1], &ret);
ASSERT_EQ(err | (long)ret, 0, "overwrite_timer");
out:
free_timer__destroy(skel);
}