1/*
2 * bcmseclib_timer.c -- timer library
3 *
4 * Copyright (C) 2014, Broadcom Corporation
5 * All Rights Reserved.
6 *
7 * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom Corporation;
8 * the contents of this file may not be disclosed to third parties, copied
9 * or duplicated in any form, in whole or in part, without the prior
10 * written permission of Broadcom Corporation.
11 *
12 * $Id: bcmseclib_timer.c,v 1.7 2011-01-11 19:03:26 $
13 */
14
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <stdlib.h>
19#include <typedefs.h>
20#include <bcm_osl.h>
21#include <bcm_llist.h>
22#include <bcmseclib_timer.h>
23#include <bcmseclib_timer_os.h>
24#include <debug.h>
25
26/* Timer manager. */
27struct bcmseclib_timer_mgr
28{
29	bcmseclib_timer_t *timerlist;
30	bcmseclib_timer_t *timer_list_mark;
31	bcmseclib_timer_t *timer_freelist;
32	int maxtimers;
33};
34
35/* Global timer manager. */
36static bcmseclib_timer_mgr_t *g_timer_mgr = NULL;
37
38static int bcmseclib_set_expiration(uint time, exp_time_t * expiry_time);
39static int bcmseclib_compare_times(exp_time_t *t1, exp_time_t *t2);
40static bool bcmseclib_has_timer_expired(exp_time_t *t);
41static void bcmseclib_translate_expiry_to_absolute(exp_time_t *t);
42
43
44/* Init the timer library
45 * Allocate memory, init free list
46 * Return zero for success, non-zero otherwise
47 */
48int
49bcmseclib_init_timer_utilities_ex(int ntimers, bcmseclib_timer_mgr_t **mgrp)
50{
51	int i;
52	bcmseclib_timer_mgr_t *mgr;
53
54	mgr = (bcmseclib_timer_mgr_t *) OS_MALLOC(sizeof(bcmseclib_timer_mgr_t));
55	if (mgr == NULL) {
56		return (-1);
57	}
58	memset(mgr, 0, sizeof(bcmseclib_timer_mgr_t));
59
60	mgr->maxtimers = ntimers;
61	mgr->timer_freelist = (bcmseclib_timer_t *) OS_MALLOC(ntimers * sizeof(bcmseclib_timer_t));
62	memset(mgr->timer_freelist, 0, ntimers * sizeof(bcmseclib_timer_t));
63
64	/* save, we'll need this to cleanup when we shutdown */
65	mgr->timer_list_mark = mgr->timer_freelist;
66
67	for (i = 0; i < (ntimers-1); i++)
68		mgr->timer_freelist[i].next = &mgr->timer_freelist[i+1];
69
70	mgr->timer_freelist[i].next = NULL;
71
72	if (mgrp == NULL) {
73		mgrp = &g_timer_mgr;
74	}
75	*mgrp = mgr;
76	return 0;
77}
78/* Clean it up:
79 * Free the allocated memory
80 * All users of timer utilities should have freed their timers
81 * via bcmseclib_free_timer() before this
82 */
83int
84bcmseclib_deinit_timer_utilities_ex(bcmseclib_timer_mgr_t *mgr)
85{
86	if (mgr == NULL) {
87		mgr = g_timer_mgr;
88	}
89
90	OS_FREE(mgr->timer_list_mark);
91	OS_FREE(mgr);
92	mgr = NULL;
93
94	return 0;
95}
96
97/* Setup an existing timer with specified parms
98 * and add it to the active list
99 * If it's already on the active list relocate it
100 * to the appropriate position
101 */
102void
103bcmseclib_add_timer(bcmseclib_timer_t *t, uint ms, bool periodic)
104{
105	bcmseclib_timer_t *plist, *pprev;
106	bcmseclib_timer_mgr_t *mgr;
107	char *funstr = "bcmseclib_add_timer";
108
109	(void) funstr;
110	PRINT_TRACE(("%s(0x%x): duration %d periodic %d\n", funstr, (int)t->mgr, ms, periodic));
111
112	mgr = t->mgr;
113
114	/* If already in active list: remove it! */
115	/* this call may fail: ok, just means t was not on list */
116	bcm_llist_del_member(&mgr->timerlist, t);
117
118	/* set time value and periodic flag */
119	t->ms = ms;
120	t->periodic = periodic;
121	bcmseclib_set_expiration(ms, &t->expiry_time);
122
123	/* place in correct position (by ascending expiry time) in active list */
124
125	/* if list is NULL, we're the one & only */
126	if (mgr->timerlist == NULL) {
127		t->next = NULL;
128		mgr->timerlist = t;
129		goto done;
130	}
131
132	/* walk list until we find a time greater than ours, insert before that member */
133	pprev = NULL;
134	for (plist = mgr->timerlist; plist; ) {
135		if (bcmseclib_compare_times(&t->expiry_time, &plist->expiry_time) <= 0) {
136			t->next = plist;
137			if (pprev == NULL)
138				mgr->timerlist = t;
139			else
140				pprev->next = t;
141			break;
142		}
143		pprev = plist;
144		plist = plist->next;
145	}
146
147	/* end of list */
148	if (plist == NULL) {
149		pprev->next = t;
150		t->next = NULL;
151	}
152
153done:
154	return;
155}
156
157/* De-activate timer: remove from active list, but maintain settings,
158 * application is maintaining ownership
159 */
160bool
161bcmseclib_del_timer(bcmseclib_timer_t *t)
162{
163	bcmseclib_timer_mgr_t *mgr = t->mgr;
164
165	/* Unlink from active list */
166	/* this call may fail: ok, just means t was not on list */
167	if (bcm_llist_del_member(&mgr->timerlist, t) == 0)
168		return TRUE;
169
170	return FALSE;
171}
172
173/* Application is surrendering this timer:
174 * Remove from active list and clear all members
175 */
176void
177bcmseclib_free_timer(bcmseclib_timer_t *t)
178{
179	bcmseclib_timer_mgr_t *mgr;
180
181	if (t == NULL)
182		return;
183
184	mgr = t->mgr;
185
186	/* unlink from active list if necessary */
187	bcm_llist_del_member(&mgr->timerlist, t);
188
189	/* place in free list */
190	t->next = mgr->timer_freelist;
191	mgr->timer_freelist = t;
192}
193
194/* Create the data structures, fill in callback args, but do NOT
195 * add to active list
196 */
197bcmseclib_timer_t *
198bcmseclib_init_timer_ex(bcmseclib_timer_mgr_t *mgr, void (*fn)(void *arg), void *arg, const char *name)
199{
200	bcmseclib_timer_t *pnew;
201	char *funstr = "bcmseclib_init_timer";
202
203	if (mgr == NULL) {
204		mgr = g_timer_mgr;
205	}
206
207	/* Find a free timer, if none complain and return NULL */
208	if (mgr->timer_freelist == NULL) {
209		PRINT_ERR(("%s: No timer blocks availavble\n", funstr));
210		return NULL;
211	}
212
213	pnew = mgr->timer_freelist;
214	mgr->timer_freelist = pnew->next;
215	pnew->next = NULL;
216
217	/* Fill in cb fun, arg, name */
218	ASSERT(fn);
219	if (fn == NULL) {
220		PRINT_ERR(("%s: NULL cb function arg!\n", funstr));
221		return NULL;
222	}
223	pnew->fn = fn;
224	pnew->arg = arg;
225	pnew->mgr = mgr;
226
227#ifdef BCMDBG
228	if ((pnew->name = OS_MALLOC(strlen(name) + 1)))
229		strcpy(pnew->name, name);
230#endif
231
232	/* return pointer to timer */
233	return pnew;
234}
235
236/* Check the active timer list
237 * Return:
238 * TRUE if we've got a timeout to consider, set value of t appropriately
239 * FALSE otherwise, value of t irrelevant
240 * Primary user should be the select/waitfor loop in the dispatcher.
241 */
242bool
243bcmseclib_get_timeout_ex(bcmseclib_timer_mgr_t *mgr, exp_time_t *t)
244{
245	char *funstr = "bcmseclib_get_timeout";
246
247	if (mgr == NULL) {
248		mgr = g_timer_mgr;
249	}
250
251	/* no timers pending */
252	if (mgr->timerlist == NULL)
253		return FALSE;
254
255	if (t == NULL) {
256		PRINT_ERR(("%s: can't get timeout into null pointer\n", funstr));
257		return FALSE;
258	}
259
260	memcpy(t, &mgr->timerlist->expiry_time, sizeof(exp_time_t));
261
262	/* Translate expiry time which is a literal time of day to
263	 * time remaining to expiry for use by select/waitfor functions
264	 */
265	bcmseclib_translate_expiry_to_absolute(t);
266
267	return TRUE;
268}
269
270/* Check the timer list, process expirations */
271void
272bcmseclib_process_timer_expiry_ex(bcmseclib_timer_mgr_t *mgr)
273{
274	bcmseclib_timer_t *ptimer;
275	char *funstr = "bcmseclib_process_timer_expiry";
276
277	if (mgr == NULL) {
278		mgr = g_timer_mgr;
279	}
280
281	ptimer = mgr->timerlist;
282	if (ptimer == NULL)
283		return;
284
285	/* process all expired timers */
286	for (; ptimer && bcmseclib_has_timer_expired(&ptimer->expiry_time); ptimer = mgr->timerlist) {
287		/* Remove from the active list */
288		/* Caution: this modifies timerlist: recheck against NULL afterwards */
289		bcmseclib_del_timer(ptimer);
290
291		/* add timer back if periodic */
292		/* Caution: do this before the cb is fired in case it is delete
293		 *          in the handler
294		*/
295		if (ptimer->periodic)
296			bcmseclib_add_timer(ptimer, ptimer->ms, ptimer->periodic);
297
298		/* exec cb function */
299		if (!ptimer->fn) {
300			PRINT_ERR(("%s: NULL cb function\n", funstr));
301		} else {
302
303			(*ptimer->fn)(ptimer->arg);
304		}
305	}
306}
307
308/* Theory of operation:
309 * All timers that are added specify their expiration time in milliseconds (msec)
310 * relative to now. This 'msec' expiration time is converted to an exp_time_t
311 * structure that stores the expiry time relative to a fixed reference point.
312 * The resulting expiration time can be compared to the current time (relative
313 * to the fixed reference point) to determine if the timer has expired.
314 */
315
316/* Convert the expiration time (msec) to an exp_time_t structure that
317 * stores the expiry time relative to a fixed reference point.
318 */
319static int
320bcmseclib_set_expiration(uint msec, exp_time_t * expiry_time)
321{
322	bcmseclib_time_t now;
323	int sec = msec/1000;
324	int usec = (msec % 1000) * 1000;
325
326	memset(&now, 0, sizeof(now));
327	bcmseclib_os_get_time(&now);
328
329	expiry_time->sec = now.sec + sec;
330	expiry_time->usec = now.usec + usec;
331
332	/* Handle overflow of usec. */
333	while (expiry_time->usec >= 1000000) {
334		expiry_time->sec += 1;
335		expiry_time->usec -= 1000000;
336	}
337
338	return 0;
339}
340
341/* Return:
342 * < 0 for t1 < t2
343 *   0 for t1 == t2
344 * > 0 for t1 > t2
345 *
346 * Where: "less than" means sooner, "greater than" means later
347 */
348static int
349bcmseclib_compare_times(exp_time_t *t1, exp_time_t *t2)
350{
351
352	if (t1->sec > t2->sec)
353		return 1;
354
355	if (t1->sec < t2->sec)
356		return -1;
357
358	/* sec parts are equal */
359	if (t1->usec > t2->usec)
360		return 1;
361	if (t1->usec < t2->usec)
362		return -1;
363
364	return 0;
365}
366
367/* Return:
368 * TRUE if time has expired
369 * FALSE otherwise
370 * TODO: should we advance slightly, ie return TRUE if timer will
371 * expire "soon"?
372 */
373static bool
374bcmseclib_has_timer_expired(exp_time_t *t)
375{
376	bcmseclib_time_t now;
377
378	memset(&now, 0, sizeof(now));
379	bcmseclib_os_get_time(&now);
380
381	if (bcmseclib_compare_times(&now, t) >= 0)
382		return TRUE;
383
384	return FALSE;
385}
386
387/* Input: time value in t which stores the expiry time relative to a fixed reference point.
388 * Output: time remaining to expiration as absolute number of sec/usec relative to now.
389 */
390static void
391bcmseclib_translate_expiry_to_absolute(exp_time_t *t)
392{
393	bcmseclib_time_t now;
394
395	memset(&now, 0, sizeof(now));
396	bcmseclib_os_get_time(&now);
397	if (bcmseclib_compare_times(&now, t) >= 0) {
398		t->sec = t->usec = 0;
399		return;
400	}
401
402	PRINT_TRACE(("t->sec %ld t->usec %ld now.sec %ld now.usec %ld\n",
403		t->sec, t->usec, now.sec, now.usec));
404
405
406	t->sec -= now.sec;
407	t->usec -= now.usec;
408
409	/* Handle underflow. */
410	if (t->usec < 0) {
411		t->sec -= 1;
412		t->usec += 1000000;
413	}
414
415	ASSERT(t->sec >= 0);
416	ASSERT(t->usec >= 0);
417
418}
419