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 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 154 FIXTURE_TEARDOWN(slice_ext) 155 { 156 self->noise_params.run = 0; 157 pthread_join(self->noise_thread, NULL); 158 } 159 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