prisched.c 8.82 KB
Newer Older
Mark Spencer's avatar
Mark Spencer committed
1 2 3
/*
 * libpri: An implementation of Primary Rate ISDN
 *
4
 * Written by Mark Spencer <markster@digium.com>
Mark Spencer's avatar
Mark Spencer committed
5
 *
6
 * Copyright (C) 2001-2005, Digium, Inc.
Mark Spencer's avatar
Mark Spencer committed
7
 * All Rights Reserved.
8 9 10 11 12 13 14 15
 */

/*
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
Mark Spencer's avatar
Mark Spencer committed
16
 *
17 18 19 20
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2 as published by the
 * Free Software Foundation. See the LICENSE file included with
 * this program for more details.
Mark Spencer's avatar
Mark Spencer committed
21
 *
22 23 24 25 26 27
 * In addition, when this program is distributed with Asterisk in
 * any form that would qualify as a 'combined work' or as a
 * 'derivative work' (but not mere aggregation), you can redistribute
 * and/or modify the combination under the terms of the license
 * provided with that copy of Asterisk, instead of the license
 * terms granted here.
Mark Spencer's avatar
Mark Spencer committed
28 29
 */

30
#include <stdio.h>
31 32
#include <stdlib.h>
#include <string.h>
33

Mark Spencer's avatar
Mark Spencer committed
34 35 36 37
#include "libpri.h"
#include "pri_internal.h"


38 39 40
/*! Initial number of scheduled timer slots. */
#define SCHED_EVENTS_INITIAL	128
/*!
41 42
 * \brief Maximum number of scheduled timer slots.
 * \note Should be a power of 2 and at least SCHED_EVENTS_INITIAL.
43 44 45
 */
#define SCHED_EVENTS_MAX		8192

46
/*! \brief The maximum number of timers that were active at once. */
47
static unsigned maxsched = 0;
48 49
/*! Last pool id */
static unsigned pool_id = 0;
Mark Spencer's avatar
Mark Spencer committed
50 51

/* Scheduler routines */
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
/*!
 * \internal
 * \brief Increase the number of scheduler timer slots available.
 *
 * \param ctrl D channel controller.
 *
 * \retval 0 on success.
 * \retval -1 on error.
 */
static int pri_schedule_grow(struct pri *ctrl)
{
	unsigned num_slots;
	struct pri_sched *timers;

	/* Determine how many slots in the new timer table. */
	if (ctrl->sched.num_slots) {
		if (SCHED_EVENTS_MAX <= ctrl->sched.num_slots) {
			/* Cannot grow the timer table any more. */
			return -1;
		}
		num_slots = ctrl->sched.num_slots * 2;
		if (SCHED_EVENTS_MAX < num_slots) {
			num_slots = SCHED_EVENTS_MAX;
		}
	} else {
		num_slots = SCHED_EVENTS_INITIAL;
	}

	/* Get and initialize the new timer table. */
	timers = calloc(num_slots, sizeof(struct pri_sched));
	if (!timers) {
		/* Could not get a new timer table. */
		return -1;
	}
	if (ctrl->sched.timer) {
		/* Copy over the old timer table. */
		memcpy(timers, ctrl->sched.timer,
			ctrl->sched.num_slots * sizeof(struct pri_sched));
		free(ctrl->sched.timer);
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
	} else {
		/* Creating the timer pool. */
		pool_id += SCHED_EVENTS_MAX;
		if (pool_id < SCHED_EVENTS_MAX
			|| pool_id + (SCHED_EVENTS_MAX - 1) < SCHED_EVENTS_MAX) {
			/*
			 * Not likely to happen.
			 *
			 * Timer id's may be aliased if this D channel is used in an
			 * NFAS group with redundant D channels.  Another D channel in
			 * the group may have the same pool_id.
			 */
			pri_error(ctrl,
				"Pool_id wrapped.  Please ignore if you are not using NFAS with backup D channels.\n");
			pool_id = SCHED_EVENTS_MAX;
		}
		ctrl->sched.first_id = pool_id;
109 110 111 112 113 114 115 116
	}

	/* Put the new timer table in place. */
	ctrl->sched.timer = timers;
	ctrl->sched.num_slots = num_slots;
	return 0;
}

117 118 119 120 121 122 123 124 125 126 127
/*!
 * \brief Start a timer to schedule an event.
 *
 * \param ctrl D channel controller.
 * \param ms Number of milliseconds to scheduled event.
 * \param function Callback function to call when timeout.
 * \param data Value to give callback function when timeout.
 *
 * \retval 0 if scheduler table is full and could not schedule the event.
 * \retval id Scheduled event id.
 */
