Skip to content
Snippets Groups Projects
wr_date.c 6.87 KiB
Newer Older
/*
 * Trivial tool to set WR date in the switch from local or NTP time
 *
 * Alessandro Rubini, 2011, for CERN, 2013. GPL2 or later
 */
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "../../kernel/wbgen-regs/ppsg-regs.h"

#define WRDATE_CFG_FILE "/wr/etc/wrdate.conf"

/* Address for hardware, from nic-hardware.h */
#define FPGA_BASE_PPSG  0x10010500

void help(char *prgname)
{
	fprintf(stderr, "%s: Use: \"%s [<options>] <cmd> [<args>]\n",
		prgname, prgname);
	fprintf(stderr,
		"  The program usss %s as default config file name\n"
		"  -f       force: run even if not on a WR switch\n"
		"  -c <cfg> configfile to use in place of the default\n"
		"  -v       verbose: report what the program does\n"
		"  -n       do not act in practice\n"
		"    get             print WR time to stdout\n"
		"    set <value>     set WR time to scalar seconds\n"
		"    set host        set from current host time\n"
		"    set ntp         set from ntp (sets host time too)\n"
		"    set ntp:<ip>    set from specified ntp server\n",
		WRDATE_CFG_FILE);
	exit(1);
}

int opt_verbose, opt_force, opt_not;
char *opt_cfgfile = WRDATE_CFG_FILE;
char *prgname;

/* Check that we actualy are on the wr switch, exit if not */
int wrdate_check_host(int fdmem)
{
	int ret;

	/*
	 * Check the specific CPU: a different switch will require a
	 * different memory address so this must be match.
	 * system(3) is bad, but it's fast to code
	 */
	ret = system("grep -q ARM926EJ-S /proc/cpuinfo");
	if (opt_force && ret) {
		fprintf(stderr, "%s: not running on the WR switch\n", prgname);
		if (!opt_not)
			exit(1);
	}
	return ret ? -1 : 0;
}

int wrdate_cfgfile(char *fname)
{
	/* FIXME: parse config file */
	return 0;
}

/* create a map, that is never goind to be released */
void *create_map(int fdmem, unsigned long address, unsigned long size)
{
	unsigned long ps = getpagesize();
	unsigned long offset, fragment, len;
	void *mapaddr;

	offset = address & ~(ps -1);
	fragment = address & (ps -1);
	len = address + size - offset;

	mapaddr = mmap(0, len, PROT_READ | PROT_WRITE,
		       MAP_SHARED, fdmem, offset);
	if (mapaddr == MAP_FAILED) {
		fprintf(stderr, "%s: mmap: %s\n", prgname, strerror(errno));
		exit(1);
	}
	return mapaddr + fragment;
}

int wrdate_get(struct PPSG_WB *pps)
{
	unsigned long utch, utcl, nsec, tmp1, tmp2;
	uint64_t utc;
	time_t t;
	struct timeval tv;
	struct tm tm;
	char s[64];

	if (opt_not) {
		gettimeofday(&tv, NULL);
		utch = 0;
		utcl = tv.tv_sec;
		nsec = tv.tv_usec * 1000;
	} else {
		utch = pps->CNTR_UTCHI;

		do {
			utcl = pps->CNTR_UTCLO;
			nsec = pps->CNTR_NSEC * 16; /* we count a 16.5MHz */
			tmp1 = pps->CNTR_UTCHI;
			tmp2 = pps->CNTR_UTCLO;
		} while((tmp1 != utch) || (tmp2 != utcl));
	}

	utc = (uint64_t)(utch) << 32 | utcl;
	t = utc;
	localtime_r(&t, &tm);
	strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &tm);
	printf("%lli.%09li\n%s.%09li\n", utc, nsec, s, nsec);
	return 0;
}

/* This returns wr time, used for syncing to a second transition */
void gettimeof_wr(struct timeval *tv, struct PPSG_WB *pps)
{
	unsigned long utcl, nsec, tmp2;

	/* FIXME: not 2038-clean */
	do {
		utcl = pps->CNTR_UTCLO;
		nsec = pps->CNTR_NSEC * 16; /* we count a 16.5MHz */
		tmp2 = pps->CNTR_UTCLO;
	} while(tmp2 != utcl);
	tv->tv_sec = utcl;
	tv->tv_usec = nsec / 1000;
}

