/************************************************************************/
/* Copyright 1990 - 1997 MBARI						*/
/************************************************************************/
/* $Header: usrTime.c,v 1.4 94/02/18 11:41:40 hebo Exp $		*/
/* Summary  : Unix-compatible Time Routines for SBE VPU-30 under vxWorks*/
/* Filename : usrTime.c							*/
/* Author   : Bob Herlien (rah)						*/
/* Project  : New ROV							*/
/* $Revision: 1.4 $							*/
/* Created  : 02/28/91							*/
/************************************************************************/
/* Modification History:						*/
/* $Log:	usrTime.c,v $
 * Revision 1.4  94/02/18  11:41:40  11:41:40  hebo (Bob Herlien)
 * Ported to VxWorks 5.1.1.  Added localtime_r() and gmtime_r().
 * 
 * Revision 1.2  91/09/23  09:39:23  09:39:23  hebo (Bob Herlien 408-647-3748)
 * Minor bug fixes, added -DMICROTIME
 * 
 * Revision 1.1  91/09/10  11:15:42  11:15:42  hebo (Bob Herlien 408-647-3748)
 * Moved RTC sync from task level to interrupt level, minor bug fixes, rah
 * 
 * Revision 1.0  91/08/09  11:10:33  11:10:33  hebo (Bob Herlien 408-647-3748)
 * Initial revision
 * 	*/
/* 28feb91 rah, created							*/
/************************************************************************/
/* The Unix Epoch (day 0) is Jan 1, 1970.  The real-time clock chip	*/
/* only gives 2 decimal digits of year.  Also, a Unix time_t value	*/
/* (signed long) can only go +/- 68 years from 1970.  Therefore, these	*/
/* routines assume that the year is between 1970 and 2038.  Note that	*/
/* the leap-year exception for centuries not divisible by 400 is not a	*/
/* factor in this period.						*/
/************************************************************************/

#include <mbari_types.h>		/* MBARI standard types		    */
#include <vxWorks.h>			/* VxWorks types		    */
#include <time.h>			/* struct tm definition		    */
#include <sys/times.h>			/* struct timeval, timezone defns   */
#include <usrTime.h>			/* time structure definitions	    */
#include <sysLib.h>			/* VxWorks Driver defines	    */
#include <stdio.h>			/* VxWorks standard I/O library	    */
#include <string.h>			/* VxWorks string types		    */
#include <limits.h>			/* VxWorks integer limits	    */

#ifdef SYNCRTC
#define __PROTOTYPE_5_0
#include <taskLib.h>			/* For taskSpawn		    */
#define RTC_SYNC_PRIO	5		/* High priority but rarely runs    */
#define RTC_STACK_SIZE	2048
#define RTC_SYNC_TIME	3600		/* Sync once per hour		    */	
#endif


#define LCLOFS		28800L		/* Offset in seconds from GMT to PST*/
#define LCLST		"PST"		/* Local timezone is PST	    */
#define LCLDST		"PDT"		/* Local daylight savings zone is PDT*/
#define ADJTIME_MAX	((INT_MAX / USECS_PER_SEC) - 1)
					/* Max seconds in adjtime call	    */
#define CLKRATE		60		/* Default clock tick rate	    */
#define CLK_GRANULARITY  1		/* Granularity of system clock in usec*/
					/* Used to round down # usecs/tick  */
					/* On a VPU-30, PIT clocks at 4 usec*/
					/* To defeat rounding, set to 1	    */
					/* See usrTimeInit()		    */

/********************************/
/*	External Functions	*/
/********************************/

Extern void	usrClock();			     /* Normal clock tick   */
Extern time_t	sysRtcRead( struct tm *tp );	     /* From sysRtc.c	    */
Extern Void	sysRtcWrite( const struct tm *tp );  /* From sysRtc.c	    */
Extern Void	sysMicroClockInit( Nat32 tick );     /* From sysMicroClock.c*/
Extern Void	sysMicroClockAdj( Nat32 tickTime );  /* From sysMicroClock.c*/
#ifdef MICROTIME
Extern Nat32	sysMicroClockGet();		     /* From sysMicroClock.c*/
Extern Void	sysMicroClockSet( Nat32 microTime ); /* From sysMicroClock.c*/
#endif