128
unsigned pri_schedule_event(struct pri *ctrl, int ms, void (*function)(void *data), void *data)
Mark Spencer's avatar
Mark Spencer committed
129
{
130 131
	unsigned max_used;
	unsigned x;
Mark Spencer's avatar
Mark Spencer committed
132
	struct timeval tv;
133

134 135 136
	max_used = ctrl->sched.max_used;
	for (x = 0; x < max_used; ++x) {
		if (!ctrl->sched.timer[x].callback) {
Mark Spencer's avatar
Mark Spencer committed
137
			break;
138 139
		}
	}
140
	if (x == ctrl->sched.num_slots && pri_schedule_grow(ctrl)) {
141 142 143
		pri_error(ctrl, "No more room in scheduler\n");
		return 0;
	}
144 145 146
	if (ctrl->sched.max_used <= x) {
		ctrl->sched.max_used = x + 1;
	}
147 148
	if (x >= maxsched) {
		maxsched = x + 1;
Mark Spencer's avatar
Mark Spencer committed
149 150 151 152
	}
	gettimeofday(&tv, NULL);
	tv.tv_sec += ms / 1000;
	tv.tv_usec += (ms % 1000) * 1000;
Mark Spencer's avatar
Mark Spencer committed
153
	if (tv.tv_usec > 1000000) {
Mark Spencer's avatar
Mark Spencer committed
154
		tv.tv_usec -= 1000000;
Mark Spencer's avatar
Mark Spencer committed
155 156
		tv.tv_sec += 1;
	}
157 158 159
	ctrl->sched.timer[x].when = tv;
	ctrl->sched.timer[x].callback = function;
	ctrl->sched.timer[x].data = data;
160
	return ctrl->sched.first_id + x;
Mark Spencer's avatar
Mark Spencer committed
161 162
}

163 164 165 166 167 168 169 170
/*!
 * \brief Determine the time of the next scheduled event to expire.
 *
 * \param ctrl D channel controller.
 *
 * \return Time of the next scheduled event to expire or NULL if no timers active.
 */
struct timeval *pri_schedule_next(struct pri *ctrl)
Mark Spencer's avatar
Mark Spencer committed
171 172
{
	struct timeval *closest = NULL;
173
	unsigned x;
174

175 176 177 178 179 180 181 182 183 184 185 186
	/* Scan the scheduled timer slots backwards so we can update the max_used value. */
	for (x = ctrl->sched.max_used; x--;) {
		if (ctrl->sched.timer[x].callback) {
			if (!closest) {
				/* This is the highest sheduled timer slot in use. */
				closest = &ctrl->sched.timer[x].when;
				ctrl->sched.max_used = x + 1;
			} else if ((closest->tv_sec > ctrl->sched.timer[x].when.tv_sec)
				|| ((closest->tv_sec == ctrl->sched.timer[x].when.tv_sec)
					&& (closest->tv_usec > ctrl->sched.timer[x].when.tv_usec))) {
				closest = &ctrl->sched.timer[x].when;
			}
187
		}
Mark Spencer's avatar
Mark Spencer committed
188
	}
189 190 191 192
	if (!closest) {
		/* No scheduled timer slots are active. */
		ctrl->sched.max_used = 0;
	}
Mark Spencer's avatar
Mark Spencer committed
193 194 195
	return closest;
}

196 197 198 199 200 201 202 203 204 205
/*!
 * \internal
 * \brief Run all expired timers or return an event generated by an expired timer.
 *
 * \param ctrl D channel controller.
 * \param tv Current time.
 *
 * \return Event for upper layer to process or NULL if all expired timers run.
 */
