iscsi_cmd.c revision 36c5fee33fa8b822175d410202aebcf592c8d342
/*
* 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 2006 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 "iscsi_conn.h"
#include "iscsi_cmd.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 = (iscsi_cmd_t *)calloc(sizeof (iscsi_cmd_t), 1);
if (cmd == NULL)
return (NULL);
(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();
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++;
}
/*
* []----
* | 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.
* []----
*/
void
iscsi_cmd_cancel(iscsi_conn_t *c, iscsi_cmd_t *cmd)
{
(void) pthread_mutex_lock(&c->c_mutex);
if (cmd->c_state == CmdAlloc) {
cmd->c_state = CmdCanceled;
if (cmd->c_t10_cmd != NULL)
t10_cmd_state(cmd->c_t10_cmd, T10_Cmd_Event_Canceled);
}
(void) pthread_mutex_unlock(&c->c_mutex);
}
/*
* []----
* | 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;
(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) &&
(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;
}
}
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;
}
c->c_cmds_active--;
free(cmd);
cmd = n;
} else if (sna_lte(statsn, cmd->c_statsn)) {
break;
} else {
cmd = cmd->c_next;
}
}
(void) pthread_mutex_unlock(&c->c_mutex);
}
/*
* []----
* | 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 > 5) ? 0 : 5 - c->c_cmds_active;
} 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,
*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_state(t, T10_Cmd_Event_Release);
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);
}