timepps-Solaris.h   [plain text]


/***********************************************************************
 *								       *
 * Copyright (c) David L. Mills 1999-2009			       *
 *								       *
 * Permission to use, copy, modify, and distribute this software and   *
 * its documentation for any purpose and without fee is hereby	       *
 * granted, provided that the above copyright notice appears in all    *
 * copies and that both the copyright notice and this permission       *
 * notice appear in supporting documentation, and that the name        *
 * University of Delaware not be used in advertising or publicity      *
 * pertaining to distribution of the software without specific,        *
 * written prior permission. The University of Delaware makes no       *
 * representations about the suitability this software for any	       *
 * purpose. It is provided "as is" without express or implied          *
 * warranty.							       *
 *								       *
 ***********************************************************************
 *								       *
 * This header file complies with "Pulse-Per-Second API for UNIX-like  *
 * Operating Systems, Version 1.0", rfc2783. Credit is due Jeff Mogul  *
 * and Marc Brett, from whom much of this code was shamelessly stolen. *
 *								       *
 * this modified timepps.h can be used to provide a PPSAPI interface   *
 * to a machine running Solaris (2.6 and above).		       *
 *								       *
 ***********************************************************************
 *								       *
 * A full PPSAPI interface to the Solaris kernel would be better, but  *
 * this at least removes the necessity for special coding from the NTP *
 * NTP drivers. 						       *
 *								       *
 ***********************************************************************
 *								       *
 * Some of this include file					       *
 * Copyright (c) 1999 by Ulrich Windl,				       *
 *	based on code by Reg Clemens <reg@dwf.com>		       *
 *		based on code by Poul-Henning Kamp <phk@FreeBSD.org>   *
 *								       *
 ***********************************************************************
 *								       *
 * "THE BEER-WARE LICENSE" (Revision 42):                              *
 * <phk@FreeBSD.org> wrote this file.  As long as you retain this      *
 * notice you can do whatever you want with this stuff. If we meet some*
 * day, and you think this stuff is worth it, you can buy me a beer    *
 * in return.	Poul-Henning Kamp				       *
 *								       *
 **********************************************************************/

/* Solaris version, TIOCGPPSEV and TIOCSPPS assumed to exist. */

#ifndef _SYS_TIMEPPS_H_
#define _SYS_TIMEPPS_H_

#include <termios.h>	/* to get TOCGPPSEV and TIOCSPPS */

/* Implementation note: the logical states ``assert'' and ``clear''
 * are implemented in terms of the UART register, i.e. ``assert''
 * means the bit is set.
 */

/*
 * The following definitions are architecture independent
 */

#define PPS_API_VERS_1	1		/* API version number */
#define PPS_JAN_1970	2208988800UL	/* 1970 - 1900 in seconds */
#define PPS_NANOSECOND	1000000000L	/* one nanosecond in decimal */
#define PPS_FRAC	4294967296.	/* 2^32 as a double */

#define PPS_NORMALIZE(x)	/* normalize timespec */ \
	do { \
		if ((x).tv_nsec >= PPS_NANOSECOND) { \
			(x).tv_nsec -= PPS_NANOSECOND; \
			(x).tv_sec++; \
		} else if ((x).tv_nsec < 0) { \
			(x).tv_nsec += PPS_NANOSECOND; \
			(x).tv_sec--; \
		} \
	} while (0)

#define PPS_TSPECTONTP(x)	/* convert timespec to l_fp */ \
	do { \
		double d_temp; \
	\
		(x).integral += (unsigned int)PPS_JAN_1970; \
		d_temp = (x).fractional * PPS_FRAC / PPS_NANOSECOND; \
		if (d_temp >= PPS_FRAC) \
			(x).integral++; \
		(x).fractional = (unsigned int)d_temp; \
	} while (0)

/*
 * Device/implementation parameters (mode)
 */

#define PPS_CAPTUREASSERT	0x01	/* capture assert events */
#define PPS_CAPTURECLEAR	0x02	/* capture clear events */
#define PPS_CAPTUREBOTH 	0x03	/* capture assert and clear events */

