Skip to content

Commit 15cf392

Browse files
kkdwivediAlexei Starovoitov
authored andcommitted
selftests/bpf: Add stress test for rqspinlock in NMI
Introduce a kernel module that will exercise lock acquisition in the NMI path, and bias toward creating contention such that NMI waiters end up being non-head waiters. Prior to the rqspinlock fix made in the commit 0d80e7f ("rqspinlock: Choose trylock fallback for NMI waiters"), it was possible for the queueing path of non-head waiters to get stuck in NMI, which this stress test reproduces fairly easily with just 3 CPUs. Both AA and ABBA flavors are supported, and it will serve as a test case for future fixes that address this corner case. More information about the problem in question is available in the commit cited above. When the fix is reverted, this stress test will lock up the system. To enable this test automatically through the test_progs infrastructure, add a load_module_params API to exercise both AA and ABBA cases when running the test. Note that the test runs for at most 5 seconds, and becomes a noop after that, in order to allow the system to make forward progress. In addition, CPU 0 is always kept untouched by the created threads and NMIs. The test will automatically scale to the number of available online CPUs. Note that at least 3 CPUs are necessary to run this test, hence skip the selftest in case the environment has less than 3 CPUs available. Signed-off-by: Kumar Kartikeya Dwivedi <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 0e8e60e commit 15cf392

File tree

6 files changed

+240
-4
lines changed

6 files changed

+240
-4
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ TEST_PROGS_EXTENDED := \
120120
test_bpftool.py
121121

122122
TEST_KMODS := bpf_testmod.ko bpf_test_no_cfi.ko bpf_test_modorder_x.ko \
123-
bpf_test_modorder_y.ko
123+
bpf_test_modorder_y.ko bpf_test_rqspinlock.ko
124124
TEST_KMOD_TARGETS = $(addprefix $(OUTPUT)/,$(TEST_KMODS))
125125

126126
# Compile but not part of 'make run_tests'

tools/testing/selftests/bpf/prog_tests/res_spin_lock.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,19 @@ void test_res_spin_lock_success(void)
9999
res_spin_lock__destroy(skel);
100100
return;
101101
}
102+
103+
void serial_test_res_spin_lock_stress(void)
104+
{
105+
if (libbpf_num_possible_cpus() < 3) {
106+
test__skip();
107+
return;
108+
}
109+
110+
ASSERT_OK(load_module("bpf_test_rqspinlock.ko", false), "load module AA");
111+
sleep(5);
112+
unload_module("bpf_test_rqspinlock", false);
113+
114+
ASSERT_OK(load_module_params("bpf_test_rqspinlock.ko", "test_ab=1", false), "load module ABBA");
115+
sleep(5);
116+
unload_module("bpf_test_rqspinlock", false);
117+
}

tools/testing/selftests/bpf/test_kmods/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Q = @
88
endif
99

1010
MODULES = bpf_testmod.ko bpf_test_no_cfi.ko bpf_test_modorder_x.ko \
11-
bpf_test_modorder_y.ko
11+
bpf_test_modorder_y.ko bpf_test_rqspinlock.ko
1212