/* This sets WR time from host time */
int wrdate_internal_set(struct PPSG_WB *pps)
{
	struct timeval tvh, tvr; /* host, rabbit */
	signed long diff;
	unsigned long prev;

	gettimeofday(&tvh, NULL);
	gettimeof_wr(&tvr, pps);

	/* Warn if more than 200ms away */
	diff = tvh.tv_usec - tvr.tv_usec;
	if (diff > 500 * 1000)
		diff -= 1000 * 1000;
	if (diff < -500 * 1000)
		diff += 1000 * 1000;

	if (diff > 200 * 1000 || diff < -200 * 1000)
		fprintf(stderr, "%s: Warning: fractional second differs by"
			"more than 0.2 (%li ms)\n", prgname, diff / 1000);

	if (opt_verbose) {
		printf("Host time: %9li.%06li\n", (long)(tvh.tv_sec),
		       (long)(tvh.tv_usec));
		printf("WR   time: %9li.%06li\n", (long)(tvr.tv_sec),
		       (long)(tvr.tv_usec));
		printf("Fractional difference: %li usec\n", diff);
	}

	if (0) { /* Older implementation, wrong and needless */

		/* Wait for the wr time to pass the second. */
		prev = tvr.tv_sec;
		do
			gettimeof_wr(&tvr, pps);
		while (tvr.tv_sec == prev);

		/* If we are near a second change in the host, wait for it */
		gettimeofday(&tvh, NULL);
		if (tvh.tv_usec > 500 * 1000) {
			prev = tvh.tv_sec;
			do
				gettimeofday(&tvh, NULL);
			while (tvh.tv_sec == prev);
		}

		/* Finally, write the host time to White Rabbit */

		/* FIXME: not 2038-clean */
		pps->CNTR_UTCLO = tvh.tv_sec; // no effect: procedure is wrong

	} else { /* good implementation */

		signed long long diff64;

		/* diff is the expected step to be added, so host - WR */
		diff = tvh.tv_usec - tvr.tv_usec;
		diff64 = tvh.tv_sec - tvr.tv_sec;
		if (diff > 500 * 1000)
			diff64++;
		if (diff < -500 * 1000)
			diff64--;
		if (opt_verbose)
			printf("adjusting by %lli seconds\n", diff64);
		/* We must write a signed "adjustment" value to registers */
		pps->ADJ_UTCLO = diff64 & 0xffffffff;
		pps->ADJ_UTCHI = (diff64 >> 32) & 0xff;
		pps->ADJ_NSEC = 0;
		asm("" : : : "memory"); /* barrier... */
		pps->CR = pps->CR | PPSG_CR_CNT_ADJ;
	}

	return 0;
}

/* Frontend to the set mechanism: parse the argument */
int wrdate_set(struct PPSG_WB *pps, char *arg)
{
	char *s;
	unsigned long t; /* WARNING: 64 bit */
	struct timeval tv;


	if (!strcmp(arg, "host"))
		return wrdate_internal_set(pps);

	s = strdup(arg);
	if (sscanf(arg, "%li%s", &t, s) == 1) {
		tv.tv_sec = t;
		tv.tv_usec = 0;
		if (settimeofday(&tv, NULL) < 0) {
			fprintf(stderr, "%s: settimeofday(%s): %s\n",
				prgname, arg, strerror(errno));
			exit(1);
		}
		return wrdate_internal_set(pps);
	}

	/* FIXME: other time formats */
	printf(" FIXME\n");
	return 0;
}

int main(int argc, char **argv)
{
	int c, fd;
	char *cmd;
	struct PPSG_WB *pps;

	prgname = argv[0];

	while ( (c = getopt(argc, argv, "fc:vn")) != -1) {
		switch(c) {
		case 'f':
			opt_force = 1;
			break;
		case 'c':
			opt_cfgfile = optarg;
			break;
		case 'v':
			opt_verbose = 1;
			break;
		case 'n':
			opt_not = 1;
			break;
		default:
			help(argv[0]);
		}
	}
	if (optind > argc - 1)
		help(argv[0]);

	cmd = argv[optind++];


	fd = open("/dev/mem", O_RDWR | O_SYNC);
	if (fd < 0) {
		fprintf(stderr, "%s: /dev/mem: %s\n", argv[0], strerror(errno));
		exit(1);
	}
	wrdate_check_host(fd);
	pps = create_map(fd, FPGA_BASE_PPSG, sizeof(*pps));
	close(fd);

	wrdate_cfgfile(opt_cfgfile);

	if (!strcmp(cmd, "get")) {
		if (optind < argc)
			help(argv[0]);
		return wrdate_get(pps);
	}

	/* only other command is "set", with one argument */
	if (strcmp(cmd, "set") || optind != argc - 1)
		help(argv[0]);

	return wrdate_set(pps, argv[optind]);
}