/* gsm_timer.cpp * * Copyright (C) 2012 Ivan Klyuchnikov * * 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. */ /* These store the amount of frame number that we wait until next timer expires. */ static int nearest; static int *nearest_p; /*! \addtogroup gsm_timer * @{ */ /*! \file gsm_timer.cpp */ #include #include #include #include #include #include static struct rb_root timer_root = RB_ROOT; /* * TODO: make this depend on the BTS. This means that * all time functions schedule based on the BTS they * are scheduled on. */ static int get_current_fn() { return BTS::main_bts()->current_frame_number(); } static void __add_gsm_timer(struct osmo_gsm_timer_list *timer) { struct rb_node **new_node = &(timer_root.rb_node); struct rb_node *parent = NULL; while (*new_node) { struct osmo_gsm_timer_list *this_timer; this_timer = container_of(*new_node, struct osmo_gsm_timer_list, node); parent = *new_node; if (timer->fn < this_timer->fn) new_node = &((*new_node)->rb_left); else new_node = &((*new_node)->rb_right); } rb_link_node(&timer->node, parent, new_node); rb_insert_color(&timer->node, &timer_root); } /*! \brief add a new timer to the timer management * \param[in] timer the timer that should be added */ void osmo_gsm_timer_add(struct osmo_gsm_timer_list *timer) { osmo_gsm_timer_del(timer); timer->active = 1; INIT_LLIST_HEAD(&timer->list); __add_gsm_timer(timer); } /*! \brief schedule a gsm timer at a given future relative time * \param[in] timer the to-be-added timer * \param[in] number of frames from now * * This function can be used to (re-)schedule a given timer at a * specified number of frames in the future. It will * internally add it to the timer management data structures, thus * osmo_timer_add() is automatically called. */ void osmo_gsm_timer_schedule(struct osmo_gsm_timer_list *timer, int fn) { int current_fn; current_fn = get_current_fn(); timer->fn = current_fn + fn; osmo_gsm_timer_add(timer); } /*! \brief delete a gsm timer from timer management * \param[in] timer the to-be-deleted timer * * This function can be used to delete a previously added/scheduled * timer from the timer management code. */ void osmo_gsm_timer_del(struct osmo_gsm_timer_list *timer) { if (timer->active) { timer->active = 0; rb_erase(&timer->node, &timer_root); /* make sure this is not already scheduled for removal. */ if (!llist_empty(&timer->list)) llist_del_init(&timer->list); } } /*! \brief check if given timer is still pending * \param[in] timer the to-be-checked timer * \return 1 if pending, 0 otherwise * * This function can be used to determine whether a given timer * has alredy expired (returns 0) or is still pending (returns 1) */ int osmo_gsm_timer_pending(struct osmo_gsm_timer_list *timer) { return timer->active; } /* * if we have a nearest frame number return the delta between the current * FN and the FN of the nearest timer. * If the nearest timer timed out return NULL and then we will * dispatch everything after the select */ int *osmo_gsm_timers_nearest(void) { /* nearest_p is exactly what we need already: NULL if nothing is * waiting, {0,0} if we must dispatch immediately, and the correct * delay if we need to wait */ return nearest_p; } static void update_nearest(int *cand, int *current) { if (*cand != LONG_MAX) { if (*cand > *current) nearest = *cand - *current; else { /* loop again inmediately */ nearest = 0; } nearest_p = &nearest; } else { nearest_p = NULL; } } /* * Find the nearest FN and update s_nearest_time */ void osmo_gsm_timers_prepare(void) { struct rb_node *node; int current_fn; current_fn = get_current_fn(); node = rb_first(&timer_root); if (node) { struct osmo_gsm_timer_list *this_timer; this_timer = container_of(node, struct osmo_gsm_timer_list, node); update_nearest(&this_timer->fn, ¤t_fn); } else { nearest_p = NULL; } } /* * fire all timers... and remove them */ int osmo_gsm_timers_update(void) { int current_fn; struct rb_node *node; struct llist_head timer_eviction_list; struct osmo_gsm_timer_list *this_timer; int work = 0; current_fn = get_current_fn(); INIT_LLIST_HEAD(&timer_eviction_list); for (node = rb_first(&timer_root); node; node = rb_next(node)) { this_timer = container_of(node, struct osmo_gsm_timer_list, node); if (this_timer->fn > current_fn) break; llist_add(&this_timer->list, &timer_eviction_list); } /* * The callbacks might mess with our list and in this case * even llist_for_each_entry_safe is not safe to use. To allow * osmo_gsm_timer_del to be called from within the callback we need * to restart the iteration for each element scheduled for removal. * * The problematic scenario is the following: Given two timers A * and B that have expired at the same time. Thus, they are both * in the eviction list in this order: A, then B. If we remove * timer B from the A's callback, we continue with B in the next * iteration step, leading to an access-after-release. */ restart: llist_for_each_entry(this_timer, &timer_eviction_list, list) { osmo_gsm_timer_del(this_timer); this_timer->cb(this_timer->data); work = 1; goto restart; } return work; } int osmo_gsm_timers_check(void) { struct rb_node *node; int i = 0; for (node = rb_first(&timer_root); node; node = rb_next(node)) { i++; } return i; } /*! }@ */