/***********************************************************************
* *
* Copyright (c) David L. Mills 1999-2000 *
* *
* Permission to use, copy, modify, and distribute this software and *
* its documentation for any purpose and with or 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 Windows with a suitably modified *
* serialpps.sys being used in place of serial.sys. It can *
* be extended to support a modified parallel port driver once *
* available. *
* *
* This Windows version was derived by Dave Hart *
* <davehart@davehart.com> from Mills' timepps-Solaris.h *
* *
***********************************************************************
* *
* 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 *
* *
**********************************************************************/
#ifndef _SYS_TIMEPPS_H_
#define _SYS_TIMEPPS_H_
/* 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_HECTONANOSECONDS 10000000 /* 100ns units in a second */
#define PPS_FILETIME_1970 0x019db1ded53e8000 /* unix epoch to Windows */
#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 ntp_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)
#define PPS_NTPTOTSPEC(x) /* convert ntp_fp to timespec */ \
do { \
double d_temp; \
\
(x).tv_sec -= (time_t)PPS_JAN_1970; \
d_temp = (double)((x).tv_nsec); \
d_temp *= PPS_NANOSECOND; \
d_temp /= PPS_FRAC; \
(x).tv_nsec = (long)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 Windows yet)
*/
#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 */
#pragma warning(push)
#pragma warning(disable: 201) /* nonstd extension nameless union */
typedef struct ntp_fp {
union ntp_fp_sec {
unsigned int integral;
int s_integral;
};
unsigned int fractional;
} ntp_fp_t; /* NTP-compatible time stamp */
#pragma warning(pop)
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 */
/* 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, \
(a)->s_integral, (a)->fractional)
/*
* 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
/*
* 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 */
OVERLAPPED ol; /* caches ol.hEvent for DeviceIoControl */
pps_params_t params; /* PPS parameters set by user */
} pps_unit_t;
typedef pps_unit_t* pps_handle_t; /* pps handlebars */
/*
*------ Here begins the implementation-specific part! ------
*/
#include <windows.h>
#include <errno.h>
#ifndef EOPNOTSUPP
#define EOPNOTSUPP 45
#endif
typedef struct _OLD_SERIAL_PPS_STAMPS {
LARGE_INTEGER Timestamp;
LARGE_INTEGER Counterstamp;
} OLD_SERIAL_PPS_STAMPS, *POLDSERIAL_PPS_STAMPS;
typedef struct _SERIAL_PPS_STAMPS {
LARGE_INTEGER Timestamp;
LARGE_INTEGER Counterstamp;
DWORD SeqNum;
} SERIAL_PPS_STAMPS, *PSERIAL_PPS_STAMPS;
#define IOCTL_SERIAL_GET_PPS_STAMPS CTL_CODE(FILE_DEVICE_SERIAL_PORT,114,METHOD_BUFFERED,FILE_ANY_ACCESS)
/* byte offset of member m in struct typedef s */
#define PPS_OFFSETOF(m,s) (size_t)(&((s *)0)->m)
/*
* ntpd on Windows only looks to errno after finding
* GetLastError returns NO_ERROR. To accomodate its
* use of msyslog in portable code such as refclock_atom.c,
* this implementation always clears the Windows
* error code using SetLastError(NO_ERROR) when
* returning an errno. This is also a good idea
* for any non-ntpd clients as they should use only
* the errno for PPSAPI functions.
*/
#define RETURN_PPS_ERRNO(e) \
do { \
SetLastError(NO_ERROR); \
errno = (e); \
return -1; \
} while (0)
#ifdef OWN_PPS_NTP_TIMESTAMP_FROM_COUNTER
extern void pps_ntp_timestamp_from_counter(ntp_fp_t *, ULONGLONG, ULONGLONG);
#else
/*
* helper routine for serialpps.sys ioctl which returns
* performance counter "timestamp" as well as a system
* FILETIME timestamp. Converts one of the inputs to
* NTP fixed-point format.
*/
static inline void
pps_ntp_timestamp_from_counter(
ntp_fp_t *result,
ULONGLONG Timestamp,
ULONGLONG Counterstamp)
{
ULONGLONG BiasedTimestamp;
/* convert from 100ns units to NTP fixed point format */
BiasedTimestamp = Timestamp - PPS_FILETIME_1970;
result->integral = PPS_JAN_1970 +
(unsigned)(BiasedTimestamp / PPS_HECTONANOSECONDS);
result->fractional =
(unsigned) ((BiasedTimestamp % PPS_HECTONANOSECONDS) *
(PPS_FRAC / PPS_HECTONANOSECONDS));
}
#endif
/*
* create PPS handle from file descriptor
*/
static inline int
time_pps_create(
int filedes, /* file descriptor */
pps_handle_t *handle /* returned handle */
)
{
OLD_SERIAL_PPS_STAMPS old_pps_stamps;
DWORD bytes;
OVERLAPPED ol;
/*
* Check for valid arguments and attach PPS signal.
*/
if (!handle)
RETURN_PPS_ERRNO(EFAULT);
if (PPS_OFFSETOF(tspec.tv_nsec, pps_timeu_t) !=
PPS_OFFSETOF(ntpfp.fractional, pps_timeu_t)) {
fprintf(stderr,
"timepps.h needs work, union of \n"
"unsigned int ntp_fp.integral and\n"
"time_t timespec.tv_sec accessed\n"
"interchangeably.\n");
RETURN_PPS_ERRNO(EFAULT);
}
/*
* For this ioctl which will never block, we don't want to go
* through the overhead of a completion port, so we use an
* event handle in the overlapped structure with its 1 bit set.
*
* From GetQueuedCompletionStatus docs:
* Even if you have passed the function a file handle associated
* with a completion port and a valid OVERLAPPED structure, an
* application can prevent completion port notification. This is
* done by specifying a valid event handle for the hEvent member
* of the OVERLAPPED structure, and setting its low-order bit. A
* valid event handle whose low-order bit is set keeps I/O
* completion from being queued to the completion port.
*/
ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ol.hEvent = (HANDLE) ((ULONG_PTR)ol.hEvent | 1);
if (FALSE == DeviceIoControl(
(HANDLE)_get_osfhandle(filedes),
IOCTL_SERIAL_GET_PPS_STAMPS,
NULL,
0,
&old_pps_stamps,
sizeof(old_pps_stamps),
&bytes,
&ol)
|| sizeof(old_pps_stamps) != bytes) {
/*
* If you want to write some dead code this could detect the
* IOCTL being pended, but the driver always has the info
* instantly, so ERROR_IO_PENDING isn't a concern.
*/
CloseHandle(ol.hEvent);
fprintf(stderr,
"time_pps_create: IOCTL_SERIAL_GET_PPS_STAMPS: %d %d\n",
bytes,
GetLastError());
RETURN_PPS_ERRNO(ENXIO);
}
/*
* Allocate and initialize default unit structure.
*/
*handle = malloc(sizeof(pps_unit_t));
if (!(*handle))
RETURN_PPS_ERRNO(ENOMEM);
memset(*handle, 0, sizeof(pps_unit_t));
(*handle)->filedes = filedes;
(*handle)->ol.hEvent = ol.hEvent;
(*handle)->params.api_version = PPS_API_VERS_1;
(*handle)->params.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC;
return (0);
}
/*
* release PPS handle
*/
static inline int
time_pps_destroy(
pps_handle_t handle
)
{
/*
* Check for valid arguments and detach PPS signal.
*/
if (!handle)
RETURN_PPS_ERRNO(EBADF);
CloseHandle(handle->ol.hEvent);
free(handle);
return (0);
}
/*
* set parameters for handle
*/
static inline int
time_pps_setparams(
pps_handle_t handle,
const pps_params_t *params
)
{
int mode, mode_in;
/*
* Check for valid arguments and set parameters.
*/
if (!handle)
RETURN_PPS_ERRNO(EBADF);
if (!params)
RETURN_PPS_ERRNO(EFAULT);
/*
* 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)
RETURN_PPS_ERRNO(EINVAL);
/*
* only settable modes are PPS_CAPTUREASSERT and PPS_OFFSETASSERT
*/
mode_in = params->mode;
/*
* 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 (handle->params.assert_offset.tv_sec ||
handle->params.assert_offset.tv_nsec)
RETURN_PPS_ERRNO(EINVAL);
/*
* 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))
RETURN_PPS_ERRNO(EOPNOTSUPP);
/*
* ok, ready to go.
*/
mode = handle->params.mode;
handle->params = *params;
handle->params.mode = mode | mode_in;
handle->params.api_version = PPS_API_VERS_1;
return (0);
}
/*
* get parameters for handle
*/
static inline int
time_pps_getparams(
pps_handle_t handle,
pps_params_t *params
)
{
/*
* Check for valid arguments and get parameters.
*/
if (!handle)
RETURN_PPS_ERRNO(EBADF);
if (!params)
RETURN_PPS_ERRNO(EFAULT);
*params = handle->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)
RETURN_PPS_ERRNO(EBADF);
if (!mode)
RETURN_PPS_ERRNO(EFAULT);
*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
)
{
SERIAL_PPS_STAMPS pps_stamps;
pps_info_t infobuf;
BOOL rc;
DWORD bytes;
DWORD lasterr;
/*
* Check for valid arguments and fetch timestamps
*/
if (!handle)
RETURN_PPS_ERRNO(EBADF);
if (!ppsinfo)
RETURN_PPS_ERRNO(EFAULT);
/*
* nb. PPS_CANWAIT is NOT set by the implementation, we can totally
* ignore the timeout variable.
*/
memset(&infobuf, 0, sizeof(infobuf));
/*
* if not captureassert, nothing to return.
*/
if (!handle->params.mode & PPS_CAPTUREASSERT) {
*ppsinfo = infobuf;
return (0);
}
/*
* First rev of serialpps.sys didn't support the SeqNum field,
* support it by simply returning constant 0 for serial in that case.
*/
pps_stamps.SeqNum = 0;
/*
* interrogate (hopefully) serialpps.sys
* if it's the standard serial.sys or another driver,
* IOCTL_SERIAL_GET_PPS_STAMPS is most likely unknown
* and will result in ERROR_INVALID_PARAMETER.
*/
bytes = 0;
rc = DeviceIoControl(
(HANDLE)_get_osfhandle(handle->filedes),
IOCTL_SERIAL_GET_PPS_STAMPS,
NULL,
0,
&pps_stamps,
sizeof(pps_stamps),
&bytes,
&handle->ol);
if (!rc) {
lasterr = GetLastError();
if (ERROR_INVALID_PARAMETER != lasterr)
fprintf(stderr, "time_pps_fetch: ioctl err %d\n",
lasterr);
RETURN_PPS_ERRNO(EOPNOTSUPP);
} else if (bytes != sizeof(pps_stamps) &&
bytes != sizeof(OLD_SERIAL_PPS_STAMPS)) {
fprintf(stderr,
"time_pps_fetch: wanted %d or %d bytes got %d from "
"IOCTL_SERIAL_GET_PPS_STAMPS 0x%x\n" ,
sizeof(OLD_SERIAL_PPS_STAMPS),
sizeof(SERIAL_PPS_STAMPS),
bytes,
IOCTL_SERIAL_GET_PPS_STAMPS);
RETURN_PPS_ERRNO(ENXIO);
}
/*
* pps_ntp_timestamp_from_counter takes the two flavors
* of timestamp we have (counter and system time) and
* uses whichever it can to give the best NTP fixed-point
* conversion. In ntpd the Counterstamp is typically
* used. A stub implementation in this file simply
* converts from Windows Timestamp to NTP fixed-point.
*/
pps_ntp_timestamp_from_counter(
&infobuf.assert_timestamp_ntpfp,
pps_stamps.Timestamp.QuadPart,
pps_stamps.Counterstamp.QuadPart);
/*
* Note that only assert timestamps
* are captured by this interface.
*/
infobuf.assert_sequence = pps_stamps.SeqNum;
/*
* Apply offset and translate to specified format
*/
switch (tsformat) {
case PPS_TSFMT_NTPFP: /* NTP format requires no translation */
if (handle->params.mode & PPS_OFFSETASSERT) {
NTPFP_L_ADDS(&infobuf.assert_timestamp_ntpfp,
&handle->params.assert_offset_ntpfp);
}
break;
case PPS_TSFMT_TSPEC: /* timespec format requires conversion to nsecs form */
PPS_NTPTOTSPEC(infobuf.assert_timestamp);
if (handle->params.mode & PPS_OFFSETASSERT) {
infobuf.assert_timestamp.tv_sec +=
handle->params.assert_offset.tv_sec;
infobuf.assert_timestamp.tv_nsec +=
handle->params.assert_offset.tv_nsec;
PPS_NORMALIZE(infobuf.assert_timestamp);
}
break;
default:
RETURN_PPS_ERRNO(EINVAL);
}
infobuf.current_mode = handle->params.mode;
*ppsinfo = infobuf;
return (0);
}
/*
* time_pps_kcbind - specify kernel consumer
*
* Not supported so far by Windows.
*/
static inline int
time_pps_kcbind(
pps_handle_t handle,
const int kernel_consumer,
const int edge, const int tsformat
)
{
/*
* Check for valid arguments before revealing the ugly truth
*/
if (!handle)
RETURN_PPS_ERRNO(EBADF);
RETURN_PPS_ERRNO(EOPNOTSUPP);
}
#endif /* _SYS_TIMEPPS_H_ */