aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/timers/leap-a-day.c
blob: fb46ad6ac92cadc71254c68c80953eeae51b218b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/* Leap second stress test
 *              by: John Stultz (john.stultz@linaro.org)
 *              (C) Copyright IBM 2012
 *              (C) Copyright 2013, 2015 Linaro Limited
 *              Licensed under the GPLv2
 *
 *  This test signals the kernel to insert a leap second
 *  every day at midnight GMT. This allows for stessing the
 *  kernel's leap-second behavior, as well as how well applications
 *  handle the leap-second discontinuity.
 *
 *  Usage: leap-a-day [-s] [-i <num>]
 *
 *  Options:
 *	-s:	Each iteration, set the date to 10 seconds before midnight GMT.
 *		This speeds up the number of leapsecond transitions tested,
 *		but because it calls settimeofday frequently, advancing the
 *		time by 24 hours every ~16 seconds, it may cause application
 *		disruption.
 *
 *	-i:	Number of iterations to run (default: infinite)
 *
 *  Other notes: Disabling NTP prior to running this is advised, as the two
 *		 may conflict in their commands to the kernel.
 *
 *  To build:
 *	$ gcc leap-a-day.c -o leap-a-day -lrt
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 */



#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/errno.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#ifdef KTEST
#include "../kselftest.h"
#else
static inline int ksft_exit_pass(void)
{
	exit(0);
}
static inline int ksft_exit_fail(void)
{
	exit(1);
}
#endif

#define NSEC_PER_SEC 1000000000ULL
#define CLOCK_TAI 11

time_t next_leap;
int error_found;

/* returns 1 if a <= b, 0 otherwise */
static inline int in_order(struct timespec a, struct timespec b)
{
	if (a.tv_sec < b.tv_sec)
		return 1;
	if (a.tv_sec > b.tv_sec)
		return 0;
	if (a.tv_nsec > b.tv_nsec)
		return 0;
	return 1;
}

struct timespec timespec_add(struct timespec ts, unsigned long long ns)
{
	ts.tv_nsec += ns;
	while (ts.tv_nsec >= NSEC_PER_SEC) {
		ts.tv_nsec -= NSEC_PER_SEC;
		ts.tv_sec++;
	}
	return ts;
}

char *time_state_str(int state)
{
	switch (state) {
	case TIME_OK:	return "TIME_OK";
	case TIME_INS:	return "TIME_INS";
	case TIME_DEL:	return "TIME_DEL";
	case TIME_OOP:	return "TIME_OOP";
	case TIME_WAIT:	return "TIME_WAIT";
	case TIME_BAD:	return "TIME_BAD";
	}
	return "ERROR";
}

/* clear NTP time_status & time_state */
int clear_time_state(void)
{
	struct timex tx;
	int ret;

	/*
	 * We have to call adjtime twice here, as kernels
	 * prior to 6b1859dba01c7 (included in 3.5 and
	 * -stable), had an issue with the state machine
	 * and wouldn't clear the STA_INS/DEL flag directly.
	 */
	tx.modes = ADJ_STATUS;
	tx.status = STA_PLL;
	ret = adjtimex(&tx);

	/* Clear maxerror, as it can cause UNSYNC to be set */
	tx.modes = ADJ_MAXERROR;
	tx.maxerror = 0;
	ret = adjtimex(&tx);

	/* Clear the status */
	tx.modes = ADJ_STATUS;
	tx.status = 0;
	ret = adjtimex(&tx);

	return ret;
}

/* Make sure we cleanup on ctrl-c */
void handler(int unused)
{
	clear_time_state();
	exit(0);
}

void sigalarm(int signo)
{
	struct timex tx;
	int ret;

	tx.modes = 0;
	ret = adjtimex(&tx);

	if (tx.time.tv_sec < next_leap) {
		printf("Error: Early timer expiration! (Should be %ld)\n", next_leap);
		error_found = 1;
		printf("adjtimex: %10ld sec + %6ld us (%i)\t%s\n",
					tx.time.tv_sec,
					tx.time.tv_usec,
					tx.tai,
					time_state_str(ret));
	}
	if (ret != TIME_WAIT) {
		printf("Error: Timer seeing incorrect NTP state? (Should be TIME_WAIT)\n");
		error_found = 1;
		printf("adjtimex: %10ld sec + %6ld us (%i)\t%s\n",
					tx.time.tv_sec,
					tx.time.tv_usec,
					tx.tai,
					time_state_str(ret));
	}
}


/* Test for known hrtimer failure */
void test_hrtimer_failure(void)
{
	struct timespec now, target;

	clock_gettime(CLOCK_REALTIME, &now);
	target = timespec_add(now, NSEC_PER_SEC/2);
	clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &target, NULL);
	clock_gettime(CLOCK_REALTIME, &now);

	if (!in_order(target, now)) {
		printf("ERROR: hrtimer early expiration failure observed.\n");
		error_found = 1;
	}
}

