[<prev] [next>] [day] [month] [year] [list]
Message-ID: <4BC46D48.9040004@vlnb.net>
Date: Tue, 13 Apr 2010 17:10:32 +0400
From: Vladislav Bolkhovitin <vst@...b.net>
To: linux-scsi@...r.kernel.org
CC: linux-kernel@...r.kernel.org,
scst-devel <scst-devel@...ts.sourceforge.net>,
James Bottomley <James.Bottomley@...senPartnership.com>,
Andrew Morton <akpm@...ux-foundation.org>,
FUJITA Tomonori <fujita.tomonori@....ntt.co.jp>,
Mike Christie <michaelc@...wisc.edu>,
Jeff Garzik <jeff@...zik.org>,
Linus Torvalds <torvalds@...ux-foundation.org>,
Bart Van Assche <bart.vanassche@...il.com>
Subject: Re: [PATCH][RFC 3/4/4/5] iSCSI-SCST's implementation files
This patch contains iSCSI-SCST's implementation files.
Signed-off-by: Vladislav Bolkhovitin <vst@...b.net>
---
config.c | 933 ++++++++++++++++
conn.c | 785 +++++++++++++
digest.c | 226 +++
event.c | 163 ++
iscsi.c | 3583 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
nthread.c | 1524 ++++++++++++++++++++++++++
param.c | 306 +++++
session.c | 482 ++++++++
target.c | 500 ++++++++
9 files changed, 8502 insertions(+)
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/config.c linux-2.6.33/drivers/scst/iscsi-scst/config.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/config.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/config.c
@@ -0,0 +1,933 @@
+/*
+ * Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@....org>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "iscsi.h"
+
+/* Protected by target_mgmt_mutex */
+int ctr_open_state;
+
+/* Protected by target_mgmt_mutex */
+static LIST_HEAD(iscsi_attrs_list);
+
+static ssize_t iscsi_version_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+
+ sprintf(buf, "%s\n", ISCSI_VERSION_STRING);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ strcat(buf, "EXTRACHECKS\n");
+#endif
+
+#ifdef CONFIG_SCST_TRACING
+ strcat(buf, "TRACING\n");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG
+ strcat(buf, "DEBUG\n");
+#endif
+
+#ifdef CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES
+ strcat(buf, "DEBUG_DIGEST_FAILURES\n");
+#endif
+ return strlen(buf);
+}
+
+static struct kobj_attribute iscsi_version_attr =
+ __ATTR(version, S_IRUGO, iscsi_version_show, NULL);
+
+static ssize_t iscsi_open_state_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ switch (ctr_open_state) {
+ case ISCSI_CTR_OPEN_STATE_CLOSED:
+ sprintf(buf, "%s\n", "closed");
+ break;
+ case ISCSI_CTR_OPEN_STATE_OPEN:
+ sprintf(buf, "%s\n", "open");
+ break;
+ case ISCSI_CTR_OPEN_STATE_CLOSING:
+ sprintf(buf, "%s\n", "closing");
+ break;
+ default:
+ sprintf(buf, "%s\n", "unknown");
+ break;
+ }
+
+ return strlen(buf);
+}
+
+static struct kobj_attribute iscsi_open_state_attr =
+ __ATTR(open_state, S_IRUGO, iscsi_open_state_show, NULL);
+
+const struct attribute *iscsi_attrs[] = {
+ &iscsi_version_attr.attr,
+ &iscsi_open_state_attr.attr,
+ NULL,
+};
+
+/* target_mgmt_mutex supposed to be locked */
+static int add_conn(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_session *session;
+ struct iscsi_kern_conn_info info;
+ struct iscsi_target *target;
+
+ rc = copy_from_user(&info, ptr, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ target = target_lookup_by_id(info.tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", info.tid);
+ err = -ENOENT;
+ goto out;
+ }
+
+ mutex_lock(&target->target_mutex);
+
+ session = session_lookup(target, info.sid);
+ if (!session) {
+ PRINT_ERROR("Session %lld not found",
+ (long long unsigned int)info.tid);
+ err = -ENOENT;
+ goto out_unlock;
+ }
+
+ err = __add_conn(session, &info);
+
+out_unlock:
+ mutex_unlock(&target->target_mutex);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int del_conn(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_session *session;
+ struct iscsi_kern_conn_info info;
+ struct iscsi_target *target;
+
+ rc = copy_from_user(&info, ptr, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ target = target_lookup_by_id(info.tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", info.tid);
+ err = -ENOENT;
+ goto out;
+ }
+
+ mutex_lock(&target->target_mutex);
+
+ session = session_lookup(target, info.sid);
+ if (!session) {
+ PRINT_ERROR("Session %llx not found",
+ (long long unsigned int)info.sid);
+ err = -ENOENT;
+ goto out_unlock;
+ }
+
+ err = __del_conn(session, &info);
+
+out_unlock:
+ mutex_unlock(&target->target_mutex);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int add_session(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_kern_session_info *info;
+ struct iscsi_target *target;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
+ err = -ENOMEM;
+ goto out;
+ }
+
+ rc = copy_from_user(info, ptr, sizeof(*info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out_free;
+ }
+
+ info->initiator_name[sizeof(info->initiator_name)-1] = '\0';
+
+ target = target_lookup_by_id(info->tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", info->tid);
+ err = -ENOENT;
+ goto out_free;
+ }
+
+ err = __add_session(target, info);
+
+out_free:
+ kfree(info);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int del_session(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_kern_session_info *info;
+ struct iscsi_target *target;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
+ err = -ENOMEM;
+ goto out;
+ }
+
+ rc = copy_from_user(info, ptr, sizeof(*info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out_free;
+ }
+
+ info->initiator_name[sizeof(info->initiator_name)-1] = '\0';
+
+ target = target_lookup_by_id(info->tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", info->tid);
+ err = -ENOENT;
+ goto out_free;
+ }
+
+ mutex_lock(&target->target_mutex);
+ err = __del_session(target, info->sid);
+ mutex_unlock(&target->target_mutex);
+
+out_free:
+ kfree(info);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int iscsi_params_config(void __user *ptr, int set)
+{
+ int err, rc;
+ struct iscsi_kern_params_info info;
+ struct iscsi_target *target;
+
+ rc = copy_from_user(&info, ptr, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ target = target_lookup_by_id(info.tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", info.tid);
+ err = -ENOENT;
+ goto out;
+ }
+
+ mutex_lock(&target->target_mutex);
+ err = iscsi_params_set(target, &info, set);
+ mutex_unlock(&target->target_mutex);
+
+ if (err < 0)
+ goto out;
+
+ if (!set) {
+ rc = copy_to_user(ptr, &info, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy to user %d bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+ }
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int mgmt_cmd_callback(void __user *ptr)
+{
+ int err = 0, rc;
+ struct iscsi_kern_mgmt_cmd_res_info cinfo;
+ struct scst_sysfs_user_info *info;
+
+ rc = copy_from_user(&cinfo, ptr, sizeof(cinfo));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ cinfo.value[sizeof(cinfo.value)-1] = '\0';
+
+ info = scst_sysfs_user_get_info(cinfo.cookie);
+ TRACE_DBG("cookie %u, info %p, result %d", cinfo.cookie, info,
+ cinfo.result);
+ if (info == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ info->info_status = 0;
+
+ if (cinfo.result != 0) {
+ info->info_status = cinfo.result;
+ goto out_complete;
+ }
+
+ switch (cinfo.req_cmd) {
+ case E_ENABLE_TARGET:
+ case E_DISABLE_TARGET:
+ {
+ struct iscsi_target *target;
+
+ target = target_lookup_by_id(cinfo.tid);
+ if (target == NULL) {
+ PRINT_ERROR("Target %d not found", cinfo.tid);
+ err = -ENOENT;
+ goto out_status;
+ }
+
+ target->tgt_enabled = (cinfo.req_cmd == E_ENABLE_TARGET) ? 1 : 0;
+ break;
+ }
+
+ case E_GET_ATTR_VALUE:
+ info->data = kstrdup(cinfo.value, GFP_KERNEL);
+ if (info->data == NULL) {
+ PRINT_ERROR("Can't dublicate value %s", cinfo.value);
+ info->info_status = -ENOMEM;
+ goto out_complete;
+ }
+ break;
+ }
+
+out_complete:
+ complete(&info->info_completion);
+
+out:
+ return err;
+
+out_status:
+ info->info_status = err;
+ goto out_complete;
+}
+
+static ssize_t iscsi_attr_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct iscsi_attr *tgt_attr;
+ void *value;
+
+ tgt_attr = container_of(attr, struct iscsi_attr, attr);
+
+ pos = iscsi_sysfs_send_event(
+ (tgt_attr->target != NULL) ? tgt_attr->target->tid : 0,
+ E_GET_ATTR_VALUE, tgt_attr->name, NULL, &value);
+
+ if (pos != 0)
+ goto out;
+
+ pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", (char *)value);
+
+ kfree(value);
+
+out:
+ return pos;
+}
+
+static ssize_t iscsi_attr_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int res;
+ char *buffer;
+ struct iscsi_attr *tgt_attr;
+
+ buffer = kzalloc(count+1, GFP_KERNEL);
+ if (buffer == NULL) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(buffer, buf, count);
+ buffer[count] = '\0';
+
+ tgt_attr = container_of(attr, struct iscsi_attr, attr);
+
+ res = iscsi_sysfs_send_event(
+ (tgt_attr->target != NULL) ? tgt_attr->target->tid : 0,
+ E_SET_ATTR_VALUE, tgt_attr->name, buffer, NULL);
+
+ kfree(buffer);
+
+ if (res == 0)
+ res = count;
+
+out:
+ return res;
+}
+
+/*
+ * target_mgmt_mutex supposed to be locked. If target != 0, target_mutex
+ * supposed to be locked as well.
+ */
+int iscsi_add_attr(struct iscsi_target *target,
+ const struct iscsi_kern_attr *attr_info)
+{
+ int res = 0;
+ struct iscsi_attr *tgt_attr;
+ struct list_head *attrs_list;
+ const char *name;
+
+ if (target != NULL) {
+ attrs_list = &target->attrs_list;
+ name = target->name;
+ } else {
+ attrs_list = &iscsi_attrs_list;
+ name = "global";
+ }
+
+ list_for_each_entry(tgt_attr, attrs_list, attrs_list_entry) {
+ if (strncmp(tgt_attr->name, attr_info->name,
+ sizeof(tgt_attr->name) == 0)) {
+ PRINT_ERROR("Attribute %s for %s already exist",
+ attr_info->name, name);
+ res = -EEXIST;
+ goto out;
+ }
+ }
+
+ TRACE_DBG("Adding %s's attr %s with mode %x", name,
+ attr_info->name, attr_info->mode);
+
+ tgt_attr = kzalloc(sizeof(*tgt_attr), GFP_KERNEL);
+ if (tgt_attr == NULL) {
+ PRINT_ERROR("Unable to allocate user (size %zd)",
+ sizeof(*tgt_attr));
+ res = -ENOMEM;
+ goto out;
+ }
+
+ tgt_attr->target = target;
+
+ tgt_attr->name = kstrdup(attr_info->name, GFP_KERNEL);
+ if (tgt_attr->name == NULL) {
+ PRINT_ERROR("Unable to allocate attr %s name/value (target %s)",
+ attr_info->name, name);
+ res = -ENOMEM;
+ goto out_free;
+ }
+
+ list_add(&tgt_attr->attrs_list_entry, attrs_list);
+
+ tgt_attr->attr.attr.name = tgt_attr->name;
+ tgt_attr->attr.attr.owner = THIS_MODULE;
+ tgt_attr->attr.attr.mode = attr_info->mode & (S_IRUGO | S_IWUGO);
+ tgt_attr->attr.show = iscsi_attr_show;
+ tgt_attr->attr.store = iscsi_attr_store;
+
+ res = sysfs_create_file(
+ (target != NULL) ? scst_sysfs_get_tgt_kobj(target->scst_tgt) :
+ scst_sysfs_get_tgtt_kobj(&iscsi_template),
+ &tgt_attr->attr.attr);
+ if (res != 0) {
+ PRINT_ERROR("Unable to create file '%s' for target '%s'",
+ tgt_attr->attr.attr.name, name);
+ goto out_del;
+ }
+
+out:
+ return res;
+
+out_del:
+ list_del(&tgt_attr->attrs_list_entry);
+
+out_free:
+ kfree(tgt_attr->name);
+ kfree(tgt_attr);
+ goto out;
+}
+
+void __iscsi_del_attr(struct iscsi_target *target,
+ struct iscsi_attr *tgt_attr)
+{
+
+ TRACE_DBG("Deleting %s's attr %s",
+ (target != NULL) ? target->name : "global", tgt_attr->name);
+
+ list_del(&tgt_attr->attrs_list_entry);
+
+ sysfs_remove_file((target != NULL) ?
+ scst_sysfs_get_tgt_kobj(target->scst_tgt) :
+ scst_sysfs_get_tgtt_kobj(&iscsi_template),
+ &tgt_attr->attr.attr);
+
+ kfree(tgt_attr->name);
+ kfree(tgt_attr);
+ return;
+}
+
+/*
+ * target_mgmt_mutex supposed to be locked. If target != 0, target_mutex
+ * supposed to be locked as well.
+ */
+static int iscsi_del_attr(struct iscsi_target *target,
+ const char *attr_name)
+{
+ int res = 0;
+ struct iscsi_attr *tgt_attr, *a;
+ struct list_head *attrs_list;
+
+ if (target != NULL)
+ attrs_list = &target->attrs_list;
+ else
+ attrs_list = &iscsi_attrs_list;
+
+ tgt_attr = NULL;
+ list_for_each_entry(a, attrs_list, attrs_list_entry) {
+ if (strncmp(a->name, attr_name, sizeof(a->name)) == 0) {
+ tgt_attr = a;
+ break;
+ }
+ }
+
+ if (tgt_attr == NULL) {
+ PRINT_ERROR("attr %s not found (target %s)", attr_name,
+ (target != NULL) ? target->name : "global");
+ res = -ENOENT;
+ goto out;
+ }
+
+ __iscsi_del_attr(target, tgt_attr);
+
+out:
+ return res;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int iscsi_attr_cmd(void __user *ptr, unsigned int cmd)
+{
+ int rc, err = 0;
+ struct iscsi_kern_attr_info info;
+ struct iscsi_target *target;
+ struct scst_sysfs_user_info *i = NULL;
+
+ rc = copy_from_user(&info, ptr, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ info.attr.name[sizeof(info.attr.name)-1] = '\0';
+
+ if (info.cookie != 0) {
+ i = scst_sysfs_user_get_info(info.cookie);
+ TRACE_DBG("cookie %u, uinfo %p", info.cookie, i);
+ if (i == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ target = target_lookup_by_id(info.tid);
+
+ if (target != NULL)
+ mutex_lock(&target->target_mutex);
+
+ switch (cmd) {
+ case ISCSI_ATTR_ADD:
+ err = iscsi_add_attr(target, &info.attr);
+ break;
+ case ISCSI_ATTR_DEL:
+ err = iscsi_del_attr(target, info.attr.name);
+ break;
+ default:
+ BUG();
+ }
+
+ if (target != NULL)
+ mutex_unlock(&target->target_mutex);
+
+ if (i != NULL) {
+ i->info_status = err;
+ complete(&i->info_completion);
+ }
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int add_target(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_kern_target_info *info;
+ struct scst_sysfs_user_info *uinfo;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
+ err = -ENOMEM;
+ goto out;
+ }
+
+ rc = copy_from_user(info, ptr, sizeof(*info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out_free;
+ }
+
+ if (target_lookup_by_id(info->tid) != NULL) {
+ PRINT_ERROR("Target %u already exist!", info->tid);
+ err = -EEXIST;
+ goto out_free;
+ }
+
+ info->name[sizeof(info->name)-1] = '\0';
+
+ if (info->cookie != 0) {
+ uinfo = scst_sysfs_user_get_info(info->cookie);
+ TRACE_DBG("cookie %u, uinfo %p", info->cookie, uinfo);
+ if (uinfo == NULL) {
+ err = -EINVAL;
+ goto out_free;
+ }
+ } else
+ uinfo = NULL;
+
+ err = __add_target(info);
+
+ if (uinfo != NULL) {
+ uinfo->info_status = err;
+ complete(&uinfo->info_completion);
+ }
+
+out_free:
+ kfree(info);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int del_target(void __user *ptr)
+{
+ int err, rc;
+ struct iscsi_kern_target_info info;
+ struct scst_sysfs_user_info *uinfo;
+
+ rc = copy_from_user(&info, ptr, sizeof(info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy %d user's bytes", rc);
+ err = -EFAULT;
+ goto out;
+ }
+
+ info.name[sizeof(info.name)-1] = '\0';
+
+ if (info.cookie != 0) {
+ uinfo = scst_sysfs_user_get_info(info.cookie);
+ TRACE_DBG("cookie %u, uinfo %p", info.cookie, uinfo);
+ if (uinfo == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+ } else
+ uinfo = NULL;
+
+ err = __del_target(info.tid);
+
+ if (uinfo != NULL) {
+ uinfo->info_status = err;
+ complete(&uinfo->info_completion);
+ }
+
+out:
+ return err;
+}
+
+static int iscsi_register(void __user *arg)
+{
+ struct iscsi_kern_register_info reg;
+ char ver[sizeof(ISCSI_SCST_INTERFACE_VERSION)+1];
+ int res, rc;
+
+ rc = copy_from_user(®, arg, sizeof(reg));
+ if (rc != 0) {
+ PRINT_ERROR("%s", "Unable to get register info");
+ res = -EFAULT;
+ goto out;
+ }
+
+ rc = copy_from_user(ver, (void __user *)(unsigned long)reg.version,
+ sizeof(ver));
+ if (rc != 0) {
+ PRINT_ERROR("%s", "Unable to get version string");
+ res = -EFAULT;
+ goto out;
+ }
+ ver[sizeof(ver)-1] = '\0';
+
+ if (strcmp(ver, ISCSI_SCST_INTERFACE_VERSION) != 0) {
+ PRINT_ERROR("Incorrect version of user space %s (expected %s)",
+ ver, ISCSI_SCST_INTERFACE_VERSION);
+ res = -EINVAL;
+ goto out;
+ }
+
+ memset(®, 0, sizeof(reg));
+ reg.max_data_seg_len = ISCSI_CONN_IOV_MAX << PAGE_SHIFT;
+ reg.max_queued_cmds = scst_get_max_lun_commands(NULL, NO_SUCH_LUN);
+
+ res = 0;
+
+ rc = copy_to_user(arg, ®, sizeof(reg));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy to user %d bytes", rc);
+ res = -EFAULT;
+ goto out;
+ }
+
+out:
+ return res;
+}
+
+static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ long err;
+
+ if (cmd == REGISTER_USERD) {
+ err = iscsi_register((void __user *)arg);
+ goto out;
+ }
+
+ err = mutex_lock_interruptible(&target_mgmt_mutex);
+ if (err < 0)
+ goto out;
+
+ switch (cmd) {
+ case ADD_TARGET:
+ err = add_target((void __user *)arg);
+ break;
+
+ case DEL_TARGET:
+ err = del_target((void __user *)arg);
+ break;
+
+ case ISCSI_ATTR_ADD:
+ case ISCSI_ATTR_DEL:
+ err = iscsi_attr_cmd((void __user *)arg, cmd);
+ break;
+
+ case MGMT_CMD_CALLBACK:
+ err = mgmt_cmd_callback((void __user *)arg);
+ break;
+
+ case ADD_SESSION:
+ err = add_session((void __user *)arg);
+ break;
+
+ case DEL_SESSION:
+ err = del_session((void __user *)arg);
+ break;
+
+ case ISCSI_PARAM_SET:
+ err = iscsi_params_config((void __user *)arg, 1);
+ break;
+
+ case ISCSI_PARAM_GET:
+ err = iscsi_params_config((void __user *)arg, 0);
+ break;
+
+ case ADD_CONN:
+ err = add_conn((void __user *)arg);
+ break;
+
+ case DEL_CONN:
+ err = del_conn((void __user *)arg);
+ break;
+
+ default:
+ PRINT_ERROR("Invalid ioctl cmd %x", cmd);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&target_mgmt_mutex);
+
+out:
+ return err;
+}
+
+static int open(struct inode *inode, struct file *file)
+{
+ bool already;
+
+ mutex_lock(&target_mgmt_mutex);
+ already = (ctr_open_state != ISCSI_CTR_OPEN_STATE_CLOSED);
+ if (!already)
+ ctr_open_state = ISCSI_CTR_OPEN_STATE_OPEN;
+ mutex_unlock(&target_mgmt_mutex);
+
+ if (already) {
+ PRINT_WARNING("%s", "Attempt to second open the control "
+ "device!");
+ return -EBUSY;
+ } else
+ return 0;
+}
+
+static int release(struct inode *inode, struct file *filp)
+{
+ struct iscsi_attr *attr, *t;
+
+ TRACE(TRACE_MGMT, "%s", "Releasing allocated resources");
+
+ mutex_lock(&target_mgmt_mutex);
+ ctr_open_state = ISCSI_CTR_OPEN_STATE_CLOSING;
+ mutex_unlock(&target_mgmt_mutex);
+
+ target_del_all();
+
+ mutex_lock(&target_mgmt_mutex);
+
+ list_for_each_entry_safe(attr, t, &iscsi_attrs_list,
+ attrs_list_entry) {
+ __iscsi_del_attr(NULL, attr);
+ }
+
+ ctr_open_state = ISCSI_CTR_OPEN_STATE_CLOSED;
+
+ mutex_unlock(&target_mgmt_mutex);
+
+ return 0;
+}
+
+const struct file_operations ctr_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = ioctl,
+ .compat_ioctl = ioctl,
+ .open = open,
+ .release = release,
+};
+
+#ifdef CONFIG_SCST_DEBUG
+static void iscsi_dump_char(int ch, unsigned char *text, int *pos)
+{
+ int i = *pos;
+
+ if (ch < 0) {
+ while ((i % 16) != 0) {
+ printk(KERN_CONT " ");
+ text[i] = ' ';
+ i++;
+ if ((i % 16) == 0)
+ printk(KERN_CONT " | %.16s |\n", text);
+ else if ((i % 4) == 0)
+ printk(KERN_CONT " |");
+ }
+ i = 0;
+ goto out;
+ }
+
+ text[i] = (ch < 0x20 || (ch >= 0x80 && ch <= 0xa0)) ? ' ' : ch;
+ printk(KERN_CONT " %02x", ch);
+ i++;
+ if ((i % 16) == 0) {
+ printk(KERN_CONT " | %.16s |\n", text);
+ i = 0;
+ } else if ((i % 4) == 0)
+ printk(KERN_CONT " |");
+
+out:
+ *pos = i;
+ return;
+}
+
+void iscsi_dump_pdu(struct iscsi_pdu *pdu)
+{
+ unsigned char text[16];
+ int pos = 0;
+
+ if (trace_flag & TRACE_D_DUMP_PDU) {
+ unsigned char *buf;
+ int i;
+
+ buf = (void *)&pdu->bhs;
+ printk(KERN_DEBUG "BHS: (%p,%zd)\n", buf, sizeof(pdu->bhs));
+ for (i = 0; i < (int)sizeof(pdu->bhs); i++)
+ iscsi_dump_char(*buf++, text, &pos);
+ iscsi_dump_char(-1, text, &pos);
+
+ buf = (void *)pdu->ahs;
+ printk(KERN_DEBUG "AHS: (%p,%d)\n", buf, pdu->ahssize);
+ for (i = 0; i < pdu->ahssize; i++)
+ iscsi_dump_char(*buf++, text, &pos);
+ iscsi_dump_char(-1, text, &pos);
+
+ printk(KERN_DEBUG "Data: (%d)\n", pdu->datasize);
+ }
+}
+
+unsigned long iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(struct iscsi_cmnd *cmnd)
+{
+ unsigned long flag;
+
+ if (cmnd->cmd_req != NULL)
+ cmnd = cmnd->cmd_req;
+
+ if (cmnd->scst_cmd == NULL)
+ flag = TRACE_MGMT_DEBUG;
+ else {
+ int status = scst_cmd_get_status(cmnd->scst_cmd);
+ if ((status == SAM_STAT_TASK_SET_FULL) ||
+ (status == SAM_STAT_BUSY))
+ flag = TRACE_FLOW_CONTROL;
+ else
+ flag = TRACE_MGMT_DEBUG;
+ }
+ return flag;
+}
+
+#endif /* CONFIG_SCST_DEBUG */
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/conn.c linux-2.6.33/drivers/scst/iscsi-scst/conn.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/conn.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/conn.c
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@...istech.com>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, version 2
+ * of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/file.h>
+#include <linux/ip.h>
+#include <net/tcp.h>
+
+#include "iscsi.h"
+#include "digest.h"
+
+static int print_conn_state(char *p, size_t size, struct iscsi_conn *conn)
+{
+ int pos = 0;
+
+ if (conn->closing) {
+ pos += scnprintf(p, size, "%s", "closing");
+ goto out;
+ }
+
+ switch (conn->rd_state) {
+ case ISCSI_CONN_RD_STATE_PROCESSING:
+ pos += scnprintf(&p[pos], size - pos, "%s", "read_processing ");
+ break;
+ case ISCSI_CONN_RD_STATE_IN_LIST:
+ pos += scnprintf(&p[pos], size - pos, "%s", "in_read_list ");
+ break;
+ }
+
+ switch (conn->wr_state) {
+ case ISCSI_CONN_WR_STATE_PROCESSING:
+ pos += scnprintf(&p[pos], size - pos, "%s", "write_processing ");
+ break;
+ case ISCSI_CONN_WR_STATE_IN_LIST:
+ pos += scnprintf(&p[pos], size - pos, "%s", "in_write_list ");
+ break;
+ case ISCSI_CONN_WR_STATE_SPACE_WAIT:
+ pos += scnprintf(&p[pos], size - pos, "%s", "space_waiting ");
+ break;
+ }
+
+ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags))
+ pos += scnprintf(&p[pos], size - pos, "%s", "reinstating ");
+ else if (pos == 0)
+ pos += scnprintf(&p[pos], size - pos, "%s", "established idle ");
+
+out:
+ return pos;
+}
+
+static int conn_free(struct iscsi_conn *conn);
+
+static void iscsi_conn_release(struct kobject *kobj)
+{
+ struct iscsi_conn *conn;
+ struct iscsi_target *target;
+
+ conn = container_of(kobj, struct iscsi_conn, iscsi_conn_kobj);
+ target = conn->target;
+
+ mutex_lock(&target->target_mutex);
+ conn_free(conn);
+ mutex_unlock(&target->target_mutex);
+ return;
+}
+
+static struct kobj_type iscsi_conn_ktype = {
+ .sysfs_ops = &scst_sysfs_ops,
+ .release = iscsi_conn_release,
+};
+
+static ssize_t iscsi_get_initiator_ip(struct iscsi_conn *conn,
+ char *buf, int size)
+{
+ int pos;
+ struct sock *sk;
+
+ sk = conn->sock->sk;
+ switch (sk->sk_family) {
+ case AF_INET:
+ pos = scnprintf(buf, size,
+ "%u.%u.%u.%u", NIPQUAD(inet_sk(sk)->inet_daddr));
+ break;
+ case AF_INET6:
+ pos = scnprintf(buf, size, "[%p6]",
+ &inet6_sk(sk)->daddr);
+ break;
+ default:
+ pos = scnprintf(buf, size, "Unknown family %d",
+ sk->sk_family);
+ break;
+ }
+ return pos;
+}
+
+static ssize_t iscsi_conn_ip_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct iscsi_conn *conn;
+
+ conn = container_of(kobj, struct iscsi_conn, iscsi_conn_kobj);
+
+ pos = iscsi_get_initiator_ip(conn, buf, SCST_SYSFS_BLOCK_SIZE);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_conn_ip_attr =
+ __ATTR(ip, S_IRUGO, iscsi_conn_ip_show, NULL);
+
+static ssize_t iscsi_conn_cid_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct iscsi_conn *conn;
+
+ conn = container_of(kobj, struct iscsi_conn, iscsi_conn_kobj);
+
+ pos = sprintf(buf, "%u", conn->cid);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_conn_cid_attr =
+ __ATTR(cid, S_IRUGO, iscsi_conn_cid_show, NULL);
+
+static ssize_t iscsi_conn_state_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct iscsi_conn *conn;
+
+ conn = container_of(kobj, struct iscsi_conn, iscsi_conn_kobj);
+
+ pos = print_conn_state(buf, SCST_SYSFS_BLOCK_SIZE, conn);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_conn_state_attr =
+ __ATTR(state, S_IRUGO, iscsi_conn_state_show, NULL);
+
+/* target_mutex supposed to be locked */
+struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid)
+{
+ struct iscsi_conn *conn;
+
+ /*
+ * We need to find the latest conn to correctly handle
+ * multi-reinstatements
+ */
+ list_for_each_entry_reverse(conn, &session->conn_list,
+ conn_list_entry) {
+ if (conn->cid == cid)
+ return conn;
+ }
+ return NULL;
+}
+
+void iscsi_make_conn_rd_active(struct iscsi_conn *conn)
+{
+
+ spin_lock_bh(&iscsi_rd_lock);
+
+ TRACE_DBG("conn %p, rd_state %x, rd_data_ready %d", conn,
+ conn->rd_state, conn->rd_data_ready);
+
+ conn->rd_data_ready = 1;
+
+ if (conn->rd_state == ISCSI_CONN_RD_STATE_IDLE) {
+ list_add_tail(&conn->rd_list_entry, &iscsi_rd_list);
+ conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
+ wake_up(&iscsi_rd_waitQ);
+ }
+
+ spin_unlock_bh(&iscsi_rd_lock);
+ return;
+}
+
+void iscsi_make_conn_wr_active(struct iscsi_conn *conn)
+{
+
+ spin_lock_bh(&iscsi_wr_lock);
+
+ TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d", conn,
+ conn->wr_state, conn->wr_space_ready);
+
+ if (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE) {
+ list_add_tail(&conn->wr_list_entry, &iscsi_wr_list);
+ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
+ wake_up(&iscsi_wr_waitQ);
+ }
+
+ spin_unlock_bh(&iscsi_wr_lock);
+ return;
+}
+
+void __mark_conn_closed(struct iscsi_conn *conn, int flags)
+{
+ spin_lock_bh(&iscsi_rd_lock);
+ conn->closing = 1;
+ if (flags & ISCSI_CONN_ACTIVE_CLOSE)
+ conn->active_close = 1;
+ if (flags & ISCSI_CONN_DELETING)
+ conn->deleting = 1;
+ spin_unlock_bh(&iscsi_rd_lock);
+
+ iscsi_make_conn_rd_active(conn);
+}
+
+void mark_conn_closed(struct iscsi_conn *conn)
+{
+ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE);
+}
+
+static void __iscsi_state_change(struct sock *sk)
+{
+ struct iscsi_conn *conn = sk->sk_user_data;
+
+ if (unlikely(sk->sk_state != TCP_ESTABLISHED)) {
+ if (!conn->closing) {
+ PRINT_ERROR("Connection with initiator %s "
+ "unexpectedly closed!",
+ conn->session->initiator_name);
+ TRACE_MGMT_DBG("conn %p, sk state %d", conn,
+ sk->sk_state);
+ __mark_conn_closed(conn, 0);
+ }
+ } else
+ iscsi_make_conn_rd_active(conn);
+ return;
+}
+
+static void iscsi_state_change(struct sock *sk)
+{
+ struct iscsi_conn *conn = sk->sk_user_data;
+
+ __iscsi_state_change(sk);
+ conn->old_state_change(sk);
+
+ return;
+}
+
+static void iscsi_data_ready(struct sock *sk, int len)
+{
+ struct iscsi_conn *conn = sk->sk_user_data;
+
+ iscsi_make_conn_rd_active(conn);
+
+ conn->old_data_ready(sk, len);
+ return;
+}
+
+static void iscsi_write_space_ready(struct sock *sk)
+{
+ struct iscsi_conn *conn = sk->sk_user_data;
+
+ TRACE_DBG("Write space ready for conn %p", conn);
+
+ spin_lock_bh(&iscsi_wr_lock);
+ conn->wr_space_ready = 1;
+ if ((conn->wr_state == ISCSI_CONN_WR_STATE_SPACE_WAIT)) {
+ list_add_tail(&conn->wr_list_entry, &iscsi_wr_list);
+ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
+ wake_up(&iscsi_wr_waitQ);
+ }
+ spin_unlock_bh(&iscsi_wr_lock);
+
+ conn->old_write_space(sk);
+ return;
+}
+
+static void conn_rsp_timer_fn(unsigned long arg)
+{
+ struct iscsi_conn *conn = (struct iscsi_conn *)arg;
+ struct iscsi_cmnd *cmnd;
+ unsigned long j = jiffies;
+
+ TRACE_DBG("Timer (conn %p)", conn);
+
+ spin_lock_bh(&conn->write_list_lock);
+
+ if (!list_empty(&conn->write_timeout_list)) {
+ unsigned long timeout_time;
+ cmnd = list_entry(conn->write_timeout_list.next,
+ struct iscsi_cmnd, write_timeout_list_entry);
+
+ timeout_time = j + conn->rsp_timeout + ISCSI_ADD_SCHED_TIME;
+
+ if (unlikely(time_after_eq(j, cmnd->write_start +
+ conn->rsp_timeout))) {
+ if (!conn->closing) {
+ PRINT_ERROR("Timeout sending data/waiting "
+ "for reply to/from initiator "
+ "%s (SID %llx), closing connection",
+ conn->session->initiator_name,
+ (long long unsigned int)
+ conn->session->sid);
+ /*
+ * We must call mark_conn_closed() outside of
+ * write_list_lock or we will have a circular
+ * locking dependency with iscsi_rd_lock.
+ */
+ spin_unlock_bh(&conn->write_list_lock);
+ mark_conn_closed(conn);
+ goto out;
+ }
+ } else if (!timer_pending(&conn->rsp_timer) ||
+ time_after(conn->rsp_timer.expires, timeout_time)) {
+ TRACE_DBG("Restarting timer on %ld (conn %p)",
+ timeout_time, conn);
+ /*
+ * Timer might have been restarted while we were
+ * entering here.
+ */
+ mod_timer(&conn->rsp_timer, timeout_time);
+ }
+ }
+
+ spin_unlock_bh(&conn->write_list_lock);
+
+ if (unlikely(conn->conn_tm_active)) {
+ TRACE_MGMT_DBG("TM active: making conn %p RD active", conn);
+ iscsi_make_conn_rd_active(conn);
+ }
+
+out:
+ return;
+}
+
+static void conn_nop_in_delayed_work_fn(struct delayed_work *work)
+{
+ struct iscsi_conn *conn = container_of(work, struct iscsi_conn,
+ nop_in_delayed_work);
+
+ if (time_after_eq(jiffies, conn->last_rcv_time +
+ conn->nop_in_interval)) {
+ iscsi_send_nop_in(conn);
+ }
+
+ if (conn->nop_in_interval > 0) {
+ TRACE_DBG("Reschedule Nop-In work for conn %p", conn);
+ schedule_delayed_work(&conn->nop_in_delayed_work,
+ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
+ }
+ return;
+}
+
+/* Must be called from rd thread only */
+void iscsi_check_tm_data_wait_timeouts(struct iscsi_conn *conn, bool force)
+{
+ struct iscsi_cmnd *cmnd;
+ unsigned long j = jiffies;
+ bool aborted_cmds_pending;
+ unsigned long timeout_time = j + ISCSI_TM_DATA_WAIT_TIMEOUT +
+ ISCSI_ADD_SCHED_TIME;
+
+ TRACE_DBG_FLAG(force ? TRACE_CONN_OC_DBG : TRACE_MGMT_DEBUG,
+ "j %ld (TIMEOUT %d, force %d)", j,
+ ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME, force);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+again:
+ spin_lock_bh(&iscsi_rd_lock);
+ spin_lock(&conn->write_list_lock);
+
+ aborted_cmds_pending = false;
+ list_for_each_entry(cmnd, &conn->write_timeout_list,
+ write_timeout_list_entry) {
+ if (test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags)) {
+ TRACE_DBG_FLAG(force ? TRACE_CONN_OC_DBG : TRACE_MGMT_DEBUG,
+ "Checking aborted cmnd %p (scst_state %d, "
+ "on_write_timeout_list %d, write_start %ld, "
+ "r2t_len_to_receive %d)", cmnd,
+ cmnd->scst_state, cmnd->on_write_timeout_list,
+ cmnd->write_start, cmnd->r2t_len_to_receive);
+ if ((cmnd->r2t_len_to_receive != 0) &&
+ (time_after_eq(j, cmnd->write_start + ISCSI_TM_DATA_WAIT_TIMEOUT) ||
+ force)) {
+ spin_unlock(&conn->write_list_lock);
+ spin_unlock_bh(&iscsi_rd_lock);
+ iscsi_fail_data_waiting_cmnd(cmnd);
+ goto again;
+ }
+ aborted_cmds_pending = true;
+ }
+ }
+
+ if (aborted_cmds_pending) {
+ if (!force &&
+ (!timer_pending(&conn->rsp_timer) ||
+ time_after(conn->rsp_timer.expires, timeout_time))) {
+ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)",
+ timeout_time, conn);
+ mod_timer(&conn->rsp_timer, timeout_time);
+ }
+ } else {
+ TRACE_MGMT_DBG("Clearing conn_tm_active for conn %p", conn);
+ conn->conn_tm_active = 0;
+ }
+
+ spin_unlock(&conn->write_list_lock);
+ spin_unlock_bh(&iscsi_rd_lock);
+ return;
+}
+
+/* target_mutex supposed to be locked */
+void conn_reinst_finished(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd, *t;
+
+ clear_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags);
+
+ list_for_each_entry_safe(cmnd, t, &conn->reinst_pending_cmd_list,
+ reinst_pending_cmd_list_entry) {
+ TRACE_MGMT_DBG("Restarting reinst pending cmnd %p",
+ cmnd);
+ list_del(&cmnd->reinst_pending_cmd_list_entry);
+ iscsi_restart_cmnd(cmnd);
+ }
+ return;
+}
+
+static void conn_activate(struct iscsi_conn *conn)
+{
+ TRACE_MGMT_DBG("Enabling conn %p", conn);
+
+ /* Catch double bind */
+ BUG_ON(conn->sock->sk->sk_state_change == iscsi_state_change);
+
+ write_lock_bh(&conn->sock->sk->sk_callback_lock);
+
+ conn->old_state_change = conn->sock->sk->sk_state_change;
+ conn->sock->sk->sk_state_change = iscsi_state_change;
+
+ conn->old_data_ready = conn->sock->sk->sk_data_ready;
+ conn->sock->sk->sk_data_ready = iscsi_data_ready;
+
+ conn->old_write_space = conn->sock->sk->sk_write_space;
+ conn->sock->sk->sk_write_space = iscsi_write_space_ready;
+
+ write_unlock_bh(&conn->sock->sk->sk_callback_lock);
+
+ /*
+ * Check, if conn was closed while we were initializing it.
+ * This function will make conn rd_active, if necessary.
+ */
+ __iscsi_state_change(conn->sock->sk);
+
+ return;
+}
+
+/*
+ * Note: the code below passes a kernel space pointer (&opt) to setsockopt()
+ * while the declaration of setsockopt specifies that it expects a user space
+ * pointer. This seems to work fine, and this approach is also used in some
+ * other parts of the Linux kernel (see e.g. fs/ocfs2/cluster/tcp.c).
+ */
+static int conn_setup_sock(struct iscsi_conn *conn)
+{
+ int res = 0;
+ int opt = 1;
+ mm_segment_t oldfs;
+ struct iscsi_session *session = conn->session;
+
+ TRACE_DBG("%llu", (long long unsigned int)session->sid);
+
+ conn->sock = SOCKET_I(conn->file->f_dentry->d_inode);
+
+ if (conn->sock->ops->sendpage == NULL) {
+ PRINT_ERROR("Socket for sid %llu doesn't support sendpage()",
+ (long long unsigned int)session->sid);
+ res = -EINVAL;
+ goto out;
+ }
+
+#if 0
+ conn->sock->sk->sk_allocation = GFP_NOIO;
+#endif
+ conn->sock->sk->sk_user_data = conn;
+
+ oldfs = get_fs();
+ set_fs(get_ds());
+ conn->sock->ops->setsockopt(conn->sock, SOL_TCP, TCP_NODELAY,
+ (void __force __user *)&opt, sizeof(opt));
+ set_fs(oldfs);
+
+out:
+ return res;
+}
+
+/* target_mutex supposed to be locked */
+static int conn_free(struct iscsi_conn *conn)
+{
+ struct iscsi_session *session = conn->session;
+
+ TRACE_MGMT_DBG("Freeing conn %p (sess=%p, %#Lx %u)", conn,
+ session, (long long unsigned int)session->sid, conn->cid);
+
+ del_timer_sync(&conn->rsp_timer);
+
+ BUG_ON(atomic_read(&conn->conn_ref_cnt) != 0);
+ BUG_ON(!list_empty(&conn->cmd_list));
+ BUG_ON(!list_empty(&conn->write_list));
+ BUG_ON(!list_empty(&conn->write_timeout_list));
+ BUG_ON(conn->conn_reinst_successor != NULL);
+ BUG_ON(!test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags));
+
+ /* Just in case if new conn gets freed before the old one */
+ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags)) {
+ struct iscsi_conn *c;
+ TRACE_MGMT_DBG("Freeing being reinstated conn %p", conn);
+ list_for_each_entry(c, &session->conn_list,
+ conn_list_entry) {
+ if (c->conn_reinst_successor == conn) {
+ c->conn_reinst_successor = NULL;
+ break;
+ }
+ }
+ }
+
+ list_del(&conn->conn_list_entry);
+
+ fput(conn->file);
+ conn->file = NULL;
+ conn->sock = NULL;
+
+ free_page((unsigned long)conn->read_iov);
+
+ kfree(conn);
+
+ if (list_empty(&session->conn_list)) {
+ BUG_ON(session->sess_reinst_successor != NULL);
+ session_free(session, true);
+ }
+
+ return 0;
+}
+
+/* target_mutex supposed to be locked */
+static int iscsi_conn_alloc(struct iscsi_session *session,
+ struct iscsi_kern_conn_info *info, struct iscsi_conn **new_conn)
+{
+ struct iscsi_conn *conn;
+ int res = 0;
+ struct iscsi_conn *c;
+ int n = 1;
+ char addr[64];
+
+ conn = kzalloc(sizeof(*conn), GFP_KERNEL);
+ if (!conn) {
+ res = -ENOMEM;
+ goto out_err;
+ }
+
+ TRACE_MGMT_DBG("Creating connection %p for sid %#Lx, cid %u", conn,
+ (long long unsigned int)session->sid, info->cid);
+
+ /* Changing it, change ISCSI_CONN_IOV_MAX as well !! */
+ conn->read_iov = (struct iovec *)get_zeroed_page(GFP_KERNEL);
+ if (conn->read_iov == NULL) {
+ res = -ENOMEM;
+ goto out_err_free_conn;
+ }
+
+ atomic_set(&conn->conn_ref_cnt, 0);
+ conn->session = session;
+ if (session->sess_reinstating)
+ __set_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags);
+ conn->cid = info->cid;
+ conn->stat_sn = info->stat_sn;
+ conn->exp_stat_sn = info->exp_stat_sn;
+ conn->rd_state = ISCSI_CONN_RD_STATE_IDLE;
+ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
+
+ conn->hdigest_type = session->sess_params.header_digest;
+ conn->ddigest_type = session->sess_params.data_digest;
+ res = digest_init(conn);
+ if (res != 0)
+ goto out_err_free1;
+
+ conn->target = session->target;
+ spin_lock_init(&conn->cmd_list_lock);
+ INIT_LIST_HEAD(&conn->cmd_list);
+ spin_lock_init(&conn->write_list_lock);
+ INIT_LIST_HEAD(&conn->write_list);
+ INIT_LIST_HEAD(&conn->write_timeout_list);
+ setup_timer(&conn->rsp_timer, conn_rsp_timer_fn, (unsigned long)conn);
+ init_waitqueue_head(&conn->read_state_waitQ);
+ init_completion(&conn->ready_to_free);
+ INIT_LIST_HEAD(&conn->reinst_pending_cmd_list);
+ INIT_LIST_HEAD(&conn->nop_req_list);
+ spin_lock_init(&conn->nop_req_list_lock);
+
+ conn->nop_in_ttt = 0;
+ INIT_DELAYED_WORK(&conn->nop_in_delayed_work,
+ (void (*)(struct work_struct *))conn_nop_in_delayed_work_fn);
+ conn->last_rcv_time = jiffies;
+ conn->rsp_timeout = session->tgt_params.rsp_timeout * HZ;
+ conn->nop_in_interval = session->tgt_params.nop_in_interval * HZ;
+ if (conn->nop_in_interval > 0) {
+ TRACE_DBG("Schedule Nop-In work for conn %p", conn);
+ schedule_delayed_work(&conn->nop_in_delayed_work,
+ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
+ }
+
+ conn->file = fget(info->fd);
+
+ res = conn_setup_sock(conn);
+ if (res != 0)
+ goto out_err_free2;
+
+ iscsi_get_initiator_ip(conn, addr, sizeof(addr));
+
+restart:
+ list_for_each_entry(c, &session->conn_list, conn_list_entry) {
+ if (strcmp(addr, kobject_name(&conn->iscsi_conn_kobj)) == 0) {
+ char c_addr[64];
+
+ iscsi_get_initiator_ip(conn, c_addr, sizeof(c_addr));
+
+ TRACE_DBG("Duplicated conn from the same initiator "
+ "%s found", c_addr);
+
+ snprintf(addr, sizeof(addr), "%s_%d", c_addr, n);
+ n++;
+ goto restart;
+ }
+ }
+
+ res = kobject_init_and_add(&conn->iscsi_conn_kobj, &iscsi_conn_ktype,
+ scst_sysfs_get_sess_kobj(session->scst_sess), addr);
+ if (res != 0) {
+ PRINT_ERROR("Unable create sysfs entries for conn %s",
+ addr);
+ goto out_err_free2;
+ }
+
+ TRACE_DBG("conn %p, iscsi_conn_kobj %p", conn, &conn->iscsi_conn_kobj);
+
+ res = sysfs_create_file(&conn->iscsi_conn_kobj,
+ &iscsi_conn_state_attr.attr);
+ if (res != 0) {
+ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
+ iscsi_conn_state_attr.attr.name, addr);
+ goto out_err_free3;
+ }
+
+ res = sysfs_create_file(&conn->iscsi_conn_kobj,
+ &iscsi_conn_cid_attr.attr);
+ if (res != 0) {
+ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
+ iscsi_conn_cid_attr.attr.name, addr);
+ goto out_err_free3;
+ }
+
+ res = sysfs_create_file(&conn->iscsi_conn_kobj,
+ &iscsi_conn_ip_attr.attr);
+ if (res != 0) {
+ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
+ iscsi_conn_ip_attr.attr.name, addr);
+ goto out_err_free3;
+ }
+
+ list_add_tail(&conn->conn_list_entry, &session->conn_list);
+
+ *new_conn = conn;
+
+out:
+ return res;
+
+out_err_free3:
+ kobject_put(&conn->iscsi_conn_kobj);
+ goto out;
+
+out_err_free2:
+ fput(conn->file);
+
+out_err_free1:
+ free_page((unsigned long)conn->read_iov);
+
+out_err_free_conn:
+ kfree(conn);
+
+out_err:
+ goto out;
+}
+
+/* target_mutex supposed to be locked */
+int __add_conn(struct iscsi_session *session, struct iscsi_kern_conn_info *info)
+{
+ struct iscsi_conn *conn, *new_conn = NULL;
+ int err;
+ bool reinstatement = false;
+
+ conn = conn_lookup(session, info->cid);
+ if ((conn != NULL) &&
+ !test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags)) {
+ /* conn reinstatement */
+ reinstatement = true;
+ } else if (!list_empty(&session->conn_list)) {
+ err = -EEXIST;
+ goto out;
+ }
+
+ err = iscsi_conn_alloc(session, info, &new_conn);
+ if (err != 0)
+ goto out;
+
+ if (reinstatement) {
+ TRACE_MGMT_DBG("Reinstating conn (old %p, new %p)", conn,
+ new_conn);
+ conn->conn_reinst_successor = new_conn;
+ __set_bit(ISCSI_CONN_REINSTATING, &new_conn->conn_aflags);
+ __mark_conn_closed(conn, 0);
+ }
+
+ conn_activate(new_conn);
+
+out:
+ return err;
+}
+
+/* target_mutex supposed to be locked */
+int __del_conn(struct iscsi_session *session, struct iscsi_kern_conn_info *info)
+{
+ struct iscsi_conn *conn;
+ int err = -EEXIST;
+
+ conn = conn_lookup(session, info->cid);
+ if (!conn) {
+ PRINT_ERROR("Connection %d not found", info->cid);
+ return err;
+ }
+
+ PRINT_INFO("Deleting connection with initiator %s (%p)",
+ conn->session->initiator_name, conn);
+
+ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE|ISCSI_CONN_DELETING);
+
+ return 0;
+}
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+
+void iscsi_extracheck_is_rd_thread(struct iscsi_conn *conn)
+{
+ if (unlikely(current != conn->rd_task)) {
+ printk(KERN_EMERG "conn %p rd_task != current %p (pid %d)\n",
+ conn, current, current->pid);
+ while (in_softirq())
+ local_bh_enable();
+ printk(KERN_EMERG "rd_state %x\n", conn->rd_state);
+ printk(KERN_EMERG "rd_task %p\n", conn->rd_task);
+ printk(KERN_EMERG "rd_task->pid %d\n", conn->rd_task->pid);
+ BUG();
+ }
+}
+
+void iscsi_extracheck_is_wr_thread(struct iscsi_conn *conn)
+{
+ if (unlikely(current != conn->wr_task)) {
+ printk(KERN_EMERG "conn %p wr_task != current %p (pid %d)\n",
+ conn, current, current->pid);
+ while (in_softirq())
+ local_bh_enable();
+ printk(KERN_EMERG "wr_state %x\n", conn->wr_state);
+ printk(KERN_EMERG "wr_task %p\n", conn->wr_task);
+ printk(KERN_EMERG "wr_task->pid %d\n", conn->wr_task->pid);
+ BUG();
+ }
+}
+
+#endif /* CONFIG_SCST_EXTRACHECKS */
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/digest.c linux-2.6.33/drivers/scst/iscsi-scst/digest.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/digest.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/digest.c
@@ -0,0 +1,226 @@
+/*
+ * iSCSI digest handling.
+ *
+ * Copyright (C) 2004 - 2006 Xiranet Communications GmbH
+ * <arne.redlich@...anet.com>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/types.h>
+#include <linux/scatterlist.h>
+
+#include "iscsi.h"
+#include "digest.h"
+#include <linux/crc32c.h>
+
+void digest_alg_available(int *val)
+{
+#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C)
+ int crc32c = 1;
+#else
+ int crc32c = 0;
+#endif
+
+ if ((*val & DIGEST_CRC32C) && !crc32c) {
+ PRINT_ERROR("%s", "CRC32C digest algorithm not available "
+ "in kernel");
+ *val |= ~DIGEST_CRC32C;
+ }
+}
+
+/**
+ * initialize support for digest calculation.
+ *
+ * digest_init -
+ * @conn: ptr to connection to make use of digests
+ *
+ * @return: 0 on success, < 0 on error
+ */
+int digest_init(struct iscsi_conn *conn)
+{
+ if (!(conn->hdigest_type & DIGEST_ALL))
+ conn->hdigest_type = DIGEST_NONE;
+
+ if (!(conn->ddigest_type & DIGEST_ALL))
+ conn->ddigest_type = DIGEST_NONE;
+
+ return 0;
+}
+
+static u32 evaluate_crc32_from_sg(struct scatterlist *sg, int nbytes,
+ uint32_t padding)
+{
+ u32 crc = ~0;
+ int pad_bytes = ((nbytes + 3) & -4) - nbytes;
+
+#ifdef CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES
+ if (((scst_random() % 100000) == 752)) {
+ PRINT_INFO("%s", "Simulating digest failure");
+ return 0;
+ }
+#endif
+
+#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C)
+ while (nbytes > 0) {
+ int d = min(nbytes, (int)(sg->length));
+ crc = crc32c(crc, sg_virt(sg), d);
+ nbytes -= d;
+ sg++;
+ }
+
+ if (pad_bytes)
+ crc = crc32c(crc, (u8 *)&padding, pad_bytes);
+#endif
+
+ return ~cpu_to_le32(crc);
+}
+
+static u32 digest_header(struct iscsi_pdu *pdu)
+{
+ struct scatterlist sg[2];
+ unsigned int nbytes = sizeof(struct iscsi_hdr);
+ int asize = (pdu->ahssize + 3) & -4;
+
+ sg_init_table(sg, 2);
+
+ sg_set_buf(&sg[0], &pdu->bhs, nbytes);
+ if (pdu->ahssize) {
+ sg_set_buf(&sg[1], pdu->ahs, asize);
+ nbytes += asize;
+ }
+ EXTRACHECKS_BUG_ON((nbytes & 3) != 0);
+ return evaluate_crc32_from_sg(sg, nbytes, 0);
+}
+
+static u32 digest_data(struct iscsi_cmnd *cmd, u32 size, u32 offset,
+ uint32_t padding)
+{
+ struct scatterlist *sg = cmd->sg;
+ int idx, count;
+ struct scatterlist saved_sg;
+ u32 crc;
+
+ offset += sg[0].offset;
+ idx = offset >> PAGE_SHIFT;
+ offset &= ~PAGE_MASK;
+
+ count = get_pgcnt(size, offset);
+
+ TRACE_DBG("req %p, idx %d, count %d, sg_cnt %d, size %d, "
+ "offset %d", cmd, idx, count, cmd->sg_cnt, size, offset);
+ BUG_ON(idx + count > cmd->sg_cnt);
+
+ saved_sg = sg[idx];
+ sg[idx].offset = offset;
+ sg[idx].length -= offset - saved_sg.offset;
+
+ crc = evaluate_crc32_from_sg(sg + idx, size, padding);
+
+ sg[idx] = saved_sg;
+ return crc;
+}
+
+int digest_rx_header(struct iscsi_cmnd *cmnd)
+{
+ u32 crc;
+
+ crc = digest_header(&cmnd->pdu);
+ if (unlikely(crc != cmnd->hdigest)) {
+ PRINT_ERROR("%s", "RX header digest failed");
+ return -EIO;
+ } else
+ TRACE_DBG("RX header digest OK for cmd %p", cmnd);
+
+ return 0;
+}
+
+void digest_tx_header(struct iscsi_cmnd *cmnd)
+{
+ cmnd->hdigest = digest_header(&cmnd->pdu);
+ TRACE_DBG("TX header digest for cmd %p: %x", cmnd, cmnd->hdigest);
+}
+
+int digest_rx_data(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_cmnd *req;
+ struct iscsi_data_out_hdr *req_hdr;
+ u32 offset, crc;
+ int res = 0;
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_SCSI_DATA_OUT:
+ req = cmnd->cmd_req;
+ if (unlikely(req == NULL)) {
+ /* It can be for prelim completed commands */
+ req = cmnd;
+ goto out;
+ }
+ req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
+ offset = be32_to_cpu(req_hdr->buffer_offset);
+ break;
+
+ default:
+ req = cmnd;
+ offset = 0;
+ }
+
+ /*
+ * We need to skip the digest check for prelim completed commands,
+ * because we use shared data buffer for them, so, most likely, the
+ * check will fail. Plus, for such commands we sometimes don't have
+ * sg_cnt set correctly (cmnd_prepare_get_rejected_cmd_data() doesn't
+ * do it).
+ */
+ if (unlikely(req->prelim_compl_flags != 0))
+ goto out;
+
+ crc = digest_data(req, cmnd->pdu.datasize, offset,
+ cmnd->conn->rpadding);
+
+ if (unlikely(crc != cmnd->ddigest)) {
+ PRINT_ERROR("%s", "RX data digest failed");
+ TRACE_MGMT_DBG("Calculated crc %x, ddigest %x, offset %d", crc,
+ cmnd->ddigest, offset);
+ iscsi_dump_pdu(&cmnd->pdu);
+ res = -EIO;
+ } else
+ TRACE_DBG("RX data digest OK for cmd %p", cmnd);
+
+out:
+ return res;
+}
+
+void digest_tx_data(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_data_in_hdr *hdr;
+ u32 offset;
+
+ TRACE_DBG("%s:%d req %p, own_sg %d, sg %p, sgcnt %d cmnd %p, "
+ "own_sg %d, sg %p, sgcnt %d", __func__, __LINE__,
+ cmnd->parent_req, cmnd->parent_req->own_sg,
+ cmnd->parent_req->sg, cmnd->parent_req->sg_cnt,
+ cmnd, cmnd->own_sg, cmnd->sg, cmnd->sg_cnt);
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_SCSI_DATA_IN:
+ hdr = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
+ offset = be32_to_cpu(hdr->buffer_offset);
+ break;
+ default:
+ offset = 0;
+ }
+
+ cmnd->ddigest = digest_data(cmnd, cmnd->pdu.datasize, offset, 0);
+ TRACE_DBG("TX data digest for cmd %p: %x (offset %d, opcode %x)", cmnd,
+ cmnd->ddigest, offset, cmnd_opcode(cmnd));
+}
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/event.c linux-2.6.33/drivers/scst/iscsi-scst/event.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/event.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/event.c
@@ -0,0 +1,163 @@
+/*
+ * Event notification code.
+ *
+ * Copyright (C) 2005 FUJITA Tomonori <tomof@....org>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Some functions are based on audit code.
+ */
+
+#include <net/tcp.h>
+#include "iscsi_scst.h"
+#include "iscsi.h"
+
+static struct sock *nl;
+static u32 iscsid_pid;
+
+static int event_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+ u32 uid, pid, seq;
+ char *data;
+
+ pid = NETLINK_CREDS(skb)->pid;
+ uid = NETLINK_CREDS(skb)->uid;
+ seq = nlh->nlmsg_seq;
+ data = NLMSG_DATA(nlh);
+
+ iscsid_pid = pid;
+
+ return 0;
+}
+
+static void event_recv_skb(struct sk_buff *skb)
+{
+ int err;
+ struct nlmsghdr *nlh;
+ u32 rlen;
+
+ while (skb->len >= NLMSG_SPACE(0)) {
+ nlh = (struct nlmsghdr *)skb->data;
+ if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+ goto out;
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ err = event_recv_msg(skb, nlh);
+ if (err)
+ netlink_ack(skb, nlh, -err);
+ else if (nlh->nlmsg_flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ skb_pull(skb, rlen);
+ }
+
+out:
+ return;
+}
+
+/* event_mutex supposed to be held */
+static int __event_send(const void *buf, int buf_len)
+{
+ int res = 0, len;
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ static u32 seq; /* protected by event_mutex */
+
+ if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN)
+ goto out;
+
+ len = NLMSG_SPACE(buf_len);
+
+ skb = alloc_skb(NLMSG_SPACE(len), GFP_KERNEL);
+ if (skb == NULL) {
+ PRINT_ERROR("alloc_skb() failed (len %d)", len);
+ res = -ENOMEM;
+ goto out;
+ }
+
+ nlh = __nlmsg_put(skb, iscsid_pid, seq++, NLMSG_DONE,
+ len - sizeof(*nlh), 0);
+
+ memcpy(NLMSG_DATA(nlh), buf, buf_len);
+ res = netlink_unicast(nl, skb, iscsid_pid, 0);
+ if (res <= 0) {
+ if (res != -ECONNREFUSED)
+ PRINT_ERROR("netlink_unicast() failed: %d", res);
+ else
+ TRACE(TRACE_MINOR, "netlink_unicast() failed: %s. "
+ "Not functioning user space?",
+ "Connection refused");
+ goto out;
+ }
+
+out:
+ return res;
+}
+
+int event_send(u32 tid, u64 sid, u32 cid, u32 cookie,
+ enum iscsi_kern_event_code code,
+ const char *param1, const char *param2)
+{
+ int err;
+ static DEFINE_MUTEX(event_mutex);
+ struct iscsi_kern_event event;
+ int param1_size, param2_size;
+
+ param1_size = (param1 != NULL) ? strlen(param1) : 0;
+ param2_size = (param2 != NULL) ? strlen(param2) : 0;
+
+ event.tid = tid;
+ event.sid = sid;
+ event.cid = cid;
+ event.code = code;
+ event.cookie = cookie;
+ event.param1_size = param1_size;
+ event.param2_size = param2_size;
+
+ mutex_lock(&event_mutex);
+
+ err = __event_send(&event, sizeof(event));
+ if (err <= 0)
+ goto out_unlock;
+
+ if (param1_size > 0) {
+ err = __event_send(param1, param1_size);
+ if (err <= 0)
+ goto out_unlock;
+ }
+
+ if (param2_size > 0) {
+ err = __event_send(param2, param2_size);
+ if (err <= 0)
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&event_mutex);
+ return err;
+}
+
+int __init event_init(void)
+{
+ nl = netlink_kernel_create(&init_net, NETLINK_ISCSI_SCST, 1,
+ event_recv_skb, NULL, THIS_MODULE);
+ if (!nl) {
+ PRINT_ERROR("%s", "netlink_kernel_create() failed");
+ return -ENOMEM;
+ } else
+ return 0;
+}
+
+void event_exit(void)
+{
+ netlink_kernel_release(nl);
+}
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/iscsi.c linux-2.6.33/drivers/scst/iscsi-scst/iscsi.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/iscsi.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/iscsi.c
@@ -0,0 +1,3583 @@
+/*
+ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@...istech.com>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, version 2
+ * of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/hash.h>
+#include <linux/kthread.h>
+#include <linux/scatterlist.h>
+#include <net/tcp.h>
+#include <scsi/scsi.h>
+
+#include "iscsi.h"
+#include "digest.h"
+
+#define ISCSI_INIT_WRITE_WAKE 0x1
+
+static int ctr_major;
+static char ctr_name[] = "iscsi-scst-ctl";
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+unsigned long iscsi_trace_flag = ISCSI_DEFAULT_LOG_FLAGS;
+#endif
+
+static struct kmem_cache *iscsi_cmnd_cache;
+
+DEFINE_SPINLOCK(iscsi_rd_lock);
+LIST_HEAD(iscsi_rd_list);
+DECLARE_WAIT_QUEUE_HEAD(iscsi_rd_waitQ);
+
+DEFINE_SPINLOCK(iscsi_wr_lock);
+LIST_HEAD(iscsi_wr_list);
+DECLARE_WAIT_QUEUE_HEAD(iscsi_wr_waitQ);
+
+static struct page *dummy_page;
+static struct scatterlist dummy_sg;
+
+struct iscsi_thread_t {
+ struct task_struct *thr;
+ struct list_head threads_list_entry;
+};
+
+static LIST_HEAD(iscsi_threads_list);
+
+static void cmnd_remove_data_wait_hash(struct iscsi_cmnd *cmnd);
+static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status);
+static void iscsi_check_send_delayed_tm_resp(struct iscsi_session *sess);
+static void req_cmnd_release(struct iscsi_cmnd *req);
+static int iscsi_preliminary_complete(struct iscsi_cmnd *req,
+ struct iscsi_cmnd *orig_req, bool get_data);
+static int cmnd_insert_data_wait_hash(struct iscsi_cmnd *cmnd);
+static void __cmnd_abort(struct iscsi_cmnd *cmnd);
+static void iscsi_set_resid(struct iscsi_cmnd *rsp, bool bufflen_set);
+static void iscsi_cmnd_init_write(struct iscsi_cmnd *rsp, int flags);
+
+static void req_del_from_write_timeout_list(struct iscsi_cmnd *req)
+{
+ struct iscsi_conn *conn;
+
+ if (!req->on_write_timeout_list)
+ goto out;
+
+ conn = req->conn;
+
+ TRACE_DBG("Deleting cmd %p from conn %p write_timeout_list",
+ req, conn);
+
+ spin_lock_bh(&conn->write_list_lock);
+
+ /* Recheck, since it can be changed behind us */
+ if (unlikely(!req->on_write_timeout_list))
+ goto out_unlock;
+
+ list_del(&req->write_timeout_list_entry);
+ req->on_write_timeout_list = 0;
+
+out_unlock:
+ spin_unlock_bh(&conn->write_list_lock);
+
+out:
+ return;
+}
+
+static inline u32 cmnd_write_size(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
+
+ if (hdr->flags & ISCSI_CMD_WRITE)
+ return be32_to_cpu(hdr->data_length);
+ return 0;
+}
+
+static inline int cmnd_read_size(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
+
+ if (hdr->flags & ISCSI_CMD_READ) {
+ struct iscsi_ahs_hdr *ahdr;
+
+ if (!(hdr->flags & ISCSI_CMD_WRITE))
+ return be32_to_cpu(hdr->data_length);
+
+ ahdr = (struct iscsi_ahs_hdr *)cmnd->pdu.ahs;
+ if (ahdr != NULL) {
+ uint8_t *p = (uint8_t *)ahdr;
+ unsigned int size = 0;
+ do {
+ int s;
+
+ ahdr = (struct iscsi_ahs_hdr *)p;
+
+ if (ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH) {
+ struct iscsi_rlength_ahdr *rh =
+ (struct iscsi_rlength_ahdr *)ahdr;
+ return be32_to_cpu(rh->read_length);
+ }
+
+ s = 3 + be16_to_cpu(ahdr->ahslength);
+ s = (s + 3) & -4;
+ size += s;
+ p += s;
+ } while (size < cmnd->pdu.ahssize);
+ }
+ return -1;
+ }
+ return 0;
+}
+
+void iscsi_restart_cmnd(struct iscsi_cmnd *cmnd)
+{
+ int status;
+
+ EXTRACHECKS_BUG_ON(cmnd->r2t_len_to_receive != 0);
+ EXTRACHECKS_BUG_ON(cmnd->r2t_len_to_send != 0);
+
+ req_del_from_write_timeout_list(cmnd);
+
+ /*
+ * Let's remove cmnd from the hash earlier to keep it smaller.
+ * See also corresponding comment in req_cmnd_release().
+ */
+ if (cmnd->hashed)
+ cmnd_remove_data_wait_hash(cmnd);
+
+ if (unlikely(test_bit(ISCSI_CONN_REINSTATING,
+ &cmnd->conn->conn_aflags))) {
+ struct iscsi_target *target = cmnd->conn->session->target;
+ bool get_out;
+
+ mutex_lock(&target->target_mutex);
+
+ get_out = test_bit(ISCSI_CONN_REINSTATING,
+ &cmnd->conn->conn_aflags);
+ /* Let's don't look dead */
+ if (scst_cmd_get_cdb(cmnd->scst_cmd)[0] == TEST_UNIT_READY)
+ get_out = false;
+
+ if (!get_out)
+ goto unlock_cont;
+
+ TRACE_MGMT_DBG("Pending cmnd %p, because conn %p is "
+ "reinstated", cmnd, cmnd->conn);
+
+ cmnd->scst_state = ISCSI_CMD_STATE_REINST_PENDING;
+ list_add_tail(&cmnd->reinst_pending_cmd_list_entry,
+ &cmnd->conn->reinst_pending_cmd_list);
+
+unlock_cont:
+ mutex_unlock(&target->target_mutex);
+
+ if (get_out)
+ goto out;
+ }
+
+ if (unlikely(cmnd->prelim_compl_flags != 0)) {
+ if (test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags)) {
+ TRACE_MGMT_DBG("cmnd %p (scst_cmd %p) aborted", cmnd,
+ cmnd->scst_cmd);
+ req_cmnd_release_force(cmnd);
+ goto out;
+ }
+
+ if (cmnd->scst_cmd == NULL) {
+ TRACE_MGMT_DBG("Finishing preliminary completed cmd %p "
+ "with NULL scst_cmd", cmnd);
+ req_cmnd_release(cmnd);
+ goto out;
+ }
+
+ status = SCST_PREPROCESS_STATUS_ERROR_SENSE_SET;
+ } else
+ status = SCST_PREPROCESS_STATUS_SUCCESS;
+
+ cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED;
+
+ scst_restart_cmd(cmnd->scst_cmd, status, SCST_CONTEXT_THREAD);
+
+out:
+ return;
+}
+
+void iscsi_fail_data_waiting_cmnd(struct iscsi_cmnd *cmnd)
+{
+
+ TRACE_MGMT_DBG("Failing data waiting cmnd %p", cmnd);
+
+ /*
+ * There is no race with conn_abort(), since all functions
+ * called from single read thread
+ */
+ iscsi_extracheck_is_rd_thread(cmnd->conn);
+ cmnd->r2t_len_to_receive = 0;
+ cmnd->r2t_len_to_send = 0;
+
+ req_cmnd_release_force(cmnd);
+ return;
+}
+
+struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn,
+ struct iscsi_cmnd *parent)
+{
+ struct iscsi_cmnd *cmnd;
+
+ /* ToDo: __GFP_NOFAIL?? */
+ cmnd = kmem_cache_zalloc(iscsi_cmnd_cache, GFP_KERNEL|__GFP_NOFAIL);
+
+ atomic_set(&cmnd->ref_cnt, 1);
+ cmnd->scst_state = ISCSI_CMD_STATE_NEW;
+ cmnd->conn = conn;
+ cmnd->parent_req = parent;
+
+ if (parent == NULL) {
+ conn_get(conn);
+
+ INIT_LIST_HEAD(&cmnd->rsp_cmd_list);
+ INIT_LIST_HEAD(&cmnd->rx_ddigest_cmd_list);
+ cmnd->target_task_tag = cpu_to_be32(ISCSI_RESERVED_TAG);
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_add_tail(&cmnd->cmd_list_entry, &conn->cmd_list);
+ spin_unlock_bh(&conn->cmd_list_lock);
+ }
+
+ TRACE_DBG("conn %p, parent %p, cmnd %p", conn, parent, cmnd);
+ return cmnd;
+}
+
+/* Frees a command. Also frees the additional header. */
+static void cmnd_free(struct iscsi_cmnd *cmnd)
+{
+
+ TRACE_DBG("cmnd %p", cmnd);
+
+ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags))) {
+ TRACE_MGMT_DBG("Free aborted cmd %p (scst cmd %p, state %d, "
+ "parent_req %p)", cmnd, cmnd->scst_cmd,
+ cmnd->scst_state, cmnd->parent_req);
+ }
+
+ /* Catch users from cmd_list or rsp_cmd_list */
+ EXTRACHECKS_BUG_ON(atomic_read(&cmnd->ref_cnt) != 0);
+
+ kfree(cmnd->pdu.ahs);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ if (unlikely(cmnd->on_write_list || cmnd->on_write_timeout_list)) {
+ struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+
+ PRINT_CRIT_ERROR("cmnd %p still on some list?, %x, %x, %x, "
+ "%x, %x, %x, %x", cmnd, req->opcode, req->scb[0],
+ req->flags, req->itt, be32_to_cpu(req->data_length),
+ req->cmd_sn, be32_to_cpu(cmnd->pdu.datasize));
+
+ if (unlikely(cmnd->parent_req)) {
+ struct iscsi_scsi_cmd_hdr *preq =
+ cmnd_hdr(cmnd->parent_req);
+ PRINT_CRIT_ERROR("%p %x %u", preq, preq->opcode,
+ preq->scb[0]);
+ }
+ BUG();
+ }
+#endif
+
+ kmem_cache_free(iscsi_cmnd_cache, cmnd);
+ return;
+}
+
+/* Might be called under some lock and on SIRQ */
+void cmnd_done(struct iscsi_cmnd *cmnd)
+{
+
+ TRACE_DBG("cmnd %p", cmnd);
+
+ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags))) {
+ TRACE_MGMT_DBG("Done aborted cmd %p (scst cmd %p, state %d, "
+ "parent_req %p)", cmnd, cmnd->scst_cmd,
+ cmnd->scst_state, cmnd->parent_req);
+ }
+
+ EXTRACHECKS_BUG_ON(cmnd->on_rx_digest_list);
+ EXTRACHECKS_BUG_ON(cmnd->hashed);
+
+ req_del_from_write_timeout_list(cmnd);
+
+ if (cmnd->parent_req == NULL) {
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_cmnd *rsp, *t;
+
+ TRACE_DBG("Deleting req %p from conn %p", cmnd, conn);
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_del(&cmnd->cmd_list_entry);
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ conn_put(conn);
+
+ EXTRACHECKS_BUG_ON(!list_empty(&cmnd->rx_ddigest_cmd_list));
+
+ /* Order between above and below code is important! */
+
+ if ((cmnd->scst_cmd != NULL) || (cmnd->scst_aen != NULL)) {
+ switch (cmnd->scst_state) {
+ case ISCSI_CMD_STATE_PROCESSED:
+ TRACE_DBG("cmd %p PROCESSED", cmnd);
+ scst_tgt_cmd_done(cmnd->scst_cmd,
+ SCST_CONTEXT_DIRECT_ATOMIC);
+ break;
+
+ case ISCSI_CMD_STATE_AFTER_PREPROC:
+ {
+ /* It can be for some aborted commands */
+ struct scst_cmd *scst_cmd = cmnd->scst_cmd;
+ TRACE_DBG("cmd %p AFTER_PREPROC", cmnd);
+ cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED;
+ cmnd->scst_cmd = NULL;
+ scst_restart_cmd(scst_cmd,
+ SCST_PREPROCESS_STATUS_ERROR_FATAL,
+ SCST_CONTEXT_THREAD);
+ break;
+ }
+
+ case ISCSI_CMD_STATE_AEN:
+ TRACE_DBG("cmd %p AEN PROCESSED", cmnd);
+ scst_aen_done(cmnd->scst_aen);
+ break;
+
+ case ISCSI_CMD_STATE_OUT_OF_SCST_PRELIM_COMPL:
+ break;
+
+ default:
+ PRINT_CRIT_ERROR("Unexpected cmnd scst state "
+ "%d", cmnd->scst_state);
+ BUG();
+ break;
+ }
+ }
+
+ if (cmnd->own_sg) {
+ TRACE_DBG("own_sg for req %p", cmnd);
+ if (cmnd->sg != &dummy_sg)
+ scst_free(cmnd->sg, cmnd->sg_cnt);
+#ifdef CONFIG_SCST_DEBUG
+ cmnd->own_sg = 0;
+ cmnd->sg = NULL;
+ cmnd->sg_cnt = -1;
+#endif
+ }
+
+ if (cmnd->dec_active_cmnds) {
+ struct iscsi_session *sess = cmnd->conn->session;
+ TRACE_DBG("Decrementing active_cmds (cmd %p, sess %p, "
+ "new value %d)", cmnd, sess,
+ atomic_read(&sess->active_cmds)-1);
+ atomic_dec(&sess->active_cmds);
+#ifdef CONFIG_SCST_EXTRACHECKS
+ if (unlikely(atomic_read(&sess->active_cmds) < 0)) {
+ PRINT_CRIT_ERROR("active_cmds < 0 (%d)!!",
+ atomic_read(&sess->active_cmds));
+ BUG();
+ }
+#endif
+ }
+
+ list_for_each_entry_safe(rsp, t, &cmnd->rsp_cmd_list,
+ rsp_cmd_list_entry) {
+ cmnd_free(rsp);
+ }
+
+ cmnd_free(cmnd);
+ } else {
+ if (cmnd->own_sg) {
+ TRACE_DBG("own_sg for rsp %p", cmnd);
+ if ((cmnd->sg != &dummy_sg) && (cmnd->sg != cmnd->rsp_sg))
+ scst_free(cmnd->sg, cmnd->sg_cnt);
+#ifdef CONFIG_SCST_DEBUG
+ cmnd->own_sg = 0;
+ cmnd->sg = NULL;
+ cmnd->sg_cnt = -1;
+#endif
+ }
+
+ EXTRACHECKS_BUG_ON(cmnd->dec_active_cmnds);
+
+ if (cmnd == cmnd->parent_req->main_rsp) {
+ TRACE_DBG("Finishing main rsp %p (req %p)", cmnd,
+ cmnd->parent_req);
+ cmnd->parent_req->main_rsp = NULL;
+ }
+
+ cmnd_put(cmnd->parent_req);
+ /*
+ * rsp will be freed on the last parent's put and can already
+ * be freed!!
+ */
+ }
+ return;
+}
+
+/*
+ * Corresponding conn may also get destroyed after this function, except only
+ * if it's called from the read thread!
+ *
+ * It can't be called in parallel with iscsi_cmnds_init_write()!
+ */
+void req_cmnd_release_force(struct iscsi_cmnd *req)
+{
+ struct iscsi_cmnd *rsp, *t;
+ struct iscsi_conn *conn = req->conn;
+ LIST_HEAD(cmds_list);
+
+ TRACE_MGMT_DBG("req %p", req);
+
+ BUG_ON(req == conn->read_cmnd);
+
+ spin_lock_bh(&conn->write_list_lock);
+ list_for_each_entry_safe(rsp, t, &conn->write_list, write_list_entry) {
+ if (rsp->parent_req != req)
+ continue;
+
+ cmd_del_from_write_list(rsp);
+
+ list_add_tail(&rsp->write_list_entry, &cmds_list);
+ }
+ spin_unlock_bh(&conn->write_list_lock);
+
+ list_for_each_entry_safe(rsp, t, &cmds_list, write_list_entry) {
+ TRACE_MGMT_DBG("Putting write rsp %p", rsp);
+ list_del(&rsp->write_list_entry);
+ cmnd_put(rsp);
+ }
+
+ /* Supposed nobody can add responses in the list anymore */
+ list_for_each_entry_reverse(rsp, &req->rsp_cmd_list,
+ rsp_cmd_list_entry) {
+ bool r;
+
+ if (rsp->force_cleanup_done)
+ continue;
+
+ rsp->force_cleanup_done = 1;
+
+ if (cmnd_get_check(rsp))
+ continue;
+
+ spin_lock_bh(&conn->write_list_lock);
+ r = rsp->on_write_list || rsp->write_processing_started;
+ spin_unlock_bh(&conn->write_list_lock);
+
+ cmnd_put(rsp);
+
+ if (r)
+ continue;
+
+ /*
+ * If both on_write_list and write_processing_started not set,
+ * we can safely put() rsp.
+ */
+ TRACE_MGMT_DBG("Putting rsp %p", rsp);
+ cmnd_put(rsp);
+ }
+
+ if (req->main_rsp != NULL) {
+ TRACE_MGMT_DBG("Putting main rsp %p", req->main_rsp);
+ cmnd_put(req->main_rsp);
+ req->main_rsp = NULL;
+ }
+
+ req_cmnd_release(req);
+ return;
+}
+
+/*
+ * Corresponding conn may also get destroyed after this function, except only
+ * if it's called from the read thread!
+ */
+static void req_cmnd_release(struct iscsi_cmnd *req)
+{
+ struct iscsi_cmnd *c, *t;
+
+ TRACE_DBG("req %p", req);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ BUG_ON(req->release_called);
+ req->release_called = 1;
+#endif
+
+ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags))) {
+ TRACE_MGMT_DBG("Release aborted req cmd %p (scst cmd %p, "
+ "state %d)", req, req->scst_cmd, req->scst_state);
+ }
+
+ BUG_ON(req->parent_req != NULL);
+
+ /*
+ * We have to remove hashed req from the hash list before sending
+ * response. Otherwise we can have a race, when for some reason cmd's
+ * release (and, hence, removal from the hash) is delayed after the
+ * transmission and initiator sends cmd with the same ITT, hence
+ * the new command will be erroneously rejected as a duplicate.
+ */
+ if (unlikely(req->hashed)) {
+ /* It sometimes can happen during errors recovery */
+ cmnd_remove_data_wait_hash(req);
+ }
+
+ if (unlikely(req->main_rsp != NULL)) {
+ TRACE_DBG("Sending main rsp %p", req->main_rsp);
+ iscsi_cmnd_init_write(req->main_rsp, ISCSI_INIT_WRITE_WAKE);
+ req->main_rsp = NULL;
+ }
+
+ list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list,
+ rx_ddigest_cmd_list_entry) {
+ cmd_del_from_rx_ddigest_list(c);
+ cmnd_put(c);
+ }
+
+ EXTRACHECKS_BUG_ON(req->pending);
+
+ if (req->dec_active_cmnds) {
+ struct iscsi_session *sess = req->conn->session;
+ TRACE_DBG("Decrementing active_cmds (cmd %p, sess %p, "
+ "new value %d)", req, sess,
+ atomic_read(&sess->active_cmds)-1);
+ atomic_dec(&sess->active_cmds);
+ req->dec_active_cmnds = 0;
+#ifdef CONFIG_SCST_EXTRACHECKS
+ if (unlikely(atomic_read(&sess->active_cmds) < 0)) {
+ PRINT_CRIT_ERROR("active_cmds < 0 (%d)!!",
+ atomic_read(&sess->active_cmds));
+ BUG();
+ }
+#endif
+ }
+
+ cmnd_put(req);
+ return;
+}
+
+/*
+ * Corresponding conn may also get destroyed after this function, except only
+ * if it's called from the read thread!
+ */
+void rsp_cmnd_release(struct iscsi_cmnd *cmnd)
+{
+ TRACE_DBG("%p", cmnd);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ BUG_ON(cmnd->release_called);
+ cmnd->release_called = 1;
+#endif
+
+ EXTRACHECKS_BUG_ON(cmnd->parent_req == NULL);
+
+ cmnd_put(cmnd);
+ return;
+}
+
+static struct iscsi_cmnd *iscsi_alloc_rsp(struct iscsi_cmnd *parent)
+{
+ struct iscsi_cmnd *rsp;
+
+ rsp = cmnd_alloc(parent->conn, parent);
+
+ TRACE_DBG("Adding rsp %p to parent %p", rsp, parent);
+ list_add_tail(&rsp->rsp_cmd_list_entry, &parent->rsp_cmd_list);
+
+ cmnd_get(parent);
+ return rsp;
+}
+
+static inline struct iscsi_cmnd *iscsi_alloc_main_rsp(struct iscsi_cmnd *parent)
+{
+ struct iscsi_cmnd *rsp;
+
+ EXTRACHECKS_BUG_ON(parent->main_rsp != NULL);
+
+ rsp = iscsi_alloc_rsp(parent);
+ parent->main_rsp = rsp;
+ return rsp;
+}
+
+static void iscsi_cmnds_init_write(struct list_head *send, int flags)
+{
+ struct iscsi_cmnd *rsp = list_entry(send->next, struct iscsi_cmnd,
+ write_list_entry);
+ struct iscsi_conn *conn = rsp->conn;
+ struct list_head *pos, *next;
+
+ BUG_ON(list_empty(send));
+
+ if (!(conn->ddigest_type & DIGEST_NONE)) {
+ list_for_each(pos, send) {
+ rsp = list_entry(pos, struct iscsi_cmnd,
+ write_list_entry);
+
+ if (rsp->pdu.datasize != 0) {
+ TRACE_DBG("Doing data digest (%p:%x)", rsp,
+ cmnd_opcode(rsp));
+ digest_tx_data(rsp);
+ }
+ }
+ }
+
+ spin_lock_bh(&conn->write_list_lock);
+ list_for_each_safe(pos, next, send) {
+ rsp = list_entry(pos, struct iscsi_cmnd, write_list_entry);
+
+ TRACE_DBG("%p:%x", rsp, cmnd_opcode(rsp));
+
+ BUG_ON(conn != rsp->conn);
+
+ list_del(&rsp->write_list_entry);
+ cmd_add_on_write_list(conn, rsp);
+ }
+ spin_unlock_bh(&conn->write_list_lock);
+
+ if (flags & ISCSI_INIT_WRITE_WAKE)
+ iscsi_make_conn_wr_active(conn);
+
+ return;
+}
+
+static void iscsi_cmnd_init_write(struct iscsi_cmnd *rsp, int flags)
+{
+ LIST_HEAD(head);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ if (unlikely(rsp->on_write_list)) {
+ PRINT_CRIT_ERROR("cmd already on write list (%x %x %x "
+ "%u %u %d %d", cmnd_itt(rsp),
+ cmnd_opcode(rsp), cmnd_scsicode(rsp),
+ rsp->hdigest, rsp->ddigest,
+ list_empty(&rsp->rsp_cmd_list), rsp->hashed);
+ BUG();
+ }
+#endif
+ list_add_tail(&rsp->write_list_entry, &head);
+ iscsi_cmnds_init_write(&head, flags);
+ return;
+}
+
+static void send_data_rsp(struct iscsi_cmnd *req, u8 status, int send_status)
+{
+ struct iscsi_cmnd *rsp;
+ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+ struct iscsi_data_in_hdr *rsp_hdr;
+ u32 pdusize, expsize, size, offset, sn;
+ LIST_HEAD(send);
+
+ TRACE_DBG("req %p", req);
+
+ pdusize = req->conn->session->sess_params.max_xmit_data_length;
+ expsize = req->read_size;
+ size = min(expsize, (u32)req->bufflen);
+ offset = 0;
+ sn = 0;
+
+ while (1) {
+ rsp = iscsi_alloc_rsp(req);
+ TRACE_DBG("rsp %p", rsp);
+ rsp->sg = req->sg;
+ rsp->sg_cnt = req->sg_cnt;
+ rsp->bufflen = req->bufflen;
+ rsp_hdr = (struct iscsi_data_in_hdr *)&rsp->pdu.bhs;
+
+ rsp_hdr->opcode = ISCSI_OP_SCSI_DATA_IN;
+ rsp_hdr->itt = req_hdr->itt;
+ rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
+ rsp_hdr->buffer_offset = cpu_to_be32(offset);
+ rsp_hdr->data_sn = cpu_to_be32(sn);
+
+ if (size <= pdusize) {
+ TRACE_DBG("offset %d, size %d", offset, size);
+ rsp->pdu.datasize = size;
+ if (send_status) {
+ unsigned int scsisize;
+
+ TRACE_DBG("status %x", status);
+
+ EXTRACHECKS_BUG_ON((cmnd_hdr(req)->flags & ISCSI_CMD_WRITE) != 0);
+
+ rsp_hdr->flags = ISCSI_FLG_FINAL | ISCSI_FLG_STATUS;
+ rsp_hdr->cmd_status = status;
+
+ scsisize = req->bufflen;
+ if (scsisize < expsize) {
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+ size = expsize - scsisize;
+ } else if (scsisize > expsize) {
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
+ size = scsisize - expsize;
+ } else
+ size = 0;
+ rsp_hdr->residual_count = cpu_to_be32(size);
+ }
+ list_add_tail(&rsp->write_list_entry, &send);
+ break;
+ }
+
+ TRACE_DBG("pdusize %d, offset %d, size %d", pdusize, offset,
+ size);
+
+ rsp->pdu.datasize = pdusize;
+
+ size -= pdusize;
+ offset += pdusize;
+ sn++;
+
+ list_add_tail(&rsp->write_list_entry, &send);
+ }
+ iscsi_cmnds_init_write(&send, 0);
+ return;
+}
+
+static void iscsi_init_status_rsp(struct iscsi_cmnd *rsp,
+ int status, const u8 *sense_buf, int sense_len, bool bufflen_set)
+{
+ struct iscsi_cmnd *req = rsp->parent_req;
+ struct iscsi_scsi_rsp_hdr *rsp_hdr;
+ struct scatterlist *sg;
+
+ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
+ rsp_hdr->cmd_status = status;
+ rsp_hdr->itt = cmnd_hdr(req)->itt;
+
+ if (SCST_SENSE_VALID(sense_buf)) {
+ TRACE_DBG("%s", "SENSE VALID");
+
+ sg = rsp->sg = rsp->rsp_sg;
+ rsp->sg_cnt = 2;
+ rsp->own_sg = 1;
+
+ sg_init_table(sg, 2);
+ sg_set_buf(&sg[0], &rsp->sense_hdr, sizeof(rsp->sense_hdr));
+ sg_set_buf(&sg[1], sense_buf, sense_len);
+
+ rsp->sense_hdr.length = cpu_to_be16(sense_len);
+
+ rsp->pdu.datasize = sizeof(rsp->sense_hdr) + sense_len;
+ rsp->bufflen = rsp->pdu.datasize;
+ } else {
+ rsp->pdu.datasize = 0;
+ rsp->bufflen = 0;
+ }
+
+ iscsi_set_resid(rsp, bufflen_set);
+ return;
+}
+
+static inline struct iscsi_cmnd *create_status_rsp(struct iscsi_cmnd *req,
+ int status, const u8 *sense_buf, int sense_len, bool bufflen_set)
+{
+ struct iscsi_cmnd *rsp;
+
+ rsp = iscsi_alloc_rsp(req);
+ TRACE_DBG("rsp %p", rsp);
+
+ iscsi_init_status_rsp(rsp, status, sense_buf, sense_len, bufflen_set);
+ return rsp;
+}
+
+static struct iscsi_cmnd *create_prelim_status_rsp(struct iscsi_cmnd *req,
+ int status, const u8 *sense_buf, int sense_len)
+{
+ struct iscsi_cmnd *rsp;
+
+ rsp = iscsi_alloc_main_rsp(req);
+ TRACE_DBG("main rsp %p", rsp);
+
+ iscsi_init_status_rsp(rsp, status, sense_buf, sense_len, false);
+ return rsp;
+}
+
+static int iscsi_set_prelim_r2t_len_to_receive(struct iscsi_cmnd *req)
+{
+ struct iscsi_hdr *req_hdr = &req->pdu.bhs;
+ int res = 0;
+
+ if (req_hdr->flags & ISCSI_CMD_FINAL)
+ goto out;
+
+ res = cmnd_insert_data_wait_hash(req);
+ if (res != 0) {
+ /*
+ * We have to close connection, because otherwise a data
+ * corruption is possible if we allow to receive data
+ * for this request in another request with dublicated ITT.
+ */
+ mark_conn_closed(req->conn);
+ goto out;
+ }
+
+ /*
+ * We need to wait for one or more PDUs. Let's simplify
+ * other code and pretend we need to receive 1 byte.
+ * In data_out_start() we will correct it.
+ */
+ if (req->outstanding_r2t == 0) {
+ req->outstanding_r2t = 1;
+ req_add_to_write_timeout_list(req);
+ }
+ req->r2t_len_to_receive = 1;
+ req->r2t_len_to_send = 0;
+
+ TRACE_DBG("req %p, op %x, outstanding_r2t %d, r2t_len_to_receive %d, "
+ "r2t_len_to_send %d", req, cmnd_opcode(req),
+ req->outstanding_r2t, req->r2t_len_to_receive,
+ req->r2t_len_to_send);
+
+out:
+ return res;
+}
+
+static int create_preliminary_status_rsp(struct iscsi_cmnd *req,
+ int status, const u8 *sense_buf, int sense_len)
+{
+ int res = 0;
+ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+
+ if (req->prelim_compl_flags != 0) {
+ TRACE_MGMT_DBG("req %p already prelim completed", req);
+ goto out;
+ }
+
+ req->scst_state = ISCSI_CMD_STATE_OUT_OF_SCST_PRELIM_COMPL;
+
+ if ((req_hdr->flags & ISCSI_CMD_READ) &&
+ (req_hdr->flags & ISCSI_CMD_WRITE)) {
+ int sz = cmnd_read_size(req);
+ if (sz > 0)
+ req->read_size = sz;
+ } else if (req_hdr->flags & ISCSI_CMD_READ)
+ req->read_size = be32_to_cpu(req_hdr->data_length);
+
+ create_prelim_status_rsp(req, status, sense_buf, sense_len);
+ res = iscsi_preliminary_complete(req, req, true);
+
+out:
+ return res;
+}
+
+static int set_scst_preliminary_status_rsp(struct iscsi_cmnd *req,
+ bool get_data, int key, int asc, int ascq)
+{
+ int res = 0;
+
+ if (req->scst_cmd == NULL) {
+ /* There must be already error set */
+ goto complete;
+ }
+
+ scst_set_cmd_error(req->scst_cmd, key, asc, ascq);
+
+complete:
+ res = iscsi_preliminary_complete(req, req, get_data);
+ return res;
+}
+
+static int create_reject_rsp(struct iscsi_cmnd *req, int reason, bool get_data)
+{
+ int res = 0;
+ struct iscsi_cmnd *rsp;
+ struct iscsi_reject_hdr *rsp_hdr;
+ struct scatterlist *sg;
+
+ TRACE_MGMT_DBG("Reject: req %p, reason %x", req, reason);
+
+ if (cmnd_opcode(req) == ISCSI_OP_SCSI_CMD) {
+ if (req->scst_cmd == NULL) {
+ /* BUSY status must be already set */
+ struct iscsi_scsi_rsp_hdr *rsp_hdr;
+ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&req->main_rsp->pdu.bhs;
+ BUG_ON(rsp_hdr->cmd_status == 0);
+ /*
+ * Let's not send REJECT here. The initiator will retry
+ * and, hopefully, next time we will not fail allocating
+ * scst_cmd, so we will then send the REJECT.
+ */
+ goto out;
+ } else
+ set_scst_preliminary_status_rsp(req, get_data,
+ SCST_LOAD_SENSE(scst_sense_invalid_message));
+ }
+
+ rsp = iscsi_alloc_main_rsp(req);
+ rsp_hdr = (struct iscsi_reject_hdr *)&rsp->pdu.bhs;
+
+ rsp_hdr->opcode = ISCSI_OP_REJECT;
+ rsp_hdr->ffffffff = ISCSI_RESERVED_TAG;
+ rsp_hdr->reason = reason;
+
+ sg = rsp->sg = rsp->rsp_sg;
+ rsp->sg_cnt = 1;
+ rsp->own_sg = 1;
+ sg_init_one(sg, &req->pdu.bhs, sizeof(struct iscsi_hdr));
+ rsp->bufflen = rsp->pdu.datasize = sizeof(struct iscsi_hdr);
+
+ res = iscsi_preliminary_complete(req, req, true);
+
+out:
+ return res;
+}
+
+static inline int iscsi_get_allowed_cmds(struct iscsi_session *sess)
+{
+ int res = max(-1, (int)sess->tgt_params.queued_cmnds -
+ atomic_read(&sess->active_cmds)-1);
+ TRACE_DBG("allowed cmds %d (sess %p, active_cmds %d)", res,
+ sess, atomic_read(&sess->active_cmds));
+ return res;
+}
+
+static u32 cmnd_set_sn(struct iscsi_cmnd *cmnd, int set_stat_sn)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_session *sess = conn->session;
+ u32 res;
+
+ spin_lock(&sess->sn_lock);
+
+ if (set_stat_sn)
+ cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn++);
+ cmnd->pdu.bhs.exp_sn = cpu_to_be32(sess->exp_cmd_sn);
+ cmnd->pdu.bhs.max_sn = cpu_to_be32(sess->exp_cmd_sn +
+ iscsi_get_allowed_cmds(sess));
+
+ res = cpu_to_be32(conn->stat_sn);
+
+ spin_unlock(&sess->sn_lock);
+ return res;
+}
+
+/* Called under sn_lock */
+static void __update_stat_sn(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ u32 exp_stat_sn;
+
+ cmnd->pdu.bhs.exp_sn = exp_stat_sn = be32_to_cpu(cmnd->pdu.bhs.exp_sn);
+ TRACE_DBG("%x,%x", cmnd_opcode(cmnd), exp_stat_sn);
+ if ((int)(exp_stat_sn - conn->exp_stat_sn) > 0 &&
+ (int)(exp_stat_sn - conn->stat_sn) <= 0) {
+ /* free pdu resources */
+ cmnd->conn->exp_stat_sn = exp_stat_sn;
+ }
+ return;
+}
+
+static inline void update_stat_sn(struct iscsi_cmnd *cmnd)
+{
+ spin_lock(&cmnd->conn->session->sn_lock);
+ __update_stat_sn(cmnd);
+ spin_unlock(&cmnd->conn->session->sn_lock);
+ return;
+}
+
+/* Called under sn_lock */
+static int check_cmd_sn(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_session *session = cmnd->conn->session;
+ u32 cmd_sn;
+
+ cmnd->pdu.bhs.sn = cmd_sn = be32_to_cpu(cmnd->pdu.bhs.sn);
+ TRACE_DBG("%d(%d)", cmd_sn, session->exp_cmd_sn);
+ if (likely((s32)(cmd_sn - session->exp_cmd_sn) >= 0))
+ return 0;
+ PRINT_ERROR("sequence error (%x,%x)", cmd_sn, session->exp_cmd_sn);
+ return -ISCSI_REASON_PROTOCOL_ERROR;
+}
+
+static struct iscsi_cmnd *cmnd_find_itt_get(struct iscsi_conn *conn, u32 itt)
+{
+ struct iscsi_cmnd *cmnd, *found_cmnd = NULL;
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
+ if ((cmnd->pdu.bhs.itt == itt) && !cmnd_get_check(cmnd)) {
+ found_cmnd = cmnd;
+ break;
+ }
+ }
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ return found_cmnd;
+}
+
+/**
+ ** We use the ITT hash only to find original request PDU for subsequent
+ ** Data-Out PDUs.
+ **/
+
+/* Must be called under cmnd_data_wait_hash_lock */
+static struct iscsi_cmnd *__cmnd_find_data_wait_hash(struct iscsi_conn *conn,
+ u32 itt)
+{
+ struct list_head *head;
+ struct iscsi_cmnd *cmnd;
+
+ head = &conn->session->cmnd_data_wait_hash[cmnd_hashfn(itt)];
+
+ list_for_each_entry(cmnd, head, hash_list_entry) {
+ if (cmnd->pdu.bhs.itt == itt)
+ return cmnd;
+ }
+ return NULL;
+}
+
+static struct iscsi_cmnd *cmnd_find_data_wait_hash(struct iscsi_conn *conn,
+ u32 itt)
+{
+ struct iscsi_cmnd *res;
+ struct iscsi_session *session = conn->session;
+
+ spin_lock(&session->cmnd_data_wait_hash_lock);
+ res = __cmnd_find_data_wait_hash(conn, itt);
+ spin_unlock(&session->cmnd_data_wait_hash_lock);
+
+ return res;
+}
+
+static inline u32 get_next_ttt(struct iscsi_conn *conn)
+{
+ u32 ttt;
+ struct iscsi_session *session = conn->session;
+
+ /* Not compatible with MC/S! */
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ if (unlikely(session->next_ttt == ISCSI_RESERVED_TAG))
+ session->next_ttt++;
+ ttt = session->next_ttt++;
+
+ return ttt;
+}
+
+static int cmnd_insert_data_wait_hash(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_session *session = cmnd->conn->session;
+ struct iscsi_cmnd *tmp;
+ struct list_head *head;
+ int err = 0;
+ u32 itt = cmnd->pdu.bhs.itt;
+
+ if (unlikely(cmnd->hashed)) {
+ /* It can be for preliminary completed commands */
+ goto out;
+ }
+
+ /*
+ * We don't need TTT, because ITT/buffer_offset pair is sufficient
+ * to find out the original request and buffer for Data-Out PDUs, but
+ * crazy iSCSI spec requires us to send this superfluous field in
+ * R2T PDUs and some initiators may rely on it.
+ */
+ cmnd->target_task_tag = get_next_ttt(cmnd->conn);
+
+ TRACE_DBG("%p:%x", cmnd, itt);
+ if (unlikely(itt == ISCSI_RESERVED_TAG)) {
+ PRINT_ERROR("%s", "ITT is RESERVED_TAG");
+ PRINT_BUFFER("Incorrect BHS", &cmnd->pdu.bhs,
+ sizeof(cmnd->pdu.bhs));
+ err = -ISCSI_REASON_PROTOCOL_ERROR;
+ goto out;
+ }
+
+ spin_lock(&session->cmnd_data_wait_hash_lock);
+
+ head = &session->cmnd_data_wait_hash[cmnd_hashfn(itt)];
+
+ tmp = __cmnd_find_data_wait_hash(cmnd->conn, itt);
+ if (likely(!tmp)) {
+ TRACE_DBG("Adding cmnd %p to the hash (ITT %x)", cmnd,
+ cmnd_itt(cmnd));
+ list_add_tail(&cmnd->hash_list_entry, head);
+ cmnd->hashed = 1;
+ } else {
+ PRINT_ERROR("Task %x in progress, cmnd %p", itt, cmnd);
+ err = -ISCSI_REASON_TASK_IN_PROGRESS;
+ }
+
+ spin_unlock(&session->cmnd_data_wait_hash_lock);
+
+out:
+ return err;
+}
+
+static void cmnd_remove_data_wait_hash(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_session *session = cmnd->conn->session;
+ struct iscsi_cmnd *tmp;
+
+ spin_lock(&session->cmnd_data_wait_hash_lock);
+
+ tmp = __cmnd_find_data_wait_hash(cmnd->conn, cmnd->pdu.bhs.itt);
+
+ if (likely(tmp && tmp == cmnd)) {
+ TRACE_DBG("Deleting cmnd %p from the hash (ITT %x)", cmnd,
+ cmnd_itt(cmnd));
+ list_del(&cmnd->hash_list_entry);
+ cmnd->hashed = 0;
+ } else
+ PRINT_ERROR("%p:%x not found", cmnd, cmnd_itt(cmnd));
+
+ spin_unlock(&session->cmnd_data_wait_hash_lock);
+
+ return;
+}
+
+static void cmnd_prepare_get_rejected_immed_data(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct scatterlist *sg = cmnd->sg;
+ char __user *addr;
+ u32 size;
+ unsigned int i;
+
+ TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(cmnd),
+ "Skipping (cmnd %p, ITT %x, op %x, cmd op %x, "
+ "datasize %u, scst_cmd %p, scst state %d)", cmnd,
+ cmnd_itt(cmnd), cmnd_opcode(cmnd), cmnd_hdr(cmnd)->scb[0],
+ cmnd->pdu.datasize, cmnd->scst_cmd, cmnd->scst_state);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ size = cmnd->pdu.datasize;
+ if (!size)
+ goto out;
+
+ /* We already checked pdu.datasize in check_segment_length() */
+
+ if (sg == NULL) {
+ /*
+ * There are no problems with the safety from concurrent
+ * accesses to dummy_page in dummy_sg, since data only
+ * will be read and then discarded.
+ */
+ sg = cmnd->sg = &dummy_sg;
+ cmnd->bufflen = PAGE_SIZE;
+ cmnd->own_sg = 1;
+ }
+
+ addr = (char __force __user *)(page_address(sg_page(&sg[0])));
+ BUG_ON(addr == NULL);
+ conn->read_size = size;
+ for (i = 0; size > PAGE_SIZE; i++, size -= cmnd->bufflen) {
+ /* We already checked pdu.datasize in check_segment_length() */
+ BUG_ON(i >= ISCSI_CONN_IOV_MAX);
+ conn->read_iov[i].iov_base = addr;
+ conn->read_iov[i].iov_len = cmnd->bufflen;
+ }
+ conn->read_iov[i].iov_base = addr;
+ conn->read_iov[i].iov_len = size;
+ conn->read_msg.msg_iov = conn->read_iov;
+ conn->read_msg.msg_iovlen = ++i;
+
+out:
+ return;
+}
+
+static void iscsi_set_resid(struct iscsi_cmnd *rsp, bool bufflen_set)
+{
+ struct iscsi_cmnd *req = rsp->parent_req;
+ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+ struct iscsi_scsi_rsp_hdr *rsp_hdr;
+ int resid, resp_len, in_resp_len;
+
+ if ((req_hdr->flags & ISCSI_CMD_READ) &&
+ (req_hdr->flags & ISCSI_CMD_WRITE)) {
+ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
+
+ if (bufflen_set) {
+ resp_len = req->bufflen;
+ if (req->scst_cmd != NULL)
+ in_resp_len = scst_cmd_get_in_bufflen(req->scst_cmd);
+ else
+ in_resp_len = 0;
+ } else {
+ resp_len = 0;
+ in_resp_len = 0;
+ }
+
+ resid = be32_to_cpu(req_hdr->data_length) - in_resp_len;
+ if (resid > 0) {
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+ rsp_hdr->residual_count = cpu_to_be32(resid);
+ } else if (resid < 0) {
+ resid = -resid;
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
+ rsp_hdr->residual_count = cpu_to_be32(resid);
+ }
+
+ resid = req->read_size - resp_len;
+ if (resid > 0) {
+ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
+ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
+ } else if (resid < 0) {
+ resid = -resid;
+ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_OVERFLOW;
+ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
+ }
+ } else {
+ if (bufflen_set)
+ resp_len = req->bufflen;
+ else
+ resp_len = 0;
+
+ resid = req->read_size - resp_len;
+ if (resid > 0) {
+ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+ rsp_hdr->residual_count = cpu_to_be32(resid);
+ } else if (resid < 0) {
+ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
+ resid = -resid;
+ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
+ rsp_hdr->residual_count = cpu_to_be32(resid);
+ }
+ }
+ return;
+}
+
+static int iscsi_preliminary_complete(struct iscsi_cmnd *req,
+ struct iscsi_cmnd *orig_req, bool get_data)
+{
+ int res = 0;
+ bool set_r2t_len;
+
+#ifdef CONFIG_SCST_DEBUG
+ {
+ struct iscsi_hdr *req_hdr = &req->pdu.bhs;
+ TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(orig_req),
+ "Prelim completed req %p, orig_req %p (FINAL %x, "
+ "outstanding_r2t %d)", req, orig_req,
+ (req_hdr->flags & ISCSI_CMD_FINAL),
+ orig_req->outstanding_r2t);
+ }
+#endif
+
+ iscsi_extracheck_is_rd_thread(req->conn);
+ BUG_ON(req->parent_req != NULL);
+
+ if (test_bit(ISCSI_CMD_PRELIM_COMPLETED, &req->prelim_compl_flags)) {
+ TRACE_MGMT_DBG("req %p already prelim completed", req);
+ /* To not try to get data twice */
+ get_data = false;
+ }
+
+ set_r2t_len = !req->hashed &&
+ (cmnd_opcode(req) == ISCSI_OP_SCSI_CMD) &&
+ !test_bit(ISCSI_CMD_PRELIM_COMPLETED,
+ &orig_req->prelim_compl_flags);
+ set_bit(ISCSI_CMD_PRELIM_COMPLETED, &orig_req->prelim_compl_flags);
+
+ TRACE_DBG("get_data %d, set_r2t_len %d", get_data, set_r2t_len);
+
+ if (get_data)
+ cmnd_prepare_get_rejected_immed_data(req);
+
+ if (set_r2t_len)
+ res = iscsi_set_prelim_r2t_len_to_receive(orig_req);
+ return res;
+}
+
+static int cmnd_prepare_recv_pdu(struct iscsi_conn *conn,
+ struct iscsi_cmnd *cmd, u32 offset, u32 size)
+{
+ struct scatterlist *sg = cmd->sg;
+ unsigned int bufflen = cmd->bufflen;
+ unsigned int idx, i;
+ char __user *addr;
+ int res = 0;
+
+ TRACE_DBG("%p %u,%u", cmd->sg, offset, size);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ if (unlikely((offset >= bufflen) ||
+ (offset + size > bufflen))) {
+ PRINT_ERROR("Wrong ltn (%u %u %u)", offset, size, bufflen);
+ mark_conn_closed(conn);
+ res = -EIO;
+ goto out;
+ }
+
+ offset += sg[0].offset;
+ idx = offset >> PAGE_SHIFT;
+ offset &= ~PAGE_MASK;
+
+ conn->read_msg.msg_iov = conn->read_iov;
+ conn->read_size = size;
+
+ i = 0;
+ while (1) {
+ addr = (char __force __user *)(page_address(sg_page(&sg[idx])));
+ BUG_ON(addr == NULL);
+ conn->read_iov[i].iov_base = addr + offset;
+ if (offset + size <= PAGE_SIZE) {
+ TRACE_DBG("idx=%d, offset=%u, size=%d, addr=%p",
+ idx, offset, size, addr);
+ conn->read_iov[i].iov_len = size;
+ conn->read_msg.msg_iovlen = ++i;
+ break;
+ }
+ conn->read_iov[i].iov_len = PAGE_SIZE - offset;
+ TRACE_DBG("idx=%d, offset=%u, size=%d, iov_len=%zd, addr=%p",
+ idx, offset, size, conn->read_iov[i].iov_len, addr);
+ size -= conn->read_iov[i].iov_len;
+ if (unlikely(++i >= ISCSI_CONN_IOV_MAX)) {
+ PRINT_ERROR("Initiator %s violated negotiated "
+ "parameters by sending too much data (size "
+ "left %d)", conn->session->initiator_name,
+ size);
+ mark_conn_closed(conn);
+ res = -EINVAL;
+ break;
+ }
+ idx++;
+ offset = sg[idx].offset;
+ }
+ TRACE_DBG("msg_iov=%p, msg_iovlen=%zd",
+ conn->read_msg.msg_iov, conn->read_msg.msg_iovlen);
+
+out:
+ return res;
+}
+
+static void send_r2t(struct iscsi_cmnd *req)
+{
+ struct iscsi_session *sess = req->conn->session;
+ struct iscsi_cmnd *rsp;
+ struct iscsi_r2t_hdr *rsp_hdr;
+ u32 offset, burst;
+ LIST_HEAD(send);
+
+ EXTRACHECKS_BUG_ON(req->r2t_len_to_send == 0);
+
+ /*
+ * There is no race with data_out_start() and conn_abort(), since
+ * all functions called from single read thread
+ */
+ iscsi_extracheck_is_rd_thread(req->conn);
+
+ /*
+ * We don't need to check for PRELIM_COMPLETED here, because for such
+ * commands we set r2t_len_to_send = 0, hence made sure we won't
+ * called here.
+ */
+
+ EXTRACHECKS_BUG_ON(req->outstanding_r2t >
+ sess->sess_params.max_outstanding_r2t);
+
+ if (req->outstanding_r2t == sess->sess_params.max_outstanding_r2t)
+ goto out;
+
+ burst = sess->sess_params.max_burst_length;
+ offset = be32_to_cpu(cmnd_hdr(req)->data_length) -
+ req->r2t_len_to_send;
+
+ do {
+ rsp = iscsi_alloc_rsp(req);
+ rsp->pdu.bhs.ttt = req->target_task_tag;
+ rsp_hdr = (struct iscsi_r2t_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->opcode = ISCSI_OP_R2T;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->lun = cmnd_hdr(req)->lun;
+ rsp_hdr->itt = cmnd_hdr(req)->itt;
+ rsp_hdr->r2t_sn = cpu_to_be32(req->r2t_sn++);
+ rsp_hdr->buffer_offset = cpu_to_be32(offset);
+ if (req->r2t_len_to_send > burst) {
+ rsp_hdr->data_length = cpu_to_be32(burst);
+ req->r2t_len_to_send -= burst;
+ offset += burst;
+ } else {
+ rsp_hdr->data_length = cpu_to_be32(req->r2t_len_to_send);
+ req->r2t_len_to_send = 0;
+ }
+
+ TRACE_WRITE("req %p, data_length %u, buffer_offset %u, "
+ "r2t_sn %u, outstanding_r2t %u", req,
+ be32_to_cpu(rsp_hdr->data_length),
+ be32_to_cpu(rsp_hdr->buffer_offset),
+ be32_to_cpu(rsp_hdr->r2t_sn), req->outstanding_r2t);
+
+ list_add_tail(&rsp->write_list_entry, &send);
+ req->outstanding_r2t++;
+
+ } while ((req->outstanding_r2t < sess->sess_params.max_outstanding_r2t) &&
+ (req->r2t_len_to_send != 0));
+
+ iscsi_cmnds_init_write(&send, ISCSI_INIT_WRITE_WAKE);
+
+out:
+ return;
+}
+
+static int iscsi_pre_exec(struct scst_cmd *scst_cmd)
+{
+ int res = SCST_PREPROCESS_STATUS_SUCCESS;
+ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
+ scst_cmd_get_tgt_priv(scst_cmd);
+ struct iscsi_cmnd *c, *t;
+
+ EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd));
+
+ /* If data digest isn't used this list will be empty */
+ list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list,
+ rx_ddigest_cmd_list_entry) {
+ TRACE_DBG("Checking digest of RX ddigest cmd %p", c);
+ if (digest_rx_data(c) != 0) {
+ scst_set_cmd_error(scst_cmd,
+ SCST_LOAD_SENSE(iscsi_sense_crc_error));
+ res = SCST_PREPROCESS_STATUS_ERROR_SENSE_SET;
+ /*
+ * The rest of rx_ddigest_cmd_list will be freed
+ * in req_cmnd_release()
+ */
+ goto out;
+ }
+ cmd_del_from_rx_ddigest_list(c);
+ cmnd_put(c);
+ }
+
+out:
+ return res;
+}
+
+static int nop_out_start(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_hdr *req_hdr = &cmnd->pdu.bhs;
+ u32 size, tmp;
+ int i, err = 0;
+
+ TRACE_DBG("%p", cmnd);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ if (!(req_hdr->flags & ISCSI_FLG_FINAL)) {
+ PRINT_ERROR("%s", "Initiator sent Nop-Out with not a single "
+ "PDU");
+ err = -ISCSI_REASON_PROTOCOL_ERROR;
+ goto out;
+ }
+
+ if (cmnd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG)) {
+ if (unlikely(!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE)))
+ PRINT_ERROR("%s", "Initiator sent RESERVED tag for "
+ "non-immediate Nop-Out command");
+ }
+
+ spin_lock(&conn->session->sn_lock);
+ __update_stat_sn(cmnd);
+ err = check_cmd_sn(cmnd);
+ spin_unlock(&conn->session->sn_lock);
+ if (unlikely(err))
+ goto out;
+
+ size = cmnd->pdu.datasize;
+
+ if (size) {
+ conn->read_msg.msg_iov = conn->read_iov;
+ if (cmnd->pdu.bhs.itt != cpu_to_be32(ISCSI_RESERVED_TAG)) {
+ struct scatterlist *sg;
+
+ cmnd->sg = sg = scst_alloc(size, GFP_KERNEL,
+ &cmnd->sg_cnt);
+ if (sg == NULL) {
+ TRACE(TRACE_OUT_OF_MEM, "Allocating buffer for"
+ " %d Nop-Out payload failed", size);
+ err = -ISCSI_REASON_OUT_OF_RESOURCES;
+ goto out;
+ }
+
+ /* We already checked it in check_segment_length() */
+ BUG_ON(cmnd->sg_cnt > (signed)ISCSI_CONN_IOV_MAX);
+
+ cmnd->own_sg = 1;
+ cmnd->bufflen = size;
+
+ for (i = 0; i < cmnd->sg_cnt; i++) {
+ conn->read_iov[i].iov_base =
+ (void __force __user *)(page_address(sg_page(&sg[i])));
+ tmp = min_t(u32, size, PAGE_SIZE);
+ conn->read_iov[i].iov_len = tmp;
+ conn->read_size += tmp;
+ size -= tmp;
+ }
+ BUG_ON(size != 0);
+ } else {
+ /*
+ * There are no problems with the safety from concurrent
+ * accesses to dummy_page, since for ISCSI_RESERVED_TAG
+ * the data only read and then discarded.
+ */
+ for (i = 0; i < (signed)ISCSI_CONN_IOV_MAX; i++) {
+ conn->read_iov[i].iov_base =
+ (void __force __user *)(page_address(dummy_page));
+ tmp = min_t(u32, size, PAGE_SIZE);
+ conn->read_iov[i].iov_len = tmp;
+ conn->read_size += tmp;
+ size -= tmp;
+ }
+
+ /* We already checked size in check_segment_length() */
+ BUG_ON(size != 0);
+ }
+
+ conn->read_msg.msg_iovlen = i;
+ TRACE_DBG("msg_iov=%p, msg_iovlen=%zd", conn->read_msg.msg_iov,
+ conn->read_msg.msg_iovlen);
+ }
+
+out:
+ return err;
+}
+
+int cmnd_rx_continue(struct iscsi_cmnd *req)
+{
+ struct iscsi_conn *conn = req->conn;
+ struct iscsi_session *session = conn->session;
+ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+ struct scst_cmd *scst_cmd = req->scst_cmd;
+ scst_data_direction dir;
+ bool unsolicited_data_expected = false;
+ int res = 0;
+
+ TRACE_DBG("scsi command: %x", req_hdr->scb[0]);
+
+ EXTRACHECKS_BUG_ON(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC);
+
+ dir = scst_cmd_get_data_direction(scst_cmd);
+
+ /*
+ * Check for preliminary completion here to save R2Ts. For TASK QUEUE
+ * FULL statuses that might be a big performance win.
+ */
+ if (unlikely(scst_cmd_prelim_completed(scst_cmd) ||
+ unlikely(req->prelim_compl_flags != 0))) {
+ /*
+ * If necessary, ISCSI_CMD_ABORTED will be set by
+ * iscsi_xmit_response().
+ */
+ res = iscsi_preliminary_complete(req, req, true);
+ goto trace;
+ }
+
+ /* For prelim completed commands sg&K can be already set! */
+
+ if (dir != SCST_DATA_BIDI) {
+ req->sg = scst_cmd_get_sg(scst_cmd);
+ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
+ req->bufflen = scst_cmd_get_bufflen(scst_cmd);
+ } else {
+ req->sg = scst_cmd_get_in_sg(scst_cmd);
+ req->sg_cnt = scst_cmd_get_in_sg_cnt(scst_cmd);
+ req->bufflen = scst_cmd_get_in_bufflen(scst_cmd);
+ }
+
+ if (dir & SCST_DATA_WRITE) {
+ unsolicited_data_expected = !(req_hdr->flags & ISCSI_CMD_FINAL);
+
+ if (unlikely(session->sess_params.initial_r2t &&
+ unsolicited_data_expected)) {
+ PRINT_ERROR("Initiator %s violated negotiated "
+ "parameters: initial R2T is required (ITT %x, "
+ "op %x)", session->initiator_name,
+ cmnd_itt(req), req_hdr->scb[0]);
+ goto out_close;
+ }
+
+ if (unlikely(!session->sess_params.immediate_data &&
+ req->pdu.datasize)) {
+ PRINT_ERROR("Initiator %s violated negotiated "
+ "parameters: forbidden immediate data sent "
+ "(ITT %x, op %x)", session->initiator_name,
+ cmnd_itt(req), req_hdr->scb[0]);
+ goto out_close;
+ }
+
+ if (unlikely(session->sess_params.first_burst_length < req->pdu.datasize)) {
+ PRINT_ERROR("Initiator %s violated negotiated "
+ "parameters: immediate data len (%d) > "
+ "first_burst_length (%d) (ITT %x, op %x)",
+ session->initiator_name,
+ req->pdu.datasize,
+ session->sess_params.first_burst_length,
+ cmnd_itt(req), req_hdr->scb[0]);
+ goto out_close;
+ }
+
+ req->r2t_len_to_receive = be32_to_cpu(req_hdr->data_length) -
+ req->pdu.datasize;
+
+ if (unlikely(req->r2t_len_to_receive > req->bufflen)) {
+ PRINT_ERROR("req->r2t_len_to_receive %d > req->bufflen "
+ "%d", req->r2t_len_to_receive, req->bufflen);
+ goto out_close;
+ }
+
+ res = cmnd_insert_data_wait_hash(req);
+ if (unlikely(res != 0)) {
+ /*
+ * We have to close connection, because otherwise a data
+ * corruption is possible if we allow to receive data
+ * for this request in another request with dublicated
+ * ITT.
+ */
+ goto out_close;
+ }
+
+ if (unsolicited_data_expected) {
+ req->outstanding_r2t = 1;
+ req->r2t_len_to_send = req->r2t_len_to_receive -
+ min_t(unsigned int,
+ session->sess_params.first_burst_length -
+ req->pdu.datasize,
+ req->r2t_len_to_receive);
+ } else
+ req->r2t_len_to_send = req->r2t_len_to_receive;
+
+ req_add_to_write_timeout_list(req);
+
+ if (req->pdu.datasize) {
+ res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize);
+ /* For performance better to send R2Ts ASAP */
+ if (likely(res == 0) && (req->r2t_len_to_send != 0))
+ send_r2t(req);
+ }
+ } else {
+ if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) ||
+ req->pdu.datasize)) {
+ PRINT_ERROR("Unexpected unsolicited data (ITT %x "
+ "CDB %x", cmnd_itt(req), req_hdr->scb[0]);
+ set_scst_preliminary_status_rsp(req, true,
+ SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data));
+ }
+ }
+
+trace:
+ TRACE_DBG("req=%p, dir=%d, unsolicited_data_expected=%d, "
+ "r2t_len_to_receive=%d, r2t_len_to_send=%d, bufflen=%d, "
+ "own_sg %d", req, dir, unsolicited_data_expected,
+ req->r2t_len_to_receive, req->r2t_len_to_send, req->bufflen,
+ req->own_sg);
+
+out:
+ return res;
+
+out_close:
+ mark_conn_closed(conn);
+ res = -EINVAL;
+ goto out;
+}
+
+static int scsi_cmnd_start(struct iscsi_cmnd *req)
+{
+ struct iscsi_conn *conn = req->conn;
+ struct iscsi_session *session = conn->session;
+ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+ struct scst_cmd *scst_cmd;
+ scst_data_direction dir;
+ struct iscsi_ahs_hdr *ahdr;
+ int res = 0;
+
+ TRACE_DBG("scsi command: %x", req_hdr->scb[0]);
+
+ TRACE_DBG("Incrementing active_cmds (cmd %p, sess %p, "
+ "new value %d)", req, session,
+ atomic_read(&session->active_cmds)+1);
+ atomic_inc(&session->active_cmds);
+ req->dec_active_cmnds = 1;
+
+ scst_cmd = scst_rx_cmd(session->scst_sess,
+ (uint8_t *)&req_hdr->lun, sizeof(req_hdr->lun),
+ req_hdr->scb, sizeof(req_hdr->scb), SCST_NON_ATOMIC);
+ if (scst_cmd == NULL) {
+ res = create_preliminary_status_rsp(req, SAM_STAT_BUSY,
+ NULL, 0);
+ goto out;
+ }
+
+ req->scst_cmd = scst_cmd;
+ scst_cmd_set_tag(scst_cmd, req_hdr->itt);
+ scst_cmd_set_tgt_priv(scst_cmd, req);
+
+ if ((req_hdr->flags & ISCSI_CMD_READ) &&
+ (req_hdr->flags & ISCSI_CMD_WRITE)) {
+ int sz = cmnd_read_size(req);
+ if (unlikely(sz < 0)) {
+ PRINT_ERROR("%s", "BIDI data transfer, but initiator "
+ "not supplied Bidirectional Read Expected Data "
+ "Transfer Length AHS");
+ set_scst_preliminary_status_rsp(req, true,
+ SCST_LOAD_SENSE(scst_sense_parameter_value_invalid));
+ } else {
+ req->read_size = sz;
+ dir = SCST_DATA_BIDI;
+ scst_cmd_set_expected(scst_cmd, dir, sz);
+ scst_cmd_set_expected_in_transfer_len(scst_cmd,
+ be32_to_cpu(req_hdr->data_length));
+ scst_cmd_set_tgt_need_alloc_data_buf(scst_cmd);
+ }
+ } else if (req_hdr->flags & ISCSI_CMD_READ) {
+ req->read_size = be32_to_cpu(req_hdr->data_length);
+ dir = SCST_DATA_READ;
+ scst_cmd_set_expected(scst_cmd, dir, req->read_size);
+ scst_cmd_set_tgt_need_alloc_data_buf(scst_cmd);
+ } else if (req_hdr->flags & ISCSI_CMD_WRITE) {
+ dir = SCST_DATA_WRITE;
+ scst_cmd_set_expected(scst_cmd, dir,
+ be32_to_cpu(req_hdr->data_length));
+ } else {
+ dir = SCST_DATA_NONE;
+ scst_cmd_set_expected(scst_cmd, dir, 0);
+ }
+
+ switch (req_hdr->flags & ISCSI_CMD_ATTR_MASK) {
+ case ISCSI_CMD_SIMPLE:
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE);
+ break;
+ case ISCSI_CMD_HEAD_OF_QUEUE:
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
+ break;
+ case ISCSI_CMD_ORDERED:
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
+ break;
+ case ISCSI_CMD_ACA:
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ACA);
+ break;
+ case ISCSI_CMD_UNTAGGED:
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
+ break;
+ default:
+ PRINT_ERROR("Unknown task code %x, use ORDERED instead",
+ req_hdr->flags & ISCSI_CMD_ATTR_MASK);
+ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
+ break;
+ }
+
+ /* cmd_sn is already in CPU format converted in check_cmd_sn() */
+ scst_cmd_set_tgt_sn(scst_cmd, req_hdr->cmd_sn);
+
+ ahdr = (struct iscsi_ahs_hdr *)req->pdu.ahs;
+ if (ahdr != NULL) {
+ uint8_t *p = (uint8_t *)ahdr;
+ unsigned int size = 0;
+ do {
+ int s;
+
+ ahdr = (struct iscsi_ahs_hdr *)p;
+
+ if (ahdr->ahstype == ISCSI_AHSTYPE_CDB) {
+ struct iscsi_cdb_ahdr *eca =
+ (struct iscsi_cdb_ahdr *)ahdr;
+ scst_cmd_set_ext_cdb(scst_cmd, eca->cdb,
+ be16_to_cpu(ahdr->ahslength) - 1);
+ break;
+ }
+ s = 3 + be16_to_cpu(ahdr->ahslength);
+ s = (s + 3) & -4;
+ size += s;
+ p += s;
+ } while (size < req->pdu.ahssize);
+ }
+
+ TRACE_DBG("START Command (itt %x, queue_type %d)",
+ req_hdr->itt, scst_cmd_get_queue_type(scst_cmd));
+ req->scst_state = ISCSI_CMD_STATE_RX_CMD;
+ conn->rx_task = current;
+ scst_cmd_init_stage1_done(scst_cmd, SCST_CONTEXT_DIRECT, 0);
+
+ if (req->scst_state != ISCSI_CMD_STATE_RX_CMD)
+ res = cmnd_rx_continue(req);
+ else {
+ TRACE_DBG("Delaying req %p post processing (scst_state %d)",
+ req, req->scst_state);
+ res = 1;
+ }
+
+out:
+ return res;
+}
+
+static int data_out_start(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_data_out_hdr *req_hdr =
+ (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
+ struct iscsi_cmnd *orig_req;
+#if 0
+ struct iscsi_hdr *orig_req_hdr;
+#endif
+ u32 offset = be32_to_cpu(req_hdr->buffer_offset);
+ int res = 0;
+
+ /*
+ * There is no race with send_r2t() and conn_abort(), since
+ * all functions called from single read thread
+ */
+ iscsi_extracheck_is_rd_thread(cmnd->conn);
+
+ update_stat_sn(cmnd);
+
+ orig_req = cmnd_find_data_wait_hash(conn, req_hdr->itt);
+ cmnd->cmd_req = orig_req;
+ if (unlikely(orig_req == NULL)) {
+ /*
+ * It shouldn't happen, since we don't abort any request until
+ * we received all related PDUs from the initiator or timeout
+ * them. Let's quietly drop such PDUs.
+ */
+ TRACE_MGMT_DBG("Unable to find scsi task ITT %x",
+ cmnd_itt(cmnd));
+ res = iscsi_preliminary_complete(cmnd, cmnd, true);
+ goto out;
+ }
+
+ if (unlikely(orig_req->r2t_len_to_receive < cmnd->pdu.datasize)) {
+ if (orig_req->prelim_compl_flags != 0) {
+ /* We can have fake r2t_len_to_receive */
+ goto go;
+ }
+ PRINT_ERROR("Data size (%d) > R2T length to receive (%d)",
+ cmnd->pdu.datasize, orig_req->r2t_len_to_receive);
+ set_scst_preliminary_status_rsp(orig_req, false,
+ SCST_LOAD_SENSE(iscsi_sense_incorrect_amount_of_data));
+ goto go;
+ }
+
+ /* Crazy iSCSI spec requires us to make this unneeded check */
+#if 0 /* ...but some initiators (Windows) don't care to correctly set it */
+ orig_req_hdr = &orig_req->pdu.bhs;
+ if (unlikely(orig_req_hdr->lun != req_hdr->lun)) {
+ PRINT_ERROR("Wrong LUN (%lld) in Data-Out PDU (expected %lld), "
+ "orig_req %p, cmnd %p", (unsigned long long)req_hdr->lun,
+ (unsigned long long)orig_req_hdr->lun, orig_req, cmnd);
+ create_reject_rsp(orig_req, ISCSI_REASON_PROTOCOL_ERROR, false);
+ goto go;
+ }
+#endif
+
+go:
+ if (req_hdr->flags & ISCSI_FLG_FINAL)
+ orig_req->outstanding_r2t--;
+
+ if (unlikely(orig_req->prelim_compl_flags != 0)) {
+ res = iscsi_preliminary_complete(cmnd, orig_req, true);
+ goto out;
+ }
+
+ TRACE_WRITE("cmnd %p, orig_req %p, offset %u, datasize %u", cmnd,
+ orig_req, offset, cmnd->pdu.datasize);
+
+ res = cmnd_prepare_recv_pdu(conn, orig_req, offset, cmnd->pdu.datasize);
+
+out:
+ return res;
+}
+
+static void data_out_end(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_data_out_hdr *req_hdr =
+ (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
+ struct iscsi_cmnd *req;
+
+ EXTRACHECKS_BUG_ON(cmnd == NULL);
+ req = cmnd->cmd_req;
+ if (unlikely(req == NULL))
+ goto out;
+
+ TRACE_DBG("cmnd %p, req %p", cmnd, req);
+
+ iscsi_extracheck_is_rd_thread(cmnd->conn);
+
+ if (!(cmnd->conn->ddigest_type & DIGEST_NONE) &&
+ !cmnd->ddigest_checked) {
+ cmd_add_on_rx_ddigest_list(req, cmnd);
+ cmnd_get(cmnd);
+ }
+
+ /*
+ * Now we received the data and can adjust r2t_len_to_receive of the
+ * orig req. We couldn't do it earlier, because it will break data
+ * receiving errors recovery (calls of iscsi_fail_data_waiting_cmnd()).
+ */
+ req->r2t_len_to_receive -= cmnd->pdu.datasize;
+
+ if (unlikely(req->prelim_compl_flags != 0)) {
+ /*
+ * We might need to wait for one or more PDUs. Let's simplify
+ * other code.
+ */
+ req->r2t_len_to_receive = req->outstanding_r2t;
+ req->r2t_len_to_send = 0;
+ }
+
+ TRACE_DBG("req %p, FINAL %x, outstanding_r2t %d, r2t_len_to_receive %d,"
+ " r2t_len_to_send %d", req, req_hdr->flags & ISCSI_FLG_FINAL,
+ req->outstanding_r2t, req->r2t_len_to_receive,
+ req->r2t_len_to_send);
+
+ if (!(req_hdr->flags & ISCSI_FLG_FINAL))
+ goto out;
+
+ if (req->r2t_len_to_receive == 0) {
+ if (!req->pending)
+ iscsi_restart_cmnd(req);
+ } else if (req->r2t_len_to_send != 0)
+ send_r2t(req);
+
+out:
+ return;
+}
+
+/* Might be called under target_mutex and cmd_list_lock */
+static void __cmnd_abort(struct iscsi_cmnd *cmnd)
+{
+ unsigned long timeout_time = jiffies + ISCSI_TM_DATA_WAIT_TIMEOUT +
+ ISCSI_ADD_SCHED_TIME;
+ struct iscsi_conn *conn = cmnd->conn;
+
+ TRACE_MGMT_DBG("Aborting cmd %p, scst_cmd %p (scst state %x, "
+ "ref_cnt %d, on_write_timeout_list %d, write_start %ld, ITT %x, "
+ "sn %u, op %x, r2t_len_to_receive %d, r2t_len_to_send %d, "
+ "CDB op %x, size to write %u, outstanding_r2t %d, "
+ "sess->exp_cmd_sn %u, conn %p, rd_task %p)",
+ cmnd, cmnd->scst_cmd, cmnd->scst_state,
+ atomic_read(&cmnd->ref_cnt), cmnd->on_write_timeout_list,
+ cmnd->write_start, cmnd_itt(cmnd), cmnd->pdu.bhs.sn,
+ cmnd_opcode(cmnd), cmnd->r2t_len_to_receive,
+ cmnd->r2t_len_to_send, cmnd_scsicode(cmnd),
+ cmnd_write_size(cmnd), cmnd->outstanding_r2t,
+ cmnd->conn->session->exp_cmd_sn, cmnd->conn,
+ cmnd->conn->rd_task);
+
+ /*
+ * Lock to sync with iscsi_check_tm_data_wait_timeouts(), including
+ * CMD_ABORTED bit set.
+ */
+ spin_lock_bh(&iscsi_rd_lock);
+
+ /*
+ * We suppose that preliminary commands completion is tested by
+ * comparing prelim_compl_flags with 0. Otherwise a race is possible,
+ * like sending command in SCST core as PRELIM_COMPLETED, while it
+ * wasn't aborted in it yet and have as the result a wrong success
+ * status sent to the initiator.
+ */
+ set_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags);
+
+ TRACE_MGMT_DBG("Setting conn_tm_active for conn %p", conn);
+ conn->conn_tm_active = 1;
+
+ spin_unlock_bh(&iscsi_rd_lock);
+
+ /*
+ * We need the lock to sync with req_add_to_write_timeout_list() and
+ * close races for rsp_timer.expires.
+ */
+ spin_lock_bh(&conn->write_list_lock);
+ if (!timer_pending(&conn->rsp_timer) ||
+ time_after(conn->rsp_timer.expires, timeout_time)) {
+ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)", timeout_time,
+ conn);
+ mod_timer(&conn->rsp_timer, timeout_time);
+ } else
+ TRACE_MGMT_DBG("Timer for conn %p is going to fire on %ld "
+ "(timeout time %ld)", conn, conn->rsp_timer.expires,
+ timeout_time);
+ spin_unlock_bh(&conn->write_list_lock);
+
+ return;
+}
+
+/* Must be called from the read or conn close thread */
+static int cmnd_abort(struct iscsi_cmnd *req, int *status)
+{
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
+ struct iscsi_cmnd *cmnd;
+ int res = -1;
+
+ req_hdr->ref_cmd_sn = be32_to_cpu(req_hdr->ref_cmd_sn);
+
+ if (!before(req_hdr->ref_cmd_sn, req_hdr->cmd_sn)) {
+ TRACE(TRACE_MGMT, "ABORT TASK: RefCmdSN(%u) > CmdSN(%u)",
+ req_hdr->ref_cmd_sn, req_hdr->cmd_sn);
+ *status = ISCSI_RESPONSE_UNKNOWN_TASK;
+ goto out;
+ }
+
+ cmnd = cmnd_find_itt_get(req->conn, req_hdr->rtt);
+ if (cmnd) {
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
+
+ if (req_hdr->lun != hdr->lun) {
+ PRINT_ERROR("ABORT TASK: LUN mismatch: req LUN "
+ "%llx, cmd LUN %llx, rtt %u",
+ (long long unsigned int)req_hdr->lun,
+ (long long unsigned int)hdr->lun,
+ req_hdr->rtt);
+ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ goto out_put;
+ }
+
+ if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
+ if (req_hdr->ref_cmd_sn != req_hdr->cmd_sn) {
+ PRINT_ERROR("ABORT TASK: RefCmdSN(%u) != TM "
+ "cmd CmdSN(%u) for immediate command "
+ "%p", req_hdr->ref_cmd_sn,
+ req_hdr->cmd_sn, cmnd);
+ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ goto out_put;
+ }
+ } else {
+ if (req_hdr->ref_cmd_sn != hdr->cmd_sn) {
+ PRINT_ERROR("ABORT TASK: RefCmdSN(%u) != "
+ "CmdSN(%u) for command %p",
+ req_hdr->ref_cmd_sn, req_hdr->cmd_sn,
+ cmnd);
+ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ goto out_put;
+ }
+ }
+
+ if (before(req_hdr->cmd_sn, hdr->cmd_sn) ||
+ (req_hdr->cmd_sn == hdr->cmd_sn)) {
+ PRINT_ERROR("ABORT TASK: SN mismatch: req SN %x, "
+ "cmd SN %x, rtt %u", req_hdr->cmd_sn,
+ hdr->cmd_sn, req_hdr->rtt);
+ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ goto out_put;
+ }
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ __cmnd_abort(cmnd);
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ cmnd_put(cmnd);
+ res = 0;
+ } else {
+ TRACE_MGMT_DBG("cmd RTT %x not found", req_hdr->rtt);
+ /*
+ * iSCSI RFC:
+ *
+ * b) If the Referenced Task Tag does not identify an existing task,
+ * but if the CmdSN indicated by the RefCmdSN field in the Task
+ * Management function request is within the valid CmdSN window
+ * and less than the CmdSN of the Task Management function
+ * request itself, then targets must consider the CmdSN received
+ * and return the "Function complete" response.
+ *
+ * c) If the Referenced Task Tag does not identify an existing task
+ * and if the CmdSN indicated by the RefCmdSN field in the Task
+ * Management function request is outside the valid CmdSN window,
+ * then targets must return the "Task does not exist" response.
+ *
+ * 128 seems to be a good "window".
+ */
+ if (between(req_hdr->ref_cmd_sn, req_hdr->cmd_sn - 128,
+ req_hdr->cmd_sn)) {
+ *status = ISCSI_RESPONSE_FUNCTION_COMPLETE;
+ res = 0;
+ } else
+ *status = ISCSI_RESPONSE_UNKNOWN_TASK;
+ }
+
+out:
+ return res;
+
+out_put:
+ cmnd_put(cmnd);
+ goto out;
+}
+
+/* Must be called from the read or conn close thread */
+static int target_abort(struct iscsi_cmnd *req, int all)
+{
+ struct iscsi_target *target = req->conn->session->target;
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
+ struct iscsi_session *session;
+ struct iscsi_conn *conn;
+ struct iscsi_cmnd *cmnd;
+
+ mutex_lock(&target->target_mutex);
+
+ list_for_each_entry(session, &target->session_list,
+ session_list_entry) {
+ list_for_each_entry(conn, &session->conn_list,
+ conn_list_entry) {
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_for_each_entry(cmnd, &conn->cmd_list,
+ cmd_list_entry) {
+ if (cmnd == req)
+ continue;
+ if (all)
+ __cmnd_abort(cmnd);
+ else if (req_hdr->lun == cmnd_hdr(cmnd)->lun)
+ __cmnd_abort(cmnd);
+ }
+ spin_unlock_bh(&conn->cmd_list_lock);
+ }
+ }
+
+ mutex_unlock(&target->target_mutex);
+ return 0;
+}
+
+/* Must be called from the read or conn close thread */
+static void task_set_abort(struct iscsi_cmnd *req)
+{
+ struct iscsi_session *session = req->conn->session;
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
+ struct iscsi_target *target = session->target;
+ struct iscsi_conn *conn;
+ struct iscsi_cmnd *cmnd;
+
+ mutex_lock(&target->target_mutex);
+
+ list_for_each_entry(conn, &session->conn_list, conn_list_entry) {
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
+ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
+ if (cmnd == req)
+ continue;
+ if (req_hdr->lun != hdr->lun)
+ continue;
+ if (before(req_hdr->cmd_sn, hdr->cmd_sn) ||
+ req_hdr->cmd_sn == hdr->cmd_sn)
+ continue;
+ __cmnd_abort(cmnd);
+ }
+ spin_unlock_bh(&conn->cmd_list_lock);
+ }
+
+ mutex_unlock(&target->target_mutex);
+ return;
+}
+
+/* Must be called from the read or conn close thread */
+void conn_abort(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd, *r, *t;
+
+ TRACE_MGMT_DBG("Aborting conn %p", conn);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ cancel_delayed_work_sync(&conn->nop_in_delayed_work);
+
+ /* No locks, we are the only user */
+ list_for_each_entry_safe(r, t, &conn->nop_req_list,
+ nop_req_list_entry) {
+ list_del(&r->nop_req_list_entry);
+ cmnd_put(r);
+ }
+
+ spin_lock_bh(&conn->cmd_list_lock);
+again:
+ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
+ __cmnd_abort(cmnd);
+ if (cmnd->r2t_len_to_receive != 0) {
+ if (!cmnd_get_check(cmnd)) {
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ /* ToDo: this is racy for MC/S */
+ iscsi_fail_data_waiting_cmnd(cmnd);
+
+ cmnd_put(cmnd);
+
+ /*
+ * We are in the read thread, so we may not
+ * worry that after cmnd release conn gets
+ * released as well.
+ */
+ spin_lock_bh(&conn->cmd_list_lock);
+ goto again;
+ }
+ }
+ }
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ return;
+}
+
+static void execute_task_management(struct iscsi_cmnd *req)
+{
+ struct iscsi_conn *conn = req->conn;
+ struct iscsi_session *sess = conn->session;
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
+ int rc, status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ int function = req_hdr->function & ISCSI_FUNCTION_MASK;
+ struct scst_rx_mgmt_params params;
+
+ TRACE(TRACE_MGMT, "TM fn %d", function);
+
+ TRACE_MGMT_DBG("TM req %p, ITT %x, RTT %x, sn %u, con %p", req,
+ cmnd_itt(req), req_hdr->rtt, req_hdr->cmd_sn, conn);
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ spin_lock(&sess->sn_lock);
+ sess->tm_active++;
+ sess->tm_sn = req_hdr->cmd_sn;
+ if (sess->tm_rsp != NULL) {
+ struct iscsi_cmnd *tm_rsp = sess->tm_rsp;
+
+ TRACE_MGMT_DBG("Dropping delayed TM rsp %p", tm_rsp);
+
+ sess->tm_rsp = NULL;
+ sess->tm_active--;
+
+ spin_unlock(&sess->sn_lock);
+
+ BUG_ON(sess->tm_active < 0);
+
+ rsp_cmnd_release(tm_rsp);
+ } else
+ spin_unlock(&sess->sn_lock);
+
+ memset(¶ms, 0, sizeof(params));
+ params.atomic = SCST_NON_ATOMIC;
+ params.tgt_priv = req;
+
+ if ((function != ISCSI_FUNCTION_ABORT_TASK) &&
+ (req_hdr->rtt != ISCSI_RESERVED_TAG)) {
+ PRINT_ERROR("Invalid RTT %x (TM fn %d)", req_hdr->rtt,
+ function);
+ rc = -1;
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ goto reject;
+ }
+
+ /* cmd_sn is already in CPU format converted in check_cmd_sn() */
+
+ switch (function) {
+ case ISCSI_FUNCTION_ABORT_TASK:
+ rc = cmnd_abort(req, &status);
+ if (rc == 0) {
+ params.fn = SCST_ABORT_TASK;
+ params.tag = req_hdr->rtt;
+ params.tag_set = 1;
+ params.lun = (uint8_t *)&req_hdr->lun;
+ params.lun_len = sizeof(req_hdr->lun);
+ params.lun_set = 1;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ }
+ break;
+ case ISCSI_FUNCTION_ABORT_TASK_SET:
+ task_set_abort(req);
+ params.fn = SCST_ABORT_TASK_SET;
+ params.lun = (uint8_t *)&req_hdr->lun;
+ params.lun_len = sizeof(req_hdr->lun);
+ params.lun_set = 1;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ case ISCSI_FUNCTION_CLEAR_TASK_SET:
+ task_set_abort(req);
+ params.fn = SCST_CLEAR_TASK_SET;
+ params.lun = (uint8_t *)&req_hdr->lun;
+ params.lun_len = sizeof(req_hdr->lun);
+ params.lun_set = 1;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ case ISCSI_FUNCTION_CLEAR_ACA:
+ params.fn = SCST_CLEAR_ACA;
+ params.lun = (uint8_t *)&req_hdr->lun;
+ params.lun_len = sizeof(req_hdr->lun);
+ params.lun_set = 1;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ case ISCSI_FUNCTION_TARGET_COLD_RESET:
+ case ISCSI_FUNCTION_TARGET_WARM_RESET:
+ target_abort(req, 1);
+ params.fn = SCST_TARGET_RESET;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ case ISCSI_FUNCTION_LOGICAL_UNIT_RESET:
+ target_abort(req, 0);
+ params.fn = SCST_LUN_RESET;
+ params.lun = (uint8_t *)&req_hdr->lun;
+ params.lun_len = sizeof(req_hdr->lun);
+ params.lun_set = 1;
+ params.cmd_sn = req_hdr->cmd_sn;
+ params.cmd_sn_set = 1;
+ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
+ ¶ms);
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ case ISCSI_FUNCTION_TASK_REASSIGN:
+ rc = -1;
+ status = ISCSI_RESPONSE_ALLEGIANCE_REASSIGNMENT_UNSUPPORTED;
+ break;
+ default:
+ PRINT_ERROR("Unknown TM function %d", function);
+ rc = -1;
+ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
+ break;
+ }
+
+reject:
+ if (rc != 0)
+ iscsi_send_task_mgmt_resp(req, status);
+
+ return;
+}
+
+static void nop_out_exec(struct iscsi_cmnd *req)
+{
+ struct iscsi_cmnd *rsp;
+ struct iscsi_nop_in_hdr *rsp_hdr;
+
+ TRACE_DBG("%p", req);
+
+ if (cmnd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
+ rsp = iscsi_alloc_main_rsp(req);
+
+ rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->opcode = ISCSI_OP_NOP_IN;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->itt = req->pdu.bhs.itt;
+ rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
+
+ if (req->pdu.datasize)
+ BUG_ON(req->sg == NULL);
+ else
+ BUG_ON(req->sg != NULL);
+
+ if (req->sg) {
+ rsp->sg = req->sg;
+ rsp->sg_cnt = req->sg_cnt;
+ rsp->bufflen = req->bufflen;
+ }
+
+ /* We already checked it in check_segment_length() */
+ BUG_ON(get_pgcnt(req->pdu.datasize, 0) > ISCSI_CONN_IOV_MAX);
+
+ rsp->pdu.datasize = req->pdu.datasize;
+ } else {
+ bool found = false;
+ struct iscsi_cmnd *r;
+ struct iscsi_conn *conn = req->conn;
+
+ TRACE_DBG("Receive Nop-In response (ttt 0x%08x)",
+ be32_to_cpu(cmnd_ttt(req)));
+
+ spin_lock_bh(&conn->nop_req_list_lock);
+ list_for_each_entry(r, &conn->nop_req_list,
+ nop_req_list_entry) {
+ if (cmnd_ttt(req) == cmnd_ttt(r)) {
+ list_del(&r->nop_req_list_entry);
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_bh(&conn->nop_req_list_lock);
+
+ if (found)
+ cmnd_put(r);
+ else
+ TRACE_MGMT_DBG("%s", "Got Nop-out response without "
+ "corresponding Nop-In request");
+ }
+
+ req_cmnd_release(req);
+ return;
+}
+
+static void logout_exec(struct iscsi_cmnd *req)
+{
+ struct iscsi_logout_req_hdr *req_hdr;
+ struct iscsi_cmnd *rsp;
+ struct iscsi_logout_rsp_hdr *rsp_hdr;
+
+ PRINT_INFO("Logout received from initiator %s",
+ req->conn->session->initiator_name);
+ TRACE_DBG("%p", req);
+
+ req_hdr = (struct iscsi_logout_req_hdr *)&req->pdu.bhs;
+ rsp = iscsi_alloc_main_rsp(req);
+ rsp_hdr = (struct iscsi_logout_rsp_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->opcode = ISCSI_OP_LOGOUT_RSP;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->itt = req_hdr->itt;
+ rsp->should_close_conn = 1;
+
+ req_cmnd_release(req);
+
+ return;
+}
+
+static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd)
+{
+
+ TRACE_DBG("cmnd %p, op %x, SN %u", cmnd, cmnd_opcode(cmnd),
+ cmnd->pdu.bhs.sn);
+
+ iscsi_extracheck_is_rd_thread(cmnd->conn);
+
+ if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) {
+ if (cmnd->r2t_len_to_receive == 0)
+ iscsi_restart_cmnd(cmnd);
+ else if (cmnd->r2t_len_to_send != 0)
+ send_r2t(cmnd);
+ goto out;
+ }
+
+ if (cmnd->prelim_compl_flags != 0) {
+ TRACE_MGMT_DBG("Terminating prelim completed non-SCSI cmnd %p "
+ "(op %x)", cmnd, cmnd_opcode(cmnd));
+ req_cmnd_release(cmnd);
+ goto out;
+ }
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_NOP_OUT:
+ nop_out_exec(cmnd);
+ break;
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ execute_task_management(cmnd);
+ break;
+ case ISCSI_OP_LOGOUT_CMD:
+ logout_exec(cmnd);
+ break;
+ default:
+ PRINT_CRIT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
+ BUG();
+ break;
+ }
+
+out:
+ return;
+}
+
+static void set_cork(struct socket *sock, int on)
+{
+ int opt = on;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(get_ds());
+ sock->ops->setsockopt(sock, SOL_TCP, TCP_CORK,
+ (void __force __user *)&opt, sizeof(opt));
+ set_fs(oldfs);
+ return;
+}
+
+void cmnd_tx_start(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+
+ TRACE_DBG("conn %p, cmnd %p, opcode %x", conn, cmnd, cmnd_opcode(cmnd));
+ iscsi_cmnd_set_length(&cmnd->pdu);
+
+ iscsi_extracheck_is_wr_thread(conn);
+
+ set_cork(conn->sock, 1);
+
+ conn->write_iop = conn->write_iov;
+ conn->write_iop->iov_base = (void __force __user *)(&cmnd->pdu.bhs);
+ conn->write_iop->iov_len = sizeof(cmnd->pdu.bhs);
+ conn->write_iop_used = 1;
+ conn->write_size = sizeof(cmnd->pdu.bhs) + cmnd->pdu.datasize;
+ conn->write_offset = 0;
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_NOP_IN:
+ if (cmnd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG))
+ cmnd->pdu.bhs.sn = cmnd_set_sn(cmnd, 0);
+ else
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_SCSI_RSP:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_TEXT_RSP:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_SCSI_DATA_IN:
+ {
+ struct iscsi_data_in_hdr *rsp =
+ (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
+ u32 offset = cpu_to_be32(rsp->buffer_offset);
+
+ TRACE_DBG("cmnd %p, offset %u, datasize %u, bufflen %u", cmnd,
+ offset, cmnd->pdu.datasize, cmnd->bufflen);
+
+ BUG_ON(offset > cmnd->bufflen);
+ BUG_ON(offset + cmnd->pdu.datasize > cmnd->bufflen);
+
+ conn->write_offset = offset;
+
+ cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0);
+ break;
+ }
+ case ISCSI_OP_LOGOUT_RSP:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_R2T:
+ cmnd->pdu.bhs.sn = cmnd_set_sn(cmnd, 0);
+ break;
+ case ISCSI_OP_ASYNC_MSG:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ case ISCSI_OP_REJECT:
+ cmnd_set_sn(cmnd, 1);
+ break;
+ default:
+ PRINT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
+ break;
+ }
+
+ iscsi_dump_pdu(&cmnd->pdu);
+ return;
+}
+
+void cmnd_tx_end(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+
+ TRACE_DBG("%p:%x (should_close_conn %d, should_close_all_conn %d)",
+ cmnd, cmnd_opcode(cmnd), cmnd->should_close_conn,
+ cmnd->should_close_all_conn);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_NOP_IN:
+ case ISCSI_OP_SCSI_RSP:
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_R2T:
+ case ISCSI_OP_ASYNC_MSG:
+ case ISCSI_OP_REJECT:
+ case ISCSI_OP_SCSI_DATA_IN:
+ case ISCSI_OP_LOGOUT_RSP:
+ break;
+ default:
+ PRINT_CRIT_ERROR("unexpected cmnd op %x", cmnd_opcode(cmnd));
+ BUG();
+ break;
+ }
+#endif
+
+ if (unlikely(cmnd->should_close_conn)) {
+ if (cmnd->should_close_all_conn) {
+ PRINT_INFO("Closing all connections for target %x at "
+ "initiator's %s request",
+ cmnd->conn->session->target->tid,
+ conn->session->initiator_name);
+ target_del_all_sess(cmnd->conn->session->target, 0);
+ } else {
+ PRINT_INFO("Closing connection at initiator's %s "
+ "request", conn->session->initiator_name);
+ mark_conn_closed(conn);
+ }
+ }
+
+ set_cork(cmnd->conn->sock, 0);
+ return;
+}
+
+/*
+ * Push the command for execution. This functions reorders the commands.
+ * Called from the read thread.
+ *
+ * Basically, since we don't support MC/S and TCP guarantees data delivery
+ * order, all that SN's stuff isn't needed at all (commands delivery order is
+ * a natural commands execution order), but insane iSCSI spec requires
+ * us to check it and we have to, because some crazy initiators can rely
+ * on the SN's based order and reorder requests during sending. For all other
+ * normal initiators all that code is a NOP.
+ */
+static void iscsi_push_cmnd(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_session *session = cmnd->conn->session;
+ struct list_head *entry;
+ u32 cmd_sn;
+
+ TRACE_DBG("cmnd %p, iSCSI opcode %x, sn %u, exp sn %u", cmnd,
+ cmnd_opcode(cmnd), cmnd->pdu.bhs.sn, session->exp_cmd_sn);
+
+ iscsi_extracheck_is_rd_thread(cmnd->conn);
+
+ BUG_ON(cmnd->parent_req != NULL);
+
+ if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
+ TRACE_DBG("Immediate cmd %p (cmd_sn %u)", cmnd,
+ cmnd->pdu.bhs.sn);
+ iscsi_cmnd_exec(cmnd);
+ goto out;
+ }
+
+ spin_lock(&session->sn_lock);
+
+ cmd_sn = cmnd->pdu.bhs.sn;
+ if (cmd_sn == session->exp_cmd_sn) {
+ while (1) {
+ session->exp_cmd_sn = ++cmd_sn;
+
+ if (unlikely(session->tm_active > 0)) {
+ if (before(cmd_sn, session->tm_sn)) {
+ struct iscsi_conn *conn = cmnd->conn;
+
+ spin_unlock(&session->sn_lock);
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ __cmnd_abort(cmnd);
+ spin_unlock_bh(&conn->cmd_list_lock);
+
+ spin_lock(&session->sn_lock);
+ }
+ iscsi_check_send_delayed_tm_resp(session);
+ }
+
+ spin_unlock(&session->sn_lock);
+
+ iscsi_cmnd_exec(cmnd);
+
+ spin_lock(&session->sn_lock);
+
+ if (list_empty(&session->pending_list))
+ break;
+ cmnd = list_entry(session->pending_list.next,
+ struct iscsi_cmnd,
+ pending_list_entry);
+ if (cmnd->pdu.bhs.sn != cmd_sn)
+ break;
+
+ list_del(&cmnd->pending_list_entry);
+ cmnd->pending = 0;
+
+ TRACE_MGMT_DBG("Processing pending cmd %p (cmd_sn %u)",
+ cmnd, cmd_sn);
+ }
+ } else {
+ int drop = 0;
+
+ TRACE_DBG("Pending cmd %p (cmd_sn %u, exp_cmd_sn %u)",
+ cmnd, cmd_sn, session->exp_cmd_sn);
+
+ /*
+ * iSCSI RFC 3720: "The target MUST silently ignore any
+ * non-immediate command outside of [from ExpCmdSN to MaxCmdSN
+ * inclusive] range". But we won't honor the MaxCmdSN
+ * requirement, because, since we adjust MaxCmdSN from the
+ * separate write thread, rarely it is possible that initiator
+ * can legally send command with CmdSN>MaxSN. But it won't
+ * hurt anything, in the worst case it will lead to
+ * additional QUEUE FULL status.
+ */
+
+ if (unlikely(before(cmd_sn, session->exp_cmd_sn))) {
+ PRINT_ERROR("Unexpected cmd_sn (%u,%u)", cmd_sn,
+ session->exp_cmd_sn);
+ drop = 1;
+ }
+
+#if 0
+ if (unlikely(after(cmd_sn, session->exp_cmd_sn +
+ iscsi_get_allowed_cmds(session)))) {
+ TRACE_MGMT_DBG("Too large cmd_sn %u (exp_cmd_sn %u, "
+ "max_sn %u)", cmd_sn, session->exp_cmd_sn,
+ iscsi_get_allowed_cmds(session));
+ }
+#endif
+
+ spin_unlock(&session->sn_lock);
+
+ if (unlikely(drop)) {
+ req_cmnd_release_force(cmnd);
+ goto out;
+ }
+
+ if (unlikely(test_bit(ISCSI_CMD_ABORTED,
+ &cmnd->prelim_compl_flags))) {
+ struct iscsi_cmnd *tm_clone;
+
+ TRACE_MGMT_DBG("Pending aborted cmnd %p, creating TM "
+ "clone (scst cmd %p, state %d)", cmnd,
+ cmnd->scst_cmd, cmnd->scst_state);
+
+ tm_clone = cmnd_alloc(cmnd->conn, NULL);
+ if (tm_clone != NULL) {
+ set_bit(ISCSI_CMD_ABORTED,
+ &tm_clone->prelim_compl_flags);
+ tm_clone->pdu = cmnd->pdu;
+
+ TRACE_MGMT_DBG("TM clone %p created",
+ tm_clone);
+
+ iscsi_cmnd_exec(cmnd);
+ cmnd = tm_clone;
+ } else
+ PRINT_ERROR("%s", "Unable to create TM clone");
+ }
+
+ TRACE_MGMT_DBG("Pending cmnd %p (op %x, sn %u, exp sn %u)",
+ cmnd, cmnd_opcode(cmnd), cmd_sn, session->exp_cmd_sn);
+
+ spin_lock(&session->sn_lock);
+ list_for_each(entry, &session->pending_list) {
+ struct iscsi_cmnd *tmp =
+ list_entry(entry, struct iscsi_cmnd,
+ pending_list_entry);
+ if (before(cmd_sn, tmp->pdu.bhs.sn))
+ break;
+ }
+ list_add_tail(&cmnd->pending_list_entry, entry);
+ cmnd->pending = 1;
+ }
+
+ spin_unlock(&session->sn_lock);
+out:
+ return;
+}
+
+static int check_segment_length(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iscsi_session *session = conn->session;
+
+ if (unlikely(cmnd->pdu.datasize > session->sess_params.max_recv_data_length)) {
+ PRINT_ERROR("Initiator %s violated negotiated parameters: "
+ "data too long (ITT %x, datasize %u, "
+ "max_recv_data_length %u", session->initiator_name,
+ cmnd_itt(cmnd), cmnd->pdu.datasize,
+ session->sess_params.max_recv_data_length);
+ mark_conn_closed(conn);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int cmnd_rx_start(struct iscsi_cmnd *cmnd)
+{
+ int res, rc;
+
+ iscsi_dump_pdu(&cmnd->pdu);
+
+ res = check_segment_length(cmnd);
+ if (res != 0)
+ goto out;
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_SCSI_CMD:
+ res = scsi_cmnd_start(cmnd);
+ if (unlikely(res < 0))
+ goto out;
+ spin_lock(&cmnd->conn->session->sn_lock);
+ __update_stat_sn(cmnd);
+ rc = check_cmd_sn(cmnd);
+ spin_unlock(&cmnd->conn->session->sn_lock);
+ break;
+ case ISCSI_OP_SCSI_DATA_OUT:
+ res = data_out_start(cmnd);
+ goto out;
+ case ISCSI_OP_NOP_OUT:
+ rc = nop_out_start(cmnd);
+ break;
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_LOGOUT_CMD:
+ spin_lock(&cmnd->conn->session->sn_lock);
+ __update_stat_sn(cmnd);
+ rc = check_cmd_sn(cmnd);
+ spin_unlock(&cmnd->conn->session->sn_lock);
+ break;
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_SNACK_CMD:
+ default:
+ rc = -ISCSI_REASON_UNSUPPORTED_COMMAND;
+ break;
+ }
+
+ if (unlikely(rc < 0)) {
+ PRINT_ERROR("Error %d (iSCSI opcode %x, ITT %x)", rc,
+ cmnd_opcode(cmnd), cmnd_itt(cmnd));
+ res = create_reject_rsp(cmnd, -rc, true);
+ }
+
+out:
+ return res;
+}
+
+void cmnd_rx_end(struct iscsi_cmnd *cmnd)
+{
+
+ TRACE_DBG("cmnd %p, opcode %x", cmnd, cmnd_opcode(cmnd));
+
+ cmnd->conn->last_rcv_time = jiffies;
+ TRACE_DBG("Updated last_rcv_time %ld", cmnd->conn->last_rcv_time);
+
+ switch (cmnd_opcode(cmnd)) {
+ case ISCSI_OP_SCSI_CMD:
+ case ISCSI_OP_NOP_OUT:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_LOGOUT_CMD:
+ iscsi_push_cmnd(cmnd);
+ goto out;
+ case ISCSI_OP_SCSI_DATA_OUT:
+ data_out_end(cmnd);
+ break;
+ default:
+ PRINT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
+ break;
+ }
+
+ req_cmnd_release(cmnd);
+
+out:
+ return;
+}
+
+static int iscsi_alloc_data_buf(struct scst_cmd *cmd)
+{
+ /*
+ * sock->ops->sendpage() is async zero copy operation,
+ * so we must be sure not to free and reuse
+ * the command's buffer before the sending was completed
+ * by the network layers. It is possible only if we
+ * don't use SGV cache.
+ */
+ EXTRACHECKS_BUG_ON(!(scst_cmd_get_data_direction(cmd) & SCST_DATA_READ));
+ scst_cmd_set_no_sgv(cmd);
+ return 1;
+}
+
+static void iscsi_preprocessing_done(struct scst_cmd *scst_cmd)
+{
+ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
+ scst_cmd_get_tgt_priv(scst_cmd);
+
+ TRACE_DBG("req %p", req);
+
+ if (req->conn->rx_task == current)
+ req->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC;
+ else {
+ /*
+ * We wait for the state change without any protection, so
+ * without cmnd_get() it is possible that req will die
+ * "immediately" after the state assignment and
+ * iscsi_make_conn_rd_active() will operate on dead data.
+ * We use the ordered version of cmnd_get(), because "get"
+ * must be done before the state assignment.
+ *
+ * We protected from the race on calling cmnd_rx_continue(),
+ * because there can be only one read thread processing
+ * connection.
+ */
+ cmnd_get_ordered(req);
+ req->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC;
+ iscsi_make_conn_rd_active(req->conn);
+ if (unlikely(req->conn->closing)) {
+ TRACE_DBG("Waking up closing conn %p", req->conn);
+ wake_up(&req->conn->read_state_waitQ);
+ }
+ cmnd_put(req);
+ }
+
+ return;
+}
+
+/*
+ * No locks.
+ *
+ * IMPORTANT! Connection conn must be protected by additional conn_get()
+ * upon entrance in this function, because otherwise it could be destroyed
+ * inside as a result of iscsi_send(), which releases sent commands.
+ */
+static void iscsi_try_local_processing(struct iscsi_conn *conn)
+{
+ int local;
+
+ spin_lock_bh(&iscsi_wr_lock);
+ switch (conn->wr_state) {
+ case ISCSI_CONN_WR_STATE_IN_LIST:
+ list_del(&conn->wr_list_entry);
+ /* go through */
+ case ISCSI_CONN_WR_STATE_IDLE:
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->wr_task = current;
+#endif
+ conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
+ conn->wr_space_ready = 0;
+ local = 1;
+ break;
+ default:
+ local = 0;
+ break;
+ }
+ spin_unlock_bh(&iscsi_wr_lock);
+
+ if (local) {
+ int rc = 1;
+
+ if (test_write_ready(conn))
+ rc = iscsi_send(conn);
+
+ spin_lock_bh(&iscsi_wr_lock);
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->wr_task = NULL;
+#endif
+ if ((rc <= 0) || test_write_ready(conn)) {
+ list_add_tail(&conn->wr_list_entry, &iscsi_wr_list);
+ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
+ wake_up(&iscsi_wr_waitQ);
+ } else
+ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
+ spin_unlock_bh(&iscsi_wr_lock);
+ }
+ return;
+}
+
+static int iscsi_xmit_response(struct scst_cmd *scst_cmd)
+{
+ int is_send_status = scst_cmd_get_is_send_status(scst_cmd);
+ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
+ scst_cmd_get_tgt_priv(scst_cmd);
+ struct iscsi_conn *conn = req->conn;
+ int status = scst_cmd_get_status(scst_cmd);
+ u8 *sense = scst_cmd_get_sense_buffer(scst_cmd);
+ int sense_len = scst_cmd_get_sense_buffer_len(scst_cmd);
+
+ if (unlikely(scst_cmd_atomic(scst_cmd)))
+ return SCST_TGT_RES_NEED_THREAD_CTX;
+
+ scst_cmd_set_tgt_priv(scst_cmd, NULL);
+
+ EXTRACHECKS_BUG_ON(req->scst_state != ISCSI_CMD_STATE_RESTARTED);
+
+ if (unlikely(scst_cmd_aborted(scst_cmd)))
+ set_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags);
+
+ if (unlikely(req->prelim_compl_flags != 0)) {
+ if (test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags)) {
+ TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req,
+ req->scst_cmd);
+ scst_set_delivery_status(req->scst_cmd,
+ SCST_CMD_DELIVERY_ABORTED);
+ req->scst_state = ISCSI_CMD_STATE_PROCESSED;
+ req_cmnd_release_force(req);
+ goto out;
+ }
+
+ TRACE_DBG("Prelim completed req %p", req);
+
+ /*
+ * We could preliminary have finished req before we
+ * knew its device, so check if we return correct sense
+ * format.
+ */
+ scst_check_convert_sense(scst_cmd);
+
+ if (!req->own_sg) {
+ req->sg = scst_cmd_get_sg(scst_cmd);
+ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
+ }
+ } else {
+ EXTRACHECKS_BUG_ON(req->own_sg);
+ req->sg = scst_cmd_get_sg(scst_cmd);
+ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
+ }
+
+ req->bufflen = scst_cmd_get_resp_data_len(scst_cmd);
+
+ req->scst_state = ISCSI_CMD_STATE_PROCESSED;
+
+ TRACE_DBG("req %p, is_send_status=%x, req->bufflen=%d, req->sg=%p, "
+ "req->sg_cnt %d", req, is_send_status, req->bufflen, req->sg,
+ req->sg_cnt);
+
+ EXTRACHECKS_BUG_ON(req->hashed);
+ if (req->main_rsp != NULL)
+ EXTRACHECKS_BUG_ON(cmnd_opcode(req->main_rsp) != ISCSI_OP_REJECT);
+
+ if (unlikely((req->bufflen != 0) && !is_send_status)) {
+ PRINT_CRIT_ERROR("%s", "Sending DATA without STATUS is "
+ "unsupported");
+ scst_set_cmd_error(scst_cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ BUG(); /* ToDo */
+ }
+
+ if (req->bufflen != 0) {
+ /*
+ * Check above makes sure that is_send_status is set,
+ * so status is valid here, but in future that could change.
+ * ToDo
+ */
+ if ((status != SAM_STAT_CHECK_CONDITION) &&
+ ((cmnd_hdr(req)->flags & (ISCSI_CMD_WRITE|ISCSI_CMD_READ)) !=
+ (ISCSI_CMD_WRITE|ISCSI_CMD_READ))) {
+ send_data_rsp(req, status, is_send_status);
+ } else {
+ struct iscsi_cmnd *rsp;
+ send_data_rsp(req, 0, 0);
+ if (is_send_status) {
+ rsp = create_status_rsp(req, status, sense,
+ sense_len, true);
+ iscsi_cmnd_init_write(rsp, 0);
+ }
+ }
+ } else if (is_send_status) {
+ struct iscsi_cmnd *rsp;
+ rsp = create_status_rsp(req, status, sense, sense_len, false);
+ iscsi_cmnd_init_write(rsp, 0);
+ }
+#ifdef CONFIG_SCST_EXTRACHECKS
+ else
+ BUG();
+#endif
+
+ /*
+ * "_ordered" here to protect from reorder, which can lead to
+ * preliminary connection destroy in req_cmnd_release(). Just in
+ * case, actually, because reordering shouldn't go so far, but who
+ * knows..
+ */
+ conn_get_ordered(conn);
+ req_cmnd_release(req);
+ iscsi_try_local_processing(conn);
+ conn_put(conn);
+
+out:
+ return SCST_TGT_RES_SUCCESS;
+}
+
+/* Called under sn_lock */
+static bool iscsi_is_delay_tm_resp(struct iscsi_cmnd *rsp)
+{
+ bool res = 0;
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&rsp->parent_req->pdu.bhs;
+ int function = req_hdr->function & ISCSI_FUNCTION_MASK;
+ struct iscsi_session *sess = rsp->conn->session;
+
+ /* This should be checked for immediate TM commands as well */
+
+ switch (function) {
+ default:
+ if (before(sess->exp_cmd_sn, req_hdr->cmd_sn))
+ res = 1;
+ break;
+ }
+ return res;
+}
+
+/* Called under sn_lock, but might drop it inside, then reaquire */
+static void iscsi_check_send_delayed_tm_resp(struct iscsi_session *sess)
+ __acquires(&sn_lock)
+ __releases(&sn_lock)
+{
+ struct iscsi_cmnd *tm_rsp = sess->tm_rsp;
+
+ if (tm_rsp == NULL)
+ goto out;
+
+ if (iscsi_is_delay_tm_resp(tm_rsp))
+ goto out;
+
+ TRACE_MGMT_DBG("Sending delayed rsp %p", tm_rsp);
+
+ sess->tm_rsp = NULL;
+ sess->tm_active--;
+
+ spin_unlock(&sess->sn_lock);
+
+ BUG_ON(sess->tm_active < 0);
+
+ iscsi_cmnd_init_write(tm_rsp, ISCSI_INIT_WRITE_WAKE);
+
+ spin_lock(&sess->sn_lock);
+
+out:
+ return;
+}
+
+static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status)
+{
+ struct iscsi_cmnd *rsp;
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
+ struct iscsi_task_rsp_hdr *rsp_hdr;
+ struct iscsi_session *sess = req->conn->session;
+ int fn = req_hdr->function & ISCSI_FUNCTION_MASK;
+
+ TRACE_MGMT_DBG("TM req %p finished", req);
+ TRACE(TRACE_MGMT, "TM fn %d finished, status %d", fn, status);
+
+ rsp = iscsi_alloc_rsp(req);
+ rsp_hdr = (struct iscsi_task_rsp_hdr *)&rsp->pdu.bhs;
+
+ rsp_hdr->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->itt = req_hdr->itt;
+ rsp_hdr->response = status;
+
+ if (fn == ISCSI_FUNCTION_TARGET_COLD_RESET) {
+ rsp->should_close_conn = 1;
+ rsp->should_close_all_conn = 1;
+ }
+
+ BUG_ON(sess->tm_rsp != NULL);
+
+ spin_lock(&sess->sn_lock);
+ if (iscsi_is_delay_tm_resp(rsp)) {
+ TRACE_MGMT_DBG("Delaying TM fn %d response %p "
+ "(req %p), because not all affected commands "
+ "received (TM cmd sn %u, exp sn %u)",
+ req_hdr->function & ISCSI_FUNCTION_MASK, rsp, req,
+ req_hdr->cmd_sn, sess->exp_cmd_sn);
+ sess->tm_rsp = rsp;
+ spin_unlock(&sess->sn_lock);
+ goto out_release;
+ }
+ sess->tm_active--;
+ spin_unlock(&sess->sn_lock);
+
+ BUG_ON(sess->tm_active < 0);
+
+ iscsi_cmnd_init_write(rsp, ISCSI_INIT_WRITE_WAKE);
+
+out_release:
+ req_cmnd_release(req);
+ return;
+}
+
+static inline int iscsi_get_mgmt_response(int status)
+{
+ switch (status) {
+ case SCST_MGMT_STATUS_SUCCESS:
+ return ISCSI_RESPONSE_FUNCTION_COMPLETE;
+
+ case SCST_MGMT_STATUS_TASK_NOT_EXIST:
+ return ISCSI_RESPONSE_UNKNOWN_TASK;
+
+ case SCST_MGMT_STATUS_LUN_NOT_EXIST:
+ return ISCSI_RESPONSE_UNKNOWN_LUN;
+
+ case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
+ return ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
+
+ case SCST_MGMT_STATUS_REJECTED:
+ case SCST_MGMT_STATUS_FAILED:
+ default:
+ return ISCSI_RESPONSE_FUNCTION_REJECTED;
+ }
+}
+
+static void iscsi_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd)
+{
+ int fn = scst_mgmt_cmd_get_fn(scst_mcmd);
+ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
+ scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
+ int status =
+ iscsi_get_mgmt_response(scst_mgmt_cmd_get_status(scst_mcmd));
+
+ if ((status == ISCSI_RESPONSE_UNKNOWN_TASK) &&
+ (fn == SCST_ABORT_TASK)) {
+ /* If we are here, we found the task, so must succeed */
+ status = ISCSI_RESPONSE_FUNCTION_COMPLETE;
+ }
+
+ TRACE_MGMT_DBG("req %p, scst_mcmd %p, fn %d, scst status %d, status %d",
+ req, scst_mcmd, fn, scst_mgmt_cmd_get_status(scst_mcmd),
+ status);
+
+ switch (fn) {
+ case SCST_NEXUS_LOSS_SESS:
+ case SCST_ABORT_ALL_TASKS_SESS:
+ /* They are internal */
+ break;
+ default:
+ iscsi_send_task_mgmt_resp(req, status);
+ scst_mgmt_cmd_set_tgt_priv(scst_mcmd, NULL);
+ break;
+ }
+ return;
+}
+
+static int iscsi_scsi_aen(struct scst_aen *aen)
+{
+ int res = SCST_AEN_RES_SUCCESS;
+ uint64_t lun = scst_aen_get_lun(aen);
+ const uint8_t *sense = scst_aen_get_sense(aen);
+ int sense_len = scst_aen_get_sense_len(aen);
+ struct iscsi_session *sess = scst_sess_get_tgt_priv(
+ scst_aen_get_sess(aen));
+ struct iscsi_conn *conn;
+ bool found;
+ struct iscsi_cmnd *fake_req, *rsp;
+ struct iscsi_async_msg_hdr *rsp_hdr;
+ struct scatterlist *sg;
+
+ TRACE_MGMT_DBG("SCSI AEN to sess %p (initiator %s)", sess,
+ sess->initiator_name);
+
+ mutex_lock(&sess->target->target_mutex);
+
+ found = false;
+ list_for_each_entry_reverse(conn, &sess->conn_list, conn_list_entry) {
+ if (!test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags) &&
+ (conn->conn_reinst_successor == NULL)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ TRACE_MGMT_DBG("Unable to find alive conn for sess %p", sess);
+ goto out_err;
+ }
+
+ /* Create a fake request */
+ fake_req = cmnd_alloc(conn, NULL);
+ if (fake_req == NULL) {
+ PRINT_ERROR("%s", "Unable to alloc fake AEN request");
+ goto out_err;
+ }
+
+ mutex_unlock(&sess->target->target_mutex);
+
+ rsp = iscsi_alloc_main_rsp(fake_req);
+ if (rsp == NULL) {
+ PRINT_ERROR("%s", "Unable to alloc AEN rsp");
+ goto out_err_free_req;
+ }
+
+ fake_req->scst_state = ISCSI_CMD_STATE_AEN;
+ fake_req->scst_aen = aen;
+
+ rsp_hdr = (struct iscsi_async_msg_hdr *)&rsp->pdu.bhs;
+
+ rsp_hdr->opcode = ISCSI_OP_ASYNC_MSG;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->lun = lun; /* it's already in SCSI form */
+ rsp_hdr->ffffffff = 0xffffffff;
+ rsp_hdr->async_event = ISCSI_ASYNC_SCSI;
+
+ sg = rsp->sg = rsp->rsp_sg;
+ rsp->sg_cnt = 2;
+ rsp->own_sg = 1;
+
+ sg_init_table(sg, 2);
+ sg_set_buf(&sg[0], &rsp->sense_hdr, sizeof(rsp->sense_hdr));
+ sg_set_buf(&sg[1], sense, sense_len);
+
+ rsp->sense_hdr.length = cpu_to_be16(sense_len);
+ rsp->pdu.datasize = sizeof(rsp->sense_hdr) + sense_len;
+ rsp->bufflen = rsp->pdu.datasize;
+
+ req_cmnd_release(fake_req);
+
+out:
+ return res;
+
+out_err_free_req:
+ req_cmnd_release(fake_req);
+
+out_err:
+ mutex_unlock(&sess->target->target_mutex);
+ res = SCST_AEN_RES_FAILED;
+ goto out;
+}
+
+static int iscsi_report_aen(struct scst_aen *aen)
+{
+ int res;
+ int event_fn = scst_aen_get_event_fn(aen);
+
+ switch (event_fn) {
+ case SCST_AEN_SCSI:
+ res = iscsi_scsi_aen(aen);
+ break;
+ default:
+ TRACE_MGMT_DBG("Unsupported AEN %d", event_fn);
+ res = SCST_AEN_RES_NOT_SUPPORTED;
+ break;
+ }
+ return res;
+}
+
+void iscsi_send_nop_in(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *req, *rsp;
+ struct iscsi_nop_in_hdr *rsp_hdr;
+
+ req = cmnd_alloc(conn, NULL);
+ if (req == NULL) {
+ PRINT_ERROR("%s", "Unable to alloc fake Nop-In request");
+ goto out_err;
+ }
+
+ rsp = iscsi_alloc_main_rsp(req);
+ if (rsp == NULL) {
+ PRINT_ERROR("%s", "Unable to alloc Nop-In rsp");
+ goto out_err_free_req;
+ }
+
+ cmnd_get(rsp);
+
+ rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
+ rsp_hdr->opcode = ISCSI_OP_NOP_IN;
+ rsp_hdr->flags = ISCSI_FLG_FINAL;
+ rsp_hdr->itt = cpu_to_be32(ISCSI_RESERVED_TAG);
+ rsp_hdr->ttt = conn->nop_in_ttt++;
+
+ if (conn->nop_in_ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+ conn->nop_in_ttt = 0;
+
+ /* Supposed that all other fields are zeroed */
+
+ TRACE_DBG("Sending Nop-In request (ttt 0x%08x)", rsp_hdr->ttt);
+ spin_lock_bh(&conn->nop_req_list_lock);
+ list_add_tail(&rsp->nop_req_list_entry, &conn->nop_req_list);
+ spin_unlock_bh(&conn->nop_req_list_lock);
+
+out_err_free_req:
+ req_cmnd_release(req);
+
+out_err:
+ return;
+}
+
+static int iscsi_target_detect(struct scst_tgt_template *templ)
+{
+ /* Nothing to do */
+ return 0;
+}
+
+static int iscsi_target_release(struct scst_tgt *scst_tgt)
+{
+ /* Nothing to do */
+ return 0;
+}
+
+static struct scst_trace_log iscsi_local_trace_tbl[] = {
+ { TRACE_D_WRITE, "d_write" },
+ { TRACE_CONN_OC, "conn" },
+ { TRACE_CONN_OC_DBG, "conn_dbg" },
+ { TRACE_D_IOV, "iov" },
+ { TRACE_D_DUMP_PDU, "pdu" },
+ { TRACE_NET_PG, "net_page" },
+ { 0, NULL }
+};
+
+#define ISCSI_TRACE_TLB_HELP ", d_read, d_write, conn, conn_dbg, iov, pdu, net_page"
+
+#define ISCSI_MGMT_CMD_HELP \
+ " echo \"add_attribute IncomingUser name password\" >mgmt\n" \
+ " echo \"del_attribute IncomingUser name\" >mgmt\n" \
+ " echo \"add_attribute OutgoingUser name password\" >mgmt\n" \
+ " echo \"del_attribute OutgoingUser name\" >mgmt\n" \
+ " echo \"add_target_attribute target_name IncomingUser name password\" >mgmt\n" \
+ " echo \"del_target_attribute target_name IncomingUser name\" >mgmt\n" \
+ " echo \"add_target_attribute target_name OutgoingUser name password\" >mgmt\n" \
+ " echo \"del_target_attribute target_name OutgoingUser name\" >mgmt\n"
+
+struct scst_tgt_template iscsi_template = {
+ .name = "iscsi",
+ .sg_tablesize = 0xFFFF /* no limit */,
+ .threads_num = 0,
+ .no_clustering = 1,
+ .xmit_response_atomic = 0,
+ .tgtt_attrs = iscsi_attrs,
+ .tgt_attrs = iscsi_tgt_attrs,
+ .sess_attrs = iscsi_sess_attrs,
+ .enable_target = iscsi_enable_target,
+ .is_target_enabled = iscsi_is_target_enabled,
+ .add_target = iscsi_sysfs_add_target,
+ .del_target = iscsi_sysfs_del_target,
+ .mgmt_cmd = iscsi_sysfs_mgmt_cmd,
+ .mgmt_cmd_help = ISCSI_MGMT_CMD_HELP,
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+ .default_trace_flags = ISCSI_DEFAULT_LOG_FLAGS,
+ .trace_flags = &trace_flag,
+ .trace_tbl = iscsi_local_trace_tbl,
+ .trace_tbl_help = ISCSI_TRACE_TLB_HELP,
+#endif
+ .detect = iscsi_target_detect,
+ .release = iscsi_target_release,
+ .xmit_response = iscsi_xmit_response,
+ .alloc_data_buf = iscsi_alloc_data_buf,
+ .preprocessing_done = iscsi_preprocessing_done,
+ .pre_exec = iscsi_pre_exec,
+ .task_mgmt_affected_cmds_done = iscsi_task_mgmt_affected_cmds_done,
+ .task_mgmt_fn_done = iscsi_task_mgmt_fn_done,
+ .report_aen = iscsi_report_aen,
+};
+
+static __init int iscsi_run_threads(int count, char *name, int (*fn)(void *))
+{
+ int res = 0;
+ int i;
+ struct iscsi_thread_t *thr;
+
+ for (i = 0; i < count; i++) {
+ thr = kmalloc(sizeof(*thr), GFP_KERNEL);
+ if (!thr) {
+ res = -ENOMEM;
+ PRINT_ERROR("Failed to allocate thr %d", res);
+ goto out;
+ }
+ thr->thr = kthread_run(fn, NULL, "%s%d", name, i);
+ if (IS_ERR(thr->thr)) {
+ res = PTR_ERR(thr->thr);
+ PRINT_ERROR("kthread_create() failed: %d", res);
+ kfree(thr);
+ goto out;
+ }
+ list_add_tail(&thr->threads_list_entry, &iscsi_threads_list);
+ }
+
+out:
+ return res;
+}
+
+static void iscsi_stop_threads(void)
+{
+ struct iscsi_thread_t *t, *tmp;
+
+ list_for_each_entry_safe(t, tmp, &iscsi_threads_list,
+ threads_list_entry) {
+ int rc = kthread_stop(t->thr);
+ if (rc < 0)
+ TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
+ list_del(&t->threads_list_entry);
+ kfree(t);
+ }
+ return;
+}
+
+static int __init iscsi_init(void)
+{
+ int err = 0;
+ int num;
+
+ PRINT_INFO("iSCSI SCST Target - version %s", ISCSI_VERSION_STRING);
+
+ dummy_page = alloc_pages(GFP_KERNEL, 0);
+ if (dummy_page == NULL) {
+ PRINT_ERROR("%s", "Dummy page allocation failed");
+ goto out;
+ }
+
+ sg_init_table(&dummy_sg, 1);
+ sg_set_page(&dummy_sg, dummy_page, PAGE_SIZE, 0);
+
+ ctr_major = register_chrdev(0, ctr_name, &ctr_fops);
+ if (ctr_major < 0) {
+ PRINT_ERROR("failed to register the control device %d",
+ ctr_major);
+ err = ctr_major;
+ goto out_callb;
+ }
+
+ err = event_init();
+ if (err < 0)
+ goto out_reg;
+
+ iscsi_cmnd_cache = KMEM_CACHE(iscsi_cmnd, SCST_SLAB_FLAGS);
+ if (!iscsi_cmnd_cache) {
+ err = -ENOMEM;
+ goto out_event;
+ }
+
+ err = scst_register_target_template(&iscsi_template);
+ if (err < 0)
+ goto out_kmem;
+
+ num = max((int)num_online_cpus(), 2);
+
+ err = iscsi_run_threads(num, "iscsird", istrd);
+ if (err != 0)
+ goto out_thr;
+
+ err = iscsi_run_threads(num, "iscsiwr", istwr);
+ if (err != 0)
+ goto out_thr;
+
+out:
+ return err;
+
+out_thr:
+ iscsi_stop_threads();
+
+ scst_unregister_target_template(&iscsi_template);
+
+out_kmem:
+ kmem_cache_destroy(iscsi_cmnd_cache);
+
+out_event:
+ event_exit();
+
+out_reg:
+ unregister_chrdev(ctr_major, ctr_name);
+
+out_callb:
+ __free_pages(dummy_page, 0);
+ goto out;
+}
+
+static void __exit iscsi_exit(void)
+{
+ iscsi_stop_threads();
+
+ unregister_chrdev(ctr_major, ctr_name);
+
+ event_exit();
+
+ kmem_cache_destroy(iscsi_cmnd_cache);
+
+ scst_unregister_target_template(&iscsi_template);
+
+ __free_pages(dummy_page, 0);
+ return;
+}
+
+module_init(iscsi_init);
+module_exit(iscsi_exit);
+
+MODULE_LICENSE("GPL");
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/nthread.c linux-2.6.33/drivers/scst/iscsi-scst/nthread.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/nthread.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/nthread.c
@@ -0,0 +1,1524 @@
+/*
+ * Network threads.
+ *
+ * Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@....org>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/kthread.h>
+#include <asm/ioctls.h>
+#include <linux/delay.h>
+#include <net/tcp.h>
+
+#include "iscsi.h"
+#include "digest.h"
+
+enum rx_state {
+ RX_INIT_BHS, /* Must be zero for better "switch" optimization. */
+ RX_BHS,
+ RX_CMD_START,
+ RX_DATA,
+ RX_END,
+
+ RX_CMD_CONTINUE,
+ RX_INIT_HDIGEST,
+ RX_CHECK_HDIGEST,
+ RX_INIT_DDIGEST,
+ RX_CHECK_DDIGEST,
+ RX_AHS,
+ RX_PADDING,
+};
+
+enum tx_state {
+ TX_INIT = 0, /* Must be zero for better "switch" optimization. */
+ TX_BHS_DATA,
+ TX_INIT_PADDING,
+ TX_PADDING,
+ TX_INIT_DDIGEST,
+ TX_DDIGEST,
+ TX_END,
+};
+
+static inline void iscsi_check_closewait(struct iscsi_conn *conn) {};
+
+static void free_pending_commands(struct iscsi_conn *conn)
+{
+ struct iscsi_session *session = conn->session;
+ struct list_head *pending_list = &session->pending_list;
+ int req_freed;
+ struct iscsi_cmnd *cmnd;
+
+ spin_lock(&session->sn_lock);
+ do {
+ req_freed = 0;
+ list_for_each_entry(cmnd, pending_list, pending_list_entry) {
+ TRACE_CONN_CLOSE_DBG("Pending cmd %p"
+ "(conn %p, cmd_sn %u, exp_cmd_sn %u)",
+ cmnd, conn, cmnd->pdu.bhs.sn,
+ session->exp_cmd_sn);
+ if ((cmnd->conn == conn) &&
+ (session->exp_cmd_sn == cmnd->pdu.bhs.sn)) {
+ TRACE_CONN_CLOSE_DBG("Freeing pending cmd %p",
+ cmnd);
+
+ list_del(&cmnd->pending_list_entry);
+ cmnd->pending = 0;
+
+ session->exp_cmd_sn++;
+
+ spin_unlock(&session->sn_lock);
+
+ req_cmnd_release_force(cmnd);
+
+ req_freed = 1;
+ spin_lock(&session->sn_lock);
+ break;
+ }
+ }
+ } while (req_freed);
+ spin_unlock(&session->sn_lock);
+
+ return;
+}
+
+static void free_orphaned_pending_commands(struct iscsi_conn *conn)
+{
+ struct iscsi_session *session = conn->session;
+ struct list_head *pending_list = &session->pending_list;
+ int req_freed;
+ struct iscsi_cmnd *cmnd;
+
+ spin_lock(&session->sn_lock);
+ do {
+ req_freed = 0;
+ list_for_each_entry(cmnd, pending_list, pending_list_entry) {
+ TRACE_CONN_CLOSE_DBG("Pending cmd %p"
+ "(conn %p, cmd_sn %u, exp_cmd_sn %u)",
+ cmnd, conn, cmnd->pdu.bhs.sn,
+ session->exp_cmd_sn);
+ if (cmnd->conn == conn) {
+ PRINT_ERROR("Freeing orphaned pending cmd %p",
+ cmnd);
+
+ list_del(&cmnd->pending_list_entry);
+ cmnd->pending = 0;
+
+ if (session->exp_cmd_sn == cmnd->pdu.bhs.sn)
+ session->exp_cmd_sn++;
+
+ spin_unlock(&session->sn_lock);
+
+ req_cmnd_release_force(cmnd);
+
+ req_freed = 1;
+ spin_lock(&session->sn_lock);
+ break;
+ }
+ }
+ } while (req_freed);
+ spin_unlock(&session->sn_lock);
+
+ return;
+}
+
+#ifdef CONFIG_SCST_DEBUG
+static void trace_conn_close(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd;
+
+#if 0
+ if (time_after(jiffies, start_waiting + 10*HZ))
+ trace_flag |= TRACE_CONN_OC_DBG;
+#endif
+
+ spin_lock_bh(&conn->cmd_list_lock);
+ list_for_each_entry(cmnd, &conn->cmd_list,
+ cmd_list_entry) {
+ TRACE_CONN_CLOSE_DBG(
+ "cmd %p, scst_cmd %p, scst_state %x, scst_cmd state "
+ "%d, r2t_len_to_receive %d, ref_cnt %d, sn %u, "
+ "parent_req %p, pending %d",
+ cmnd, cmnd->scst_cmd, cmnd->scst_state,
+ ((cmnd->parent_req == NULL) && cmnd->scst_cmd) ?
+ cmnd->scst_cmd->state : -1,
+ cmnd->r2t_len_to_receive, atomic_read(&cmnd->ref_cnt),
+ cmnd->pdu.bhs.sn, cmnd->parent_req, cmnd->pending);
+ }
+ spin_unlock_bh(&conn->cmd_list_lock);
+ return;
+}
+#else /* CONFIG_SCST_DEBUG */
+static void trace_conn_close(struct iscsi_conn *conn) {}
+#endif /* CONFIG_SCST_DEBUG */
+
+void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd)
+{
+ int fn = scst_mgmt_cmd_get_fn(scst_mcmd);
+ void *priv = scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
+
+ TRACE_MGMT_DBG("scst_mcmd %p, fn %d, priv %p", scst_mcmd, fn, priv);
+
+ switch (fn) {
+ case SCST_NEXUS_LOSS_SESS:
+ case SCST_ABORT_ALL_TASKS_SESS:
+ {
+ struct iscsi_conn *conn = (struct iscsi_conn *)priv;
+ struct iscsi_session *sess = conn->session;
+ struct iscsi_conn *c;
+
+ mutex_lock(&sess->target->target_mutex);
+
+ /*
+ * We can't mark sess as shutting down earlier, because until
+ * now it might have pending commands. Otherwise, in case of
+ * reinstatement it might lead to data corruption, because
+ * commands in being reinstated session can be executed
+ * after commands in the new session.
+ */
+ sess->sess_shutting_down = 1;
+ list_for_each_entry(c, &sess->conn_list, conn_list_entry) {
+ if (!test_bit(ISCSI_CONN_SHUTTINGDOWN, &c->conn_aflags)) {
+ sess->sess_shutting_down = 0;
+ break;
+ }
+ }
+
+ if (conn->conn_reinst_successor != NULL) {
+ BUG_ON(!test_bit(ISCSI_CONN_REINSTATING,
+ &conn->conn_reinst_successor->conn_aflags));
+ conn_reinst_finished(conn->conn_reinst_successor);
+ conn->conn_reinst_successor = NULL;
+ } else if (sess->sess_reinst_successor != NULL) {
+ sess_reinst_finished(sess->sess_reinst_successor);
+ sess->sess_reinst_successor = NULL;
+ }
+ mutex_unlock(&sess->target->target_mutex);
+
+ complete_all(&conn->ready_to_free);
+ break;
+ }
+ default:
+ /* Nothing to do */
+ break;
+ }
+
+ return;
+}
+
+/* No locks */
+static void close_conn(struct iscsi_conn *conn)
+{
+ struct iscsi_session *session = conn->session;
+ struct iscsi_target *target = conn->target;
+ typeof(jiffies) start_waiting = jiffies;
+ typeof(jiffies) shut_start_waiting = start_waiting;
+ bool pending_reported = 0, wait_expired = 0, shut_expired = 0;
+ bool reinst;
+
+#define CONN_PENDING_TIMEOUT ((typeof(jiffies))10*HZ)
+#define CONN_WAIT_TIMEOUT ((typeof(jiffies))10*HZ)
+#define CONN_REG_SHUT_TIMEOUT ((typeof(jiffies))125*HZ)
+#define CONN_DEL_SHUT_TIMEOUT ((typeof(jiffies))10*HZ)
+
+ TRACE_MGMT_DBG("Closing connection %p (conn_ref_cnt=%d)", conn,
+ atomic_read(&conn->conn_ref_cnt));
+
+ iscsi_extracheck_is_rd_thread(conn);
+
+ BUG_ON(!conn->closing);
+
+ if (conn->active_close) {
+ /* We want all our already send operations to complete */
+ conn->sock->ops->shutdown(conn->sock, RCV_SHUTDOWN);
+ } else {
+ conn->sock->ops->shutdown(conn->sock,
+ RCV_SHUTDOWN|SEND_SHUTDOWN);
+ }
+
+ mutex_lock(&session->target->target_mutex);
+
+ set_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags);
+ reinst = (conn->conn_reinst_successor != NULL);
+
+ mutex_unlock(&session->target->target_mutex);
+
+ if (reinst) {
+ int rc;
+ int lun = 0;
+
+ /* Abort all outstanding commands */
+ rc = scst_rx_mgmt_fn_lun(session->scst_sess,
+ SCST_ABORT_ALL_TASKS_SESS, (uint8_t *)&lun, sizeof(lun),
+ SCST_NON_ATOMIC, conn);
+ if (rc != 0)
+ PRINT_ERROR("SCST_ABORT_ALL_TASKS_SESS failed %d", rc);
+ } else {
+ int rc;
+ int lun = 0;
+
+ rc = scst_rx_mgmt_fn_lun(session->scst_sess,
+ SCST_NEXUS_LOSS_SESS, (uint8_t *)&lun, sizeof(lun),
+ SCST_NON_ATOMIC, conn);
+ if (rc != 0)
+ PRINT_ERROR("SCST_NEXUS_LOSS_SESS failed %d", rc);
+ }
+
+ if (conn->read_state != RX_INIT_BHS) {
+ struct iscsi_cmnd *cmnd = conn->read_cmnd;
+
+ if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
+ TRACE_CONN_CLOSE_DBG("Going to wait for cmnd %p to "
+ "change state from RX_CMD", cmnd);
+ }
+ wait_event(conn->read_state_waitQ,
+ cmnd->scst_state != ISCSI_CMD_STATE_RX_CMD);
+
+ TRACE_CONN_CLOSE_DBG("Releasing conn->read_cmnd %p (conn %p)",
+ conn->read_cmnd, conn);
+
+ conn->read_cmnd = NULL;
+ conn->read_state = RX_INIT_BHS;
+ req_cmnd_release_force(cmnd);
+ }
+
+ conn_abort(conn);
+
+ /* ToDo: not the best way to wait */
+ while (atomic_read(&conn->conn_ref_cnt) != 0) {
+ if (conn->conn_tm_active)
+ iscsi_check_tm_data_wait_timeouts(conn, true);
+
+ mutex_lock(&target->target_mutex);
+ spin_lock(&session->sn_lock);
+ if (session->tm_rsp && session->tm_rsp->conn == conn) {
+ struct iscsi_cmnd *tm_rsp = session->tm_rsp;
+ TRACE_MGMT_DBG("Dropping delayed TM rsp %p", tm_rsp);
+ session->tm_rsp = NULL;
+ session->tm_active--;
+ WARN_ON(session->tm_active < 0);
+ spin_unlock(&session->sn_lock);
+ mutex_unlock(&target->target_mutex);
+
+ rsp_cmnd_release(tm_rsp);
+ } else {
+ spin_unlock(&session->sn_lock);
+ mutex_unlock(&target->target_mutex);
+ }
+
+ /* It's safe to check it without sn_lock */
+ if (!list_empty(&session->pending_list)) {
+ TRACE_CONN_CLOSE_DBG("Disposing pending commands on "
+ "connection %p (conn_ref_cnt=%d)", conn,
+ atomic_read(&conn->conn_ref_cnt));
+
+ free_pending_commands(conn);
+
+ if (time_after(jiffies,
+ start_waiting + CONN_PENDING_TIMEOUT)) {
+ if (!pending_reported) {
+ TRACE_CONN_CLOSE("%s",
+ "Pending wait time expired");
+ pending_reported = 1;
+ }
+ free_orphaned_pending_commands(conn);
+ }
+ }
+
+ iscsi_make_conn_wr_active(conn);
+
+ /* That's for active close only, actually */
+ if (time_after(jiffies, start_waiting + CONN_WAIT_TIMEOUT) &&
+ !wait_expired) {
+ TRACE_CONN_CLOSE("Wait time expired (conn %p, "
+ "sk_state %d)",
+ conn, conn->sock->sk->sk_state);
+ conn->sock->ops->shutdown(conn->sock, SEND_SHUTDOWN);
+ wait_expired = 1;
+ shut_start_waiting = jiffies;
+ }
+
+ if (wait_expired && !shut_expired &&
+ time_after(jiffies, shut_start_waiting +
+ conn->deleting ? CONN_DEL_SHUT_TIMEOUT :
+ CONN_REG_SHUT_TIMEOUT)) {
+ TRACE_CONN_CLOSE("Wait time after shutdown expired "
+ "(conn %p, sk_state %d)", conn,
+ conn->sock->sk->sk_state);
+ conn->sock->sk->sk_prot->disconnect(conn->sock->sk, 0);
+ shut_expired = 1;
+ }
+
+ if (conn->deleting)
+ msleep(200);
+ else
+ msleep(1000);
+
+ TRACE_CONN_CLOSE_DBG("conn %p, conn_ref_cnt %d left, "
+ "wr_state %d, exp_cmd_sn %u",
+ conn, atomic_read(&conn->conn_ref_cnt),
+ conn->wr_state, session->exp_cmd_sn);
+
+ trace_conn_close(conn);
+
+ iscsi_check_closewait(conn);
+ }
+
+ write_lock_bh(&conn->sock->sk->sk_callback_lock);
+ conn->sock->sk->sk_state_change = conn->old_state_change;
+ conn->sock->sk->sk_data_ready = conn->old_data_ready;
+ conn->sock->sk->sk_write_space = conn->old_write_space;
+ write_unlock_bh(&conn->sock->sk->sk_callback_lock);
+
+ while (1) {
+ bool t;
+
+ spin_lock_bh(&iscsi_wr_lock);
+ t = (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE);
+ spin_unlock_bh(&iscsi_wr_lock);
+
+ if (t && (atomic_read(&conn->conn_ref_cnt) == 0))
+ break;
+
+ TRACE_CONN_CLOSE_DBG("Waiting for wr thread (conn %p), "
+ "wr_state %x", conn, conn->wr_state);
+ msleep(50);
+ }
+
+ wait_for_completion(&conn->ready_to_free);
+
+ TRACE_CONN_CLOSE("Notifying user space about closing connection %p",
+ conn);
+ event_send(target->tid, session->sid, conn->cid, 0, E_CONN_CLOSE,
+ NULL, NULL);
+
+ kobject_put(&conn->iscsi_conn_kobj);
+ return;
+}
+
+static int close_conn_thr(void *arg)
+{
+ struct iscsi_conn *conn = (struct iscsi_conn *)arg;
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ /*
+ * To satisfy iscsi_extracheck_is_rd_thread() in functions called
+ * on the connection close. It is safe, because at this point conn
+ * can't be used by any other thread.
+ */
+ conn->rd_task = current;
+#endif
+ close_conn(conn);
+ return 0;
+}
+
+/* No locks */
+static void start_close_conn(struct iscsi_conn *conn)
+{
+ struct task_struct *t;
+
+ t = kthread_run(close_conn_thr, conn, "iscsi_conn_cleanup");
+ if (IS_ERR(t)) {
+ PRINT_ERROR("kthread_run() failed (%ld), closing conn %p "
+ "directly", PTR_ERR(t), conn);
+ close_conn(conn);
+ }
+ return;
+}
+
+static inline void iscsi_conn_init_read(struct iscsi_conn *conn,
+ void __user *data, size_t len)
+{
+ conn->read_iov[0].iov_base = data;
+ conn->read_iov[0].iov_len = len;
+ conn->read_msg.msg_iov = conn->read_iov;
+ conn->read_msg.msg_iovlen = 1;
+ conn->read_size = len;
+ return;
+}
+
+static void iscsi_conn_prepare_read_ahs(struct iscsi_conn *conn,
+ struct iscsi_cmnd *cmnd)
+{
+ int asize = (cmnd->pdu.ahssize + 3) & -4;
+
+ /* ToDo: __GFP_NOFAIL ?? */
+ cmnd->pdu.ahs = kmalloc(asize, __GFP_NOFAIL|GFP_KERNEL);
+ BUG_ON(cmnd->pdu.ahs == NULL);
+ iscsi_conn_init_read(conn, (void __force __user *)cmnd->pdu.ahs, asize);
+ return;
+}
+
+static struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd = NULL;
+
+ spin_lock_bh(&conn->write_list_lock);
+ if (!list_empty(&conn->write_list)) {
+ cmnd = list_entry(conn->write_list.next, struct iscsi_cmnd,
+ write_list_entry);
+ cmd_del_from_write_list(cmnd);
+ cmnd->write_processing_started = 1;
+ }
+ spin_unlock_bh(&conn->write_list_lock);
+
+ if (unlikely(test_bit(ISCSI_CMD_ABORTED,
+ &cmnd->parent_req->prelim_compl_flags))) {
+ TRACE_MGMT_DBG("Going to send acmd %p (scst cmd %p, "
+ "state %d, parent_req %p)", cmnd, cmnd->scst_cmd,
+ cmnd->scst_state, cmnd->parent_req);
+ }
+
+ if (unlikely(cmnd_opcode(cmnd) == ISCSI_OP_SCSI_TASK_MGT_RSP)) {
+#ifdef CONFIG_SCST_DEBUG
+ struct iscsi_task_mgt_hdr *req_hdr =
+ (struct iscsi_task_mgt_hdr *)&cmnd->parent_req->pdu.bhs;
+ struct iscsi_task_rsp_hdr *rsp_hdr =
+ (struct iscsi_task_rsp_hdr *)&cmnd->pdu.bhs;
+ TRACE_MGMT_DBG("Going to send TM response %p (status %d, "
+ "fn %d, parent_req %p)", cmnd, rsp_hdr->response,
+ req_hdr->function & ISCSI_FUNCTION_MASK,
+ cmnd->parent_req);
+#endif
+ }
+
+ return cmnd;
+}
+
+/* Returns number of bytes left to receive or <0 for error */
+static int do_recv(struct iscsi_conn *conn)
+{
+ int res;
+ mm_segment_t oldfs;
+ struct msghdr msg;
+ int first_len;
+
+ EXTRACHECKS_BUG_ON(conn->read_cmnd == NULL);
+
+ if (unlikely(conn->closing)) {
+ res = -EIO;
+ goto out;
+ }
+
+ /*
+ * We suppose that if sock_recvmsg() returned less data than requested,
+ * then next time it will return -EAGAIN, so there's no point to call
+ * it again.
+ */
+
+restart:
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = conn->read_msg.msg_iov;
+ msg.msg_iovlen = conn->read_msg.msg_iovlen;
+ first_len = msg.msg_iov->iov_len;
+
+ oldfs = get_fs();
+ set_fs(get_ds());
+ res = sock_recvmsg(conn->sock, &msg, conn->read_size,
+ MSG_DONTWAIT | MSG_NOSIGNAL);
+ set_fs(oldfs);
+
+ if (res > 0) {
+ /*
+ * To save some considerable effort and CPU power we
+ * suppose that TCP functions adjust
+ * conn->read_msg.msg_iov and conn->read_msg.msg_iovlen
+ * on amount of copied data. This BUG_ON is intended
+ * to catch if it is changed in the future.
+ */
+ BUG_ON((res >= first_len) &&
+ (conn->read_msg.msg_iov->iov_len != 0));
+ conn->read_size -= res;
+ if (conn->read_size != 0) {
+ if (res >= first_len) {
+ int done = 1 + ((res - first_len) >> PAGE_SHIFT);
+ conn->read_msg.msg_iov += done;
+ conn->read_msg.msg_iovlen -= done;
+ }
+ }
+ res = conn->read_size;
+ } else {
+ switch (res) {
+ case -EAGAIN:
+ TRACE_DBG("EAGAIN received for conn %p", conn);
+ res = conn->read_size;
+ break;
+ case -ERESTARTSYS:
+ TRACE_DBG("ERESTARTSYS received for conn %p", conn);
+ goto restart;
+ default:
+ if (!conn->closing) {
+ PRINT_ERROR("sock_recvmsg() failed: %d", res);
+ mark_conn_closed(conn);
+ }
+ if (res == 0)
+ res = -EIO;
+ break;
+ }
+ }
+
+out:
+ return res;
+}
+
+static int iscsi_rx_check_ddigest(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd = conn->read_cmnd;
+ int res;
+
+ res = do_recv(conn);
+ if (res == 0) {
+ conn->read_state = RX_END;
+
+ if (cmnd->pdu.datasize <= 16*1024) {
+ /*
+ * It's cache hot, so let's compute it inline. The
+ * choice here about what will expose more latency:
+ * possible cache misses or the digest calculation.
+ */
+ TRACE_DBG("cmnd %p, opcode %x: checking RX "
+ "ddigest inline", cmnd, cmnd_opcode(cmnd));
+ cmnd->ddigest_checked = 1;
+ res = digest_rx_data(cmnd);
+ if (unlikely(res != 0)) {
+ mark_conn_closed(conn);
+ goto out;
+ }
+ } else if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) {
+ cmd_add_on_rx_ddigest_list(cmnd, cmnd);
+ cmnd_get(cmnd);
+ } else if (cmnd_opcode(cmnd) != ISCSI_OP_SCSI_DATA_OUT) {
+ /*
+ * We could get here only for Nop-Out. ISCSI RFC
+ * doesn't specify how to deal with digest errors in
+ * this case. Is closing connection correct?
+ */
+ TRACE_DBG("cmnd %p, opcode %x: checking NOP RX "
+ "ddigest", cmnd, cmnd_opcode(cmnd));
+ res = digest_rx_data(cmnd);
+ if (unlikely(res != 0)) {
+ mark_conn_closed(conn);
+ goto out;
+ }
+ }
+ }
+
+out:
+ return res;
+}
+
+/* No locks, conn is rd processing */
+static int process_read_io(struct iscsi_conn *conn, int *closed)
+{
+ struct iscsi_cmnd *cmnd = conn->read_cmnd;
+ int res;
+
+ /* In case of error cmnd will be freed in close_conn() */
+
+ do {
+ switch (conn->read_state) {
+ case RX_INIT_BHS:
+ EXTRACHECKS_BUG_ON(conn->read_cmnd != NULL);
+ cmnd = cmnd_alloc(conn, NULL);
+ conn->read_cmnd = cmnd;
+ iscsi_conn_init_read(cmnd->conn,
+ (void __force __user *)&cmnd->pdu.bhs,
+ sizeof(cmnd->pdu.bhs));
+ conn->read_state = RX_BHS;
+ /* go through */
+
+ case RX_BHS:
+ res = do_recv(conn);
+ if (res == 0) {
+ iscsi_cmnd_get_length(&cmnd->pdu);
+ if (cmnd->pdu.ahssize == 0) {
+ if ((conn->hdigest_type & DIGEST_NONE) == 0)
+ conn->read_state = RX_INIT_HDIGEST;
+ else
+ conn->read_state = RX_CMD_START;
+ } else {
+ iscsi_conn_prepare_read_ahs(conn, cmnd);
+ conn->read_state = RX_AHS;
+ }
+ }
+ break;
+
+ case RX_CMD_START:
+ res = cmnd_rx_start(cmnd);
+ if (res == 0) {
+ if (cmnd->pdu.datasize == 0)
+ conn->read_state = RX_END;
+ else
+ conn->read_state = RX_DATA;
+ } else if (res > 0)
+ conn->read_state = RX_CMD_CONTINUE;
+ else
+ BUG_ON(!conn->closing);
+ break;
+
+ case RX_CMD_CONTINUE:
+ if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
+ TRACE_DBG("cmnd %p is still in RX_CMD state",
+ cmnd);
+ res = 1;
+ break;
+ }
+ res = cmnd_rx_continue(cmnd);
+ if (unlikely(res != 0))
+ BUG_ON(!conn->closing);
+ else {
+ if (cmnd->pdu.datasize == 0)
+ conn->read_state = RX_END;
+ else
+ conn->read_state = RX_DATA;
+ }
+ break;
+
+ case RX_DATA:
+ res = do_recv(conn);
+ if (res == 0) {
+ int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize;
+ if (psz != 0) {
+ TRACE_DBG("padding %d bytes", psz);
+ iscsi_conn_init_read(conn,
+ (void __force __user *)&conn->rpadding, psz);
+ conn->read_state = RX_PADDING;
+ } else if ((conn->ddigest_type & DIGEST_NONE) != 0)
+ conn->read_state = RX_END;
+ else
+ conn->read_state = RX_INIT_DDIGEST;
+ }
+ break;
+
+ case RX_END:
+ if (unlikely(conn->read_size != 0)) {
+ PRINT_CRIT_ERROR("conn read_size !=0 on RX_END "
+ "(conn %p, op %x, read_size %d)", conn,
+ cmnd_opcode(cmnd), conn->read_size);
+ BUG();
+ }
+ conn->read_cmnd = NULL;
+ conn->read_state = RX_INIT_BHS;
+
+ cmnd_rx_end(cmnd);
+
+ EXTRACHECKS_BUG_ON(conn->read_size != 0);
+
+ /*
+ * To maintain fairness. Res must be 0 here anyway, the
+ * assignment is only to remove compiler warning about
+ * uninitialized variable.
+ */
+ res = 0;
+ goto out;
+
+ case RX_INIT_HDIGEST:
+ iscsi_conn_init_read(conn,
+ (void __force __user *)&cmnd->hdigest, sizeof(u32));
+ conn->read_state = RX_CHECK_HDIGEST;
+ /* go through */
+
+ case RX_CHECK_HDIGEST:
+ res = do_recv(conn);
+ if (res == 0) {
+ res = digest_rx_header(cmnd);
+ if (unlikely(res != 0)) {
+ PRINT_ERROR("rx header digest for "
+ "initiator %s failed (%d)",
+ conn->session->initiator_name,
+ res);
+ mark_conn_closed(conn);
+ } else
+ conn->read_state = RX_CMD_START;
+ }
+ break;
+
+ case RX_INIT_DDIGEST:
+ iscsi_conn_init_read(conn,
+ (void __force __user *)&cmnd->ddigest,
+ sizeof(u32));
+ conn->read_state = RX_CHECK_DDIGEST;
+ /* go through */
+
+ case RX_CHECK_DDIGEST:
+ res = iscsi_rx_check_ddigest(conn);
+ break;
+
+ case RX_AHS:
+ res = do_recv(conn);
+ if (res == 0) {
+ if ((conn->hdigest_type & DIGEST_NONE) == 0)
+ conn->read_state = RX_INIT_HDIGEST;
+ else
+ conn->read_state = RX_CMD_START;
+ }
+ break;
+
+ case RX_PADDING:
+ res = do_recv(conn);
+ if (res == 0) {
+ if ((conn->ddigest_type & DIGEST_NONE) == 0)
+ conn->read_state = RX_INIT_DDIGEST;
+ else
+ conn->read_state = RX_END;
+ }
+ break;
+
+ default:
+ PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd));
+ res = -1; /* to keep compiler happy */
+ BUG();
+ }
+ } while (res == 0);
+
+ if (unlikely(conn->closing)) {
+ start_close_conn(conn);
+ *closed = 1;
+ }
+
+out:
+ return res;
+}
+
+/*
+ * Called under iscsi_rd_lock and BHs disabled, but will drop it inside,
+ * then reaquire.
+ */
+static void scst_do_job_rd(void)
+ __acquires(&iscsi_rd_lock)
+ __releases(&iscsi_rd_lock)
+{
+
+ /*
+ * We delete/add to tail connections to maintain fairness between them.
+ */
+
+ while (!list_empty(&iscsi_rd_list)) {
+ int closed = 0, rc;
+ struct iscsi_conn *conn = list_entry(iscsi_rd_list.next,
+ typeof(*conn), rd_list_entry);
+
+ list_del(&conn->rd_list_entry);
+
+ BUG_ON(conn->rd_state == ISCSI_CONN_RD_STATE_PROCESSING);
+ conn->rd_data_ready = 0;
+ conn->rd_state = ISCSI_CONN_RD_STATE_PROCESSING;
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->rd_task = current;
+#endif
+ spin_unlock_bh(&iscsi_rd_lock);
+
+ rc = process_read_io(conn, &closed);
+
+ spin_lock_bh(&iscsi_rd_lock);
+
+ if (unlikely(closed))
+ continue;
+
+ if (unlikely(conn->conn_tm_active)) {
+ spin_unlock_bh(&iscsi_rd_lock);
+ iscsi_check_tm_data_wait_timeouts(conn, false);
+ spin_lock_bh(&iscsi_rd_lock);
+ }
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->rd_task = NULL;
+#endif
+ if ((rc == 0) || conn->rd_data_ready) {
+ list_add_tail(&conn->rd_list_entry, &iscsi_rd_list);
+ conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
+ } else
+ conn->rd_state = ISCSI_CONN_RD_STATE_IDLE;
+ }
+ return;
+}
+
+static inline int test_rd_list(void)
+{
+ int res = !list_empty(&iscsi_rd_list) ||
+ unlikely(kthread_should_stop());
+ return res;
+}
+
+int istrd(void *arg)
+{
+
+ PRINT_INFO("Read thread started, PID %d", current->pid);
+
+ current->flags |= PF_NOFREEZE;
+
+ spin_lock_bh(&iscsi_rd_lock);
+ while (!kthread_should_stop()) {
+ wait_queue_t wait;
+ init_waitqueue_entry(&wait, current);
+
+ if (!test_rd_list()) {
+ add_wait_queue_exclusive_head(&iscsi_rd_waitQ, &wait);
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (test_rd_list())
+ break;
+ spin_unlock_bh(&iscsi_rd_lock);
+ schedule();
+ spin_lock_bh(&iscsi_rd_lock);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&iscsi_rd_waitQ, &wait);
+ }
+ scst_do_job_rd();
+ }
+ spin_unlock_bh(&iscsi_rd_lock);
+
+ /*
+ * If kthread_should_stop() is true, we are guaranteed to be
+ * on the module unload, so iscsi_rd_list must be empty.
+ */
+ BUG_ON(!list_empty(&iscsi_rd_list));
+
+ PRINT_INFO("Read thread PID %d finished", current->pid);
+ return 0;
+}
+
+static inline void check_net_priv(struct iscsi_cmnd *cmd, struct page *page) {}
+static inline void __iscsi_get_page_callback(struct iscsi_cmnd *cmd) {}
+static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) {}
+
+void req_add_to_write_timeout_list(struct iscsi_cmnd *req)
+{
+ struct iscsi_conn *conn;
+ unsigned long timeout_time;
+ bool set_conn_tm_active = false;
+
+ if (req->on_write_timeout_list)
+ goto out;
+
+ conn = req->conn;
+
+ TRACE_DBG("Adding req %p to conn %p write_timeout_list",
+ req, conn);
+
+ spin_lock_bh(&conn->write_list_lock);
+
+ /* Recheck, since it can be changed behind us */
+ if (unlikely(req->on_write_timeout_list)) {
+ spin_unlock_bh(&conn->write_list_lock);
+ goto out;
+ }
+
+ req->on_write_timeout_list = 1;
+ req->write_start = jiffies;
+
+ list_add_tail(&req->write_timeout_list_entry,
+ &conn->write_timeout_list);
+
+ if (!timer_pending(&conn->rsp_timer)) {
+ if (unlikely(conn->conn_tm_active ||
+ test_bit(ISCSI_CMD_ABORTED,
+ &req->prelim_compl_flags))) {
+ set_conn_tm_active = true;
+ timeout_time = req->write_start +
+ ISCSI_TM_DATA_WAIT_TIMEOUT +
+ ISCSI_ADD_SCHED_TIME;
+ } else
+ timeout_time = req->write_start +
+ conn->rsp_timeout + ISCSI_ADD_SCHED_TIME;
+
+ TRACE_DBG("Starting timer on %ld (con %p, write_start %ld)",
+ timeout_time, conn, req->write_start);
+
+ conn->rsp_timer.expires = timeout_time;
+ add_timer(&conn->rsp_timer);
+ } else if (unlikely(test_bit(ISCSI_CMD_ABORTED,
+ &req->prelim_compl_flags))) {
+ unsigned long timeout_time = jiffies +
+ ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME;
+ set_conn_tm_active = true;
+ if (time_after(conn->rsp_timer.expires, timeout_time)) {
+ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)",
+ timeout_time, conn);
+ mod_timer(&conn->rsp_timer, timeout_time);
+ }
+ }
+
+ spin_unlock_bh(&conn->write_list_lock);
+
+ /*
+ * conn_tm_active can be already cleared by
+ * iscsi_check_tm_data_wait_timeouts(). write_list_lock is an inner
+ * lock for iscsi_rd_lock.
+ */
+ if (unlikely(set_conn_tm_active)) {
+ spin_lock_bh(&iscsi_rd_lock);
+ TRACE_MGMT_DBG("Setting conn_tm_active for conn %p", conn);
+ conn->conn_tm_active = 1;
+ spin_unlock_bh(&iscsi_rd_lock);
+ }
+
+out:
+ return;
+}
+
+static int write_data(struct iscsi_conn *conn)
+{
+ mm_segment_t oldfs;
+ struct file *file;
+ struct iovec *iop;
+ struct socket *sock;
+ ssize_t (*sock_sendpage)(struct socket *, struct page *, int, size_t,
+ int);
+ ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
+ struct iscsi_cmnd *write_cmnd = conn->write_cmnd;
+ struct iscsi_cmnd *ref_cmd;
+ struct page *page;
+ struct scatterlist *sg;
+ int saved_size, size, sendsize;
+ int length, offset, idx;
+ int flags, res, count, sg_size;
+ bool do_put = false, ref_cmd_to_parent;
+
+ iscsi_extracheck_is_wr_thread(conn);
+
+ if (!write_cmnd->own_sg) {
+ ref_cmd = write_cmnd->parent_req;
+ ref_cmd_to_parent = true;
+ } else {
+ ref_cmd = write_cmnd;
+ ref_cmd_to_parent = false;
+ }
+
+ req_add_to_write_timeout_list(write_cmnd->parent_req);
+
+ file = conn->file;
+ size = conn->write_size;
+ saved_size = size;
+ iop = conn->write_iop;
+ count = conn->write_iop_used;
+
+ if (iop) {
+ while (1) {
+ loff_t off = 0;
+ int rest;
+
+ BUG_ON(count > (signed)(sizeof(conn->write_iov) /
+ sizeof(conn->write_iov[0])));
+retry:
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+ res = vfs_writev(file,
+ (struct iovec __force __user *)iop,
+ count, &off);
+ set_fs(oldfs);
+ TRACE_WRITE("sid %#Lx, cid %u, res %d, iov_len %ld",
+ (long long unsigned int)conn->session->sid,
+ conn->cid, res, (long)iop->iov_len);
+ if (unlikely(res <= 0)) {
+ if (res == -EAGAIN) {
+ conn->write_iop = iop;
+ conn->write_iop_used = count;
+ goto out_iov;
+ } else if (res == -EINTR)
+ goto retry;
+ goto out_err;
+ }
+
+ rest = res;
+ size -= res;
+ while ((typeof(rest))iop->iov_len <= rest && rest) {
+ rest -= iop->iov_len;
+ iop++;
+ count--;
+ }
+ if (count == 0) {
+ conn->write_iop = NULL;
+ conn->write_iop_used = 0;
+ if (size)
+ break;
+ goto out_iov;
+ }
+ BUG_ON(iop > conn->write_iov + sizeof(conn->write_iov)
+ /sizeof(conn->write_iov[0]));
+ iop->iov_base += rest;
+ iop->iov_len -= rest;
+ }
+ }
+
+ sg = write_cmnd->sg;
+ if (unlikely(sg == NULL)) {
+ PRINT_INFO("WARNING: Data missed (cmd %p)!", write_cmnd);
+ res = 0;
+ goto out;
+ }
+
+ /* To protect from too early transfer completion race */
+ __iscsi_get_page_callback(ref_cmd);
+ do_put = true;
+
+ sock = conn->sock;
+
+ if ((write_cmnd->parent_req->scst_cmd != NULL) &&
+ scst_cmd_get_dh_data_buff_alloced(write_cmnd->parent_req->scst_cmd))
+ sock_sendpage = sock_no_sendpage;
+ else
+ sock_sendpage = sock->ops->sendpage;
+
+ flags = MSG_DONTWAIT;
+ sg_size = size;
+
+ if (sg != write_cmnd->rsp_sg) {
+ offset = conn->write_offset + sg[0].offset;
+ idx = offset >> PAGE_SHIFT;
+ offset &= ~PAGE_MASK;
+ length = min(size, (int)PAGE_SIZE - offset);
+ TRACE_WRITE("write_offset %d, sg_size %d, idx %d, offset %d, "
+ "length %d", conn->write_offset, sg_size, idx, offset,
+ length);
+ } else {
+ idx = 0;
+ offset = conn->write_offset;
+ while (offset >= sg[idx].length) {
+ offset -= sg[idx].length;
+ idx++;
+ }
+ length = sg[idx].length - offset;
+ offset += sg[idx].offset;
+ sock_sendpage = sock_no_sendpage;
+ TRACE_WRITE("rsp_sg: write_offset %d, sg_size %d, idx %d, "
+ "offset %d, length %d", conn->write_offset, sg_size,
+ idx, offset, length);
+ }
+ page = sg_page(&sg[idx]);
+
+ while (1) {
+ sendpage = sock_sendpage;
+
+ sendsize = min(size, length);
+ if (size <= sendsize) {
+retry2:
+ res = sendpage(sock, page, offset, size, flags);
+ TRACE_WRITE("Final %s sid %#Lx, cid %u, res %d (page "
+ "index %lu, offset %u, size %u, cmd %p, "
+ "page %p)", (sendpage != sock_no_sendpage) ?
+ "sendpage" : "sock_no_sendpage",
+ (long long unsigned int)conn->session->sid,
+ conn->cid, res, page->index,
+ offset, size, write_cmnd, page);
+ if (unlikely(res <= 0)) {
+ if (res == -EINTR)
+ goto retry2;
+ else
+ goto out_res;
+ }
+
+ check_net_priv(ref_cmd, page);
+ if (res == size) {
+ conn->write_size = 0;
+ res = saved_size;
+ goto out_put;
+ }
+
+ offset += res;
+ size -= res;
+ goto retry2;
+ }
+
+retry1:
+ res = sendpage(sock, page, offset, sendsize, flags | MSG_MORE);
+ TRACE_WRITE("%s sid %#Lx, cid %u, res %d (page index %lu, "
+ "offset %u, sendsize %u, size %u, cmd %p, page %p)",
+ (sendpage != sock_no_sendpage) ? "sendpage" :
+ "sock_no_sendpage",
+ (unsigned long long)conn->session->sid, conn->cid,
+ res, page->index, offset, sendsize, size,
+ write_cmnd, page);
+ if (unlikely(res <= 0)) {
+ if (res == -EINTR)
+ goto retry1;
+ else
+ goto out_res;
+ }
+
+ check_net_priv(ref_cmd, page);
+
+ size -= res;
+
+ if (res == sendsize) {
+ idx++;
+ EXTRACHECKS_BUG_ON(idx >= ref_cmd->sg_cnt);
+ page = sg_page(&sg[idx]);
+ length = sg[idx].length;
+ offset = sg[idx].offset;
+ } else {
+ offset += res;
+ sendsize -= res;
+ goto retry1;
+ }
+ }
+
+out_off:
+ conn->write_offset += sg_size - size;
+
+out_iov:
+ conn->write_size = size;
+ if ((saved_size == size) && res == -EAGAIN)
+ goto out_put;
+
+ res = saved_size - size;
+
+out_put:
+ if (do_put)
+ __iscsi_put_page_callback(ref_cmd);
+
+out:
+ return res;
+
+out_res:
+ check_net_priv(ref_cmd, page);
+ if (res == -EAGAIN)
+ goto out_off;
+ /* else go through */
+
+out_err:
+#ifndef CONFIG_SCST_DEBUG
+ if (!conn->closing)
+#endif
+ {
+ PRINT_ERROR("error %d at sid:cid %#Lx:%u, cmnd %p", res,
+ (long long unsigned int)conn->session->sid,
+ conn->cid, conn->write_cmnd);
+ }
+ if (ref_cmd_to_parent &&
+ ((ref_cmd->scst_cmd != NULL) || (ref_cmd->scst_aen != NULL))) {
+ if (ref_cmd->scst_state == ISCSI_CMD_STATE_AEN)
+ scst_set_aen_delivery_status(ref_cmd->scst_aen,
+ SCST_AEN_RES_FAILED);
+ else
+ scst_set_delivery_status(ref_cmd->scst_cmd,
+ SCST_CMD_DELIVERY_FAILED);
+ }
+ goto out_put;
+}
+
+static int exit_tx(struct iscsi_conn *conn, int res)
+{
+ iscsi_extracheck_is_wr_thread(conn);
+
+ switch (res) {
+ case -EAGAIN:
+ case -ERESTARTSYS:
+ res = 0;
+ break;
+ default:
+#ifndef CONFIG_SCST_DEBUG
+ if (!conn->closing)
+#endif
+ {
+ PRINT_ERROR("Sending data failed: initiator %s, "
+ "write_size %d, write_state %d, res %d",
+ conn->session->initiator_name,
+ conn->write_size,
+ conn->write_state, res);
+ }
+ conn->write_state = TX_END;
+ conn->write_size = 0;
+ mark_conn_closed(conn);
+ break;
+ }
+ return res;
+}
+
+static int tx_ddigest(struct iscsi_cmnd *cmnd, int state)
+{
+ int res, rest = cmnd->conn->write_size;
+ struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
+ struct kvec iov;
+
+ iscsi_extracheck_is_wr_thread(cmnd->conn);
+
+ TRACE_DBG("Sending data digest %x (cmd %p)", cmnd->ddigest, cmnd);
+
+ iov.iov_base = (char *)(&cmnd->ddigest) + (sizeof(u32) - rest);
+ iov.iov_len = rest;
+
+ res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
+ if (res > 0) {
+ cmnd->conn->write_size -= res;
+ if (!cmnd->conn->write_size)
+ cmnd->conn->write_state = state;
+ } else
+ res = exit_tx(cmnd->conn, res);
+
+ return res;
+}
+
+static void init_tx_hdigest(struct iscsi_cmnd *cmnd)
+{
+ struct iscsi_conn *conn = cmnd->conn;
+ struct iovec *iop;
+
+ iscsi_extracheck_is_wr_thread(conn);
+
+ digest_tx_header(cmnd);
+
+ BUG_ON(conn->write_iop_used >=
+ (signed)(sizeof(conn->write_iov)/sizeof(conn->write_iov[0])));
+
+ iop = &conn->write_iop[conn->write_iop_used];
+ conn->write_iop_used++;
+ iop->iov_base = (void __force __user *)&(cmnd->hdigest);
+ iop->iov_len = sizeof(u32);
+ conn->write_size += sizeof(u32);
+
+ return;
+}
+
+static int tx_padding(struct iscsi_cmnd *cmnd, int state)
+{
+ int res, rest = cmnd->conn->write_size;
+ struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
+ struct kvec iov;
+ static const uint32_t padding;
+
+ iscsi_extracheck_is_wr_thread(cmnd->conn);
+
+ TRACE_DBG("Sending %d padding bytes (cmd %p)", rest, cmnd);
+
+ iov.iov_base = (char *)(&padding) + (sizeof(uint32_t) - rest);
+ iov.iov_len = rest;
+
+ res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
+ if (res > 0) {
+ cmnd->conn->write_size -= res;
+ if (!cmnd->conn->write_size)
+ cmnd->conn->write_state = state;
+ } else
+ res = exit_tx(cmnd->conn, res);
+
+ return res;
+}
+
+static int iscsi_do_send(struct iscsi_conn *conn, int state)
+{
+ int res;
+
+ iscsi_extracheck_is_wr_thread(conn);
+
+ res = write_data(conn);
+ if (res > 0) {
+ if (!conn->write_size)
+ conn->write_state = state;
+ } else
+ res = exit_tx(conn, res);
+
+ return res;
+}
+
+/*
+ * No locks, conn is wr processing.
+ *
+ * IMPORTANT! Connection conn must be protected by additional conn_get()
+ * upon entrance in this function, because otherwise it could be destroyed
+ * inside as a result of cmnd release.
+ */
+int iscsi_send(struct iscsi_conn *conn)
+{
+ struct iscsi_cmnd *cmnd = conn->write_cmnd;
+ int ddigest, res = 0;
+
+ TRACE_DBG("conn %p, write_cmnd %p", conn, cmnd);
+
+ iscsi_extracheck_is_wr_thread(conn);
+
+ ddigest = conn->ddigest_type != DIGEST_NONE ? 1 : 0;
+
+ switch (conn->write_state) {
+ case TX_INIT:
+ BUG_ON(cmnd != NULL);
+ cmnd = conn->write_cmnd = iscsi_get_send_cmnd(conn);
+ if (!cmnd)
+ goto out;
+ cmnd_tx_start(cmnd);
+ if (!(conn->hdigest_type & DIGEST_NONE))
+ init_tx_hdigest(cmnd);
+ conn->write_state = TX_BHS_DATA;
+ case TX_BHS_DATA:
+ res = iscsi_do_send(conn, cmnd->pdu.datasize ?
+ TX_INIT_PADDING : TX_END);
+ if (res <= 0 || conn->write_state != TX_INIT_PADDING)
+ break;
+ case TX_INIT_PADDING:
+ cmnd->conn->write_size = ((cmnd->pdu.datasize + 3) & -4) -
+ cmnd->pdu.datasize;
+ if (cmnd->conn->write_size != 0)
+ conn->write_state = TX_PADDING;
+ else if (ddigest)
+ conn->write_state = TX_INIT_DDIGEST;
+ else
+ conn->write_state = TX_END;
+ break;
+ case TX_PADDING:
+ res = tx_padding(cmnd, ddigest ? TX_INIT_DDIGEST : TX_END);
+ if (res <= 0 || conn->write_state != TX_INIT_DDIGEST)
+ break;
+ case TX_INIT_DDIGEST:
+ cmnd->conn->write_size = sizeof(u32);
+ conn->write_state = TX_DDIGEST;
+ case TX_DDIGEST:
+ res = tx_ddigest(cmnd, TX_END);
+ break;
+ default:
+ PRINT_CRIT_ERROR("%d %d %x", res, conn->write_state,
+ cmnd_opcode(cmnd));
+ BUG();
+ }
+
+ if (res == 0)
+ goto out;
+
+ if (conn->write_state != TX_END)
+ goto out;
+
+ if (unlikely(conn->write_size)) {
+ PRINT_CRIT_ERROR("%d %x %u", res, cmnd_opcode(cmnd),
+ conn->write_size);
+ BUG();
+ }
+ cmnd_tx_end(cmnd);
+
+ rsp_cmnd_release(cmnd);
+
+ conn->write_cmnd = NULL;
+ conn->write_state = TX_INIT;
+
+out:
+ return res;
+}
+
+/* No locks, conn is wr processing.
+ *
+ * IMPORTANT! Connection conn must be protected by additional conn_get()
+ * upon entrance in this function, because otherwise it could be destroyed
+ * inside as a result of iscsi_send(), which releases sent commands.
+ */
+static int process_write_queue(struct iscsi_conn *conn)
+{
+ int res = 0;
+
+ if (likely(test_write_ready(conn)))
+ res = iscsi_send(conn);
+ return res;
+}
+
+/*
+ * Called under iscsi_wr_lock and BHs disabled, but will drop it inside,
+ * then reaquire.
+ */
+static void scst_do_job_wr(void)
+ __acquires(&iscsi_wr_lock)
+ __releases(&iscsi_wr_lock)
+{
+
+ /*
+ * We delete/add to tail connections to maintain fairness between them.
+ */
+
+ while (!list_empty(&iscsi_wr_list)) {
+ int rc;
+ struct iscsi_conn *conn = list_entry(iscsi_wr_list.next,
+ typeof(*conn), wr_list_entry);
+
+ TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d, "
+ "write ready %d", conn, conn->wr_state,
+ conn->wr_space_ready, test_write_ready(conn));
+
+ list_del(&conn->wr_list_entry);
+
+ BUG_ON(conn->wr_state == ISCSI_CONN_WR_STATE_PROCESSING);
+
+ conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
+ conn->wr_space_ready = 0;
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->wr_task = current;
+#endif
+ spin_unlock_bh(&iscsi_wr_lock);
+
+ conn_get(conn);
+
+ rc = process_write_queue(conn);
+
+ spin_lock_bh(&iscsi_wr_lock);
+#ifdef CONFIG_SCST_EXTRACHECKS
+ conn->wr_task = NULL;
+#endif
+ if ((rc == -EAGAIN) && !conn->wr_space_ready) {
+ conn->wr_state = ISCSI_CONN_WR_STATE_SPACE_WAIT;
+ goto cont;
+ }
+
+ if (test_write_ready(conn)) {
+ list_add_tail(&conn->wr_list_entry, &iscsi_wr_list);
+ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
+ } else
+ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
+
+cont:
+ conn_put(conn);
+ }
+ return;
+}
+
+static inline int test_wr_list(void)
+{
+ int res = !list_empty(&iscsi_wr_list) ||
+ unlikely(kthread_should_stop());
+ return res;
+}
+
+int istwr(void *arg)
+{
+
+ PRINT_INFO("Write thread started, PID %d", current->pid);
+
+ current->flags |= PF_NOFREEZE;
+
+ spin_lock_bh(&iscsi_wr_lock);
+ while (!kthread_should_stop()) {
+ wait_queue_t wait;
+ init_waitqueue_entry(&wait, current);
+
+ if (!test_wr_list()) {
+ add_wait_queue_exclusive_head(&iscsi_wr_waitQ, &wait);
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (test_wr_list())
+ break;
+ spin_unlock_bh(&iscsi_wr_lock);
+ schedule();
+ spin_lock_bh(&iscsi_wr_lock);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&iscsi_wr_waitQ, &wait);
+ }
+ scst_do_job_wr();
+ }
+ spin_unlock_bh(&iscsi_wr_lock);
+
+ /*
+ * If kthread_should_stop() is true, we are guaranteed to be
+ * on the module unload, so iscsi_wr_list must be empty.
+ */
+ BUG_ON(!list_empty(&iscsi_wr_list));
+
+ PRINT_INFO("Write thread PID %d finished", current->pid);
+ return 0;
+}
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/param.c linux-2.6.33/drivers/scst/iscsi-scst/param.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/param.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/param.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2005 FUJITA Tomonori <tomof@....org>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "iscsi.h"
+#include "digest.h"
+
+#define CHECK_PARAM(info, iparams, word, min, max) \
+do { \
+ if (!(info)->partial || ((info)->partial & 1 << key_##word)) { \
+ TRACE_DBG("%s: %u", #word, (iparams)[key_##word]); \
+ if ((iparams)[key_##word] < (min) || \
+ (iparams)[key_##word] > (max)) { \
+ if ((iparams)[key_##word] < (min)) { \
+ (iparams)[key_##word] = (min); \
+ PRINT_WARNING("%s: %u is too small, resetting " \
+ "it to allowed min %u", \
+ #word, (iparams)[key_##word], (min)); \
+ } else { \
+ PRINT_WARNING("%s: %u is too big, resetting " \
+ "it to allowed max %u", \
+ #word, (iparams)[key_##word], (max)); \
+ (iparams)[key_##word] = (max); \
+ } \
+ } \
+ } \
+} while (0)
+
+#define SET_PARAM(params, info, iparams, word) \
+({ \
+ int changed = 0; \
+ if (!(info)->partial || ((info)->partial & 1 << key_##word)) { \
+ if ((params)->word != (iparams)[key_##word]) \
+ changed = 1; \
+ (params)->word = (iparams)[key_##word]; \
+ TRACE_DBG("%s set to %u", #word, (params)->word); \
+ } \
+ changed; \
+})
+
+#define GET_PARAM(params, info, iparams, word) \
+do { \
+ (iparams)[key_##word] = (params)->word; \
+} while (0)
+
+const char *iscsi_get_bool_value(int val)
+{
+ if (val)
+ return "Yes";
+ else
+ return "No";
+}
+
+const char *iscsi_get_digest_name(int val, char *res)
+{
+ int pos = 0;
+
+ if (val & DIGEST_NONE)
+ pos = sprintf(&res[pos], "%s", "None");
+
+ if (val & DIGEST_CRC32C)
+ pos += sprintf(&res[pos], "%s%s", (pos != 0) ? ", " : "",
+ "CRC32C");
+
+ if (pos == 0)
+ sprintf(&res[pos], "%s", "Unknown");
+
+ return res;
+}
+
+static void log_params(struct iscsi_sess_params *params)
+{
+ char digest_name[64];
+
+ PRINT_INFO("Negotiated parameters: InitialR2T %s, ImmediateData %s, "
+ "MaxConnections %d, MaxRecvDataSegmentLength %d, "
+ "MaxXmitDataSegmentLength %d, ",
+ iscsi_get_bool_value(params->initial_r2t),
+ iscsi_get_bool_value(params->immediate_data), params->max_connections,
+ params->max_recv_data_length, params->max_xmit_data_length);
+ PRINT_INFO(" MaxBurstLength %d, FirstBurstLength %d, "
+ "DefaultTime2Wait %d, DefaultTime2Retain %d, ",
+ params->max_burst_length, params->first_burst_length,
+ params->default_wait_time, params->default_retain_time);
+ PRINT_INFO(" MaxOutstandingR2T %d, DataPDUInOrder %s, "
+ "DataSequenceInOrder %s, ErrorRecoveryLevel %d, ",
+ params->max_outstanding_r2t,
+ iscsi_get_bool_value(params->data_pdu_inorder),
+ iscsi_get_bool_value(params->data_sequence_inorder),
+ params->error_recovery_level);
+ PRINT_INFO(" HeaderDigest %s, DataDigest %s, OFMarker %s, "
+ "IFMarker %s, OFMarkInt %d, IFMarkInt %d",
+ iscsi_get_digest_name(params->header_digest, digest_name),
+ iscsi_get_digest_name(params->data_digest, digest_name),
+ iscsi_get_bool_value(params->ofmarker),
+ iscsi_get_bool_value(params->ifmarker),
+ params->ofmarkint, params->ifmarkint);
+}
+
+/* target_mutex supposed to be locked */
+static void sess_params_check(struct iscsi_kern_params_info *info)
+{
+ int32_t *iparams = info->session_params;
+ const int max_len = ISCSI_CONN_IOV_MAX * PAGE_SIZE;
+
+ CHECK_PARAM(info, iparams, initial_r2t, 0, 1);
+ CHECK_PARAM(info, iparams, immediate_data, 0, 1);
+ CHECK_PARAM(info, iparams, max_connections, 1, 1);
+ CHECK_PARAM(info, iparams, max_recv_data_length, 512, max_len);
+ CHECK_PARAM(info, iparams, max_xmit_data_length, 512, max_len);
+ CHECK_PARAM(info, iparams, max_burst_length, 512, max_len);
+ CHECK_PARAM(info, iparams, first_burst_length, 512, max_len);
+ CHECK_PARAM(info, iparams, max_outstanding_r2t, 1, 65535);
+ CHECK_PARAM(info, iparams, error_recovery_level, 0, 0);
+ CHECK_PARAM(info, iparams, data_pdu_inorder, 0, 1);
+ CHECK_PARAM(info, iparams, data_sequence_inorder, 0, 1);
+
+ digest_alg_available(&iparams[key_header_digest]);
+ digest_alg_available(&iparams[key_data_digest]);
+
+ CHECK_PARAM(info, iparams, ofmarker, 0, 0);
+ CHECK_PARAM(info, iparams, ifmarker, 0, 0);
+
+ return;
+}
+
+/* target_mutex supposed to be locked */
+static void sess_params_set(struct iscsi_sess_params *params,
+ struct iscsi_kern_params_info *info)
+{
+ int32_t *iparams = info->session_params;
+
+ SET_PARAM(params, info, iparams, initial_r2t);
+ SET_PARAM(params, info, iparams, immediate_data);
+ SET_PARAM(params, info, iparams, max_connections);
+ SET_PARAM(params, info, iparams, max_recv_data_length);
+ SET_PARAM(params, info, iparams, max_xmit_data_length);
+ SET_PARAM(params, info, iparams, max_burst_length);
+ SET_PARAM(params, info, iparams, first_burst_length);
+ SET_PARAM(params, info, iparams, default_wait_time);
+ SET_PARAM(params, info, iparams, default_retain_time);
+ SET_PARAM(params, info, iparams, max_outstanding_r2t);
+ SET_PARAM(params, info, iparams, data_pdu_inorder);
+ SET_PARAM(params, info, iparams, data_sequence_inorder);
+ SET_PARAM(params, info, iparams, error_recovery_level);
+ SET_PARAM(params, info, iparams, header_digest);
+ SET_PARAM(params, info, iparams, data_digest);
+ SET_PARAM(params, info, iparams, ofmarker);
+ SET_PARAM(params, info, iparams, ifmarker);
+ SET_PARAM(params, info, iparams, ofmarkint);
+ SET_PARAM(params, info, iparams, ifmarkint);
+ return;
+}
+
+static void sess_params_get(struct iscsi_sess_params *params,
+ struct iscsi_kern_params_info *info)
+{
+ int32_t *iparams = info->session_params;
+
+ GET_PARAM(params, info, iparams, initial_r2t);
+ GET_PARAM(params, info, iparams, immediate_data);
+ GET_PARAM(params, info, iparams, max_connections);
+ GET_PARAM(params, info, iparams, max_recv_data_length);
+ GET_PARAM(params, info, iparams, max_xmit_data_length);
+ GET_PARAM(params, info, iparams, max_burst_length);
+ GET_PARAM(params, info, iparams, first_burst_length);
+ GET_PARAM(params, info, iparams, default_wait_time);
+ GET_PARAM(params, info, iparams, default_retain_time);
+ GET_PARAM(params, info, iparams, max_outstanding_r2t);
+ GET_PARAM(params, info, iparams, data_pdu_inorder);
+ GET_PARAM(params, info, iparams, data_sequence_inorder);
+ GET_PARAM(params, info, iparams, error_recovery_level);
+ GET_PARAM(params, info, iparams, header_digest);
+ GET_PARAM(params, info, iparams, data_digest);
+ GET_PARAM(params, info, iparams, ofmarker);
+ GET_PARAM(params, info, iparams, ifmarker);
+ GET_PARAM(params, info, iparams, ofmarkint);
+ GET_PARAM(params, info, iparams, ifmarkint);
+ return;
+}
+
+/* target_mutex supposed to be locked */
+static void tgt_params_check(struct iscsi_session *session,
+ struct iscsi_kern_params_info *info)
+{
+ int32_t *iparams = info->target_params;
+
+ CHECK_PARAM(info, iparams, queued_cmnds, MIN_NR_QUEUED_CMNDS,
+ min_t(int, MAX_NR_QUEUED_CMNDS,
+ scst_get_max_lun_commands(session->scst_sess, NO_SUCH_LUN)));
+ CHECK_PARAM(info, iparams, rsp_timeout, MIN_RSP_TIMEOUT,
+ MAX_RSP_TIMEOUT);
+ CHECK_PARAM(info, iparams, nop_in_interval, MIN_NOP_IN_INTERVAL,
+ MAX_NOP_IN_INTERVAL);
+ return;
+}
+
+/* target_mutex supposed to be locked */
+static int iscsi_tgt_params_set(struct iscsi_session *session,
+ struct iscsi_kern_params_info *info, int set)
+{
+ struct iscsi_tgt_params *params = &session->tgt_params;
+ int32_t *iparams = info->target_params;
+
+ if (set) {
+ struct iscsi_conn *conn;
+
+ tgt_params_check(session, info);
+
+ SET_PARAM(params, info, iparams, queued_cmnds);
+ SET_PARAM(params, info, iparams, rsp_timeout);
+ SET_PARAM(params, info, iparams, nop_in_interval);
+
+ PRINT_INFO("Target parameters set for session %llx: "
+ "QueuedCommands %d, Response timeout %d, Nop-In "
+ "interval %d", session->sid, params->queued_cmnds,
+ params->rsp_timeout, params->nop_in_interval);
+
+ list_for_each_entry(conn, &session->conn_list,
+ conn_list_entry) {
+ conn->rsp_timeout = session->tgt_params.rsp_timeout * HZ;
+ conn->nop_in_interval = session->tgt_params.nop_in_interval * HZ;
+ spin_lock_bh(&iscsi_rd_lock);
+ if (!conn->closing && (conn->nop_in_interval > 0)) {
+ TRACE_DBG("Schedule Nop-In work for conn %p", conn);
+ schedule_delayed_work(&conn->nop_in_delayed_work,
+ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
+ }
+ spin_unlock_bh(&iscsi_rd_lock);
+ }
+ } else {
+ GET_PARAM(params, info, iparams, queued_cmnds);
+ GET_PARAM(params, info, iparams, rsp_timeout);
+ GET_PARAM(params, info, iparams, nop_in_interval);
+ }
+
+ return 0;
+}
+
+/* target_mutex supposed to be locked */
+static int iscsi_sess_params_set(struct iscsi_session *session,
+ struct iscsi_kern_params_info *info, int set)
+{
+ struct iscsi_sess_params *params;
+
+ if (set)
+ sess_params_check(info);
+
+ params = &session->sess_params;
+
+ if (set) {
+ sess_params_set(params, info);
+ log_params(params);
+ } else
+ sess_params_get(params, info);
+
+ return 0;
+}
+
+/* target_mutex supposed to be locked */
+int iscsi_params_set(struct iscsi_target *target,
+ struct iscsi_kern_params_info *info, int set)
+{
+ int err;
+ struct iscsi_session *session;
+
+ if (info->sid == 0) {
+ PRINT_ERROR("sid must not be %d", 0);
+ err = -EINVAL;
+ goto out;
+ }
+
+ session = session_lookup(target, info->sid);
+ if (session == NULL) {
+ PRINT_ERROR("Session for sid %llx not found", info->sid);
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (set && !list_empty(&session->conn_list) &&
+ (info->params_type != key_target)) {
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (info->params_type == key_session)
+ err = iscsi_sess_params_set(session, info, set);
+ else if (info->params_type == key_target)
+ err = iscsi_tgt_params_set(session, info, set);
+ else
+ err = -EINVAL;
+
+out:
+ return err;
+}
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/session.c linux-2.6.33/drivers/scst/iscsi-scst/session.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/session.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/session.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@...istech.com>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, version 2
+ * of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "iscsi.h"
+
+/* target_mutex supposed to be locked */
+struct iscsi_session *session_lookup(struct iscsi_target *target, u64 sid)
+{
+ struct iscsi_session *session;
+
+ list_for_each_entry(session, &target->session_list,
+ session_list_entry) {
+ if (session->sid == sid)
+ return session;
+ }
+ return NULL;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int iscsi_session_alloc(struct iscsi_target *target,
+ struct iscsi_kern_session_info *info, struct iscsi_session **result)
+{
+ int err;
+ unsigned int i;
+ struct iscsi_session *session;
+ char *name = NULL;
+
+ session = kzalloc(sizeof(*session), GFP_KERNEL);
+ if (!session)
+ return -ENOMEM;
+
+ session->target = target;
+ session->sid = info->sid;
+ atomic_set(&session->active_cmds, 0);
+ session->exp_cmd_sn = info->exp_cmd_sn;
+
+ session->initiator_name = kstrdup(info->initiator_name, GFP_KERNEL);
+ if (!session->initiator_name) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ name = info->initiator_name;
+
+ INIT_LIST_HEAD(&session->conn_list);
+ INIT_LIST_HEAD(&session->pending_list);
+
+ spin_lock_init(&session->sn_lock);
+
+ spin_lock_init(&session->cmnd_data_wait_hash_lock);
+ for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++)
+ INIT_LIST_HEAD(&session->cmnd_data_wait_hash[i]);
+
+ session->next_ttt = 1;
+
+ session->scst_sess = scst_register_session(target->scst_tgt, 0,
+ name, NULL, NULL);
+ if (session->scst_sess == NULL) {
+ PRINT_ERROR("%s", "scst_register_session() failed");
+ err = -ENOMEM;
+ goto err;
+ }
+
+ scst_sess_set_tgt_priv(session->scst_sess, session);
+
+ TRACE_MGMT_DBG("Session %p created: target %p, tid %u, sid %#Lx",
+ session, target, target->tid, info->sid);
+
+ *result = session;
+ return 0;
+
+err:
+ if (session) {
+ kfree(session->initiator_name);
+ kfree(session);
+ }
+ return err;
+}
+
+/* target_mutex supposed to be locked */
+void sess_reinst_finished(struct iscsi_session *sess)
+{
+ struct iscsi_conn *c;
+
+ TRACE_MGMT_DBG("Enabling reinstate successor sess %p", sess);
+
+ BUG_ON(!sess->sess_reinstating);
+
+ list_for_each_entry(c, &sess->conn_list, conn_list_entry) {
+ conn_reinst_finished(c);
+ }
+ sess->sess_reinstating = 0;
+ return;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+int __add_session(struct iscsi_target *target,
+ struct iscsi_kern_session_info *info)
+{
+ struct iscsi_session *new_sess = NULL, *sess, *old_sess;
+ int err = 0, i;
+ union iscsi_sid sid;
+ bool reinstatement = false;
+ struct iscsi_kern_params_info *params_info;
+
+ TRACE_MGMT_DBG("Adding session SID %llx", info->sid);
+
+ err = iscsi_session_alloc(target, info, &new_sess);
+ if (err != 0)
+ goto out;
+
+ mutex_lock(&target->target_mutex);
+
+ sess = session_lookup(target, info->sid);
+ if (sess != NULL) {
+ PRINT_ERROR("Attempt to add session with existing SID %llx",
+ info->sid);
+ err = -EEXIST;
+ goto out_err_unlock;
+ }
+
+ params_info = kmalloc(sizeof(*params_info), GFP_KERNEL);
+ if (params_info == NULL) {
+ PRINT_ERROR("Unable to allocate params info (size %zd)",
+ sizeof(*params_info));
+ err = -ENOMEM;
+ goto out_err_unlock;
+ }
+
+ sid = *(union iscsi_sid *)&info->sid;
+ sid.id.tsih = 0;
+ old_sess = NULL;
+
+ /*
+ * We need to find the latest session to correctly handle
+ * multi-reinstatements
+ */
+ list_for_each_entry_reverse(sess, &target->session_list,
+ session_list_entry) {
+ union iscsi_sid i = *(union iscsi_sid *)&sess->sid;
+ i.id.tsih = 0;
+ if ((sid.id64 == i.id64) &&
+ (strcmp(info->initiator_name, sess->initiator_name) == 0)) {
+ if (!sess->sess_shutting_down) {
+ /* session reinstatement */
+ old_sess = sess;
+ }
+ break;
+ }
+ }
+ sess = NULL;
+
+ list_add_tail(&new_sess->session_list_entry, &target->session_list);
+
+ memset(params_info, 0, sizeof(*params_info));
+ params_info->tid = target->tid;
+ params_info->sid = info->sid;
+ params_info->params_type = key_session;
+ for (i = 0; i < session_key_last; i++)
+ params_info->session_params[i] = info->session_params[i];
+
+ err = iscsi_params_set(target, params_info, 1);
+ if (err != 0)
+ goto out_del;
+
+ memset(params_info, 0, sizeof(*params_info));
+ params_info->tid = target->tid;
+ params_info->sid = info->sid;
+ params_info->params_type = key_target;
+ for (i = 0; i < target_key_last; i++)
+ params_info->target_params[i] = info->target_params[i];
+
+ err = iscsi_params_set(target, params_info, 1);
+ if (err != 0)
+ goto out_del;
+
+ kfree(params_info);
+ params_info = NULL;
+
+ if (old_sess != NULL) {
+ reinstatement = true;
+
+ TRACE_MGMT_DBG("Reinstating sess %p with SID %llx (old %p, "
+ "SID %llx)", new_sess, new_sess->sid, old_sess,
+ old_sess->sid);
+
+ new_sess->sess_reinstating = 1;
+ old_sess->sess_reinst_successor = new_sess;
+
+ target_del_session(old_sess->target, old_sess, 0);
+ }
+
+ mutex_unlock(&target->target_mutex);
+
+ if (reinstatement) {
+ /*
+ * Mutex target_mgmt_mutex won't allow to add connections to
+ * the new session after target_mutex was dropped, so it's safe
+ * to replace the initial UA without it. We can't do it under
+ * target_mutex, because otherwise we can establish a
+ * circular locking dependency between target_mutex and
+ * scst_mutex in SCST core (iscsi_report_aen() called by
+ * SCST core under scst_mutex).
+ */
+ scst_set_initial_UA(new_sess->scst_sess,
+ SCST_LOAD_SENSE(scst_sense_nexus_loss_UA));
+ }
+
+out:
+ return err;
+
+out_del:
+ list_del(&new_sess->session_list_entry);
+ kfree(params_info);
+
+out_err_unlock:
+ mutex_unlock(&target->target_mutex);
+
+ scst_unregister_session(new_sess->scst_sess, 1, NULL);
+ new_sess->scst_sess = NULL;
+
+ mutex_lock(&target->target_mutex);
+ session_free(new_sess, false);
+ mutex_unlock(&target->target_mutex);
+ goto out;
+}
+
+static void __session_free(struct iscsi_session *session)
+{
+ kfree(session->initiator_name);
+ kfree(session);
+}
+
+static void iscsi_unreg_sess_done(struct scst_session *scst_sess)
+{
+ struct iscsi_session *session;
+
+ session = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
+
+ session->scst_sess = NULL;
+ __session_free(session);
+ return;
+}
+
+/* target_mutex supposed to be locked */
+int session_free(struct iscsi_session *session, bool del)
+{
+ unsigned int i;
+
+ TRACE_MGMT_DBG("Freeing session %p (SID %llx)",
+ session, session->sid);
+
+ BUG_ON(!list_empty(&session->conn_list));
+ if (unlikely(atomic_read(&session->active_cmds) != 0)) {
+ PRINT_CRIT_ERROR("active_cmds not 0 (%d)!!",
+ atomic_read(&session->active_cmds));
+ BUG();
+ }
+
+ for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++)
+ BUG_ON(!list_empty(&session->cmnd_data_wait_hash[i]));
+
+ if (session->sess_reinst_successor != NULL)
+ sess_reinst_finished(session->sess_reinst_successor);
+
+ if (session->sess_reinstating) {
+ struct iscsi_session *s;
+ TRACE_MGMT_DBG("Freeing being reinstated sess %p", session);
+ list_for_each_entry(s, &session->target->session_list,
+ session_list_entry) {
+ if (s->sess_reinst_successor == session) {
+ s->sess_reinst_successor = NULL;
+ break;
+ }
+ }
+ }
+
+ if (del)
+ list_del(&session->session_list_entry);
+
+ if (session->scst_sess != NULL) {
+ /*
+ * We must NOT call scst_unregister_session() in the waiting
+ * mode, since we are under target_mutex. Otherwise we can
+ * establish a circular locking dependency between target_mutex
+ * and scst_mutex in SCST core (iscsi_report_aen() called by
+ * SCST core under scst_mutex).
+ */
+ scst_unregister_session(session->scst_sess, 0,
+ iscsi_unreg_sess_done);
+ } else
+ __session_free(session);
+
+ return 0;
+}
+
+/* target_mutex supposed to be locked */
+int __del_session(struct iscsi_target *target, u64 sid)
+{
+ struct iscsi_session *session;
+
+ session = session_lookup(target, sid);
+ if (!session)
+ return -ENOENT;
+
+ if (!list_empty(&session->conn_list)) {
+ PRINT_ERROR("%llu still have connections",
+ (long long unsigned int)session->sid);
+ return -EBUSY;
+ }
+
+ return session_free(session, true);
+}
+
+#define ISCSI_SESS_BOOL_PARAM_ATTR(name, exported_name) \
+static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+{ \
+ int pos; \
+ struct scst_session *scst_sess; \
+ struct iscsi_session *sess; \
+ \
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
+ \
+ pos = sprintf(buf, "%s\n", \
+ iscsi_get_bool_value(sess->sess_params.name)); \
+ \
+ return pos; \
+} \
+ \
+static struct kobj_attribute iscsi_sess_attr_##name = \
+ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
+
+#define ISCSI_SESS_INT_PARAM_ATTR(name, exported_name) \
+static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+{ \
+ int pos; \
+ struct scst_session *scst_sess; \
+ struct iscsi_session *sess; \
+ \
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
+ \
+ pos = sprintf(buf, "%d\n", sess->sess_params.name); \
+ \
+ return pos; \
+} \
+ \
+static struct kobj_attribute iscsi_sess_attr_##name = \
+ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
+
+#define ISCSI_SESS_DIGEST_PARAM_ATTR(name, exported_name) \
+static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+{ \
+ int pos; \
+ struct scst_session *scst_sess; \
+ struct iscsi_session *sess; \
+ char digest_name[64]; \
+ \
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
+ \
+ pos = sprintf(buf, "%s\n", iscsi_get_digest_name( \
+ sess->sess_params.name, digest_name)); \
+ \
+ return pos; \
+} \
+ \
+static struct kobj_attribute iscsi_sess_attr_##name = \
+ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
+
+ISCSI_SESS_BOOL_PARAM_ATTR(initial_r2t, InitialR2T);
+ISCSI_SESS_BOOL_PARAM_ATTR(immediate_data, ImmediateData);
+ISCSI_SESS_INT_PARAM_ATTR(max_recv_data_length, MaxRecvDataSegmentLength);
+ISCSI_SESS_INT_PARAM_ATTR(max_xmit_data_length, MaxXmitDataSegmentLength);
+ISCSI_SESS_INT_PARAM_ATTR(max_burst_length, MaxBurstLength);
+ISCSI_SESS_INT_PARAM_ATTR(first_burst_length, FirstBurstLength);
+ISCSI_SESS_INT_PARAM_ATTR(max_outstanding_r2t, MaxOutstandingR2T);
+ISCSI_SESS_DIGEST_PARAM_ATTR(header_digest, HeaderDigest);
+ISCSI_SESS_DIGEST_PARAM_ATTR(data_digest, DataDigest);
+
+static ssize_t iscsi_sess_sid_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct scst_session *scst_sess;
+ struct iscsi_session *sess;
+
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
+
+ pos = sprintf(buf, "%llx\n", sess->sid);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_attr_sess_sid =
+ __ATTR(sid, S_IRUGO, iscsi_sess_sid_show, NULL);
+
+static ssize_t iscsi_sess_reinstating_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct scst_session *scst_sess;
+ struct iscsi_session *sess;
+
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
+
+ pos = sprintf(buf, "%d\n", sess->sess_reinstating ? 1 : 0);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_sess_attr_reinstating =
+ __ATTR(reinstating, S_IRUGO, iscsi_sess_reinstating_show, NULL);
+
+static ssize_t iscsi_sess_force_close_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int res;
+ struct scst_session *scst_sess;
+ struct iscsi_session *sess;
+ struct iscsi_conn *conn;
+
+ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
+ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
+
+ if (mutex_lock_interruptible(&sess->target->target_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ PRINT_INFO("Deleting session %llu with initiator %s (%p)",
+ (long long unsigned int)sess->sid, sess->initiator_name, sess);
+
+ list_for_each_entry(conn, &sess->conn_list, conn_list_entry) {
+ TRACE_MGMT_DBG("Deleting connection with initiator %p", conn);
+ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE|ISCSI_CONN_DELETING);
+ }
+
+ mutex_unlock(&sess->target->target_mutex);
+
+ res = count;
+
+out:
+ return res;
+}
+
+static struct kobj_attribute iscsi_sess_attr_force_close =
+ __ATTR(force_close, S_IWUSR, NULL, iscsi_sess_force_close_store);
+
+const struct attribute *iscsi_sess_attrs[] = {
+ &iscsi_sess_attr_initial_r2t.attr,
+ &iscsi_sess_attr_immediate_data.attr,
+ &iscsi_sess_attr_max_recv_data_length.attr,
+ &iscsi_sess_attr_max_xmit_data_length.attr,
+ &iscsi_sess_attr_max_burst_length.attr,
+ &iscsi_sess_attr_first_burst_length.attr,
+ &iscsi_sess_attr_max_outstanding_r2t.attr,
+ &iscsi_sess_attr_header_digest.attr,
+ &iscsi_sess_attr_data_digest.attr,
+ &iscsi_attr_sess_sid.attr,
+ &iscsi_sess_attr_reinstating.attr,
+ &iscsi_sess_attr_force_close.attr,
+ NULL,
+};
+
diff -uprN orig/linux-2.6.33/drivers/scst/iscsi-scst/target.c linux-2.6.33/drivers/scst/iscsi-scst/target.c
--- orig/linux-2.6.33/drivers/scst/iscsi-scst/target.c
+++ linux-2.6.33/drivers/scst/iscsi-scst/target.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@...istech.com>
+ * Copyright (C) 2007 - 2010 Vladislav Bolkhovitin
+ * Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, version 2
+ * of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+
+#include "iscsi.h"
+#include "digest.h"
+
+#define MAX_NR_TARGETS (1UL << 30)
+
+DEFINE_MUTEX(target_mgmt_mutex);
+
+/* All 3 protected by target_mgmt_mutex */
+static LIST_HEAD(target_list);
+static u32 next_target_id;
+static u32 nr_targets;
+
+/* target_mgmt_mutex supposed to be locked */
+struct iscsi_target *target_lookup_by_id(u32 id)
+{
+ struct iscsi_target *target;
+
+ list_for_each_entry(target, &target_list, target_list_entry) {
+ if (target->tid == id)
+ return target;
+ }
+ return NULL;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static struct iscsi_target *target_lookup_by_name(const char *name)
+{
+ struct iscsi_target *target;
+
+ list_for_each_entry(target, &target_list, target_list_entry) {
+ if (!strcmp(target->name, name))
+ return target;
+ }
+ return NULL;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+static int iscsi_target_create(struct iscsi_kern_target_info *info, u32 tid,
+ struct iscsi_target **out_target)
+{
+ int err = -EINVAL, len;
+ char *name = info->name;
+ struct iscsi_target *target;
+
+ TRACE_MGMT_DBG("Creating target tid %u, name %s", tid, name);
+
+ len = strlen(name);
+ if (!len) {
+ PRINT_ERROR("The length of the target name is zero %u", tid);
+ goto out;
+ }
+
+ if (!try_module_get(THIS_MODULE)) {
+ PRINT_ERROR("Fail to get module %u", tid);
+ goto out;
+ }
+
+ target = kzalloc(sizeof(*target), GFP_KERNEL);
+ if (!target) {
+ err = -ENOMEM;
+ goto out_put;
+ }
+
+ target->tid = info->tid = tid;
+
+ strlcpy(target->name, name, sizeof(target->name));
+
+ mutex_init(&target->target_mutex);
+ INIT_LIST_HEAD(&target->session_list);
+ INIT_LIST_HEAD(&target->attrs_list);
+
+ target->scst_tgt = scst_register_target(&iscsi_template, target->name);
+ if (!target->scst_tgt) {
+ PRINT_ERROR("%s", "scst_register_target() failed");
+ err = -EBUSY;
+ goto out_free;
+ }
+
+ scst_tgt_set_tgt_priv(target->scst_tgt, target);
+
+ list_add_tail(&target->target_list_entry, &target_list);
+
+ *out_target = target;
+
+ return 0;
+
+out_free:
+ kfree(target);
+
+out_put:
+ module_put(THIS_MODULE);
+
+out:
+ return err;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+int __add_target(struct iscsi_kern_target_info *info)
+{
+ int err;
+ u32 tid = info->tid;
+ struct iscsi_target *target;
+ struct iscsi_kern_params_info *params_info;
+ struct iscsi_kern_attr *attr_info;
+ union add_info_union {
+ struct iscsi_kern_params_info params_info;
+ struct iscsi_kern_attr attr_info;
+ } *add_info;
+ int i, rc;
+ unsigned long attrs_ptr_long;
+ struct iscsi_kern_attr __user *attrs_ptr;
+
+ if (nr_targets > MAX_NR_TARGETS) {
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (target_lookup_by_name(info->name)) {
+ PRINT_ERROR("Target %s already exist!", info->name);
+ err = -EEXIST;
+ goto out;
+ }
+
+ if (tid && target_lookup_by_id(tid)) {
+ PRINT_ERROR("Target %u already exist!", tid);
+ err = -EEXIST;
+ goto out;
+ }
+
+ add_info = kmalloc(sizeof(*add_info), GFP_KERNEL);
+ if (add_info == NULL) {
+ PRINT_ERROR("Unable to allocate additional info (size %zd)",
+ sizeof(*add_info));
+ err = -ENOMEM;
+ goto out;
+ }
+ params_info = (struct iscsi_kern_params_info *)add_info;
+ attr_info = (struct iscsi_kern_attr *)add_info;
+
+ if (tid == 0) {
+ do {
+ if (!++next_target_id)
+ ++next_target_id;
+ } while (target_lookup_by_id(next_target_id));
+
+ tid = next_target_id;
+ }
+
+ err = iscsi_target_create(info, tid, &target);
+ if (err != 0)
+ goto out_free;
+
+ nr_targets++;
+
+ mutex_lock(&target->target_mutex);
+
+ attrs_ptr_long = info->attrs_ptr;
+ attrs_ptr = (struct iscsi_kern_attr __user *)attrs_ptr_long;
+ for (i = 0; i < info->attrs_num; i++) {
+ memset(attr_info, 0, sizeof(*attr_info));
+
+ rc = copy_from_user(attr_info, attrs_ptr, sizeof(*attr_info));
+ if (rc != 0) {
+ PRINT_ERROR("Failed to copy users of target %s "
+ "failed", info->name);
+ err = -EFAULT;
+ goto out_del_unlock;
+ }
+
+ attr_info->name[sizeof(attr_info->name)-1] = '\0';
+
+ err = iscsi_add_attr(target, attr_info);
+ if (err != 0)
+ goto out_del_unlock;
+
+ attrs_ptr++;
+ }
+
+ mutex_unlock(&target->target_mutex);
+
+ err = tid;
+
+out_free:
+ kfree(add_info);
+
+out:
+ return err;
+
+out_del_unlock:
+ mutex_unlock(&target->target_mutex);
+ __del_target(tid);
+ goto out_free;
+}
+
+static void target_destroy(struct iscsi_target *target)
+{
+ struct iscsi_attr *attr, *t;
+
+ TRACE_MGMT_DBG("Destroying target tid %u", target->tid);
+
+ list_for_each_entry_safe(attr, t, &target->attrs_list,
+ attrs_list_entry) {
+ __iscsi_del_attr(target, attr);
+ }
+
+ scst_unregister_target(target->scst_tgt);
+
+ kfree(target);
+
+ module_put(THIS_MODULE);
+ return;
+}
+
+/* target_mgmt_mutex supposed to be locked */
+int __del_target(u32 id)
+{
+ struct iscsi_target *target;
+ int err;
+
+ target = target_lookup_by_id(id);
+ if (!target) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ mutex_lock(&target->target_mutex);
+
+ if (!list_empty(&target->session_list)) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ list_del(&target->target_list_entry);
+ nr_targets--;
+
+ mutex_unlock(&target->target_mutex);
+
+ target_destroy(target);
+ return 0;
+
+out_unlock:
+ mutex_unlock(&target->target_mutex);
+
+out:
+ return err;
+}
+
+/* target_mutex supposed to be locked */
+void target_del_session(struct iscsi_target *target,
+ struct iscsi_session *session, int flags)
+{
+
+ TRACE_MGMT_DBG("Deleting session %p", session);
+
+ if (!list_empty(&session->conn_list)) {
+ struct iscsi_conn *conn, *tc;
+ list_for_each_entry_safe(conn, tc, &session->conn_list,
+ conn_list_entry) {
+ TRACE_MGMT_DBG("Mark conn %p closing", conn);
+ __mark_conn_closed(conn, flags);
+ }
+ } else {
+ TRACE_MGMT_DBG("Freeing session %p without connections",
+ session);
+ __del_session(target, session->sid);
+ }
+ return;
+}
+
+/* target_mutex supposed to be locked */
+void target_del_all_sess(struct iscsi_target *target, int flags)
+{
+ struct iscsi_session *session, *ts;
+
+ if (!list_empty(&target->session_list)) {
+ TRACE_MGMT_DBG("Deleting all sessions from target %p", target);
+ list_for_each_entry_safe(session, ts, &target->session_list,
+ session_list_entry) {
+ target_del_session(target, session, flags);
+ }
+ }
+ return;
+}
+
+void target_del_all(void)
+{
+ struct iscsi_target *target, *t;
+ bool first = true;
+
+ TRACE_MGMT_DBG("%s", "Deleting all targets");
+
+ /* Not the best, ToDo */
+ while (1) {
+ mutex_lock(&target_mgmt_mutex);
+
+ if (list_empty(&target_list))
+ break;
+
+ /*
+ * In the first iteration we won't delete targets to go at
+ * first through all sessions of all targets and close their
+ * connections. Otherwise we can stuck for noticeable time
+ * waiting during a target's unregistration for the activities
+ * suspending over active connection. This can especially got
+ * bad if any being wait connection itself stuck waiting for
+ * something and can be recovered only by connection close.
+ * Let's for such cases not wait while such connection recover
+ * theyself, but act in advance.
+ */
+
+ list_for_each_entry_safe(target, t, &target_list,
+ target_list_entry) {
+ mutex_lock(&target->target_mutex);
+
+ if (!list_empty(&target->session_list)) {
+ target_del_all_sess(target,
+ ISCSI_CONN_ACTIVE_CLOSE |
+ ISCSI_CONN_DELETING);
+ } else if (!first) {
+ TRACE_MGMT_DBG("Deleting target %p", target);
+ list_del(&target->target_list_entry);
+ nr_targets--;
+ mutex_unlock(&target->target_mutex);
+ target_destroy(target);
+ continue;
+ }
+
+ mutex_unlock(&target->target_mutex);
+ }
+ mutex_unlock(&target_mgmt_mutex);
+ msleep(100);
+
+ first = false;
+ }
+
+ mutex_unlock(&target_mgmt_mutex);
+
+ TRACE_MGMT_DBG("%s", "Deleting all targets finished");
+ return;
+}
+
+static ssize_t iscsi_tgt_tid_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos;
+ struct scst_tgt *scst_tgt;
+ struct iscsi_target *tgt;
+
+ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+ tgt = (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
+
+ pos = sprintf(buf, "%u\n", tgt->tid);
+ return pos;
+}
+
+static struct kobj_attribute iscsi_tgt_attr_tid =
+ __ATTR(tid, S_IRUGO, iscsi_tgt_tid_show, NULL);
+
+const struct attribute *iscsi_tgt_attrs[] = {
+ &iscsi_tgt_attr_tid.attr,
+ NULL,
+};
+
+ssize_t iscsi_sysfs_send_event(uint32_t tid, enum iscsi_kern_event_code code,
+ const char *param1, const char *param2, void **data)
+{
+ int res;
+ struct scst_sysfs_user_info *info;
+
+ if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN) {
+ PRINT_ERROR("%s", "User space process not connected");
+ res = -EPERM;
+ goto out;
+ }
+
+ res = scst_sysfs_user_add_info(&info);
+ if (res != 0)
+ goto out;
+
+ TRACE_DBG("Sending event %d (tid %d, param1 %s, param2 %s, cookie %d, "
+ "info %p)", tid, code, param1, param2, info->info_cookie, info);
+
+ res = event_send(tid, 0, 0, info->info_cookie, code, param1, param2);
+ if (res <= 0) {
+ PRINT_ERROR("event_send() failed: %d", res);
+ if (res == 0)
+ res = -EFAULT;
+ goto out_free;
+ }
+
+ /*
+ * It may wait 30 secs in blocking connect to an unreacheable
+ * iSNS server. It must be fixed, but not now. ToDo.
+ */
+ res = scst_wait_info_completion(info, 31 * HZ);
+
+ if (data != NULL)
+ *data = info->data;
+
+out_free:
+ scst_sysfs_user_del_info(info);
+
+out:
+ return res;
+}
+
+int iscsi_enable_target(struct scst_tgt *scst_tgt, bool enable)
+{
+ struct iscsi_target *tgt =
+ (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
+ int res;
+ uint32_t type;
+
+ if (enable)
+ type = E_ENABLE_TARGET;
+ else
+ type = E_DISABLE_TARGET;
+
+ TRACE_DBG("%s target %d", enable ? "Enabling" : "Disabling", tgt->tid);
+
+ res = iscsi_sysfs_send_event(tgt->tid, type, NULL, NULL, NULL);
+ return res;
+}
+
+bool iscsi_is_target_enabled(struct scst_tgt *scst_tgt)
+{
+ struct iscsi_target *tgt =
+ (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
+
+ return tgt->tgt_enabled;
+}
+
+ssize_t iscsi_sysfs_add_target(const char *target_name, char *params)
+{
+ int res;
+
+ res = iscsi_sysfs_send_event(0, E_ADD_TARGET, target_name,
+ params, NULL);
+ if (res > 0) {
+ /* It's tid */
+ res = 0;
+ }
+ return res;
+}
+
+ssize_t iscsi_sysfs_del_target(const char *target_name)
+{
+ int res = 0, tid;
+
+ /* We don't want to have tgt visible after the mutex unlock */
+ {
+ struct iscsi_target *tgt;
+ mutex_lock(&target_mgmt_mutex);
+ tgt = target_lookup_by_name(target_name);
+ if (tgt == NULL) {
+ PRINT_ERROR("Target %s not found", target_name);
+ mutex_unlock(&target_mgmt_mutex);
+ res = -ENOENT;
+ goto out;
+ }
+ tid = tgt->tid;
+ mutex_unlock(&target_mgmt_mutex);
+ }
+
+ TRACE_DBG("Deleting target %s (tid %d)", target_name, tid);
+
+ res = iscsi_sysfs_send_event(tid, E_DEL_TARGET, NULL, NULL, NULL);
+
+out:
+ return res;
+}
+
+ssize_t iscsi_sysfs_mgmt_cmd(char *cmd)
+{
+ int res;
+
+ TRACE_DBG("Sending mgmt cmd %s", cmd);
+
+ res = iscsi_sysfs_send_event(0, E_MGMT_CMD, cmd, NULL, NULL);
+ return res;
+}
+
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists