summaryrefslogblamecommitdiffstats
path: root/main/common/timestuff.c
blob: 5cab2d3f3c6072d3b38b6a29bdb3339cdb2e219e (plain) (tree)

































































































































































































































































































































































































                                                                                  
/**************************************************************************
 *
 * Copyright (c) 2013 Alcatel-Lucent
 * 
 * Alcatel Lucent licenses this file to You under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  A copy of the License is contained the
 * file LICENSE at the top level of this repository.
 * You may also obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************
 *
 * timestuff.c
 *
 * These functions support the monitor's ability to deal with elapsed
 * time on targets with or without hardware-timer support.
 *
 * The INCLUDE_HWTMR definition in config.h determines which mode
 * the timer will run in.
 *
 * The monitor does not require any hardware-assist for maintaining
 * elapsed time.  With no hardware assist (INCLUDE_HWTMR set to 0),
 * the value of LOOPS_PER_SECOND must be established in config.h, and
 * can be calibrated using the '-c' option in the sleep command.  Even
 * with this calibration the accuracy of this mechanism is limited;
 * however, since there is no code in the monitor that really requires
 * extremely accurate timing this may be ok.
 * 
 * On the other hand, it is preferrable to have accuracy, so on targets
 * that have a pollable clock,  the hooks can be put in place to allow
 * that clock to be used as the hardware assist for the monitor's 
 * elapsed time measurements.  The INCLUDE_HWTMR is set to 1, and 
 * TIMER_TICKS_PER_MSEC defines the number of ticks of the timer that
 * correspond to 1 millisecond of elapsed time.  The function target_timer() 
 * is assumed to be established and must return an unsigned long value
 * that is the content of the polled hardware timer.
 *
 * Regardless of the hardware-assist or not, the following interface is
 * used by the code in the monitor...
 *
 *	#include "timer.h"
 *
 *	struct elapsed_tmr tmr;
 *
 *	startElapsedTimer(&tmr,TIMEOUT):
 *	do {
 *		SOMETHING();
 *	} while(!msecElapsed(&tmr));
 *
 * Refer to the functions startElapsedTimer() and msecElapsed() below for
 * more details.
 * 
 * Original author:     Ed Sutter (ed.sutter@alcatel-lucent.com)
 *
 */

#include "config.h"
#include "stddefs.h"
#include "cli.h"
#include "genlib.h"
#include "ether.h"
#include "timer.h"
#include "cpuio.h"

/* startElapsedTimer() & msecElapsed():
 * The timer is started by loading the values timeout_low and timeout_high
 * with the number of ticks that must elapse for the timer to expire.
 *
 * In the case of the non-hardware-assisted timer, the expiration count
 * is based on the value of LoopsPerMillisecond (derived from the 
 * LOOPS_PER_SECOND definition in config.h).  Each time msecElapsed()
 * is called the elapsed count is incremented until it exceeds the timeout
 * timeout_low timeout_high values recorded by startElapsedTimer().
 * 
 * The case of the hardware-assisted timer is similar except that now
 * the number of ticks initialized in timeout_low and timeout_high are
 * based on the tick rate of the hardware timer (TIMER_TICKS_PER_MSEC).
 * This value is expected to be set in config.h.  Each time msecElapsed()
 * is called, it samples the timer and adds to the running total of ticks
 * until it matches or exceeds the timeout_low and timeout_high values.
 *
 * Notice that 64-bit values are used (high & low) because a 32-bit value
 * isn't large enough to deal with the tick rates (per second) of various
 * CPUs. 
 */
