/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2000 by Cisco Systems, Inc. All rights reserved. * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * iSCSI Software Initiator */ #include "iscsi.h" /* main header */ static void iscsi_enqueue_cmd_tail(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp); /* * +--------------------------------------------------------------------+ * | public queue functions | * +--------------------------------------------------------------------+ * * Public queue locking rules. When acquiring multiple queue locks * they MUST always be acquired in a forward order. If a lock is * aquire in a reverese order it could lead to a deadlock panic. * The forward order of locking is described as shown below. * * pending -> cmdsn -> active -> completion * * If a cmd_mutex is held, it is either held after the pending queue * mutex or after the active queue mutex. */ /* * iscsi_init_queue - used to initialize iscsi queue */ void iscsi_init_queue(iscsi_queue_t *queue) { ASSERT(queue != NULL); queue->head = NULL; queue->tail = NULL; queue->count = 0; mutex_init(&queue->mutex, NULL, MUTEX_DRIVER, NULL); } /* * iscsi_destroy_queue - used to terminate iscsi queue */ void iscsi_destroy_queue(iscsi_queue_t *queue) { ASSERT(queue != NULL); ASSERT(queue->count == 0); mutex_destroy(&queue->mutex); } /* * iscsi_enqueue_pending_cmd - used to add a command in a pending queue */ void iscsi_enqueue_pending_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { ASSERT(isp != NULL); ASSERT(icmdp != NULL); ASSERT(mutex_owned(&isp->sess_queue_pending.mutex)); icmdp->cmd_state = ISCSI_CMD_STATE_PENDING; if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { iscsi_enqueue_cmd_tail(&isp->sess_queue_pending.head, &isp->sess_queue_pending.tail, icmdp); isp->sess_queue_pending.count++; KSTAT_WAITQ_ENTER(isp); } else { iscsi_enqueue_cmd_head(&isp->sess_queue_pending.head, &isp->sess_queue_pending.tail, icmdp); isp->sess_queue_pending.count++; KSTAT_WAITQ_ENTER(isp); } iscsi_sess_redrive_io(isp); } /* * iscsi_dequeue_pending_cmd - used to remove a command from a pending queue */ void iscsi_dequeue_pending_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; ASSERT(isp != NULL); ASSERT(icmdp != NULL); ASSERT(mutex_owned(&isp->sess_queue_pending.mutex)); rval = iscsi_dequeue_cmd(&isp->sess_queue_pending.head, &isp->sess_queue_pending.tail, icmdp); if (ISCSI_SUCCESS(rval)) { isp->sess_queue_pending.count--; if (((kstat_io_t *)(&isp->stats.ks_io_data))->wcnt) { KSTAT_WAITQ_EXIT(isp); } else { cmn_err(CE_WARN, "kstat wcnt == 0 when exiting waitq," " please check\n"); } } else { ASSERT(FALSE); } } /* * iscsi_enqueue_active_cmd - used to add a command in a active queue * * This interface attempts to keep newer items are on the tail, * older items are on the head. But, Do not assume that the list * is completely sorted. If someone attempts to enqueue an item * that already has cmd_lbolt_active assigned and is older than * the current head, otherwise add to the tail. */ void iscsi_enqueue_active_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp) { iscsi_sess_t *isp = NULL; ASSERT(icp != NULL); ASSERT(icmdp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); /* * When receiving data associated to a command it * is temporarily removed from the active queue. * Then once the data receive is completed it may * be returned to the active queue. If this was * an aborting command we need to preserve its * state. */ if (icmdp->cmd_state != ISCSI_CMD_STATE_ABORTING) { icmdp->cmd_state = ISCSI_CMD_STATE_ACTIVE; } /* * It's possible that this is not a newly issued icmdp - we may * have tried to abort it but the abort failed or was rejected * and we are putting it back on the active list. So if it is older * than the head of the active queue, put it at the head to keep * the CommandTimeout valid. */ if (icmdp->cmd_lbolt_active == 0) { icmdp->cmd_lbolt_active = ddi_get_lbolt(); iscsi_enqueue_cmd_tail(&icp->conn_queue_active.head, &icp->conn_queue_active.tail, icmdp); } else if ((icp->conn_queue_active.head != NULL) && (icmdp->cmd_lbolt_active < icp->conn_queue_active.head->cmd_lbolt_active)) { iscsi_enqueue_cmd_head(&icp->conn_queue_active.head, &icp->conn_queue_active.tail, icmdp); } else { iscsi_enqueue_cmd_tail(&icp->conn_queue_active.head, &icp->conn_queue_active.tail, icmdp); } icp->conn_queue_active.count++; if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { KSTAT_RUNQ_ENTER(isp); } } /* * iscsi_dequeue_active_cmd - used to remove a command from a active queue */ void iscsi_dequeue_active_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_sess_t *isp = NULL; ASSERT(icp != NULL); ASSERT(icmdp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); ASSERT(mutex_owned(&icp->conn_queue_active.mutex)); rval = iscsi_dequeue_cmd(&icp->conn_queue_active.head, &icp->conn_queue_active.tail, icmdp); if (ISCSI_SUCCESS(rval)) { icp->conn_queue_active.count--; if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { if (((kstat_io_t *)(&isp->stats.ks_io_data))->rcnt) { KSTAT_RUNQ_EXIT(isp); } else { cmn_err(CE_WARN, "kstat rcnt == 0 when exiting runq," " please check\n"); } } } else { ASSERT(FALSE); } } /* * iscsi_enqueue_idm_aborting_cmd - used to add a command to the queue * representing command waiting for a callback from IDM for aborting * * Not sorted */ void iscsi_enqueue_idm_aborting_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp) { iscsi_sess_t *isp = NULL; ASSERT(icp != NULL); ASSERT(icmdp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); ASSERT(icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI); ASSERT(mutex_owned(&icp->conn_queue_idm_aborting.mutex)); icmdp->cmd_state = ISCSI_CMD_STATE_IDM_ABORTING; icmdp->cmd_lbolt_idm_aborting = ddi_get_lbolt(); iscsi_enqueue_cmd_tail(&icp->conn_queue_idm_aborting.head, &icp->conn_queue_idm_aborting.tail, icmdp); icp->conn_queue_idm_aborting.count++; } /* * iscsi_dequeue_idm_aborting_cmd - used to remove a command from the queue * representing commands waiting for a callback from IDM for aborting. */ void iscsi_dequeue_idm_aborting_cmd(iscsi_conn_t *icp, iscsi_cmd_t *icmdp) { iscsi_sess_t *isp = NULL; ASSERT(icp != NULL); ASSERT(icmdp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); ASSERT(mutex_owned(&icp->conn_queue_idm_aborting.mutex)); (void) iscsi_dequeue_cmd(&icp->conn_queue_idm_aborting.head, &icp->conn_queue_idm_aborting.tail, icmdp); icp->conn_queue_idm_aborting.count--; } /* * iscsi_enqueue_completed_cmd - used to add a command in completion queue */ void iscsi_enqueue_completed_cmd(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { ASSERT(isp != NULL); ASSERT(icmdp != NULL); mutex_enter(&isp->sess_queue_completion.mutex); if (icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED) { icmdp->cmd_state = ISCSI_CMD_STATE_COMPLETED; } else { /* * This command has already been completed, probably * through the abort code path. It should be in * the process of being returned to to the upper * layers, so do nothing. */ mutex_exit(&isp->sess_queue_completion.mutex); return; } iscsi_enqueue_cmd_tail(&isp->sess_queue_completion.head, &isp->sess_queue_completion.tail, icmdp); ++isp->sess_queue_completion.count; mutex_exit(&isp->sess_queue_completion.mutex); (void) iscsi_thread_send_wakeup(isp->sess_ic_thread); } /* * iscsi_move_queue - used to move the whole contents of a queue * * The source queue has to be initialized. Its mutex is entered before * doing the actual move. The destination queue should be initialized. * This function is intended to move a queue located in a shared location * into local space. No mutex is needed for the destination queue. */ void iscsi_move_queue( iscsi_queue_t *src_queue, iscsi_queue_t *dst_queue ) { ASSERT(src_queue != NULL); ASSERT(dst_queue != NULL); mutex_enter(&src_queue->mutex); dst_queue->count = src_queue->count; dst_queue->head = src_queue->head; dst_queue->tail = src_queue->tail; src_queue->count = 0; src_queue->head = NULL; src_queue->tail = NULL; mutex_exit(&src_queue->mutex); } /* * +--------------------------------------------------------------------+ * | private functions | * +--------------------------------------------------------------------+ */ /* * iscsi_dequeue_cmd - used to remove a command from a queue */ iscsi_status_t iscsi_dequeue_cmd(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp) { #ifdef DEBUG iscsi_cmd_t *tp = NULL; #endif ASSERT(head != NULL); ASSERT(tail != NULL); ASSERT(icmdp != NULL); if (*head == NULL) { /* empty queue, error */ return (ISCSI_STATUS_INTERNAL_ERROR); } else if (*head == *tail) { /* one element queue */ if (*head == icmdp) { *head = NULL; *tail = NULL; } else { return (ISCSI_STATUS_INTERNAL_ERROR); } } else { /* multi-element queue */ if (*head == icmdp) { /* at the head */ *head = icmdp->cmd_next; (*head)->cmd_prev = NULL; } else if (*tail == icmdp) { *tail = icmdp->cmd_prev; (*tail)->cmd_next = NULL; } else { #ifdef DEBUG /* in the middle? */ for (tp = (*head)->cmd_next; (tp != NULL) && (tp != icmdp); tp = tp->cmd_next) ; if (tp == NULL) { /* not found */ return (ISCSI_STATUS_INTERNAL_ERROR); } #endif if (icmdp->cmd_prev == NULL) { return (ISCSI_STATUS_INTERNAL_ERROR); } icmdp->cmd_prev->cmd_next = icmdp->cmd_next; if (icmdp->cmd_next == NULL) { return (ISCSI_STATUS_INTERNAL_ERROR); } icmdp->cmd_next->cmd_prev = icmdp->cmd_prev; } } /* icmdp no longer in the queue */ icmdp->cmd_prev = NULL; icmdp->cmd_next = NULL; return (ISCSI_STATUS_SUCCESS); } /* * iscsi_enqueue_cmd_head - used to add a command to the head of a queue */ void iscsi_enqueue_cmd_head(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp) { ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_next == NULL); ASSERT(icmdp->cmd_prev == NULL); ASSERT(icmdp != *head); ASSERT(icmdp != *tail); if (*head == NULL) { /* empty queue */ *head = *tail = icmdp; icmdp->cmd_prev = NULL; icmdp->cmd_next = NULL; } else { /* non-empty queue */ icmdp->cmd_next = *head; icmdp->cmd_prev = NULL; (*head)->cmd_prev = icmdp; *head = icmdp; } } /* * iscsi_enqueue_cmd_tail - used to add a command to the tail of a queue */ static void iscsi_enqueue_cmd_tail(iscsi_cmd_t **head, iscsi_cmd_t **tail, iscsi_cmd_t *icmdp) { ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_next == NULL); ASSERT(icmdp->cmd_prev == NULL); ASSERT(icmdp != *head); ASSERT(icmdp != *tail); if (*head == NULL) { /* empty queue */ *head = *tail = icmdp; icmdp->cmd_prev = NULL; icmdp->cmd_next = NULL; } else { /* non-empty queue */ icmdp->cmd_next = NULL; icmdp->cmd_prev = *tail; (*tail)->cmd_next = icmdp; *tail = icmdp; } }