1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2019 Xiao Yang <ice_yangxiao@163.com>
4 *
5 * Description:
6 * Testcase to check the basic functionality of futex(FUTEX_CMP_REQUEUE).
7 * futex(FUTEX_CMP_REQUEUE) can wake up the number of waiters specified
8 * by val argument and then requeue the number of waiters limited by val2
9 * argument(i.e. move some remaining waiters from uaddr to uaddr2 address).
10 */
11
12 #include <errno.h>
13 #include <sys/wait.h>
14 #include <stdlib.h>
15 #include <linux/futex.h>
16 #include <sys/time.h>
17
18 #include "tst_timer_test.h"
19 #include "tst_test.h"
20 #include "futextest.h"
21
22 struct shared_data {
23 futex_t futexes[2];
24 int spurious;
25 int test_done;
26 };
27
28 static struct shared_data *sd;
29 static int max_sleep_ms;
30
31 static struct tcase {
32 int num_waiters;
33 int set_wakes;
34 int set_requeues;
35 } tcases[] = {
36 {10, 3, 7},
37 {10, 0, 10},
38 {10, 2, 6},
39 {100, 50, 50},
40 {100, 0, 70},
41 {1000, 100, 900},
42 {1000, 300, 500},
43 };
44
45 static struct futex_test_variants variants[] = {
46 #if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
47 { .fntype = FUTEX_FN_FUTEX, .tstype = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
48 #endif
49
50 #if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
51 { .fntype = FUTEX_FN_FUTEX64, .tstype = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
52 #endif
53 };
54
do_child(void)55 static void do_child(void)
56 {
57 struct futex_test_variants *tv = &variants[tst_variant];
58 struct tst_ts usec = tst_ts_from_ms(tv->tstype, max_sleep_ms);
59 int slept_for_ms = 0;
60 int pid = getpid();
61 int ret = 0;
62
63 if (futex_wait(tv->fntype, &sd->futexes[0], sd->futexes[0], &usec, 0) == -1) {
64 if (errno == EAGAIN) {
65 /* spurious wakeup or signal */
66 tst_atomic_inc(&sd->spurious);
67 } else {
68 tst_res(TFAIL | TERRNO, "process %d wasn't woken up",
69 pid);
70 ret = 1;
71 }
72 }
73
74 /* make sure TST_PROCESS_STATE_WAIT() can always succeed */
75 while (!tst_atomic_load(&sd->test_done)
76 && (slept_for_ms < max_sleep_ms)) {
77 usleep(50000);
78 slept_for_ms += 50;
79 }
80
81 exit(ret);
82 }
83
verify_futex_cmp_requeue(unsigned int n)84 static void verify_futex_cmp_requeue(unsigned int n)
85 {
86 struct futex_test_variants *tv = &variants[tst_variant];
87 int num_requeues = 0, num_waits = 0, num_total = 0;
88 int i, status, spurious, woken_up;
89 struct tcase *tc = &tcases[n];
90 int pid[tc->num_waiters];
91 int exp_ret = tc->set_wakes + tc->set_requeues;
92
93 tst_atomic_store(0, &sd->spurious);
94 tst_atomic_store(0, &sd->test_done);
95 for (i = 0; i < tc->num_waiters; i++) {
96 pid[i] = SAFE_FORK();
97 if (!pid[i])
98 do_child();
99 }
100
101 for (i = 0; i < tc->num_waiters; i++)
102 TST_PROCESS_STATE_WAIT(pid[i], 'S', 0);
103
104 tst_res(TINFO, "Test %d: waiters: %d, wakes: %d, requeues: %d",
105 n, tc->num_waiters, tc->set_wakes, tc->set_requeues);
106
107 /*
108 * change futex value, so any spurious wakeups or signals after
109 * this point get bounced back to userspace.
110 */
111 sd->futexes[0]++;
112 sd->futexes[1]++;
113
114 /*
115 * Wakes up a maximum of tc->set_wakes waiters. tc->set_requeues
116 * specifies an upper limit on the number of waiters that are requeued.
117 * Returns the total number of waiters that were woken up or requeued.
118 */
119 TEST(futex_cmp_requeue(tv->fntype, &sd->futexes[0], sd->futexes[0],
120 &sd->futexes[1], tc->set_wakes, tc->set_requeues, 0));
121
122 /* Fail if more than requested wakes + requeues were returned */
123 if (TST_RET > exp_ret) {
124 tst_res(TFAIL, "futex_cmp_requeue() returned %ld, expected <= %d",
125 TST_RET, exp_ret);
126 } else {
127 tst_res(TINFO, "futex_cmp_requeue() returned %ld", TST_RET);
128 }
129
130 num_requeues = futex_wake(tv->fntype, &sd->futexes[1], tc->num_waiters, 0);
131 num_waits = futex_wake(tv->fntype, &sd->futexes[0], tc->num_waiters, 0);
132
133 tst_atomic_store(1, &sd->test_done);
134 for (i = 0; i < tc->num_waiters; i++) {
135 SAFE_WAITPID(pid[i], &status, 0);
136 if (WIFEXITED(status) && !WEXITSTATUS(status))
137 num_total++;
138 }
139
140 spurious = tst_atomic_load(&sd->spurious);
141 tst_res(TINFO, "children woken, futex0: %d, futex1: %d, "
142 "spurious wakeups: %d",
143 num_waits, num_requeues, spurious);
144
145 /* Fail if any waiter timed out */
146 if (num_total != tc->num_waiters) {
147 tst_res(TFAIL, "%d waiters were not woken up normally",
148 tc->num_waiters - num_total);
149 return;
150 }
151
152 /*
153 * num_requeues should be in range:
154 * (tc->set_requeues - spurious, tc->set_requeues)
155 *
156 * Fewer processes than requested can be requeued at futex1
157 * if some woke up spuriously. Finding more processes than
158 * requested at futex1 is always a failure.
159 */
160 if ((num_requeues < tc->set_requeues - spurious)
161 || (num_requeues > tc->set_requeues)) {
162 tst_res(TFAIL,
163 "requeued %d waiters, expected range: (%d, %d)",
164 num_requeues, tc->set_requeues - spurious,
165 tc->set_requeues);
166 return;
167 }
168
169 /*
170 * woken_up = (TST_RET - num_requeues) should be in range:
171 * (tc->set_wakes - spurious, tc->set_wakes + spurious)
172 *
173 * Fewer processes than requested can be woken up, if some of
174 * them woke up spuriously before requeue. More processes than
175 * requested may appear to be woken up, if some woke up
176 * spuriously after requeue.
177 */
178 woken_up = TST_RET - num_requeues;
179 if ((woken_up < tc->set_wakes - spurious)
180 || (woken_up > tc->set_wakes + spurious)) {
181 tst_res(TFAIL,
182 "woken up %d, expected range (%d, %d)",
183 woken_up, tc->set_wakes - spurious,
184 tc->set_wakes + spurious);
185 return;
186 }
187
188 tst_res(TPASS, "futex_cmp_requeue()");
189 }
190
setup(void)191 static void setup(void)
192 {
193 struct futex_test_variants *tv = &variants[tst_variant];
194
195 tst_res(TINFO, "Testing variant: %s", tv->desc);
196 futex_supported_by_kernel(tv->fntype);
197
198 max_sleep_ms = tst_multiply_timeout(5000);
199
200 sd = SAFE_MMAP(NULL, sizeof(*sd), PROT_READ | PROT_WRITE,
201 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
202
203 sd->futexes[0] = FUTEX_INITIALIZER;
204 sd->futexes[1] = FUTEX_INITIALIZER + 1000;
205 }
206
cleanup(void)207 static void cleanup(void)
208 {
209 if (sd)
210 SAFE_MUNMAP((void *)sd, sizeof(*sd));
211 }
212
213 static struct tst_test test = {
214 .setup = setup,
215 .cleanup = cleanup,
216 .tcnt = ARRAY_SIZE(tcases),
217 .test = verify_futex_cmp_requeue,
218 .test_variants = ARRAY_SIZE(variants),
219 .forks_child = 1,
220 };
221