unix-time.c 6.49 KB
Newer Older
1
/*
2 3 4 5
 * Copyright (C) 2011 CERN (www.cern.ch)
 * Author: Alessandro Rubini
 *
 * Released to the public domain
6 7 8 9 10 11 12 13 14
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/timex.h>
#include <ppsi/ppsi.h>

15 16 17 18
#ifndef MOD_TAI
#define MOD_TAI 0x80
#endif

19 20
static void clock_fatal_error(char *context)
{
21
	pp_error("failure in \"%s\": %s\n.Exiting.\n", context,
22 23 24 25
		  strerror(errno));
	exit(1);
}

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
static void unix_time_clear_utc_flags(void)
{
	struct timex t;
	
	/*
	 * 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.
	 */
	t.modes = ADJ_STATUS;
	t.status = STA_PLL;
	adjtimex(&t);

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

	/* Clear the status */
	t.modes = ADJ_STATUS;
	t.status = 0;
	adjtimex(&t);	
		
}

52 53 54 55 56 57 58 59 60 61 62 63
static int unix_time_get_utc_time(struct pp_instance *ppi, int *hours, int *minutes, int *seconds)
{
	int ret;
	struct timex t;
	time_t now;
	struct tm *date;
	
	/* Get the UTC time */
	memset(&t, 0, sizeof(t));
	ret = adjtimex(&t);
	if (ret >= 0) {
		now = t.time.tv_sec;
64 65
		/* use gmtime for correct leap handling */
		date = gmtime(&now);
66 67 68 69 70 71 72 73 74 75 76 77 78 79
		*hours = date->tm_hour;
		*minutes = date->tm_min;
		*seconds = date->tm_sec;
		return 0;
	} else {
		*hours = 0;
		*minutes = 0;
		*seconds = 0;
		return -1;
	}
	
	return -1;
}

80
static int unix_time_get_utc_offset(struct pp_instance *ppi, int *offset, int *leap59, int *leap61)
81
{
82
	int ret;
83
	struct timex t;
84 85 86 87
	int hours, minutes, seconds;
	
	unix_time_get_utc_time(ppi, &hours, &minutes, &seconds);

88 89 90 91
	/*
	 * Get the UTC/TAI difference
	 */
	memset(&t, 0, sizeof(t));
92 93
	ret = adjtimex(&t);
	if (ret >= 0) {
94 95 96 97 98 99 100 101 102 103 104
		if (hours >= 12) {
			if ((t.status & STA_INS) == STA_INS) {
				*leap59 = 0;
				*leap61 = 1;
			} else if ((t.status & STA_DEL) == STA_DEL) {
				*leap59 = 1;
				*leap61 = 0;
			} else {
				*leap59 = 0;
				*leap61 = 0;
			}	
105
		} else {
106
			unix_time_clear_utc_flags();			
107 108
			*leap59 = 0;
			*leap61 = 0;
109
		}
110 111 112 113 114 115 116 117
		/*
		 * Our WRS kernel has tai support, but our compiler does not.
		 * We are 32-bit only, and we know for sure that tai is
		 * exactly after stbcnt. It's a bad hack, but it works
		 */
		*offset = *((int *)(&t.stbcnt) + 1);
		return 0;
	} else {
118 119
		*leap59 = 0;
		*leap61 = 0;
120 121 122 123 124
		*offset = 0;
		return -1;
	}		
}

125 126 127
static int unix_time_set_utc_offset(struct pp_instance *ppi, int offset, int leap59, int leap61) 
{
	struct timex t;
128
	int ret;
129
	
130 131
	unix_time_clear_utc_flags();

132 133 134 135 136 137 138 139 140 141 142 143 144 145
	/* get the current flags first */
	memset(&t, 0, sizeof(t));
	ret = adjtimex(&t);
	
	if (ret >= 0) {
		if (leap59) {
			t.modes = MOD_STATUS;
			t.status |= STA_DEL;
			t.status &= ~STA_INS;
		} else if (leap61) {
			t.modes = MOD_STATUS;
			t.status |= STA_INS;
			t.status &= ~STA_DEL;
		} else {
146
			t.modes = MOD_STATUS;
147 148
			t.status &= ~STA_INS;
			t.status &= ~STA_DEL;
149
		}
150 151 152 153 154 155

	    if (adjtimex(&t) < 0) {
		    pp_diag(ppi, time, 1, "set UTC flags failed\n");
		    return -1;
	    }

156
    } else
157
		pp_diag(ppi, time, 1, "get UTC flags failed\n");
158
	
159
	t.modes = MOD_TAI;
160 161
	t.constant = offset;
	if (adjtimex(&t) < 0) {
162
		pp_diag(ppi, time, 1, "set UTC offset failed\n");
163
		return -1;
164 165 166
	} else
		pp_diag(ppi, time, 1, "set UTC offset to: %i\n", offset);
    
167 168 169 170
	
	return 0;
}

