356 lines
8.7 KiB
C
356 lines
8.7 KiB
C
|
/* Copyright (C) 2004 David Decotigny
|
||
|
|
||
|
This program is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU General Public License
|
||
|
as published by the Free Software Foundation; either version 2
|
||
|
of the License, or (at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
||
|
USA.
|
||
|
*/
|
||
|
|
||
|
#include <sos/assert.h>
|
||
|
#include <sos/klibc.h>
|
||
|
#include <hwcore/irq.h>
|
||
|
#include <sos/list.h>
|
||
|
|
||
|
#include "time.h"
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Number of nanoseconds in 1 second
|
||
|
*/
|
||
|
#define NS_IN_SEC 1000000000UL
|
||
|
|
||
|
|
||
|
/**
|
||
|
* The list of timeout actions waiting for a timeout. The timeout
|
||
|
* actions are stored in the list in increasing initial timeout
|
||
|
* order. Actually, the "timeout" field won't reflect this initial
|
||
|
* timeout: for each element in the list, it stores the timeout
|
||
|
* _difference_ between the timeout action and the previous in the
|
||
|
* list.
|
||
|
*/
|
||
|
static struct sos_timeout_action *timeout_action_list;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Current resolution of a time tick
|
||
|
*/
|
||
|
static struct sos_time tick_resolution;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Time elapsed between boot and last timer tick
|
||
|
*
|
||
|
* @note No 'volatile' here because the tick value is NEVER modified
|
||
|
* while in any of the functions below: it is modified only out of
|
||
|
* these functions by the IRQ timer handler because these functions
|
||
|
* are protected against timer IRQ and are "one shot" (no busy waiting
|
||
|
* for a change in the tick's value).
|
||
|
*/
|
||
|
static struct sos_time last_tick_time;
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_inc(struct sos_time *dest,
|
||
|
const struct sos_time *to_add)
|
||
|
{
|
||
|
/* nanosec is always < 1e9 so that their sum is always < 2e9, which
|
||
|
is smaller than 2^32-1 */
|
||
|
sos_ui32_t sigma_ns = dest->nanosec + to_add->nanosec;
|
||
|
|
||
|
dest->sec += to_add->sec;
|
||
|
dest->sec += sigma_ns / NS_IN_SEC;
|
||
|
dest->nanosec = sigma_ns % NS_IN_SEC;
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_dec(struct sos_time *dest,
|
||
|
const struct sos_time *to_dec)
|
||
|
{
|
||
|
/* nanosec is always < 1e9 so that their difference is always in
|
||
|
(-1e9, 1e9), which is compatible with the (-2^31, 2^31 - 1)
|
||
|
cpacity of a signed dword */
|
||
|
sos_si32_t diff_ns = ((sos_si32_t)dest->nanosec)
|
||
|
- ((sos_si32_t)to_dec->nanosec);
|
||
|
|
||
|
/* Make sure substraction is possible */
|
||
|
SOS_ASSERT_FATAL(dest->sec >= to_dec->sec);
|
||
|
if (dest->sec == to_dec->sec)
|
||
|
SOS_ASSERT_FATAL(dest->nanosec >= to_dec->nanosec);
|
||
|
|
||
|
dest->sec -= to_dec->sec;
|
||
|
if (diff_ns > 0)
|
||
|
dest->sec += diff_ns / NS_IN_SEC;
|
||
|
else
|
||
|
dest->sec -= ((-diff_ns) / NS_IN_SEC);
|
||
|
dest->nanosec = (NS_IN_SEC + diff_ns) % NS_IN_SEC;
|
||
|
if (diff_ns < 0)
|
||
|
dest->sec --;
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
int sos_time_cmp(const struct sos_time *t1,
|
||
|
const struct sos_time *t2)
|
||
|
{
|
||
|
/* Compare seconds */
|
||
|
if (t1->sec < t2->sec)
|
||
|
return -1;
|
||
|
else if (t1->sec > t2->sec)
|
||
|
return 1;
|
||
|
|
||
|
/* seconds are equal => compare nanoseconds */
|
||
|
else if (t1->nanosec < t2->nanosec)
|
||
|
return -1;
|
||
|
else if (t1->nanosec > t2->nanosec)
|
||
|
return 1;
|
||
|
|
||
|
/* else: sec and nanosecs are equal */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_bool_t sos_time_is_zero(const struct sos_time *tm)
|
||
|
{
|
||
|
return ( (0 == tm->sec) && (0 == tm->nanosec) );
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_subsysem_setup(const struct sos_time *initial_resolution)
|
||
|
{
|
||
|
timeout_action_list = NULL;
|
||
|
last_tick_time = (struct sos_time) { .sec = 0, .nanosec = 0 };
|
||
|
memcpy(& tick_resolution, initial_resolution, sizeof(struct sos_time));
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_get_tick_resolution(struct sos_time *resolution)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
sos_disable_IRQs(flags);
|
||
|
|
||
|
memcpy(resolution, & tick_resolution, sizeof(struct sos_time));
|
||
|
|
||
|
sos_restore_IRQs(flags);
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_set_tick_resolution(const struct sos_time *resolution)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
|
||
|
sos_disable_IRQs(flags);
|
||
|
memcpy(& tick_resolution, resolution, sizeof(struct sos_time));
|
||
|
sos_restore_IRQs(flags);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_get_now(struct sos_time *now)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
sos_disable_IRQs(flags);
|
||
|
|
||
|
memcpy(now, & last_tick_time, sizeof(struct sos_time));
|
||
|
|
||
|
sos_restore_IRQs(flags);
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Helper routine to add the action in the list. MUST be called with
|
||
|
* interrupts disabled !
|
||
|
*/
|
||
|
static sos_ret_t _add_action(struct sos_timeout_action *act,
|
||
|
const struct sos_time *due_date,
|
||
|
sos_bool_t is_relative_due_date,
|
||
|
sos_timeout_routine_t *routine,
|
||
|
void *routine_data)
|
||
|
{
|
||
|
struct sos_timeout_action *other, *insert_before;
|
||
|
int nb_act;
|
||
|
|
||
|
/* Delay must be specified */
|
||
|
if (due_date == NULL)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Action container MUST be specified */
|
||
|
if (act == NULL)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Refuse to add an empty action */
|
||
|
if (NULL == routine)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Refuse to add the action if it is already added */
|
||
|
if (NULL != act->tmo_next)
|
||
|
return -SOS_EBUSY;
|
||
|
|
||
|
/* Compute action absolute due date */
|
||
|
if (is_relative_due_date)
|
||
|
{
|
||
|
/* The provided due_date is relative to the current time */
|
||
|
memcpy(& act->timeout, & last_tick_time, sizeof(struct sos_time));
|
||
|
sos_time_inc(& act->timeout, due_date);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* The provided due_date is absolute (ie relative to the system
|
||
|
boot instant) */
|
||
|
|
||
|
if (sos_time_cmp(due_date, & last_tick_time) < 0)
|
||
|
/* Refuse to add a past action ! */
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
memcpy(& act->timeout, due_date, sizeof(struct sos_time));
|
||
|
}
|
||
|
|
||
|
/* Prepare the action data structure */
|
||
|
act->routine = routine;
|
||
|
act->routine_data = routine_data;
|
||
|
|
||
|
/* Find the right place in the list of the timeout action. */
|
||
|
insert_before = NULL;
|
||
|
list_foreach_forward_named(timeout_action_list,
|
||
|
other, nb_act,
|
||
|
tmo_prev, tmo_next)
|
||
|
{
|
||
|
if (sos_time_cmp(& act->timeout, & other->timeout) < 0)
|
||
|
{
|
||
|
insert_before = other;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Loop over to next timeout */
|
||
|
}
|
||
|
|
||
|
/* Now insert the action in the list */
|
||
|
if (insert_before != NULL)
|
||
|
list_insert_before_named(timeout_action_list, insert_before, act,
|
||
|
tmo_prev, tmo_next);
|
||
|
else
|
||
|
list_add_tail_named(timeout_action_list, act,
|
||
|
tmo_prev, tmo_next);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_time_register_action_relative(struct sos_timeout_action *act,
|
||
|
const struct sos_time *delay,
|
||
|
sos_timeout_routine_t *routine,
|
||
|
void *routine_data)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
sos_ret_t retval;
|
||
|
|
||
|
sos_disable_IRQs(flags);
|
||
|
retval = _add_action(act, delay, TRUE, routine, routine_data);
|
||
|
sos_restore_IRQs(flags);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_time_register_action_absolute(struct sos_timeout_action *act,
|
||
|
const struct sos_time *date,
|
||
|
sos_timeout_routine_t *routine,
|
||
|
void *routine_data)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
sos_ret_t retval;
|
||
|
|
||
|
sos_disable_IRQs(flags);
|
||
|
retval = _add_action(act, date, FALSE, routine, routine_data);
|
||
|
sos_restore_IRQs(flags);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Helper routine to remove the action from the list. MUST be called
|
||
|
* with interrupts disabled !
|
||
|
*/
|
||
|
static sos_ret_t _remove_action(struct sos_timeout_action *act)
|
||
|
{
|
||
|
/* Don't do anything if action is not in timeout list */
|
||
|
if (NULL == act->tmo_next)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Update the action's remaining timeout */
|
||
|
if (sos_time_cmp(& act->timeout, & last_tick_time) <= 0)
|
||
|
act->timeout = (struct sos_time){ .sec=0, .nanosec=0 };
|
||
|
else
|
||
|
sos_time_dec(& act->timeout, & last_tick_time);
|
||
|
|
||
|
/* Actually remove the action from the list */
|
||
|
list_delete_named(timeout_action_list, act,
|
||
|
tmo_prev, tmo_next);
|
||
|
act->tmo_prev = act->tmo_next = NULL;
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_unregister_action(struct sos_timeout_action *act)
|
||
|
{
|
||
|
sos_ret_t retval;
|
||
|
sos_ui32_t flags;
|
||
|
|
||
|
sos_disable_IRQs(flags);
|
||
|
retval = _remove_action(act);
|
||
|
sos_restore_IRQs(flags);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_time_do_tick()
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
|
||
|
sos_disable_IRQs(flags);
|
||
|
|
||
|
/* Update kernel time */
|
||
|
sos_time_inc(& last_tick_time, & tick_resolution);
|
||
|
|
||
|
while (! list_is_empty_named(timeout_action_list, tmo_prev, tmo_next))
|
||
|
{
|
||
|
struct sos_timeout_action *act;
|
||
|
act = list_get_head_named(timeout_action_list, tmo_prev, tmo_next);
|
||
|
|
||
|
/* Did we go too far in the actions' list ? */
|
||
|
if (sos_time_cmp(& last_tick_time, & act->timeout) < 0)
|
||
|
{
|
||
|
/* Yes: No need to look further. */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Remove the action from the list */
|
||
|
_remove_action(act);
|
||
|
|
||
|
/* Call the action's routine */
|
||
|
act->routine(act);
|
||
|
}
|
||
|
|
||
|
sos_restore_IRQs(flags);
|
||
|
return SOS_OK;
|
||
|
}
|