diff options
author | Martin Burnicki <martin.burnicki@meinberg.de> | 2019-05-21 17:27:36 +0200 |
---|---|---|
committer | Martin Burnicki <martin.burnicki@meinberg.de> | 2019-05-21 17:27:36 +0200 |
commit | 96474af49ef0789d25f0a4c47d66ba26012c5a6c (patch) | |
tree | 858c9d4d026d1de4420e472b17ed16b956068406 | |
download | kernel-time-state-96474af49ef0789d25f0a4c47d66ba26012c5a6c.tar.gz kernel-time-state-96474af49ef0789d25f0a4c47d66ba26012c5a6c.zip |
Initial revision.
-rw-r--r-- | kernel-time-state.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/kernel-time-state.c b/kernel-time-state.c new file mode 100644 index 0000000..31c8676 --- /dev/null +++ b/kernel-time-state.c @@ -0,0 +1,552 @@ + +/************************************************************************** + * + * Program 'kernel-time-state' + * + * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany + * + * Allows to check or modify the time synchronization state of + * kernels that support the adjtimex / ntp_adjtime API calls, + * i.e. leap second announcement flags, TAI offset, etc. + * + * Changing the kernel state usually requires root permissions. + * + **************************************************************************/ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> // gmtime, clock_gettime +#include <sys/timex.h> // adjtimex or ntp_adjtime + + +#if defined( __linux ) + #define kern_api_fnc adjtimex +#elif defined( __FreeBSD__ ) \ + || defined( __NetBSD__ ) \ + || defined( __sun ) + #define kern_api_fnc ntp_adjtime +#else + #error kernel time API not supported +#endif + + +static const char program_name[] = "kernel-time-state"; + +static const char program_version[] = "v1.0"; +static const char program_copyright[] = "(c) Meinberg 2019"; +static const char program_contact[] = "contact: <martin.burnicki@meinberg.de>"; + + + +#define N_STATUS_BITS 16 + + +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) + + +static int show_current_state; // Show current state. +static int show_tai_time; // Show TAI and UTC time. +static int quiet; // Quiet, suppress startup message. +static int verbose; // Increase verbosity level of output. +static int continuous; // Run continuously. +static int kern_status_set; // New status flags to be set in the kernel at startup (STA_..). +static int kern_status_clear; // New status flags to be cleared in the kernel at startup (STA_..). +static int new_tai_offs; // New TAI offset to be passed to the kernel. +static int must_set_tai; // Indicate a new TAI offset has to be passed to the kernel. +static int must_print_usage; // Must print usage information, then exit. + +static const char str_read_kernel_time_status[] = "read the kernel time status"; + + + + +static /*HDR*/ +long long delta_ts_ns( const struct timespec *tsp_2, const struct timespec *tsp_1 ) +{ + return ( ( (long long) tsp_2->tv_sec - (long long) tsp_1->tv_sec ) * 1000000000LL ) + + ( (long long) tsp_2->tv_nsec - (long long) tsp_1->tv_nsec ); + +} // delta_ts_ns + + + +static /*HDR*/ +void print_date_time( const struct timespec *p_ts ) +{ + struct tm *p_tm = gmtime( &p_ts->tv_sec ); + + if ( p_tm ) + printf( "%04u-%02u-%02u %02u:%02u:%02u.%09li", + p_tm->tm_year + 1900, p_tm->tm_mon + 1, p_tm->tm_mday, + p_tm->tm_hour, p_tm->tm_min, p_tm->tm_sec, p_ts->tv_nsec ); + else + printf( "(gmtime call failed)" ); + +} // print_date_time + + + +static /*HDR*/ +void do_show_tai_utc_time( void ) +{ + struct timespec ts_utc = { 0 }; + int rc_utc = clock_gettime( CLOCK_REALTIME, &ts_utc ); + int errno_utc = errno; + + #if defined( CLOCK_TAI ) + struct timespec ts_tai = { 0 }; + int rc_tai = clock_gettime( CLOCK_TAI, &ts_tai ); + int errno_tai = errno; + #endif + + if ( rc_utc == 0 ) + { + printf( "UTC: " ); + print_date_time( &ts_utc ); + } + else + fprintf( stderr, "Failed to read UTC time: %s", strerror( errno_utc ) ); + + printf( "\n" ); + + #if defined( CLOCK_TAI ) + if ( rc_tai == 0 ) + { + printf( "TAI: " ); + print_date_time( &ts_tai ); + + if ( rc_utc == 0 ) + { + char c; + double dt = delta_ts_ns( &ts_tai, &ts_utc ) / 1e9; + + if ( dt < 0 ) + { + c = '-'; + dt = -dt; + } + else + c = '+'; + + printf( " = UTC %c %.0f s", c, dt ); + } + } + else + fprintf( stderr, "Failed to read TAI time: %s", strerror( errno_tai ) ); + + printf( "\n" ); + #else + printf( "TAI: not supported by clock_gettime() on this system.\n" ); + #endif + +} // do_show_tai_utc_time + + + +static /*HDR*/ +const char *kernel_state_string( int rc, int verbose ) +{ + switch ( rc ) + { + case TIME_OK: return verbose ? ", no leap second warning" : ""; + case TIME_INS: return ", < insert leap second warning"; + case TIME_DEL: return ", < delete leap second warning"; + case TIME_OOP: return ", << leap second in progress"; + case TIME_WAIT: return ", < leap second has occured"; + case TIME_ERROR: return ", * clock not synchronized"; + + } // switch + + return "(** unknown kernel clock state)"; + +} // kernel_state_string + + + +static /*HDR*/ +const char *kernel_status_bit_name( int bit_num ) +{ + static const char *bit_names[N_STATUS_BITS] = + { + "STA_PLL", + "STA_PPSFREQ", + "STA_PPSTIME", + "STA_FLL", + + "STA_INS", + "STA_DEL", + "STA_UNSYNC", + "STA_FREQHOLD", + + "STA_PPSSIGNAL", + "STA_PPSJITTER", + "STA_PPSWANDER", + "STA_PPSERROR", + + "STA_CLOCKERR", + "STA_NANO", + "STA_MODE", + "STA_CLK" + }; + + if ( bit_num < N_STATUS_BITS ) + return bit_names[bit_num]; + + return "(undef)"; + +} // kernel_status_bit_name + + + +static /*HDR*/ +int do_kern_api_fnc( struct timex *p, const char *info ) +{ + int rc = kern_api_fnc( p ); + + if ( rc < 0 ) // error + { + int this_errno = errno; + + fprintf( stderr, "*** " STRINGIFY( kern_api_fnc ) " failed" ); + + if ( info ) + fprintf( stderr, " to %s", info ); + + fprintf( stderr, ": %s\n", strerror( this_errno ) ); + } + + return rc; + +} // do_kern_api_fnc + + + +static /*HDR*/ +void show_kern_status( struct timex *p, int rc, int verbose ) +{ + int i; + + printf( "TAI %i, status 0x%04X:", p->tai, p->status ); + + for ( i = 0; i < N_STATUS_BITS; i++ ) + if ( ( p->status >> i ) & 0x01 ) + printf( " %s", kernel_status_bit_name( i ) ); + + printf( "%s", kernel_state_string( rc, verbose ) ); + +} // show_kern_status + + + +static /*HDR*/ +int check_change_kern_status( void ) +{ + struct timex ntv = { 0 }; + int rc = 0; + + if ( kern_status_set || kern_status_clear ) + { + // First read the current status. + memset( &ntv, 0, sizeof( ntv ) ); + + rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); + + if ( rc < 0 ) // error + goto out; + + ntv.status |= kern_status_set; + ntv.status &= ~kern_status_clear; + + // if ( ntv.status & STA_PLL ) + // ntv.status &= ~STA_UNSYNC; + + ntv.modes |= MOD_STATUS; // TODO or use '=' ? + + // Write new status flags to the kernel. + rc = do_kern_api_fnc( &ntv, "set kernel clock state" ); + + if ( rc < 0 ) // error + { + // Not a fatal error. Continue and read the current status. + memset( &ntv, 0, sizeof( ntv ) ); + rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); + } + + show_kern_status( &ntv, rc, 1 ); + printf( "\n" ); + } + + if ( must_set_tai ) + { + memset( &ntv, 0, sizeof( ntv ) ); + ntv.modes = MOD_TAI; + ntv.constant = new_tai_offs; + + rc = do_kern_api_fnc( &ntv, "set kernel TAI offset" ); + + if ( rc < 0 ) // error + { + // Not a fatal error. Continue and read the current status. + memset( &ntv, 0, sizeof( ntv ) ); + rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); + } + + show_kern_status( &ntv, rc, verbose ); + printf( "\n" ); + } + +out: + return rc; + +} // check_change_kern_status + + + +static /*HDR*/ +int do_test( void ) +{ + const char *err_info = NULL; + + for (;;) + { + int rc_1; + int rc_2; + int rc_a; + struct timespec ts_1; + struct timespec ts_2; + struct timex ntv; + struct timespec *tsp; + long long dt; + + memset( &ntv, 0, sizeof( ntv ) ); + + // Do all 3 calls immediately after each other. + rc_1 = clock_gettime( CLOCK_REALTIME, &ts_1 ); + rc_a = kern_api_fnc( &ntv ); + rc_2 = clock_gettime( CLOCK_REALTIME, &ts_2 ); + + // check if any call returned an error + if ( rc_1 != 0 ) + { + err_info = "first clock_gettime() call"; + break; + } + + if ( rc_a < 0 ) + { + err_info = STRINGIFY( kern_api_fnc ) "() call"; + break; + } + + if ( rc_2 != 0 ) + { + err_info = "second clock_gettime() call"; + break; + } + + tsp = &ts_1; + + dt = delta_ts_ns( &ts_2, &ts_1 ); + + printf( "%i ", rc_a ); + print_date_time( tsp ); + printf( " (%lli.%09li, %lli ns)", + (long long) tsp->tv_sec, tsp->tv_nsec, dt ); + + printf( ", " ); + show_kern_status( &ntv, rc_a, verbose ); + printf( "\n" ); + + usleep( 250000 ); + } + + if ( err_info ) + { + fprintf( stderr, "*** Error: %s failed: %s\n\n", err_info, strerror( errno ) ); + return -1; + } + + return 0; + +} // do_test + + + +static /*HDR*/ +void check_options( int argc, char *argv[] ) +{ + char c; + + while ( ( c = getopt( argc, argv, "stT:LidpPmnNuqvch?" ) ) != -1 ) + { + switch ( c ) + { + case 's': + show_current_state = 1; + break; + + case 't': + show_tai_time = 1; + break; + + case 'T': + new_tai_offs = atoi( optarg ); + must_set_tai = 1; + break; + + case 'L': + kern_status_clear |= STA_INS; + kern_status_clear |= STA_DEL; + break; + + case 'i': + kern_status_set |= STA_INS; + kern_status_set &= ~STA_DEL; + break; + + case 'd': + kern_status_set |= STA_DEL; + kern_status_set &= ~STA_INS; + break; + + case 'p': + kern_status_set |= STA_PLL; + break; + + case 'P': + kern_status_clear |= STA_PLL; + break; + + case 'm': + kern_status_set |= STA_MODE; + break; + + case 'n': + kern_status_set |= STA_NANO; + break; + + case 'N': + kern_status_clear |= STA_NANO; + break; + + case 'u': + kern_status_set |= STA_UNSYNC; + kern_status_clear |= STA_PLL | STA_NANO | STA_MODE; + break; + + case 'c': + continuous = 1; + break; + + case 'q': + quiet = 1; + break; + + case 'v': + verbose++; + break; + + case 'h': + case '?': + default: + must_print_usage = 1; + } + } + +} // check_options + + + +static /*HDR*/ +void usage( void ) +{ + printf( "Show or modify the kernel time synchronization state.\n" ); + printf( "'root' permissions are required to change the state.\n" ); + printf( "\n" ); + printf( "Usage: %s [opt] [opt] ...\n", program_name ); + printf( "\n" ); + printf( "where [opt] can be one or more of the following options:\n" ); + printf( "\n" ); + printf( " -s Show the current kernel time state\n" ); + printf( "\n" ); + printf( " -t Read both UTC and TAI time, and show difference\n" ); + printf( " -T <n> Set TAI offset to <n> seconds\n" ); + printf( " (note the Linux kernel may not accept setting TAI back to 0)\n" ); + printf( "\n" ); + printf( " -L Clear leap second announcement flags (STA_INS and STA_DEL)\n" ); + printf( " -i Set leap second insertion flag (STA_INS)\n" ); + printf( " -d Set leap second deletion flag (STA_DEL)\n" ); + printf( " (leap second flags may only be accepted at specific dates)\n" ); + printf( "\n" ); + printf( " -p Set PLL flag (STA_PLL)\n" ); + printf( " -P Clear PLL flag (STA_PLL)\n" ); + printf( " -m Set mode flag (STA_MODE)\n" ); + printf( " -n Set nano time flag (STA_NANO)\n" ); + printf( " -N Clear nano time flag (STA_NANO)\n" ); + printf( " -u Set \"unsync\" flag (STA_UNSYNC)\n" ); + printf( "\n" ); + printf( " -q Quiet mode\n" ); + printf( " -v Increase verbosity level\n" ); + printf( " -c Run continuously, periodically report kernel time status\n" ); + printf( "\n" ); + printf( " -h, -? Print this usage information\n" ); + printf( "\n" ); + +} // usage + + + +int main( int argc, char *argv[] ) +{ + int rc = 0; + + if ( argc > 1 ) + { + check_options( argc, argv ); + // show_current_state = 1; + } + else + must_print_usage = 1; + + if ( !quiet || must_print_usage ) + fprintf( stderr, "\n%s %s, %s, %s\n", program_name, program_version, + program_copyright, program_contact ); + + if ( must_print_usage ) + { + usage(); + rc = 1; + goto out; + } + + if ( show_current_state ) + { + struct timex ntv = { 0 }; + rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); + + if ( rc < 0 ) + goto out; + + show_kern_status( &ntv, rc, verbose ); + printf( "\n" ); + } + + if ( show_tai_time ) + do_show_tai_utc_time(); + + rc = check_change_kern_status(); + + if ( rc < 0 ) + goto out; + + if ( continuous ) + rc = do_test(); + +out: + return abs( rc ); +} + |