int main(int argc, char **argv)
{
	timer_t tm1;
	struct itimerspec its1;
	struct sigevent se;
	struct sigaction act;
	int signum = SIGRTMAX;
	int settime = 0;
	int tai_time = 0;
	int insert = 1;
	int iterations = -1;
	int opt;

	/* Process arguments */
	while ((opt = getopt(argc, argv, "sti:")) != -1) {
		switch (opt) {
		case 's':
			printf("Setting time to speed up testing\n");
			settime = 1;
			break;
		case 'i':
			iterations = atoi(optarg);
			break;
		case 't':
			tai_time = 1;
			break;
		default:
			printf("Usage: %s [-s] [-i <iterations>]\n", argv[0]);
			printf("	-s: Set time to right before leap second each iteration\n");
			printf("	-i: Number of iterations\n");
			printf("	-t: Print TAI time\n");
			exit(-1);
		}
	}

	/* Make sure TAI support is present if -t was used */
	if (tai_time) {
		struct timespec ts;

		if (clock_gettime(CLOCK_TAI, &ts)) {
			printf("System doesn't support CLOCK_TAI\n");
			ksft_exit_fail();
		}
	}

	signal(SIGINT, handler);
	signal(SIGKILL, handler);

	/* Set up timer signal handler: */
	sigfillset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = sigalarm;
	sigaction(signum, &act, NULL);

	if (iterations < 0)
		printf("This runs continuously. Press ctrl-c to stop\n");
	else
		printf("Running for %i iterations. Press ctrl-c to stop\n", iterations);

	printf("\n");
	while (1) {
		int ret;
		struct timespec ts;
		struct timex tx;
		time_t now;

		/* Get the current time */
		clock_gettime(CLOCK_REALTIME, &ts);

		/* Calculate the next possible leap second 23:59:60 GMT */
		next_leap = ts.tv_sec;
		next_leap += 86400 - (next_leap % 86400);

		if (settime) {
			struct timeval tv;

			tv.tv_sec = next_leap - 10;
			tv.tv_usec = 0;
			settimeofday(&tv, NULL);
			printf("Setting time to %s", ctime(&tv.tv_sec));
		}

		/* Reset NTP time state */
		clear_time_state();

		/* Set the leap second insert flag */
		tx.modes = ADJ_STATUS;
		if (insert)
			tx.status = STA_INS;
		else
			tx.status = STA_DEL;
		ret = adjtimex(&tx);
		if (ret < 0) {
			printf("Error: Problem setting STA_INS/STA_DEL!: %s\n",
							time_state_str(ret));
			return ksft_exit_fail();
		}

		/* Validate STA_INS was set */
		tx.modes = 0;
		ret = adjtimex(&tx);
		if (tx.status != STA_INS && tx.status != STA_DEL) {
			printf("Error: STA_INS/STA_DEL not set!: %s\n",
							time_state_str(ret));
			return ksft_exit_fail();
		}

		if (tai_time) {
			printf("Using TAI time,"
				" no inconsistencies should be seen!\n");
		}

		printf("Scheduling leap second for %s", ctime(&next_leap));

		/* Set up timer */
		printf("Setting timer for %ld -  %s", next_leap, ctime(&next_leap));
		memset(&se, 0, sizeof(se));
		se.sigev_notify = SIGEV_SIGNAL;
		se.sigev_signo = signum;
		se.sigev_value.sival_int = 0;
		if (timer_create(CLOCK_REALTIME, &se, &tm1) == -1) {
			printf("Error: timer_create failed\n");
			return ksft_exit_fail();
		}
		its1.it_value.tv_sec = next_leap;
		its1.it_value.tv_nsec = 0;
		its1.it_interval.tv_sec = 0;
		its1.it_interval.tv_nsec = 0;
		timer_settime(tm1, TIMER_ABSTIME, &its1, NULL);

		/* Wake up 3 seconds before leap */
		ts.tv_sec = next_leap - 3;
		ts.tv_nsec = 0;


		while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL))
			printf("Something woke us up, returning to sleep\n");

		/* Validate STA_INS is still set */
		tx.modes = 0;
		ret = adjtimex(&tx);
		if (tx.status != STA_INS && tx.status != STA_DEL) {
			printf("Something cleared STA_INS/STA_DEL, setting it again.\n");
			tx.modes = ADJ_STATUS;
			if (insert)
				tx.status = STA_INS;
			else
				tx.status = STA_DEL;
			ret = adjtimex(&tx);
		}

		/* Check adjtimex output every half second */
		now = tx.time.tv_sec;
		while (now < next_leap + 2) {
			char buf[26];
			struct timespec tai;
			int ret;

			tx.modes = 0;
			ret = adjtimex(&tx);

			if (tai_time) {
				clock_gettime(CLOCK_TAI, &tai);
				printf("%ld sec, %9ld ns\t%s\n",
						tai.tv_sec,
						tai.tv_nsec,
						time_state_str(ret));
			} else {
				ctime_r(&tx.time.tv_sec, buf);
				buf[strlen(buf)-1] = 0; /*remove trailing\n */

				printf("%s + %6ld us (%i)\t%s\n",
						buf,
						tx.time.tv_usec,
						tx.tai,
						time_state_str(ret));
			}
			now = tx.time.tv_sec;
			/* Sleep for another half second */
			ts.tv_sec = 0;
			ts.tv_nsec = NSEC_PER_SEC / 2;
			clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
		}
		/* Switch to using other mode */
		insert = !insert;

		/* Note if kernel has known hrtimer failure */
		test_hrtimer_failure();

		printf("Leap complete\n");
		if (error_found) {
			printf("Errors observed\n");
			clear_time_state();
			return ksft_exit_fail();
		}
		printf("\n");
		if ((iterations != -1) && !(--iterations))
			break;
	}

	clear_time_state();
	return ksft_exit_pass();
}