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