1 /* Generic test case for CPU affinity functions.
2    Copyright (C) 2015-2021 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 /* This file is included by the tst-affinity*.c files to test the two
20    variants of the functions, under different conditions.  The
21    following functions have to be definied:
22 
23    static int getaffinity (size_t, cpu_set_t *);
24    static int setaffinity (size_t, const cpu_set_t *);
25    static bool early_test (struct conf *);
26 
27    The first two functions shall affect the affinity mask for the
28    current thread and return 0 for success, -1 for error (with an
29    error code in errno).
30 
31    early_test is invoked before the tests in this file affect the
32    affinity masks.  If it returns true, testing continues, otherwise
33    no more tests run and the overall test exits with status 1.
34 */
35 
36 #include <errno.h>
37 #include <limits.h>
38 #include <sched.h>
39 #include <stdbool.h>
40 #include <stdio.h>
41 
42 /* CPU set configuration determined.  Can be used from early_test.  */
43 struct conf
44 {
45   int set_size;			/* in bits */
46   int last_cpu;
47 };
48 
49 static int
find_set_size(void)50 find_set_size (void)
51 {
52   /* There is considerable controversy about how to determine the size
53      of the kernel CPU mask.  The probing loop below is only intended
54      for testing purposes.  */
55   for (int num_cpus = 64; num_cpus <= INT_MAX / 2; ++num_cpus)
56     {
57       cpu_set_t *set = CPU_ALLOC (num_cpus);
58       size_t size = CPU_ALLOC_SIZE (num_cpus);
59 
60       if (set == NULL)
61 	{
62 	  printf ("error: CPU_ALLOC (%d) failed\n", num_cpus);
63 	  return -1;
64 	}
65       if (getaffinity (size, set) == 0)
66 	{
67 	  CPU_FREE (set);
68 	  return num_cpus;
69 	}
70       if (errno != EINVAL)
71 	{
72 	  printf ("error: getaffinity for %d CPUs: %m\n", num_cpus);
73 	  CPU_FREE (set);
74 	  return -1;
75 	}
76       CPU_FREE (set);
77     }
78   puts ("error: Cannot find maximum CPU number");
79   return -1;
80 }
81 
82 static int
find_last_cpu(const cpu_set_t * set,size_t size)83 find_last_cpu (const cpu_set_t *set, size_t size)
84 {
85   /* We need to determine the set size with CPU_COUNT_S and the
86      cpus_found counter because there is no direct way to obtain the
87      actual CPU set size, in bits, from the value of
88      CPU_ALLOC_SIZE.  */
89   size_t cpus_found = 0;
90   size_t total_cpus = CPU_COUNT_S (size, set);
91   int last_cpu = -1;
92 
93   for (int cpu = 0; cpus_found < total_cpus; ++cpu)
94     {
95       if (CPU_ISSET_S (cpu, size, set))
96 	{
97 	  last_cpu = cpu;
98 	  ++cpus_found;
99 	}
100     }
101   return last_cpu;
102 }
103 
104 static void
setup_conf(struct conf * conf)105 setup_conf (struct conf *conf)
106 {
107   *conf = (struct conf) {-1, -1};
108   conf->set_size = find_set_size ();
109   if (conf->set_size > 0)
110     {
111       cpu_set_t *set = CPU_ALLOC (conf->set_size);
112 
113       if (set == NULL)
114 	{
115 	  printf ("error: CPU_ALLOC (%d) failed\n", conf->set_size);
116 	  CPU_FREE (set);
117 	  return;
118 	}
119       if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), set) < 0)
120 	{
121 	  printf ("error: getaffinity failed: %m\n");
122 	  CPU_FREE (set);
123 	  return;
124 	}
125       conf->last_cpu = find_last_cpu (set, CPU_ALLOC_SIZE (conf->set_size));
126       if (conf->last_cpu < 0)
127 	puts ("info: No test CPU found");
128       CPU_FREE (set);
129     }
130 }
131 
132 static bool
test_size(const struct conf * conf,size_t size)133 test_size (const struct conf *conf, size_t size)
134 {
135   if (size < conf->set_size)
136     {
137       printf ("info: Test not run for CPU set size %zu\n", size);
138       return true;
139     }
140 
141   cpu_set_t *initial_set = CPU_ALLOC (size);
142   cpu_set_t *set2 = CPU_ALLOC (size);
143   cpu_set_t *active_cpu_set = CPU_ALLOC (size);
144 
145   if (initial_set == NULL || set2 == NULL || active_cpu_set == NULL)
146     {
147       printf ("error: size %zu: CPU_ALLOC failed\n", size);
148       return false;
149     }
150   size_t kernel_size = CPU_ALLOC_SIZE (size);
151 
152   if (getaffinity (kernel_size, initial_set) < 0)
153     {
154       printf ("error: size %zu: getaffinity: %m\n", size);
155       return false;
156     }
157   if (setaffinity (kernel_size, initial_set) < 0)
158     {
159       printf ("error: size %zu: setaffinity: %m\n", size);
160       return true;
161     }
162 
163   /* Use one-CPU set to test switching between CPUs.  */
164   int last_active_cpu = -1;
165   for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
166     {
167       int active_cpu = sched_getcpu ();
168       if (last_active_cpu >= 0 && last_active_cpu != active_cpu)
169 	{
170 	  printf ("error: Unexpected CPU %d, expected %d\n",
171 		  active_cpu, last_active_cpu);
172 	  return false;
173 	}
174 
175       if (!CPU_ISSET_S (cpu, kernel_size, initial_set))
176 	continue;
177       last_active_cpu = cpu;
178 
179       CPU_ZERO_S (kernel_size, active_cpu_set);
180       CPU_SET_S (cpu, kernel_size, active_cpu_set);
181       if (setaffinity (kernel_size, active_cpu_set) < 0)
182 	{
183 	  printf ("error: size %zu: setaffinity (%d): %m\n", size, cpu);
184 	  return false;
185 	}
186       active_cpu = sched_getcpu ();
187       if (active_cpu != cpu)
188 	{
189 	  printf ("error: Unexpected CPU %d, expected %d\n", active_cpu, cpu);
190 	  return false;
191 	}
192       unsigned int numa_cpu, numa_node;
193       if (getcpu (&numa_cpu, &numa_node) != 0)
194 	{
195 	  printf ("error: getcpu: %m\n");
196 	  return false;
197 	}
198       if ((unsigned int) active_cpu != numa_cpu)
199 	{
200 	  printf ("error: Unexpected CPU %d, expected %d\n",
201 		  active_cpu, numa_cpu);
202 	  return false;
203 	}
204       if (getaffinity (kernel_size, set2) < 0)
205 	{
206 	  printf ("error: size %zu: getaffinity (2): %m\n", size);
207 	  return false;
208 	}
209       if (!CPU_EQUAL_S (kernel_size, active_cpu_set, set2))
210 	{
211 	  printf ("error: size %zu: CPU sets do not match\n", size);
212 	  return false;
213 	}
214     }
215 
216   /* Test setting the all-ones set.  */
217   for (int cpu = 0; cpu < size; ++cpu)
218     CPU_SET_S (cpu, kernel_size, set2);
219   if (setaffinity (kernel_size, set2) < 0)
220     {
221       printf ("error: size %zu: setaffinity (3): %m\n", size);
222       return false;
223     }
224 
225   if (setaffinity (kernel_size, initial_set) < 0)
226     {
227       printf ("error: size %zu: setaffinity (4): %m\n", size);
228       return false;
229     }
230   if (getaffinity (kernel_size, set2) < 0)
231     {
232       printf ("error: size %zu: getaffinity (3): %m\n", size);
233       return false;
234     }
235   if (!CPU_EQUAL_S (kernel_size, initial_set, set2))
236     {
237       printf ("error: size %zu: CPU sets do not match (2)\n", size);
238       return false;
239     }
240 
241   CPU_FREE (initial_set);
242   CPU_FREE (set2);
243   CPU_FREE (active_cpu_set);
244 
245   return true;
246 }
247 
248 static int
do_test(void)249 do_test (void)
250 {
251   {
252     cpu_set_t set;
253     if (getaffinity (sizeof (set), &set) < 0 && errno == ENOSYS)
254       {
255 	puts ("warning: getaffinity not supported, test cannot run");
256 	return 0;
257       }
258     if (sched_getcpu () < 0 && errno == ENOSYS)
259       {
260 	puts ("warning: sched_getcpu not supported, test cannot run");
261 	return 0;
262       }
263   }
264 
265   struct conf conf;
266   setup_conf (&conf);
267   /* Note: The CPU set size in bits can be less than the CPU count
268      (and the maximum test CPU) because the userspace interface rounds
269      up the bit count, and the rounded-up buffer size is passed into
270      the kernel.  The kernel does not know that some of the buffer are
271      actually padding, and writes data there.  */
272   printf ("info: Detected CPU set size (in bits): %d\n", conf.set_size);
273   printf ("info: Maximum test CPU: %d\n", conf.last_cpu);
274   if (conf.set_size < 0 || conf.last_cpu < 0)
275     return 1;
276 
277   if (!early_test (&conf))
278     return 1;
279 
280   bool error = false;
281   error |= !test_size (&conf, 1024);
282   error |= !test_size (&conf, conf.set_size);
283   error |= !test_size (&conf, 2);
284   error |= !test_size (&conf, 32);
285   error |= !test_size (&conf, 40);
286   error |= !test_size (&conf, 64);
287   error |= !test_size (&conf, 96);
288   error |= !test_size (&conf, 128);
289   error |= !test_size (&conf, 256);
290   error |= !test_size (&conf, 8192);
291   return error;
292 }
293 
294 #define TEST_FUNCTION do_test ()
295 #include "../test-skeleton.c"
296