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 #ifdef LIBNUMA_VER_SUFFICIENT 15 #include <numa.h> 16 #include <numaif.h> 17 #endif 18 19 #include <linux/futex.h> 20 #include <sys/mman.h> 21 22 #include "futextest.h" 23 #include "futex2test.h" 24 #include "kselftest_harness.h" 25 26 #define MAX_THREADS 64 27 28 static pthread_barrier_t barrier_main; 29 static pthread_t threads[MAX_THREADS]; 30 31 struct thread_args { 32 void *futex_ptr; 33 unsigned int flags; 34 int result; 35 }; 36 37 static struct thread_args thread_args[MAX_THREADS]; 38 39 #ifndef FUTEX_NO_NODE 40 #define FUTEX_NO_NODE (-1) 41 #endif 42 43 #ifndef FUTEX2_MPOL 44 #define FUTEX2_MPOL 0x08 45 #endif 46 47 static void *thread_lock_fn(void *arg) 48 { 49 struct thread_args *args = arg; 50 int ret; 51 52 pthread_barrier_wait(&barrier_main); 53 ret = futex2_wait(args->futex_ptr, 0, args->flags, NULL, 0); 54 args->result = ret; 55 return NULL; 56 } 57 58 static void create_max_threads(struct __test_metadata *_metadata, void *futex_ptr) 59 { 60 int i, ret; 61 62 for (i = 0; i < MAX_THREADS; i++) { 63 thread_args[i].futex_ptr = futex_ptr; 64 thread_args[i].flags = FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA; 65 thread_args[i].result = 0; 66 ret = pthread_create(&threads[i], NULL, thread_lock_fn, &thread_args[i]); 67 ASSERT_EQ(ret, 0) 68 TH_LOG("pthread_create failed"); 69 } 70 } 71 72 static void join_max_threads(struct __test_metadata *_metadata) 73 { 74 int i, ret; 75 76 for (i = 0; i < MAX_THREADS; i++) { 77 ret = pthread_join(threads[i], NULL); 78 ASSERT_EQ(ret, 0) 79 TH_LOG("pthread_join failed for thread %d", i); 80 } 81 } 82 83 static void __test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value, 84 unsigned int futex_flags) 85 { 86 int to_wake, ret, i; 87 88 pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); 89 create_max_threads(_metadata, futex_ptr); 90 pthread_barrier_wait(&barrier_main); 91 to_wake = MAX_THREADS; 92 93 do { 94 ret = futex2_wake(futex_ptr, to_wake, futex_flags); 95 96 if (err_value) { 97 EXPECT_LT(ret, 0) { 98 TH_LOG("futex2_wake(%d, 0x%x) should fail, but didn't", 99 to_wake, futex_flags); 100 } 101 102 EXPECT_EQ(errno, err_value) { 103 TH_LOG("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)", 104 to_wake, futex_flags, err_value, errno, strerror(errno)); 105 } 106 107 break; 108 } 109 if (ret < 0) { 110 if (errno == ENOSYS || (errno == EINVAL && (futex_flags & FUTEX2_NUMA))) 111 SKIP(return, "futex2 or FUTEX2_NUMA not supported by kernel"); 112 113 ASSERT_GE(ret, 0) { 114 TH_LOG("Failed futex2_wake(%d, 0x%x): %s", 115 to_wake, futex_flags, strerror(errno)); 116 } 117 } 118 if (!ret) 119 usleep(50); 120 to_wake -= ret; 121 122 } while (to_wake); 123 join_max_threads(_metadata); 124 125 for (i = 0; i < MAX_THREADS; i++) { 126 if (err_value) { 127 EXPECT_EQ(thread_args[i].result, -1) { 128 TH_LOG("Thread %d should fail but succeeded (%d)", 129 i, thread_args[i].result); 130 } 131 } else { 132 EXPECT_EQ(thread_args[i].result, 0) 133 TH_LOG("Thread %d failed (%d)", i, thread_args[i].result); 134 } 135 } 136 } 137 138 static void test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value) 139 { 140 __test_futex(_metadata, futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA); 141 } 142 143 TEST(futex_numa_mpol) 144 { 145 struct futex32_numa *futex_numa; 146 void *futex_ptr; 147 int mem_size; 148 149 mem_size = sysconf(_SC_PAGE_SIZE); 150 futex_ptr = mmap(NULL, mem_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 151 ASSERT_NE(futex_ptr, MAP_FAILED) 152 TH_LOG("mmap() for %d bytes failed: %s", mem_size, strerror(errno)); 153 154 /* Create an invalid memory region for the "Memory out of range" test */ 155 mprotect(futex_ptr + mem_size, mem_size, PROT_NONE); 156 157 futex_numa = futex_ptr; 158 159 TH_LOG("Regular test"); 160 futex_numa->futex = 0; 161 futex_numa->numa = FUTEX_NO_NODE; 162 test_futex(_metadata, futex_ptr, 0); 163 164 EXPECT_NE(futex_numa->numa, FUTEX_NO_NODE) 165 TH_LOG("NUMA node is left uninitialized"); 166 167 /* FUTEX2_NUMA futex must be 8-byte aligned */ 168 TH_LOG("Mis-aligned futex"); 169 test_futex(_metadata, futex_ptr + mem_size - 4, EINVAL); 170 171 TH_LOG("Memory out of range"); 172 test_futex(_metadata, futex_ptr + mem_size, EFAULT); 173 174 futex_numa->numa = FUTEX_NO_NODE; 175 mprotect(futex_ptr, mem_size, PROT_READ); 176 TH_LOG("Memory, RO"); 177 test_futex(_metadata, futex_ptr, EFAULT); 178 179 mprotect(futex_ptr, mem_size, PROT_NONE); 180 TH_LOG("Memory, no access"); 181 test_futex(_metadata, futex_ptr, EFAULT); 182 183 mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE); 184 TH_LOG("Memory back to RW"); 185 test_futex(_metadata, futex_ptr, 0); 186 187 /* MPOL test. Does not work as expected */ 188 #ifdef LIBNUMA_VER_SUFFICIENT 189 for (int i = 0; i < 4; i++) { 190 unsigned long nodemask; 191 int ret; 192 193 nodemask = 1 << i; 194 ret = mbind(futex_ptr, mem_size, MPOL_BIND, &nodemask, 195 sizeof(nodemask) * 8, 0); 196 if (ret == 0) { 197 ret = numa_set_mempolicy_home_node(futex_ptr, mem_size, i, 0); 198 ASSERT_EQ(ret, 0) 199 TH_LOG("Failed to set home node: %s, %d", strerror(errno), errno); 200 201 TH_LOG("Node %d test", i); 202 futex_numa->futex = 0; 203 futex_numa->numa = FUTEX_NO_NODE; 204 205 ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | 206 FUTEX2_NUMA | FUTEX2_MPOL); 207 EXPECT_GE(ret, 0) 208 TH_LOG("Failed to wake 0 with MPOL: %s", strerror(errno)); 209 EXPECT_EQ(futex_numa->numa, i) 210 TH_LOG("Returned NUMA node is %d expected %d", futex_numa->numa, i); 211 } 212 } 213 #else 214 SKIP(return, "futex2 MPOL hints test requires libnuma 2.0.18+"); 215 #endif 216 munmap(futex_ptr, mem_size * 2); 217 } 218 219 TEST_HARNESS_MAIN 220