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