1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
4 */
5
6 #define _GNU_SOURCE
7
8 #include <errno.h>
9 #include <pthread.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13
14 #include <linux/prctl.h>
15 #include <sys/prctl.h>
16
17 #include "../../kselftest_harness.h"
18
19 #define MAX_THREADS 64
20
21 static pthread_barrier_t barrier_main;
22 static pthread_mutex_t global_lock;
23 static pthread_t threads[MAX_THREADS];
24 static int counter;
25
26 #ifndef PR_FUTEX_HASH
27 #define PR_FUTEX_HASH 78
28 # define PR_FUTEX_HASH_SET_SLOTS 1
29 # define PR_FUTEX_HASH_GET_SLOTS 2
30 #endif
31
futex_hash_slots_set(unsigned int slots)32 static int futex_hash_slots_set(unsigned int slots)
33 {
34 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0);
35 }
36
futex_hash_slots_get(void)37 static int futex_hash_slots_get(void)
38 {
39 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
40 }
41
futex_hash_slots_set_verify(int slots)42 static void futex_hash_slots_set_verify(int slots)
43 {
44 int ret;
45
46 ret = futex_hash_slots_set(slots);
47 if (ret != 0) {
48 ksft_test_result_fail("Failed to set slots to %d: %m\n", slots);
49 ksft_finished();
50 }
51 ret = futex_hash_slots_get();
52 if (ret != slots) {
53 ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n",
54 slots, ret);
55 ksft_finished();
56 }
57 ksft_test_result_pass("SET and GET slots %d passed\n", slots);
58 }
59
futex_hash_slots_set_must_fail(int slots)60 static void futex_hash_slots_set_must_fail(int slots)
61 {
62 int ret;
63
64 ret = futex_hash_slots_set(slots);
65 ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n",
66 slots);
67 }
68
thread_return_fn(void * arg)69 static void *thread_return_fn(void *arg)
70 {
71 return NULL;
72 }
73
thread_lock_fn(void * arg)74 static void *thread_lock_fn(void *arg)
75 {
76 pthread_barrier_wait(&barrier_main);
77
78 pthread_mutex_lock(&global_lock);
79 counter++;
80 usleep(20);
81 pthread_mutex_unlock(&global_lock);
82 return NULL;
83 }
84
create_max_threads(void * (* thread_fn)(void *))85 static void create_max_threads(void *(*thread_fn)(void *))
86 {
87 int i, ret;
88
89 for (i = 0; i < MAX_THREADS; i++) {
90 ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
91 if (ret)
92 ksft_exit_fail_msg("pthread_create failed: %m\n");
93 }
94 }
95
join_max_threads(void)96 static void join_max_threads(void)
97 {
98 int i, ret;
99
100 for (i = 0; i < MAX_THREADS; i++) {
101 ret = pthread_join(threads[i], NULL);
102 if (ret)
103 ksft_exit_fail_msg("pthread_join failed for thread %d\n", i);
104 }
105 }
106
107 #define SEC_IN_NSEC 1000000000
108 #define MSEC_IN_NSEC 1000000
109
futex_dummy_op(void)110 static void futex_dummy_op(void)
111 {
112 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
113 struct timespec timeout;
114 int ret;
115
116 pthread_mutex_lock(&lock);
117 clock_gettime(CLOCK_REALTIME, &timeout);
118 timeout.tv_nsec += 100 * MSEC_IN_NSEC;
119 if (timeout.tv_nsec >= SEC_IN_NSEC) {
120 timeout.tv_nsec -= SEC_IN_NSEC;
121 timeout.tv_sec++;
122 }
123 ret = pthread_mutex_timedlock(&lock, &timeout);
124 if (ret == 0)
125 ksft_exit_fail_msg("Successfully locked an already locked mutex.\n");
126
127 if (ret != ETIMEDOUT)
128 ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret);
129 }
130
131 static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";
132 static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";
133
TEST(priv_hash)134 TEST(priv_hash)
135 {
136 int futex_slots1, futex_slotsn, online_cpus;
137 pthread_mutexattr_t mutex_attr_pi;
138 int ret, retry = 20;
139
140 ret = pthread_mutexattr_init(&mutex_attr_pi);
141 ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
142 ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
143 if (ret != 0) {
144 ksft_exit_fail_msg("Failed to initialize pthread mutex.\n");
145 }
146 /* First thread, expect to be 0, not yet initialized */
147 ret = futex_hash_slots_get();
148 if (ret != 0)
149 ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret);
150
151 ksft_test_result_pass("Basic get slots and immutable status.\n");
152 ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
153 if (ret != 0)
154 ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret);
155
156 ret = pthread_join(threads[0], NULL);
157 if (ret != 0)
158 ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret);
159
160 /* First thread, has to initialize private hash */
161 futex_slots1 = futex_hash_slots_get();
162 if (futex_slots1 <= 0) {
163 ksft_print_msg("Current hash buckets: %d\n", futex_slots1);
164 ksft_exit_fail_msg("%s", test_msg_auto_create);
165 }
166
167 ksft_test_result_pass("%s", test_msg_auto_create);
168
169 online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
170 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
171 if (ret != 0)
172 ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n");
173
174 ret = pthread_mutex_lock(&global_lock);
175 if (ret != 0)
176 ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n");
177
178 counter = 0;
179 create_max_threads(thread_lock_fn);
180 pthread_barrier_wait(&barrier_main);
181
182 /*
183 * The current default size of hash buckets is 16. The auto increase
184 * works only if more than 16 CPUs are available.
185 */
186 ksft_print_msg("Online CPUs: %d\n", online_cpus);
187 if (online_cpus > 16) {
188 retry_getslots:
189 futex_slotsn = futex_hash_slots_get();
190 if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
191 retry--;
192 /*
193 * Auto scaling on thread creation can be slightly delayed
194 * because it waits for a RCU grace period twice. The new
195 * private hash is assigned upon the first futex operation
196 * after grace period.
197 * To cover all this for testing purposes the function
198 * below will acquire a lock and acquire it again with a
199 * 100ms timeout which must timeout. This ensures we
200 * sleep for 100ms and issue a futex operation.
201 */
202 if (retry > 0) {
203 futex_dummy_op();
204 goto retry_getslots;
205 }
206 ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",
207 futex_slots1, futex_slotsn);
208 ksft_exit_fail_msg("%s", test_msg_auto_inc);
209 }
210 ksft_test_result_pass("%s", test_msg_auto_inc);
211 } else {
212 ksft_test_result_skip("%s", test_msg_auto_inc);
213 }
214 ret = pthread_mutex_unlock(&global_lock);
215
216 /* Once the user changes it, it has to be what is set */
217 futex_hash_slots_set_verify(2);
218 futex_hash_slots_set_verify(4);
219 futex_hash_slots_set_verify(8);
220 futex_hash_slots_set_verify(32);
221 futex_hash_slots_set_verify(16);
222
223 ret = futex_hash_slots_set(15);
224 ksft_test_result(ret < 0, "Use 15 slots\n");
225
226 futex_hash_slots_set_verify(2);
227 join_max_threads();
228 ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n",
229 counter, MAX_THREADS);
230 counter = 0;
231 /* Once the user set something, auto resize must be disabled */
232 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
233
234 create_max_threads(thread_lock_fn);
235 join_max_threads();
236
237 ret = futex_hash_slots_get();
238 ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n",
239 ret);
240
241 futex_hash_slots_set_must_fail(1 << 29);
242 futex_hash_slots_set_verify(4);
243
244 /*
245 * Once the global hash has been requested, then this requested can not
246 * be undone.
247 */
248 ret = futex_hash_slots_set(0);
249 ksft_test_result(ret == 0, "Global hash request\n");
250 if (ret != 0)
251 return;
252
253 futex_hash_slots_set_must_fail(4);
254 futex_hash_slots_set_must_fail(8);
255 futex_hash_slots_set_must_fail(8);
256 futex_hash_slots_set_must_fail(0);
257 futex_hash_slots_set_must_fail(6);
258
259 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
260 if (ret != 0)
261 ksft_exit_fail_msg("pthread_barrier_init failed: %m\n");
262
263 create_max_threads(thread_lock_fn);
264 join_max_threads();
265
266 ret = futex_hash_slots_get();
267 ksft_test_result(ret == 0, "Continue to use global hash\n");
268 }
269
270 TEST_HARNESS_MAIN
271