/**
* @file timerserver.c
*
* Timer Manager - rtems_timer_initiate_server directive along with
* the Timer Server Body and support routines
*
* @note Data specific to the Timer Server is declared in this
* file as the Timer Server so it does not have to be in the
* minimum footprint. It is only really required when
* task-based timers are used. Since task-based timers can
* not be started until the server is initiated, this structure
* does not have to be initialized until then.
*/
/* COPYRIGHT (c) 1989-2008.
* On-Line Applications Research Corporation (OAR).
*
* Copyright (c) 2009 embedded brains GmbH.
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rtems.com/license/LICENSE.
*
* $Id$
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <rtems/system.h>
#include <rtems/rtems/status.h>
#include <rtems/rtems/support.h>
#include <rtems/score/object.h>
#include <rtems/score/thread.h>
#include <rtems/rtems/timer.h>
#include <rtems/score/tod.h>
#include <rtems/score/watchdog.h>
#include <rtems/rtems/tasks.h>
#include <rtems/rtems/support.h>
#include <rtems/score/thread.h>
static Timer_server_Control _Timer_server_Default;
static void _Timer_server_Stop_interval_system_watchdog(
Timer_server_Control *ts
)
{
_Watchdog_Remove( &ts->Interval_watchdogs.System_watchdog );
}
static void _Timer_server_Reset_interval_system_watchdog(
Timer_server_Control *ts
)
{
ISR_Level level;
_Timer_server_Stop_interval_system_watchdog( ts );
_ISR_Disable( level );
if ( !_Chain_Is_empty( &ts->Interval_watchdogs.Chain ) ) {
Watchdog_Interval delta_interval =
_Watchdog_First( &ts->Interval_watchdogs.Chain )->delta_interval;
_ISR_Enable( level );
/*
* The unit is TICKS here.
*/
_Watchdog_Insert_ticks(
&ts->Interval_watchdogs.System_watchdog,
delta_interval
);
} else {
_ISR_Enable( level );
}
}
static void _Timer_server_Stop_tod_system_watchdog(
Timer_server_Control *ts
)
{
_Watchdog_Remove( &ts->TOD_watchdogs.System_watchdog );
}
static void _Timer_server_Reset_tod_system_watchdog(
Timer_server_Control *ts
)
{
ISR_Level level;
_Timer_server_Stop_tod_system_watchdog( ts );
_ISR_Disable( level );
if ( !_Chain_Is_empty( &ts->TOD_watchdogs.Chain ) ) {
Watchdog_Interval delta_interval =
_Watchdog_First( &ts->TOD_watchdogs.Chain )->delta_interval;
_ISR_Enable( level );
/*
* The unit is SECONDS here.
*/
_Watchdog_Insert_seconds(
&ts->TOD_watchdogs.System_watchdog,
delta_interval
);
} else {
_ISR_Enable( level );
}
}
static void _Timer_server_Insert_timer(
Timer_server_Control *ts,
Timer_Control *timer
)
{
if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) {
_Watchdog_Insert( &ts->Interval_watchdogs.Chain, &timer->Ticker );
} else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) {
_Watchdog_Insert( &ts->TOD_watchdogs.Chain, &timer->Ticker );
}
}
static void _Timer_server_Insert_timer_and_make_snapshot(
Timer_server_Control *ts,
Timer_Control *timer
)
{
Watchdog_Control *first_watchdog;
Watchdog_Interval delta_interval;
Watchdog_Interval last_snapshot;
Watchdog_Interval snapshot;
Watchdog_Interval delta;
ISR_Level level;
/*
* We have to update the time snapshots here, because otherwise we may have
* problems with the integer range of the delta values. The time delta DT
* from the last snapshot to now may be arbitrarily long. The last snapshot
* is the reference point for the delta chain. Thus if we do not update the
* reference point we have to add DT to the initial delta of the watchdog
* being inserted. This could result in an integer overflow.
*/
_Thread_Disable_dispatch();
if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) {
/*
* We have to advance the last known ticks value of the server and update
* the watchdog chain accordingly.
*/
_ISR_Disable( level );
snapshot = _Watchdog_Ticks_since_boot;
last_snapshot = ts->Interval_watchdogs.last_snapshot;
if ( !_Chain_Is_empty( &ts->Interval_watchdogs.Chain ) ) {
first_watchdog = _Watchdog_First( &ts->Interval_watchdogs.Chain );
/*
* We assume adequate unsigned arithmetic here.
*/
delta = snapshot - last_snapshot;
delta_interval = first_watchdog->delta_interval;
if (delta_interval > delta) {
delta_interval -= delta;
} else {
delta_interval = 0;
}
first_watchdog->delta_interval = delta_interval;
}
ts->Interval_watchdogs.last_snapshot = snapshot;
_ISR_Enable( level );
_Watchdog_Insert( &ts->Interval_watchdogs.Chain, &timer->Ticker );
if ( !ts->active ) {
_Timer_server_Reset_interval_system_watchdog( ts );
}
} else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) {
/*
* We have to advance the last known seconds value of the server and update
* the watchdog chain accordingly.
*/
_ISR_Disable( level );
snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
last_snapshot = ts->TOD_watchdogs.last_snapshot;
if ( !_Chain_Is_empty( &ts->TOD_watchdogs.Chain ) ) {
first_watchdog = _Watchdog_First( &ts->TOD_watchdogs.Chain );
delta_interval = first_watchdog->delta_interval;
if ( snapshot > last_snapshot ) {
/*
* We advanced in time.
*/
delta = snapshot - last_snapshot;
if (delta_interval > delta) {
delta_interval -= delta;
} else {
delta_interval = 0;
}
} else {
/*
* Someone put us in the past.
*/
delta = last_snapshot - snapshot;
delta_interval += delta;
}
first_watchdog->delta_interval = delta_interval;
}
ts->TOD_watchdogs.last_snapshot = snapshot;
_ISR_Enable( level );
_Watchdog_Insert( &ts->TOD_watchdogs.Chain, &timer->Ticker );
if ( !ts->active ) {
_Timer_server_Reset_tod_system_watchdog( ts );
}
}
_Thread_Enable_dispatch();
}
static void _Timer_server_Schedule_operation_method(
Timer_server_Control *ts,
Timer_Control *timer
)
{
if ( ts->insert_chain == NULL ) {
_Timer_server_Insert_timer_and_make_snapshot( ts, timer );
} else {
/*
* We interrupted a critical section of the timer server. The timer
* server is not preemptible, so we must be in interrupt context here. No
* thread dispatch will happen until the timer server finishes its
* critical section. We have to use the protected chain methods because
* we may be interrupted by a higher priority interrupt.
*/
_Chain_Append( ts->insert_chain, &timer->Object.Node );
}
}
static void _Timer_server_Process_interval_watchdogs(
Timer_server_Watchdogs *watchdogs,
Chain_Control *fire_chain
)
{
Watchdog_Interval snapshot = _Watchdog_Ticks_since_boot;
/*
* We assume adequate unsigned arithmetic here.
*/
Watchdog_Interval delta = snapshot - watchdogs->last_snapshot;
watchdogs->last_snapshot = snapshot;
_Watchdog_Adjust_to_chain( &watchdogs->Chain, delta, fire_chain );
}
static void _Timer_server_Process_tod_watchdogs(
Timer_server_Watchdogs *watchdogs,
Chain_Control *fire_chain
)
{
Watchdog_Interval snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
Watchdog_Interval last_snapshot = watchdogs->last_snapshot;
Watchdog_Interval delta;
/*
* Process the seconds chain. Start by checking that the Time
* of Day (TOD) has not been set backwards. If it has then
* we want to adjust the watchdogs->Chain to indicate this.
*/
if ( snapshot > last_snapshot ) {
/*
* This path is for normal forward movement and cases where the
* TOD has been set forward.
*/
delta = snapshot - last_snapshot;
_Watchdog_Adjust_to_chain( &watchdogs->Chain, delta, fire_chain );
} else if ( snapshot < last_snapshot ) {
/*
* The current TOD is before the last TOD which indicates that
* TOD has been set backwards.
*/
delta = last_snapshot - snapshot;
_Watchdog_Adjust( &watchdogs->Chain, WATCHDOG_BACKWARD, delta );
}
watchdogs->last_snapshot = snapshot;
}
static void _Timer_server_Process_insertions( Timer_server_Control *ts )
{
while ( true ) {
Timer_Control *timer = (Timer_Control *) _Chain_Get( ts->insert_chain );
if ( timer == NULL ) {
break;
}
_Timer_server_Insert_timer( ts, timer );
}
}
static void _Timer_server_Get_watchdogs_that_fire_now(
Timer_server_Control *ts,
Chain_Control *insert_chain,
Chain_Control *fire_chain
)
{
/*
* Afterwards all timer inserts are directed to this chain and the interval
* and TOD chains will be no more modified by other parties.
*/
ts->insert_chain = insert_chain;
while ( true ) {
ISR_Level level;
/*
* Remove all the watchdogs that need to fire so we can invoke them.
*/
_Timer_server_Process_interval_watchdogs(
&ts->Interval_watchdogs,
fire_chain
);
_Timer_server_Process_tod_watchdogs( &ts->TOD_watchdogs, fire_chain );
/*
* The insertions have to take place here, because they reference the
* current time. The previous process methods take a snapshot of the
* current time. In case someone inserts a watchdog with an initial value
* of zero it will be processed in the next iteration of the timer server
* body loop.
*/
_Timer_server_Process_insertions( ts );
_ISR_Disable( level );
if ( _Chain_Is_empty( insert_chain ) ) {
ts->insert_chain = NULL;
_ISR_Enable( level );
break;
} else {
_ISR_Enable( level );
}
}
}
/**
* @brief Timer server body.
*
* This is the server for task based timers. This task executes whenever a
* task-based timer should fire. It services both "after" and "when" timers.
* It is not created automatically but must be created explicitly by the
* application before task-based timers may be initiated. The parameter
* @a arg points to the corresponding timer server control block.
*/
static rtems_task _Timer_server_Body(
rtems_task_argument arg
)
{
Timer_server_Control *ts = (Timer_server_Control *) arg;
Chain_Control insert_chain;
Chain_Control fire_chain;
_Chain_Initialize_empty( &insert_chain );
_Chain_Initialize_empty( &fire_chain );
while ( true ) {
_Timer_server_Get_watchdogs_that_fire_now( ts, &insert_chain, &fire_chain );
if ( !_Chain_Is_empty( &fire_chain ) ) {
/*
* Fire the watchdogs.
*/
while ( true ) {
Watchdog_Control *watchdog;
ISR_Level level;
/*
* It is essential that interrupts are disable here since an interrupt
* service routine may remove a watchdog from the chain.
*/
_ISR_Disable( level );
watchdog = (Watchdog_Control *) _Chain_Get_unprotected( &fire_chain );
if ( watchdog != NULL ) {
watchdog->state = WATCHDOG_INACTIVE;
_ISR_Enable( level );
} else {
_ISR_Enable( level );
break;
}
/*
* The timer server may block here and wait for resources or time.
* The system watchdogs are inactive and will remain inactive since
* the active flag of the timer server is true.
*/
(*watchdog->routine)( watchdog->id, watchdog->user_data );
}
} else {
ts->active = false;
/*
* Block until there is something to do.
*/
_Thread_Disable_dispatch();
_Thread_Set_state( ts->thread, STATES_DELAYING );
_Timer_server_Reset_interval_system_watchdog( ts );
_Timer_server_Reset_tod_system_watchdog( ts );
_Thread_Enable_dispatch();
ts->active = true;
/*
* Maybe an interrupt did reset the system timers, so we have to stop
* them here. Since we are active now, there will be no more resets
* until we are inactive again.
*/
_Timer_server_Stop_interval_system_watchdog( ts );
_Timer_server_Stop_tod_system_watchdog( ts );
}
}
}
/**
* @brief rtems_timer_initiate_server
*
* This directive creates and starts the server for task-based timers.
* It must be invoked before any task-based timers can be initiated.
*
* @param[in] priority is the timer server priority
* @param[in] stack_size is the stack size in bytes
* @param[in] attribute_set is the timer server attributes
*
* @return This method returns RTEMS_SUCCESSFUL if successful and an
* error code otherwise.
*/
rtems_status_code rtems_timer_initiate_server(
uint32_t priority,
uint32_t stack_size,
rtems_attribute attribute_set
)
{
rtems_id id;
rtems_status_code status;
rtems_task_priority _priority;
static bool initialized = false;
bool tmpInitialized;
Timer_server_Control *ts = &_Timer_server_Default;
/*
* Make sure the requested priority is valid. The if is
* structured so we check it is invalid before looking for
* a specific invalid value as the default.
*/
_priority = priority;
if ( !_RTEMS_tasks_Priority_is_valid( priority ) ) {
if ( priority != RTEMS_TIMER_SERVER_DEFAULT_PRIORITY )
return RTEMS_INVALID_PRIORITY;
_priority = 0;
}
/*
* Just to make sure this is only called once.
*/
_Thread_Disable_dispatch();
tmpInitialized = initialized;
initialized = true;
_Thread_Enable_dispatch();
if ( tmpInitialized )
return RTEMS_INCORRECT_STATE;
/*
* Create the Timer Server with the name the name of "TIME". The attribute
* RTEMS_SYSTEM_TASK allows us to set a priority to 0 which will makes it
* higher than any other task in the system. It can be viewed as a low
* priority interrupt. It is also always NO_PREEMPT so it looks like
* an interrupt to other tasks.
*
* We allow the user to override the default priority because the Timer
* Server can invoke TSRs which must adhere to language run-time or
* other library rules. For example, if using a TSR written in Ada the
* Server should run at the same priority as the priority Ada task.
* Otherwise, the priority ceiling for the mutex used to protect the
* GNAT run-time is violated.
*/
status = rtems_task_create(
_Objects_Build_name('T','I','M','E'), /* "TIME" */
_priority, /* create with priority 1 since 0 is illegal */
stack_size, /* let user specify stack size */
RTEMS_NO_PREEMPT, /* no preempt is like an interrupt */
/* user may want floating point but we need */
/* system task specified for 0 priority */
attribute_set | RTEMS_SYSTEM_TASK,
&id /* get the id back */
);
if (status) {
initialized = false;
return status;
}
/*
* Do all the data structure initialization before starting the
* Timer Server so we do not have to have a critical section.
*/
/*
* We work with the TCB pointer, not the ID, so we need to convert
* to a TCB pointer from here out.
*/
ts->thread = (Thread_Control *)_Objects_Get_local_object(
&_RTEMS_tasks_Information,
_Objects_Get_index(id)
);
/*
* Initialize the timer lists that the server will manage.
*/
_Chain_Initialize_empty( &ts->Interval_watchdogs.Chain );
_Chain_Initialize_empty( &ts->TOD_watchdogs.Chain );
/*
* Initialize the timers that will be used to control when the
* Timer Server wakes up and services the task-based timers.
*/
_Watchdog_Initialize(
&ts->Interval_watchdogs.System_watchdog,
_Thread_Delay_ended,
id,
NULL
);
_Watchdog_Initialize(
&ts->TOD_watchdogs.System_watchdog,
_Thread_Delay_ended,
id,
NULL
);
/*
* Initialize the pointer to the timer schedule method so applications that
* do not use the Timer Server do not have to pull it in.
*/
ts->schedule_operation = _Timer_server_Schedule_operation_method;
ts->Interval_watchdogs.last_snapshot = _Watchdog_Ticks_since_boot;
ts->TOD_watchdogs.last_snapshot = (Watchdog_Interval) _TOD_Seconds_since_epoch();
ts->insert_chain = NULL;
ts->active = false;
/*
* The default timer server is now available.
*/
_Timer_server = ts;
/*
* Start the timer server
*/
status = rtems_task_start(
id,
_Timer_server_Body,
(rtems_task_argument) ts
);
#if defined(RTEMS_DEBUG)
/*
* One would expect a call to rtems_task_delete() here to clean up
* but there is actually no way (in normal circumstances) that the
* start can fail. The id and starting address are known to be
* be good. If this service fails, something is weirdly wrong on the
* target such as a stray write in an ISR or incorrect memory layout.
*/
if (status) {
initialized = false;
}
#endif
return status;
}