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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1999-2000 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * hci1394_tlist.c
31  *   This implements a timed double linked list.
32  *   This list supports:
33  *	- addition of node to the end of the list
34  *	- atomic deletion of node anywhere in list
35  *	- get and remove node from head of list
36  *	- enable/disable of timeout feature
37  *	- timeout feature, if enabled, will remove each node on the list which
38  *	  has been on the list for > timeout.  The callback provided will be
39  *	  called for each node removed. The worst case time is around
40  *	  timer_resolution after the timeout has occurred (i.e. if you set the
41  *	  timer resolution to 50uS and the timeout to 100uS, you could get the
42  *	  callback anywhere from 100uS to 150uS from when you added the node to
43  *	  the list.  This is a general statement and ignores things like
44  *	  interrupt latency, context switching, etc.  So if you see a time
45  *	  around 155uS, don't complain :-)
46  *	- The timer is only used when something is on the list
47  */
48 
49 #include <sys/kmem.h>
50 #include <sys/types.h>
51 #include <sys/conf.h>
52 #include <sys/ddi.h>
53 #include <sys/sunddi.h>
54 #include <sys/types.h>
55 
56 #include <sys/1394/adapters/hci1394.h>
57 
58 
59 static clock_t t1394_tlist_nsectohz(hrtime_t  nS);
60 static void hci1394_tlist_remove(hci1394_tlist_t *list,
61     hci1394_tlist_node_t *node);
62 static void hci1394_tlist_callback(void *tlist_handle);
63 
64 
65 /*
66  * hci1394_tlist_init()
67  *    Initialize the tlist.  The list will be protected by a mutex at the
68  *    iblock_cookie passed in.  init() returns a handle to be used for the rest
69  *    of the functions. If you do not wish to use the timeout feature, set
70  *    (hci1394_timer_t *) to null.
71  */
72 void
73 hci1394_tlist_init(hci1394_drvinfo_t *drvinfo, hci1394_tlist_timer_t *timer,
74     hci1394_tlist_handle_t *tlist_handle)
75 {
76 	hci1394_tlist_t *list;
77 
78 
79 	ASSERT(tlist_handle != NULL);
80 	TNF_PROBE_0_DEBUG(hci1394_tlist_init_enter, HCI1394_TNF_HAL_STACK, "");
81 
82 	/* try to alloc the space to keep track of the list */
83 	list = kmem_alloc(sizeof (hci1394_tlist_t), KM_SLEEP);
84 
85 	/* setup the return parameter */
86 	*tlist_handle = list;
87 
88 	/* initialize the list structure */
89 	list->tl_drvinfo = drvinfo;
90 	list->tl_state = HCI1394_TLIST_TIMEOUT_OFF;
91 	list->tl_head = NULL;
92 	list->tl_tail = NULL;
93 	if (timer == NULL) {
94 		list->tl_timer_enabled = B_FALSE;
95 	} else {
96 		ASSERT(timer->tlt_callback != NULL);
97 		list->tl_timer_enabled = B_TRUE;
98 		list->tl_timer_info = *timer;
99 	}
100 	mutex_init(&list->tl_mutex, NULL, MUTEX_DRIVER,
101 	    drvinfo->di_iblock_cookie);
102 
103 	TNF_PROBE_0_DEBUG(hci1394_tlist_init_exit, HCI1394_TNF_HAL_STACK, "");
104 }
105 
106 
107 /*
108  * hci1394_tlist_fini()
109  *    Frees up the space allocated in init().  Notice that a pointer to the
110  *    handle is used for the parameter.  fini() will set your handle to NULL
111  *    before returning. Make sure that any pending timeouts are canceled.
112  */
113 void
114 hci1394_tlist_fini(hci1394_tlist_handle_t *tlist_handle)
115 {
116 	hci1394_tlist_t *list;
117 
118 
119 	ASSERT(tlist_handle != NULL);
120 	TNF_PROBE_0_DEBUG(hci1394_tlist_fini_enter, HCI1394_TNF_HAL_STACK, "");
121 
122 	list = (hci1394_tlist_t *)*tlist_handle;
123 	hci1394_tlist_timeout_cancel(list);
124 	mutex_destroy(&list->tl_mutex);
125 	kmem_free(list, sizeof (hci1394_tlist_t));
126 
127 	/* set handle to null.  This helps catch bugs. */
128 	*tlist_handle = NULL;
129 
130 	TNF_PROBE_0_DEBUG(hci1394_tlist_fini_exit, HCI1394_TNF_HAL_STACK, "");
131 }
132 
133 
134 /*
135  * hci1394_tlist_add()
136  *    Add the node to the tail of the linked list. The list is protected by a
137  *    mutex at the iblock_cookie passed in during init.
138  */
139 void
140 hci1394_tlist_add(hci1394_tlist_handle_t tlist_handle,
141     hci1394_tlist_node_t *node)
142 {
143 	ASSERT(tlist_handle != NULL);
144 	ASSERT(node != NULL);
145 	TNF_PROBE_0_DEBUG(hci1394_tlist_add_enter, HCI1394_TNF_HAL_STACK, "");
146 
147 	mutex_enter(&tlist_handle->tl_mutex);
148 
149 	/* add's always go at the end of the list */
150 	node->tln_next = NULL;
151 
152 	/* Set state that this node is currently on the tlist */
153 	node->tln_on_list = B_TRUE;
154 
155 	/* enter in the expire time (in uS) */
156 	if (tlist_handle->tl_timer_enabled == B_TRUE) {
157 		node->tln_expire_time = gethrtime() +
158 		    tlist_handle->tl_timer_info.tlt_timeout;
159 	}
160 
161 	/* if there is nothing in the list */
162 	if (tlist_handle->tl_tail == NULL) {
163 		tlist_handle->tl_head = node;
164 		tlist_handle->tl_tail = node;
165 		node->tln_prev = NULL;
166 
167 		if ((tlist_handle->tl_timer_enabled == B_TRUE) &&
168 		    (tlist_handle->tl_state == HCI1394_TLIST_TIMEOUT_OFF)) {
169 			/* turn the timer on */
170 			tlist_handle->tl_timeout_id = timeout(
171 			    hci1394_tlist_callback, tlist_handle,
172 			    t1394_tlist_nsectohz(
173 			    tlist_handle->tl_timer_info.tlt_timer_resolution));
174 			tlist_handle->tl_state = HCI1394_TLIST_TIMEOUT_ON;
175 		}
176 	} else {
177 		/* put the node on the end of the list */
178 		tlist_handle->tl_tail->tln_next = node;
179 		node->tln_prev = tlist_handle->tl_tail;
180 		tlist_handle->tl_tail = node;
181 		/*
182 		 * if timeouts are enabled,  we don't have to call
183 		 * timeout() because the timer is already on.
184 		 */
185 	}
186 
187 	mutex_exit(&tlist_handle->tl_mutex);
188 
189 	TNF_PROBE_0_DEBUG(hci1394_tlist_add_exit, HCI1394_TNF_HAL_STACK, "");
190 }
191 
192 
193 /*
194  * hci1394_tlist_delete()
195  *    Remove the node from the list.  The node can be anywhere in the list. Make
196  *    sure that the node is only removed once since different threads maybe
197  *    trying to delete the same node at the same time.
198  */
199 int
200 hci1394_tlist_delete(hci1394_tlist_handle_t tlist_handle,
201     hci1394_tlist_node_t *node)
202 {
203 	ASSERT(tlist_handle != NULL);
204 	ASSERT(node != NULL);
205 	TNF_PROBE_0_DEBUG(hci1394_tlist_delete_enter,
206 	    HCI1394_TNF_HAL_STACK, "");
207 
208 	mutex_enter(&tlist_handle->tl_mutex);
209 
210 	/*
211 	 * check for race condition.  Someone else may have already removed this
212 	 * node from the list. hci1394_tlist_delete() supports two threads
213 	 * trying to delete the node at the same time. The "losing" thread will
214 	 * have DDI_FAILURE returned.
215 	 */
216 	if (node->tln_on_list == B_FALSE) {
217 		mutex_exit(&tlist_handle->tl_mutex);
218 		TNF_PROBE_0_DEBUG(hci1394_tlist_delete_exit,
219 		    HCI1394_TNF_HAL_STACK, "");
220 		return (DDI_FAILURE);
221 	}
222 
223 	hci1394_tlist_remove(tlist_handle, node);
224 	mutex_exit(&tlist_handle->tl_mutex);
225 
226 	TNF_PROBE_0_DEBUG(hci1394_tlist_delete_exit, HCI1394_TNF_HAL_STACK, "");
227 	return (DDI_SUCCESS);
228 }
229 
230 
231 /*
232  * hci1394_tlist_get()
233  *    get the node at the head of the linked list. This function also removes
234  *    the node from the list.
235  */
236 void
237 hci1394_tlist_get(hci1394_tlist_handle_t tlist_handle,
238     hci1394_tlist_node_t **node)
239 {
240 	ASSERT(tlist_handle != NULL);
241 	ASSERT(node != NULL);
242 	TNF_PROBE_0_DEBUG(hci1394_tlist_get_enter, HCI1394_TNF_HAL_STACK, "");
243 
244 	mutex_enter(&tlist_handle->tl_mutex);
245 
246 	/* set the return parameter */
247 	*node = tlist_handle->tl_head;
248 
249 	/* remove the node from the tlist */
250 	if (*node != NULL) {
251 		hci1394_tlist_remove(tlist_handle, *node);
252 	}
253 
254 	mutex_exit(&tlist_handle->tl_mutex);
255 
256 	TNF_PROBE_0_DEBUG(hci1394_tlist_get_exit, HCI1394_TNF_HAL_STACK, "");
257 }
258 
259 
260 /*
261  * hci1394_tlist_peek()
262  *    get the node at the head of the linked list. This function does not
263  *    remove the node from the list.
264  */
265 void
266 hci1394_tlist_peek(hci1394_tlist_handle_t tlist_handle,
267     hci1394_tlist_node_t **node)
268 {
269 	ASSERT(tlist_handle != NULL);
270 	ASSERT(node != NULL);
271 	TNF_PROBE_0_DEBUG(hci1394_tlist_peek_enter, HCI1394_TNF_HAL_STACK, "");
272 
273 	mutex_enter(&tlist_handle->tl_mutex);
274 	*node = tlist_handle->tl_head;
275 	mutex_exit(&tlist_handle->tl_mutex);
276 
277 	TNF_PROBE_0_DEBUG(hci1394_tlist_peek_exit, HCI1394_TNF_HAL_STACK, "");
278 }
279 
280 
281 /*
282  * hci1394_tlist_timeout_update()
283  *    update the timeout to a different value. timeout is in uS.  The update
284  *    does not happen immediately.  The new timeout will not take effect until
285  *    the all of nodes currently present in the list are gone. It only makes
286  *    sense to call this function when you have the timeout feature enabled.
287  */
288 void
289 hci1394_tlist_timeout_update(hci1394_tlist_handle_t tlist_handle,
290     hrtime_t timeout)
291 {
292 	ASSERT(tlist_handle != NULL);
293 	TNF_PROBE_0_DEBUG(hci1394_tlist_update_timeout_enter,
294 	    HCI1394_TNF_HAL_STACK, "");
295 
296 	/* set timeout to the new timeout */
297 	tlist_handle->tl_timer_info.tlt_timeout = timeout;
298 
299 	TNF_PROBE_0_DEBUG(hci1394_tlist_update_timeout_exit,
300 	    HCI1394_TNF_HAL_STACK, "");
301 }
302 
303 
304 /*
305  * hci1394_tlist_timeout_cancel()
306  *    cancel any scheduled timeouts.  This should be called after the list is
307  *    empty and there is no chance for any other nodes to be placed on the list.
308  *    This function is meant to be called during a suspend or detach.
309  */
310 void
311 hci1394_tlist_timeout_cancel(hci1394_tlist_handle_t tlist_handle)
312 {
313 	ASSERT(tlist_handle != NULL);
314 	TNF_PROBE_0_DEBUG(hci1394_tlist_timeout_cancel_enter,
315 	    HCI1394_TNF_HAL_STACK, "");
316 
317 	/*
318 	 * Cancel the timeout. Do NOT use the tlist mutex here. It could cause a
319 	 * deadlock.
320 	 */
321 	if (tlist_handle->tl_state == HCI1394_TLIST_TIMEOUT_ON) {
322 		(void) untimeout(tlist_handle->tl_timeout_id);
323 		tlist_handle->tl_state = HCI1394_TLIST_TIMEOUT_OFF;
324 	}
325 
326 	TNF_PROBE_0_DEBUG(hci1394_tlist_timeout_cancel_exit,
327 	    HCI1394_TNF_HAL_STACK, "");
328 }
329 
330 
331 /*
332  * hci1394_tlist_callback()
333  *    The callback we use for the timeout() function. See if there are any nodes
334  *    on the list which have timed out. If so, call the registered callback for
335  *    each timed out node. We always start looking at the top of the list since
336  *    the list is time sorted (oldest at the top).
337  */
338 static void
339 hci1394_tlist_callback(void *tlist_handle)
340 {
341 	hci1394_tlist_t *list;
342 	hci1394_tlist_node_t *node;
343 	hrtime_t current_time;
344 
345 
346 	ASSERT(tlist_handle != NULL);
347 	TNF_PROBE_0_DEBUG(hci1394_tlist_callback_enter,
348 	    HCI1394_TNF_HAL_STACK, "");
349 
350 	list = (hci1394_tlist_t *)tlist_handle;
351 
352 	mutex_enter(&list->tl_mutex);
353 
354 	/*
355 	 * if there is something on the list, check to see if the oldest has
356 	 * expired.  If there is nothing on the list, there is no reason to
357 	 * renew the timeout.
358 	 */
359 	node = list->tl_head;
360 	current_time = gethrtime();
361 	while (node != NULL) {
362 		/*
363 		 * if current time is greater than the time the command expires,
364 		 * AND, the expire time has not rolled over, then the command
365 		 * has timed out.
366 		 */
367 		if (((uint64_t)current_time >=
368 		    (uint64_t)node->tln_expire_time) &&
369 		    (((uint64_t)node->tln_expire_time -
370 		    (uint64_t)list->tl_timer_info.tlt_timeout) <
371 		    (uint64_t)node->tln_expire_time)) {
372 			/* remove the node from the tlist */
373 			hci1394_tlist_remove(list, node);
374 
375 			/*
376 			 * Call the timeout callback. We unlock the the mutex
377 			 * around the callback so that other transactions will
378 			 * not be blocked while the callback is running. This
379 			 * is OK to do here because we have already removed this
380 			 * entry from our list. This code should not reference
381 			 * "node" again after the callback! After the callback
382 			 * returns, we need to resync node to the head of the
383 			 * list since we released/acquired the list mutex around
384 			 * the callback.
385 			 */
386 			mutex_exit(&list->tl_mutex);
387 			list->tl_timer_info.tlt_callback(node,
388 			    list->tl_timer_info.tlt_callback_arg);
389 			mutex_enter(&list->tl_mutex);
390 			node = list->tl_head;
391 
392 		/*
393 		 * else, if current time is greater than the time the command
394 		 * expires, AND, current_time is not about to rollover. (this
395 		 * works since it is in the else and we periodically sample
396 		 * well below the rollover time)
397 		 */
398 		} else if ((uint64_t)(current_time >=
399 		    (uint64_t)node->tln_expire_time) &&
400 		    (((uint64_t)current_time +
401 		    (uint64_t)list->tl_timer_info.tlt_timeout) >
402 		    (uint64_t)current_time)) {
403 			/* remove the node from the tlist */
404 			hci1394_tlist_remove(list, node);
405 
406 			/*
407 			 * Call the timeout callback. We unlock the the mutex
408 			 * around the callback so that other transactions will
409 			 * not be blocked while the callback is running. This
410 			 * is OK to do here because we have already removed this
411 			 * entry from our list. This code should not reference
412 			 * "node" again after the callback! After the callback
413 			 * returns, we need to resync node to the head of the
414 			 * list since we released/acquired the list mutex around
415 			 * the callback.
416 			 */
417 			mutex_exit(&list->tl_mutex);
418 			list->tl_timer_info.tlt_callback(node,
419 			    list->tl_timer_info.tlt_callback_arg);
420 			mutex_enter(&list->tl_mutex);
421 			node = list->tl_head;
422 
423 		} else {
424 			/*
425 			 * this command has not timed out.
426 			 * Since this list is time sorted, we are
427 			 * done looking for nodes that have expired
428 			 */
429 			break;
430 		}
431 	}
432 
433 	/*
434 	 * if there are nodes still on the pending list, kick
435 	 * off the timer again.
436 	 */
437 	if (node != NULL) {
438 		list->tl_timeout_id = timeout(hci1394_tlist_callback, list,
439 		    t1394_tlist_nsectohz(
440 		    list->tl_timer_info.tlt_timer_resolution));
441 		list->tl_state = HCI1394_TLIST_TIMEOUT_ON;
442 	} else {
443 		list->tl_state = HCI1394_TLIST_TIMEOUT_OFF;
444 	}
445 
446 	mutex_exit(&list->tl_mutex);
447 
448 	TNF_PROBE_0_DEBUG(hci1394_tlist_callback_exit,
449 	    HCI1394_TNF_HAL_STACK, "");
450 }
451 
452 
453 /*
454  * hci1394_tlist_remove()
455  *    This is an internal function which removes the given node from the list.
456  *    The list MUST be locked before calling this function.
457  */
458 static void
459 hci1394_tlist_remove(hci1394_tlist_t *list, hci1394_tlist_node_t *node)
460 {
461 	ASSERT(list != NULL);
462 	ASSERT(node != NULL);
463 	ASSERT(node->tln_on_list == B_TRUE);
464 	ASSERT(MUTEX_HELD(&list->tl_mutex));
465 	TNF_PROBE_0_DEBUG(hci1394_tlist_remove_enter,
466 	    HCI1394_TNF_HAL_STACK, "");
467 
468 	/* if this is the only node on the list */
469 	if ((list->tl_head == node) &&
470 	    (list->tl_tail == node)) {
471 		list->tl_head = NULL;
472 		list->tl_tail = NULL;
473 
474 	/* if the node is at the head of the list */
475 	} else if (list->tl_head == node) {
476 		list->tl_head = node->tln_next;
477 		node->tln_next->tln_prev = NULL;
478 
479 	/* if the node is at the tail of the list */
480 	} else if (list->tl_tail == node) {
481 		list->tl_tail = node->tln_prev;
482 		node->tln_prev->tln_next = NULL;
483 
484 	/* if the node is in the middle of the list */
485 	} else {
486 		node->tln_prev->tln_next = node->tln_next;
487 		node->tln_next->tln_prev = node->tln_prev;
488 	}
489 
490 	/* Set state that this node has been removed from the list */
491 	node->tln_on_list = B_FALSE;
492 
493 	/* cleanup the node's link pointers */
494 	node->tln_prev = NULL;
495 	node->tln_next = NULL;
496 
497 	TNF_PROBE_0_DEBUG(hci1394_tlist_remove_exit, HCI1394_TNF_HAL_STACK, "");
498 }
499 
500 
501 /*
502  * t1394_tlist_nsectohz()
503  *     Convert nS to hz.  This allows us to call timeout() but keep our time
504  *     reference in nS.
505  */
506 #define	HCI1394_TLIST_nS_TO_uS(nS)  ((clock_t)(nS / 1000))
507 static clock_t t1394_tlist_nsectohz(hrtime_t  nS)
508 {
509 	return (drv_usectohz(HCI1394_TLIST_nS_TO_uS(nS)));
510 }
511