source: rtems/cpukit/rtems/src/timerserver.c @ 34db8ec

4.115
Last change on this file since 34db8ec was 34db8ec, checked in by Sebastian Huber <sebastian.huber@…>, on 08/27/14 at 12:06:10

rtems: SMP fix for timer server

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