xref: /illumos-gate/usr/src/uts/common/io/scsi/adapters/iscsi/iscsi_queue.c (revision 257873cfc1dd3337766407f80397db60a56f2f5a)
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 2008 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 
31 static 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  */
54 void
55 iscsi_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  */
68 void
69 iscsi_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  */
80 void
81 iscsi_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  */
106 void
107 iscsi_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  */
140 void
141 iscsi_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  */
192 void
193 iscsi_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_completed_cmd - used to add a command in completion queue
226  */
227 void
228 iscsi_enqueue_completed_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp)
229 {
230 	ASSERT(isp != NULL);
231 	ASSERT(icmdp != NULL);
232 
233 	mutex_enter(&isp->sess_queue_completion.mutex);
234 	if (icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED) {
235 		icmdp->cmd_state = ISCSI_CMD_STATE_COMPLETED;
236 	} else {
237 		/*
238 		 * This command has already been completed, probably
239 		 * through the abort code path. It should  be in
240 		 * the process of being returned to to the upper
241 		 * layers, so do nothing.
242 		 */
243 		mutex_exit(&isp->sess_queue_completion.mutex);
244 		return;
245 	}
246 	iscsi_enqueue_cmd_tail(&isp->sess_queue_completion.head,
247 	    &isp->sess_queue_completion.tail, icmdp);
248 	++isp->sess_queue_completion.count;
249 	mutex_exit(&isp->sess_queue_completion.mutex);
250 
251 	iscsi_thread_send_wakeup(isp->sess_ic_thread);
252 }
253 
254 /*
255  * iscsi_move_queue - used to move the whole contents of a queue
256  *
257  *   The source queue has to be initialized.  Its mutex is entered before
258  * doing the actual move.  The destination queue should be initialized.
259  * This function is intended to move a queue located in a shared location
260  * into local space.  No mutex is needed for the destination queue.
261  */
262 void
263 iscsi_move_queue(
264 	iscsi_queue_t	*src_queue,
265 	iscsi_queue_t	*dst_queue
266 )
267 {
268 	ASSERT(src_queue != NULL);
269 	ASSERT(dst_queue != NULL);
270 	mutex_enter(&src_queue->mutex);
271 	dst_queue->count = src_queue->count;
272 	dst_queue->head  = src_queue->head;
273 	dst_queue->tail  = src_queue->tail;
274 	src_queue->count = 0;
275 	src_queue->head  = NULL;
276 	src_queue->tail  = NULL;
277 	mutex_exit(&src_queue->mutex);
278 }
279 
280 /*
281  * +--------------------------------------------------------------------+
282  * | private functions							|
283  * +--------------------------------------------------------------------+
284  */
285 
286 /*
287  * iscsi_dequeue_cmd - used to remove a command from a queue
288  */
289 iscsi_status_t
290 iscsi_dequeue_cmd(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp)
291 {
292 #ifdef DEBUG
293 	iscsi_cmd_t	*tp	= NULL;
294 #endif
295 
296 	ASSERT(head != NULL);
297 	ASSERT(tail != NULL);
298 	ASSERT(icmdp != NULL);
299 
300 	if (*head == NULL) {
301 		/* empty queue, error */
302 		return (ISCSI_STATUS_INTERNAL_ERROR);
303 	} else if (*head == *tail) {
304 		/* one element queue */
305 		if (*head == icmdp) {
306 			*head = NULL;
307 			*tail = NULL;
308 		} else {
309 			return (ISCSI_STATUS_INTERNAL_ERROR);
310 		}
311 	} else {
312 		/* multi-element queue */
313 		if (*head == icmdp) {
314 			/* at the head */
315 			*head = icmdp->cmd_next;
316 			(*head)->cmd_prev = NULL;
317 		} else if (*tail == icmdp) {
318 			*tail = icmdp->cmd_prev;
319 			(*tail)->cmd_next = NULL;
320 		} else {
321 #ifdef DEBUG
322 			/* in the middle? */
323 			for (tp = (*head)->cmd_next; (tp != NULL) &&
324 			    (tp != icmdp); tp = tp->cmd_next)
325 				;
326 			if (tp == NULL) {
327 				/* not found */
328 				return (ISCSI_STATUS_INTERNAL_ERROR);
329 			}
330 #endif
331 			if (icmdp->cmd_prev == NULL) {
332 				return (ISCSI_STATUS_INTERNAL_ERROR);
333 			}
334 			icmdp->cmd_prev->cmd_next = icmdp->cmd_next;
335 			if (icmdp->cmd_next == NULL) {
336 				return (ISCSI_STATUS_INTERNAL_ERROR);
337 			}
338 			icmdp->cmd_next->cmd_prev = icmdp->cmd_prev;
339 		}
340 	}
341 
342 	/* icmdp no longer in the queue */
343 	icmdp->cmd_prev = NULL;
344 	icmdp->cmd_next = NULL;
345 	return (ISCSI_STATUS_SUCCESS);
346 }
347 
348 /*
349  * iscsi_enqueue_cmd_head - used to add a command to the head of a queue
350  */
351 void
352 iscsi_enqueue_cmd_head(iscsi_cmd_t **head, iscsi_cmd_t **tail,
353     iscsi_cmd_t *icmdp)
354 {
355 	ASSERT(icmdp != NULL);
356 	ASSERT(icmdp->cmd_next == NULL);
357 	ASSERT(icmdp->cmd_prev == NULL);
358 	ASSERT(icmdp != *head);
359 	ASSERT(icmdp != *tail);
360 
361 	if (*head == NULL) {
362 		/* empty queue */
363 		*head = *tail = icmdp;
364 		icmdp->cmd_prev = NULL;
365 		icmdp->cmd_next = NULL;
366 	} else {
367 		/* non-empty queue */
368 		icmdp->cmd_next = *head;
369 		icmdp->cmd_prev = NULL;
370 		(*head)->cmd_prev = icmdp;
371 		*head = icmdp;
372 	}
373 }
374 
375 /*
376  * iscsi_enqueue_cmd_tail - used to add a command to the tail of a queue
377  */
378 static void
379 iscsi_enqueue_cmd_tail(iscsi_cmd_t **head, iscsi_cmd_t **tail,
380     iscsi_cmd_t *icmdp)
381 {
382 	ASSERT(icmdp != NULL);
383 	ASSERT(icmdp->cmd_next == NULL);
384 	ASSERT(icmdp->cmd_prev == NULL);
385 	ASSERT(icmdp != *head);
386 	ASSERT(icmdp != *tail);
387 
388 	if (*head == NULL) {
389 		/* empty queue */
390 		*head = *tail = icmdp;
391 		icmdp->cmd_prev = NULL;
392 		icmdp->cmd_next = NULL;
393 	} else {
394 		/* non-empty queue */
395 		icmdp->cmd_next = NULL;
396 		icmdp->cmd_prev = *tail;
397 		(*tail)->cmd_next = icmdp;
398 		*tail = icmdp;
399 	}
400 }
401