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