static pri_event *__pri_schedule_run(struct pri *ctrl, struct timeval *tv)
Mark Spencer's avatar
Mark Spencer committed
206
{
207 208
	unsigned x;
	unsigned max_used;
Mark Spencer's avatar
Mark Spencer committed
209 210
	void (*callback)(void *);
	void *data;
211

212 213 214 215 216 217
	max_used = ctrl->sched.max_used;
	for (x = 0; x < max_used; ++x) {
		if (ctrl->sched.timer[x].callback
			&& ((ctrl->sched.timer[x].when.tv_sec < tv->tv_sec)
			|| ((ctrl->sched.timer[x].when.tv_sec == tv->tv_sec)
			&& (ctrl->sched.timer[x].when.tv_usec <= tv->tv_usec)))) {
218 219
			/* This timer has expired. */
			ctrl->schedev = 0;
220 221 222
			callback = ctrl->sched.timer[x].callback;
			data = ctrl->sched.timer[x].data;
			ctrl->sched.timer[x].callback = NULL;
223 224 225 226 227
			callback(data);
			if (ctrl->schedev) {
				return &ctrl->ev;
			}
		}
Mark Spencer's avatar
Mark Spencer committed
228
	}
Mark Spencer's avatar
Mark Spencer committed
229
	return NULL;
Mark Spencer's avatar
Mark Spencer committed
230 231
}

232 233 234 235 236 237 238 239
/*!
 * \brief Run all expired timers or return an event generated by an expired timer.
 *
 * \param ctrl D channel controller.
 *
 * \return Event for upper layer to process or NULL if all expired timers run.
 */
pri_event *pri_schedule_run(struct pri *ctrl)
240 241
{
	struct timeval tv;
242

243
	gettimeofday(&tv, NULL);
244
	return __pri_schedule_run(ctrl, &tv);
245 246
}

247 248 249 250 251 252 253 254 255
/*!
 * \brief Delete a scheduled event.
 *
 * \param ctrl D channel controller.
 * \param id Scheduled event id to delete.
 * 0 is a disabled/unscheduled event id that is ignored.
 *
 * \return Nothing
 */
256
void pri_schedule_del(struct pri *ctrl, unsigned id)
Mark Spencer's avatar
Mark Spencer committed
257
{
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	struct pri *nfas;

	if (!id) {
		/* Disabled/unscheduled event id. */
		return;
	}
	if (ctrl->sched.first_id <= id
		&& id <= ctrl->sched.first_id + (SCHED_EVENTS_MAX - 1)) {
		ctrl->sched.timer[id - ctrl->sched.first_id].callback = NULL;
		return;
	}
	if (ctrl->nfas) {
		/* Try to find the timer on another D channel. */
		for (nfas = PRI_NFAS_MASTER(ctrl); nfas; nfas = nfas->slave) {
			if (nfas->sched.first_id <= id
				&& id <= nfas->sched.first_id + (SCHED_EVENTS_MAX - 1)) {
				nfas->sched.timer[id - nfas->sched.first_id].callback = NULL;
				return;
			}
		}
278
	}
279 280 281
	pri_error(ctrl,
		"Asked to delete sched id 0x%08x??? first_id=0x%08x, num_slots=0x%08x\n", id,
		ctrl->sched.first_id, ctrl->sched.num_slots);
Mark Spencer's avatar
Mark Spencer committed
282
}
283 284 285 286 287 288 289 290 291 292 293 294

/*!
 * \brief Is the scheduled event this callback.
 *
 * \param ctrl D channel controller.
 * \param id Scheduled event id to check.
 * 0 is a disabled/unscheduled event id.
 * \param function Callback function to call when timeout.
 * \param data Value to give callback function when timeout.
 *
 * \return TRUE if scheduled event has the callback.
 */
295
int pri_schedule_check(struct pri *ctrl, unsigned id, void (*function)(void *data), void *data)
296
{
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
	struct pri *nfas;

	if (!id) {
		/* Disabled/unscheduled event id. */
		return 0;
	}
	if (ctrl->sched.first_id <= id
		&& id <= ctrl->sched.first_id + (SCHED_EVENTS_MAX - 1)) {
		return ctrl->sched.timer[id - ctrl->sched.first_id].callback == function
			&& ctrl->sched.timer[id - ctrl->sched.first_id].data == data;
	}
	if (ctrl->nfas) {
		/* Try to find the timer on another D channel. */
		for (nfas = PRI_NFAS_MASTER(ctrl); nfas; nfas = nfas->slave) {
			if (nfas->sched.first_id <= id
				&& id <= nfas->sched.first_id + (SCHED_EVENTS_MAX - 1)) {
				return nfas->sched.timer[id - nfas->sched.first_id].callback == function
					&& nfas->sched.timer[id - nfas->sched.first_id].data == data;
			}
316 317
		}
	}
318 319 320
	pri_error(ctrl,
		"Asked to check sched id 0x%08x??? first_id=0x%08x, num_slots=0x%08x\n", id,
		ctrl->sched.first_id, ctrl->sched.num_slots);
321 322
	return 0;
}