/*
* Ethernet Switch IGMP Snooper
* Copyright (C) 2014 ASUSTeK Inc.
* All Rights Reserved.
* 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
#include
#include
#include
#include
#include
#include "snooper.h"
#include "queue.h"
#ifdef DEBUG_TIMER
#define log_timer(fmt, args...) log_debug("%s::" fmt, "timer", ##args)
#else
#define log_timer(...) do {} while(0)
#endif
struct timer_head;
struct timer_entry;
static struct {
TAILQ_HEAD(timer_head, timer_entry) pending;
unsigned long time;
unsigned long next;
} timers;
#ifndef CLOCK_MONOTONIC
long timer_tps;
#endif
inline void time_to_timeval(unsigned long time, struct timeval *tv)
{
tv->tv_sec = time / TIMER_HZ;
tv->tv_usec = (time % TIMER_HZ) * 1000000L / TIMER_HZ;
}
inline unsigned long timeval_to_time(struct timeval *tv)
{
return (unsigned long) tv->tv_sec * TIMER_HZ +
(unsigned long) tv->tv_usec * TIMER_HZ / 1000000L;
}
unsigned long now(void)
{
unsigned long time;
#ifdef CLOCK_MONOTONIC
struct timespec tp;
if (clock_gettime(CLOCK_MONOTONIC, &tp) < 0)
return -1;
time = (unsigned long) tp.tv_sec * TIMER_HZ + tp.tv_nsec / (1000000000 / TIMER_HZ);
#else
struct tms dummy;
time = (unsigned long) times(&dummy);
#endif
return time;
}
int init_timers(void)
{
#ifndef CLOCK_MONOTONIC
timer_tps = sysconf(_SC_CLK_TCK);
if (timer_tps <= 0)
return -1;
#endif
memset(&timers, 0, sizeof(timers));
TAILQ_INIT(&timers.pending);
timers.time = now();
timers.next = timers.time;
if (timers.time == (unsigned long) -1 && errno != 0) {
log_error("timer: %s", strerror(errno));
return -1;
}
return 0;
}
int timer_pending(struct timer_entry *timer)
{
return timer->link.tqe_prev &&
timer->link.tqe_prev != &TAILQ_NEXT(timer, link);
}
void set_timer(struct timer_entry *timer, void (*func)(struct timer_entry *timer, void *data), void *data)
{
struct timer_entry *entry;
timer->func = func;
timer->data = data;
log_timer("%-6s %p expires %d %s", "set", timer,
time_diff(timer->expires, now()) / TIMER_HZ,
timer_pending(timer) ? "pending" : "");
if (timer_pending(timer)) {
TAILQ_FOREACH(entry, &timers.pending, link) {
if (entry == timer)
return;
}
*(timer->link.tqe_prev = &TAILQ_NEXT(timer, link)) = NULL;
}
}
int mod_timer(struct timer_entry *timer, unsigned long expires)
{
int pending;
if (!timer || !timer->func)
return -1;
log_timer("%-6s %p expires %d %s", "mod", timer,
time_diff(expires, now()) / TIMER_HZ,
timer_pending(timer) ? "pending" : "");
pending = timer_pending(timer);
if (pending) {
if (timer->expires == expires)
return 1;
TAILQ_REMOVE(&timers.pending, timer, link);
if (timers.next == timer->expires)
timers.next = timers.time;
}
timer->expires = expires;
if (time_after(timers.next, timer->expires))
timers.next = timer->expires;
TAILQ_INSERT_TAIL(&timers.pending, timer, link);
return pending;
}
int del_timer(struct timer_entry *timer)
{
log_timer("%-6s %p expires %d %s", "del", timer,
time_diff(timer->expires, now()) / TIMER_HZ,
timer_pending(timer) ? "pending" : "");
if (timer_pending(timer)) {
TAILQ_REMOVE(&timers.pending, timer, link);
*(timer->link.tqe_prev = &TAILQ_NEXT(timer, link)) = NULL;
if (timers.next == timer->expires)
timers.next = timers.time;
}
return 0;
}
int next_timer(struct timeval *tv)
{
struct timer_entry *timer;
int timeout;
if (TAILQ_EMPTY(&timers.pending))
return -1;
if (time_before_eq(timers.next, timers.time)) {
timers.next = timers.time + ~0UL / 2;
TAILQ_FOREACH(timer, &timers.pending, link) {
if (time_after(timers.next, timer->expires))
timers.next = timer->expires;
}
}
timeout = time_diff(timers.next, now());
if (timeout < 0)
timeout = 0;
if (tv)
time_to_timeval(timeout, tv);
return timeout;
}
int run_timers(void)
{
struct timer_entry *timer, *next;
timers.time = now();
if (TAILQ_EMPTY(&timers.pending) ||
time_after(timers.next, timers.time))
return 0;
TAILQ_FOREACH_SAFE(timer, &timers.pending, link, next) {
if (time_after(timer->expires, timers.time))
continue;
log_timer("%-6s %p", "run", timer);
TAILQ_REMOVE(&timers.pending, timer, link);
*(timer->link.tqe_prev = &TAILQ_NEXT(timer, link)) = NULL;
timer->func(timer, timer->data);
}
return 0;
}
void purge_timers(void)
{
struct timer_entry *timer, *next;
TAILQ_FOREACH_SAFE(timer, &timers.pending, link, next) {
TAILQ_REMOVE(&timers.pending, timer, link);
*(timer->link.tqe_prev = &TAILQ_NEXT(timer, link)) = NULL;
}
}