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