1 /*
2  * Copyright (c) 2004-2007 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 #if HAVE_CONFIG_H
37 #  include <config.h>
38 #endif				/* HAVE_CONFIG_H */
39 
40 #include <math.h>
41 #include <stdlib.h>
42 #include <complib/cl_event_wheel.h>
43 #include <complib/cl_debug.h>
44 
45 #define CL_DBG(fmt, ...)
46 
47 static cl_status_t __event_will_age_before(IN const cl_list_item_t *
48 					   const p_list_item, IN void *context)
49 {
50 	uint64_t aging_time = *((uint64_t *) context);
51 	cl_event_wheel_reg_info_t *p_event;
52 
53 	p_event =
54 	    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t, list_item);
55 
56 	if (p_event->aging_time < aging_time)
57 		return CL_SUCCESS;
58 	else
59 		return CL_NOT_FOUND;
60 }
61 
62 static void __cl_event_wheel_callback(IN void *context)
63 {
64 	cl_event_wheel_t *p_event_wheel = (cl_event_wheel_t *) context;
65 	cl_list_item_t *p_list_item, *p_prev_event_list_item;
66 	cl_list_item_t *p_list_next_item;
67 	cl_event_wheel_reg_info_t *p_event;
68 	uint64_t current_time;
69 	uint64_t next_aging_time;
70 	uint32_t new_timeout;
71 	cl_status_t cl_status;
72 
73 	/* might be during closing ...  */
74 	if (p_event_wheel->closing)
75 		return;
76 
77 	current_time = cl_get_time_stamp();
78 
79 	if (NULL != p_event_wheel->p_external_lock)
80 
81 		/* Take care of the order of acquiring locks to avoid the deadlock!
82 		 * The external lock goes first.
83 		 */
84 		cl_spinlock_acquire(p_event_wheel->p_external_lock);
85 
86 	cl_spinlock_acquire(&p_event_wheel->lock);
87 
88 	p_list_item = cl_qlist_head(&p_event_wheel->events_wheel);
89 	if (p_list_item == cl_qlist_end(&p_event_wheel->events_wheel))
90 		/* the list is empty - nothing to do */
91 		goto Exit;
92 
93 	/* we found such an item.  get the p_event */
94 	p_event =
95 	    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t, list_item);
96 
97 	while (p_event->aging_time <= current_time) {
98 		/* this object has aged - invoke it's callback */
99 		if (p_event->pfn_aged_callback)
100 			next_aging_time =
101 			    p_event->pfn_aged_callback(p_event->key,
102 						       p_event->num_regs,
103 						       p_event->context);
104 		else
105 			next_aging_time = 0;
106 
107 		/* point to the next object in the wheel */
108 		p_list_next_item = cl_qlist_next(p_list_item);
109 
110 		/* We need to retire the event if the next aging time passed */
111 		if (next_aging_time < current_time) {
112 			/* remove it from the map */
113 			cl_qmap_remove_item(&p_event_wheel->events_map,
114 					    &(p_event->map_item));
115 
116 			/* pop p_event from the wheel */
117 			cl_qlist_remove_head(&p_event_wheel->events_wheel);
118 
119 			/* delete the event info object - allocated by cl_event_wheel_reg */
120 			free(p_event);
121 		} else {
122 			/* update the required aging time */
123 			p_event->aging_time = next_aging_time;
124 			p_event->num_regs++;
125 
126 			/* do not remove from the map  - but remove from the list head and
127 			   place in the correct position */
128 
129 			/* pop p_event from the wheel */
130 			cl_qlist_remove_head(&p_event_wheel->events_wheel);
131 
132 			/* find the event that ages just before */
133 			p_prev_event_list_item =
134 			    cl_qlist_find_from_tail(&p_event_wheel->
135 						    events_wheel,
136 						    __event_will_age_before,
137 						    &p_event->aging_time);
138 
139 			/* insert just after */
140 			cl_qlist_insert_next(&p_event_wheel->events_wheel,
141 					     p_prev_event_list_item,
142 					     &p_event->list_item);
143 
144 			/* as we have modified the list - restart from first item: */
145 			p_list_next_item =
146 			    cl_qlist_head(&p_event_wheel->events_wheel);
147 		}
148 
149 		/* advance to next event */
150 		p_list_item = p_list_next_item;
151 		if (p_list_item == cl_qlist_end(&p_event_wheel->events_wheel))
152 			/* the list is empty - nothing to do */
153 			break;
154 
155 		/* get the p_event */
156 		p_event =
157 		    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t,
158 				  list_item);
159 	}
160 
161 	/* We need to restart the timer only if the list is not empty now */
162 	if (p_list_item != cl_qlist_end(&p_event_wheel->events_wheel)) {
163 		/* get the p_event */
164 		p_event =
165 		    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t,
166 				  list_item);
167 
168 		/* start the timer to the timeout [msec] */
169 		new_timeout =
170 		    (uint32_t) ((p_event->aging_time - current_time + 500) / 1000);
171 		CL_DBG("__cl_event_wheel_callback: Restart timer in: "
172 		       "%u [msec]\n", new_timeout);
173 		cl_status = cl_timer_start(&p_event_wheel->timer, new_timeout);
174 		if (cl_status != CL_SUCCESS) {
175 			CL_DBG("__cl_event_wheel_callback : ERR 6200: "
176 			       "Failed to start timer\n");
177 		}
178 	}
179 
180 	/* release the lock */
181 Exit:
182 	cl_spinlock_release(&p_event_wheel->lock);
183 	if (NULL != p_event_wheel->p_external_lock)
184 		cl_spinlock_release(p_event_wheel->p_external_lock);
185 }
186 
187 /*
188  * Construct and Initialize
189  */
190 void cl_event_wheel_construct(IN cl_event_wheel_t * const p_event_wheel)
191 {
192 	cl_spinlock_construct(&(p_event_wheel->lock));
193 	cl_timer_construct(&(p_event_wheel->timer));
194 }
195 
196 cl_status_t cl_event_wheel_init(IN cl_event_wheel_t * const p_event_wheel)
197 {
198 	cl_status_t cl_status = CL_SUCCESS;
199 
200 	/* initialize */
201 	p_event_wheel->p_external_lock = NULL;
202 	p_event_wheel->closing = FALSE;
203 	cl_status = cl_spinlock_init(&(p_event_wheel->lock));
204 	if (cl_status != CL_SUCCESS)
205 		return cl_status;
206 	cl_qlist_init(&p_event_wheel->events_wheel);
207 	cl_qmap_init(&p_event_wheel->events_map);
208 
209 	/* init the timer with timeout */
210 	cl_status = cl_timer_init(&p_event_wheel->timer, __cl_event_wheel_callback, p_event_wheel);	/* cb context */
211 
212 	return cl_status;
213 }
214 
215 cl_status_t cl_event_wheel_init_ex(IN cl_event_wheel_t * const p_event_wheel,
216 				   IN cl_spinlock_t * p_external_lock)
217 {
218 	cl_status_t cl_status;
219 
220 	cl_status = cl_event_wheel_init(p_event_wheel);
221 	if (CL_SUCCESS != cl_status)
222 		return cl_status;
223 
224 	p_event_wheel->p_external_lock = p_external_lock;
225 	return cl_status;
226 }
227 
228 void cl_event_wheel_dump(IN cl_event_wheel_t * const p_event_wheel)
229 {
230 	cl_list_item_t *p_list_item;
231 	cl_event_wheel_reg_info_t __attribute__((__unused__)) *p_event;
232 
233 	p_list_item = cl_qlist_head(&p_event_wheel->events_wheel);
234 
235 	while (p_list_item != cl_qlist_end(&p_event_wheel->events_wheel)) {
236 		p_event =
237 		    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t,
238 				  list_item);
239 		CL_DBG("cl_event_wheel_dump: Found event key:<0x%"
240 		       PRIx64 ">, num_regs:%d, aging time:%" PRIu64 "\n",
241 		       p_event->key, p_event->num_regs, p_event->aging_time);
242 		p_list_item = cl_qlist_next(p_list_item);
243 	}
244 }
245 
246 void cl_event_wheel_destroy(IN cl_event_wheel_t * const p_event_wheel)
247 {
248 	cl_list_item_t *p_list_item;
249 	cl_map_item_t *p_map_item;
250 	cl_event_wheel_reg_info_t *p_event;
251 
252 	/* we need to get a lock */
253 	cl_spinlock_acquire(&p_event_wheel->lock);
254 
255 	cl_event_wheel_dump(p_event_wheel);
256 
257 	/* go over all the items in the list and remove them */
258 	p_list_item = cl_qlist_remove_head(&p_event_wheel->events_wheel);
259 	while (p_list_item != cl_qlist_end(&p_event_wheel->events_wheel)) {
260 		p_event =
261 		    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t,
262 				  list_item);
263 
264 		CL_DBG("cl_event_wheel_destroy: Found outstanding event"
265 		       " key:<0x%" PRIx64 ">\n", p_event->key);
266 
267 		/* remove it from the map */
268 		p_map_item = &(p_event->map_item);
269 		cl_qmap_remove_item(&p_event_wheel->events_map, p_map_item);
270 		free(p_event);	/* allocated by cl_event_wheel_reg */
271 		p_list_item =
272 		    cl_qlist_remove_head(&p_event_wheel->events_wheel);
273 	}
274 
275 	/* destroy the timer */
276 	cl_timer_destroy(&p_event_wheel->timer);
277 
278 	/* destroy the lock (this should be done without releasing - we don't want
279 	   any other run to grab the lock at this point. */
280 	cl_spinlock_release(&p_event_wheel->lock);
281 	cl_spinlock_destroy(&(p_event_wheel->lock));
282 }
283 
284 cl_status_t cl_event_wheel_reg(IN cl_event_wheel_t * const p_event_wheel,
285 			       IN const uint64_t key,
286 			       IN const uint64_t aging_time_usec,
287 			       IN cl_pfn_event_aged_cb_t pfn_callback,
288 			       IN void *const context)
289 {
290 	cl_event_wheel_reg_info_t *p_event;
291 	uint64_t timeout;
292 	uint32_t to;
293 	cl_status_t cl_status = CL_SUCCESS;
294 	cl_list_item_t *prev_event_list_item;
295 	cl_map_item_t *p_map_item;
296 
297 	/* Get the lock on the manager */
298 	cl_spinlock_acquire(&(p_event_wheel->lock));
299 
300 	cl_event_wheel_dump(p_event_wheel);
301 
302 	/* Make sure such a key does not exists */
303 	p_map_item = cl_qmap_get(&p_event_wheel->events_map, key);
304 	if (p_map_item != cl_qmap_end(&p_event_wheel->events_map)) {
305 		CL_DBG("cl_event_wheel_reg: Already exists key:0x%"
306 		       PRIx64 "\n", key);
307 
308 		/* already there - remove it from the list as it is getting a new time */
309 		p_event =
310 		    PARENT_STRUCT(p_map_item, cl_event_wheel_reg_info_t,
311 				  map_item);
312 
313 		/* remove the item from the qlist */
314 		cl_qlist_remove_item(&p_event_wheel->events_wheel,
315 				     &p_event->list_item);
316 		/* and the qmap */
317 		cl_qmap_remove_item(&p_event_wheel->events_map,
318 				    &p_event->map_item);
319 	} else {
320 		/* make a new one */
321 		p_event = (cl_event_wheel_reg_info_t *)
322 		    malloc(sizeof(cl_event_wheel_reg_info_t));
323 		p_event->num_regs = 0;
324 	}
325 
326 	p_event->key = key;
327 	p_event->aging_time = aging_time_usec;
328 	p_event->pfn_aged_callback = pfn_callback;
329 	p_event->context = context;
330 	p_event->num_regs++;
331 
332 	CL_DBG("cl_event_wheel_reg: Registering event key:0x%" PRIx64
333 	       " aging in %u [msec]\n", p_event->key,
334 	       (uint32_t) ((p_event->aging_time - cl_get_time_stamp()) / 1000));
335 
336 	/* If the list is empty - need to start the timer */
337 	if (cl_is_qlist_empty(&p_event_wheel->events_wheel)) {
338 		/* Edward Bortnikov 03/29/2003
339 		 * ++TBD Consider moving the timer manipulation behind the list manipulation.
340 		 */
341 
342 		/* calculate the new timeout */
343 		timeout =
344 		    (p_event->aging_time - cl_get_time_stamp() + 500) / 1000;
345 
346 		/* stop the timer if it is running */
347 
348 		/* Edward Bortnikov 03/29/2003
349 		 * Don't call cl_timer_stop() because it spins forever.
350 		 * cl_timer_start() will invoke cl_timer_stop() by itself.
351 		 *
352 		 * The problematic scenario is when __cl_event_wheel_callback()
353 		 * is in race condition with this code. It sets timer.in_timer_cb
354 		 * to TRUE and then blocks on p_event_wheel->lock. Following this,
355 		 * the call to cl_timer_stop() hangs. Following this, the whole system
356 		 * enters into a deadlock.
357 		 *
358 		 * cl_timer_stop(&p_event_wheel->timer);
359 		 */
360 
361 		/* The timeout for the cl_timer_start should be given as uint32_t.
362 		   if there is an overflow - warn about it. */
363 		to = (uint32_t) timeout;
364 		if (timeout > (uint32_t) timeout) {
365 			to = 0xffffffff;	/* max 32 bit timer */
366 			CL_DBG("cl_event_wheel_reg: timeout requested is "
367 			       "too large. Using timeout: %u\n", to);
368 		}
369 
370 		/* start the timer to the timeout [msec] */
371 		cl_status = cl_timer_start(&p_event_wheel->timer, to);
372 		if (cl_status != CL_SUCCESS) {
373 			CL_DBG("cl_event_wheel_reg : ERR 6203: "
374 			       "Failed to start timer\n");
375 			goto Exit;
376 		}
377 	}
378 
379 	/* insert the object to the qlist and the qmap */
380 
381 	/* BUT WE MUST INSERT IT IN A SORTED MANNER */
382 	prev_event_list_item =
383 	    cl_qlist_find_from_tail(&p_event_wheel->events_wheel,
384 				    __event_will_age_before,
385 				    &p_event->aging_time);
386 
387 	cl_qlist_insert_next(&p_event_wheel->events_wheel,
388 			     prev_event_list_item, &p_event->list_item);
389 
390 	cl_qmap_insert(&p_event_wheel->events_map, key, &(p_event->map_item));
391 
392 Exit:
393 	cl_spinlock_release(&p_event_wheel->lock);
394 
395 	return cl_status;
396 }
397 
398 void cl_event_wheel_unreg(IN cl_event_wheel_t * const p_event_wheel,
399 			  IN uint64_t key)
400 {
401 	cl_event_wheel_reg_info_t *p_event;
402 	cl_map_item_t *p_map_item;
403 
404 	CL_DBG("cl_event_wheel_unreg: " "Removing key:0x%" PRIx64 "\n", key);
405 
406 	cl_spinlock_acquire(&p_event_wheel->lock);
407 	p_map_item = cl_qmap_get(&p_event_wheel->events_map, key);
408 	if (p_map_item != cl_qmap_end(&p_event_wheel->events_map)) {
409 		/* we found such an item. */
410 		p_event =
411 		    PARENT_STRUCT(p_map_item, cl_event_wheel_reg_info_t,
412 				  map_item);
413 
414 		/* remove the item from the qlist */
415 		cl_qlist_remove_item(&p_event_wheel->events_wheel,
416 				     &(p_event->list_item));
417 		/* remove the item from the qmap */
418 		cl_qmap_remove_item(&p_event_wheel->events_map,
419 				    &(p_event->map_item));
420 
421 		CL_DBG("cl_event_wheel_unreg: Removed key:0x%" PRIx64 "\n",
422 		       key);
423 
424 		/* free the item */
425 		free(p_event);
426 	} else {
427 		CL_DBG("cl_event_wheel_unreg: did not find key:0x%" PRIx64
428 		       "\n", key);
429 	}
430 
431 	cl_spinlock_release(&p_event_wheel->lock);
432 }
433 
434 uint32_t cl_event_wheel_num_regs(IN cl_event_wheel_t * const p_event_wheel,
435 				 IN uint64_t key)
436 {
437 
438 	cl_event_wheel_reg_info_t *p_event;
439 	cl_map_item_t *p_map_item;
440 	uint32_t num_regs = 0;
441 
442 	/* try to find the key in the map */
443 	CL_DBG("cl_event_wheel_num_regs: Looking for key:0x%" PRIx64 "\n", key);
444 
445 	cl_spinlock_acquire(&p_event_wheel->lock);
446 	p_map_item = cl_qmap_get(&p_event_wheel->events_map, key);
447 	if (p_map_item != cl_qmap_end(&p_event_wheel->events_map)) {
448 		/* ok so we can simply return it's num_regs */
449 		p_event =
450 		    PARENT_STRUCT(p_map_item, cl_event_wheel_reg_info_t,
451 				  map_item);
452 		num_regs = p_event->num_regs;
453 	}
454 
455 	cl_spinlock_release(&p_event_wheel->lock);
456 	return (num_regs);
457 }
458 
459 #ifdef __CL_EVENT_WHEEL_TEST__
460 
461 /* Dump out the complete state of the event wheel */
462 void __cl_event_wheel_dump(IN cl_event_wheel_t * const p_event_wheel)
463 {
464 	cl_list_item_t *p_list_item;
465 	cl_map_item_t *p_map_item;
466 	cl_event_wheel_reg_info_t *p_event;
467 
468 	printf("************** Event Wheel Dump ***********************\n");
469 	printf("Event Wheel List has %u items:\n",
470 	       cl_qlist_count(&p_event_wheel->events_wheel));
471 
472 	p_list_item = cl_qlist_head(&p_event_wheel->events_wheel);
473 	while (p_list_item != cl_qlist_end(&p_event_wheel->events_wheel)) {
474 		p_event =
475 		    PARENT_STRUCT(p_list_item, cl_event_wheel_reg_info_t,
476 				  list_item);
477 		printf("Event key:0x%" PRIx64 " Context:%s NumRegs:%u\n",
478 		       p_event->key, (char *)p_event->context,
479 		       p_event->num_regs);
480 
481 		/* next */
482 		p_list_item = cl_qlist_next(p_list_item);
483 	}
484 
485 	printf("Event Map has %u items:\n",
486 	       cl_qmap_count(&p_event_wheel->events_map));
487 
488 	p_map_item = cl_qmap_head(&p_event_wheel->events_map);
489 	while (p_map_item != cl_qmap_end(&p_event_wheel->events_map)) {
490 		p_event =
491 		    PARENT_STRUCT(p_map_item, cl_event_wheel_reg_info_t,
492 				  map_item);
493 		printf("Event key:0x%" PRIx64 " Context:%s NumRegs:%u\n",
494 		       p_event->key, (char *)p_event->context,
495 		       p_event->num_regs);
496 
497 		/* next */
498 		p_map_item = cl_qmap_next(p_map_item);
499 	}
500 
501 }
502 
503 /* The callback for aging event */
504 /* We assume we pass a text context */
505 static uint64_t __test_event_aging(uint64_t key, uint32_t num_regs, void *context)
506 {
507 	printf("*****************************************************\n");
508 	printf("Aged key: 0x%" PRIx64 " Context:%s\n", key, (char *)context);
509 }
510 
511 int main()
512 {
513 	cl_event_wheel_t event_wheel;
514 	/*  uint64_t key; */
515 
516 	/* init complib */
517 	complib_init();
518 
519 	/* construct */
520 	cl_event_wheel_construct(&event_wheel);
521 
522 	/* init */
523 	cl_event_wheel_init(&event_wheel);
524 
525 	/* Start Playing */
526 	cl_event_wheel_reg(&event_wheel, 1,	/*  key */
527 			   cl_get_time_stamp() + 3000000,	/*  3 sec lifetime */
528 			   __test_event_aging,	/*  cb */
529 			   "The first Aging Event");
530 
531 	cl_event_wheel_reg(&event_wheel, 2,	/*  key */
532 			   cl_get_time_stamp() + 3000000,	/*  3 sec lifetime */
533 			   __test_event_aging,	/*  cb */
534 			   "The Second Aging Event");
535 
536 	cl_event_wheel_reg(&event_wheel, 3,	/*  key */
537 			   cl_get_time_stamp() + 3500000,	/*  3 sec lifetime */
538 			   __test_event_aging,	/*  cb */
539 			   "The Third Aging Event");
540 
541 	__cl_event_wheel_dump(&event_wheel);
542 
543 	sleep(2);
544 	cl_event_wheel_reg(&event_wheel, 2,	/*  key */
545 			   cl_get_time_stamp() + 8000000,	/*  3 sec lifetime */
546 			   __test_event_aging,	/*  cb */
547 			   "The Second Aging Event Moved");
548 
549 	__cl_event_wheel_dump(&event_wheel);
550 
551 	sleep(1);
552 	/* remove the third event */
553 	cl_event_wheel_unreg(&event_wheel, 3);	/*  key */
554 
555 	/* get the number of registrations for the keys */
556 	printf("Event 1 Registered: %u\n",
557 	       cl_event_wheel_num_regs(&event_wheel, 1));
558 	printf("Event 2 Registered: %u\n",
559 	       cl_event_wheel_num_regs(&event_wheel, 2));
560 
561 	sleep(5);
562 	/* destroy */
563 	cl_event_wheel_destroy(&event_wheel);
564 
565 	complib_exit();
566 
567 	return (0);
568 }
569 
570 #endif				/* __CL_EVENT_WHEEL_TEST__ */
571