xref: /linux/tools/testing/selftests/rseq/slice_test.c (revision 23b0f90ba871f096474e1c27c3d14f455189d2d9)
1 // SPDX-License-Identifier: LGPL-2.1
2 #define _GNU_SOURCE
3 #include <assert.h>
4 #include <pthread.h>
5 #include <sched.h>
6 #include <signal.h>
7 #include <stdbool.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <syscall.h>
11 #include <unistd.h>
12 
13 #include <linux/prctl.h>
14 #include <sys/prctl.h>
15 #include <sys/time.h>
16 
17 #include "rseq.h"
18 
19 #include "../kselftest_harness.h"
20 
21 #ifndef __NR_rseq_slice_yield
22 # define __NR_rseq_slice_yield	471
23 #endif
24 
25 #define BITS_PER_INT	32
26 #define BITS_PER_BYTE	8
27 
28 #ifndef PR_RSEQ_SLICE_EXTENSION
29 # define PR_RSEQ_SLICE_EXTENSION		79
30 #  define PR_RSEQ_SLICE_EXTENSION_GET		1
31 #  define PR_RSEQ_SLICE_EXTENSION_SET		2
32 #  define PR_RSEQ_SLICE_EXT_ENABLE		0x01
33 #endif
34 
35 #ifndef RSEQ_SLICE_EXT_REQUEST_BIT
36 # define RSEQ_SLICE_EXT_REQUEST_BIT	0
37 # define RSEQ_SLICE_EXT_GRANTED_BIT	1
38 #endif
39 
40 #ifndef asm_inline
41 # define asm_inline	asm __inline
42 #endif
43 
44 #define NSEC_PER_SEC	1000000000L
45 #define NSEC_PER_USEC	      1000L
46 
47 struct noise_params {
48 	int64_t	noise_nsecs;
49 	int64_t	sleep_nsecs;
50 	int64_t	run;
51 };
52 
53 FIXTURE(slice_ext)
54 {
55 	pthread_t		noise_thread;
56 	struct noise_params	noise_params;
57 };
58 
59 FIXTURE_VARIANT(slice_ext)
60 {
61 	int64_t	total_nsecs;
62 	int64_t	slice_nsecs;
63 	int64_t	noise_nsecs;
64 	int64_t	sleep_nsecs;
65 	bool	no_yield;
66 };
67 
68 FIXTURE_VARIANT_ADD(slice_ext, n2_2_50)
69 {
70 	.total_nsecs	=  5LL * NSEC_PER_SEC,
71 	.slice_nsecs	=  2LL * NSEC_PER_USEC,
72 	.noise_nsecs    =  2LL * NSEC_PER_USEC,
73 	.sleep_nsecs	= 50LL * NSEC_PER_USEC,
74 };
75 
76 FIXTURE_VARIANT_ADD(slice_ext, n50_2_50)
77 {
78 	.total_nsecs	=  5LL * NSEC_PER_SEC,
79 	.slice_nsecs	= 50LL * NSEC_PER_USEC,
80 	.noise_nsecs    =  2LL * NSEC_PER_USEC,
81 	.sleep_nsecs	= 50LL * NSEC_PER_USEC,
82 };
83 
84 FIXTURE_VARIANT_ADD(slice_ext, n2_2_50_no_yield)
85 {
86 	.total_nsecs	=  5LL * NSEC_PER_SEC,
87 	.slice_nsecs	=  2LL * NSEC_PER_USEC,
88 	.noise_nsecs    =  2LL * NSEC_PER_USEC,
89 	.sleep_nsecs	= 50LL * NSEC_PER_USEC,
90 	.no_yield	= true,
91 };
92 
93 
94 static inline bool elapsed(struct timespec *start, struct timespec *now,
95 			   int64_t span)
96 {
97 	int64_t delta = now->tv_sec - start->tv_sec;
98 
99 	delta *= NSEC_PER_SEC;
100 	delta += now->tv_nsec - start->tv_nsec;
101 	return delta >= span;
102 }
103 
104 static void *noise_thread(void *arg)
105 {
106 	struct noise_params *p = arg;
107 
108 	while (RSEQ_READ_ONCE(p->run)) {
109 		struct timespec ts_start, ts_now;
110 
111 		clock_gettime(CLOCK_MONOTONIC, &ts_start);
112 		do {
113 			clock_gettime(CLOCK_MONOTONIC, &ts_now);
114 		} while (!elapsed(&ts_start, &ts_now, p->noise_nsecs));
115 
116 		ts_start.tv_sec = 0;
117 		ts_start.tv_nsec = p->sleep_nsecs;
118 		clock_nanosleep(CLOCK_MONOTONIC, 0, &ts_start, NULL);
119 	}
120 	return NULL;
121 }
122 
123 FIXTURE_SETUP(slice_ext)
124 {
125 	cpu_set_t affinity;
126 
127 	ASSERT_EQ(sched_getaffinity(0, sizeof(affinity), &affinity), 0);
128 
129 	/* Pin it on a single CPU. Avoid CPU 0 */
130 	for (int i = 1; i < CPU_SETSIZE; i++) {
131 		if (!CPU_ISSET(i, &affinity))
132 			continue;
133 
134 		CPU_ZERO(&affinity);
135 		CPU_SET(i, &affinity);
136 		ASSERT_EQ(sched_setaffinity(0, sizeof(affinity), &affinity), 0);
137 		break;
138 	}
139 
140 	ASSERT_EQ(rseq_register_current_thread(), 0);
141 
142 	ASSERT_EQ(prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET,
143 			PR_RSEQ_SLICE_EXT_ENABLE, 0, 0), 0);
144 
145 	self->noise_params.noise_nsecs = variant->noise_nsecs;
146 	self->noise_params.sleep_nsecs = variant->sleep_nsecs;
147 	self->noise_params.run = 1;
148 
149 	ASSERT_EQ(pthread_create(&self->noise_thread, NULL, noise_thread, &self->noise_params), 0);
150 }
151 
152 FIXTURE_TEARDOWN(slice_ext)
153 {
154 	self->noise_params.run = 0;
155 	pthread_join(self->noise_thread, NULL);
156 }
157 
158 TEST_F(slice_ext, slice_test)
159 {
160 	unsigned long success = 0, yielded = 0, scheduled = 0, raced = 0;
161 	unsigned long total = 0, aborted = 0;
162 	struct rseq_abi *rs = rseq_get_abi();
163 	struct timespec ts_start, ts_now;
164 
165 	ASSERT_NE(rs, NULL);
166 
167 	clock_gettime(CLOCK_MONOTONIC, &ts_start);
168 	do {
169 		struct timespec ts_cs;
170 		bool req = false;
171 
172 		clock_gettime(CLOCK_MONOTONIC, &ts_cs);
173 
174 		total++;
175 		RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 1);
176 		do {
177 			clock_gettime(CLOCK_MONOTONIC, &ts_now);
178 		} while (!elapsed(&ts_cs, &ts_now, variant->slice_nsecs));
179 
180 		/*
181 		 * request can be cleared unconditionally, but for making
182 		 * the stats work this is actually checking it first
183 		 */
184 		if (RSEQ_READ_ONCE(rs->slice_ctrl.request)) {
185 			RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 0);
186 			/* Race between check and clear! */
187 			req = true;
188 			success++;
189 		}
190 
191 		if (RSEQ_READ_ONCE(rs->slice_ctrl.granted)) {
192 			/* The above raced against a late grant */
193 			if (req)
194 				success--;
195 			if (variant->no_yield) {
196 				syscall(__NR_getpid);
197 				aborted++;
198 			} else {
199 				yielded++;
200 				if (!syscall(__NR_rseq_slice_yield))
201 					raced++;
202 			}
203 		} else {
204 			if (!req)
205 				scheduled++;
206 		}
207 
208 		clock_gettime(CLOCK_MONOTONIC, &ts_now);
209 	} while (!elapsed(&ts_start, &ts_now, variant->total_nsecs));
210 
211 	printf("# Total     %12ld\n", total);
212 	printf("# Success   %12ld\n", success);
213 	printf("# Yielded   %12ld\n", yielded);
214 	printf("# Aborted   %12ld\n", aborted);
215 	printf("# Scheduled %12ld\n", scheduled);
216 	printf("# Raced     %12ld\n", raced);
217 }
218 
219 TEST_HARNESS_MAIN
220