From: Vladislav B. <vs...@vl...> - 2010-04-13 13:11:57
|
This patch contains iSCSI-SCST's implementation files. Signed-off-by: Vladislav Bolkhovitin <vs...@vl...> --- 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 <to...@ac...> + * 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 <ro...@ar...> + * 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 + * <arn...@xi...> + * 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 <to...@ac...> + * 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 <ro...@ar...> + * 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_scs... [truncated message content] |