OpenSolaris_b135/cmd/iscsi/iscsitgtd/iscsi_cmd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
#include <syslog.h>
#include <synch.h>
#include <sys/time.h>
#include <sys/asynch.h>
#include <umem.h>
#include <strings.h>

#include "iscsi_conn.h"
#include "iscsi_cmd.h"
#include "target.h"
#include "utility.h"

static pthread_mutex_t	cmd_mutex;
static int		cmd_ttt;

/*
 * []----
 * | iscsi_cmd_init -- called at the beginning of time to initialize locks
 * []----
 */
void
iscsi_cmd_init()
{
	(void) pthread_mutex_init(&cmd_mutex, NULL);
	cmd_ttt = 0;
}

/*
 * []----
 * | iscsi_cmd_alloc -- allocate space for new command
 * []----
 */
iscsi_cmd_t *
iscsi_cmd_alloc(iscsi_conn_t *c, int op)
{
	iscsi_cmd_t *cmd = umem_cache_alloc(iscsi_cmd_cache, UMEM_DEFAULT);

	if (cmd == NULL) {
		queue_prt(mgmtq, Q_CONN_ERRS, "Failed to get command buf\n");
		return (NULL);
	}

	bzero(cmd, sizeof (*cmd));
	(void) pthread_mutex_lock(&cmd_mutex);
	cmd->c_ttt = cmd_ttt++;
	(void) pthread_mutex_unlock(&cmd_mutex);

	(void) pthread_mutex_lock(&c->c_mutex);
	cmd->c_opcode		= op;
	cmd->c_statsn		= c->c_statsn;
	cmd->c_state		= CmdAlloc;
	if (c->c_cmd_head == NULL) {
		c->c_cmd_head = cmd;
		assert(c->c_cmd_tail == NULL);
		c->c_cmd_tail = cmd;
	} else {
		c->c_cmd_tail->c_next = cmd;
		cmd->c_prev = c->c_cmd_tail;
		c->c_cmd_tail = cmd;
	}
	cmd->c_allegiance = c;
	cmd->c_t_start = gethrtime();
	c->c_cmds_active++;
	(void) pthread_mutex_unlock(&c->c_mutex);

	return (cmd);
}

/*
 * []----
 * | iscsi_cmd_find -- search for a specific command and return it
 * |
 * | XXX Need to switch to use an AVL tree.
 * []----
 */
iscsi_cmd_t *
iscsi_cmd_find(iscsi_conn_t *c, uint32_t val, find_type_t type)
{
	iscsi_cmd_t	*cmd = NULL;

	(void) pthread_mutex_lock(&c->c_mutex);
	for (cmd = c->c_cmd_head; cmd; cmd = cmd->c_next) {

		/*
		 * Depending on type determine correct matching value.
		 * Only return a hit if the command hasn't already been
		 * freed.
		 */
		if ((((type == FindTTT) && (cmd->c_ttt == val)) ||
		    ((type == FindITT) && (cmd->c_itt == val))) &&
		    (cmd->c_state != CmdFree))
			break;
	}
	(void) pthread_mutex_unlock(&c->c_mutex);

	return (cmd);
}

/*
 * []----
 * | iscsi_cmd_free -- mark a command as freed.
 * []----
 */
void
iscsi_cmd_free(iscsi_conn_t *c, iscsi_cmd_t *cmd)
{
	hrtime_t	h	= gethrtime();

	assert(cmd->c_state != CmdFree);
	cmd->c_state		= CmdFree;
	cmd->c_t_completion	= h - cmd->c_t_start;
	c->c_cmds_avg_sum	+= cmd->c_t_completion;
	c->c_cmds_avg_cnt++;
	/* decrement active count here */
	c->c_cmds_active--;
}

/*
 * Find all duplicated t10_cmd and shoot an event
 */
void
iscsi_cancel_dups(iscsi_cmd_t *cmd, t10_cmd_event_t e)
{
	t10_cmd_t	*c2free;
	t10_cmd_t	*nc;

	/* Run the list */
	c2free = cmd->c_t10_cmd;
	while (c2free != NULL) {
		nc = c2free->c_cmd_next;
		t10_cmd_shoot_event(c2free, e);
		c2free = nc;
	}
}

/*
 * []----
 * | iscsi_cmd_cancel -- mark a command as canceled
 * |
 * | We don't actually stop commands in flight. We only prevent the canceled
 * | commands from returning status and/or data to the initiator. At the
 * | connection layer if a command is canceled nothing will be sent on the
 * | wire and at that point the command is marked CmdFree so that future calls
 * | to cmd_remove will actually free the space.
 * |
 * | NOTE: connection mutex must be held during this call.
 * []----
 */
void
iscsi_cmd_cancel(iscsi_conn_t *c, iscsi_cmd_t *cmd)
{
	assert(pthread_mutex_trylock(&c->c_mutex) != 0);
	if (cmd->c_state == CmdAlloc) {
		cmd->c_state = CmdCanceled;
		if (cmd->c_t10_cmd != NULL) {
			if (cmd->c_t10_dup)
				iscsi_cancel_dups(cmd, T10_Cmd_T6);
			else
				t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T6);
		}
	}
}

/*
 * []----
 * | iscsi_cmd_remove -- actually free space allocated to commands
 * |
 * | According to the iSCSI specification the target must kept resources
 * | around until the initiator sends a command with a status serial
 * | number higher than the held resource. This is so that an initiator
 * | can request data again if needed. During the processing of each new
 * | command this routine is called to free old commands.
 * []----
 */