void
startElapsedTimer(struct elapsed_tmr *tmr, long milliseconds)
{
	unsigned long new_tm_low;
	unsigned long stepmsecs, stepticks, remainder;

#if INCLUDE_HWTMR
	tmr->tmrflags = HWTMR_ENABLED;
	tmr->tpm = (unsigned long)TIMER_TICKS_PER_MSEC;
#else
	tmr->tmrflags = 0;
	tmr->tpm = (unsigned long)LoopsPerMillisecond;
#endif
	
	tmr->elapsed_low = 0;
	tmr->elapsed_high = 0;
	tmr->timeout_high = 0;
	tmr->timeout_low = 0;
	
	/* Convert incoming timeout from a millisecond count to a
	 * tick count...
	 * Maximum number of milliseconds and ticks before 32-bit
	 * (tick counter) unsigned long overlaps
	 */
	stepmsecs = 0xffffffff / tmr->tpm;
	stepticks = stepmsecs * tmr->tpm;
	remainder = (milliseconds % stepmsecs);

	/* Take care of the step remainder
	 */
	tmr->timeout_low = remainder * tmr->tpm;
	milliseconds -= remainder;

	for (;milliseconds; milliseconds -= stepmsecs) {
		new_tm_low = tmr->timeout_low + stepticks;

		if (new_tm_low < tmr->timeout_low)
			tmr->timeout_high++;
		tmr->timeout_low = new_tm_low;
	}
	
#if INCLUDE_HWTMR
	tmr->tmrval = target_timer();
#else
	tmr->tmrval = 0;
#endif

}

int
msecElapsed(struct elapsed_tmr *tmr)
{
	ulong new_elapsed_low, new_tmrval, elapsed;

	/* If timeout has already occurred, then we can assume that this
	 * function being called without a matching startElapsedTimer() call.
	 */
	if (ELAPSED_TIMEOUT(tmr))
		return(1);

#if INCLUDE_HWTMR
	new_tmrval = target_timer();
#else
	new_tmrval = tmr->tmrval + 1;
#endif

	/* Record how many ticks elapsed since the last call to msecElapsed
	 * and add that value to the total number of ticks that have elapsed.
	 */
	elapsed = new_tmrval - tmr->tmrval;
	new_elapsed_low = tmr->elapsed_low + elapsed;

	if (new_elapsed_low < tmr->elapsed_low)
		tmr->elapsed_high++;

	/* If the total elapsed number of ticks exceeds the timeout number
	 * of ticks, then we can return 1 to indicate that the requested
	 * amount of time has elapsed.  Otherwise, we record the values and
	 * return 0.
	 */
	if ((tmr->elapsed_high >= tmr->timeout_high) &&
		(new_elapsed_low >= tmr->timeout_low)) {
		tmr->tmrflags |= TIMEOUT_OCCURRED;
		return(1);
	}
	
	tmr->tmrval = new_tmrval;
	tmr->elapsed_low = new_elapsed_low;
	return(0);
}

/* msecRemainging():
 * Used to query how many milliseconds were left (if any) in the timeout.
 */
ulong
msecRemaining(struct elapsed_tmr *tmr)
{
	ulong high, low, msectot, leftover, divisor;

	if (ELAPSED_TIMEOUT(tmr))
		return(0);
	
	high = tmr->timeout_high - tmr->elapsed_high;
	low = tmr->timeout_low - tmr->elapsed_low;

	msectot = leftover = 0;

#if INCLUDE_HWTMR
	divisor = (ulong)TIMER_TICKS_PER_MSEC;
#else
	divisor = (ulong)LoopsPerMillisecond;
#endif

	while(1) {
		while (low > divisor) {
			msectot++;
			low -= divisor;
		}
		leftover += low;
		if (high == 0)
			break;
		else {
			high--;
			low = 0xffffffff;
		}
	} 

	while(leftover > divisor) {
		msectot++;
		low -= divisor;
	}
	return(msectot);
}

/* monDelay():
 * Delay for specified number of milliseconds.
 * Refer to msecElapsed() description for a discussion on the
 * accuracy of this delay.
 */
void
monDelay(int milliseconds)
{
	struct elapsed_tmr tmr;

	startElapsedTimer(&tmr,milliseconds);
	while(!msecElapsed(&tmr)) {
		WATCHDOG_MACRO;
		pollethernet();
	}
}

/* monTimer():
 * Provide the API with the ability to start a millisecond-granularity
 * timer with some countdown value, and poll it waiting for completion.
 */
