1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29/*
30 * Simple implementation of timeout functionality. The granuality is a sec
31 */
32#include <pthread.h>
33#include <stdlib.h>
34
35uint_t		sip_timeout(void *arg, void (*callback_func)(void *),
36		    struct timeval *timeout_time);
37boolean_t	sip_untimeout(uint_t);
38
39typedef struct timeout {
40	struct timeout *sip_timeout_next;
41	hrtime_t sip_timeout_val;
42	void (*sip_timeout_callback_func)(void *);
43	void *sip_timeout_callback_func_arg;
44	int   sip_timeout_id;
45} sip_timeout_t;
46
47static pthread_mutex_t timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
48static pthread_cond_t  timeout_cond_var = PTHREAD_COND_INITIALIZER;
49static sip_timeout_t *timeout_list;
50static sip_timeout_t *timeout_current_start;
51static sip_timeout_t *timeout_current_end;
52
53/*
54 * LONG_SLEEP_TIME = (24 * 60 * 60 * NANOSEC)
55 */
56#define	LONG_SLEEP_TIME	(0x15180LL * 0x3B9ACA00LL)
57
58uint_t timer_id = 0;
59
60/*
61 * Invoke the callback function
62 */
63/* ARGSUSED */
64static void *
65sip_run_to_functions(void *arg)
66{
67	sip_timeout_t *timeout = NULL;
68
69	(void) pthread_mutex_lock(&timeout_mutex);
70	while (timeout_current_start != NULL) {
71		timeout = timeout_current_start;
72		if (timeout_current_end == timeout_current_start)
73			timeout_current_start = timeout_current_end = NULL;
74		else
75			timeout_current_start = timeout->sip_timeout_next;
76		(void) pthread_mutex_unlock(&timeout_mutex);
77		timeout->sip_timeout_callback_func(
78		    timeout->sip_timeout_callback_func_arg);
79		free(timeout);
80		(void) pthread_mutex_lock(&timeout_mutex);
81	}
82	(void) pthread_mutex_unlock(&timeout_mutex);
83	pthread_exit(NULL);
84	return ((void *)0);
85}
86
87/*
88 * In the very very unlikely case timer id wraps around and we have two timers
89 * with the same id. If that happens timer with the least amount of time left
90 * will be deleted. In case both timers have same time left than the one that
91 * was scheduled first will be deleted as it will be in the front of the list.
92 */
93boolean_t
94sip_untimeout(uint_t id)
95{
96	boolean_t	ret = B_FALSE;
97	sip_timeout_t	*current, *last;
98
99	last = NULL;
100	(void) pthread_mutex_lock(&timeout_mutex);
101
102	/*
103	 * Check if this is in the to-be run list
104	 */
105	if (timeout_current_start != NULL) {
106		current = timeout_current_start;
107		while (current != NULL) {
108			if (current->sip_timeout_id == id) {
109				if (current == timeout_current_start) {
110					timeout_current_start =
111					    current->sip_timeout_next;
112				} else {
113					last->sip_timeout_next =
114					    current->sip_timeout_next;
115				}
116				if (current == timeout_current_end)
117					timeout_current_end = last;
118				if (current->sip_timeout_callback_func_arg !=
119				    NULL) {
120					free(current->
121					    sip_timeout_callback_func_arg);
122					current->sip_timeout_callback_func_arg =
123					    NULL;
124				}
125				free(current);
126				ret = B_TRUE;
127				break;
128			}
129			last = current;
130			current = current->sip_timeout_next;
131		}
132	}
133
134	/*
135	 * Check if this is in the to-be scheduled list
136	 */
137	if (!ret && timeout_list != NULL) {
138		last = NULL;
139		current = timeout_list;
140		while (current != NULL) {
141			if (current->sip_timeout_id == id) {
142				if (current == timeout_list) {
143					timeout_list =
144					    current->sip_timeout_next;
145				} else {
146					last->sip_timeout_next =
147					    current->sip_timeout_next;
148				}
149				if (current->sip_timeout_callback_func_arg !=
150				    NULL) {
151					free(current->
152					    sip_timeout_callback_func_arg);
153					current->sip_timeout_callback_func_arg =
154					    NULL;
155				}
156				free(current);
157				ret = B_TRUE;
158				break;
159			}
160			last = current;
161			current = current->sip_timeout_next;
162		}
163	}
164	(void) pthread_mutex_unlock(&timeout_mutex);
165	return (ret);
166}
167
168/*
169 * Add a new timeout
170 */
171uint_t
172sip_timeout(void *arg, void (*callback_func)(void *),
173    struct timeval *timeout_time)
174{
175	sip_timeout_t	*new_timeout;
176	sip_timeout_t	*current;
177	sip_timeout_t	*last;
178	hrtime_t	future_time;
179	uint_t		tid;
180#ifdef	__linux__
181	struct timespec	tspec;
182	hrtime_t	now;
183#endif
184
185	new_timeout = malloc(sizeof (sip_timeout_t));
186	if (new_timeout == NULL)
187		return (0);
188
189#ifdef	__linux__
190	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
191		return (0);
192	now = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC + tspec.tv_nsec;
193	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
194	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + now;
195#else
196	future_time = (hrtime_t)timeout_time->tv_sec * (hrtime_t)NANOSEC +
197	    (hrtime_t)(timeout_time->tv_usec * MILLISEC) + gethrtime();
198#endif
199	if (future_time <= 0L) {
200		free(new_timeout);
201		return (0);
202	}
203
204	new_timeout->sip_timeout_next = NULL;
205	new_timeout->sip_timeout_val = future_time;
206	new_timeout->sip_timeout_callback_func = callback_func;
207	new_timeout->sip_timeout_callback_func_arg = arg;
208	(void) pthread_mutex_lock(&timeout_mutex);
209	timer_id++;
210	if (timer_id == 0)
211		timer_id++;
212	tid = timer_id;
213	new_timeout->sip_timeout_id = tid;
214	last = current = timeout_list;
215	while (current != NULL) {
216		if (current->sip_timeout_val <= new_timeout->sip_timeout_val) {
217			last = current;
218			current = current->sip_timeout_next;
219		} else {
220			break;
221		}
222	}
223
224	if (current == timeout_list) {
225		new_timeout->sip_timeout_next  = timeout_list;
226		timeout_list = new_timeout;
227	} else {
228		new_timeout->sip_timeout_next = current,
229		last->sip_timeout_next = new_timeout;
230	}
231	(void) pthread_cond_signal(&timeout_cond_var);
232	(void) pthread_mutex_unlock(&timeout_mutex);
233	return (tid);
234}
235
236/*
237 * Schedule the next timeout
238 */
239static hrtime_t
240sip_schedule_to_functions()
241{
242	sip_timeout_t		*timeout = NULL;
243	sip_timeout_t		*last = NULL;
244	boolean_t		create_thread = B_FALSE;
245	hrtime_t		current_time;
246#ifdef	__linux__
247	struct timespec	tspec;
248#endif
249
250	/*
251	 * Thread is holding the mutex.
252	 */
253#ifdef	__linux__
254	if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
255		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
256	current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
257	    tspec.tv_nsec;
258#else
259	current_time = gethrtime();
260#endif
261	if (timeout_list == NULL)
262		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
263	timeout = timeout_list;
264
265	/*
266	 * Get all the timeouts that have fired.
267	 */
268	while (timeout != NULL && timeout->sip_timeout_val <= current_time) {
269		last = timeout;
270		timeout = timeout->sip_timeout_next;
271	}
272
273	timeout = last;
274	if (timeout != NULL) {
275		if (timeout_current_end != NULL) {
276			timeout_current_end->sip_timeout_next = timeout_list;
277			timeout_current_end = timeout;
278		} else {
279			timeout_current_start = timeout_list;
280			timeout_current_end = timeout;
281			create_thread = B_TRUE;
282		}
283		timeout_list = timeout->sip_timeout_next;
284		timeout->sip_timeout_next = NULL;
285		if (create_thread) {
286			pthread_t	thr;
287
288			(void) pthread_create(&thr, NULL, sip_run_to_functions,
289			    NULL);
290			(void) pthread_detach(thr);
291		}
292	}
293	if (timeout_list != NULL)
294		return (timeout_list->sip_timeout_val);
295	else
296		return ((hrtime_t)LONG_SLEEP_TIME + current_time);
297}
298
299/*
300 * The timer routine
301 */
302/* ARGSUSED */
303static void *
304sip_timer_thr(void *arg)
305{
306	timestruc_t	to;
307	hrtime_t	current_time;
308	hrtime_t	next_timeout;
309	hrtime_t	delta;
310	struct timeval tim;
311#ifdef	__linux__
312	struct timespec	tspec;
313#endif
314	delta = (hrtime_t)5 * NANOSEC;
315	(void) pthread_mutex_lock(&timeout_mutex);
316	for (;;) {
317		(void) gettimeofday(&tim, NULL);
318		to.tv_sec = tim.tv_sec + (delta / NANOSEC);
319		to.tv_nsec = (hrtime_t)(tim.tv_usec * MILLISEC) +
320		    (delta % NANOSEC);
321		if (to.tv_nsec > NANOSEC) {
322			to.tv_sec += (to.tv_nsec / NANOSEC);
323			to.tv_nsec %= NANOSEC;
324		}
325		(void) pthread_cond_timedwait(&timeout_cond_var,
326		    &timeout_mutex, &to);
327		/*
328		 * We return from timedwait because we either timed out
329		 * or a new element was added and we need to reset the time
330		 */
331again:
332		next_timeout =  sip_schedule_to_functions();
333#ifdef	__linux__
334		if (clock_gettime(CLOCK_REALTIME, &tspec) != 0)
335			goto again; /* ??? */
336		current_time = (hrtime_t)tspec.tv_sec * (hrtime_t)NANOSEC +
337		    tspec.tv_nsec;
338#else
339		current_time = gethrtime();
340#endif
341		delta = next_timeout - current_time;
342		if (delta <= 0)
343			goto again;
344	}
345	/* NOTREACHED */
346	return ((void *)0);
347}
348
349/*
350 * The init routine, starts the timer thread
351 */
352void
353sip_timeout_init()
354{
355	static boolean_t	timout_init = B_FALSE;
356	pthread_t		thread1;
357
358	(void) pthread_mutex_lock(&timeout_mutex);
359	if (timout_init == B_FALSE) {
360		timout_init = B_TRUE;
361		(void) pthread_mutex_unlock(&timeout_mutex);
362	} else {
363		(void) pthread_mutex_unlock(&timeout_mutex);
364		return;
365	}
366	(void) pthread_create(&thread1, NULL, sip_timer_thr, NULL);
367}
368