#define PPS_OFFSETASSERT	0x10	/* apply compensation for assert ev. */
#define PPS_OFFSETCLEAR 	0x20	/* apply compensation for clear ev. */
#define PPS_OFFSETBOTH		0x30	/* apply compensation for both */

#define PPS_CANWAIT		0x100	/* Can we wait for an event? */
#define PPS_CANPOLL		0x200	/* "This bit is reserved for */

/*
 * Kernel actions (mode)
 */

#define PPS_ECHOASSERT		0x40	/* feed back assert event to output */
#define PPS_ECHOCLEAR		0x80	/* feed back clear event to output */

/*
 * Timestamp formats (tsformat)
 */

#define PPS_TSFMT_TSPEC 	0x1000	/* select timespec format */
#define PPS_TSFMT_NTPFP 	0x2000	/* select NTP format */

/*
 * Kernel discipline actions (not used in Solaris)
 */

#define PPS_KC_HARDPPS		0	/* enable kernel consumer */
#define PPS_KC_HARDPPS_PLL	1	/* phase-lock mode */
#define PPS_KC_HARDPPS_FLL	2	/* frequency-lock mode */

/*
 * Type definitions
 */

typedef unsigned long pps_seq_t;	/* sequence number */

typedef struct ntp_fp {
	unsigned int	integral;
	unsigned int	fractional;
} ntp_fp_t;				/* NTP-compatible time stamp */

typedef union pps_timeu {		/* timestamp format */
	struct timespec tspec;
	ntp_fp_t	ntpfp;
	unsigned long	longpad[3];
} pps_timeu_t;				/* generic data type to represent time stamps */

/*
 * Timestamp information structure
 */

typedef struct pps_info {
	pps_seq_t	assert_sequence;	/* seq. num. of assert event */
	pps_seq_t	clear_sequence; 	/* seq. num. of clear event */
	pps_timeu_t	assert_tu;		/* time of assert event */
	pps_timeu_t	clear_tu;		/* time of clear event */
	int		current_mode;		/* current mode bits */
} pps_info_t;

#define assert_timestamp	assert_tu.tspec
#define clear_timestamp 	clear_tu.tspec

#define assert_timestamp_ntpfp	assert_tu.ntpfp
#define clear_timestamp_ntpfp	clear_tu.ntpfp

/*
 * Parameter structure
 */

typedef struct pps_params {
	int		api_version;	/* API version # */
	int		mode;		/* mode bits */
	pps_timeu_t assert_off_tu;	/* offset compensation for assert */
	pps_timeu_t clear_off_tu;	/* offset compensation for clear */
} pps_params_t;

#define assert_offset		assert_off_tu.tspec
#define clear_offset		clear_off_tu.tspec

#define assert_offset_ntpfp	assert_off_tu.ntpfp
#define clear_offset_ntpfp	clear_off_tu.ntpfp

/* addition of NTP fixed-point format */

#define NTPFP_M_ADD(r_i, r_f, a_i, a_f) 	/* r += a */ \
	do { \
		register u_int32 lo_tmp; \
		register u_int32 hi_tmp; \
		\
		lo_tmp = ((r_f) & 0xffff) + ((a_f) & 0xffff); \
		hi_tmp = (((r_f) >> 16) & 0xffff) + (((a_f) >> 16) & 0xffff); \
		if (lo_tmp & 0x10000) \
			hi_tmp++; \
		(r_f) = ((hi_tmp & 0xffff) << 16) | (lo_tmp & 0xffff); \
		\
		(r_i) += (a_i); \
		if (hi_tmp & 0x10000) \
			(r_i)++; \
	} while (0)

#define	NTPFP_L_ADDS(r, a)	NTPFP_M_ADD((r)->integral, (r)->fractional, \
					    (int)(a)->integral, (a)->fractional)

/*
 * The following definitions are architecture-dependent
 */

