summaryrefslogtreecommitdiffstats
path: root/main/common/timestuff.c
blob: d5ba4cf405c466494359de81b319cf50e189c7c7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/**************************************************************************
 *
 * 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