source: rtems/cpukit/rtems/src/timerserver.c @ 54cf0e34

4.115
Last change on this file since 54cf0e34 was 54cf0e34, checked in by Sebastian Huber <sebastian.huber@…>, on Apr 10, 2015 at 2:19:48 PM

score: Add Watchdog_Header

This type is intended to encapsulate all state to manage a watchdog
chain.

Update #2307.

  • Property mode set to 100644
File size: 17.3 KB
Line 
1/**
2 *  @file timerserver.c
3 *
4 *  Timer Manager - rtems_timer_initiate_server directive along with
5 *  the Timer Server Body and support routines
6 *
7 *  @note Data specific to the Timer Server is declared in this
8 *        file as the Timer Server so it does not have to be in the
9 *        minimum footprint.  It is only really required when
10 *        task-based timers are used.  Since task-based timers can
11 *        not be started until the server is initiated, this structure
12 *        does not have to be initialized until then.
13 */
14
15/*  COPYRIGHT (c) 1989-2008.
16 *  On-Line Applications Research Corporation (OAR).
17 *
18 *  Copyright (c) 2009 embedded brains GmbH.
19 *
20 *  The license and distribution terms for this file may be
21 *  found in the file LICENSE in this distribution or at
22 *  http://www.rtems.org/license/LICENSE.
23 */
24
25#if HAVE_CONFIG_H
26#include "config.h"
27#endif
28
29#include <rtems/rtems/timerimpl.h>
30#include <rtems/rtems/tasksimpl.h>
31#include <rtems/score/isrlevel.h>
32#include <rtems/score/threadimpl.h>
33#include <rtems/score/todimpl.h>
34
35static Timer_server_Control _Timer_server_Default;
36
37static void _Timer_server_Stop_interval_system_watchdog(
38  Timer_server_Control *ts
39)
40{
41  _Watchdog_Remove( &ts->Interval_watchdogs.System_watchdog );
42}
43
44static void _Timer_server_Reset_interval_system_watchdog(
45  Timer_server_Control *ts
46)
47{
48  ISR_Level level;
49
50  _Timer_server_Stop_interval_system_watchdog( ts );
51
52  _ISR_Disable( level );
53  if ( !_Watchdog_Is_empty( &ts->Interval_watchdogs.Header ) ) {
54    Watchdog_Interval delta_interval =
55      _Watchdog_First( &ts->Interval_watchdogs.Header )->delta_interval;
56    _ISR_Enable( level );
57
58    /*
59     *  The unit is TICKS here.
60     */
61    _Watchdog_Insert_ticks(
62      &ts->Interval_watchdogs.System_watchdog,
63      delta_interval
64    );
65  } else {
66    _ISR_Enable( level );
67  }
68}
69
70static void _Timer_server_Stop_tod_system_watchdog(
71  Timer_server_Control *ts
72)
73{
74  _Watchdog_Remove( &ts->TOD_watchdogs.System_watchdog );
75}
76
77static void _Timer_server_Reset_tod_system_watchdog(
78  Timer_server_Control *ts
79)
80{
81  ISR_Level level;
82
83  _Timer_server_Stop_tod_system_watchdog( ts );
84
85  _ISR_Disable( level );
86  if ( !_Watchdog_Is_empty( &ts->TOD_watchdogs.Header ) ) {
87    Watchdog_Interval delta_interval =
88      _Watchdog_First( &ts->TOD_watchdogs.Header )->delta_interval;
89    _ISR_Enable( level );
90
91    /*
92     *  The unit is SECONDS here.
93     */
94    _Watchdog_Insert_seconds(
95      &ts->TOD_watchdogs.System_watchdog,
96      delta_interval
97    );
98  } else {
99    _ISR_Enable( level );
100  }
101}
102
103static void _Timer_server_Insert_timer(
104  Timer_server_Control *ts,
105  Timer_Control *timer
106)
107{
108  if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) {
109    _Watchdog_Insert( &ts->Interval_watchdogs.Header, &timer->Ticker );
110  } else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) {
111    _Watchdog_Insert( &ts->TOD_watchdogs.Header, &timer->Ticker );
112  }
113}
114
115static void _Timer_server_Insert_timer_and_make_snapshot(
116  Timer_server_Control *ts,
117  Timer_Control *timer
118)
119{
120  Watchdog_Control *first_watchdog;
121  Watchdog_Interval delta_interval;
122  Watchdog_Interval last_snapshot;
123  Watchdog_Interval snapshot;
124  Watchdog_Interval delta;
125  ISR_Level level;
126
127  /*
128   *  We have to update the time snapshots here, because otherwise we may have
129   *  problems with the integer range of the delta values.  The time delta DT
130   *  from the last snapshot to now may be arbitrarily long.  The last snapshot
131   *  is the reference point for the delta chain.  Thus if we do not update the
132   *  reference point we have to add DT to the initial delta of the watchdog
133   *  being inserted.  This could result in an integer overflow.
134   */
135
136  _Thread_Disable_dispatch();
137
138  if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) {
139    /*
140     *  We have to advance the last known ticks value of the server and update
141     *  the watchdog chain accordingly.
142     */
143    _ISR_Disable( level );
144    snapshot = _Watchdog_Ticks_since_boot;
145    last_snapshot = ts->Interval_watchdogs.last_snapshot;
146    if ( !_Watchdog_Is_empty( &ts->Interval_watchdogs.Header ) ) {
147      first_watchdog = _Watchdog_First( &ts->Interval_watchdogs.Header );
148
149      /*
150       *  We assume adequate unsigned arithmetic here.
151       */
152      delta = snapshot - last_snapshot;
153
154      delta_interval = first_watchdog->delta_interval;
155      if (delta_interval > delta) {
156        delta_interval -= delta;
157      } else {
158        delta_interval = 0;
159      }
160      first_watchdog->delta_interval = delta_interval;
161    }
162    ts->Interval_watchdogs.last_snapshot = snapshot;
163    _ISR_Enable( level );
164
165    _Watchdog_Insert( &ts->Interval_watchdogs.Header, &timer->Ticker );
166
167    if ( !ts->active ) {
168      _Timer_server_Reset_interval_system_watchdog( ts );
169    }
170  } else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) {
171    /*
172     *  We have to advance the last known seconds value of the server and update
173     *  the watchdog chain accordingly.
174     */
175    _ISR_Disable( level );
176    snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
177    last_snapshot = ts->TOD_watchdogs.last_snapshot;
178    if ( !_Watchdog_Is_empty( &ts->TOD_watchdogs.Header ) ) {
179      first_watchdog = _Watchdog_First( &ts->TOD_watchdogs.Header );
180      delta_interval = first_watchdog->delta_interval;
181      if ( snapshot > last_snapshot ) {
182        /*
183         *  We advanced in time.
184         */
185        delta = snapshot - last_snapshot;
186        if (delta_interval > delta) {
187          delta_interval -= delta;
188        } else {
189          delta_interval = 0;
190        }
191      } else {
192        /*
193         *  Someone put us in the past.
194         */
195        delta = last_snapshot - snapshot;
196        delta_interval += delta;
197      }
198      first_watchdog->delta_interval = delta_interval;
199    }
200    ts->TOD_watchdogs.last_snapshot = snapshot;
201    _ISR_Enable( level );
202
203    _Watchdog_Insert( &ts->TOD_watchdogs.Header, &timer->Ticker );
204
205    if ( !ts->active ) {
206      _Timer_server_Reset_tod_system_watchdog( ts );
207    }
208  }
209
210  _Thread_Enable_dispatch();
211}
212
213static void _Timer_server_Schedule_operation_method(
214  Timer_server_Control *ts,
215  Timer_Control *timer
216)
217{
218  if ( ts->insert_chain == NULL ) {
219    _Timer_server_Insert_timer_and_make_snapshot( ts, timer );
220  } else {
221    /*
222     *  We interrupted a critical section of the timer server.  The timer
223     *  server is not preemptible, so we must be in interrupt context here.  No
224     *  thread dispatch will happen until the timer server finishes its
225     *  critical section.  We have to use the protected chain methods because
226     *  we may be interrupted by a higher priority interrupt.
227     */
228    _Chain_Append( ts->insert_chain, &timer->Object.Node );
229  }
230}
231
232static void _Timer_server_Process_interval_watchdogs(
233  Timer_server_Watchdogs *watchdogs,
234  Chain_Control *fire_chain
235)
236{
237  Watchdog_Interval snapshot = _Watchdog_Ticks_since_boot;
238
239  /*
240   *  We assume adequate unsigned arithmetic here.
241   */
242  Watchdog_Interval delta = snapshot - watchdogs->last_snapshot;
243
244  watchdogs->last_snapshot = snapshot;
245
246  _Watchdog_Adjust_to_chain( &watchdogs->Header, delta, fire_chain );
247}
248
249static void _Timer_server_Process_tod_watchdogs(
250  Timer_server_Watchdogs *watchdogs,
251  Chain_Control *fire_chain
252)
253{
254  Watchdog_Interval snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
255  Watchdog_Interval last_snapshot = watchdogs->last_snapshot;
256  Watchdog_Interval delta;
257
258  /*
259   *  Process the seconds chain.  Start by checking that the Time
260   *  of Day (TOD) has not been set backwards.  If it has then
261   *  we want to adjust the watchdogs->Header to indicate this.
262   */
263  if ( snapshot > last_snapshot ) {
264    /*
265     *  This path is for normal forward movement and cases where the
266     *  TOD has been set forward.
267     */
268    delta = snapshot - last_snapshot;
269    _Watchdog_Adjust_to_chain( &watchdogs->Header, delta, fire_chain );
270
271  } else if ( snapshot < last_snapshot ) {
272     /*
273      *  The current TOD is before the last TOD which indicates that
274      *  TOD has been set backwards.
275      */
276     delta = last_snapshot - snapshot;
277     _Watchdog_Adjust_backward( &watchdogs->Header, delta );
278  }
279
280  watchdogs->last_snapshot = snapshot;
281}
282
283static void _Timer_server_Process_insertions( Timer_server_Control *ts )
284{
285  while ( true ) {
286    Timer_Control *timer = (Timer_Control *) _Chain_Get( ts->insert_chain );
287
288    if ( timer == NULL ) {
289      break;
290    }
291
292    _Timer_server_Insert_timer( ts, timer );
293  }
294}
295
296static void _Timer_server_Get_watchdogs_that_fire_now(
297  Timer_server_Control *ts,
298  Chain_Control *insert_chain,
299  Chain_Control *fire_chain
300)
301{
302  /*
303   *  Afterwards all timer inserts are directed to this chain and the interval
304   *  and TOD chains will be no more modified by other parties.
305   */
306  ts->insert_chain = insert_chain;
307
308  while ( true ) {
309    ISR_Level level;
310
311    /*
312     *  Remove all the watchdogs that need to fire so we can invoke them.
313     */
314    _Timer_server_Process_interval_watchdogs(
315      &ts->Interval_watchdogs,
316      fire_chain
317    );
318    _Timer_server_Process_tod_watchdogs( &ts->TOD_watchdogs, fire_chain );
319
320    /*
321     *  The insertions have to take place here, because they reference the
322     *  current time.  The previous process methods take a snapshot of the
323     *  current time.  In case someone inserts a watchdog with an initial value
324     *  of zero it will be processed in the next iteration of the timer server
325     *  body loop.
326     */
327    _Timer_server_Process_insertions( ts );
328
329    _ISR_Disable( level );
330    if ( _Chain_Is_empty( insert_chain ) ) {
331      ts->insert_chain = NULL;
332      _ISR_Enable( level );
333
334      break;
335    } else {
336      _ISR_Enable( level );
337    }
338  }
339}
340
341/* FIXME: This locking approach for SMP is improvable! */
342
343static void _Timer_server_SMP_lock_aquire( void )
344{
345#if defined( RTEMS_SMP )
346  _Thread_Disable_dispatch();
347#endif
348}
349
350static void _Timer_server_SMP_lock_release( void )
351{
352#if defined( RTEMS_SMP )
353  _Thread_Enable_dispatch();
354#endif
355}
356
357/**
358 *  @brief Timer server body.
359 *
360 *  This is the server for task based timers.  This task executes whenever a
361 *  task-based timer should fire.  It services both "after" and "when" timers.
362 *  It is not created automatically but must be created explicitly by the
363 *  application before task-based timers may be initiated.  The parameter
364 *  @a arg points to the corresponding timer server control block.
365 */
366static rtems_task _Timer_server_Body(
367  rtems_task_argument arg
368)
369{
370  Timer_server_Control *ts = (Timer_server_Control *) arg;
371  Chain_Control insert_chain;
372  Chain_Control fire_chain;
373
374  _Chain_Initialize_empty( &insert_chain );
375  _Chain_Initialize_empty( &fire_chain );
376
377  _Timer_server_SMP_lock_aquire();
378
379  while ( true ) {
380    _Timer_server_Get_watchdogs_that_fire_now( ts, &insert_chain, &fire_chain );
381
382    if ( !_Chain_Is_empty( &fire_chain ) ) {
383      /*
384       *  Fire the watchdogs.
385       */
386      while ( true ) {
387        Watchdog_Control *watchdog;
388        ISR_Level level;
389
390        /*
391         *  It is essential that interrupts are disable here since an interrupt
392         *  service routine may remove a watchdog from the chain.
393         */
394        _ISR_Disable( level );
395        watchdog = (Watchdog_Control *) _Chain_Get_unprotected( &fire_chain );
396        if ( watchdog != NULL ) {
397          watchdog->state = WATCHDOG_INACTIVE;
398          _ISR_Enable( level );
399        } else {
400          _ISR_Enable( level );
401
402          break;
403        }
404
405        _Timer_server_SMP_lock_release();
406
407        /*
408         *  The timer server may block here and wait for resources or time.
409         *  The system watchdogs are inactive and will remain inactive since
410         *  the active flag of the timer server is true.
411         */
412        (*watchdog->routine)( watchdog->id, watchdog->user_data );
413
414        _Timer_server_SMP_lock_aquire();
415      }
416    } else {
417      ts->active = false;
418
419      /*
420       *  Block until there is something to do.
421       */
422#if !defined( RTEMS_SMP )
423      _Thread_Disable_dispatch();
424#endif
425        _Thread_Set_state( ts->thread, STATES_DELAYING );
426        _Timer_server_Reset_interval_system_watchdog( ts );
427        _Timer_server_Reset_tod_system_watchdog( ts );
428#if !defined( RTEMS_SMP )
429      _Thread_Enable_dispatch();
430#endif
431
432      _Timer_server_SMP_lock_release();
433      _Timer_server_SMP_lock_aquire();
434
435      ts->active = true;
436
437      /*
438       *  Maybe an interrupt did reset the system timers, so we have to stop
439       *  them here.  Since we are active now, there will be no more resets
440       *  until we are inactive again.
441       */
442      _Timer_server_Stop_interval_system_watchdog( ts );
443      _Timer_server_Stop_tod_system_watchdog( ts );
444    }
445  }
446}
447
448/**
449 *  @brief rtems_timer_initiate_server
450 *
451 *  This directive creates and starts the server for task-based timers.
452 *  It must be invoked before any task-based timers can be initiated.
453 *
454 *  @param[in] priority is the timer server priority
455 *  @param[in] stack_size is the stack size in bytes
456 *  @param[in] attribute_set is the timer server attributes
457 *
458 *  @return This method returns RTEMS_SUCCESSFUL if successful and an
459 *          error code otherwise.
460 */
461rtems_status_code rtems_timer_initiate_server(
462  uint32_t             priority,
463  uint32_t             stack_size,
464  rtems_attribute      attribute_set
465)
466{
467  rtems_id              id;
468  rtems_status_code     status;
469  rtems_task_priority   _priority;
470  static bool           initialized = false;
471  bool                  tmpInitialized;
472  Timer_server_Control *ts = &_Timer_server_Default;
473
474  /*
475   *  Make sure the requested priority is valid.  The if is
476   *  structured so we check it is invalid before looking for
477   *  a specific invalid value as the default.
478   */
479  _priority = priority;
480  if ( !_RTEMS_tasks_Priority_is_valid( priority ) ) {
481    if ( priority != RTEMS_TIMER_SERVER_DEFAULT_PRIORITY )
482      return RTEMS_INVALID_PRIORITY;
483    _priority = PRIORITY_PSEUDO_ISR;
484  }
485
486  /*
487   *  Just to make sure this is only called once.
488   */
489  _Thread_Disable_dispatch();
490    tmpInitialized  = initialized;
491    initialized = true;
492  _Thread_Enable_dispatch();
493
494  if ( tmpInitialized )
495    return RTEMS_INCORRECT_STATE;
496
497  /*
498   *  Create the Timer Server with the name the name of "TIME".  The attribute
499   *  RTEMS_SYSTEM_TASK allows us to set a priority to 0 which will makes it
500   *  higher than any other task in the system.  It can be viewed as a low
501   *  priority interrupt.  It is also always NO_PREEMPT so it looks like
502   *  an interrupt to other tasks.
503   *
504   *  We allow the user to override the default priority because the Timer
505   *  Server can invoke TSRs which must adhere to language run-time or
506   *  other library rules.  For example, if using a TSR written in Ada the
507   *  Server should run at the same priority as the priority Ada task.
508   *  Otherwise, the priority ceiling for the mutex used to protect the
509   *  GNAT run-time is violated.
510   */
511  status = rtems_task_create(
512    _Objects_Build_name('T','I','M','E'),           /* "TIME" */
513    _priority,            /* create with priority 1 since 0 is illegal */
514    stack_size,           /* let user specify stack size */
515    rtems_configuration_is_smp_enabled() ?
516      RTEMS_DEFAULT_MODES : /* no preempt is not supported for SMP */
517      RTEMS_NO_PREEMPT,   /* no preempt is like an interrupt */
518                          /* user may want floating point but we need */
519                          /*   system task specified for 0 priority */
520    attribute_set | RTEMS_SYSTEM_TASK,
521    &id                   /* get the id back */
522  );
523  if (status) {
524    initialized = false;
525    return status;
526  }
527
528  /*
529   *  Do all the data structure initialization before starting the
530   *  Timer Server so we do not have to have a critical section.
531   */
532
533  /*
534   *  We work with the TCB pointer, not the ID, so we need to convert
535   *  to a TCB pointer from here out.
536   */
537  ts->thread = (Thread_Control *)_Objects_Get_local_object(
538    &_RTEMS_tasks_Information,
539    _Objects_Get_index(id)
540  );
541
542  /*
543   *  Initialize the timer lists that the server will manage.
544   */
545  _Watchdog_Header_initialize( &ts->Interval_watchdogs.Header );
546  _Watchdog_Header_initialize( &ts->TOD_watchdogs.Header );
547
548  /*
549   *  Initialize the timers that will be used to control when the
550   *  Timer Server wakes up and services the task-based timers.
551   */
552  _Watchdog_Initialize(
553    &ts->Interval_watchdogs.System_watchdog,
554    _Thread_Delay_ended,
555    0,
556    ts->thread
557  );
558  _Watchdog_Initialize(
559    &ts->TOD_watchdogs.System_watchdog,
560    _Thread_Delay_ended,
561    0,
562    ts->thread
563  );
564
565  /*
566   *  Initialize the pointer to the timer schedule method so applications that
567   *  do not use the Timer Server do not have to pull it in.
568   */
569  ts->schedule_operation = _Timer_server_Schedule_operation_method;
570
571  ts->Interval_watchdogs.last_snapshot = _Watchdog_Ticks_since_boot;
572  ts->TOD_watchdogs.last_snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
573
574  ts->insert_chain = NULL;
575  ts->active = false;
576
577  /*
578   * The default timer server is now available.
579   */
580  _Timer_server = ts;
581
582  /*
583   *  Start the timer server
584   */
585  status = rtems_task_start(
586    id,
587    _Timer_server_Body,
588    (rtems_task_argument) ts
589  );
590
591  #if defined(RTEMS_DEBUG)
592    /*
593     *  One would expect a call to rtems_task_delete() here to clean up
594     *  but there is actually no way (in normal circumstances) that the
595     *  start can fail.  The id and starting address are known to be
596     *  be good.  If this service fails, something is weirdly wrong on the
597     *  target such as a stray write in an ISR or incorrect memory layout.
598     */
599    if (status) {
600      initialized = false;
601    }
602  #endif
603
604  return status;
605}
Note: See TracBrowser for help on using the repository browser.