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 * Copyright 2000 by Cisco Systems, Inc.  All rights reserved.
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * iSCSI Software Initiator
27 */
28
29#include "iscsi.h"		/* main header */
30
31static void iscsi_enqueue_cmd_tail(iscsi_cmd_t **head, iscsi_cmd_t **tail,
32    iscsi_cmd_t *icmdp);
33
34
35/*
36 * +--------------------------------------------------------------------+
37 * | public queue functions						|
38 * +--------------------------------------------------------------------+
39 *
40 * Public queue locking rules.  When acquiring multiple queue locks
41 * they MUST always be acquired in a forward order.  If a lock is
42 * aquire in a reverese order it could lead to a deadlock panic.
43 * The forward order of locking is described as shown below.
44 *
45 *		 pending -> cmdsn -> active -> completion
46 *
47 * If a cmd_mutex is held, it is either held after the pending queue
48 * mutex or after the active queue mutex.
49 */
50
51/*
52 * iscsi_init_queue - used to initialize iscsi queue
53 */
54void
55iscsi_init_queue(iscsi_queue_t *queue)
56{
57	ASSERT(queue != NULL);
58
59	queue->head = NULL;
60	queue->tail = NULL;
61	queue->count = 0;
62	mutex_init(&queue->mutex, NULL, MUTEX_DRIVER, NULL);
63}
64
65/*
66 * iscsi_destroy_queue - used to terminate iscsi queue
67 */
68void
69iscsi_destroy_queue(iscsi_queue_t *queue)
70{
71	ASSERT(queue != NULL);
72	ASSERT(queue->count == 0);
73
74	mutex_destroy(&queue->mutex);
75}
76
77/*
78 * iscsi_enqueue_pending_cmd - used to add a command in a pending queue
79 */
80void
81iscsi_enqueue_pending_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp)
82{
83	ASSERT(isp != NULL);
84	ASSERT(icmdp != NULL);
85	ASSERT(mutex_owned(&isp->sess_queue_pending.mutex));
86
87	icmdp->cmd_state = ISCSI_CMD_STATE_PENDING;
88	if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) {
89		iscsi_enqueue_cmd_tail(&isp->sess_queue_pending.head,
90		    &isp->sess_queue_pending.tail, icmdp);
91		isp->sess_queue_pending.count++;
92		KSTAT_WAITQ_ENTER(isp);
93	} else {
94		iscsi_enqueue_cmd_head(&isp->sess_queue_pending.head,
95		    &isp->sess_queue_pending.tail, icmdp);
96		isp->sess_queue_pending.count++;
97		KSTAT_WAITQ_ENTER(isp);
98	}
99	iscsi_sess_redrive_io(isp);
100}
101
102
103/*
104 * iscsi_dequeue_pending_cmd - used to remove a command from a pending queue
105 */
106void
107iscsi_dequeue_pending_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp)
108{
109	iscsi_status_t rval = ISCSI_STATUS_SUCCESS;
110
111	ASSERT(isp != NULL);
112	ASSERT(icmdp != NULL);
113	ASSERT(mutex_owned(&isp->sess_queue_pending.mutex));
114
115	rval = iscsi_dequeue_cmd(&isp->sess_queue_pending.head,
116	    &isp->sess_queue_pending.tail, icmdp);
117	if (ISCSI_SUCCESS(rval)) {
118		isp->sess_queue_pending.count--;
119		if (((kstat_io_t *)(&isp->stats.ks_io_data))->wcnt) {
120			KSTAT_WAITQ_EXIT(isp);
121		} else {
122			cmn_err(CE_WARN,
123			    "kstat wcnt == 0 when exiting waitq,"
124			    " please check\n");
125		}
126	} else {
127		ASSERT(FALSE);
128	}
129}
130
131/*
132 * iscsi_enqueue_active_cmd - used to add a command in a active queue
133 *
134 * This interface attempts to keep newer items are on the tail,
135 * older items are on the head.  But, Do not assume that the list
136 * is completely sorted.  If someone attempts to enqueue an item
137 * that already has cmd_lbolt_active assigned and is older than
138 * the current head, otherwise add to the tail.
139 */
140void
141iscsi_enqueue_active_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp)
142{
143	iscsi_sess_t		*isp    = NULL;
144
145	ASSERT(icp != NULL);
146	ASSERT(icmdp != NULL);
147	isp = icp->conn_sess;
148	ASSERT(isp != NULL);
149
150	/*
151	 * When receiving data associated to a command it
152	 * is temporarily removed from the active queue.
153	 * Then once the data receive is completed it may
154	 * be returned to the active queue.  If this was
155	 * an aborting command we need to preserve its
156	 * state.
157	 */
158	if (icmdp->cmd_state != ISCSI_CMD_STATE_ABORTING) {
159		icmdp->cmd_state = ISCSI_CMD_STATE_ACTIVE;
160	}
161
162	/*
163	 * It's possible that this is not a newly issued icmdp - we may
164	 * have tried to abort it but the abort failed or was rejected
165	 * and we are putting it back on the active list. So if it is older
166	 * than the head of the active queue, put it at the head to keep
167	 * the CommandTimeout valid.
168	 */
169	if (icmdp->cmd_lbolt_active == 0) {
170		icmdp->cmd_lbolt_active = ddi_get_lbolt();
171		iscsi_enqueue_cmd_tail(&icp->conn_queue_active.head,
172		    &icp->conn_queue_active.tail, icmdp);
173	} else if ((icp->conn_queue_active.head != NULL) &&
174	    (icmdp->cmd_lbolt_active <
175	    icp->conn_queue_active.head->cmd_lbolt_active)) {
176		iscsi_enqueue_cmd_head(&icp->conn_queue_active.head,
177		    &icp->conn_queue_active.tail, icmdp);
178	} else {
179		iscsi_enqueue_cmd_tail(&icp->conn_queue_active.head,
180		    &icp->conn_queue_active.tail, icmdp);
181	}
182	icp->conn_queue_active.count++;
183
184	if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) {
185		KSTAT_RUNQ_ENTER(isp);
186	}
187}
188
189/*
190 * iscsi_dequeue_active_cmd - used to remove a command from a active queue
191 */
192void
193iscsi_dequeue_active_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp)
194{
195	iscsi_status_t	rval	= ISCSI_STATUS_SUCCESS;
196	iscsi_sess_t	*isp	= NULL;
197
198	ASSERT(icp != NULL);
199	ASSERT(icmdp != NULL);
200	isp = icp->conn_sess;
201	ASSERT(isp != NULL);
202	ASSERT(mutex_owned(&icp->conn_queue_active.mutex));
203
204	rval = iscsi_dequeue_cmd(&icp->conn_queue_active.head,
205	    &icp->conn_queue_active.tail, icmdp);
206
207	if (ISCSI_SUCCESS(rval)) {
208		icp->conn_queue_active.count--;
209
210		if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) {
211			if (((kstat_io_t *)(&isp->stats.ks_io_data))->rcnt) {
212				KSTAT_RUNQ_EXIT(isp);
213			} else {
214				cmn_err(CE_WARN,
215				    "kstat rcnt == 0 when exiting runq,"
216				    " please check\n");
217			}
218		}
219	} else {
220		ASSERT(FALSE);
221	}
222}
223
224/*
225 * iscsi_enqueue_idm_aborting_cmd - used to add a command to the queue
226 * representing command waiting for a callback from IDM for aborting
227 *
228 * Not sorted
229 */
230void
231iscsi_enqueue_idm_aborting_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp)
232{
233	iscsi_sess_t		*isp    = NULL;
234
235	ASSERT(icp != NULL);
236	ASSERT(icmdp != NULL);
237	isp = icp->conn_sess;
238	ASSERT(isp != NULL);
239	ASSERT(icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI);
240	ASSERT(mutex_owned(&icp->conn_queue_idm_aborting.mutex));
241
242	icmdp->cmd_state = ISCSI_CMD_STATE_IDM_ABORTING;
243	icmdp->cmd_lbolt_idm_aborting = ddi_get_lbolt();
244	iscsi_enqueue_cmd_tail(&icp->conn_queue_idm_aborting.head,
245	    &icp->conn_queue_idm_aborting.tail, icmdp);
246	icp->conn_queue_idm_aborting.count++;
247}
248
249/*
250 * iscsi_dequeue_idm_aborting_cmd - used to remove a command from the queue
251 * representing commands waiting for a callback from IDM for aborting.
252 */
253void
254iscsi_dequeue_idm_aborting_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp)
255{
256	iscsi_sess_t	*isp	= NULL;
257
258	ASSERT(icp != NULL);
259	ASSERT(icmdp != NULL);
260	isp = icp->conn_sess;
261	ASSERT(isp != NULL);
262	ASSERT(mutex_owned(&icp->conn_queue_idm_aborting.mutex));
263
264	(void) iscsi_dequeue_cmd(&icp->conn_queue_idm_aborting.head,
265	    &icp->conn_queue_idm_aborting.tail, icmdp);
266	icp->conn_queue_idm_aborting.count--;
267}
268
269/*
270 * iscsi_enqueue_completed_cmd - used to add a command in completion queue
271 */
272void
273iscsi_enqueue_completed_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp)
274{
275	ASSERT(isp != NULL);
276	ASSERT(icmdp != NULL);
277
278	mutex_enter(&isp->sess_queue_completion.mutex);
279	if (icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED) {
280		icmdp->cmd_state = ISCSI_CMD_STATE_COMPLETED;
281	} else {
282		/*
283		 * This command has already been completed, probably
284		 * through the abort code path. It should  be in
285		 * the process of being returned to to the upper
286		 * layers, so do nothing.
287		 */
288		mutex_exit(&isp->sess_queue_completion.mutex);
289		return;
290	}
291	iscsi_enqueue_cmd_tail(&isp->sess_queue_completion.head,
292	    &isp->sess_queue_completion.tail, icmdp);
293	++isp->sess_queue_completion.count;
294	mutex_exit(&isp->sess_queue_completion.mutex);
295
296	(void) iscsi_thread_send_wakeup(isp->sess_ic_thread);
297}
298
299/*
300 * iscsi_move_queue - used to move the whole contents of a queue
301 *
302 *   The source queue has to be initialized.  Its mutex is entered before
303 * doing the actual move.  The destination queue should be initialized.
304 * This function is intended to move a queue located in a shared location
305 * into local space.  No mutex is needed for the destination queue.
306 */
307void
308iscsi_move_queue(
309	iscsi_queue_t	*src_queue,
310	iscsi_queue_t	*dst_queue
311)
312{
313	ASSERT(src_queue != NULL);
314	ASSERT(dst_queue != NULL);
315	mutex_enter(&src_queue->mutex);
316	dst_queue->count = src_queue->count;
317	dst_queue->head  = src_queue->head;
318	dst_queue->tail  = src_queue->tail;
319	src_queue->count = 0;
320	src_queue->head  = NULL;
321	src_queue->tail  = NULL;
322	mutex_exit(&src_queue->mutex);
323}
324
325/*
326 * +--------------------------------------------------------------------+
327 * | private functions							|
328 * +--------------------------------------------------------------------+
329 */
330
331/*
332 * iscsi_dequeue_cmd - used to remove a command from a queue
333 */
334iscsi_status_t
335iscsi_dequeue_cmd(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp)
336{
337#ifdef DEBUG
338	iscsi_cmd_t	*tp	= NULL;
339#endif
340
341	ASSERT(head != NULL);
342	ASSERT(tail != NULL);
343	ASSERT(icmdp != NULL);
344
345	if (*head == NULL) {
346		/* empty queue, error */
347		return (ISCSI_STATUS_INTERNAL_ERROR);
348	} else if (*head == *tail) {
349		/* one element queue */
350		if (*head == icmdp) {
351			*head = NULL;
352			*tail = NULL;
353		} else {
354			return (ISCSI_STATUS_INTERNAL_ERROR);
355		}
356	} else {
357		/* multi-element queue */
358		if (*head == icmdp) {
359			/* at the head */
360			*head = icmdp->cmd_next;
361			(*head)->cmd_prev = NULL;
362		} else if (*tail == icmdp) {
363			*tail = icmdp->cmd_prev;
364			(*tail)->cmd_next = NULL;
365		} else {
366#ifdef DEBUG
367			/* in the middle? */
368			for (tp = (*head)->cmd_next; (tp != NULL) &&
369			    (tp != icmdp); tp = tp->cmd_next)
370				;
371			if (tp == NULL) {
372				/* not found */
373				return (ISCSI_STATUS_INTERNAL_ERROR);
374			}
375#endif
376			if (icmdp->cmd_prev == NULL) {
377				return (ISCSI_STATUS_INTERNAL_ERROR);
378			}
379			icmdp->cmd_prev->cmd_next = icmdp->cmd_next;
380			if (icmdp->cmd_next == NULL) {
381				return (ISCSI_STATUS_INTERNAL_ERROR);
382			}
383			icmdp->cmd_next->cmd_prev = icmdp->cmd_prev;
384		}
385	}
386
387	/* icmdp no longer in the queue */
388	icmdp->cmd_prev = NULL;
389	icmdp->cmd_next = NULL;
390	return (ISCSI_STATUS_SUCCESS);
391}
392
393/*
394 * iscsi_enqueue_cmd_head - used to add a command to the head of a queue
395 */
396void
397iscsi_enqueue_cmd_head(iscsi_cmd_t **head, iscsi_cmd_t **tail,
398    iscsi_cmd_t *icmdp)
399{
400	ASSERT(icmdp != NULL);
401	ASSERT(icmdp->cmd_next == NULL);
402	ASSERT(icmdp->cmd_prev == NULL);
403	ASSERT(icmdp != *head);
404	ASSERT(icmdp != *tail);
405
406	if (*head == NULL) {
407		/* empty queue */
408		*head = *tail = icmdp;
409		icmdp->cmd_prev = NULL;
410		icmdp->cmd_next = NULL;
411	} else {
412		/* non-empty queue */
413		icmdp->cmd_next = *head;
414		icmdp->cmd_prev = NULL;
415		(*head)->cmd_prev = icmdp;
416		*head = icmdp;
417	}
418}
419
420/*
421 * iscsi_enqueue_cmd_tail - used to add a command to the tail of a queue
422 */
423static void
424iscsi_enqueue_cmd_tail(iscsi_cmd_t **head, iscsi_cmd_t **tail,
425    iscsi_cmd_t *icmdp)
426{
427	ASSERT(icmdp != NULL);
428	ASSERT(icmdp->cmd_next == NULL);
429	ASSERT(icmdp->cmd_prev == NULL);
430	ASSERT(icmdp != *head);
431	ASSERT(icmdp != *tail);
432
433	if (*head == NULL) {
434		/* empty queue */
435		*head = *tail = icmdp;
436		icmdp->cmd_prev = NULL;
437		icmdp->cmd_next = NULL;
438	} else {
439		/* non-empty queue */
440		icmdp->cmd_next = NULL;
441		icmdp->cmd_prev = *tail;
442		(*tail)->cmd_next = icmdp;
443		*tail = icmdp;
444	}
445}
446