#define PPS_CAP (PPS_CAPTUREASSERT | PPS_OFFSETASSERT | PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)
#define PPS_RO	(PPS_CANWAIT | PPS_CANPOLL)

typedef struct {
	int filedes;		/* file descriptor */
	pps_params_t params;	/* PPS parameters set by user */
} pps_unit_t;

/*
 *------ Here begins the implementation-specific part! ------
 */

#include <errno.h>

/*
 * pps handlebars, which are required to be an opaque scalar.  This
 * implementation uses the handle as a pointer so it must be large
 * enough.  uintptr_t is as large as a pointer.
 */
typedef uintptr_t pps_handle_t; 

/*
 * create PPS handle from file descriptor
 */

static inline int
time_pps_create(
	int filedes,		/* file descriptor */
	pps_handle_t *handle	/* returned handle */
	)
{
	pps_unit_t *punit;
	int one = 1;

	/*
	 * Check for valid arguments and attach PPS signal.
	 */

	if (!handle) {
		errno = EFAULT;
		return (-1);	/* null pointer */
	}

	if (ioctl(filedes, TIOCSPPS, &one) < 0) {
		perror("refclock_ioctl: TIOCSPPS failed:");
		return (-1);
	}

	/*
	 * Allocate and initialize default unit structure.
	 */

	punit = malloc(sizeof(*punit));
	if (NULL == punit) {
		errno = ENOMEM;
		return (-1);	/* what, no memory? */
	}

	memset(punit, 0, sizeof(*punit));
	punit->filedes = filedes;
	punit->params.api_version = PPS_API_VERS_1;
	punit->params.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC;

	*handle = (pps_handle_t)punit;
	return (0);
}

/*
 * release PPS handle
 */

static inline int
time_pps_destroy(
	pps_handle_t handle
	)
{
	pps_unit_t *punit;

	/*
	 * Check for valid arguments and detach PPS signal.
	 */

	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}
	punit = (pps_unit_t *)handle;
	free(punit);
	return (0);
}

/*
 * set parameters for handle
 */

static inline int
time_pps_setparams(
	pps_handle_t handle,
	const pps_params_t *params
	)
{
	pps_unit_t *	punit;
	int		mode, mode_in;
	/*
	 * Check for valid arguments and set parameters.
	 */

	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}

	if (!params) {
		errno = EFAULT;
		return (-1);	/* bad argument */
	}

	/*
	 * There was no reasonable consensu in the API working group.
	 * I require `api_version' to be set!
	 */

	if (params->api_version != PPS_API_VERS_1) {
		errno = EINVAL;
		return(-1);
	}

	/*
	 * only settable modes are PPS_CAPTUREASSERT and PPS_OFFSETASSERT
	 */

	mode_in = params->mode;
	punit = (pps_unit_t *)handle;

	/*
	 * Only one of the time formats may be selected
	 * if a nonzero assert offset is supplied.
	 */
	if ((mode_in & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) ==
	    (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) {

		if (punit->params.assert_offset.tv_sec ||
			punit->params.assert_offset.tv_nsec) {

			errno = EINVAL;
			return(-1);
		}

		/*
		 * If no offset was specified but both time
		 * format flags are used consider it harmless
		 * but turn off PPS_TSFMT_NTPFP so getparams
		 * will not show both formats lit.
		 */
		mode_in &= ~PPS_TSFMT_NTPFP;
	}

	/* turn off read-only bits */

	mode_in &= ~PPS_RO;

	/*
	 * test remaining bits, should only have captureassert, 
	 * offsetassert, and/or timestamp format bits.
	 */

	if (mode_in & ~(PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
			PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) {
		errno = EOPNOTSUPP;
		return(-1);
	}

	/*
	 * ok, ready to go.
	 */

	mode = punit->params.mode;
	memcpy(&punit->params, params, sizeof(punit->params));
	punit->params.api_version = PPS_API_VERS_1;
	punit->params.mode = mode | mode_in;
	return (0);
}

/*
 * get parameters for handle
 */

static inline int
time_pps_getparams(
	pps_handle_t handle,
	pps_params_t *params
	)
{
	pps_unit_t *	punit;

	/*
	 * Check for valid arguments and get parameters.
	 */

	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}

	if (!params) {
		errno = EFAULT;
		return (-1);	/* bad argument */
	}

	punit = (pps_unit_t *)handle;
	memcpy(params, &punit->params, sizeof(params));
	return (0);
}

