source: rtems/c/src/lib/libbsp/i386/pc386/timer/timer.c @ 6b54dcb

Last change on this file since 6b54dcb was 6b54dcb, checked in by Pavel Pisa <pisa@…>, on Oct 12, 2016 at 7:40:41 AM

bsps/i386: replace global interrupt disable by SMP build supporting locking.

  • Property mode set to 100644
File size: 14.1 KB
Line 
1/*
2 * This file contains the PC386 timer package.
3 *
4 * Rosimildo daSilva -ConnectTel, Inc - Fixed infinite loop in the Calibration
5 * routine. I've seen this problems with faster machines ( pentiums ). Sometimes
6 * RTEMS just hangs at startup.
7 *
8 * Joel 9 May 2010: This is now seen sometimes on qemu.
9 *
10 *  Modifications by:
11 *  (C) Copyright 1997 -
12 *    NavIST Group - Real-Time Distributed Systems and Industrial Automation
13 *    http://pandora.ist.utl.pt
14 *    Instituto Superior Tecnico * Lisboa * PORTUGAL
15 *
16 *  This file is provided "AS IS" without warranty of any kind, either
17 *  expressed or implied.
18 *
19 *  Based upon code by
20 *  COPYRIGHT (c) 1989-1999.
21 *  On-Line Applications Research Corporation (OAR).
22 *
23 *  The license and distribution terms for this file may be
24 *  found in the file LICENSE in this distribution or at
25 *  http://www.rtems.org/license/LICENSE.
26 */
27
28#include <stdlib.h>
29#include <bsp.h>
30#include <rtems/btimer.h>
31#include <bsp/irq-generic.h>
32#include <libcpu/cpuModel.h>
33
34/*
35 * Constants
36 */
37#define AVG_OVERHEAD  0         /* 0.1 microseconds to start/stop timer. */
38#define LEAST_VALID   1         /* Don't trust a value lower than this.  */
39#define SLOW_DOWN_IO  0x80      /* io which does nothing */
40
41#define TWO_MS (uint32_t)(2000) /* TWO_MS = 2000us (sic!) */
42
43#define MSK_NULL_COUNT 0x40     /* bit counter available for reading */
44
45#define CMD_READ_BACK_STATUS 0xE2   /* command read back status */
46
47RTEMS_INTERRUPT_LOCK_DEFINE( /* visible global variable */ ,
48   rtems_i386_i8254_access_lock, "rtems_i386_i8254_access_lock" );
49
50/*
51 * Global Variables
52 */
53volatile uint32_t         Ttimer_val;
54bool                      benchmark_timer_find_average_overhead = true;
55volatile unsigned int     fastLoop1ms, slowLoop1ms;
56
57void              (*benchmark_timer_initialize_function)(void) = 0;
58benchmark_timer_t (*benchmark_timer_read_function)(void) = 0;
59void              (*Timer_exit_function)(void) = 0;
60
61/* timer (int 08h) Interrupt Service Routine (defined in 'timerisr.s') */
62extern void timerisr(void);
63
64void Timer_exit(void);
65
66/*
67 * Pentium optimized timer handling.
68 */
69
70/*
71 *  Timer cleanup routine at RTEMS exit.
72 *
73 *  NOTE: This routine is not really necessary, since there will be
74 *        a reset at exit.
75 */
76static void tsc_timer_exit(void)
77{
78}
79
80static void tsc_timer_initialize(void)
81{
82  static bool First = true;
83
84  if (First) {
85    First = false;
86
87    atexit(Timer_exit); /* Try not to hose the system at exit. */
88  }
89  Ttimer_val = rdtsc(); /* read starting time */
90}
91
92/*
93 * Read TSC timer value.
94 */
95static uint32_t tsc_read_timer(void)
96{
97  register uint32_t  total;
98
99  total =  (uint32_t)(rdtsc() - Ttimer_val);
100
101  if (benchmark_timer_find_average_overhead)
102    return total;
103
104  if (total < LEAST_VALID)
105    return 0;                 /* below timer resolution */
106
107  return (total - AVG_OVERHEAD);
108}
109
110/*
111 * Non-Pentium timer handling.
112 */
113#define US_PER_ISR   250  /* Number of micro-seconds per timer interruption */
114
115/*
116 * Timer cleanup routine at RTEMS exit. NOTE: This routine is
117 * not really necessary, since there will be a reset at exit.
118 */
119static void timerOff(const rtems_raw_irq_connect_data* used)
120{
121  rtems_interrupt_lock_context lock_context;
122  /*
123   * disable interrrupt at i8259 level
124   */
125  bsp_interrupt_vector_disable(used->idtIndex - BSP_IRQ_VECTOR_BASE);
126
127  rtems_interrupt_lock_acquire(&rtems_i386_i8254_access_lock, &lock_context);
128
129   /* reset timer mode to standard (DOS) value */
130  outport_byte(TIMER_MODE, TIMER_SEL0|TIMER_16BIT|TIMER_RATEGEN);
131  outport_byte(TIMER_CNTR0, 0);
132  outport_byte(TIMER_CNTR0, 0);
133
134  rtems_interrupt_lock_release(&rtems_i386_i8254_access_lock, &lock_context);
135}
136
137static void timerOn(const rtems_raw_irq_connect_data* used)
138{
139  rtems_interrupt_lock_context lock_context;
140
141  rtems_interrupt_lock_acquire(&rtems_i386_i8254_access_lock, &lock_context);
142
143  /* load timer for US_PER_ISR microsecond period */
144  outport_byte(TIMER_MODE, TIMER_SEL0|TIMER_16BIT|TIMER_RATEGEN);
145  outport_byte(TIMER_CNTR0, US_TO_TICK(US_PER_ISR) >> 0 & 0xff);
146  outport_byte(TIMER_CNTR0, US_TO_TICK(US_PER_ISR) >> 8 & 0xff);
147
148  rtems_interrupt_lock_release(&rtems_i386_i8254_access_lock, &lock_context);
149
150  /*
151   * enable interrrupt at i8259 level
152   */
153  bsp_interrupt_vector_enable(used->idtIndex - BSP_IRQ_VECTOR_BASE);
154}
155
156static int timerIsOn(const rtems_raw_irq_connect_data *used)
157{
158  return bsp_interrupt_vector_enable(used->idtIndex - BSP_IRQ_VECTOR_BASE);
159}
160
161static rtems_raw_irq_connect_data timer_raw_irq_data = {
162  BSP_PERIODIC_TIMER + BSP_IRQ_VECTOR_BASE,
163  timerisr,
164  timerOn,
165  timerOff,
166  timerIsOn
167};
168
169/*
170 * Timer cleanup routine at RTEMS exit.
171 *
172 * NOTE: This routine is not really necessary, since there will be
173 *       a reset at exit.
174 */
175static void i386_timer_exit(void)
176{
177  i386_delete_idt_entry (&timer_raw_irq_data);
178}
179
180extern void rtems_irq_prologue_0(void);
181static void i386_timer_initialize(void)
182{
183  static bool First = true;
184
185  if (First) {
186    rtems_raw_irq_connect_data raw_irq_data = {
187      BSP_PERIODIC_TIMER + BSP_IRQ_VECTOR_BASE,
188      rtems_irq_prologue_0,
189      NULL,
190      NULL,
191      NULL
192    };
193
194    First = false;
195    i386_delete_idt_entry (&raw_irq_data);
196
197    atexit(Timer_exit);            /* Try not to hose the system at exit. */
198    if (!i386_set_idt_entry (&timer_raw_irq_data)) {
199      printk("raw handler connection failed\n");
200      rtems_fatal_error_occurred(1);
201    }
202  }
203  /* wait for ISR to be called at least once */
204  Ttimer_val = 0;
205  while (Ttimer_val == 0)
206    continue;
207  Ttimer_val = 0;
208}
209
210/*
211 * Read hardware timer value.
212 */
213static uint32_t i386_read_timer(void)
214{
215  register uint32_t         total, clicks;
216  register uint8_t          lsb, msb;
217  rtems_interrupt_lock_context lock_context;
218
219  rtems_interrupt_lock_acquire(&rtems_i386_i8254_access_lock, &lock_context);
220  outport_byte(TIMER_MODE, TIMER_SEL0|TIMER_LATCH);
221  inport_byte(TIMER_CNTR0, lsb);
222  inport_byte(TIMER_CNTR0, msb);
223  rtems_interrupt_lock_release(&rtems_i386_i8254_access_lock, &lock_context);
224
225  clicks = (msb << 8) | lsb;
226  total  = (Ttimer_val * US_PER_ISR) + (US_PER_ISR - TICK_TO_US(clicks));
227
228  if (benchmark_timer_find_average_overhead)
229    return total;
230
231  if (total < LEAST_VALID)
232    return 0;                            /* below timer resolution */
233
234  return (total - AVG_OVERHEAD);
235}
236
237/*
238 * General timer functions using either TSC-based implementation
239 * or interrupt-based implementation
240 */
241
242void benchmark_timer_initialize(void)
243{
244  static bool First = true;
245
246  if (First) {
247    if (x86_has_tsc()) {
248#if defined(DEBUG)
249      printk("TSC: timer initialization\n");
250#endif /* DEBUG */
251      benchmark_timer_initialize_function = &tsc_timer_initialize;
252      benchmark_timer_read_function = &tsc_read_timer;
253      Timer_exit_function = &tsc_timer_exit;
254    } else {
255#if defined(DEBUG)
256      printk("ISR: timer initialization\n");
257#endif /* DEBUG */
258      benchmark_timer_initialize_function = &i386_timer_initialize;
259      benchmark_timer_read_function = &i386_read_timer;
260      Timer_exit_function = &i386_timer_exit;
261    }
262    First = false;
263  }
264  (*benchmark_timer_initialize_function)();
265}
266
267uint32_t benchmark_timer_read(void)
268{
269  return (*benchmark_timer_read_function)();
270}
271
272void Timer_exit(void)
273{
274  if ( Timer_exit_function )
275    return (*Timer_exit_function)();
276}
277
278/*
279 * Set internal benchmark_timer_find_average_overhead flag value.
280 */
281void benchmark_timer_disable_subtracting_average_overhead(bool find_flag)
282{
283  benchmark_timer_find_average_overhead = find_flag;
284}
285
286static unsigned short lastLoadedValue;
287
288/*
289 *  Loads timer 0 with value passed as arguemnt.
290 *
291 *  Returns: Nothing. Loaded value must be a number of clock bits...
292 */
293static void loadTimerValue( unsigned short loadedValue )
294{
295  rtems_interrupt_lock_context lock_context;
296  rtems_interrupt_lock_acquire(&rtems_i386_i8254_access_lock, &lock_context);
297  lastLoadedValue = loadedValue;
298  outport_byte(TIMER_MODE, TIMER_SEL0|TIMER_16BIT|TIMER_SQWAVE);
299  outport_byte(TIMER_CNTR0, loadedValue & 0xff);
300  outport_byte(TIMER_CNTR0, (loadedValue >> 8) & 0xff);
301  rtems_interrupt_lock_release(&rtems_i386_i8254_access_lock, &lock_context);
302}
303
304/*
305 * Reads the current value of the timer, and converts the
306 *  number of ticks to micro-seconds.
307 *
308 * Returns: number of clock bits elapsed since last load.
309 */
310static unsigned int readTimer0(void)
311{
312  unsigned short lsb, msb;
313  unsigned char  status;
314  unsigned int  count;
315  rtems_interrupt_lock_context lock_context;
316  rtems_interrupt_lock_acquire(&rtems_i386_i8254_access_lock, &lock_context);
317
318  outport_byte(
319    TIMER_MODE,
320    (TIMER_RD_BACK | (RB_COUNT_0 & ~(RB_NOT_STATUS | RB_NOT_COUNT)))
321  );
322  inport_byte(TIMER_CNTR0, status);
323  inport_byte(TIMER_CNTR0, lsb);
324  inport_byte(TIMER_CNTR0, msb);
325
326  rtems_interrupt_lock_release(&rtems_i386_i8254_access_lock, &lock_context);
327
328  count = ( msb << 8 ) | lsb ;
329  if (status & RB_OUTPUT )
330    count += lastLoadedValue;
331
332  return (2*lastLoadedValue - count);
333}
334
335static void Timer0Reset(void)
336{
337  loadTimerValue(0xffff);
338  readTimer0();
339}
340
341static void fastLoop (unsigned int loopCount)
342{
343  unsigned int i;
344  for( i=0; i < loopCount; i++ )outport_byte( SLOW_DOWN_IO, 0 );
345}
346
347static void slowLoop (unsigned int loopCount)
348{
349  unsigned int j;
350  for (j=0; j <100 ;  j++) {
351    fastLoop (loopCount);
352  }
353}
354
355/*
356 * #define DEBUG_CALIBRATE
357 */
358void
359Calibrate_loop_1ms(void)
360{
361  unsigned int offset, offsetTmp, emptyCall, emptyCallTmp, res, i, j;
362  unsigned int targetClockBits, currentClockBits;
363  unsigned int slowLoopGranularity, fastLoopGranularity;
364  rtems_interrupt_level  level;
365  int retries = 0;
366
367  /*
368   * This code is designed to run before interrupt management
369   * is enabled and running it on multiple CPUs and or after
370   * secondary CPUs are bring up seems really broken.
371   * Disabling of local interrupts is enough.
372   */
373  rtems_interrupt_local_disable(level);
374
375retry:
376  if ( ++retries >= 5 ) {
377    printk( "Calibrate_loop_1ms: too many attempts. giving up!!\n" );
378    while (1);
379  }
380#ifdef DEBUG_CALIBRATE
381  printk("Calibrate_loop_1ms is starting,  please wait (but not too long.)\n");
382#endif
383  targetClockBits = US_TO_TICK(1000);
384  /*
385   * Fill up the cache to get a correct offset
386   */
387  Timer0Reset();
388  readTimer0();
389  /*
390   * Compute the minimal offset to apply due to read counter register.
391   */
392  offset = 0xffffffff;
393  for (i=0; i <1000; i++) {
394    Timer0Reset();
395    offsetTmp = readTimer0();
396    offset += offsetTmp;
397  }
398  offset = offset / 1000; /* compute average */
399  /*
400   * calibrate empty call
401   */
402  fastLoop (0);
403  emptyCall = 0;
404  j = 0;
405  for (i=0; i <10; i++) {
406    Timer0Reset();
407    fastLoop (0);
408    res =  readTimer0();
409    /* res may be inferior to offset on fast
410     * machine because we took an average for offset
411     */
412    if (res >  offset) {
413      ++j;
414      emptyCallTmp = res - offset;
415      emptyCall += emptyCallTmp;
416    }
417  }
418  if (j == 0) emptyCall = 0;
419  else emptyCall = emptyCall / j; /* compute average */
420  /*
421   * calibrate fast loop
422   */
423  Timer0Reset();
424  fastLoop (10000);
425  res = readTimer0() - offset;
426  if (res < emptyCall) {
427    printk(
428      "Problem #1 in offset computation in Calibrate_loop_1ms "
429        " in file libbsp/i386/pc386/timer/timer.c\n"
430    );
431    goto retry;
432  }
433  fastLoopGranularity = (res - emptyCall) / 10000;
434  /*
435   * calibrate slow loop
436   */
437  Timer0Reset();
438  slowLoop(10);
439  res = readTimer0();
440  if (res < offset + emptyCall) {
441    printk(
442      "Problem #2 in offset computation in Calibrate_loop_1ms "
443        " in file libbsp/i386/pc386/timer/timer.c\n"
444    );
445    goto retry;
446  }
447  slowLoopGranularity = (res - offset - emptyCall)/ 10;
448
449  if (slowLoopGranularity == 0) {
450    printk(
451      "Problem #3 in offset computation in Calibrate_loop_1ms "
452        " in file libbsp/i386/pc386/timer/timer.c\n"
453    );
454    goto retry;
455  }
456
457  targetClockBits += offset;
458#ifdef DEBUG_CALIBRATE
459  printk("offset = %u, emptyCall = %u, targetClockBits = %u\n",
460  offset, emptyCall, targetClockBits);
461  printk("slowLoopGranularity = %u fastLoopGranularity =  %u\n",
462  slowLoopGranularity, fastLoopGranularity);
463#endif
464  slowLoop1ms = (targetClockBits - emptyCall) / slowLoopGranularity;
465  if (slowLoop1ms != 0) {
466    fastLoop1ms = targetClockBits % slowLoopGranularity;
467    if (fastLoop1ms > emptyCall) fastLoop1ms -= emptyCall;
468  }
469  else
470    fastLoop1ms = targetClockBits - emptyCall / fastLoopGranularity;
471
472  if (slowLoop1ms != 0) {
473    /*
474     * calibrate slow loop
475     */
476
477    while(1)
478      {
479 int previousSign = 0; /* 0 = unset, 1 = incrementing,  2 = decrementing */
480 Timer0Reset();
481 slowLoop(slowLoop1ms);
482 currentClockBits = readTimer0();
483 if (currentClockBits > targetClockBits) {
484   if ((currentClockBits - targetClockBits) < slowLoopGranularity) {
485     /* decrement loop counter anyway to be sure slowLoop(slowLoop1ms) < targetClockBits */
486     --slowLoop1ms;
487     break;
488   }
489   else {
490     --slowLoop1ms;
491     if (slowLoop1ms == 0) break;
492     if (previousSign == 0) previousSign = 2;
493     if (previousSign == 1) break;
494   }
495 }
496 else {
497   if ((targetClockBits - currentClockBits) < slowLoopGranularity) {
498      break;
499    }
500    else {
501      ++slowLoop1ms;
502      if (previousSign == 0) previousSign = 1;
503      if (previousSign == 2) break;
504    }
505  }
506      }
507  }
508  /*
509   * calibrate fast loop
510   */
511
512  if (fastLoopGranularity != 0 ) {
513    while(1) {
514      int previousSign = 0; /* 0 = unset, 1 = incrementing,  2 = decrementing */
515      Timer0Reset();
516      if (slowLoop1ms != 0) slowLoop(slowLoop1ms);
517      fastLoop(fastLoop1ms);
518      currentClockBits = readTimer0();
519      if (currentClockBits > targetClockBits) {
520  if ((currentClockBits - targetClockBits) < fastLoopGranularity)
521    break;
522  else {
523    --fastLoop1ms;
524    if (previousSign == 0) previousSign = 2;
525    if (previousSign == 1) break;
526  }
527      }
528      else {
529  if ((targetClockBits - currentClockBits) < fastLoopGranularity)
530    break;
531  else {
532    ++fastLoop1ms;
533    if (previousSign == 0) previousSign = 1;
534    if (previousSign == 2) break;
535  }
536      }
537    }
538  }
539#ifdef DEBUG_CALIBRATE
540  printk("slowLoop1ms = %u, fastLoop1ms = %u\n", slowLoop1ms, fastLoop1ms);
541#endif
542  rtems_interrupt_local_enable(level);
543
544}
545
546/*
547 *  loop which waits at least timeToWait ms
548 */
549void Wait_X_ms( unsigned int timeToWait)
550{
551  unsigned int j;
552
553  for (j=0; j<timeToWait ; j++) {
554    if (slowLoop1ms != 0) slowLoop(slowLoop1ms);
555    fastLoop(fastLoop1ms);
556  }
557}
Note: See TracBrowser for help on using the repository browser.