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