171
static int unix_time_get(struct pp_instance *ppi, struct pp_time *t)
172 173 174 175
{
	struct timespec tp;
	if (clock_gettime(CLOCK_REALTIME, &tp) < 0)
		clock_fatal_error("clock_gettime");
176
	/* TAI = UTC + 35 */
177 178
	t->secs = tp.tv_sec + DSPRO(ppi)->currentUtcOffset;
	t->scaled_nsecs = ((int64_t)tp.tv_nsec) << 16;
179
	if (!(pp_global_d_flags & PP_FLAG_NOTIMELOG))
180 181
		pp_diag(ppi, time, 2, "%s: %9li.%09li\n", __func__,
			tp.tv_sec, tp.tv_nsec);
182 183 184
	return 0;
}

185
static int unix_time_set(struct pp_instance *ppi, const struct pp_time *t)
186 187 188
{
	struct timespec tp;

189
	if (!t) { /* Change the network notion of the utc/tai offset */
baujc's avatar
baujc committed
190
		struct timex tmx;
191

baujc's avatar
baujc committed
192 193 194
		tmx.modes = MOD_TAI;
		tmx.constant = DSPRO(ppi)->currentUtcOffset;
		if (adjtimex(&tmx) < 0)
195 196 197
			clock_fatal_error("change TAI offset");
		pp_diag(ppi, time, 1, "New TAI offset: %i\n",
			DSPRO(ppi)->currentUtcOffset);
198
	} else {
baujc's avatar
baujc committed
199 200 201 202 203 204 205 206 207 208 209 210

		/* UTC = TAI - 35 */
		tp.tv_sec = t->secs - DSPRO(ppi)->currentUtcOffset;
		tp.tv_nsec = t->scaled_nsecs >> 16;
		if ( tp.tv_sec < 0  || tp.tv_nsec<0) {
			pp_error("%s: Cannot set clock time with negative values: %lisec %lins\n", __func__,(long int) tp.tv_sec,tp.tv_nsec );
		} else {
			if (clock_settime(CLOCK_REALTIME, &tp) < 0) {
				clock_fatal_error("clock_settime");
			}
			pp_diag(ppi, time, 1, "%s: %9li.%09li\n", __func__,
					tp.tv_sec, tp.tv_nsec);
211 212
		}
	}
213 214 215
	return 0;
}

216 217 218
static int unix_time_init_servo(struct pp_instance *ppi)
{
	struct timex t;
219
	int ret;
220

221 222 223
	/* get the current flags first */
	memset(&t, 0, sizeof(t));
	ret = adjtimex(&t);
224 225
	if (ret < 0)
		pp_diag(ppi, time, 1, "get current UTC offset and flags failed");
226
	
227 228
	/* We must set MOD_PLL and recover the current frequency value */
	t.modes = MOD_STATUS;
229
	t.status |= STA_PLL;
230 231 232 233 234
	if (adjtimex(&t) < 0)
		return -1;
	return (t.freq >> 16) * 1000; /* positive or negative, not -1 */
}

235
static int unix_time_adjust(struct pp_instance *ppi, long offset_ns, long freq_ppb)
236 237
{
	struct timex t;
238
	int ret;
239

240
	t.modes = 0;
241

242 243 244 245 246
	if (freq_ppb) {
		if (freq_ppb > PP_ADJ_FREQ_MAX)
			freq_ppb = PP_ADJ_FREQ_MAX;
		if (freq_ppb < -PP_ADJ_FREQ_MAX)
			freq_ppb = -PP_ADJ_FREQ_MAX;
247

248
		t.freq = freq_ppb * ((1 << 16) / 1000);
249 250
		t.modes = MOD_FREQUENCY;
	}
251 252 253 254 255

	if (offset_ns) {
		t.offset = offset_ns / 1000;
		t.modes |= MOD_OFFSET;
	}
256

257
	ret = adjtimex(&t);
258
	pp_diag(ppi, time, 1, "%s: %li %li\n", __func__, offset_ns, freq_ppb);
259
	return ret;
260 261
}

262
static int unix_time_adjust_offset(struct pp_instance *ppi, long offset_ns)
263
{
264
	return unix_time_adjust(ppi, offset_ns, 0);
265 266
}

267
static int unix_time_adjust_freq(struct pp_instance *ppi, long freq_ppb)
268
{
269
	return unix_time_adjust(ppi, 0, freq_ppb);
270 271
}

272
static unsigned long unix_calc_timeout(struct pp_instance *ppi, int millisec)
273 274 275 276 277 278 279
{
	struct timespec now;
	uint64_t now_ms;

	clock_gettime(CLOCK_MONOTONIC, &now);
	now_ms = 1000LL * now.tv_sec + now.tv_nsec / 1000 / 1000;

280
	return now_ms + millisec;
281 282
}

283
struct pp_time_operations unix_time_ops = {
284
	.get_utc_time = unix_time_get_utc_time,
285
	.get_utc_offset = unix_time_get_utc_offset,
286
	.set_utc_offset = unix_time_set_utc_offset,
287 288 289 290 291
	.get = unix_time_get,
	.set = unix_time_set,
	.adjust = unix_time_adjust,
	.adjust_offset = unix_time_adjust_offset,
	.adjust_freq = unix_time_adjust_freq,
292
	.init_servo = unix_time_init_servo,
293
	.calc_timeout = unix_calc_timeout,
294
};
295