]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
selftests/bpf: Add test case for the freeing of bpf_timer
authorHou Tao <houtao1@huawei.com>
Fri, 17 Jan 2025 10:18:16 +0000 (18:18 +0800)
committerAlexei Starovoitov <ast@kernel.org>
Mon, 20 Jan 2025 17:09:02 +0000 (09:09 -0800)
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>
tools/testing/selftests/bpf/prog_tests/free_timer.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/free_timer.c [new file with mode: 0644]

diff --git a/tools/testing/selftests/bpf/prog_tests/free_timer.c b/tools/testing/selftests/bpf/prog_tests/free_timer.c
new file mode 100644 (file)
index 0000000..b7b77a6
--- /dev/null
@@ -0,0 +1,165 @@
+// 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);
+}
diff --git a/tools/testing/selftests/bpf/progs/free_timer.c b/tools/testing/selftests/bpf/progs/free_timer.c
new file mode 100644 (file)
index 0000000..4501ae8
--- /dev/null
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
+#include <linux/bpf.h>
+#include <time.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+
+#define MAX_ENTRIES 8
+
+struct map_value {
+       struct bpf_timer timer;
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_HASH);
+       __type(key, int);
+       __type(value, struct map_value);
+       __uint(max_entries, MAX_ENTRIES);
+} map SEC(".maps");
+
+static int timer_cb(void *map, void *key, struct map_value *value)
+{
+       volatile int sum = 0;
+       int i;
+
+       bpf_for(i, 0, 1024 * 1024) sum += i;
+
+       return 0;
+}
+
+static int start_cb(int key)
+{
+       struct map_value *value;
+
+       value = bpf_map_lookup_elem(&map, (void *)&key);
+       if (!value)
+               return 0;
+
+       bpf_timer_init(&value->timer, &map, CLOCK_MONOTONIC);
+       bpf_timer_set_callback(&value->timer, timer_cb);
+       /* Hope 100us will be enough to wake-up and run the overwrite thread */
+       bpf_timer_start(&value->timer, 100000, BPF_F_TIMER_CPU_PIN);
+
+       return 0;
+}
+
+static int overwrite_cb(int key)
+{
+       struct map_value zero = {};
+
+       /* Free the timer which may run on other CPU */
+       bpf_map_update_elem(&map, (void *)&key, &zero, BPF_ANY);
+
+       return 0;
+}
+
+SEC("syscall")
+int BPF_PROG(start_timer)
+{
+       bpf_loop(MAX_ENTRIES, start_cb, NULL, 0);
+       return 0;
+}
+
+SEC("syscall")
+int BPF_PROG(overwrite_timer)
+{
+       bpf_loop(MAX_ENTRIES, overwrite_cb, NULL, 0);
+       return 0;
+}
+
+char _license[] SEC("license") = "GPL";