1/*
2 *
3 *   Copyright © International Business Machines  Corp., 2007, 2008
4 *
5 *   This program is free software;  you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation; either version 2 of the License, or
8 *   (at your option) any later version.
9 *
10 *   This program 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
13 *   the GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with this program;  if not, write to the Free Software
17 *   Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
18 */
19
20/*
21 * NAME
22 *	getcpu1.c
23 *
24 * DESCRIPTION
25 *	getcpu01 - call getcpu() and make sure it succeeds
26 *
27 * ALGORITHM
28 *	set cpu affinity of the process
29 *	If setaffinity() fails exit from the test suite
30 *	Store the node ID of the cpu which has been set in previous step
31 *	Make a call to getcpu() system call
32 *	Verify the returned valued with the set value
33 *	if they match
34 *	  test is considered PASS
35 *	else
36 *	  test is considered FAIL
37 *
38 * USAGE:  <for command-line>
39 *  getcpu [-c n] [-f] [-i n] [-I x] [-P x] [-t]
40 *     where,  -c n : Run n copies concurrently.
41 *             -f   : Turn off functionality Testing.
42 *	       -i n : Execute test n times.
43 *	       -I x : Execute test for x seconds.
44 *	       -P x : Pause for x seconds between iterations.
45 *	       -t   : Turn on syscall timing.
46 *
47 * HISTORY
48 *	06/2008 written by Sharyathi Nagesh <sharyathi@in.ibm.com>
49 *
50 *      05/2009         Suzuki K P <suzuki@in.ibm.com>
51 *                      Use TCONF instead of TWARN for non-NUMA machines
52 *
53 * RESTRICTIONS
54 *	none
55 */
56
57#define _GNU_SOURCE
58#include <sched.h>
59#include <errno.h>
60#include "test.h"
61#include <sys/types.h>
62#include <dirent.h>
63
64#if defined(__i386__) || defined(__x86_64__)
65#if __GLIBC_PREREQ(2,6)
66#if defined(__x86_64__)
67#include <utmpx.h>
68#endif
69int sys_support = 1;
70#elif defined(__i386__)
71int sys_support = 1;
72#else
73int sys_support = 0;
74#endif
75#else
76int sys_support = 0;
77#endif
78
79#if !(__GLIBC_PREREQ(2, 7))
80#define CPU_FREE(ptr) free(ptr)
81#endif
82
83void cleanup(void);
84void setup(void);
85static inline int getcpu(unsigned int *, unsigned int *, void *);
86unsigned int set_cpu_affinity(void);
87unsigned int get_nodeid(unsigned int);
88unsigned int max_cpuid(size_t, cpu_set_t *);
89
90char *TCID = "getcpu01";
91int TST_TOTAL = 1;
92
93int main(int ac, char **av)
94{
95	int lc;
96	unsigned int cpu_id, node_id = 0;
97	unsigned int cpu_set;
98#ifdef __i386__
99	unsigned int node_set;
100#endif
101
102	/* Check For Kernel Version */
103	if (((tst_kvercmp(2, 6, 20)) < 0) || !(sys_support)) {
104		tst_resm(TCONF, "This test can only run on kernels that are ");
105		tst_resm(TCONF,
106			 "2.6.20 and higher and glibc version 2.6 and above");
107		tst_resm(TCONF, "Currently the test case has been");
108		tst_resm(TCONF, "developed only for i386 and x86_64");
109		exit(0);
110	}
111
112	tst_parse_opts(ac, av, NULL, NULL);
113
114	setup();		/* global setup */
115
116	/* The following loop checks looping state if -i option given */
117
118	for (lc = 0; TEST_LOOPING(lc); lc++) {
119		/* reset tst_count in case we are looping */
120		tst_count = 0;
121
122		/* call the system call with the TEST() macro */
123		cpu_set = set_cpu_affinity();
124#ifdef __i386__
125		node_set = get_nodeid(cpu_set);
126#endif
127		TEST(getcpu(&cpu_id, &node_id, NULL));
128		if (TEST_RETURN == 0) {
129			if (cpu_id != cpu_set) {
130				tst_resm(TFAIL, "getcpu() returned wrong value"
131					 " expected cpuid:%d, returned value cpuid: %d",
132					 cpu_set, cpu_id);
133
134			}
135#ifdef __i386__
136			else if (node_id != node_set) {
137				tst_resm(TFAIL, "getcpu() returned wrong value"
138					 " expected  node id:%d returned  node id:%d",
139					 node_set, node_id);
140
141			}
142#endif
143			else
144				tst_resm(TPASS, "getcpu() returned proper"
145					 " cpuid:%d, node id:%d", cpu_id,
146					 node_id);
147		} else {
148			tst_resm(TFAIL, "getcpu() Failed, errno=%d:%s",
149				 TEST_ERRNO, strerror(TEST_ERRNO));
150
151		}
152	}
153
154	cleanup();
155
156	tst_exit();
157}
158
159/*
160 * getcpu() - calls the system call
161 */
162static inline int getcpu(unsigned *cpu_id, unsigned *node_id,
163			 void *cache_struct)
164{
165#if defined(__i386__)
166	return syscall(318, cpu_id, node_id, cache_struct);
167#elif __GLIBC_PREREQ(2,6)
168	*cpu_id = sched_getcpu();
169#endif
170	return 0;
171}
172
173/*
174 * setup() - performs all the ONE TIME setup for this test.
175 */
176void setup(void)
177{
178
179	/* ?? */
180
181	TEST_PAUSE;
182}
183
184/*
185 * This will set the affinity to max cpu on which process can run
186 * and return that cpu id to the calling process
187 */
188unsigned int set_cpu_affinity(void)
189{
190	unsigned cpu_max;
191	cpu_set_t *set;
192	size_t size;
193	int nrcpus = 1024;
194#if __GLIBC_PREREQ(2, 7)
195realloc:
196	set = CPU_ALLOC(nrcpus);
197#else
198	set = malloc(sizeof(cpu_set_t));
199#endif
200	if (set == NULL) {
201		tst_brkm(TFAIL, NULL, "CPU_ALLOC:errno:%d", errno);
202	}
203#if __GLIBC_PREREQ(2, 7)
204	size = CPU_ALLOC_SIZE(nrcpus);
205	CPU_ZERO_S(size, set);
206#else
207	size = sizeof(cpu_set_t);
208	CPU_ZERO(set);
209#endif
210	if (sched_getaffinity(0, size, set) < 0) {
211		CPU_FREE(set);
212#if __GLIBC_PREREQ(2, 7)
213		if (errno == EINVAL && nrcpus < (1024 << 8)) {
214			nrcpus = nrcpus << 2;
215			goto realloc;
216		}
217#else
218		if (errno == EINVAL)
219			tst_resm(TFAIL,
220				 "NR_CPUS of the kernel is more than 1024, so we'd better use a newer glibc(>= 2.7)");
221		else
222#endif
223			tst_resm(TFAIL, "sched_getaffinity:errno:%d", errno);
224		tst_exit();
225	}
226	cpu_max = max_cpuid(size, set);
227#if __GLIBC_PREREQ(2, 7)
228	CPU_ZERO_S(size, set);
229	CPU_SET_S(cpu_max, size, set);
230#else
231	CPU_ZERO(set);
232	CPU_SET(cpu_max, set);
233#endif
234	if (sched_setaffinity(0, size, set) < 0) {
235		CPU_FREE(set);
236		tst_brkm(TFAIL, NULL, "sched_setaffinity:errno:%d", errno);
237	}
238	CPU_FREE(set);
239	return cpu_max;
240}
241
242/*
243 * Return the maximum cpu id
244 */
245#define BITS_PER_BYTE 8
246unsigned int max_cpuid(size_t size, cpu_set_t * set)
247{
248	unsigned int index, max = 0;
249	for (index = 0; index < size * BITS_PER_BYTE; index++)
250#if __GLIBC_PREREQ(2, 7)
251		if (CPU_ISSET_S(index, size, set))
252#else
253		if (CPU_ISSET(index, set))
254#endif
255			max = index;
256	return max;
257}
258
259/*
260 * get_nodeid(cpuid) - This will return the node to which selected cpu belongs
261 */
262unsigned int get_nodeid(unsigned int cpu_id)
263{
264	DIR *directory_parent, *directory_node;
265	struct dirent *de, *dn;
266	char directory_path[255];
267	unsigned int cpu;
268	int node_id = 0;
269
270	directory_parent = opendir("/sys/devices/system/node");
271	if (!directory_parent) {
272		tst_resm(TCONF,
273			 "/sys not mounted or not a numa system. Assuming one node");
274		tst_resm(TCONF,
275			 "Error opening: /sys/devices/system/node :%s",
276			 strerror(errno));
277		return 0;	//By Default assume it to belong to node Zero
278	} else {
279		while ((de = readdir(directory_parent)) != NULL) {
280			if (strncmp(de->d_name, "node", 4))
281				continue;
282			sprintf(directory_path, "/sys/devices/system/node/%s",
283				de->d_name);
284			directory_node = opendir(directory_path);
285			while ((dn = readdir(directory_node)) != NULL) {
286				if (strncmp(dn->d_name, "cpu", 3))
287					continue;
288				cpu = strtoul(dn->d_name + 3, NULL, 0);
289				if (cpu == cpu_id) {
290					node_id =
291					    strtoul(de->d_name + 4, NULL, 0);
292					break;
293				}
294			}
295			closedir(directory_node);
296		}
297		closedir(directory_parent);
298	}
299	return node_id;
300}
301
302/*
303 * cleanup() - performs all the ONE TIME cleanup for this test at completion
304 * 	       or premature exit.
305 */
306void cleanup(void)
307{
308
309}
310