int
monTimer(int cmd, void *arg)
{
	int rc = 0;
	struct elapsed_tmr *tmr = (struct elapsed_tmr *)arg;

	switch(cmd) {
		case TIMER_START:
			startElapsedTimer(tmr,tmr->start);
			break;
		case TIMER_ELAPSED:
			msecElapsed(tmr);
			if (ELAPSED_TIMEOUT(tmr))
				rc = 1;
			break;
		case TIMER_QUERY:
#if INCLUDE_HWTMR
			tmr->tmrflags = HWTMR_ENABLED;
			tmr->tpm = (unsigned long)TIMER_TICKS_PER_MSEC;
			tmr->currenttmrval = target_timer();
#else
			tmr->tmrflags = 0;
			tmr->tpm = (unsigned long)LoopsPerMillisecond;
			tmr->currenttmrval = 0;
#endif
			break;
		default:
			rc = -1;
			break;
	}
	return(rc);
}

#if INCLUDE_TFSSCRIPT

/* Sleep():
 *	Simple delay loop accessible by the command line.
 *	This loop count is dependent on the underlying hardware.
 *	The LoopsPerMillisecond count is loaded with a default at startup, or
 *  it can be calibrated by the user via the -c option.
 *	Note that this LoopsPerMillisecond value is used in a few other places
 *	in the monitor also for time-dependent stuff.
 *	NOTES:
 *	- This is obviously not real accurate (not intended to be), but allows the
 *	  monitor to be independent of the underlying hardware.
 *	- The delay time is very dependent on ethernet activity, since the call
 *	  to pollethernet() part of the loop.
 */


char *SleepHelp[] = {
	"Second or msec delay (not precise)",
	"-[clmv:] {count}",
#if INCLUDE_VERBOSEHELP
	" -c  calibrate new LPS count",
	" -l  store LPS count",
	" -m  millisecond",
	" -v {LPSvarname}",
#endif
	0,
};

int
Sleep(int argc,char *argv[])
{
	int opt, calibrate, count, multiplier;

	multiplier = 1000;
	calibrate = 0;
	while ((opt=getopt(argc,argv,"clmv:")) != -1) {
		switch(opt) {
		case 'c':
			calibrate = 2;
			break;
		case 'l':
			calibrate = 1;
			break;
		case 'm':
			multiplier = 1;
			break;
		case 'v':
			shell_sprintf(optarg,"%d",LoopsPerMillisecond*1000);
			return(CMD_SUCCESS);
		default:
			return(CMD_PARAM_ERROR);
		}
	}

	/* If no args, just print the current LPS value and return... */
	if (argc == 1) {
#if INCLUDE_HWTMR
		printf("Hardware-based timer, LPS not applicable\n");
#else
		printf("Current LPS = %ld\n",LoopsPerMillisecond * 1000);
#endif
		return(CMD_SUCCESS);
	}

	/* For calibration, take in the count on the command line, then use
	 * it to put out 5 dots dot at the rate of the loop to allow the user
	 * to adjust it to be about 1 second.
	 */
	if (calibrate) {
#if INCLUDE_HWTMR
		printf("Hardware-based timer, doesn't calibrate\n");
#else
		long lps;

		if (argc != optind+1)
			return(CMD_PARAM_ERROR);

		printf("Current LPS: %ld\n",LoopsPerMillisecond * 1000);
		lps = strtol(argv[optind],0,0);
		LoopsPerMillisecond = lps/1000;
		printf("New LPS: %ld%s\n",LoopsPerMillisecond * 1000,
			lps % 1000 ? " (truncated by 1000)" : "");

		if (calibrate == 2) {
			count = 10;
			while(count-- > 0) {
				monDelay(1000);
				putstr(".\007");
			}
			putchar('\n');
		}
#endif
		return(CMD_SUCCESS);
	}

	if (argc == optind)
		count = 1;
	else
		count = strtol(argv[optind],(char **)0,0);

	monDelay(count * multiplier);
	return(CMD_SUCCESS);
}
#endif