From: Juergen Perlinger Date: Sun, 18 Nov 2012 11:15:34 +0000 (+0100) Subject: [Bug 2306] Added user-mode/loop-back PPS API provider for Win32 X-Git-Tag: NTP_4_2_7P324~3^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=745573c5e47a06e46bf526d47374d8f4690a993c;p=thirdparty%2Fntp.git [Bug 2306] Added user-mode/loop-back PPS API provider for Win32 bk: 50a8c3566vIgGiWriGNwooWEf3lrXQ --- diff --git a/ChangeLog b/ChangeLog index afdba49dc..a2f019a4b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ +* [Bug 2306] Added user-mode/loop-back PPS API provider for Win32 * html/ updates from Dave Mills. * [Bug 1223] Allow configurable values for RLIMIT_STACK and RLIMIT_MEMLOCK. diff --git a/include/ntp_refclock.h b/include/ntp_refclock.h index b168d243c..f92d14cdb 100644 --- a/include/ntp_refclock.h +++ b/include/ntp_refclock.h @@ -103,6 +103,10 @@ struct refclockio { int fd; /* file descriptor */ u_long recvcount; /* count of receive completions */ int active; /* nonzero when in use */ + +#ifdef HAVE_IO_COMPLETION_PORT + void * device_context; /* device-related data for i/o subsystem */ +#endif }; /* diff --git a/ports/winnt/include/ntp_iocompletionport.h b/ports/winnt/include/ntp_iocompletionport.h index a32476f98..7d7ff7c4b 100644 --- a/ports/winnt/include/ntp_iocompletionport.h +++ b/ports/winnt/include/ntp_iocompletionport.h @@ -15,6 +15,7 @@ extern int io_completion_port_add_socket(SOCKET fd, struct interface *); struct refclockio; /* in ntp_refclock.h but inclusion here triggers problems */ extern int io_completion_port_add_clock_io(struct refclockio *rio); +extern void io_completion_port_remove_clock_io(struct refclockio *rio); extern int io_completion_port_sendto(int, void *, size_t, sockaddr_u *); extern int GetReceivedBuffers(void); diff --git a/ports/winnt/include/termios.h b/ports/winnt/include/termios.h index 350ee47fb..9c1cc625c 100644 --- a/ports/winnt/include/termios.h +++ b/ports/winnt/include/termios.h @@ -209,5 +209,6 @@ extern int ioctl (int, int, int *); extern int tcsetattr (int, int, const struct termios *); extern int tcgetattr (int, struct termios *); extern int tcflush (int, int); +extern int isserialhandle (HANDLE); #endif /* NTP_WIN_TERMIOS_H */ diff --git a/ports/winnt/libntp/termios.c b/ports/winnt/libntp/termios.c index db9895bed..d0e1351dc 100644 --- a/ports/winnt/libntp/termios.c +++ b/ports/winnt/libntp/termios.c @@ -180,6 +180,24 @@ closeserial(int fd) return close(fd); } +/* + * isserialhandle() -- check if a handle is a COM port handle + */ +int isserialhandle( + HANDLE h + ) +{ + size_t u; + size_t d; + + + for (u = 0; u < c_hnds; u++) + for (d = 0; d < hnds[u].opens; d++) + if (hnds[u].dupes[d] == h) + return TRUE; + return FALSE; +} + /* * tty_open - open serial port for refclock special uses diff --git a/ports/winnt/ntpd/ntp_iocompletionport.c b/ports/winnt/ntpd/ntp_iocompletionport.c index 9e3e541f3..031c34c4b 100644 --- a/ports/winnt/ntpd/ntp_iocompletionport.c +++ b/ports/winnt/ntpd/ntp_iocompletionport.c @@ -37,9 +37,12 @@ Some notes on the implementation: objects. Leak tracing becomes more interesting, though. -The current implementation is based on the work of Danny Mayer who wrote +The current implementation is based on the work of Danny Mayer who improved the original implementation and Dave Hart who improved on the serial I/O -routines. This version still provides the 'user space PPS' emulation +routines. The true roots of this file seem to be shrouded by the mist of time... + + +This version still provides the 'user space PPS' emulation feature. Juergen Perlinger (perlinger@ntp.org) Feb 2012 @@ -74,6 +77,44 @@ Juergen Perlinger (perlinger@ntp.org) Feb 2012 # pragma warning(disable: 201) /* nonstd extension nameless union */ #endif +/* + * --------------------------------------------------------------------- + * storage type for PPS data (DCD change counts & times) + * --------------------------------------------------------------------- + */ +struct PpsData { + u_long cc_assert; + u_long cc_clear; + l_fp ts_assert; + l_fp ts_clear; +}; +typedef struct PpsData PPSData_t; + +struct PpsDataEx { + u_long cov_count; + PPSData_t data; +}; +typedef volatile struct PpsDataEx PPSDataEx_t; + +/* + * --------------------------------------------------------------------- + * device context; uses reference counting to avoid nasty surprises. + * Currently this stores only the PPS time stamps, but it could be + * easily extended. + * --------------------------------------------------------------------- + */ +#define PPS_QUEUE_LEN 8u /* must be power of two! */ +#define PPS_QUEUE_MSK (PPS_QUEUE_LEN-1) /* mask for easy MOD ops */ + +struct DeviceContext { + volatile long ref_count; + volatile u_long cov_count; + PPSData_t pps_data; + PPSDataEx_t pps_buff[PPS_QUEUE_LEN]; +}; + +typedef struct DeviceContext DevCtx_t; + /* * --------------------------------------------------------------------- * I/O context structure @@ -88,8 +129,9 @@ Juergen Perlinger (perlinger@ntp.org) Feb 2012 * finally needed for the processing of the buffer. * --------------------------------------------------------------------- */ -struct IoCtx; -typedef struct IoCtx IoCtx_t; +//struct IoCtx; +typedef struct IoCtx IoCtx_t; +typedef struct refclockio RIO_t; typedef void (*IoCompleteFunc)(ULONG_PTR, IoCtx_t *); @@ -98,10 +140,13 @@ struct IoCtx { union { recvbuf_t * recv_buf; /* incoming -> buffer structure */ void * trans_buf; /* outgoing -> char array */ + PPSData_t * pps_buf; /* for reading PPS seq/stamps */ + HANDLE ppswake; /* pps wakeup for attach */ }; IoCompleteFunc onIoDone; /* HL callback to execute */ - struct refclockio * rio; /* RIO backlink (for offload) */ - l_fp DCDSTime; /* timestamp of DCD set */ + RIO_t * rio; /* RIO backlink (for offload) */ + DevCtx_t * devCtx; + l_fp DCDSTime; /* PPS-hack: time of DCD ON */ l_fp FlagTime; /* timestamp of flag/event char */ l_fp RecvTime; /* timestamp of callback */ DWORD errCode; /* error code of last I/O */ @@ -123,10 +168,10 @@ static void ntpd_addremove_semaphore(HANDLE, int); static inline void set_serial_recv_time (recvbuf_t *, IoCtx_t *); /* Initiate/Request async IO operations */ -static BOOL QueueSerialWait (struct refclockio *, recvbuf_t *, IoCtx_t *); -static BOOL QueueSerialRead (struct refclockio *, recvbuf_t *, IoCtx_t *); -static BOOL QueueRawSerialRead(struct refclockio *, recvbuf_t *, IoCtx_t *); -static BOOL QueueSocketRecv (SOCKET , recvbuf_t *, IoCtx_t *); +static BOOL QueueSerialWait (RIO_t *, recvbuf_t *, IoCtx_t *); +static BOOL QueueSerialRead (RIO_t *, recvbuf_t *, IoCtx_t *); +static BOOL QueueRawSerialRead(RIO_t *, recvbuf_t *, IoCtx_t *); +static BOOL QueueSocketRecv (SOCKET , recvbuf_t *, IoCtx_t *); /* High-level IO callback functions */ @@ -198,36 +243,143 @@ IoCtxPoolDone(void) /* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Alloc & Free + * Alloc & Free on local heap * * When the heap handle is NULL, these both will fail; Alloc with a NULL * return and Free silently. */ -static IoCtx_t * -IoCtxAlloc(void) +static void * __fastcall +LocalPoolAlloc( + size_t size, + const char * desc +) { - IoCtx_t * lpo; + void * ptr; - if (hHeapHandle) { - lpo = HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, sizeof(IoCtx_t)); - } else { - lpo = NULL; + /* Windows heaps can't grok zero byte allocation. + * We just get one byte. + */ + if (size == 0) + size = 1; + if (hHeapHandle != NULL) + ptr = HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, size); + else + ptr = NULL; + DPRINTF(3, ("Allocate '%s', heap=%p, ptr=%p\n", + desc, hHeapHandle, ptr)); + + return ptr; +} + +static void __fastcall +LocalPoolFree( + void * ptr, + const char * desc + ) +{ + DPRINTF(3, ("Free '%s', heap=%p, ptr=%p\n", + desc, hHeapHandle, ptr)); + if (ptr != NULL && hHeapHandle != NULL) + HeapFree(hHeapHandle, 0, ptr); +} + +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * Alloc & Free of Device context + * + * When the heap handle is NULL, these both will fail; Alloc with a NULL + * return and Free silently. + */ +static DevCtx_t * __fastcall +DevCtxAlloc(void) +{ + DevCtx_t * devCtx; + u_long slot; + + /* allocate struct and tag all slots as invalid */ + devCtx = (DevCtx_t *)LocalPoolAlloc(sizeof(DevCtx_t), "DEV ctx"); + if (devCtx != NULL) + { + devCtx->cov_count = ~0ul; + for (slot = 0; slot < PPS_QUEUE_LEN; slot++) + devCtx->pps_buff[slot].cov_count = ~slot; } - DPRINTF(3, ("Allocate IO ctx, heap=%p, ptr=%p\n", hHeapHandle, lpo)); + return devCtx; +} + +static void __fastcall +DevCtxFree( + DevCtx_t * devCtx + ) +{ + /* this would be the place to get rid of managed ressources. */ + LocalPoolFree(devCtx, "DEV ctx"); +} - return lpo; +static DevCtx_t * __fastcall +DevCtxAttach( + DevCtx_t * devCtx + ) +{ + if (devCtx != NULL) + InterlockedIncrement(&devCtx->ref_count); + return devCtx; } -static void +static void __fastcall +DevCtxDetach( + DevCtx_t * devCtx + ) +{ + if (devCtx && !InterlockedDecrement(&devCtx->ref_count)) + DevCtxFree(devCtx); +} + +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * Alloc & Free of I/O context + * + * When the heap handle is NULL, these both will fail; Alloc with a NULL + * return and Free silently. + */ +static IoCtx_t * __fastcall +IoCtxAlloc( + DevCtx_t * devCtx + ) +{ + IoCtx_t * ioCtx; + + ioCtx = (IoCtx_t *)LocalPoolAlloc(sizeof(IoCtx_t), "IO ctx"); + if (ioCtx != NULL) + ioCtx->devCtx = DevCtxAttach(devCtx); + return ioCtx; +} + +static void __fastcall IoCtxFree( - IoCtx_t *lpo + IoCtx_t * ctx ) { - DPRINTF(3, ("Free IO ctx, heap=%p, ptr=%p\n", hHeapHandle, lpo)); - if (lpo != NULL && hHeapHandle != NULL) - HeapFree(hHeapHandle, 0, lpo); + if (ctx) + DevCtxDetach(ctx->devCtx); + LocalPoolFree(ctx, "IO ctx"); } +static void __fastcall +IoCtxReset( + IoCtx_t * ctx + ) +{ + RIO_t * rio; + DevCtx_t * dev; + if (ctx) { + rio = ctx->rio; + dev = ctx->devCtx; + ZERO(*ctx); + ctx->rio = rio; + ctx->devCtx = dev; + } +} /* * ------------------------------------------------------------------- @@ -551,9 +703,9 @@ IoResultCheck( static BOOL QueueSerialWait( - struct refclockio * rio, - recvbuf_t * buff, - IoCtx_t * lpo + RIO_t * rio, + recvbuf_t * buff, + IoCtx_t * lpo ) { BOOL rc; @@ -580,19 +732,24 @@ OnSerialWaitComplete( IoCtx_t * lpo ) { - struct refclockio * rio; - recvbuf_t * buff; - DWORD modem_status; + RIO_t * rio; + DevCtx_t * dev; + recvbuf_t * buff; + PPSDataEx_t * ppsbuf; + DWORD modem_status; + u_long covc; /* check and bail out if operation failed */ if (!IoResultCheck(lpo->errCode, lpo, - "WaitCommEvent failed")) + "WaitCommEvent failed")) return; /* get & validate context and buffer. */ - rio = lpo->rio; + rio = (RIO_t *)key; buff = lpo->recv_buf; - NTP_INSIST((ULONG_PTR)rio == key); + dev = lpo->devCtx; + + NTP_INSIST(rio == lpo->rio); #ifdef DEBUG if (~(EV_RXFLAG | EV_RLSD | EV_RXCHAR) & lpo->com_events) { @@ -607,15 +764,46 @@ OnSerialWaitComplete( * this code that makes a lot of sense: move to a putative * dcdpps-ppsapi-provider.dll. */ - if (EV_RLSD & lpo->com_events) { + if ((EV_RLSD & lpo->com_events) && dev) { modem_status = 0; GetCommModemStatus((HANDLE)_get_osfhandle(rio->fd), &modem_status); - if (MS_RLSD_ON & modem_status) { - lpo->DCDSTime = lpo->RecvTime; - lpo->flTsDCDS = 1; - DPRINTF(2, ("fd %d DCD PPS at %s\n", rio->fd, - ulfptoa(&lpo->RecvTime, 6))); + + if (dev != NULL) { + /* PPS-context available -- use it! */ + if (MS_RLSD_ON & modem_status) { + dev->pps_data.cc_assert++; + dev->pps_data.ts_assert = lpo->RecvTime; + DPRINTF(2, ("upps-real: fd %d DCD PPS Rise at %s\n", rio->fd, + ulfptoa(&lpo->RecvTime, 6))); + } else { + dev->pps_data.cc_clear++; + dev->pps_data.ts_clear = lpo->RecvTime; + DPRINTF(2, ("upps-real: fd %d DCD PPS Fall at %s\n", rio->fd, + ulfptoa(&lpo->RecvTime, 6))); + } + /* + ** Update PPS buffer, writing from low to high, with index + ** update as last action. We need barriers to make sure that + ** the compiler does not shuffle the assignments and that the + ** data becomes visible to other threads in exactly that order. + ** The lock-free read/write queue depends on that order! + */ + covc = dev->cov_count + 1u; + ppsbuf = dev->pps_buff + (covc & PPS_QUEUE_MSK); + ppsbuf->cov_count = covc; + _WriteBarrier(); + ppsbuf->data = dev->pps_data; + InterlockedExchange((PLONG)&dev->cov_count, covc); + /* Note: InterlockedExchange() is a full barrier. */ + } else { + /* backward compat: 'usermode-pps-hack' */ + if (MS_RLSD_ON & modem_status) { + lpo->DCDSTime = lpo->RecvTime; + lpo->flTsDCDS = 1; + DPRINTF(2, ("upps-hack: fd %d DCD PPS Rise at %s\n", rio->fd, + ulfptoa(&lpo->RecvTime, 6))); + } } } @@ -633,7 +821,6 @@ OnSerialWaitComplete( } } - /* * ------------------------------------------------------------------- * Serial IO stuff @@ -651,9 +838,9 @@ OnSerialWaitComplete( */ static BOOL QueueSerialRead( - struct refclockio * rio, - recvbuf_t * buff, - IoCtx_t * lpo + RIO_t * rio, + recvbuf_t * buff, + IoCtx_t * lpo ) { BOOL rc; @@ -685,8 +872,8 @@ OnSerialReadComplete( IoCtx_t * lpo ) { - struct refclockio * rio; - recvbuf_t * buff; + RIO_t * rio; + recvbuf_t * buff; /* check and bail out if operation failed */ if (!IoResultCheck(lpo->errCode, lpo, @@ -702,7 +889,7 @@ OnSerialReadComplete( if (!QueueUserWorkItem(&OnSerialReadWorker, lpo, WT_EXECUTEDEFAULT)) { msyslog(LOG_ERR, "Can't offload to worker thread, will skip data: %m"); - ZERO(*lpo); + IoCtxReset(lpo); buff->recv_length = 0; QueueSerialWait(rio, buff, lpo); } @@ -722,12 +909,12 @@ OnSerialReadComplete( static DWORD WINAPI OnSerialReadWorker(void * ctx) { - IoCtx_t * lpo; - recvbuf_t * buff, *obuf; - struct refclockio * rio; - char *sptr, *send, *dptr; - BOOL eol; - char ch; + IoCtx_t * lpo; + recvbuf_t * buff, *obuf; + RIO_t * rio; + char *sptr, *send, *dptr; + BOOL eol; + char ch; /* Get context back */ lpo = (IoCtx_t*)ctx; @@ -812,7 +999,7 @@ OnSerialReadWorker(void * ctx) buff->recv_length = 0; } - ZERO(*lpo); + IoCtxReset(lpo); QueueSerialWait(rio, buff, lpo); return 0; } @@ -831,9 +1018,9 @@ OnSerialReadWorker(void * ctx) static BOOL QueueRawSerialRead( - struct refclockio * rio, - recvbuf_t * buff, - IoCtx_t * lpo + RIO_t * rio, + recvbuf_t * buff, + IoCtx_t * lpo ) { BOOL rc; @@ -861,8 +1048,8 @@ OnRawSerialReadComplete( IoCtx_t * lpo ) { - struct refclockio * rio; - recvbuf_t * buff; + RIO_t * rio; + recvbuf_t * buff; /* check and bail out if operation failed */ if (!IoResultCheck(lpo->errCode, lpo, @@ -938,7 +1125,7 @@ async_write( IoCtx_t * lpo; BOOL rc; - lpo = IoCtxAlloc(); + lpo = IoCtxAlloc(NULL); if (lpo == NULL) { DPRINTF(1, ("async_write: out of memory\n")); errno = ENOMEM; @@ -968,7 +1155,7 @@ OnSerialWriteComplete( ) { /* set RIO and force silent cleanup if no error */ - lpo->rio = (struct refclockio *)key; + lpo->rio = (RIO_t *)key; if (ERROR_SUCCESS == lpo->errCode) lpo->errCode = ERROR_OPERATION_ABORTED; IoResultCheck(lpo->errCode, lpo, @@ -976,7 +1163,123 @@ OnSerialWriteComplete( } +/* + * ------------------------------------------------------------------- + * Serial IO stuff + * + * Part 5 -- read PPS time stamps + * + * ------------------------------------------------------------------- + */ + +/* The dummy read procedure is used for getting the device context + * into the IO completion thread, using the IO completion queue for + * transport. There are other ways to accomplish what we need here, + * but using the IO machine is handy and avoids a lot of trouble. + */ +static void +OnPpsDummyRead( + ULONG_PTR key, + IoCtx_t * lpo + ) +{ + RIO_t * rio; + + rio = (RIO_t *)key; + lpo->devCtx = DevCtxAttach(rio->device_context); + SetEvent(lpo->ppswake); +} + +__declspec(dllexport) void* __stdcall +ntp_pps_attach_device( + HANDLE hndIo + ) +{ + IoCtx_t myIoCtx; + HANDLE myEvt; + DevCtx_t * dev; + DWORD rc; + + if (!isserialhandle(hndIo)) { + SetLastError(ERROR_INVALID_HANDLE); + return NULL; + } + + ZERO(myIoCtx); + dev = NULL; + myEvt = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NULL == myEvt) + goto done; + + myIoCtx.ppswake = myEvt; + myIoCtx.onIoDone = OnPpsDummyRead; + rc = ReadFile(hndIo, &myIoCtx.byteCount, 0, + &myIoCtx.byteCount, &myIoCtx.ol); + if (!rc && (GetLastError() != ERROR_IO_PENDING)) + goto done; + if (WaitForSingleObject(myEvt, INFINITE) == WAIT_OBJECT_0) + if (NULL == (dev = myIoCtx.devCtx)) + SetLastError(ERROR_INVALID_HANDLE); +done: + rc = GetLastError(); + CloseHandle(myEvt); + SetLastError(rc); + return dev; +} + +__declspec(dllexport) void __stdcall +ntp_pps_detach_device( + DevCtx_t * dev + ) +{ + DevCtxDetach(dev); +} +__declspec(dllexport) BOOL __stdcall +ntp_pps_read( + DevCtx_t * dev, + PPSData_t * data, + size_t dlen + ) +{ + u_long guard, covc; + int repc; + PPSDataEx_t * ppsbuf; + + + if (dev == NULL) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + if (data == NULL || dlen != sizeof(PPSData_t)) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + /* + ** Reading from shared memory in a lock-free fashion can be + ** a bit tricky, since we have to read the components in the + ** opposite direction from the write, and the compiler must + ** not reorder the read sequence. + ** We use read barriers and a volatile data source to avoid + ** reordering on compiler and CPU level. + */ + repc = 3; + do { + covc = dev->cov_count; + _ReadBarrier(); + ppsbuf = dev->pps_buff + ((covc >> 1) & PPS_QUEUE_MSK); + *data = ppsbuf->data; + _ReadBarrier(); + guard = covc ^ ppsbuf->cov_count; + _ReadBarrier(); + } while ((guard != 0u) && ((guard & PPS_QUEUE_MSK) == 0u) && --repc); + + if (guard != 0u) { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + return TRUE; +} /* * Add a reference clock data structures I/O handles to @@ -984,10 +1287,11 @@ OnSerialWriteComplete( */ int io_completion_port_add_clock_io( - struct refclockio *rio + RIO_t *rio ) { IoCtx_t * lpo; + DevCtx_t * dev; recvbuf_t * buff; HANDLE h; @@ -1001,7 +1305,13 @@ io_completion_port_add_clock_io( return 1; } - lpo = IoCtxAlloc(); + dev = DevCtxAlloc(); + if (NULL == dev) { + msyslog(LOG_ERR, "Can't allocate device context for i/o completion port: %m"); + return 1; + } + rio->device_context = DevCtxAttach(dev); + lpo = IoCtxAlloc(dev); if (NULL == lpo) { msyslog(LOG_ERR, "Can't allocate heap for completion port: %m"); return 1; @@ -1013,6 +1323,15 @@ io_completion_port_add_clock_io( return 0; } +void +io_completion_port_remove_clock_io( + RIO_t *rio + ) +{ + if (rio) + DevCtxDetach((DevCtx_t *)rio->device_context); +} + /* * Queue a receiver on a socket. Returns 0 if no buffer can be queued * @@ -1157,7 +1476,7 @@ io_completion_port_add_socket( for (n = 0; n < WINDOWS_RECVS_PER_SOCKET; n++) { buff = get_free_recv_buffer_alloc(); - lpo = IoCtxAlloc(); + lpo = IoCtxAlloc(NULL); if (lpo == NULL) { msyslog(LOG_ERR diff --git a/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.h b/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.h new file mode 100644 index 000000000..62159fc98 --- /dev/null +++ b/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.h @@ -0,0 +1,714 @@ +/*********************************************************************** + * * + * Copyright (c) David L. Mills 1999-2000 * + * * + * 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 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 * + * from Mills' timepps-Solaris.h * + * * + *********************************************************************** + * * + * Some of this include file * + * Copyright (c) 1999 by Ulrich Windl, * + * based on code by Reg Clemens * + * based on code by Poul-Henning Kamp * + * * + *********************************************************************** + * * + * "THE BEER-WARE LICENSE" (Revision 42): * + * 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 +#include + +#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_ */ diff --git a/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.txt b/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.txt new file mode 100644 index 000000000..ce7786531 --- /dev/null +++ b/ports/winnt/ppsapi/loopback/monolithic-serialpps-timepps.txt @@ -0,0 +1,52 @@ +/*********************************************************************** + * * + * Copyright (c) David L. Mills 1999-2000 * + * * + * 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 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 * + * from Mills' timepps-Solaris.h * + * * + *********************************************************************** + * * + * Some of this include file * + * Copyright (c) 1999 by Ulrich Windl, * + * based on code by Reg Clemens * + * based on code by Poul-Henning Kamp * + * * + *********************************************************************** + * * + * "THE BEER-WARE LICENSE" (Revision 42): * + * 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 * + * * + **********************************************************************/ + +timepps.h implements IETF RFC "Pulse-Per-Second API for UNIX-like +Operating Systems, Version 1.0" for Microsoft Windows. + + diff --git a/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.c b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.c new file mode 100644 index 000000000..4a708f4c0 --- /dev/null +++ b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.c @@ -0,0 +1,465 @@ +/* + * loopback-ppsapi-provider.c - derived from monolithic timepps.h + * for usermode PPS by Juergen Perlinger + */ + +/*********************************************************************** + * * + * 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 serialpps-ppsapi-provider.dll implements the PPSAPI provider * + * for serialpps.sys, which is a very lightly patched Windows * + * serial.sys with CD timestamping support. + * * + * This Windows version was derived by Dave Hart * + * from David L. Mills' timepps-Solaris.h * + * * + *********************************************************************** + * * + * Some of this include file * + * Copyright (c) 1999 by Ulrich Windl, * + * based on code by Reg Clemens * + * based on code by Poul-Henning Kamp * + * * + *********************************************************************** + * * + * "THE BEER-WARE LICENSE" (Revision 42): * + * 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 * + * * + **********************************************************************/ + +/* + * Implementation note: the logical states ``assert'' and ``clear'' + * are implemented in terms of the UART register, i.e. ``assert'' + * means the bit is set. This follows the sense of the serial driver + * of the Windows OS, and is opposite of the RS-232 spec for the + * CD/DCD logical state. + */ + + +#define PPSAPI_PROVIDER_EXPORTS +#include "loopback-ppsapi.h" + +/* +** global stuff +*/ + +pcreate_pps_handle p_create_pps_handle; + +#define SERIALPPS_CAPS (PPS_CAPTUREBOTH | PPS_OFFSETBOTH | PPS_TSFMT_BOTH) +#define SERIALPPS_RO (PPS_CANWAIT | PPS_CANPOLL) + + +/* + * The ntp_timestamp_from_counter callback into timepps.h routines in + * the host is saved in each unit separately, so that binaries that + * inline timepps.h into multiple source files (such as refclock_atom.c + * and a number of other ntpd refclocks including refclock_nmea.c) will + * get called back in the correct instance for each unit. This assumes + * that ppsapi_prov_init for subsequent instances happens only after the + * first instance has completed all time_pps_create() calls it will + * invoke, which is a safe assumption at least for ntpd. + */ +struct loopback_unit { + HANDLE hnd_dev; /* true device handle */ + HANDLE hnd_pps; /* loopback handle */ + ntp_fp_t ofs_assert; /* correction for assert*/ + ntp_fp_t ofs_clear; /* correction for clear */ +}; +typedef struct loopback_unit loopback_unit; + +/* + * -------------------------------------------------------------------- + * DllMain - DLL entrypoint, no-op. + * -------------------------------------------------------------------- + */ +BOOL APIENTRY DllMain( + HMODULE hModule, + DWORD why, + LPVOID lpReserved + ) +{ + UNUSED(hModule); + UNUSED(lpReserved); + UNUSED(why); + + return TRUE; +} + +/* + * -------------------------------------------------------------------- + * time conversion and other helpers + * -------------------------------------------------------------------- + */ + +/* -------------------------------------------------------------------- + * convert fixed point time stamp to struct timespec, with proper + * Epoch/Era unfolding around the current time. + */ +static struct timespec +fp_stamp_to_tspec( + ntp_fp_t x, + time_t p + ) +{ + struct timespec out; + u_int64 tmp; + u_int32 ntp; + + ntp = x.I.u; + tmp = p; + tmp -= 0x80000000u; /* unshift of half range */ + ntp -= (u_int32)PPS_JAN_1970; /* warp into UN*X domain */ + ntp -= (u_int32)tmp; /* cycle difference */ + tmp += (u_int64)ntp; /* get expanded time */ + out.tv_sec = (time_t)tmp; + out.tv_nsec = ((long)(((u_int64)x.F.u * PPS_NANOSECOND + 0x80000000u) >> 32)); + if (out.tv_nsec >= PPS_NANOSECOND) { + out.tv_nsec -= PPS_NANOSECOND; + out.tv_sec++; + } + + return out; +} + +/* -------------------------------------------------------------------- + * convert a duration in struct timespec format to + * fixed point representation. + */ +static ntp_fp_t +tspec_to_fp( + const struct timespec * ts + ) +{ + ntp_fp_t out; + long tmp; + + out.I.u = (u_int32)ts->tv_sec; + tmp = ts->tv_nsec; + if (tmp < 0) + do { + tmp += PPS_NANOSECOND; + out.I.u--; + } while (tmp < 0); + else if (tmp >= PPS_NANOSECOND) + do { + tmp -= PPS_NANOSECOND; + out.I.u++; + } while (tmp >= PPS_NANOSECOND); + out.F.u = (u_int32)((u_int64)tmp << 32) + (PPS_NANOSECOND / 2) / PPS_NANOSECOND; + return out; +} + +/* -------------------------------------------------------------------- + * count number of '1' bits in a u_long + */ +static size_t +popcount( + u_long val + ) +{ + size_t res; + + for (res = 0; val; res++) + val &= (val - 1); + return res; +} + +/* + * -------------------------------------------------------------------- + * API function implementation + * -------------------------------------------------------------------- + */ + +/* -------------------------------------------------------------------- + * prov_time_pps_create - create PPS handle given underlying device + */ +int WINAPI +prov_time_pps_create( + HANDLE device, /* underlying device */ + pps_handle_t * handle /* returned handle */ + ) +{ + loopback_unit * loopunit; + pps_unit_t * punit; + + /* + * Allocate and initialize loopback unit structure. + */ + loopunit = (loopback_unit*)calloc(1, sizeof(loopback_unit)); + if (NULL == loopunit) + return ENOMEM; + + /* Try to attach to NTPD internal data with the device handle. + * Free unit buffer on failure. + */ + loopunit->hnd_dev = device; + loopunit->hnd_pps = ntp_pps_attach_device(device); + if (NULL == loopunit->hnd_pps) { + free(loopunit); + return ENXIO; + } + + /* create the outer PPS handle structure. Undo work done so far + * if things go wrong. + */ + *handle = (*p_create_pps_handle)(loopunit); + if (!*handle) { + ntp_pps_detach_device(loopunit->hnd_pps); + free(loopunit); + return ENOMEM; + } + + /* All good so far. Store things to remember. */ + punit = (pps_unit_t *)*handle; + punit->params.api_version = PPS_API_VERS_1; + punit->params.mode = PPS_CAPTUREBOTH | PPS_TSFMT_BOTH; + return 0; +} + + +/* -------------------------------------------------------------------- + * prov_time_pps_destroy - release PPS handle + */ +int WINAPI +prov_time_pps_destroy( + pps_unit_t * unit, + void * context + ) +{ + loopback_unit * loopunit; + + loopunit = (loopback_unit*)context; + if (unit->context == context) + unit->context = NULL; + if (NULL != loopunit) + ntp_pps_detach_device(loopunit->hnd_pps); + free(loopunit); + return 0; +} + + +/* -------------------------------------------------------------------- + * prov_time_pps_setparams - set parameters for handle + */ +int WINAPI +prov_time_pps_setparams( + pps_unit_t * unit, + void * context, + const pps_params_t * params + ) +{ + loopback_unit * loopunit; + int mode; + + loopunit = (loopback_unit*)context; + mode = params->mode; + + /* + * There was no reasonable consensus in the API working group. + * I require `api_version' to be set! + */ + if (params->api_version != PPS_API_VERS_1) + return EINVAL; + + /* + * We support all edges and formats plus offsets, but not + * POLL or WAIT. And we are strict on the time stamp format: + * Only one is permitted if we set offsets! + */ + + /* + * Only one of the time formats may be selected. + */ + if ((mode & PPS_OFFSETBOTH) != 0 && + popcount(mode & PPS_TSFMT_BOTH) != 1 ) + return EINVAL; + + /* turn off read-only bits */ + mode &= ~SERIALPPS_RO; + + /* + * test remaining bits. + */ + if (mode & ~(PPS_CAPTUREBOTH | PPS_OFFSETBOTH | PPS_TSFMT_BOTH)) + return EOPNOTSUPP; + + /* + * ok, ready to go. + * + * calculate offsets as ntp_fp_t's and store them in unit as ntp_fp_t. They will + * be always applied, since fetching the time stamps is not critical. + */ + if (mode & PPS_OFFSETASSERT) { + if (mode & PPS_TSFMT_TSPEC) + loopunit->ofs_assert = tspec_to_fp(¶ms->assert_offset); + else + loopunit->ofs_assert = params->assert_offset_ntpfp; + } + if (mode & PPS_OFFSETCLEAR) { + if (mode & PPS_TSFMT_TSPEC) + loopunit->ofs_clear = tspec_to_fp(¶ms->clear_offset); + else + loopunit->ofs_clear = params->clear_offset_ntpfp; + } + /* save remaining bits */ + mode |= unit->params.mode; + unit->params = *params; + unit->params.mode = mode; + + return 0; +} + + +/* -------------------------------------------------------------------- + * prov_time_pps_fetch - Fetch timestamps + */ + +int WINAPI +prov_time_pps_fetch( + pps_unit_t * unit, + void * context, + const int tsformat, + pps_info_t * pinfo, + const struct timespec * timeout + ) +{ + loopback_unit * loopunit; + PPSData_t pps_stamps; + pps_info_t infobuf; + BOOL rc; + time_t tnow; + + /* + * nb. PPS_CANWAIT is NOT set by the implementation, we can totally + * ignore the timeout variable. + */ + UNUSED(timeout); + loopunit = (loopback_unit*)context; + + /* read & check raw data from daemon */ + memset(&infobuf, 0, sizeof(infobuf)); + rc = ntp_pps_read(loopunit->hnd_pps, &pps_stamps, sizeof(pps_stamps)); + if (!rc) { + switch (GetLastError()) + { + case ERROR_INVALID_HANDLE: + return EINVAL; + case ERROR_INVALID_PARAMETER: + return EOPNOTSUPP; + case ERROR_INVALID_DATA: + return ENXIO; + default: + return EINVAL; + } + } + + /* add offsets on raw data */ + NTPFP_L_ADDS(&pps_stamps.ts_assert, &loopunit->ofs_assert); + NTPFP_L_ADDS(&pps_stamps.ts_clear , &loopunit->ofs_clear); + + /* store sequence numbers */ + infobuf.assert_sequence = pps_stamps.cc_assert; + infobuf.clear_sequence = pps_stamps.cc_clear; + + /* + * Translate or copy to specified format + */ + switch (tsformat) { + case PPS_TSFMT_NTPFP: /* NTP format requires no translation */ + infobuf.assert_timestamp_ntpfp = pps_stamps.ts_assert; + infobuf.clear_timestamp_ntpfp = pps_stamps.ts_clear; + break; + + case PPS_TSFMT_TSPEC: /* timespec format requires conversion to nsecs form */ + time(&tnow); + infobuf.assert_timestamp = fp_stamp_to_tspec(pps_stamps.ts_assert, tnow); + infobuf.clear_timestamp = fp_stamp_to_tspec(pps_stamps.ts_clear, tnow); + break; + + default: + return EINVAL; + } + + infobuf.current_mode = unit->params.mode; + *pinfo = infobuf; + + return 0; +} + + +/* -------------------------------------------------------------------- + * prov_time_pps_kcbind - specify kernel consumer + * + * Not supported so far by Windows. + */ +int WINAPI +prov_time_pps_kcbind( + pps_unit_t * punit, + void * context, + const int kernel_consumer, + const int edge, + const int tsformat + ) +{ + UNUSED(punit); + UNUSED(context); + UNUSED(kernel_consumer); + UNUSED(edge); + UNUSED(tsformat); + + return EOPNOTSUPP; +} + + +/* -------------------------------------------------------------------- + * prov_init - returns capabilities and provider name + */ +int WINAPI +ppsapi_prov_init( + int ppsapi_timepps_prov_ver, + pcreate_pps_handle create_pps_handle, + ppps_ntp_timestamp_from_counter ntp_timestamp_from_counter, + char * short_name_buf, + size_t short_name_size, + char * full_name_buf, + size_t full_name_size + ) +{ + UNUSED(ntp_timestamp_from_counter); + + if (ppsapi_timepps_prov_ver < PPSAPI_TIMEPPS_PROV_VER) + return 0; + + p_create_pps_handle = create_pps_handle; + + strncpy(short_name_buf, "loopback", short_name_size); + short_name_buf[short_name_size - 1] ='\0'; /* ensure ASCIIZ */ + strncpy(full_name_buf, + "loopback user mode DCD change detection", + full_name_size); + full_name_buf[full_name_size - 1] ='\0'; /* ensure ASCIIZ */ + + return SERIALPPS_CAPS; +} diff --git a/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.def b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.def new file mode 100644 index 000000000..14573a25c --- /dev/null +++ b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.def @@ -0,0 +1,9 @@ +LIBRARY "loopback-ppsapi-provider.dll" + +EXPORTS + ppsapi_prov_init + prov_time_pps_create + prov_time_pps_destroy + prov_time_pps_fetch + prov_time_pps_kcbind + prov_time_pps_setparams \ No newline at end of file diff --git a/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.h b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.h new file mode 100644 index 000000000..5541e333e --- /dev/null +++ b/ports/winnt/ppsapi/loopback/src/loopback-ppsapi.h @@ -0,0 +1,49 @@ +// +// serialpps-ppsapi-provider.h +// +// For this tiny project the single header serves as a precompiled header +// as well, meaning all the bulky headers are included before or within it. +// Within, in this case. +// + +#define _CRT_SECURE_NO_WARNINGS +#include +#include +typedef __int32 int32; +typedef unsigned __int32 u_int32; +typedef __int64 int64; +typedef unsigned __int64 u_int64; +#include "timepps.h" + +#ifndef UNUSED +#define UNUSED(item) ((void)(item)) +#endif + +/* PPS data structure as captured by the serial line I/O system. This + * must match the local definition in 'ntp_iocompletionport.c' or + * 'Bad Things (tm)' are bound to happen. + */ +struct PpsData { + u_long cc_assert; + u_long cc_clear; + ntp_fp_t ts_assert; + ntp_fp_t ts_clear; +}; +typedef struct PpsData PPSData_t; + +/* prototypes imported from the NTPD executable */ +__declspec(dllimport) HANDLE WINAPI ntp_pps_attach_device(HANDLE hndIo); +__declspec(dllimport) void WINAPI ntp_pps_detach_device(HANDLE ppsHandle); +__declspec(dllimport) BOOL WINAPI ntp_pps_read(HANDLE ppsHandle, void*, size_t); + +/* prototypes exported to the NTPD executable */ +__declspec(dllexport) int WINAPI prov_time_pps_create(HANDLE, pps_handle_t*); +__declspec(dllexport) int WINAPI prov_time_pps_destroy(pps_unit_t*, void*); +__declspec(dllexport) int WINAPI prov_time_pps_setparams(pps_unit_t*, void*, + const pps_params_t*); +__declspec(dllexport) int WINAPI prov_time_pps_fetch(pps_unit_t*, void*, + const int, pps_info_t*, const struct timespec*); +__declspec(dllexport) int WINAPI prov_time_pps_kcbind(pps_unit_t*, void*, const int, const int, const int); +__declspec(dllexport) int WINAPI ppsapi_prov_init(int, pcreate_pps_handle, + ppps_ntp_timestamp_from_counter, char*, size_t, + char*, size_t); diff --git a/ports/winnt/ppsapi/loopback/src/sys/time.h b/ports/winnt/ppsapi/loopback/src/sys/time.h new file mode 100644 index 000000000..079e5b644 --- /dev/null +++ b/ports/winnt/ppsapi/loopback/src/sys/time.h @@ -0,0 +1,18 @@ +/* + * This sys/time.h is part of ppsapi-prov skeleton sample source code + * for a Windows PPSAPI provider DLL. It was adapted from + * ports/winnt/include/sys/time.h in ntpd. + */ + +#ifndef SYS_TIME_H +#define SYS_TIME_H + +#include +#include + +typedef struct timespec { + time_t tv_sec; + long tv_nsec; +} timespec_t; + +#endif /* SYS_TIME_H */ diff --git a/ports/winnt/ppsapi/loopback/src/timepps.h b/ports/winnt/ppsapi/loopback/src/timepps.h new file mode 100644 index 000000000..fb1f40c31 --- /dev/null +++ b/ports/winnt/ppsapi/loopback/src/timepps.h @@ -0,0 +1,805 @@ +/*********************************************************************** + * * + * 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 Windows with one or more backend provider DLLs * + * implementing the provider interfaces defined herein. * + * * + * This Windows version was derived by Dave Hart * + * from Mills' timepps-Solaris.h * + * * + *********************************************************************** + * * + * Some of this include file * + * Copyright (c) 1999 by Ulrich Windl, * + * based on code by Reg Clemens * + * based on code by Poul-Henning Kamp * + * * + *********************************************************************** + * * + * "THE BEER-WARE LICENSE" (Revision 42): * + * 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 TIMEPPS_H +#define TIMEPPS_H + +#include "sys/time.h" /* in ntp ref source declares struct timespec */ + +/* + * 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 { \ + while ((x).tv_nsec >= PPS_NANOSECOND) { \ + (x).tv_nsec -= PPS_NANOSECOND; \ + (x).tv_sec++; \ + } \ + while ((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_frac; \ + \ + d_frac = ((struct timespec)&(x))->tv_nsec \ + * PPS_FRAC / PPS_NANOSECOND; \ + (x).integral = ((struct timespec)&(x))->tv_sec \ + + PPS_JAN_1970; \ + (x).fractional = (unsigned int)d_frac; \ + if (d_frac >= PPS_FRAC) \ + (x).integral++; \ + } while (0) + +#define PPS_NTPTOTSPEC(x) /* convert ntp_fp to timespec */ \ + do { \ + double d_frac; \ + \ + /* careful, doing in place and tv_sec may be 64bit */ \ + d_frac = (double)((ntp_fp_t *)&(x))->F.u \ + * PPS_NANOSECOND / PPS_FRAC; \ + (x).tv_sec = ((ntp_fp_t *)&(x))->I.u \ + - (time_t)PPS_JAN_1970; \ + (x).tv_nsec = (long)d_frac; \ + } 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 */ +#define PPS_TSFMT_BOTH (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP) + +/* + * 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 { + int32 s; + u_int32 u; + } I; + union { + int32 s; + u_int32 u; + } F; +} 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 { \ + r_f = (u_int32)(r_f) + (u_int32)(a_f); \ + r_i = (u_int32)(r_i) + (u_int32)(a_i) + \ + ((u_int32)(r_f) < (u_int32)(a_f)); \ + } while (0) + +#define NTPFP_L_ADDS(r, a) NTPFP_M_ADD((r)->I.u, (r)->F.u, (a)->I.u, (a)->F.u) + + +/* + * 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 + +/* + *------ Here begins the implementation-specific part! ------ + */ + +#include +#include +#include /* offsetof() */ +#include /* _get_osfhandle() */ + +#ifndef EOPNOTSUPP +#define EOPNOTSUPP 45 +#endif + +typedef UINT_PTR pps_handle_t; /* pps handlebars */ + +#ifndef inline +#define inline __inline +#endif + +/* + * ntpd on Windows is typically distributed as a binary as few users + * have the tools needed to build from source. Rather than build + * a single timepps.h for Windows which knows how to talk to all + * PPS implementations frozen in time as of compiling, this timepps.h + * allows one or more backend providers to be used by naming a DLL + * which exports the provider interfaces defined here. + */ +typedef enum ppsapi_magic_tag { + PPSAPI_MAGIC_UNIT = 0x70707355, /* ppsU */ +} ppsapi_magic; + +typedef struct { + struct pps_provider_tag *provider; + void * context;/* provider's unit pointer */ + ppsapi_magic magic; /* to detect invalid handles */ + pps_params_t params; /* PPS parameters set by user */ +} pps_unit_t; + +typedef void (*ppps_ntp_timestamp_from_counter)( + ntp_fp_t *result, + ULONGLONG Timestamp, + ULONGLONG Counterstamp + ); + +typedef pps_handle_t (*pcreate_pps_handle)( + void * prov_context + ); + +/* + * ppsapi_prov_init() - exported by backend DLLs + * + * Return value is pps capabilities available to PPSAPI consumers + * via time_pps_getcaps(). + */ +#define PPSAPI_TIMEPPS_PROV_VER 2 + +typedef int (WINAPI *pppsapi_prov_init)( + int ppsapi_timepps_prov_ver, + pcreate_pps_handle create_pps_handle, + ppps_ntp_timestamp_from_counter ntp_timestamp_from_counter, + char * short_name_buf, + size_t short_name_size, + char * full_name_buf, + size_t full_name_size + ); + +typedef int (WINAPI *provtime_pps_create)( + HANDLE winhandle, /* user device handle */ + pps_handle_t *phandle /* returned handle */ + ); + +typedef int (WINAPI *provtime_pps_destroy)( + pps_unit_t * unit, + void * context + ); + +typedef int (WINAPI *provtime_pps_setparams)( + pps_unit_t * unit, + void * context, + const pps_params_t * params + ); + +typedef int (WINAPI *provtime_pps_fetch)( + pps_unit_t * unit, + void * context, + const int tsformat, + pps_info_t * pinfo, + const struct timespec * timeout + ); + +typedef int (WINAPI *provtime_pps_kcbind)( + pps_unit_t * unit, + void * context, + const int kernel_consumer, + const int edge, + const int tsformat + ); + +typedef struct pps_provider_tag { + struct pps_provider_tag *next; + int caps; + char * short_name; + char * full_name; + provtime_pps_create ptime_pps_create; + provtime_pps_destroy ptime_pps_destroy; + provtime_pps_setparams ptime_pps_setparams; + provtime_pps_fetch ptime_pps_fetch; + provtime_pps_kcbind ptime_pps_kcbind; +} ppsapi_provider; + +static ppsapi_provider * g_provider_list; +static ppsapi_provider * g_curr_provider; + + +static inline pps_handle_t +internal_create_pps_handle( + void * prov_context + ) +{ + pps_unit_t * punit; + + if (NULL == g_curr_provider) { + fprintf(stderr, "create_pps_handle: provider backend called me outside time_pps_create\n"); + punit = NULL; + } else + punit = malloc(sizeof(*punit)); + if (punit != NULL) { + punit->provider = g_curr_provider; + punit->context = prov_context; + punit->magic = PPSAPI_MAGIC_UNIT; + memset(&punit->params, 0, sizeof(punit->params)); + } + return (pps_handle_t)punit; +} + +static inline pps_unit_t * +unit_from_ppsapi_handle( + pps_handle_t handle + ) +{ + pps_unit_t *punit; + + punit = (pps_unit_t *)handle; + if (PPSAPI_MAGIC_UNIT != punit->magic) + punit = NULL; + return punit; +} + +/* + * 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 rely 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. + * + * You will probably want to supply your own and #define + * OWN_PPS_NTP_TIMESTAMP_FROM_COUNTER, as this stub + * converts only the low-resolution system timestamp. + * + * When implementing a provider, use the pointer to this + * conversion function supplied to your prov_init(), as + * the copy in your DLL will likely be the stub below, + * where you want the one provided by the PPSAPI client + * such as ntpd. + */ +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->I.u = PPS_JAN_1970 + + (unsigned)(BiasedTimestamp / PPS_HECTONANOSECONDS); + result->F.u = + (unsigned) ((BiasedTimestamp % PPS_HECTONANOSECONDS) * + (PPS_FRAC / PPS_HECTONANOSECONDS)); +} +#endif + + +static inline int +load_pps_provider( + char * dllpath + ) +{ + char short_name[16]; + char full_name[64]; + ppsapi_provider * prov; + HMODULE hmod; + pppsapi_prov_init pprov_init; + + prov = malloc(sizeof(*prov)); + if (NULL == prov) + return ENOMEM; + + hmod = LoadLibrary(dllpath); + if (NULL == hmod) { + fprintf(stderr, "load_pps_provider: LoadLibrary(%s) error %u\n", dllpath, GetLastError()); + free(prov); + return ENOENT; + } + + pprov_init = (pppsapi_prov_init)GetProcAddress(hmod, "ppsapi_prov_init"); + if (NULL == pprov_init) { + fprintf(stderr, "load_pps_provider: entrypoint ppsapi_prov_init not found in %s\n", dllpath); + free(prov); + FreeLibrary(hmod); + return EFAULT; + } + + prov->caps = (*pprov_init)(PPSAPI_TIMEPPS_PROV_VER, + &internal_create_pps_handle, + &pps_ntp_timestamp_from_counter, + short_name, sizeof(short_name), + full_name, sizeof(full_name)); + + if (!prov->caps) { + free(prov); + FreeLibrary(hmod); + return EACCES; + } + + prov->short_name = _strdup(short_name); + prov->full_name = _strdup(full_name); + + if (NULL == prov->short_name || !prov->short_name[0] + || NULL == prov->full_name || !prov->full_name[0]) { + + if (prov->short_name) + free(prov->short_name); + if (prov->full_name) + free(prov->full_name); + free(prov); + FreeLibrary(hmod); + return EINVAL; + } + + prov->ptime_pps_create = (provtime_pps_create) + GetProcAddress(hmod, "prov_time_pps_create"); + prov->ptime_pps_destroy = (provtime_pps_destroy) + GetProcAddress(hmod, "prov_time_pps_destroy"); + prov->ptime_pps_setparams = (provtime_pps_setparams) + GetProcAddress(hmod, "prov_time_pps_setparams"); + prov->ptime_pps_fetch = (provtime_pps_fetch) + GetProcAddress(hmod, "prov_time_pps_fetch"); + prov->ptime_pps_kcbind = (provtime_pps_kcbind) + GetProcAddress(hmod, "prov_time_pps_kcbind"); + + if (NULL == prov->ptime_pps_create + || NULL == prov->ptime_pps_destroy + || NULL == prov->ptime_pps_setparams + || NULL == prov->ptime_pps_fetch + || NULL == prov->ptime_pps_kcbind) { + + fprintf(stderr, "PPSAPI provider %s missing entrypoint\n", + prov->short_name); + free(prov->short_name); + free(prov->full_name); + free(prov); + FreeLibrary(hmod); + return EINVAL; + } + + fprintf(stderr, "loaded PPSAPI provider %s caps 0x%x provider %p\n", + prov->full_name, prov->caps, prov); + + prov->next = g_provider_list; + g_provider_list = prov; + + return 0; +} + + +/* + * time_pps_create - create PPS handle from file descriptor + * + * This is the initial entrypoint of PPSAPI from the client. Note + * to maintain source compatibility with Unix, the input file + * descriptor really is a descriptor from the C runtime low-numbered + * descriptor namespace, though it may have been converted from a + * native Windows HANDLE using _open_osfhandle(). + */ +static inline int +time_pps_create( + int filedes,/* device file descriptor */ + pps_handle_t * phandle /* returned handle */ + ) +{ + HANDLE winhandle; + char * dlls; + char * dll; + char * pch; + ppsapi_provider * prov; + pps_handle_t ppshandle; + int err; + + if (NULL == phandle) + RETURN_PPS_ERRNO(EFAULT); + + winhandle = (HANDLE)_get_osfhandle(filedes); + fprintf(stderr, "time_pps_create(%d) got winhandle %p\n", filedes, winhandle); + if (INVALID_HANDLE_VALUE == winhandle) + RETURN_PPS_ERRNO(EBADF); + + /* + * For initial testing the list of PPSAPI backend + * providers is provided by the environment variable + * PPSAPI_DLLS, separated by semicolons such as + * PPSAPI_DLLS=c:\ntp\serial_ppsapi.dll;..\parport_ppsapi.dll + * There are a million better ways, such as a well-known + * registry key under which a value is created for each + * provider DLL installed, or even a platform-specific + * ntp.conf directive or command-line switch. + */ + dlls = getenv("PPSAPI_DLLS"); + if (dlls != NULL && NULL == g_provider_list) { + dlls = dll = _strdup(dlls); + fprintf(stderr, "getenv(PPSAPI_DLLS) gives %s\n", dlls); + } else + dlls = dll = NULL; + + while (dll != NULL && dll[0]) { + pch = strchr(dll, ';'); + if (pch != NULL) + *pch = 0; + err = load_pps_provider(dll); + if (err) { + fprintf(stderr, "load_pps_provider(%s) got errno %d\n", dll, err); + RETURN_PPS_ERRNO(err); + } + dll = (NULL == pch) + ? NULL + : pch + 1; + } + + if (NULL != dlls) + free(dlls); + dlls = dll = NULL; + + /* + * Hand off to each provider in turn until one returns a PPS + * handle or they've all declined. + */ + for (prov = g_provider_list; prov != NULL; prov = prov->next) { + ppshandle = 0; + g_curr_provider = prov; + err = (*prov->ptime_pps_create)(winhandle, &ppshandle); + g_curr_provider = NULL; + fprintf(stderr, "%s prov_time_pps_create(%p) returned %d\n", + prov->short_name, winhandle, err); + if (!err && ppshandle) { + *phandle = ppshandle; + return 0; + } + } + + fprintf(stderr, "PPSAPI provider list %p\n", g_provider_list); + + RETURN_PPS_ERRNO(ENOEXEC); +} + + +/* + * release PPS handle + */ + +static inline int +time_pps_destroy( + pps_handle_t handle + ) +{ + pps_unit_t * punit; + int err; + + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + err = (*punit->provider->ptime_pps_destroy)(punit, punit->context); + + free(punit); + + if (err) + RETURN_PPS_ERRNO(err); + else + 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 err; + + /* + * Check for valid arguments and set parameters. + */ + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + if (NULL == params) + RETURN_PPS_ERRNO(EFAULT); + + err = (*punit->provider->ptime_pps_setparams)(punit, punit->context, params); + + if (err) + RETURN_PPS_ERRNO(err); + else + return 0; +} + +/* + * get parameters for handle + */ + +static inline int +time_pps_getparams( + pps_handle_t handle, + pps_params_t *params_buf + ) +{ + pps_unit_t * punit; + + /* + * Check for valid arguments and get parameters. + */ + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + if (NULL == params_buf) + RETURN_PPS_ERRNO(EFAULT); + + *params_buf = punit->params; + return 0; +} + + +/* + * time_pps_getcap - get capabilities for handle + */ +static inline int +time_pps_getcap( + pps_handle_t handle, + int *pmode + ) +{ + pps_unit_t * punit; + + /* + * Check for valid arguments and get capabilities. + */ + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + if (NULL == pmode) + RETURN_PPS_ERRNO(EFAULT); + + *pmode = punit->provider->caps; + return 0; +} + +/* + * Fetch timestamps + */ + +static inline int +time_pps_fetch( + pps_handle_t handle, + const int tsformat, + pps_info_t * pinfo, + const struct timespec * ptimeout + ) +{ + pps_unit_t * punit; + int err; + + /* + * Check for valid arguments and fetch timestamps + */ + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + if (NULL == pinfo) + RETURN_PPS_ERRNO(EFAULT); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + err = (*punit->provider->ptime_pps_fetch)(punit, + punit->context, + tsformat, + pinfo, + ptimeout); + + if (err) + RETURN_PPS_ERRNO(err); + else + 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 + ) +{ + pps_unit_t * punit; + int err; + + if (!handle) + RETURN_PPS_ERRNO(EBADF); + + punit = unit_from_ppsapi_handle(handle); + + if (NULL == punit) + RETURN_PPS_ERRNO(EBADF); + + err = (*punit->provider->ptime_pps_kcbind)( + punit, + punit->context, + kernel_consumer, + edge, + tsformat); + + if (err) + RETURN_PPS_ERRNO(err); + else + return 0; +} + + +#endif /* TIMEPPS_H */ diff --git a/ports/winnt/vs2008/instsrv/instsrv.vcproj b/ports/winnt/vs2008/instsrv/instsrv.vcproj index 26eb8a25e..e3324a66e 100644 --- a/ports/winnt/vs2008/instsrv/instsrv.vcproj +++ b/ports/winnt/vs2008/instsrv/instsrv.vcproj @@ -4,6 +4,7 @@ Version="9.00" Name="instsrv" ProjectGUID="{C3534C4D-6DF1-498E-9904-4337878A1515}" + RootNamespace="instsrv" TargetFrameworkVersion="0" > @@ -16,8 +17,8 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ports/winnt/vs2008/ntp-keygen/ntp-keygen.vcproj b/ports/winnt/vs2008/ntp-keygen/ntp-keygen.vcproj index 87831a2fe..6bf76fe93 100644 --- a/ports/winnt/vs2008/ntp-keygen/ntp-keygen.vcproj +++ b/ports/winnt/vs2008/ntp-keygen/ntp-keygen.vcproj @@ -17,8 +17,8 @@ @@ -43,13 +43,14 @@ AdditionalIncludeDirectories="..\..\ntpd,..\..\..\..\ntpd,..\..\include,..\..\..\..\include,..\..\..\..\lib\isc\win32\include,..\..\..\..\lib\isc\include,$(OPENSSL_INC),..\..\..\..\libopts" PreprocessorDefinitions="_DEBUG;_CONSOLE;WIN32;SYS_WINNT;HAVE_CONFIG_H" StringPooling="true" - MinimalRebuild="true" + MinimalRebuild="false" ExceptionHandling="0" BasicRuntimeChecks="3" SmallerTypeCheck="true" RuntimeLibrary="1" EnableFunctionLevelLinking="true" - ProgramDataBaseFileName="..\bin\Debug\keyword-gen-vc90" + ProgramDataBaseFileName="$(OutDir)keyword-gen-vc90" + BrowseInformation="1" WarningLevel="4" DebugInformationFormat="3" CompileAs="1" @@ -66,9 +67,11 @@ @@ -83,6 +86,7 @@ /> @@ -142,9 +149,11 @@ @@ -732,7 +733,7 @@ diff --git a/ports/winnt/vs2008/ntpdate/ntpdate.vcproj b/ports/winnt/vs2008/ntpdate/ntpdate.vcproj index 5025c67a9..85a4bef37 100644 --- a/ports/winnt/vs2008/ntpdate/ntpdate.vcproj +++ b/ports/winnt/vs2008/ntpdate/ntpdate.vcproj @@ -16,8 +16,8 @@