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