/********************************/
/*	Global Data		*/
/********************************/

		/* Unix defined variables, per <time.h>			    */
Int32	timezone = LCLOFS;		/* Initialized for PST		    */
char	*tzname[2] = { LCLST, LCLDST };	/* Local timezones: standard, daylight*/
Int32	daylight = 1;			/* 1 if DST, 0 if not 		    */

		/* Following variables same name as on SUNOS. MUST BE POSITIVE*/
Nat32	hz = CLKRATE;			/* Clock rate			     */
Nat32	tick = USECS_PER_SEC / CLKRATE;	/* Microseconds per clock tick       */
Nat32	tickadj = 500 / CLKRATE;	/* Microsecs to speed up or slow down*/


/********************************/
/*	Module Local Data	*/
/********************************/

MLocal Int32	adj_time;		/* Microseconds left to adjust	    */
MLocal Nat32	currentTick;		/* Current adjusted tick	    */


MLocal Nat16	jdays[12] =		/* Days since Jan. 1 in non-leap yr */
{   0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};


/************************************************************************/
/* Function    : usrTimeTick						*/
/* Purpose     : Clock tick handler for usrTime module			*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/* Comments    : Called in interrupt context from Clk Int handler	*/
/************************************************************************/
	Void
usrTimeTick( Void )
{
    Reg Int32	nextTickAdj;
    Reg Nat32	nextTick;

    nextTickAdj = adj_time;

    if ( adj_time >= 0 )
    {
	if ( adj_time > tickadj )
	    nextTickAdj = tickadj;
    }	
    else
    {
	if ( adj_time < -tickadj )
	    nextTickAdj = -tickadj;
    }	
    
    if ( (nextTick = tick + nextTickAdj) != currentTick )
    {
	sysMicroClockAdj( nextTick );
	currentTick = nextTick;
    }

    adj_time -= nextTickAdj;

    usrClock();				/* Call "regular" clock tick func*/

} /* usrTimeTick() */


#ifdef SYNCRTC
/************************************************************************/
/* Function    : rtcSyncTask						*/
/* Purpose     : Task to periodically write RTC from system time	*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/* Comments    : Runs forever						*/
/************************************************************************/
	Void
rtcSyncTask( Void )
{
    int			clkRate, ticksPastSecond;
    struct timeval	curTime;
    struct tm		tbuf;

    while( TRUE )
    {
	clkRate = sysClkRateGet();
	taskDelay( RTC_SYNC_TIME * clkRate );
						/* Default is once per hour*/
	gettimeofday( &curTime, (struct timezone *)NULL );
						/* Get current time	   */
	ticksPastSecond = curTime.tv_usec * clkRate / USECS_PER_SEC;

	if ( ticksPastSecond > 2 )
	{					/* Wait for exact second   */
	    taskDelay( clkRate - ticksPastSecond );
	    curTime.tv_sec++;
	}

	gmtime_r( curTime.tv_sec, &tbuf );	/* Convert time to struct tm*/
	sysRtcWrite( &tbuf );			/* Write RTC		   */
    }

} /* rtcSyncTask() */
#endif


/************************************************************************/
/* Function    : usrTimeInit						*/
/* Purpose     : Initialize usrTime module				*/
/* Inputs      : None							*/
/* Outputs     : OK							*/
/* Comments    : tick is rounded down by CLK_GRANULARITY, to account for*/
/*		 CPU boards whose timer chip is clocked slower than	*/
/*		 1 MHz (e.g. VPU-30)					*/
/************************************************************************/
	STATUS
usrTimeInit( Void )
{
    struct tm		curTime;
    struct timespec	posixTime;

    hz = sysClkRateGet();
    tick = (USECS_PER_SEC / hz) / CLK_GRANULARITY * CLK_GRANULARITY;
    currentTick = tick;
    sysMicroClockInit( tick );

    posixTime.tv_sec = sysRtcRead( &curTime );
    posixTime.tv_nsec = 1000 * USECS_PER_SEC / 2; /* Best guess is .5 sec */
    clock_settime( CLOCK_REALTIME, &posixTime );

    sysClkConnect( (FUNCPTR)usrTimeTick, 0 );

#ifdef SYNCRTC
    taskSpawn( "tRtc", RTC_SYNC_PRIO, VX_UNBREAKABLE, RTC_STACK_SIZE,
	       (FUNCPTR)rtcSyncTask );

#endif

    return( OK );

} /* usrTimeInit() */


