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