xref: /freebsd/contrib/ofed/opensm/complib/cl_timer.c (revision 6be3386466ab79a84b48429ae66244f21526d3df)
1 /*
2  * Copyright (c) 2004-2006 Voltaire, Inc. All rights reserved.
3  * Copyright (c) 2002-2005 Mellanox Technologies LTD. All rights reserved.
4  * Copyright (c) 1996-2003 Intel Corporation. All rights reserved.
5  *
6  * This software is available to you under a choice of one of two
7  * licenses.  You may choose to be licensed under the terms of the GNU
8  * General Public License (GPL) Version 2, available from the file
9  * COPYING in the main directory of this source tree, or the
10  * OpenIB.org BSD license below:
11  *
12  *     Redistribution and use in source and binary forms, with or
13  *     without modification, are permitted provided that the following
14  *     conditions are met:
15  *
16  *      - Redistributions of source code must retain the above
17  *        copyright notice, this list of conditions and the following
18  *        disclaimer.
19  *
20  *      - Redistributions in binary form must reproduce the above
21  *        copyright notice, this list of conditions and the following
22  *        disclaimer in the documentation and/or other materials
23  *        provided with the distribution.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
29  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
30  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32  * SOFTWARE.
33  *
34  */
35 
36 /*
37  * Abstract:
38  * Abstraction of Timer create, destroy functions.
39  *
40  */
41 
42 #if HAVE_CONFIG_H
43 #  include <config.h>
44 #endif				/* HAVE_CONFIG_H */
45 
46 #include <stdlib.h>
47 #include <string.h>
48 #include <complib/cl_timer.h>
49 #include <sys/time.h>
50 #include <sys/errno.h>
51 #include <stdio.h>
52 
53 /* Timer provider (emulates timers in user mode). */
54 typedef struct _cl_timer_prov {
55 	pthread_t thread;
56 	pthread_mutex_t mutex;
57 	pthread_cond_t cond;
58 	cl_qlist_t queue;
59 
60 	boolean_t exit;
61 
62 } cl_timer_prov_t;
63 
64 /* Global timer provider. */
65 static cl_timer_prov_t *gp_timer_prov = NULL;
66 
67 static void *__cl_timer_prov_cb(IN void *const context);
68 
69 /*
70  * Creates the process global timer provider.  Must be called by the shared
71  * object framework to solve all serialization issues.
72  */
73 cl_status_t __cl_timer_prov_create(void)
74 {
75 	CL_ASSERT(gp_timer_prov == NULL);
76 
77 	gp_timer_prov = malloc(sizeof(cl_timer_prov_t));
78 	if (!gp_timer_prov)
79 		return (CL_INSUFFICIENT_MEMORY);
80 	else
81 		memset(gp_timer_prov, 0, sizeof(cl_timer_prov_t));
82 
83 	cl_qlist_init(&gp_timer_prov->queue);
84 
85 	pthread_mutex_init(&gp_timer_prov->mutex, NULL);
86 	pthread_cond_init(&gp_timer_prov->cond, NULL);
87 
88 	if (pthread_create(&gp_timer_prov->thread, NULL,
89 			   __cl_timer_prov_cb, NULL)) {
90 		__cl_timer_prov_destroy();
91 		return (CL_ERROR);
92 	}
93 
94 	return (CL_SUCCESS);
95 }
96 
97 void __cl_timer_prov_destroy(void)
98 {
99 	pthread_t tid;
100 
101 	if (!gp_timer_prov)
102 		return;
103 
104 	tid = gp_timer_prov->thread;
105 	pthread_mutex_lock(&gp_timer_prov->mutex);
106 	gp_timer_prov->exit = TRUE;
107 	pthread_cond_broadcast(&gp_timer_prov->cond);
108 	pthread_mutex_unlock(&gp_timer_prov->mutex);
109 	pthread_join(tid, NULL);
110 
111 	/* Destroy the mutex and condition variable. */
112 	pthread_mutex_destroy(&gp_timer_prov->mutex);
113 	pthread_cond_destroy(&gp_timer_prov->cond);
114 
115 	/* Free the memory and reset the global pointer. */
116 	free(gp_timer_prov);
117 	gp_timer_prov = NULL;
118 }
119 
120 /*
121  * This is the internal work function executed by the timer's thread.
122  */
123 static void *__cl_timer_prov_cb(IN void *const context)
124 {
125 	int ret;
126 	cl_timer_t *p_timer;
127 
128 	pthread_mutex_lock(&gp_timer_prov->mutex);
129 	while (!gp_timer_prov->exit) {
130 		if (cl_is_qlist_empty(&gp_timer_prov->queue)) {
131 			/* Wait until we exit or a timer is queued. */
132 			/* cond wait does:
133 			 * pthread_cond_wait atomically unlocks the mutex (as per
134 			 * pthread_unlock_mutex) and waits for the condition variable
135 			 * cond to be signaled. The thread execution is suspended and
136 			 * does not consume any CPU time until the condition variable is
137 			 * signaled. The mutex must be locked by the calling thread on
138 			 * entrance to pthread_cond_wait. Before RETURNING TO THE
139 			 * CALLING THREAD, PTHREAD_COND_WAIT RE-ACQUIRES MUTEX (as per
140 			 * pthread_lock_mutex).
141 			 */
142 			ret = pthread_cond_wait(&gp_timer_prov->cond,
143 						&gp_timer_prov->mutex);
144 		} else {
145 			/*
146 			 * The timer elements are on the queue in expiration order.
147 			 * Get the first in the list to determine how long to wait.
148 			 */
149 
150 			p_timer =
151 			    (cl_timer_t *) cl_qlist_head(&gp_timer_prov->queue);
152 			ret =
153 			    pthread_cond_timedwait(&gp_timer_prov->cond,
154 						   &gp_timer_prov->mutex,
155 						   &p_timer->timeout);
156 
157 			/*
158 			   Sleep again on every event other than timeout and invalid
159 			   Note: EINVAL means that we got behind. This can occur when
160 			   we are very busy...
161 			 */
162 			if (ret != ETIMEDOUT && ret != EINVAL)
163 				continue;
164 
165 			/*
166 			 * The timer expired.  Check the state in case it was cancelled
167 			 * after it expired but before we got a chance to invoke the
168 			 * callback.
169 			 */
170 			if (p_timer->timer_state != CL_TIMER_QUEUED)
171 				continue;
172 
173 			/*
174 			 * Mark the timer as running to synchronize with its
175 			 * cancelation since we can't hold the mutex during the
176 			 * callback.
177 			 */
178 			p_timer->timer_state = CL_TIMER_RUNNING;
179 
180 			/* Remove the item from the timer queue. */
181 			cl_qlist_remove_item(&gp_timer_prov->queue,
182 					     &p_timer->list_item);
183 			pthread_mutex_unlock(&gp_timer_prov->mutex);
184 			/* Invoke the callback. */
185 			p_timer->pfn_callback((void *)p_timer->context);
186 
187 			/* Acquire the mutex again. */
188 			pthread_mutex_lock(&gp_timer_prov->mutex);
189 			/*
190 			 * Only set the state to idle if the timer has not been accessed
191 			 * from the callback
192 			 */
193 			if (p_timer->timer_state == CL_TIMER_RUNNING)
194 				p_timer->timer_state = CL_TIMER_IDLE;
195 
196 			/*
197 			 * Signal any thread trying to manipulate the timer
198 			 * that expired.
199 			 */
200 			pthread_cond_signal(&p_timer->cond);
201 		}
202 	}
203 	gp_timer_prov->thread = 0;
204 	pthread_mutex_unlock(&gp_timer_prov->mutex);
205 	pthread_exit(NULL);
206 }
207 
208 /* Timer implementation. */
209 void cl_timer_construct(IN cl_timer_t * const p_timer)
210 {
211 	memset(p_timer, 0, sizeof(cl_timer_t));
212 	p_timer->state = CL_UNINITIALIZED;
213 }
214 
215 cl_status_t cl_timer_init(IN cl_timer_t * const p_timer,
216 			  IN cl_pfn_timer_callback_t pfn_callback,
217 			  IN const void *const context)
218 {
219 	CL_ASSERT(p_timer);
220 	CL_ASSERT(pfn_callback);
221 
222 	cl_timer_construct(p_timer);
223 
224 	if (!gp_timer_prov)
225 		return (CL_ERROR);
226 
227 	/* Store timer parameters. */
228 	p_timer->pfn_callback = pfn_callback;
229 	p_timer->context = context;
230 
231 	/* Mark the timer as idle. */
232 	p_timer->timer_state = CL_TIMER_IDLE;
233 
234 	/* Create the condition variable that is used when cancelling a timer. */
235 	pthread_cond_init(&p_timer->cond, NULL);
236 
237 	p_timer->state = CL_INITIALIZED;
238 
239 	return (CL_SUCCESS);
240 }
241 
242 void cl_timer_destroy(IN cl_timer_t * const p_timer)
243 {
244 	CL_ASSERT(p_timer);
245 	CL_ASSERT(cl_is_state_valid(p_timer->state));
246 
247 	if (p_timer->state == CL_INITIALIZED)
248 		cl_timer_stop(p_timer);
249 
250 	p_timer->state = CL_UNINITIALIZED;
251 
252 	/* is it possible we have some threads waiting on the cond now? */
253 	pthread_cond_broadcast(&p_timer->cond);
254 	pthread_cond_destroy(&p_timer->cond);
255 
256 }
257 
258 /*
259  * Return TRUE if timeout value 1 is earlier than timeout value 2.
260  */
261 static __inline boolean_t __cl_timer_is_earlier(IN struct timespec *p_timeout1,
262 						IN struct timespec *p_timeout2)
263 {
264 	return ((p_timeout1->tv_sec < p_timeout2->tv_sec) ||
265 		((p_timeout1->tv_sec == p_timeout2->tv_sec) &&
266 		 (p_timeout1->tv_nsec < p_timeout2->tv_nsec)));
267 }
268 
269 /*
270  * Search for a timer with an earlier timeout than the one provided by
271  * the context.  Both the list item and the context are pointers to
272  * a cl_timer_t structure with valid timeouts.
273  */
274 static cl_status_t __cl_timer_find(IN const cl_list_item_t * const p_list_item,
275 				   IN void *const context)
276 {
277 	cl_timer_t *p_in_list;
278 	cl_timer_t *p_new;
279 
280 	CL_ASSERT(p_list_item);
281 	CL_ASSERT(context);
282 
283 	p_in_list = (cl_timer_t *) p_list_item;
284 	p_new = (cl_timer_t *) context;
285 
286 	CL_ASSERT(p_in_list->state == CL_INITIALIZED);
287 	CL_ASSERT(p_new->state == CL_INITIALIZED);
288 
289 	CL_ASSERT(p_in_list->timer_state == CL_TIMER_QUEUED);
290 
291 	if (__cl_timer_is_earlier(&p_in_list->timeout, &p_new->timeout))
292 		return (CL_SUCCESS);
293 
294 	return (CL_NOT_FOUND);
295 }
296 
297 /*
298  * Calculate 'struct timespec' value that is the
299  * current time plus the 'time_ms' milliseconds.
300  */
301 static __inline void __cl_timer_calculate(IN const uint32_t time_ms,
302 					  OUT struct timespec * const p_timer)
303 {
304 	struct timeval curtime, deltatime, endtime;
305 
306 	gettimeofday(&curtime, NULL);
307 
308 	deltatime.tv_sec = time_ms / 1000;
309 	deltatime.tv_usec = (time_ms % 1000) * 1000;
310 	timeradd(&curtime, &deltatime, &endtime);
311 	p_timer->tv_sec = endtime.tv_sec;
312 	p_timer->tv_nsec = endtime.tv_usec * 1000;
313 }
314 
315 cl_status_t cl_timer_start(IN cl_timer_t * const p_timer,
316 			   IN const uint32_t time_ms)
317 {
318 	cl_list_item_t *p_list_item;
319 
320 	CL_ASSERT(p_timer);
321 	CL_ASSERT(p_timer->state == CL_INITIALIZED);
322 
323 	pthread_mutex_lock(&gp_timer_prov->mutex);
324 	/* Signal the timer provider thread to wake up. */
325 	pthread_cond_signal(&gp_timer_prov->cond);
326 
327 	/* Remove the timer from the queue if currently queued. */
328 	if (p_timer->timer_state == CL_TIMER_QUEUED)
329 		cl_qlist_remove_item(&gp_timer_prov->queue,
330 				     &p_timer->list_item);
331 
332 	__cl_timer_calculate(time_ms, &p_timer->timeout);
333 
334 	/* Add the timer to the queue. */
335 	if (cl_is_qlist_empty(&gp_timer_prov->queue)) {
336 		/* The timer list is empty.  Add to the head. */
337 		cl_qlist_insert_head(&gp_timer_prov->queue,
338 				     &p_timer->list_item);
339 	} else {
340 		/* Find the correct insertion place in the list for the timer. */
341 		p_list_item = cl_qlist_find_from_tail(&gp_timer_prov->queue,
342 						      __cl_timer_find, p_timer);
343 
344 		/* Insert the timer. */
345 		cl_qlist_insert_next(&gp_timer_prov->queue, p_list_item,
346 				     &p_timer->list_item);
347 	}
348 	/* Set the state. */
349 	p_timer->timer_state = CL_TIMER_QUEUED;
350 	pthread_mutex_unlock(&gp_timer_prov->mutex);
351 
352 	return (CL_SUCCESS);
353 }
354 
355 void cl_timer_stop(IN cl_timer_t * const p_timer)
356 {
357 	CL_ASSERT(p_timer);
358 	CL_ASSERT(p_timer->state == CL_INITIALIZED);
359 
360 	pthread_mutex_lock(&gp_timer_prov->mutex);
361 	switch (p_timer->timer_state) {
362 	case CL_TIMER_RUNNING:
363 		/* Wait for the callback to complete. */
364 		pthread_cond_wait(&p_timer->cond, &gp_timer_prov->mutex);
365 		/* Timer could have been queued while we were waiting. */
366 		if (p_timer->timer_state != CL_TIMER_QUEUED)
367 			break;
368 
369 	case CL_TIMER_QUEUED:
370 		/* Change the state of the timer. */
371 		p_timer->timer_state = CL_TIMER_IDLE;
372 		/* Remove the timer from the queue. */
373 		cl_qlist_remove_item(&gp_timer_prov->queue,
374 				     &p_timer->list_item);
375 		/*
376 		 * Signal the timer provider thread to move onto the
377 		 * next timer in the queue.
378 		 */
379 		pthread_cond_signal(&gp_timer_prov->cond);
380 		break;
381 
382 	case CL_TIMER_IDLE:
383 		break;
384 	}
385 	pthread_mutex_unlock(&gp_timer_prov->mutex);
386 }
387 
388 cl_status_t cl_timer_trim(IN cl_timer_t * const p_timer,
389 			  IN const uint32_t time_ms)
390 {
391 	struct timespec newtime;
392 	cl_status_t status;
393 
394 	CL_ASSERT(p_timer);
395 	CL_ASSERT(p_timer->state == CL_INITIALIZED);
396 
397 	pthread_mutex_lock(&gp_timer_prov->mutex);
398 
399 	__cl_timer_calculate(time_ms, &newtime);
400 
401 	if (p_timer->timer_state == CL_TIMER_QUEUED) {
402 		/* If the old time is earlier, do not trim it.  Just return. */
403 		if (__cl_timer_is_earlier(&p_timer->timeout, &newtime)) {
404 			pthread_mutex_unlock(&gp_timer_prov->mutex);
405 			return (CL_SUCCESS);
406 		}
407 	}
408 
409 	/* Reset the timer to the new timeout value. */
410 
411 	pthread_mutex_unlock(&gp_timer_prov->mutex);
412 	status = cl_timer_start(p_timer, time_ms);
413 
414 	return (status);
415 }
416 
417 uint64_t cl_get_time_stamp(void)
418 {
419 	uint64_t tstamp;
420 	struct timeval tv;
421 
422 	gettimeofday(&tv, NULL);
423 
424 	/* Convert the time of day into a microsecond timestamp. */
425 	tstamp = ((uint64_t) tv.tv_sec * 1000000) + (uint64_t) tv.tv_usec;
426 
427 	return (tstamp);
428 }
429 
430 uint32_t cl_get_time_stamp_sec(void)
431 {
432 	struct timeval tv;
433 
434 	gettimeofday(&tv, NULL);
435 
436 	return (tv.tv_sec);
437 }
438