1313
$(foreach m,$(MODULES),$(eval obj-m += $(m:.ko=.o)))
1414

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
#include <linux/sched.h>
4+
#include <linux/smp.h>
5+
#include <linux/delay.h>
6+
#include <linux/module.h>
7+
#include <linux/prandom.h>
8+
#include <asm/rqspinlock.h>
9+
#include <linux/perf_event.h>
10+
#include <linux/kthread.h>
11+
#include <linux/atomic.h>
12+
#include <linux/slab.h>
13+
14+
static struct perf_event_attr hw_attr = {
15+
.type = PERF_TYPE_HARDWARE,
16+
.config = PERF_COUNT_HW_CPU_CYCLES,
17+
.size = sizeof(struct perf_event_attr),
18+
.pinned = 1,
19+
.disabled = 1,
20+
.sample_period = 100000,
21+
};
22+
23+
static rqspinlock_t lock_a;
24+
static rqspinlock_t lock_b;
25+
26+
static struct perf_event **rqsl_evts;
27+
static int rqsl_nevts;
28+
29+
static bool test_ab = false;
30+
module_param(test_ab, bool, 0644);
31+
MODULE_PARM_DESC(test_ab, "Test ABBA situations instead of AA situations");
32+
33+
static struct task_struct **rqsl_threads;
34+
static int rqsl_nthreads;
35+
static atomic_t rqsl_ready_cpus = ATOMIC_INIT(0);
36+
37+
static int pause = 0;
38+
39+
static bool nmi_locks_a(int cpu)
40+
{
41+
return (cpu & 1) && test_ab;
42+
}
43+
44+
static int rqspinlock_worker_fn(void *arg)
45+
{
46+
int cpu = smp_processor_id();
47+
unsigned long flags;
48+
int ret;
49+
50+
if (cpu) {
51+
atomic_inc(&rqsl_ready_cpus);
52+
53+
while (!kthread_should_stop()) {
54+
if (READ_ONCE(pause)) {
55+
msleep(1000);
56+
continue;
57+
}
58+
if (nmi_locks_a(cpu))
59+
ret = raw_res_spin_lock_irqsave(&lock_b, flags);
60+
else
61+
ret = raw_res_spin_lock_irqsave(&lock_a, flags);
62+
mdelay(20);
63+
if (nmi_locks_a(cpu) && !ret)
64+
raw_res_spin_unlock_irqrestore(&lock_b, flags);
65+
else if (!ret)
66+
raw_res_spin_unlock_irqrestore(&lock_a, flags);
67+
cpu_relax();
68+
}
69+
return 0;
70+
}
71+
72+
while (!kthread_should_stop()) {
73+
int expected = rqsl_nthreads > 0 ? rqsl_nthreads - 1 : 0;
74+
int ready = atomic_read(&rqsl_ready_cpus);
75+
76+
if (ready == expected && !READ_ONCE(pause)) {
77+
for (int i = 0; i < rqsl_nevts; i++)
78+
perf_event_enable(rqsl_evts[i]);
79+
pr_err("Waiting 5 secs to pause the test\n");
80+
msleep(1000 * 5);
81+
WRITE_ONCE(pause, 1);
82+
pr_err("Paused the test\n");
83+
} else {
84+
msleep(1000);
85+
cpu_relax();
86+
}
87+
}
88+
return 0;
89+
}
90+
91+
static void nmi_cb(struct perf_event *event, struct perf_sample_data *data,
92+
struct pt_regs *regs)
93+
{
94+
int cpu = smp_processor_id();
95+
unsigned long flags;
96+
int ret;
97+
98+
if (!cpu || READ_ONCE(pause))
99+
return;
100+
101+
if (nmi_locks_a(cpu))
102+
ret = raw_res_spin_lock_irqsave(&lock_a, flags);
103+
else
104+
ret = raw_res_spin_lock_irqsave(test_ab ? &lock_b : &lock_a, flags);
105+
106+
mdelay(10);
107+
108+
if (nmi_locks_a(cpu) && !ret)
109+
raw_res_spin_unlock_irqrestore(&lock_a, flags);
110+
else if (!ret)
111+
raw_res_spin_unlock_irqrestore(test_ab ? &lock_b : &lock_a, flags);
112+
}
113+
114+
static void free_rqsl_threads(void)
115+
{
116+
int i;
117+
118+
if (rqsl_threads) {
119+
for_each_online_cpu(i) {
120+
if (rqsl_threads[i])
121+
kthread_stop(rqsl_threads[i]);
122+
}
123+
kfree(rqsl_threads);
124+
}
125+
}
126+
127+
static void free_rqsl_evts(void)
128+
{
129+
int i;
130+
131+
if (rqsl_evts) {
132+
for (i = 0; i < rqsl_nevts; i++) {
133+
if (rqsl_evts[i])
134+
perf_event_release_kernel(rqsl_evts[i]);
135+
}
136+
kfree(rqsl_evts);
137+
}
138+
}
139+
140+
static int bpf_test_rqspinlock_init(void)
141+
{
142+
int i, ret;
143+
int ncpus = num_online_cpus();
144+
145+
pr_err("Mode = %s\n", test_ab ? "ABBA" : "AA");
146+
147+
if (ncpus < 3)
148+
return -ENOTSUPP;
149+
150+
raw_res_spin_lock_init(&lock_a);
151+
raw_res_spin_lock_init(&lock_b);
152+
153+
rqsl_evts = kcalloc(ncpus - 1, sizeof(*rqsl_evts), GFP_KERNEL);
154+
if (!rqsl_evts)
155+
return -ENOMEM;
156+
rqsl_nevts = ncpus - 1;
157+
158+
for (i = 1; i < ncpus; i++) {
159+
struct perf_event *e;
160+
161+
e = perf_event_create_kernel_counter(&hw_attr, i, NULL, nmi_cb, NULL);
162+
if (IS_ERR(e)) {
163+
ret = PTR_ERR(e);
164+
goto err_perf_events;
165+
}
166+
rqsl_evts[i - 1] = e;
167+
}
168+
169+
rqsl_threads = kcalloc(ncpus, sizeof(*rqsl_threads), GFP_KERNEL);
170+
if (!rqsl_threads) {
171+
ret = -ENOMEM;
172+
goto err_perf_events;
173+
}
174+
rqsl_nthreads = ncpus;
175+
176+
for_each_online_cpu(i) {
177+
struct task_struct *t;
178+
179+
t = kthread_create(rqspinlock_worker_fn, NULL, "rqsl_w/%d", i);
180+
if (IS_ERR(t)) {
181+
ret = PTR_ERR(t);
182+
goto err_threads_create;
183+
}
184+
kthread_bind(t, i);
185+
rqsl_threads[i] = t;
186+
wake_up_process(t);
187+
}
188+
return 0;
189+
190+
err_threads_create:
191+
free_rqsl_threads();
192+
err_perf_events:
193+
free_rqsl_evts();
194+
return ret;
195+
}
196+
197+
module_init(bpf_test_rqspinlock_init);
198+
199+
static void bpf_test_rqspinlock_exit(void)
200+
{
201+
free_rqsl_threads();
202+
free_rqsl_evts();
203+
}
204+
205+
module_exit(bpf_test_rqspinlock_exit);
206+
207+
MODULE_AUTHOR("Kumar Kartikeya Dwivedi");
208+
MODULE_DESCRIPTION("BPF rqspinlock stress test module");
209+
MODULE_LICENSE("GPL");