/************************************************************************/
/* Function    : adjtime						*/
/* Purpose     : Clock tick handler for usrTime module			*/
/* Inputs      : Ptr to timval struct to adjust, ptr to amount left from*/
/*		  last adjustment					*/
/* Outputs     : Zero for OK, -1 if exceeds ADJTIME_MAX (2146 seconds)	*/
/************************************************************************/
	int
adjtime( struct timeval *delta, struct timeval *olddelta )
{
    olddelta->tv_sec = (adj_time / USECS_PER_SEC);
    olddelta->tv_usec = (adj_time % USECS_PER_SEC);

    if ( (delta->tv_sec > ADJTIME_MAX) || (delta->tv_sec < -ADJTIME_MAX) )
	return( -1 );

    adj_time = (delta->tv_sec * USECS_PER_SEC) + delta->tv_usec;

    return( 0 );

} /* adjtime() */


/************************************************************************/
/* Function    : julday							*/
/* Purpose     : Calculate Julian Day given year, month, day		*/
/* Inputs      : year (00 - 99), month (0 - 11), day (1 - 31)		*/
/* Outputs     : Julian day						*/
/* Comment     : Returns Julian day in range 1:366.  Unix wants 0:365	*/
/************************************************************************/
	Int32
julday( Int32 yr, Int32 mo, Int32 day )
{
    Int32		leap;

    leap = (((yr % 4) == 0) && (mo > 1)) ? 1 : 0;
    return( jdays[mo] + day + leap );

} /* julday() */


/************************************************************************/
/* Function    : ep_days						*/
/* Purpose     : Calculate number days since Unix epoch			*/
/* Inputs      : years since 1970, Unix-type julian day (0:365)		*/
/* Outputs     : Days since 1/1/1970					*/
/* Comment     : The (year + 1)/4 term accounts for leap years, the	*/
/*		 first of which was 1972 & should be added starting '73	*/
/************************************************************************/
	Int32
ep_days( year, yday )
     int	year, yday;
{
    return( (365 * year) + ((year + 1)/4) + yday );

} /* ep_days() */


/************************************************************************/
/* Function    : stime							*/
/* Purpose     : Unix-compatible set time function			*/
/* Inputs      : Ptr to time in seconds from 1/1/70			*/
/* Outputs     : 0							*/
/************************************************************************/
	int
stime( long *tp )
{
    struct timespec	posixTime;

    posixTime.tv_sec = *tp;
    posixTime.tv_nsec = 0;
    clock_settime( CLOCK_REALTIME, &posixTime );
    sysRtcWrite( gmtime((time_t *)tp) );

    return( 0 );

} /* stime() */


/************************************************************************/
/* Function    : gettimeofday						*/
/* Purpose     : Unix-compatible function to get time in usecs		*/
/* Inputs      : Ptr to struct timeval, ptr to struct timezone		*/
/* Outputs     : 0							*/
/************************************************************************/
	int
gettimeofday( struct timeval *tp, struct timezone *tzp )
{
    struct timespec	currentTime;

    clock_gettime( CLOCK_REALTIME, &currentTime );

    if ( tp != (struct timeval *)0 )
    {
	tp->tv_sec = currentTime.tv_sec;
	tp->tv_usec = currentTime.tv_nsec / 1000;

#ifdef MICROTIME
	tp->tv_usec += sysMicroClockGet();
	if ( tp->tv_usec >= USECS_PER_SEC )
	{
	    tp->tv_usec -= USECS_PER_SEC;
	    tp->tv_sec++;
	}
#endif
    }
/******************/
    if ( tzp != (struct timezone *)0 )
    {
	tzp->tz_minuteswest = timezone / 60;
	tzp->tz_dsttime = daylight;
    }
    return( 0 );

} /* gettimeofday() */


/************************************************************************/
/* Function    : settimeofday						*/
/* Purpose     : Almost Unix-compatible function to set GMT time	*/
/* Inputs      : Ptr to struct timeval to set, ptr to struct timezone to set*/
/* Outputs     : 0 if success, -1 if failure				*/
/* Comments    : Sets timezone offset and daylight savings flag		*/
/************************************************************************/
	int
