summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Burnicki <martin.burnicki@meinberg.de>2019-05-21 17:27:36 +0200
committerMartin Burnicki <martin.burnicki@meinberg.de>2019-05-21 17:27:36 +0200
commit96474af49ef0789d25f0a4c47d66ba26012c5a6c (patch)
tree858c9d4d026d1de4420e472b17ed16b956068406
downloadkernel-time-state-96474af49ef0789d25f0a4c47d66ba26012c5a6c.tar.gz
kernel-time-state-96474af49ef0789d25f0a4c47d66ba26012c5a6c.zip
Initial revision.
-rw-r--r--kernel-time-state.c552
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 );
+}
+