tools/testing/selftests/bpf/testing_helpers.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ int unload_module(const char *name, bool verbose)
399399
return 0;
400400
}
401401

402-
int load_module(const char *path, bool verbose)
402+
static int __load_module(const char *path, const char *param_values, bool verbose)
403403
{
404404
int fd;
405405

@@ -411,7 +411,7 @@ int load_module(const char *path, bool verbose)
411411
fprintf(stdout, "Can't find %s kernel module: %d\n", path, -errno);
412412
return -ENOENT;
413413
}
414-
if (finit_module(fd, "", 0)) {
414+
if (finit_module(fd, param_values, 0)) {
415415
fprintf(stdout, "Failed to load %s into the kernel: %d\n", path, -errno);
416416
close(fd);
417417
return -EINVAL;
@@ -423,6 +423,16 @@ int load_module(const char *path, bool verbose)
423423
return 0;
424424
}
425425

426+
int load_module_params(const char *path, const char *param_values, bool verbose)
427+
{
428+
return __load_module(path, param_values, verbose);
429+
}
430+
431+
int load_module(const char *path, bool verbose)
432+
{
433+
return __load_module(path, "", verbose);
434+
}
435+
426436
int unload_bpf_testmod(bool verbose)
427437
{
428438
return unload_module("bpf_testmod", verbose);

tools/testing/selftests/bpf/testing_helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ int kern_sync_rcu(void);
3939
int finit_module(int fd, const char *param_values, int flags);
4040
int delete_module(const char *name, int flags);
4141
int load_module(const char *path, bool verbose);
42+
int load_module_params(const char *path, const char *param_values, bool verbose);
4243
int unload_module(const char *name, bool verbose);
4344

4445
static inline __u64 get_time_ns(void)

0 commit comments

Comments
 (0)