/* * 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 * * <davehart@davehart.com> from David L. 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 * * * **********************************************************************/ /* * 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; }