settimeofday( struct timeval *tp, struct timezone *tzp )
{
    struct timespec	posixTime;

    posixTime.tv_sec = tp->tv_sec;
    posixTime.tv_nsec = 1000 * tp->tv_usec;
#ifdef MICROTIME
    sysMicroClockSet(0);
#endif

    clock_settime( CLOCK_REALTIME, &posixTime );
    sysRtcWrite( gmtime((time_t *)tp) );

    if ( tzp != (struct timezone *)0 )
    {
	timezone = 60 * tzp->tz_minuteswest;
	daylight = tzp->tz_dsttime;
    }

    return( 0 );

} /* settimeofday() */


/************************************************************************/
/* Function    : date							*/
/* Purpose     : User interface function to show time and date		*/
/* Inputs      : None							*/
/* Outputs     : 0							*/
/* Comment     : Displays time and date to stdout			*/
/************************************************************************/
	Int32
date( Void )
{
    char	*s;
    time_t	timer;

    time( &timer );
    s = asctime( gmtime(&timer) );
    s[strlen(s)-1] = '\0';
    printf( "%s GMT\n", s );

    s = asctime( localtime(&timer) );
    s[strlen(s)-1] = '\0';
    printf( "%s %s\n", s, tzname[0] );

    return( 0 );

} /* date() */


/************************************************************************/
/* Function    : gmt_usage						*/
/* Purpose     : Print usage message for gmtset				*/
/* Inputs      : Return code to return to user				*/
/* Outputs     : Return code						*/
/************************************************************************/
	Int32
gmt_usage( Int32 rtn )
{
    printf("Usage: gmtset hhmmss[, yymmdd]\n");
    printf("\thhmmss = hours, minutes, seconds\n");
    printf("\tyymmdd = year, month, day\n");

    return( rtn );

} /* gmt_usage() */


/************************************************************************/
/* Function    : gmtset							*/
/* Purpose     : User interface function to set system time, date in GMT*/
/* Inputs      : Time in hhmmss format, (optional) date in yymmdd format*/
/* Outputs     : 0 if success, -1 if failure				*/
/* Comment     : System time is kept as GMT, not local time zone	*/
/************************************************************************/
	Int32
gmtset( Int32 hhmmss, Int32 yymmdd )
{
    struct tm	*tp;
    time_t	curtime;
    Int32	rem;

    time( &curtime );			/* Get current time		*/
    tp = gmtime( &curtime );		/* Fill in *tp			*/

    if ( yymmdd )			/* If date is present,		*/
    {					/*   set date			*/
	tp->tm_year = yymmdd / 10000;	/* Get year field		*/
	if ( tp->tm_year < YEAR0 )	/* If < 70, we're in 21st century*/
	    tp->tm_year += 100;
	rem = yymmdd % 10000;
	tp->tm_mon = (rem / 100) - 1;	/* Get month			*/
	tp->tm_mday = rem % 100;	/* Get day			*/
	if ( (tp->tm_mon > 11) || (tp->tm_mday > 31) )
	    return( gmt_usage(-1) );	/* Check for valid date		*/
    }
    else if ( hhmmss == 0 )
	return( gmt_usage(-1) );	/* If no parms, print usage	*/

    tp->tm_hour = hhmmss / 10000;	/* Get hour			*/
    rem = hhmmss % 10000;
    tp->tm_min = rem / 100;		/* Get minute			*/
    tp->tm_sec = rem % 100;		/* Get second			*/
    if ( (tp->tm_hour > 23) || (tp->tm_min > 59) || (tp->tm_sec > 59) )	
	return( gmt_usage(-1) );	/* Check for valid time		*/

    tp->tm_yday = julday(tp->tm_year, tp->tm_mon, tp->tm_mday) - 1;

    curtime = ep_days(tp->tm_year - YEAR0, tp->tm_yday) * SECS_PER_DAY;
    curtime += (tp->tm_hour * 3600L) + (tp->tm_min * 60) + tp->tm_sec;

    stime( &curtime );			/* Write the time & date	*/
    date();				/* Print it			*/
    return( 0 );

} /* gmtset() */
