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
FIXTURE(slice_ext)53 FIXTURE(slice_ext)
54 {
55 pthread_t noise_thread;
56 struct noise_params noise_params;
57 };
58
FIXTURE_VARIANT(slice_ext)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
FIXTURE_VARIANT_ADD(slice_ext,n2_2_50)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
FIXTURE_VARIANT_ADD(slice_ext,n50_2_50)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
FIXTURE_VARIANT_ADD(slice_ext,n2_2_50_no_yield)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
elapsed(struct timespec * start,struct timespec * now,int64_t span)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
noise_thread(void * arg)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
FIXTURE_SETUP(slice_ext)123 FIXTURE_SETUP(slice_ext)
124 {
125 cpu_set_t affinity;
126
127 if (__rseq_register_current_thread(true, false))
128 SKIP(return, "RSEQ not supported\n");
129
130 if (prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET,
131 PR_RSEQ_SLICE_EXT_ENABLE, 0, 0))
132 SKIP(return, "Time slice extension not supported\n");
133
134 ASSERT_EQ(sched_getaffinity(0, sizeof(affinity), &affinity), 0);
135
136 /* Pin it on a single CPU. Avoid CPU 0 */
137 for (int i = 1; i < CPU_SETSIZE; i++) {
138 if (!CPU_ISSET(i, &affinity))
139 continue;
140
141 CPU_ZERO(&affinity);
142 CPU_SET(i, &affinity);
143 ASSERT_EQ(sched_setaffinity(0, sizeof(affinity), &affinity), 0);
144 break;
145 }
146
147 self->noise_params.noise_nsecs = variant->noise_nsecs;
148 self->noise_params.sleep_nsecs = variant->sleep_nsecs;
149 self->noise_params.run = 1;
150
151 ASSERT_EQ(pthread_create(&self->noise_thread, NULL, noise_thread, &self->noise_params), 0);
152 }
153
FIXTURE_TEARDOWN(slice_ext)154 FIXTURE_TEARDOWN(slice_ext)
155 {
156 self->noise_params.run = 0;
157 pthread_join(self->noise_thread, NULL);
158 }
159
TEST_F(slice_ext,slice_test)160 TEST_F(slice_ext, slice_test)
161 {
162 unsigned long success = 0, yielded = 0, scheduled = 0, raced = 0;
163 unsigned long total = 0, aborted = 0;
164 struct rseq_abi *rs = rseq_get_abi();
165 struct timespec ts_start, ts_now;
166
167 ASSERT_NE(rs, NULL);
168
169 clock_gettime(CLOCK_MONOTONIC, &ts_start);
170 do {
171 struct timespec ts_cs;
172 bool req = false;
173
174 clock_gettime(CLOCK_MONOTONIC, &ts_cs);
175
176 total++;
177 RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 1);
178 do {
179 clock_gettime(CLOCK_MONOTONIC, &ts_now);
180 } while (!elapsed(&ts_cs, &ts_now, variant->slice_nsecs));
181
182 /*
183 * request can be cleared unconditionally, but for making
184 * the stats work this is actually checking it first
185 */
186 if (RSEQ_READ_ONCE(rs->slice_ctrl.request)) {
187 RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 0);
188 /* Race between check and clear! */
189 req = true;
190 success++;
191 }
192
193 if (RSEQ_READ_ONCE(rs->slice_ctrl.granted)) {
194 /* The above raced against a late grant */
195 if (req)
196 success--;
197 if (variant->no_yield) {
198 syscall(__NR_getpid);
199 aborted++;
200 } else {
201 yielded++;
202 if (!syscall(__NR_rseq_slice_yield))
203 raced++;
204 }
205 } else {
206 if (!req)
207 scheduled++;
208 }
209
210 clock_gettime(CLOCK_MONOTONIC, &ts_now);
211 } while (!elapsed(&ts_start, &ts_now, variant->total_nsecs));
212
213 printf("# Total %12ld\n", total);
214 printf("# Success %12ld\n", success);
215 printf("# Yielded %12ld\n", yielded);
216 printf("# Aborted %12ld\n", aborted);
217 printf("# Scheduled %12ld\n", scheduled);
218 printf("# Raced %12ld\n", raced);
219 }
220
221 TEST_HARNESS_MAIN
222