/* (C) 2018 by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * * Author: Neels Hofmeyr * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include static unsigned int time_now(void) { struct timespec tp; if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp)) return 0; return (unsigned int)tp.tv_sec; } /* Add a penalty timer for a target cell ID. * \param ctx talloc context to allocate new struct penalty_timer from. * \param penalty_timers llist head to add penalty timer to. * \param for_target_cell Which handover target to penalize. * \param timeout Penalty time in seconds. */ void penalty_timers_add(void *ctx, struct llist_head *penalty_timers, const struct gsm0808_cell_id *for_target_cell, int timeout) { struct penalty_timer *timer; unsigned int now; unsigned int then; now = time_now(); if (timeout <= 0) return; then = now + timeout; /* timer already running for that target cell? */ llist_for_each_entry(timer, penalty_timers, entry) { if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; /* raise, if running timer will timeout earlier or has timed * out already, otherwise keep later timeout */ if (timer->timeout < then) timer->timeout = then; return; } /* add new timer */ timer = talloc_zero(ctx, struct penalty_timer); if (!timer) return; timer->for_target_cell = *for_target_cell; timer->timeout = then; llist_add_tail(&timer->entry, penalty_timers); } /* Add a penalty timer for each target cell ID in the given list. * \param ctx talloc context to allocate new struct penalty_timer from. * \param penalty_timers llist head to add penalty timer to. * \param for_target_cells Which handover targets to penalize. * \param timeout Penalty time in seconds. */ void penalty_timers_add_list(void *ctx, struct llist_head *penalty_timers, const struct gsm0808_cell_id_list2 *for_target_cells, int timeout) { int i; for (i = 0; i < for_target_cells->id_list_len; i++) { struct gsm0808_cell_id add = { .id_discr = for_target_cells->id_discr, .id = for_target_cells->id_list[i], }; penalty_timers_add(ctx, penalty_timers, &add, timeout); } } /* Return the amount of penalty time in seconds remaining for a target cell. * \param penalty_timers llist head to look up penalty time in. * \param for_target_cell Which handover target to query. * \returns seconds remaining until all penalty time has expired. */ unsigned int penalty_timers_remaining(struct llist_head *penalty_timers, const struct gsm0808_cell_id *for_target_cell) { struct penalty_timer *timer; unsigned int now = time_now(); unsigned int max_remaining = 0; llist_for_each_entry(timer, penalty_timers, entry) { unsigned int remaining; if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; if (now >= timer->timeout) continue; remaining = timer->timeout - now; if (remaining > max_remaining) max_remaining = remaining; } return max_remaining; } /* Return the largest amount of penalty time in seconds remaining for any one of the given target cells. * Call penalty_timers_remaining() for each entry of for_target_cells and return the largest value encountered. * \param penalty_timers llist head to look up penalty time in. * \param for_target_cells Which handover targets to query. * \returns seconds remaining until all penalty time has expired. */ unsigned int penalty_timers_remaining_list(struct llist_head *penalty_timers, const struct gsm0808_cell_id_list2 *for_target_cells) { int i; unsigned int max_remaining = 0; for (i = 0; i < for_target_cells->id_list_len; i++) { unsigned int remaining; struct gsm0808_cell_id query = { .id_discr = for_target_cells->id_discr, .id = for_target_cells->id_list[i], }; remaining = penalty_timers_remaining(penalty_timers, &query); max_remaining = OSMO_MAX(max_remaining, remaining); } return max_remaining; } /* Clear penalty timers for one target cell, or completely clear the entire list. * \param penalty_timers llist head to add penalty timer to. * \param for_target_cell Which handover target to clear timers for, or NULL to clear all timers. */ void penalty_timers_clear(struct llist_head *penalty_timers, const struct gsm0808_cell_id *for_target_cell) { struct penalty_timer *timer, *timer2; llist_for_each_entry_safe(timer, timer2, penalty_timers, entry) { if (for_target_cell && !gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; llist_del(&timer->entry); talloc_free(timer); } }