1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Context switch microbenchmark.
4 *
5 * Copyright (C) 2015 Anton Blanchard <anton@au.ibm.com>, IBM
6 */
7
8#define _GNU_SOURCE
9#include <errno.h>
10#include <sched.h>
11#include <string.h>
12#include <stdio.h>
13#include <unistd.h>
14#include <stdlib.h>
15#include <getopt.h>
16#include <signal.h>
17#include <assert.h>
18#include <pthread.h>
19#include <limits.h>
20#include <sys/time.h>
21#include <sys/syscall.h>
22#include <sys/sysinfo.h>
23#include <sys/types.h>
24#include <sys/shm.h>
25#include <linux/futex.h>
26#ifdef __powerpc__
27#include <altivec.h>
28#endif
29#include "utils.h"
30
31static unsigned int timeout = 30;
32
33static int touch_vdso;
34struct timeval tv;
35
36static int touch_fp = 1;
37double fp;
38
39static int touch_vector = 1;
40vector int a, b, c;
41
42#ifdef __powerpc__
43static int touch_altivec = 1;
44
45/*
46 * Note: LTO (Link Time Optimisation) doesn't play well with this function
47 * attribute. Be very careful enabling LTO for this test.
48 */
49static void __attribute__((__target__("no-vsx"))) altivec_touch_fn(void)
50{
51	c = a + b;
52}
53#endif
54
55static void touch(void)
56{
57	if (touch_vdso)
58		gettimeofday(&tv, NULL);
59
60	if (touch_fp)
61		fp += 0.1;
62
63#ifdef __powerpc__
64	if (touch_altivec)
65		altivec_touch_fn();
66#endif
67
68	if (touch_vector)
69		c = a + b;
70
71	asm volatile("# %0 %1 %2": : "r"(&tv), "r"(&fp), "r"(&c));
72}
73
74static void start_thread_on(void *(*fn)(void *), void *arg, unsigned long cpu)
75{
76	int rc;
77	pthread_t tid;
78	cpu_set_t cpuset;
79	pthread_attr_t attr;
80
81	CPU_ZERO(&cpuset);
82	CPU_SET(cpu, &cpuset);
83
84	rc = pthread_attr_init(&attr);
85	if (rc) {
86		errno = rc;
87		perror("pthread_attr_init");
88		exit(1);
89	}
90
91	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
92	if (rc)	{
93		errno = rc;
94		perror("pthread_attr_setaffinity_np");
95		exit(1);
96	}
97
98	rc = pthread_create(&tid, &attr, fn, arg);
99	if (rc) {
100		errno = rc;
101		perror("pthread_create");
102		exit(1);
103	}
104}
105
106static void start_process_on(void *(*fn)(void *), void *arg, unsigned long cpu)
107{
108	int pid, ncpus;
109	cpu_set_t *cpuset;
110	size_t size;
111
112	pid = fork();
113	if (pid == -1) {
114		perror("fork");
115		exit(1);
116	}
117
118	if (pid)
119		return;
120
121	ncpus = get_nprocs();
122	size = CPU_ALLOC_SIZE(ncpus);
123	cpuset = CPU_ALLOC(ncpus);
124	if (!cpuset) {
125		perror("malloc");
126		exit(1);
127	}
128	CPU_ZERO_S(size, cpuset);
129	CPU_SET_S(cpu, size, cpuset);
130
131	if (sched_setaffinity(0, size, cpuset)) {
132		perror("sched_setaffinity");
133		CPU_FREE(cpuset);
134		exit(1);
135	}
136
137	CPU_FREE(cpuset);
138	fn(arg);
139
140	exit(0);
141}
142
143static unsigned long iterations;
144static unsigned long iterations_prev;
145
146static void sigalrm_handler(int junk)
147{
148	unsigned long i = iterations;
149
150	printf("%ld\n", i - iterations_prev);
151	iterations_prev = i;
152
153	if (--timeout == 0)
154		kill(0, SIGUSR1);
155
156	alarm(1);
157}
158
159static void sigusr1_handler(int junk)
160{
161	exit(0);
162}
163
164struct actions {
165	void (*setup)(int, int);
166	void *(*thread1)(void *);
167	void *(*thread2)(void *);
168};
169
170#define READ 0
171#define WRITE 1
172
173static int pipe_fd1[2];
174static int pipe_fd2[2];
175
176static void pipe_setup(int cpu1, int cpu2)
177{
178	if (pipe(pipe_fd1) || pipe(pipe_fd2))
179		exit(1);
180}
181
182static void *pipe_thread1(void *arg)
183{
184	signal(SIGALRM, sigalrm_handler);
185	alarm(1);
186
187	while (1) {
188		assert(read(pipe_fd1[READ], &c, 1) == 1);
189		touch();
190
191		assert(write(pipe_fd2[WRITE], &c, 1) == 1);
192		touch();
193
194		iterations += 2;
195	}
196
197	return NULL;
198}
199
200static void *pipe_thread2(void *arg)
201{
202	while (1) {
203		assert(write(pipe_fd1[WRITE], &c, 1) == 1);
204		touch();
205
206		assert(read(pipe_fd2[READ], &c, 1) == 1);
207		touch();
208	}
209
210	return NULL;
211}
212
213static struct actions pipe_actions = {
214	.setup = pipe_setup,
215	.thread1 = pipe_thread1,
216	.thread2 = pipe_thread2,
217};
218
219static void yield_setup(int cpu1, int cpu2)
220{
221	if (cpu1 != cpu2) {
222		fprintf(stderr, "Both threads must be on the same CPU for yield test\n");
223		exit(1);
224	}
225}
226
227static void *yield_thread1(void *arg)
228{
229	signal(SIGALRM, sigalrm_handler);
230	alarm(1);
231
232	while (1) {
233		sched_yield();
234		touch();
235
236		iterations += 2;
237	}
238
239	return NULL;
240}
241
242static void *yield_thread2(void *arg)
243{
244	while (1) {
245		sched_yield();
246		touch();
247	}
248
249	return NULL;
250}
251
252static struct actions yield_actions = {
253	.setup = yield_setup,
254	.thread1 = yield_thread1,
255	.thread2 = yield_thread2,
256};
257
258static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout,
259		      void *addr2, int val3)
260{
261	return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
262}
263
264static unsigned long cmpxchg(unsigned long *p, unsigned long expected,
265			     unsigned long desired)
266{
267	unsigned long exp = expected;
268
269	__atomic_compare_exchange_n(p, &exp, desired, 0,
270				    __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
271	return exp;
272}
273
274static unsigned long xchg(unsigned long *p, unsigned long val)
275{
276	return __atomic_exchange_n(p, val, __ATOMIC_SEQ_CST);
277}
278
279static int processes;
280
281static int mutex_lock(unsigned long *m)
282{
283	int c;
284	int flags = FUTEX_WAIT;
285	if (!processes)
286		flags |= FUTEX_PRIVATE_FLAG;
287
288	c = cmpxchg(m, 0, 1);
289	if (!c)
290		return 0;
291
292	if (c == 1)
293		c = xchg(m, 2);
294
295	while (c) {
296		sys_futex(m, flags, 2, NULL, NULL, 0);
297		c = xchg(m, 2);
298	}
299
300	return 0;
301}
302
303static int mutex_unlock(unsigned long *m)
304{
305	int flags = FUTEX_WAKE;
306	if (!processes)
307		flags |= FUTEX_PRIVATE_FLAG;
308
309	if (*m == 2)
310		*m = 0;
311	else if (xchg(m, 0) == 1)
312		return 0;
313
314	sys_futex(m, flags, 1, NULL, NULL, 0);
315
316	return 0;
317}
318
319static unsigned long *m1, *m2;
320
321static void futex_setup(int cpu1, int cpu2)
322{
323	if (!processes) {
324		static unsigned long _m1, _m2;
325		m1 = &_m1;
326		m2 = &_m2;
327	} else {
328		int shmid;
329		void *shmaddr;
330
331		shmid = shmget(IPC_PRIVATE, getpagesize(), SHM_R | SHM_W);
332		if (shmid < 0) {
333			perror("shmget");
334			exit(1);
335		}
336
337		shmaddr = shmat(shmid, NULL, 0);
338		if (shmaddr == (char *)-1) {
339			perror("shmat");
340			shmctl(shmid, IPC_RMID, NULL);
341			exit(1);
342		}
343
344		shmctl(shmid, IPC_RMID, NULL);
345
346		m1 = shmaddr;
347		m2 = shmaddr + sizeof(*m1);
348	}
349
350	*m1 = 0;
351	*m2 = 0;
352
353	mutex_lock(m1);
354	mutex_lock(m2);
355}
356
357static void *futex_thread1(void *arg)
358{
359	signal(SIGALRM, sigalrm_handler);
360	alarm(1);
361
362	while (1) {
363		mutex_lock(m2);
364		mutex_unlock(m1);
365
366		iterations += 2;
367	}
368
369	return NULL;
370}
371
372static void *futex_thread2(void *arg)
373{
374	while (1) {
375		mutex_unlock(m2);
376		mutex_lock(m1);
377	}
378
379	return NULL;
380}
381
382static struct actions futex_actions = {
383	.setup = futex_setup,
384	.thread1 = futex_thread1,
385	.thread2 = futex_thread2,
386};
387
388static struct option options[] = {
389	{ "test", required_argument, 0, 't' },
390	{ "process", no_argument, &processes, 1 },
391	{ "timeout", required_argument, 0, 's' },
392	{ "vdso", no_argument, &touch_vdso, 1 },
393	{ "no-fp", no_argument, &touch_fp, 0 },
394#ifdef __powerpc__
395	{ "no-altivec", no_argument, &touch_altivec, 0 },
396#endif
397	{ "no-vector", no_argument, &touch_vector, 0 },
398	{ 0, },
399};
400
401static void usage(void)
402{
403	fprintf(stderr, "Usage: context_switch2 <options> CPU1 CPU2\n\n");
404	fprintf(stderr, "\t\t--test=X\tpipe, futex or yield (default)\n");
405	fprintf(stderr, "\t\t--process\tUse processes (default threads)\n");
406	fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
407	fprintf(stderr, "\t\t--vdso\t\ttouch VDSO\n");
408	fprintf(stderr, "\t\t--no-fp\t\tDon't touch FP\n");
409#ifdef __powerpc__
410	fprintf(stderr, "\t\t--no-altivec\tDon't touch altivec\n");
411#endif
412	fprintf(stderr, "\t\t--no-vector\tDon't touch vector\n");
413}
414
415int main(int argc, char *argv[])
416{
417	signed char c;
418	struct actions *actions = &yield_actions;
419	int cpu1;
420	int cpu2;
421	static void (*start_fn)(void *(*fn)(void *), void *arg, unsigned long cpu);
422
423	while (1) {
424		int option_index = 0;
425
426		c = getopt_long(argc, argv, "", options, &option_index);
427
428		if (c == -1)
429			break;
430
431		switch (c) {
432		case 0:
433			if (options[option_index].flag != 0)
434				break;
435
436			usage();
437			exit(1);
438			break;
439
440		case 't':
441			if (!strcmp(optarg, "pipe")) {
442				actions = &pipe_actions;
443			} else if (!strcmp(optarg, "yield")) {
444				actions = &yield_actions;
445			} else if (!strcmp(optarg, "futex")) {
446				actions = &futex_actions;
447			} else {
448				usage();
449				exit(1);
450			}
451			break;
452
453		case 's':
454			timeout = atoi(optarg);
455			break;
456
457		default:
458			usage();
459			exit(1);
460		}
461	}
462
463	if (processes)
464		start_fn = start_process_on;
465	else
466		start_fn = start_thread_on;
467
468	if (((argc - optind) != 2)) {
469		cpu1 = cpu2 = pick_online_cpu();
470	} else {
471		cpu1 = atoi(argv[optind++]);
472		cpu2 = atoi(argv[optind++]);
473	}
474
475	printf("Using %s with ", processes ? "processes" : "threads");
476
477	if (actions == &pipe_actions)
478		printf("pipe");
479	else if (actions == &yield_actions)
480		printf("yield");
481	else
482		printf("futex");
483
484	if (!have_hwcap(PPC_FEATURE_HAS_ALTIVEC))
485		touch_altivec = 0;
486
487	if (!have_hwcap(PPC_FEATURE_HAS_VSX))
488		touch_vector = 0;
489
490	printf(" on cpus %d/%d touching FP:%s altivec:%s vector:%s vdso:%s\n",
491	       cpu1, cpu2, touch_fp ?  "yes" : "no", touch_altivec ? "yes" : "no",
492	       touch_vector ? "yes" : "no", touch_vdso ? "yes" : "no");
493
494	/* Create a new process group so we can signal everyone for exit */
495	setpgid(getpid(), getpid());
496
497	signal(SIGUSR1, sigusr1_handler);
498
499	actions->setup(cpu1, cpu2);
500
501	start_fn(actions->thread1, NULL, cpu1);
502	start_fn(actions->thread2, NULL, cpu2);
503
504	while (1)
505		sleep(3600);
506
507	return 0;
508}
509