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 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 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