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 <string.h> 13 #include <unistd.h> 14 15 #include <linux/prctl.h> 16 #include <sys/prctl.h> 17 18 #include "kselftest_harness.h" 19 20 #define MAX_THREADS 64 21 22 static pthread_barrier_t barrier_main; 23 static pthread_mutex_t global_lock; 24 static pthread_t threads[MAX_THREADS]; 25 static int counter; 26 27 #ifndef PR_FUTEX_HASH 28 #define PR_FUTEX_HASH 78 29 # define PR_FUTEX_HASH_SET_SLOTS 1 30 # define PR_FUTEX_HASH_GET_SLOTS 2 31 #endif 32 33 static int futex_hash_slots_set(unsigned int slots) 34 { 35 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0); 36 } 37 38 static int futex_hash_slots_get(void) 39 { 40 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); 41 } 42 43 static void futex_hash_slots_set_verify(struct __test_metadata *_metadata, int slots) 44 { 45 int ret; 46 47 ret = futex_hash_slots_set(slots); 48 ASSERT_EQ(ret, 0) 49 TH_LOG("Failed to set slots to %d: %s", slots, strerror(errno)); 50 51 ret = futex_hash_slots_get(); 52 ASSERT_EQ(ret, slots) { 53 TH_LOG("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %s", 54 slots, ret, strerror(errno)); 55 } 56 } 57 58 static void futex_hash_slots_set_must_fail(struct __test_metadata *_metadata, int slots) 59 { 60 int ret; 61 62 ret = futex_hash_slots_set(slots); 63 EXPECT_LT(ret, 0) 64 TH_LOG("futex_hash_slots_set(%d) should fail but succeeded", slots); 65 } 66 67 static void *thread_return_fn(void *arg) 68 { 69 return NULL; 70 } 71 72 static void *thread_lock_fn(void *arg) 73 { 74 pthread_barrier_wait(&barrier_main); 75 76 pthread_mutex_lock(&global_lock); 77 counter++; 78 usleep(20); 79 pthread_mutex_unlock(&global_lock); 80 return NULL; 81 } 82 83 static void create_max_threads(struct __test_metadata *_metadata, void *(*thread_fn)(void *)) 84 { 85 int i, ret; 86 87 for (i = 0; i < MAX_THREADS; i++) { 88 ret = pthread_create(&threads[i], NULL, thread_fn, NULL); 89 ASSERT_EQ(ret, 0) 90 TH_LOG("pthread_create failed: %s", strerror(errno)); 91 } 92 } 93 94 static void join_max_threads(struct __test_metadata *_metadata) 95 { 96 int i, ret; 97 98 for (i = 0; i < MAX_THREADS; i++) { 99 ret = pthread_join(threads[i], NULL); 100 ASSERT_EQ(ret, 0) 101 TH_LOG("pthread_join failed for thread %d: %s", i, strerror(errno)); 102 } 103 } 104 105 #define SEC_IN_NSEC 1000000000 106 #define MSEC_IN_NSEC 1000000 107 108 static void futex_dummy_op(struct __test_metadata *_metadata) 109 { 110 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 111 struct timespec timeout; 112 int ret; 113 114 pthread_mutex_lock(&lock); 115 clock_gettime(CLOCK_REALTIME, &timeout); 116 timeout.tv_nsec += 100 * MSEC_IN_NSEC; 117 if (timeout.tv_nsec >= SEC_IN_NSEC) { 118 timeout.tv_nsec -= SEC_IN_NSEC; 119 timeout.tv_sec++; 120 } 121 ret = pthread_mutex_timedlock(&lock, &timeout); 122 ASSERT_NE(ret, 0) 123 TH_LOG("Successfully locked an already locked mutex"); 124 125 ASSERT_EQ(ret, ETIMEDOUT) 126 TH_LOG("pthread_mutex_timedlock() did not timeout: %d", ret); 127 } 128 129 static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n"; 130 static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n"; 131 132 TEST(priv_hash) 133 { 134 int futex_slots1, futex_slotsn, online_cpus; 135 pthread_mutexattr_t mutex_attr_pi; 136 int ret, retry = 20; 137 138 ret = pthread_mutexattr_init(&mutex_attr_pi); 139 ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); 140 ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); 141 ASSERT_EQ(ret, 0) 142 TH_LOG("Failed to initialize pthread mutex"); 143 144 /* First thread, expect to be 0, not yet initialized */ 145 ret = futex_hash_slots_get(); 146 if (ret < 0 && errno == EINVAL) 147 SKIP(return, "PR_FUTEX_HASH not supported by kernel"); 148 149 ASSERT_EQ(ret, 0) 150 TH_LOG("futex_hash_slots_get() failed: %d, %s", ret, strerror(errno)); 151 152 ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); 153 ASSERT_EQ(ret, 0) 154 TH_LOG("pthread_create() failed: %d, %s", ret, strerror(errno)); 155 156 ret = pthread_join(threads[0], NULL); 157 ASSERT_EQ(ret, 0) 158 TH_LOG("pthread_join() failed: %d, %s", ret, strerror(errno)); 159 160 /* First thread, has to initialize private hash */ 161 futex_slots1 = futex_hash_slots_get(); 162 EXPECT_GT(futex_slots1, 0) 163 TH_LOG("Current hash buckets: %d. %s", futex_slots1, test_msg_auto_create); 164 165 online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 166 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); 167 ASSERT_EQ(ret, 0) 168 TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); 169 170 ret = pthread_mutex_lock(&global_lock); 171 ASSERT_EQ(ret, 0) 172 TH_LOG("pthread_mutex_lock failed: %s", strerror(errno)); 173 174 counter = 0; 175 create_max_threads(_metadata, thread_lock_fn); 176 pthread_barrier_wait(&barrier_main); 177 178 /* 179 * The current default size of hash buckets is 16. The auto increase 180 * works only if more than 16 CPUs are available. 181 */ 182 TH_LOG("Online CPUs: %d", online_cpus); 183 if (online_cpus > 16) { 184 retry_getslots: 185 futex_slotsn = futex_hash_slots_get(); 186 if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) { 187 retry--; 188 /* 189 * Auto scaling on thread creation can be slightly delayed 190 * because it waits for a RCU grace period twice. The new 191 * private hash is assigned upon the first futex operation 192 * after grace period. 193 * To cover all this for testing purposes the function 194 * below will acquire a lock and acquire it again with a 195 * 100ms timeout which must timeout. This ensures we 196 * sleep for 100ms and issue a futex operation. 197 */ 198 if (retry > 0) { 199 futex_dummy_op(_metadata); 200 goto retry_getslots; 201 } 202 EXPECT_NE(futex_slots1, futex_slotsn) { 203 TH_LOG("Expected increase of hash buckets but got: %d -> %d. %s", 204 futex_slots1, futex_slotsn, test_msg_auto_inc); 205 } 206 } 207 } else { 208 SKIP(return, "Automatic increase with more than 16 CPUs (only %d online)", online_cpus); 209 } 210 ret = pthread_mutex_unlock(&global_lock); 211 212 /* Once the user changes it, it has to be what is set */ 213 futex_hash_slots_set_verify(_metadata, 2); 214 futex_hash_slots_set_verify(_metadata, 4); 215 futex_hash_slots_set_verify(_metadata, 8); 216 futex_hash_slots_set_verify(_metadata, 32); 217 futex_hash_slots_set_verify(_metadata, 16); 218 219 ret = futex_hash_slots_set(15); 220 EXPECT_LT(ret, 0) 221 TH_LOG("Use 15 slots should fail but succeeded"); 222 223 futex_hash_slots_set_verify(_metadata, 2); 224 join_max_threads(_metadata); 225 226 EXPECT_EQ(counter, MAX_THREADS) 227 TH_LOG("Created and waited for %d of %d threads", counter, MAX_THREADS); 228 229 counter = 0; 230 /* Once the user set something, auto resize must be disabled */ 231 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 232 ASSERT_EQ(ret, 0) 233 TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); 234 235 create_max_threads(_metadata, thread_lock_fn); 236 join_max_threads(_metadata); 237 238 ret = futex_hash_slots_get(); 239 EXPECT_EQ(ret, 2) 240 TH_LOG("No more auto-resize after manual setting, got %d", ret); 241 242 futex_hash_slots_set_must_fail(_metadata, 1 << 29); 243 futex_hash_slots_set_verify(_metadata, 4); 244 245 /* 246 * Once the global hash has been requested, then this requested can not 247 * be undone. 248 */ 249 ret = futex_hash_slots_set(0); 250 ASSERT_EQ(ret, 0) 251 TH_LOG("Global hash request failed: %s", strerror(errno)); 252 253 futex_hash_slots_set_must_fail(_metadata, 4); 254 futex_hash_slots_set_must_fail(_metadata, 8); 255 futex_hash_slots_set_must_fail(_metadata, 8); 256 futex_hash_slots_set_must_fail(_metadata, 0); 257 futex_hash_slots_set_must_fail(_metadata, 6); 258 259 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 260 ASSERT_EQ(ret, 0) 261 TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); 262 263 create_max_threads(_metadata, thread_lock_fn); 264 join_max_threads(_metadata); 265 266 ret = futex_hash_slots_get(); 267 EXPECT_EQ(ret, 0) 268 TH_LOG("Continue to use global hash failed"); 269 } 270 271 TEST_HARNESS_MAIN 272