/** * @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-2015 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.org/license/LICENSE. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include static Timer_server_Control _Timer_server_Default; static void _Timer_server_Cancel_method( Timer_server_Control *ts, Timer_Control *timer ) { if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) { _Watchdog_Remove( &ts->Interval_watchdogs.Header, &timer->Ticker ); } else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) { _Watchdog_Remove( &ts->TOD_watchdogs.Header, &timer->Ticker ); } } static Watchdog_Interval _Timer_server_Get_ticks( void ) { return _Watchdog_Ticks_since_boot; } static Watchdog_Interval _Timer_server_Get_seconds( void ) { return _TOD_Seconds_since_epoch(); } static void _Timer_server_Update_system_watchdog( Timer_server_Watchdogs *watchdogs, Watchdog_Header *system_header ) { ISR_lock_Context lock_context; _Watchdog_Acquire( &watchdogs->Header, &lock_context ); if ( watchdogs->system_watchdog_helper == NULL ) { Thread_Control *executing; uint32_t my_generation; executing = _Thread_Executing; watchdogs->system_watchdog_helper = executing; do { my_generation = watchdogs->generation; if ( !_Watchdog_Is_empty( &watchdogs->Header ) ) { Watchdog_Control *first; Watchdog_Interval delta; first = _Watchdog_First( &watchdogs->Header ); delta = first->delta_interval; if ( watchdogs->System_watchdog.state == WATCHDOG_INACTIVE || delta != watchdogs->system_watchdog_delta ) { watchdogs->system_watchdog_delta = delta; _Watchdog_Release( &watchdogs->Header, &lock_context ); _Watchdog_Remove( system_header, &watchdogs->System_watchdog ); watchdogs->System_watchdog.initial = delta; _Watchdog_Insert( system_header, &watchdogs->System_watchdog ); _Watchdog_Acquire( &watchdogs->Header, &lock_context ); } } } while ( watchdogs->generation != my_generation ); watchdogs->system_watchdog_helper = NULL; } _Watchdog_Release( &watchdogs->Header, &lock_context ); } static void _Timer_server_Insert_timer( Timer_server_Watchdogs *watchdogs, Timer_Control *timer, Watchdog_Header *system_header, Watchdog_Interval (*get_ticks)( void ) ) { ISR_lock_Context lock_context; Watchdog_Interval now; Watchdog_Interval delta; _Watchdog_Acquire( &watchdogs->Header, &lock_context ); now = (*get_ticks)(); delta = now - watchdogs->last_snapshot; watchdogs->last_snapshot = now; watchdogs->current_snapshot = now; if ( watchdogs->system_watchdog_delta > delta ) { watchdogs->system_watchdog_delta -= delta; } else { watchdogs->system_watchdog_delta = 0; } if ( !_Watchdog_Is_empty( &watchdogs->Header ) ) { Watchdog_Control *first = _Watchdog_First( &watchdogs->Header ); if ( first->delta_interval > delta ) { first->delta_interval -= delta; } else { first->delta_interval = 0; } } _Watchdog_Insert_locked( &watchdogs->Header, &timer->Ticker, &lock_context ); ++watchdogs->generation; _Watchdog_Release( &watchdogs->Header, &lock_context ); _Timer_server_Update_system_watchdog( watchdogs, system_header ); } static void _Timer_server_Schedule_operation_method( Timer_server_Control *ts, Timer_Control *timer ) { if ( timer->the_class == TIMER_INTERVAL_ON_TASK ) { _Timer_server_Insert_timer( &ts->Interval_watchdogs, timer, &_Watchdog_Ticks_header, _Timer_server_Get_ticks ); } else if ( timer->the_class == TIMER_TIME_OF_DAY_ON_TASK ) { _Timer_server_Insert_timer( &ts->TOD_watchdogs, timer, &_Watchdog_Seconds_header, _Timer_server_Get_seconds ); } } static void _Timer_server_Update_current_snapshot( Timer_server_Watchdogs *watchdogs, Watchdog_Interval (*get_ticks)( void ) ) { ISR_lock_Context lock_context; _Watchdog_Acquire( &watchdogs->Header, &lock_context ); watchdogs->current_snapshot = (*get_ticks)(); watchdogs->system_watchdog_delta = 0; _Watchdog_Release( &watchdogs->Header, &lock_context ); } static void _Timer_server_Tickle( Timer_server_Watchdogs *watchdogs, Watchdog_Header *system_header, Watchdog_Interval (*get_ticks)( void ), bool ticks ) { ISR_lock_Context lock_context; Watchdog_Interval now; Watchdog_Interval last; _Watchdog_Acquire( &watchdogs->Header, &lock_context ); now = watchdogs->current_snapshot; last = watchdogs->last_snapshot; watchdogs->last_snapshot = now; if ( ticks || now >= last ) { _Watchdog_Adjust_forward_locked( &watchdogs->Header, now - last, &lock_context ); } else { _Watchdog_Adjust_backward_locked( &watchdogs->Header, last - now ); } ++watchdogs->generation; _Watchdog_Release( &watchdogs->Header, &lock_context ); _Timer_server_Update_system_watchdog( watchdogs, system_header ); } /** * @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; while ( true ) { rtems_event_set events; _Timer_server_Tickle( &ts->Interval_watchdogs, &_Watchdog_Ticks_header, _Timer_server_Get_ticks, true ); _Timer_server_Tickle( &ts->TOD_watchdogs, &_Watchdog_Seconds_header, _Timer_server_Get_seconds, false ); (void) rtems_event_system_receive( RTEMS_EVENT_SYSTEM_TIMER_SERVER, RTEMS_EVENT_ALL | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events ); } } static void _Timer_server_Wakeup( Objects_Id id, void *arg ) { Timer_server_Control *ts = arg; _Timer_server_Update_current_snapshot( &ts->Interval_watchdogs, _Timer_server_Get_ticks ); _Timer_server_Update_current_snapshot( &ts->TOD_watchdogs, _Timer_server_Get_seconds ); (void) rtems_event_system_send( id, RTEMS_EVENT_SYSTEM_TIMER_SERVER ); } static void _Timer_server_Initialize_watchdogs( Timer_server_Control *ts, rtems_id id, Timer_server_Watchdogs *watchdogs, Watchdog_Interval (*get_ticks)( void ) ) { Watchdog_Interval now; now = (*get_ticks)(); watchdogs->last_snapshot = now; watchdogs->current_snapshot = now; _Watchdog_Header_initialize( &watchdogs->Header ); _Watchdog_Initialize( &watchdogs->System_watchdog, _Timer_server_Wakeup, id, 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 = PRIORITY_PSEUDO_ISR; } /* * Just to make sure this is only called once. */ _Once_Lock(); tmpInitialized = initialized; initialized = true; _Once_Unlock(); 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_configuration_is_smp_enabled() ? RTEMS_DEFAULT_MODES : /* no preempt is not supported for SMP */ 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. */ _Timer_server_Initialize_watchdogs( ts, id, &ts->Interval_watchdogs, _Timer_server_Get_ticks ); _Timer_server_Initialize_watchdogs( ts, id, &ts->TOD_watchdogs, _Timer_server_Get_seconds ); /* * Initialize the pointer to the timer server methods so applications that * do not use the Timer Server do not have to pull it in. */ ts->cancel = _Timer_server_Cancel_method; ts->schedule_operation = _Timer_server_Schedule_operation_method; /* * 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; }