Commit 0a5d2efa authored by Hou Tao's avatar Hou Tao Committed by Alexei Starovoitov
Browse files

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: default avatarHou Tao <houtao1@huawei.com>
Link: https://lore.kernel.org/r/20250117101816.2101857-6-houtao@huaweicloud.com


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent 58f038e6
Loading
Loading
Loading
Loading
+165 −0
Original line number Diff line number Diff line
// 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);
}
+71 −0
Original line number Diff line number Diff line
// 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";