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 "logging.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 FH_FLAG_IMMUTABLE (1ULL << 0)
30 # define PR_FUTEX_HASH_GET_SLOTS 2
31 # define PR_FUTEX_HASH_GET_IMMUTABLE 3
32 #endif
33
futex_hash_slots_set(unsigned int slots,int flags)34 static int futex_hash_slots_set(unsigned int slots, int flags)
35 {
36 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, flags);
37 }
38
futex_hash_slots_get(void)39 static int futex_hash_slots_get(void)
40 {
41 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
42 }
43
futex_hash_immutable_get(void)44 static int futex_hash_immutable_get(void)
45 {
46 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE);
47 }
48
futex_hash_slots_set_verify(int slots)49 static void futex_hash_slots_set_verify(int slots)
50 {
51 int ret;
52
53 ret = futex_hash_slots_set(slots, 0);
54 if (ret != 0) {
55 ksft_test_result_fail("Failed to set slots to %d: %m\n", slots);
56 ksft_finished();
57 }
58 ret = futex_hash_slots_get();
59 if (ret != slots) {
60 ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n",
61 slots, ret);
62 ksft_finished();
63 }
64 ksft_test_result_pass("SET and GET slots %d passed\n", slots);
65 }
66
futex_hash_slots_set_must_fail(int slots,int flags)67 static void futex_hash_slots_set_must_fail(int slots, int flags)
68 {
69 int ret;
70
71 ret = futex_hash_slots_set(slots, flags);
72 ksft_test_result(ret < 0, "futex_hash_slots_set(%d, %d)\n",
73 slots, flags);
74 }
75
thread_return_fn(void * arg)76 static void *thread_return_fn(void *arg)
77 {
78 return NULL;
79 }
80
thread_lock_fn(void * arg)81 static void *thread_lock_fn(void *arg)
82 {
83 pthread_barrier_wait(&barrier_main);
84
85 pthread_mutex_lock(&global_lock);
86 counter++;
87 usleep(20);
88 pthread_mutex_unlock(&global_lock);
89 return NULL;
90 }
91
create_max_threads(void * (* thread_fn)(void *))92 static void create_max_threads(void *(*thread_fn)(void *))
93 {
94 int i, ret;
95
96 for (i = 0; i < MAX_THREADS; i++) {
97 ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
98 if (ret)
99 ksft_exit_fail_msg("pthread_create failed: %m\n");
100 }
101 }
102
join_max_threads(void)103 static void join_max_threads(void)
104 {
105 int i, ret;
106
107 for (i = 0; i < MAX_THREADS; i++) {
108 ret = pthread_join(threads[i], NULL);
109 if (ret)
110 ksft_exit_fail_msg("pthread_join failed for thread %d\n", i);
111 }
112 }
113
usage(char * prog)114 static void usage(char *prog)
115 {
116 printf("Usage: %s\n", prog);
117 printf(" -c Use color\n");
118 printf(" -g Test global hash instead intead local immutable \n");
119 printf(" -h Display this help message\n");
120 printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
121 VQUIET, VCRITICAL, VINFO);
122 }
123
124 static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";
125 static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";
126
main(int argc,char * argv[])127 int main(int argc, char *argv[])
128 {
129 int futex_slots1, futex_slotsn, online_cpus;
130 pthread_mutexattr_t mutex_attr_pi;
131 int use_global_hash = 0;
132 int ret;
133 int c;
134
135 while ((c = getopt(argc, argv, "cghv:")) != -1) {
136 switch (c) {
137 case 'c':
138 log_color(1);
139 break;
140 case 'g':
141 use_global_hash = 1;
142 break;
143 case 'h':
144 usage(basename(argv[0]));
145 exit(0);
146 break;
147 case 'v':
148 log_verbosity(atoi(optarg));
149 break;
150 default:
151 usage(basename(argv[0]));
152 exit(1);
153 }
154 }
155
156 ksft_print_header();
157 ksft_set_plan(22);
158
159 ret = pthread_mutexattr_init(&mutex_attr_pi);
160 ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
161 ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
162 if (ret != 0) {
163 ksft_exit_fail_msg("Failed to initialize pthread mutex.\n");
164 }
165 /* First thread, expect to be 0, not yet initialized */
166 ret = futex_hash_slots_get();
167 if (ret != 0)
168 ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret);
169
170 ret = futex_hash_immutable_get();
171 if (ret != 0)
172 ksft_exit_fail_msg("futex_hash_immutable_get() failed: %d, %m\n", ret);
173
174 ksft_test_result_pass("Basic get slots and immutable status.\n");
175 ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
176 if (ret != 0)
177 ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret);
178
179 ret = pthread_join(threads[0], NULL);
180 if (ret != 0)
181 ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret);
182
183 /* First thread, has to initialiaze private hash */
184 futex_slots1 = futex_hash_slots_get();
185 if (futex_slots1 <= 0) {
186 ksft_print_msg("Current hash buckets: %d\n", futex_slots1);
187 ksft_exit_fail_msg(test_msg_auto_create);
188 }
189
190 ksft_test_result_pass(test_msg_auto_create);
191
192 online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
193 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
194 if (ret != 0)
195 ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n");
196
197 ret = pthread_mutex_lock(&global_lock);
198 if (ret != 0)
199 ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n");
200
201 counter = 0;
202 create_max_threads(thread_lock_fn);
203 pthread_barrier_wait(&barrier_main);
204
205 /*
206 * The current default size of hash buckets is 16. The auto increase
207 * works only if more than 16 CPUs are available.
208 */
209 ksft_print_msg("Online CPUs: %d\n", online_cpus);
210 if (online_cpus > 16) {
211 futex_slotsn = futex_hash_slots_get();
212 if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
213 ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",
214 futex_slots1, futex_slotsn);
215 ksft_exit_fail_msg(test_msg_auto_inc);
216 }
217 ksft_test_result_pass(test_msg_auto_inc);
218 } else {
219 ksft_test_result_skip(test_msg_auto_inc);
220 }
221 ret = pthread_mutex_unlock(&global_lock);
222
223 /* Once the user changes it, it has to be what is set */
224 futex_hash_slots_set_verify(2);
225 futex_hash_slots_set_verify(4);
226 futex_hash_slots_set_verify(8);
227 futex_hash_slots_set_verify(32);
228 futex_hash_slots_set_verify(16);
229
230 ret = futex_hash_slots_set(15, 0);
231 ksft_test_result(ret < 0, "Use 15 slots\n");
232
233 futex_hash_slots_set_verify(2);
234 join_max_threads();
235 ksft_test_result(counter == MAX_THREADS, "Created of waited for %d of %d threads\n",
236 counter, MAX_THREADS);
237 counter = 0;
238 /* Once the user set something, auto reisze must be disabled */
239 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
240
241 create_max_threads(thread_lock_fn);
242 join_max_threads();
243
244 ret = futex_hash_slots_get();
245 ksft_test_result(ret == 2, "No more auto-resize after manaul setting, got %d\n",
246 ret);
247
248 futex_hash_slots_set_must_fail(1 << 29, 0);
249
250 /*
251 * Once the private hash has been made immutable or global hash has been requested,
252 * then this requested can not be undone.
253 */
254 if (use_global_hash) {
255 ret = futex_hash_slots_set(0, 0);
256 ksft_test_result(ret == 0, "Global hash request\n");
257 } else {
258 ret = futex_hash_slots_set(4, FH_FLAG_IMMUTABLE);
259 ksft_test_result(ret == 0, "Immutable resize to 4\n");
260 }
261 if (ret != 0)
262 goto out;
263
264 futex_hash_slots_set_must_fail(4, 0);
265 futex_hash_slots_set_must_fail(4, FH_FLAG_IMMUTABLE);
266 futex_hash_slots_set_must_fail(8, 0);
267 futex_hash_slots_set_must_fail(8, FH_FLAG_IMMUTABLE);
268 futex_hash_slots_set_must_fail(0, FH_FLAG_IMMUTABLE);
269 futex_hash_slots_set_must_fail(6, FH_FLAG_IMMUTABLE);
270
271 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
272 if (ret != 0) {
273 ksft_exit_fail_msg("pthread_barrier_init failed: %m\n");
274 return 1;
275 }
276 create_max_threads(thread_lock_fn);
277 join_max_threads();
278
279 ret = futex_hash_slots_get();
280 if (use_global_hash) {
281 ksft_test_result(ret == 0, "Continue to use global hash\n");
282 } else {
283 ksft_test_result(ret == 4, "Continue to use the 4 hash buckets\n");
284 }
285
286 ret = futex_hash_immutable_get();
287 ksft_test_result(ret == 1, "Hash reports to be immutable\n");
288
289 out:
290 ksft_finished();
291 return 0;
292 }
293