void
iscsi_cmd_remove(iscsi_conn_t *c, uint32_t statsn)
{
	iscsi_cmd_t	*cmd, *n;
	iscsi_cmd_t	*cmd_free = NULL;

	(void) pthread_mutex_lock(&c->c_mutex);
	for (cmd = c->c_cmd_head; cmd; ) {
		/*
		 * If the StatusSN for this command is less than the incoming
		 * StatusSN and the command has been freed remove it from
		 * list. Don't bother with commands that are in the state of
		 * CmdCanceled. Once the I/O has been completed the command
		 * is passed back to the connection handler where the state
		 * will be noticed and then the command will be freed. At that
		 * point the next incoming command with a valid expected
		 * status serial number will free the memory.
		 */
		if (sna_lt(cmd->c_statsn, statsn)) {
			if (cmd->c_state == CmdFree) {
				if (c->c_cmd_head == cmd) {
					c->c_cmd_head = cmd->c_next;
					if (c->c_cmd_head == NULL)
						c->c_cmd_tail = NULL;
				} else {
					n = cmd->c_prev;
					n->c_next = cmd->c_next;
					if (cmd->c_next != NULL)
						cmd->c_next->c_prev = n;
					else {
						assert(c->c_cmd_tail == cmd);
						c->c_cmd_tail = n;
					}
				}

				/*
				 * Place on local command free list, to free
				 * once mutex is released
				 */
				n = cmd->c_next;
				cmd->c_next = cmd_free;
				cmd_free = cmd;
				cmd = n;
			} else {
				cmd = cmd->c_next;
			}
		} else {
			break;
		}
	}
	(void) pthread_mutex_unlock(&c->c_mutex);

	/*
	 * Deallocate command free list
	 */
	cmd = cmd_free;
	while (cmd != NULL) {
		n = cmd->c_next;
		if (cmd->c_scb_extended)
			free(cmd->c_scb_extended);
		if (cmd->c_data_alloc == True) {
			free(cmd->c_data);
			cmd->c_data = NULL;
		}
		umem_cache_free(iscsi_cmd_cache, cmd);
		cmd = n;
	}
}

/*
 * []----
 * | iscsi_cmd_window -- return the number of available commands
 * |
 * | There are currently 7 different places where this routine is called.
 * | In some cases and command is allocated which will be freed shortly and
 * | in others no command is held. This is why the number of commands found
 * | will be decremented if larger than 0. Since the daemon doesn't have
 * | any hard limits on the number of commands being supported this is more
 * | arbitrary and the command window size is used for debugging other
 * | initiators.
 * |
 * | NOTE: connection mutex must be held during this call.
 * []----
 */
int
iscsi_cmd_window(iscsi_conn_t *c)
{
	int			cnt;

	assert(pthread_mutex_trylock(&c->c_mutex) != 0);
	if (c->c_cmds_avg_cnt == 0) {

		/*
		 * If there are no outstanding commands clear the averages
		 * so that the initiator can start again.
		 */
		c->c_cmds_avg_sum = 0;
		c->c_cmds_avg_cnt = 0;
		cnt = c->c_maxcmdsn - c->c_cmds_active;

	} else if ((c->c_cmds_avg_sum / c->c_cmds_avg_cnt) >= NANOSEC) {

		/*
		 * It would appear things are taking a real long time to
		 * complete on our end. Close down the command window to
		 * prevent the initiator from timing out commands.
		 */
		cnt = (c->c_cmds_active >= c->c_maxcmdsn) ? 0 :
		    (c->c_maxcmdsn - c->c_cmds_active) / 2;

	} else {
		cnt = (c->c_cmds_active >= c->c_maxcmdsn) ? 0 :
		    c->c_maxcmdsn - c->c_cmds_active;
	}

	return (cnt);
}

void
iscsi_cmd_delayed_store(iscsi_cmd_t *cmd, t10_cmd_t *t)
{
	iscsi_delayed_t	*d, *n;
	iscsi_delayed_t	*l	= NULL;

	if ((d = (iscsi_delayed_t *)calloc(1, sizeof (*d))) == NULL) {
		syslog(LOG_ERR, "Failed to allocate space for delayed I/O");
		queue_prt(cmd->c_allegiance->c_mgmtq, Q_CONN_ERRS,
		    "CON%x  Failed calloc for delayed I/O",
		    cmd->c_allegiance->c_num);
		t10_cmd_shoot_event(t, T10_Cmd_T5);
		return;
	}

	d->id_offset	= T10_DATA_OFFSET(t);
	d->id_t10_cmd	= t;

	for (n = cmd->c_t10_delayed; n; n = n->id_next) {
		l = n;
		if (d->id_offset < n->id_offset) {
			if (n->id_prev == NULL) {
				d->id_next = n;
				n->id_prev = d;
				cmd->c_t10_delayed = d;
			} else {
				d->id_prev = n->id_prev;
				d->id_prev->id_next = d;
				n->id_prev = d;
				d->id_next = n;
			}
			return;
		}
	}

	if (l == NULL) {
		cmd->c_t10_delayed = d;
	} else {
		l->id_next = d;
		d->id_prev = l;
	}
}

void
iscsi_cmd_delayed_remove(iscsi_cmd_t *cmd, iscsi_delayed_t *d)
{
	if (cmd->c_t10_delayed == d) {
		cmd->c_t10_delayed = d->id_next;
		if (d->id_next)
			d->id_next->id_prev = NULL;
	} else {
		d->id_prev->id_next = d->id_next;
		if (d->id_next != NULL)
			d->id_next->id_prev = d->id_prev;
	}
	free(d);
}