1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * This test checks the response of the system clock to frequency |
4 | * steps made with adjtimex(). The frequency error and stability of |
5 | * the CLOCK_MONOTONIC clock relative to the CLOCK_MONOTONIC_RAW clock |
6 | * is measured in two intervals following the step. The test fails if |
7 | * values from the second interval exceed specified limits. |
8 | * |
9 | * Copyright (C) Miroslav Lichvar <mlichvar@redhat.com> 2017 |
10 | */ |
11 | |
12 | #include <math.h> |
13 | #include <stdio.h> |
14 | #include <sys/timex.h> |
15 | #include <time.h> |
16 | #include <unistd.h> |
17 | |
18 | #include "../kselftest.h" |
19 | |
20 | #define SAMPLES 100 |
21 | #define SAMPLE_READINGS 10 |
22 | #define MEAN_SAMPLE_INTERVAL 0.1 |
23 | #define STEP_INTERVAL 1.0 |
24 | #define MAX_PRECISION 500e-9 |
25 | #define MAX_FREQ_ERROR 0.02e-6 |
26 | #define MAX_STDDEV 50e-9 |
27 | |
28 | #ifndef ADJ_SETOFFSET |
29 | #define ADJ_SETOFFSET 0x0100 |
30 | #endif |
31 | |
32 | struct sample { |
33 | double offset; |
34 | double time; |
35 | }; |
36 | |
37 | static time_t mono_raw_base; |
38 | static time_t mono_base; |
39 | static long user_hz; |
40 | static double precision; |
41 | static double mono_freq_offset; |
42 | |
43 | static double diff_timespec(struct timespec *ts1, struct timespec *ts2) |
44 | { |
45 | return ts1->tv_sec - ts2->tv_sec + (ts1->tv_nsec - ts2->tv_nsec) / 1e9; |
46 | } |
47 | |
48 | static double get_sample(struct sample *sample) |
49 | { |
50 | double delay, mindelay = 0.0; |
51 | struct timespec ts1, ts2, ts3; |
52 | int i; |
53 | |
54 | for (i = 0; i < SAMPLE_READINGS; i++) { |
55 | clock_gettime(CLOCK_MONOTONIC_RAW, &ts1); |
56 | clock_gettime(CLOCK_MONOTONIC, &ts2); |
57 | clock_gettime(CLOCK_MONOTONIC_RAW, &ts3); |
58 | |
59 | ts1.tv_sec -= mono_raw_base; |
60 | ts2.tv_sec -= mono_base; |
61 | ts3.tv_sec -= mono_raw_base; |
62 | |
63 | delay = diff_timespec(ts1: &ts3, ts2: &ts1); |
64 | if (delay <= 1e-9) { |
65 | i--; |
66 | continue; |
67 | } |
68 | |
69 | if (!i || delay < mindelay) { |
70 | sample->offset = diff_timespec(ts1: &ts2, ts2: &ts1); |
71 | sample->offset -= delay / 2.0; |
72 | sample->time = ts1.tv_sec + ts1.tv_nsec / 1e9; |
73 | mindelay = delay; |
74 | } |
75 | } |
76 | |
77 | return mindelay; |
78 | } |
79 | |
80 | static void reset_ntp_error(void) |
81 | { |
82 | struct timex txc; |
83 | |
84 | txc.modes = ADJ_SETOFFSET; |
85 | txc.time.tv_sec = 0; |
86 | txc.time.tv_usec = 0; |
87 | |
88 | if (adjtimex(&txc) < 0) { |
89 | perror("[FAIL] adjtimex" ); |
90 | ksft_exit_fail(); |
91 | } |
92 | } |
93 | |
94 | static void set_frequency(double freq) |
95 | { |
96 | struct timex txc; |
97 | int tick_offset; |
98 | |
99 | tick_offset = 1e6 * freq / user_hz; |
100 | |
101 | txc.modes = ADJ_TICK | ADJ_FREQUENCY; |
102 | txc.tick = 1000000 / user_hz + tick_offset; |
103 | txc.freq = (1e6 * freq - user_hz * tick_offset) * (1 << 16); |
104 | |
105 | if (adjtimex(&txc) < 0) { |
106 | perror("[FAIL] adjtimex" ); |
107 | ksft_exit_fail(); |
108 | } |
109 | } |
110 | |
111 | static void regress(struct sample *samples, int n, double *intercept, |
112 | double *slope, double *r_stddev, double *r_max) |
113 | { |
114 | double x, y, r, x_sum, y_sum, xy_sum, x2_sum, r2_sum; |
115 | int i; |
116 | |
117 | x_sum = 0.0, y_sum = 0.0, xy_sum = 0.0, x2_sum = 0.0; |
118 | |
119 | for (i = 0; i < n; i++) { |
120 | x = samples[i].time; |
121 | y = samples[i].offset; |
122 | |
123 | x_sum += x; |
124 | y_sum += y; |
125 | xy_sum += x * y; |
126 | x2_sum += x * x; |
127 | } |
128 | |
129 | *slope = (xy_sum - x_sum * y_sum / n) / (x2_sum - x_sum * x_sum / n); |
130 | *intercept = (y_sum - *slope * x_sum) / n; |
131 | |
132 | *r_max = 0.0, r2_sum = 0.0; |
133 | |
134 | for (i = 0; i < n; i++) { |
135 | x = samples[i].time; |
136 | y = samples[i].offset; |
137 | r = fabs(x * *slope + *intercept - y); |
138 | if (*r_max < r) |
139 | *r_max = r; |
140 | r2_sum += r * r; |
141 | } |
142 | |
143 | *r_stddev = sqrt(r2_sum / n); |
144 | } |
145 | |
146 | static int run_test(int calibration, double freq_base, double freq_step) |
147 | { |
148 | struct sample samples[SAMPLES]; |
149 | double intercept, slope, stddev1, max1, stddev2, max2; |
150 | double freq_error1, freq_error2; |
151 | int i; |
152 | |
153 | set_frequency(freq_base); |
154 | |
155 | for (i = 0; i < 10; i++) |
156 | usleep(1e6 * MEAN_SAMPLE_INTERVAL / 10); |
157 | |
158 | reset_ntp_error(); |
159 | |
160 | set_frequency(freq_base + freq_step); |
161 | |
162 | for (i = 0; i < 10; i++) |
163 | usleep(rand() % 2000000 * STEP_INTERVAL / 10); |
164 | |
165 | set_frequency(freq_base); |
166 | |
167 | for (i = 0; i < SAMPLES; i++) { |
168 | usleep(rand() % 2000000 * MEAN_SAMPLE_INTERVAL); |
169 | get_sample(sample: &samples[i]); |
170 | } |
171 | |
172 | if (calibration) { |
173 | regress(samples, SAMPLES, intercept: &intercept, slope: &slope, r_stddev: &stddev1, r_max: &max1); |
174 | mono_freq_offset = slope; |
175 | printf("CLOCK_MONOTONIC_RAW frequency offset: %11.3f ppm\n" , |
176 | 1e6 * mono_freq_offset); |
177 | return 0; |
178 | } |
179 | |
180 | regress(samples, SAMPLES / 2, intercept: &intercept, slope: &slope, r_stddev: &stddev1, r_max: &max1); |
181 | freq_error1 = slope * (1.0 - mono_freq_offset) - mono_freq_offset - |
182 | freq_base; |
183 | |
184 | regress(samples: samples + SAMPLES / 2, SAMPLES / 2, intercept: &intercept, slope: &slope, |
185 | r_stddev: &stddev2, r_max: &max2); |
186 | freq_error2 = slope * (1.0 - mono_freq_offset) - mono_freq_offset - |
187 | freq_base; |
188 | |
189 | printf("%6.0f %+10.3f %6.0f %7.0f %+10.3f %6.0f %7.0f\t" , |
190 | 1e6 * freq_step, |
191 | 1e6 * freq_error1, 1e9 * stddev1, 1e9 * max1, |
192 | 1e6 * freq_error2, 1e9 * stddev2, 1e9 * max2); |
193 | |
194 | if (fabs(freq_error2) > MAX_FREQ_ERROR || stddev2 > MAX_STDDEV) { |
195 | printf("[FAIL]\n" ); |
196 | return 1; |
197 | } |
198 | |
199 | printf("[OK]\n" ); |
200 | return 0; |
201 | } |
202 | |
203 | static void init_test(void) |
204 | { |
205 | struct timespec ts; |
206 | struct sample sample; |
207 | |
208 | if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts)) { |
209 | perror("[FAIL] clock_gettime(CLOCK_MONOTONIC_RAW)" ); |
210 | ksft_exit_fail(); |
211 | } |
212 | |
213 | mono_raw_base = ts.tv_sec; |
214 | |
215 | if (clock_gettime(CLOCK_MONOTONIC, &ts)) { |
216 | perror("[FAIL] clock_gettime(CLOCK_MONOTONIC)" ); |
217 | ksft_exit_fail(); |
218 | } |
219 | |
220 | mono_base = ts.tv_sec; |
221 | |
222 | user_hz = sysconf(_SC_CLK_TCK); |
223 | |
224 | precision = get_sample(sample: &sample) / 2.0; |
225 | printf("CLOCK_MONOTONIC_RAW+CLOCK_MONOTONIC precision: %.0f ns\t\t" , |
226 | 1e9 * precision); |
227 | |
228 | if (precision > MAX_PRECISION) |
229 | ksft_exit_skip(msg: "precision: %.0f ns > MAX_PRECISION: %.0f ns\n" , |
230 | 1e9 * precision, 1e9 * MAX_PRECISION); |
231 | |
232 | printf("[OK]\n" ); |
233 | srand(ts.tv_sec ^ ts.tv_nsec); |
234 | |
235 | run_test(calibration: 1, freq_base: 0.0, freq_step: 0.0); |
236 | } |
237 | |
238 | int main(int argc, char **argv) |
239 | { |
240 | double freq_base, freq_step; |
241 | int i, j, fails = 0; |
242 | |
243 | init_test(); |
244 | |
245 | printf("Checking response to frequency step:\n" ); |
246 | printf(" Step 1st interval 2nd interval\n" ); |
247 | printf(" Freq Dev Max Freq Dev Max\n" ); |
248 | |
249 | for (i = 2; i >= 0; i--) { |
250 | for (j = 0; j < 5; j++) { |
251 | freq_base = (rand() % (1 << 24) - (1 << 23)) / 65536e6; |
252 | freq_step = 10e-6 * (1 << (6 * i)); |
253 | fails += run_test(calibration: 0, freq_base, freq_step); |
254 | } |
255 | } |
256 | |
257 | set_frequency(0.0); |
258 | |
259 | if (fails) |
260 | return ksft_exit_fail(); |
261 | |
262 | return ksft_exit_pass(); |
263 | } |
264 | |