/*
 * get capabilities for handle
 */

static inline int
time_pps_getcap(
	pps_handle_t handle,
	int *mode
	)
{
	/*
	 * Check for valid arguments and get capabilities.
	 */

	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}

	if (!mode) {
		errno = EFAULT;
		return (-1);	/* bad argument */
	}
	*mode = PPS_CAP;
	return (0);
}

/*
 * Fetch timestamps
 */

static inline int
time_pps_fetch(
	pps_handle_t handle,
	const int tsformat,
	pps_info_t *ppsinfo,
	const struct timespec *timeout
	)
{
	struct ppsclockev {
		struct timeval tv;
		u_int serial;
	} ev;

	pps_info_t	infobuf;
	pps_unit_t *	punit;

	/*
	 * Check for valid arguments and fetch timestamps
	 */

	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}

	if (!ppsinfo) {
		errno = EFAULT;
		return (-1);	/* bad argument */
	}

	/*
	 * nb. PPS_CANWAIT is NOT set by the implementation, we can totally
	 * ignore the timeout variable.
	 */

	memset(&infobuf, 0, sizeof(infobuf));
	punit = (pps_unit_t *)handle;

	/*
	 * if not captureassert, nothing to return.
	 */

	if (!punit->params.mode & PPS_CAPTUREASSERT) {
		memcpy(ppsinfo, &infobuf, sizeof(*ppsinfo));
		return (0);
	}

	if (ioctl(punit->filedes, TIOCGPPSEV, (caddr_t) &ev) < 0) {
		perror("time_pps_fetch:");
		errno = EOPNOTSUPP;
		return(-1);
	}

	infobuf.assert_sequence = ev.serial;
	infobuf.assert_timestamp.tv_sec = ev.tv.tv_sec;
	infobuf.assert_timestamp.tv_nsec = ev.tv.tv_usec * 1000;

	/*
	 * Translate to specified format then apply offset
	 */

	switch (tsformat) {
	case PPS_TSFMT_TSPEC:
		/* timespec format requires no conversion */
		if (punit->params.mode & PPS_OFFSETASSERT) {
			infobuf.assert_timestamp.tv_sec  += 
				punit->params.assert_offset.tv_sec;
			infobuf.assert_timestamp.tv_nsec += 
				punit->params.assert_offset.tv_nsec;
			PPS_NORMALIZE(infobuf.assert_timestamp);
		}
		break;

	case PPS_TSFMT_NTPFP:
		/* NTP format requires conversion to fraction form */
		PPS_TSPECTONTP(infobuf.assert_timestamp_ntpfp);
		if (punit->params.mode & PPS_OFFSETASSERT)
			NTPFP_L_ADDS(&infobuf.assert_timestamp_ntpfp, 
				     &punit->params.assert_offset_ntpfp);
		break;		

	default:
		errno = EINVAL;
		return (-1);
	}

	infobuf.current_mode = punit->params.mode;
	memcpy(ppsinfo, &infobuf, sizeof(*ppsinfo));
	return (0);
}

/*
 * specify kernel consumer
 */

static inline int
time_pps_kcbind(
	pps_handle_t handle,
	const int kernel_consumer,
	const int edge,
	const int tsformat
	)
{
	/*
	 * Check for valid arguments and bind kernel consumer
	 */
	if (!handle) {
		errno = EBADF;
		return (-1);	/* bad handle */
	}
	if (geteuid() != 0) {
		errno = EPERM;
		return (-1);	/* must be superuser */
	}
	errno = EOPNOTSUPP;
	return(-1);
}

#endif /* _SYS_TIMEPPS_H_ */