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 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 39 static int futex_hash_slots_get(void) 40 { 41 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); 42 } 43 44 static int futex_hash_immutable_get(void) 45 { 46 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE); 47 } 48 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 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 76 static void *thread_return_fn(void *arg) 77 { 78 return NULL; 79 } 80 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 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 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 114 #define SEC_IN_NSEC 1000000000 115 #define MSEC_IN_NSEC 1000000 116 117 static void futex_dummy_op(void) 118 { 119 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 120 struct timespec timeout; 121 int ret; 122 123 pthread_mutex_lock(&lock); 124 clock_gettime(CLOCK_REALTIME, &timeout); 125 timeout.tv_nsec += 100 * MSEC_IN_NSEC; 126 if (timeout.tv_nsec >= SEC_IN_NSEC) { 127 timeout.tv_nsec -= SEC_IN_NSEC; 128 timeout.tv_sec++; 129 } 130 ret = pthread_mutex_timedlock(&lock, &timeout); 131 if (ret == 0) 132 ksft_exit_fail_msg("Succeffuly locked an already locked mutex.\n"); 133 134 if (ret != ETIMEDOUT) 135 ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret); 136 } 137 138 static void usage(char *prog) 139 { 140 printf("Usage: %s\n", prog); 141 printf(" -c Use color\n"); 142 printf(" -g Test global hash instead intead local immutable \n"); 143 printf(" -h Display this help message\n"); 144 printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n", 145 VQUIET, VCRITICAL, VINFO); 146 } 147 148 static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n"; 149 static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n"; 150 151 int main(int argc, char *argv[]) 152 { 153 int futex_slots1, futex_slotsn, online_cpus; 154 pthread_mutexattr_t mutex_attr_pi; 155 int use_global_hash = 0; 156 int ret, retry = 20; 157 int c; 158 159 while ((c = getopt(argc, argv, "cghv:")) != -1) { 160 switch (c) { 161 case 'c': 162 log_color(1); 163 break; 164 case 'g': 165 use_global_hash = 1; 166 break; 167 case 'h': 168 usage(basename(argv[0])); 169 exit(0); 170 break; 171 case 'v': 172 log_verbosity(atoi(optarg)); 173 break; 174 default: 175 usage(basename(argv[0])); 176 exit(1); 177 } 178 } 179 180 ksft_print_header(); 181 ksft_set_plan(22); 182 183 ret = pthread_mutexattr_init(&mutex_attr_pi); 184 ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); 185 ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); 186 if (ret != 0) { 187 ksft_exit_fail_msg("Failed to initialize pthread mutex.\n"); 188 } 189 /* First thread, expect to be 0, not yet initialized */ 190 ret = futex_hash_slots_get(); 191 if (ret != 0) 192 ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret); 193 194 ret = futex_hash_immutable_get(); 195 if (ret != 0) 196 ksft_exit_fail_msg("futex_hash_immutable_get() failed: %d, %m\n", ret); 197 198 ksft_test_result_pass("Basic get slots and immutable status.\n"); 199 ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); 200 if (ret != 0) 201 ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret); 202 203 ret = pthread_join(threads[0], NULL); 204 if (ret != 0) 205 ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret); 206 207 /* First thread, has to initialiaze private hash */ 208 futex_slots1 = futex_hash_slots_get(); 209 if (futex_slots1 <= 0) { 210 ksft_print_msg("Current hash buckets: %d\n", futex_slots1); 211 ksft_exit_fail_msg(test_msg_auto_create); 212 } 213 214 ksft_test_result_pass(test_msg_auto_create); 215 216 online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 217 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); 218 if (ret != 0) 219 ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n"); 220 221 ret = pthread_mutex_lock(&global_lock); 222 if (ret != 0) 223 ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n"); 224 225 counter = 0; 226 create_max_threads(thread_lock_fn); 227 pthread_barrier_wait(&barrier_main); 228 229 /* 230 * The current default size of hash buckets is 16. The auto increase 231 * works only if more than 16 CPUs are available. 232 */ 233 ksft_print_msg("Online CPUs: %d\n", online_cpus); 234 if (online_cpus > 16) { 235 retry_getslots: 236 futex_slotsn = futex_hash_slots_get(); 237 if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) { 238 retry--; 239 /* 240 * Auto scaling on thread creation can be slightly delayed 241 * because it waits for a RCU grace period twice. The new 242 * private hash is assigned upon the first futex operation 243 * after grace period. 244 * To cover all this for testing purposes the function 245 * below will acquire a lock and acquire it again with a 246 * 100ms timeout which must timeout. This ensures we 247 * sleep for 100ms and issue a futex operation. 248 */ 249 if (retry > 0) { 250 futex_dummy_op(); 251 goto retry_getslots; 252 } 253 ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n", 254 futex_slots1, futex_slotsn); 255 ksft_exit_fail_msg(test_msg_auto_inc); 256 } 257 ksft_test_result_pass(test_msg_auto_inc); 258 } else { 259 ksft_test_result_skip(test_msg_auto_inc); 260 } 261 ret = pthread_mutex_unlock(&global_lock); 262 263 /* Once the user changes it, it has to be what is set */ 264 futex_hash_slots_set_verify(2); 265 futex_hash_slots_set_verify(4); 266 futex_hash_slots_set_verify(8); 267 futex_hash_slots_set_verify(32); 268 futex_hash_slots_set_verify(16); 269 270 ret = futex_hash_slots_set(15, 0); 271 ksft_test_result(ret < 0, "Use 15 slots\n"); 272 273 futex_hash_slots_set_verify(2); 274 join_max_threads(); 275 ksft_test_result(counter == MAX_THREADS, "Created of waited for %d of %d threads\n", 276 counter, MAX_THREADS); 277 counter = 0; 278 /* Once the user set something, auto reisze must be disabled */ 279 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 280 281 create_max_threads(thread_lock_fn); 282 join_max_threads(); 283 284 ret = futex_hash_slots_get(); 285 ksft_test_result(ret == 2, "No more auto-resize after manaul setting, got %d\n", 286 ret); 287 288 futex_hash_slots_set_must_fail(1 << 29, 0); 289 290 /* 291 * Once the private hash has been made immutable or global hash has been requested, 292 * then this requested can not be undone. 293 */ 294 if (use_global_hash) { 295 ret = futex_hash_slots_set(0, 0); 296 ksft_test_result(ret == 0, "Global hash request\n"); 297 } else { 298 ret = futex_hash_slots_set(4, FH_FLAG_IMMUTABLE); 299 ksft_test_result(ret == 0, "Immutable resize to 4\n"); 300 } 301 if (ret != 0) 302 goto out; 303 304 futex_hash_slots_set_must_fail(4, 0); 305 futex_hash_slots_set_must_fail(4, FH_FLAG_IMMUTABLE); 306 futex_hash_slots_set_must_fail(8, 0); 307 futex_hash_slots_set_must_fail(8, FH_FLAG_IMMUTABLE); 308 futex_hash_slots_set_must_fail(0, FH_FLAG_IMMUTABLE); 309 futex_hash_slots_set_must_fail(6, FH_FLAG_IMMUTABLE); 310 311 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 312 if (ret != 0) { 313 ksft_exit_fail_msg("pthread_barrier_init failed: %m\n"); 314 return 1; 315 } 316 create_max_threads(thread_lock_fn); 317 join_max_threads(); 318 319 ret = futex_hash_slots_get(); 320 if (use_global_hash) { 321 ksft_test_result(ret == 0, "Continue to use global hash\n"); 322 } else { 323 ksft_test_result(ret == 4, "Continue to use the 4 hash buckets\n"); 324 } 325 326 ret = futex_hash_immutable_get(); 327 ksft_test_result(ret == 1, "Hash reports to be immutable\n"); 328 329 out: 330 ksft_finished(); 331 return 0; 332 } 333