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 
35 uint_t		sip_timeout(void *arg, void (*callback_func)(void *),
36 		    struct timeval *timeout_time);
37 boolean_t	sip_untimeout(uint_t);
38 
39 typedef 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 
47 static pthread_mutex_t timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
48 static pthread_cond_t  timeout_cond_var = PTHREAD_COND_INITIALIZER;
49 static sip_timeout_t *timeout_list;
50 static sip_timeout_t *timeout_current_start;
51 static 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 
58 uint_t timer_id = 0;
59 
60 /*
61  * Invoke the callback function
62  */
63 /* ARGSUSED */
64 static void *
sip_run_to_functions(void * arg)65 sip_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  */
93 boolean_t
sip_untimeout(uint_t id)94 sip_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  */
171 uint_t
sip_timeout(void * arg,void (* callback_func)(void *),struct timeval * timeout_time)172 sip_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  */
239 static hrtime_t
sip_schedule_to_functions()240 sip_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 */
303 static void *
sip_timer_thr(void * arg)304 sip_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 		 */
331 again:
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  */
352 void
sip_timeout_init()353 sip_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