xref: /linux/tools/testing/selftests/futex/functional/futex_numa_mpol.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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