UBUNTU: ubuntu: iscsitarget --- version 1.4.19
authorManoj Iyer <manoj.iyer@canonical.com>
Fri, 4 Dec 2009 01:54:20 +0000 (19:54 -0600)
committerLeann Ogasawara <leann.ogasawara@canonical.com>
Mon, 28 Mar 2011 13:48:22 +0000 (06:48 -0700)
BugLink: http://bugs.launchpad.net/bugs/494693

ExternalDriver: iscsitarget
Description: iSCSI storage system on Linux
Url: svn://svn.berlios.de/iscsitarget/trunk
Mask:
Version: 1.4.19

Use filemap_write_and_wait_range() in place of sync_page_range().
This appears to better preserve the original semantics.

Signed-off-by: Manoj Iyer <manoj.iyer@canonical.com>
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>

29 files changed:
ubuntu/Kconfig
ubuntu/Makefile
ubuntu/iscsitarget/BOM [new file with mode: 0644]
ubuntu/iscsitarget/Kconfig [new file with mode: 0644]
ubuntu/iscsitarget/Makefile [new file with mode: 0644]
ubuntu/iscsitarget/block-io.c [new file with mode: 0644]
ubuntu/iscsitarget/config.c [new file with mode: 0644]
ubuntu/iscsitarget/conn.c [new file with mode: 0644]
ubuntu/iscsitarget/digest.c [new file with mode: 0644]
ubuntu/iscsitarget/digest.h [new file with mode: 0644]
ubuntu/iscsitarget/event.c [new file with mode: 0644]
ubuntu/iscsitarget/file-io.c [new file with mode: 0644]
ubuntu/iscsitarget/include/iet_u.h [new file with mode: 0644]
ubuntu/iscsitarget/iotype.c [new file with mode: 0644]
ubuntu/iscsitarget/iotype.h [new file with mode: 0644]
ubuntu/iscsitarget/iscsi.c [new file with mode: 0644]
ubuntu/iscsitarget/iscsi.h [new file with mode: 0644]
ubuntu/iscsitarget/iscsi_dbg.h [new file with mode: 0644]
ubuntu/iscsitarget/iscsi_hdr.h [new file with mode: 0644]
ubuntu/iscsitarget/nthread.c [new file with mode: 0644]
ubuntu/iscsitarget/null-io.c [new file with mode: 0644]
ubuntu/iscsitarget/param.c [new file with mode: 0644]
ubuntu/iscsitarget/session.c [new file with mode: 0644]
ubuntu/iscsitarget/target.c [new file with mode: 0644]
ubuntu/iscsitarget/target_disk.c [new file with mode: 0644]
ubuntu/iscsitarget/tio.c [new file with mode: 0644]
ubuntu/iscsitarget/ua.c [new file with mode: 0644]
ubuntu/iscsitarget/volume.c [new file with mode: 0644]
ubuntu/iscsitarget/wthread.c [new file with mode: 0644]

index 3e5a87a..5d5815c 100644 (file)
@@ -27,6 +27,10 @@ source "ubuntu/omnibook/Kconfig"
 ##
 ##
 ##
+source "ubuntu/iscsitarget/Kconfig"
+##
+##
+##
 ##
 ##
 ##
index 53f5412..7bf5c55 100644 (file)
@@ -29,6 +29,10 @@ obj-$(CONFIG_OMNIBOOK)               += omnibook/
 ##
 ##
 ##
+obj-$(CONFIG_SCSI_ISCSITARGET) += iscsitarget/
+##
+##
+##
 ##
 ##
 ##
diff --git a/ubuntu/iscsitarget/BOM b/ubuntu/iscsitarget/BOM
new file mode 100644 (file)
index 0000000..cda1135
--- /dev/null
@@ -0,0 +1,2 @@
+Downloaded from:       svn://svn.berlios.de/iscsitarget/trunk
+Current Version:       1.4.19
diff --git a/ubuntu/iscsitarget/Kconfig b/ubuntu/iscsitarget/Kconfig
new file mode 100644 (file)
index 0000000..e5964dc
--- /dev/null
@@ -0,0 +1,3 @@
+config SCSI_ISCSITARGET
+       tristate "iSCSI Target Driver"
+       depends on SCSI
diff --git a/ubuntu/iscsitarget/Makefile b/ubuntu/iscsitarget/Makefile
new file mode 100644 (file)
index 0000000..727c706
--- /dev/null
@@ -0,0 +1,17 @@
+#
+# Makefile for the Linux kernel device drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (not a .c file).
+#
+# Note 2! The CFLAGS definitions are now in the main makefile.
+
+EXTRA_CFLAGS += -I$(src)/include 
+
+obj-m          += iscsi_trgt.o
+iscsi_trgt-objs        := tio.o iscsi.o nthread.o wthread.o config.o digest.o \
+                       conn.o session.o target.o volume.o iotype.o \
+                       file-io.o null-io.o target_disk.o event.o param.o \
+                       block-io.o ua.o
+
diff --git a/ubuntu/iscsitarget/block-io.c b/ubuntu/iscsitarget/block-io.c
new file mode 100644 (file)
index 0000000..708f101
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * Target device block I/O.
+ *
+ * Based on file I/O driver from FUJITA Tomonori
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * (C) 2006 Andre Brinkmann <brinkman at hni dot upb dot de>
+ * (C) 2007 Ross Walker <rswwalker at hotmail dot com>
+ * (C) 2007 Ming Zhang <blackmagic02881 at gmail dot com>
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/types.h>
+#include <linux/blkdev.h>
+#include <linux/parser.h>
+#include <linux/buffer_head.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+struct blockio_data {
+       char *path;
+       struct block_device *bdev;
+};
+
+struct tio_work {
+       atomic_t error;
+       atomic_t bios_remaining;
+       struct completion tio_complete;
+};
+
+static void blockio_bio_endio(struct bio *bio, int error)
+{
+       struct tio_work *tio_work = bio->bi_private;
+
+       error = test_bit(BIO_UPTODATE, &bio->bi_flags) ? error : -EIO;
+
+       if (error)
+               atomic_set(&tio_work->error, error);
+
+       /* If last bio signal completion */
+       if (atomic_dec_and_test(&tio_work->bios_remaining))
+               complete(&tio_work->tio_complete);
+
+       bio_put(bio);
+}
+
+/*
+ * Blockio_make_request(): The function translates an iscsi-request into
+ * a number of requests to the corresponding block device.
+ */
+static int
+blockio_make_request(struct iet_volume *volume, struct tio *tio, int rw)
+{
+       struct blockio_data *bio_data = volume->private;
+       struct request_queue *bdev_q = bdev_get_queue(bio_data->bdev);
+       struct tio_work *tio_work;
+       struct bio *tio_bio = NULL, *bio = NULL, *biotail = NULL;
+
+       u32 offset = tio->offset;
+       u32 size = tio->size;
+       u32 tio_index = 0;
+
+       int max_pages = 1;
+       int err = 0;
+
+       loff_t ppos = ((loff_t) tio->idx << PAGE_SHIFT) + offset;
+
+       /* Calculate max_pages for bio_alloc (memory saver) */
+       if (bdev_q)
+               max_pages = bio_get_nr_vecs(bio_data->bdev);
+
+       tio_work = kzalloc(sizeof (*tio_work), GFP_KERNEL);
+       if (!tio_work)
+               return -ENOMEM;
+
+       atomic_set(&tio_work->error, 0);
+       atomic_set(&tio_work->bios_remaining, 0);
+       init_completion(&tio_work->tio_complete);
+
+       /* Main processing loop, allocate and fill all bios */
+       while (tio_index < tio->pg_cnt) {
+               bio = bio_alloc(GFP_KERNEL, min(max_pages, BIO_MAX_PAGES));
+               if (!bio) {
+                       err = -ENOMEM;
+                       goto out;
+               }
+
+               bio->bi_sector = ppos >> volume->blk_shift;
+               bio->bi_bdev = bio_data->bdev;
+               bio->bi_end_io = blockio_bio_endio;
+               bio->bi_private = tio_work;
+
+               if (tio_bio)
+                       biotail = biotail->bi_next = bio;
+               else
+                       tio_bio = biotail = bio;
+
+               atomic_inc(&tio_work->bios_remaining);
+
+               /* Loop for filling bio */
+               while (tio_index < tio->pg_cnt) {
+                       unsigned int bytes = PAGE_SIZE - offset;
+
+                       if (bytes > size)
+                               bytes = size;
+
+                       if (!bio_add_page(bio, tio->pvec[tio_index], bytes, offset))
+                               break;
+
+                       size -= bytes;
+                       ppos += bytes;
+
+                       offset = 0;
+
+                       tio_index++;
+               }
+       }
+
+       /* Walk the list, submitting bios 1 by 1 */
+       while (tio_bio) {
+               bio = tio_bio;
+               tio_bio = tio_bio->bi_next;
+               bio->bi_next = NULL;
+
+               submit_bio(rw, bio);
+       }
+
+       if (bdev_q && bdev_q->unplug_fn)
+               bdev_q->unplug_fn(bdev_q);
+
+       wait_for_completion(&tio_work->tio_complete);
+
+       err = atomic_read(&tio_work->error);
+
+       kfree(tio_work);
+
+       return err;
+out:
+       while (tio_bio) {
+               bio = tio_bio;
+               tio_bio = tio_bio->bi_next;
+
+               bio_put(bio);
+       }
+
+       kfree(tio_work);
+
+       return err;
+}
+
+static int
+blockio_open_path(struct iet_volume *volume, const char *path)
+{
+       struct blockio_data *bio_data = volume->private;
+       struct block_device *bdev;
+       int flags = FMODE_READ | (LUReadonly(volume) ? 0 : FMODE_WRITE);
+       int err = 0;
+
+       bio_data->path = kstrdup(path, GFP_KERNEL);
+       if (!bio_data->path)
+               return -ENOMEM;
+
+       bdev = open_bdev_exclusive(path, flags, THIS_MODULE);
+       if (IS_ERR(bdev)) {
+               err = PTR_ERR(bdev);
+               eprintk("Can't open device %s, error %d\n", path, err);
+               bio_data->bdev = NULL;
+       } else {
+               bio_data->bdev = bdev;
+               fsync_bdev(bio_data->bdev);
+       }
+
+       return err;
+}
+
+static int
+set_scsiid(struct iet_volume *volume, const char *id)
+{
+       size_t len;
+
+       if ((len = strlen(id)) > SCSI_ID_LEN - VENDOR_ID_LEN) {
+               eprintk("SCSI ID too long, %zd provided, %u max\n", len,
+                       SCSI_ID_LEN - VENDOR_ID_LEN);
+               return -EINVAL;
+       }
+
+       memcpy(volume->scsi_id + VENDOR_ID_LEN, id, len);
+
+       return 0;
+}
+
+static void
+gen_scsiid(struct iet_volume *volume, struct inode *inode)
+{
+       int i;
+       u32 *p;
+
+       strlcpy(volume->scsi_id, VENDOR_ID, VENDOR_ID_LEN);
+
+       for (i = VENDOR_ID_LEN; i < SCSI_ID_LEN; i++)
+               if (volume->scsi_id[i])
+                       return;
+
+       /* If a scsi id doesn't exist generate a 16 byte one:
+        * Bytes   1-4: target type
+        * Bytes   5-8: target id
+        * Bytes  9-12: inode number
+        * Bytes 13-16: device type
+        */
+       p = (u32 *) (volume->scsi_id + VENDOR_ID_LEN);
+       *(p + 0) = volume->target->trgt_param.target_type;
+       *(p + 1) = volume->target->tid;
+       *(p + 2) = volume->lun;
+       *(p + 3) = (unsigned int) inode->i_sb->s_dev;
+}
+
+static int
+set_scsisn(struct iet_volume *volume, const char *sn)
+{
+       size_t len;
+
+       if ((len = strlen(sn)) > SCSI_SN_LEN) {
+               eprintk("SCSI SN too long, %zd provided, %u max\n", len,
+                       SCSI_SN_LEN);
+               return -EINVAL;
+       }
+
+       memcpy(volume->scsi_sn, sn, len);
+
+       return 0;
+}
+
+/* Create an enumeration of our accepted actions */
+enum
+{
+       Opt_scsiid, Opt_scsisn, Opt_path, Opt_ignore, Opt_err,
+};
+
+/* Create a match table using our action enums and their matching options */
+static match_table_t tokens = {
+       {Opt_scsiid, "ScsiId=%s"},
+       {Opt_scsisn, "ScsiSN=%s"},
+       {Opt_path, "Path=%s"},
+       {Opt_ignore, "Type=%s"},
+       {Opt_ignore, "IOMode=%s"},
+       {Opt_err, NULL},
+};
+
+static int
+parse_blockio_params(struct iet_volume *volume, char *params)
+{
+       struct blockio_data *info = volume->private;
+       int err = 0;
+       char *p, *q;
+
+       /* Loop through parameters separated by commas, look up our
+        * parameter in match table, return enumeration and arguments
+        * select case based on the returned enum and run the action */
+       while ((p = strsep(&params, ",")) != NULL) {
+               substring_t args[MAX_OPT_ARGS];
+               int token;
+               if (!*p)
+                       continue;
+               token = match_token(p, tokens, args);
+               switch (token) {
+               case Opt_scsiid:
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = set_scsiid(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_scsisn:
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = set_scsisn(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_path:
+                       if (info->path) {
+                               iprintk("Target %s, LUN %u: "
+                                       "duplicate \"Path\" param\n",
+                                       volume->target->name, volume->lun);
+                               err = -EINVAL;
+                               goto out;
+                       }
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = blockio_open_path(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_ignore:
+                       break;
+               default:
+                       iprintk("Target %s, LUN %u: unknown param %s\n",
+                               volume->target->name, volume->lun, p);
+                       return -EINVAL;
+               }
+       }
+
+       if (!info->path) {
+               iprintk("Target %s, LUN %u: missing \"Path\" param\n",
+                       volume->target->name, volume->lun);
+               err = -EINVAL;
+       }
+  out:
+       return err;
+}
+
+static void
+blockio_detach(struct iet_volume *volume)
+{
+       struct blockio_data *bio_data = volume->private;
+       int flags = FMODE_READ | (LUReadonly(volume) ? 0 : FMODE_WRITE);
+
+       if (bio_data->bdev)
+               close_bdev_exclusive(bio_data->bdev, flags);
+       kfree(bio_data->path);
+
+       kfree(volume->private);
+}
+
+static int
+blockio_attach(struct iet_volume *volume, char *args)
+{
+       struct blockio_data *bio_data;
+       int err = 0;
+
+       if (volume->private) {
+               eprintk("Lun %u already attached on Target %s \n",
+                       volume->lun, volume->target->name);
+               return -EBUSY;
+       }
+
+       bio_data = kzalloc(sizeof (*bio_data), GFP_KERNEL);
+       if (!bio_data)
+               return -ENOMEM;
+
+       volume->private = bio_data;
+
+       if ((err = parse_blockio_params(volume, args)) < 0) {
+               eprintk("Error attaching Lun %u to Target %s \n",
+                       volume->lun, volume->target->name);
+               goto out;
+       }
+
+       /* Assign a vendor id, generate scsi id if none exists */
+       gen_scsiid(volume, bio_data->bdev->bd_inode);
+
+       /* Offer neither write nor read caching */
+       ClearLURCache(volume);
+       ClearLUWCache(volume);
+
+       volume->blk_shift = SECTOR_SIZE_BITS;
+       volume->blk_cnt = bio_data->bdev->bd_inode->i_size >> volume->blk_shift;
+
+  out:
+       if (err < 0)
+               blockio_detach(volume);
+
+       return err;
+}
+
+static void
+blockio_show(struct iet_volume *volume, struct seq_file *seq)
+{
+       struct blockio_data *bio_data = volume->private;
+
+       /* Used to display blockio volume info in /proc/net/iet/volumes */
+       seq_printf(seq, " path:%s\n", bio_data->path);
+}
+
+struct iotype blockio = {
+       .name = "blockio",
+       .attach = blockio_attach,
+       .make_request = blockio_make_request,
+       .detach = blockio_detach,
+       .show = blockio_show,
+};
diff --git a/ubuntu/iscsitarget/config.c b/ubuntu/iscsitarget/config.c
new file mode 100644 (file)
index 0000000..51331fb
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ *
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/proc_fs.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+struct proc_entries {
+       const char *name;
+       struct file_operations *fops;
+};
+
+static struct proc_entries iet_proc_entries[] =
+{
+       {"volume", &volume_seq_fops},
+       {"session", &session_seq_fops},
+};
+
+static struct proc_dir_entry *proc_iet_dir;
+
+void iet_procfs_exit(void)
+{
+       int i;
+
+       if (!proc_iet_dir)
+               return;
+
+       for (i = 0; i < ARRAY_SIZE(iet_proc_entries); i++)
+               remove_proc_entry(iet_proc_entries[i].name, proc_iet_dir);
+
+       remove_proc_entry(proc_iet_dir->name, proc_iet_dir->parent);
+}
+
+int iet_procfs_init(void)
+{
+       int i;
+       struct proc_dir_entry *ent;
+
+       if (!(proc_iet_dir = proc_mkdir("iet", init_net.proc_net)))
+               goto err;
+
+       for (i = 0; i < ARRAY_SIZE(iet_proc_entries); i++) {
+               ent = create_proc_entry(iet_proc_entries[i].name, 0, proc_iet_dir);
+               if (ent)
+                       ent->proc_fops = iet_proc_entries[i].fops;
+               else
+                       goto err;
+       }
+
+       return 0;
+
+err:
+       if (proc_iet_dir)
+               iet_procfs_exit();
+
+       return -ENOMEM;
+}
+
+static int get_conn_info(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct iscsi_session *session;
+       struct conn_info info;
+       struct iscsi_conn *conn;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       session = session_lookup(target, info.sid);
+       if (!session)
+               return -ENOENT;
+       conn = conn_lookup(session, info.cid);
+
+       info.cid = conn->cid;
+       info.stat_sn = conn->stat_sn;
+       info.exp_stat_sn = conn->exp_stat_sn;
+
+       if (copy_to_user((void *) ptr, &info, sizeof(info)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int add_conn(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct iscsi_session *session;
+       struct conn_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       if (!(session = session_lookup(target, info.sid)))
+               return -ENOENT;
+
+       return conn_add(session, &info);
+}
+
+static int del_conn(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct iscsi_session *session;
+       struct conn_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       if (!(session = session_lookup(target, info.sid)))
+               return -ENOENT;
+
+       return conn_del(session, &info);
+}
+
+static int get_session_info(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct iscsi_session *session;
+       struct session_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       session = session_lookup(target, info.sid);
+
+       if (!session)
+               return -ENOENT;
+
+       info.exp_cmd_sn = session->exp_cmd_sn;
+       info.max_cmd_sn = session->max_cmd_sn;
+
+       if (copy_to_user((void *) ptr, &info, sizeof(info)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int add_session(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct session_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       return session_add(target, &info);
+}
+
+static int del_session(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct session_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       return session_del(target, info.sid);
+}
+
+static int add_volume(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct volume_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       return volume_add(target, &info);
+}
+
+static int del_volume(struct iscsi_target *target, unsigned long ptr)
+{
+       int err;
+       struct volume_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       return iscsi_volume_del(target, &info);
+}
+
+static int iscsi_param_config(struct iscsi_target *target, unsigned long ptr, int set)
+{
+       int err;
+       struct iscsi_param_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               goto out;
+
+       if ((err = iscsi_param_set(target, &info, set)) < 0)
+               goto out;
+
+       if (!set)
+               err = copy_to_user((void *) ptr, &info, sizeof(info));
+
+out:
+       return err;
+}
+
+static int add_target(unsigned long ptr)
+{
+       int err;
+       struct target_info info;
+
+       if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0)
+               return err;
+
+       if (!(err = target_add(&info)))
+               err = copy_to_user((void *) ptr, &info, sizeof(info));
+
+       return err;
+}
+
+static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct iscsi_target *target = NULL;
+       long err;
+       u32 id;
+
+       if ((err = get_user(id, (u32 *) arg)) != 0)
+               goto done;
+
+       if (cmd == DEL_TARGET) {
+               err = target_del(id);
+               goto done;
+       }
+
+       target = target_lookup_by_id(id);
+
+       if (cmd == ADD_TARGET)
+               if (target) {
+                       err = -EEXIST;
+                       eprintk("Target %u already exist!\n", id);
+                       goto done;
+               }
+
+       switch (cmd) {
+       case ADD_TARGET:
+               assert(!target);
+               err = add_target(arg);
+               goto done;
+       }
+
+       if (!target) {
+               eprintk("can't find the target %u\n", id);
+               err = -EINVAL;
+               goto done;
+       }
+
+       if ((err = target_lock(target, 1)) < 0) {
+               eprintk("interrupted %ld %d\n", err, cmd);
+               goto done;
+       }
+
+       switch (cmd) {
+       case ADD_VOLUME:
+               err = add_volume(target, arg);
+               break;
+
+       case DEL_VOLUME:
+               err = del_volume(target, arg);
+               break;
+
+       case ADD_SESSION:
+               err = add_session(target, arg);
+               break;
+
+       case DEL_SESSION:
+               err = del_session(target, arg);
+               break;
+
+       case GET_SESSION_INFO:
+               err = get_session_info(target, arg);
+               break;
+
+       case ISCSI_PARAM_SET:
+               err = iscsi_param_config(target, arg, 1);
+               break;
+
+       case ISCSI_PARAM_GET:
+               err = iscsi_param_config(target, arg, 0);
+               break;
+
+       case ADD_CONN:
+               err = add_conn(target, arg);
+               break;
+
+       case DEL_CONN:
+               err = del_conn(target, arg);
+               break;
+
+       case GET_CONN_INFO:
+               err = get_conn_info(target, arg);
+               break;
+       default:
+               eprintk("invalid ioctl cmd %x\n", cmd);
+               err = -EINVAL;
+       }
+
+       if (target)
+               target_unlock(target);
+
+done:
+       return err;
+}
+
+static int release(struct inode *i __attribute__((unused)),
+                  struct file *f __attribute__((unused)))
+{
+       target_del_all();
+       return 0;
+}
+
+struct file_operations ctr_fops = {
+       .owner          = THIS_MODULE,
+       .unlocked_ioctl = ioctl,
+       .compat_ioctl   = ioctl,
+       .release        = release
+};
diff --git a/ubuntu/iscsitarget/conn.c b/ubuntu/iscsitarget/conn.c
new file mode 100644 (file)
index 0000000..2c89304
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <linux/file.h>
+#include <linux/ip.h>
+#include <net/tcp.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "digest.h"
+
+static void print_conn_state(char *p, size_t size, unsigned long state)
+{
+       if (test_bit(CONN_ACTIVE, &state))
+               snprintf(p, size, "%s", "active");
+       else if (test_bit(CONN_CLOSING, &state))
+               snprintf(p, size, "%s", "closing");
+       else
+               snprintf(p, size, "%s", "unknown");
+}
+
+static void print_digest_state(char *p, size_t size, unsigned long flags)
+{
+       if (DIGEST_NONE & flags)
+               snprintf(p, size, "%s", "none");
+       else if (DIGEST_CRC32C & flags)
+               snprintf(p, size, "%s", "crc32c");
+       else
+               snprintf(p, size, "%s", "unknown");
+}
+
+void conn_info_show(struct seq_file *seq, struct iscsi_session *session)
+{
+       struct iscsi_conn *conn;
+       struct sock *sk;
+       char buf[64];
+
+       list_for_each_entry(conn, &session->conn_list, list) {
+               sk = conn->sock->sk;
+               switch (sk->sk_family) {
+               case AF_INET:
+                       snprintf(buf, sizeof(buf),
+                                "%u.%u.%u.%u", NIPQUAD(inet_sk(sk)->daddr));
+                       break;
+               case AF_INET6:
+                       snprintf(buf, sizeof(buf), "[%pI6]",
+                                &inet6_sk(sk)->daddr);
+                       break;
+               default:
+                       break;
+               }
+               seq_printf(seq, "\t\tcid:%u ip:%s ", conn->cid, buf);
+               print_conn_state(buf, sizeof(buf), conn->state);
+               seq_printf(seq, "state:%s ", buf);
+               print_digest_state(buf, sizeof(buf), conn->hdigest_type);
+               seq_printf(seq, "hd:%s ", buf);
+               print_digest_state(buf, sizeof(buf), conn->ddigest_type);
+               seq_printf(seq, "dd:%s\n", buf);
+       }
+}
+
+struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid)
+{
+       struct iscsi_conn *conn;
+
+       list_for_each_entry(conn, &session->conn_list, list) {
+               if (conn->cid == cid)
+                       return conn;
+       }
+       return NULL;
+}
+
+static void iet_state_change(struct sock *sk)
+{
+       struct iscsi_conn *conn = sk->sk_user_data;
+       struct iscsi_target *target = conn->session->target;
+
+       if (sk->sk_state != TCP_ESTABLISHED)
+               conn_close(conn);
+       else
+               nthread_wakeup(target);
+
+       target->nthread_info.old_state_change(sk);
+}
+
+static void iet_data_ready(struct sock *sk, int len)
+{
+       struct iscsi_conn *conn = sk->sk_user_data;
+       struct iscsi_target *target = conn->session->target;
+
+       nthread_wakeup(target);
+       target->nthread_info.old_data_ready(sk, len);
+}
+
+/*
+ * @locking: grabs the target's nthread_lock to protect it from races with
+ * set_conn_wspace_wait()
+ */
+static void iet_write_space(struct sock *sk)
+{
+       struct iscsi_conn *conn = sk->sk_user_data;
+       struct network_thread_info *info = &conn->session->target->nthread_info;
+
+       spin_lock_bh(&info->nthread_lock);
+
+       if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) &&
+           test_bit(CONN_WSPACE_WAIT, &conn->state)) {
+               clear_bit(CONN_WSPACE_WAIT, &conn->state);
+               __nthread_wakeup(info);
+       }
+
+       spin_unlock_bh(&info->nthread_lock);
+
+       info->old_write_space(sk);
+}
+
+static void iet_socket_bind(struct iscsi_conn *conn)
+{
+       int opt = 1;
+       mm_segment_t oldfs;
+       struct iscsi_session *session = conn->session;
+       struct iscsi_target *target = session->target;
+
+       dprintk(D_GENERIC, "%llu\n", (unsigned long long) session->sid);
+
+       conn->sock = SOCKET_I(conn->file->f_dentry->d_inode);
+       conn->sock->sk->sk_user_data = conn;
+
+       write_lock_bh(&conn->sock->sk->sk_callback_lock);
+       target->nthread_info.old_state_change = conn->sock->sk->sk_state_change;
+       conn->sock->sk->sk_state_change = iet_state_change;
+
+       target->nthread_info.old_data_ready = conn->sock->sk->sk_data_ready;
+       conn->sock->sk->sk_data_ready = iet_data_ready;
+
+       target->nthread_info.old_write_space = conn->sock->sk->sk_write_space;
+       conn->sock->sk->sk_write_space = iet_write_space;
+       write_unlock_bh(&conn->sock->sk->sk_callback_lock);
+
+       oldfs = get_fs();
+       set_fs(get_ds());
+       conn->sock->ops->setsockopt(conn->sock, SOL_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt));
+       set_fs(oldfs);
+}
+
+int conn_free(struct iscsi_conn *conn)
+{
+       dprintk(D_GENERIC, "%p %#Lx %u\n", conn->session,
+               (unsigned long long) conn->session->sid, conn->cid);
+
+       assert(atomic_read(&conn->nr_cmnds) == 0);
+       assert(list_empty(&conn->pdu_list));
+       assert(list_empty(&conn->write_list));
+
+       list_del(&conn->list);
+       list_del(&conn->poll_list);
+
+       del_timer_sync(&conn->nop_timer);
+       digest_cleanup(conn);
+       kfree(conn);
+
+       return 0;
+}
+
+static int iet_conn_alloc(struct iscsi_session *session, struct conn_info *info)
+{
+       struct iscsi_conn *conn;
+
+       dprintk(D_SETUP, "%#Lx:%u\n", (unsigned long long) session->sid, info->cid);
+
+       conn = kzalloc(sizeof(*conn), GFP_KERNEL);
+       if (!conn)
+               return -ENOMEM;
+
+       conn->session = session;
+       conn->cid = info->cid;
+       conn->stat_sn = info->stat_sn;
+       conn->exp_stat_sn = info->exp_stat_sn;
+
+       conn->hdigest_type = info->header_digest;
+       conn->ddigest_type = info->data_digest;
+       if (digest_init(conn) < 0) {
+               kfree(conn);
+               return -ENOMEM;
+       }
+
+       spin_lock_init(&conn->list_lock);
+       atomic_set(&conn->nr_cmnds, 0);
+       atomic_set(&conn->nr_busy_cmnds, 0);
+       INIT_LIST_HEAD(&conn->pdu_list);
+       INIT_LIST_HEAD(&conn->write_list);
+       INIT_LIST_HEAD(&conn->poll_list);
+       init_timer(&conn->nop_timer);
+
+       list_add(&conn->list, &session->conn_list);
+
+       set_bit(CONN_ACTIVE, &conn->state);
+
+       conn->file = fget(info->fd);
+       iet_socket_bind(conn);
+
+       list_add(&conn->poll_list, &session->target->nthread_info.active_conns);
+
+       nthread_wakeup(conn->session->target);
+
+       return 0;
+}
+
+void conn_close(struct iscsi_conn *conn)
+{
+       if (test_and_clear_bit(CONN_ACTIVE, &conn->state))
+               set_bit(CONN_CLOSING, &conn->state);
+
+       nthread_wakeup(conn->session->target);
+}
+
+int conn_add(struct iscsi_session *session, struct conn_info *info)
+{
+       struct iscsi_conn *conn;
+       int err = -EEXIST;
+
+       conn = conn_lookup(session, info->cid);
+       if (conn)
+               return err;
+
+       return iet_conn_alloc(session, info);
+}
+
+int conn_del(struct iscsi_session *session, struct conn_info *info)
+{
+       struct iscsi_conn *conn;
+       int err = -EEXIST;
+
+       conn = conn_lookup(session, info->cid);
+       if (!conn)
+               return err;
+
+       conn_close(conn);
+
+       return 0;
+}
+
+/* target_lock() supposed to be held */
+void conn_del_all(struct iscsi_session *session)
+{
+       struct iscsi_conn *conn;
+
+       list_for_each_entry(conn, &session->conn_list, list)
+               conn_close(conn);
+}
diff --git a/ubuntu/iscsitarget/digest.c b/ubuntu/iscsitarget/digest.c
new file mode 100644 (file)
index 0000000..32b4d76
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * iSCSI digest handling.
+ * (C) 2004 - 2006 Xiranet Communications GmbH <arne.redlich@xiranet.com>
+ * This code is licensed under the GPL.
+ */
+
+#include <linux/types.h>
+
+#include "iscsi.h"
+#include "digest.h"
+#include "iscsi_dbg.h"
+
+void digest_alg_available(unsigned int *val)
+{
+       if (*val & DIGEST_CRC32C &&
+           !crypto_has_alg("crc32c", 0, CRYPTO_ALG_ASYNC)) {
+               printk("CRC32C digest algorithm not available in kernel\n");
+               *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)
+{
+       int err = 0;
+
+       if (!(conn->hdigest_type & DIGEST_ALL))
+               conn->hdigest_type = DIGEST_NONE;
+
+       if (!(conn->ddigest_type & DIGEST_ALL))
+               conn->ddigest_type = DIGEST_NONE;
+
+       if (conn->hdigest_type & DIGEST_CRC32C ||
+           conn->ddigest_type & DIGEST_CRC32C) {
+               conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+                                                     CRYPTO_ALG_ASYNC);
+               conn->rx_hash.flags = 0;
+               if (IS_ERR(conn->rx_hash.tfm)) {
+                       conn->rx_hash.tfm = NULL;
+                       err = -ENOMEM;
+                       goto out;
+               }
+
+               conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+                                                     CRYPTO_ALG_ASYNC);
+               conn->tx_hash.flags = 0;
+               if (IS_ERR(conn->tx_hash.tfm)) {
+                       conn->tx_hash.tfm = NULL;
+                       err = -ENOMEM;
+                       goto out;
+               }
+       }
+
+out:
+       if (err)
+               digest_cleanup(conn);
+
+       return err;
+}
+
+/**
+ * free resources used for digest calculation.
+ *
+ * digest_cleanup -
+ * @conn: ptr to connection that made use of digests
+ */
+void digest_cleanup(struct iscsi_conn *conn)
+{
+       if (conn->tx_hash.tfm)
+               crypto_free_hash(conn->tx_hash.tfm);
+       if (conn->rx_hash.tfm)
+               crypto_free_hash(conn->rx_hash.tfm);
+}
+
+/**
+ * debug handling of header digest errors:
+ * simulates a digest error after n PDUs / every n-th PDU of type
+ * HDIGEST_ERR_CORRUPT_PDU_TYPE.
+ */
+static inline void __dbg_simulate_header_digest_error(struct iscsi_cmnd *cmnd)
+{
+#define HDIGEST_ERR_AFTER_N_CMNDS 1000
+#define HDIGEST_ERR_ONLY_ONCE     1
+#define HDIGEST_ERR_CORRUPT_PDU_TYPE ISCSI_OP_SCSI_CMD
+#define HDIGEST_ERR_CORRUPT_PDU_WITH_DATA_ONLY 0
+
+       static int num_cmnds = 0;
+       static int num_errs = 0;
+
+       if (cmnd_opcode(cmnd) == HDIGEST_ERR_CORRUPT_PDU_TYPE) {
+               if (HDIGEST_ERR_CORRUPT_PDU_WITH_DATA_ONLY) {
+                       if (cmnd->pdu.datasize)
+                               num_cmnds++;
+               } else
+                       num_cmnds++;
+       }
+
+       if ((num_cmnds == HDIGEST_ERR_AFTER_N_CMNDS)
+           && (!(HDIGEST_ERR_ONLY_ONCE && num_errs))) {
+               printk("*** Faking header digest error ***\n");
+               printk("\tcmnd: 0x%x, itt 0x%x, sn 0x%x\n",
+                      cmnd_opcode(cmnd),
+                      be32_to_cpu(cmnd->pdu.bhs.itt),
+                      be32_to_cpu(cmnd->pdu.bhs.sn));
+               cmnd->hdigest = ~cmnd->hdigest;
+               /* make things even worse by manipulating header fields */
+               cmnd->pdu.datasize += 8;
+               num_errs++;
+               num_cmnds = 0;
+       }
+       return;
+}
+
+/**
+ * debug handling of data digest errors:
+ * simulates a digest error after n PDUs / every n-th PDU of type
+ * DDIGEST_ERR_CORRUPT_PDU_TYPE.
+ */
+static inline void __dbg_simulate_data_digest_error(struct iscsi_cmnd *cmnd)
+{
+#define DDIGEST_ERR_AFTER_N_CMNDS 50
+#define DDIGEST_ERR_ONLY_ONCE     1
+#define DDIGEST_ERR_CORRUPT_PDU_TYPE   ISCSI_OP_SCSI_DATA_OUT
+#define DDIGEST_ERR_CORRUPT_UNSOL_DATA_ONLY 0
+
+       static int num_cmnds = 0;
+       static int num_errs = 0;
+
+       if ((cmnd->pdu.datasize)
+           && (cmnd_opcode(cmnd) == DDIGEST_ERR_CORRUPT_PDU_TYPE)) {
+               switch (cmnd_opcode(cmnd)) {
+               case ISCSI_OP_SCSI_DATA_OUT:
+                       if ((DDIGEST_ERR_CORRUPT_UNSOL_DATA_ONLY)
+                           && (cmnd->pdu.bhs.ttt != ISCSI_RESERVED_TAG))
+                               break;
+               default:
+                       num_cmnds++;
+               }
+       }
+
+       if ((num_cmnds == DDIGEST_ERR_AFTER_N_CMNDS)
+           && (!(DDIGEST_ERR_ONLY_ONCE && num_errs))
+           && (cmnd->pdu.datasize)
+           && (!cmnd->conn->read_overflow)) {
+               printk("*** Faking data digest error: ***");
+               printk("\tcmnd 0x%x, itt 0x%x, sn 0x%x\n",
+                      cmnd_opcode(cmnd),
+                      be32_to_cpu(cmnd->pdu.bhs.itt),
+                      be32_to_cpu(cmnd->pdu.bhs.sn));
+               cmnd->ddigest = ~cmnd->ddigest;
+               num_errs++;
+               num_cmnds = 0;
+       }
+}
+
+static void digest_header(struct hash_desc *hash, struct iscsi_pdu *pdu,
+                         u8 *crc)
+{
+       struct scatterlist sg[2];
+       unsigned int nbytes = sizeof(struct iscsi_hdr);
+
+       sg_init_table(sg, pdu->ahssize ? 2 : 1);
+
+       sg_set_buf(&sg[0], &pdu->bhs, nbytes);
+       if (pdu->ahssize) {
+               sg_set_buf(&sg[1], pdu->ahs, pdu->ahssize);
+               nbytes += pdu->ahssize;
+       }
+
+       crypto_hash_init(hash);
+       crypto_hash_update(hash, sg, nbytes);
+       crypto_hash_final(hash, crc);
+}
+
+int digest_rx_header(struct iscsi_cmnd *cmnd)
+{
+       u32 crc;
+
+       digest_header(&cmnd->conn->rx_hash, &cmnd->pdu, (u8 *) &crc);
+       if (crc != cmnd->hdigest)
+               return -EIO;
+
+       return 0;
+}
+
+void digest_tx_header(struct iscsi_cmnd *cmnd)
+{
+       digest_header(&cmnd->conn->tx_hash, &cmnd->pdu, (u8 *) &cmnd->hdigest);
+}
+
+static void digest_data(struct hash_desc *hash, struct iscsi_cmnd *cmnd,
+                       struct tio *tio, u32 offset, u8 *crc)
+{
+       struct scatterlist *sg = cmnd->conn->hash_sg;
+       u32 size, length;
+       int i, idx, count;
+       unsigned int nbytes;
+
+       size = cmnd->pdu.datasize;
+       nbytes = size = (size + 3) & ~3;
+
+       offset += tio->offset;
+       idx = offset >> PAGE_CACHE_SHIFT;
+       offset &= ~PAGE_CACHE_MASK;
+       count = get_pgcnt(size, offset);
+       assert(idx + count <= tio->pg_cnt);
+
+       assert(count <= ISCSI_CONN_IOV_MAX);
+
+       sg_init_table(sg, ARRAY_SIZE(cmnd->conn->hash_sg));
+       crypto_hash_init(hash);
+
+       for (i = 0; size; i++) {
+               if (offset + size > PAGE_CACHE_SIZE)
+                       length = PAGE_CACHE_SIZE - offset;
+               else
+                       length = size;
+
+               sg_set_page(&sg[i], tio->pvec[idx + i], length, offset);
+               size -= length;
+               offset = 0;
+       }
+
+       sg_mark_end(&sg[i - 1]);
+
+       crypto_hash_update(hash, sg, nbytes);
+       crypto_hash_final(hash, crc);
+}
+
+int digest_rx_data(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio;
+       struct iscsi_cmnd *scsi_cmnd;
+       struct iscsi_data_out_hdr *req;
+       u32 offset, crc;
+
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_SCSI_REJECT:
+       case ISCSI_OP_PDU_REJECT:
+       case ISCSI_OP_DATA_REJECT:
+               return 0;
+       case ISCSI_OP_SCSI_DATA_OUT:
+               scsi_cmnd = cmnd->req;
+               req = (struct iscsi_data_out_hdr *) &cmnd->pdu.bhs;
+               tio = scsi_cmnd->tio;
+               offset = be32_to_cpu(req->buffer_offset);
+               break;
+       default:
+               tio = cmnd->tio;
+               offset = 0;
+       }
+
+       digest_data(&cmnd->conn->rx_hash, cmnd, tio, offset, (u8 *) &crc);
+
+       if (!cmnd->conn->read_overflow &&
+           (cmnd_opcode(cmnd) != ISCSI_OP_PDU_REJECT)) {
+               if (crc != cmnd->ddigest)
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+void digest_tx_data(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio = cmnd->tio;
+       struct iscsi_data_out_hdr *req = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
+
+       assert(tio);
+       digest_data(&cmnd->conn->tx_hash, cmnd, tio,
+                   be32_to_cpu(req->buffer_offset), (u8 *) &cmnd->ddigest);
+}
diff --git a/ubuntu/iscsitarget/digest.h b/ubuntu/iscsitarget/digest.h
new file mode 100644 (file)
index 0000000..9b9d845
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * iSCSI digest handling.
+ * (C) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
+ * This code is licensed under the GPL.
+ */
+
+#ifndef __IET_DIGEST_H__
+#define __IET_DIGEST_H__
+
+extern void digest_alg_available(unsigned int *val);
+extern int digest_init(struct iscsi_conn *conn);
+extern void digest_cleanup(struct iscsi_conn *conn);
+
+extern int digest_rx_header(struct iscsi_cmnd *cmnd);
+extern int digest_rx_data(struct iscsi_cmnd *cmnd);
+
+extern void digest_tx_header(struct iscsi_cmnd *cmnd);
+extern void digest_tx_data(struct iscsi_cmnd *cmnd);
+
+#endif /* __IET_DIGEST_H__ */
diff --git a/ubuntu/iscsitarget/event.c b/ubuntu/iscsitarget/event.c
new file mode 100644 (file)
index 0000000..240404d
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Event notification code.
+ * (C) 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ *
+ * Some functions are based on audit code.
+ */
+
+#include <net/tcp.h>
+#include "iet_u.h"
+#include "iscsi_dbg.h"
+
+static struct sock *nl;
+static u32 ietd_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);
+
+       ietd_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)
+                       break;
+               rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+               if (rlen > skb->len)
+                       rlen = skb->len;
+               if ((err = event_recv_msg(skb, nlh))) {
+                       netlink_ack(skb, nlh, -err);
+               } else if (nlh->nlmsg_flags & NLM_F_ACK)
+                       netlink_ack(skb, nlh, 0);
+               skb_pull(skb, rlen);
+       }
+}
+
+static int notify(void *data, int len, int gfp_mask)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       static u32 seq = 0;
+
+       if (!(skb = alloc_skb(NLMSG_SPACE(len), gfp_mask)))
+               return -ENOMEM;
+
+       nlh = __nlmsg_put(skb, ietd_pid, seq++, NLMSG_DONE, len - sizeof(*nlh), 0);
+
+       memcpy(NLMSG_DATA(nlh), data, len);
+
+       return netlink_unicast(nl, skb, ietd_pid, 0);
+}
+
+int event_send(u32 tid, u64 sid, u32 cid, u32 state, int atomic)
+{
+       int err;
+       struct iet_event event;
+
+       event.tid = tid;
+       event.sid = sid;
+       event.cid = cid;
+       event.state = state;
+
+       err = notify(&event, NLMSG_SPACE(sizeof(struct iet_event)), 0);
+
+       return err;
+}
+
+int event_init(void)
+{
+       nl = netlink_kernel_create(&init_net, NETLINK_IET, 1, event_recv_skb,
+                                  NULL, THIS_MODULE);
+       if (!nl)
+               return -ENOMEM;
+       else
+               return 0;
+}
+
+void event_exit(void)
+{
+       netlink_kernel_release(nl);
+}
diff --git a/ubuntu/iscsitarget/file-io.c b/ubuntu/iscsitarget/file-io.c
new file mode 100644 (file)
index 0000000..a492ce4
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * Target device file I/O.
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/types.h>
+#include <linux/blkdev.h>
+#include <linux/parser.h>
+#include <linux/writeback.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+struct fileio_data {
+       char *path;
+       struct file *filp;
+};
+
+static int fileio_make_request(struct iet_volume *lu, struct tio *tio, int rw)
+{
+       struct fileio_data *p = lu->private;
+       struct file *filp;
+       mm_segment_t oldfs;
+       struct page *page;
+       u32 offset, size;
+       loff_t ppos, count;
+       char *buf;
+       int i, err = 0;
+       ssize_t ret;
+
+       assert(p);
+       filp = p->filp;
+       size = tio->size;
+       offset= tio->offset;
+
+       ppos = (loff_t) tio->idx << PAGE_CACHE_SHIFT;
+       ppos += offset;
+
+       for (i = 0; i < tio->pg_cnt; i++) {
+               page = tio->pvec[i];
+               assert(page);
+               buf = page_address(page);
+               buf += offset;
+
+               if (offset + size > PAGE_CACHE_SIZE)
+                       count = PAGE_CACHE_SIZE - offset;
+               else
+                       count = size;
+
+               oldfs = get_fs();
+               set_fs(get_ds());
+
+               if (rw == READ)
+                       ret = do_sync_read(filp, buf, count, &ppos);
+               else
+                       ret = do_sync_write(filp, buf, count, &ppos);
+
+               set_fs(oldfs);
+
+               if (ret != count) {
+                       eprintk("I/O error %lld, %ld\n", count, (long) ret);
+                       err = -EIO;
+               }
+
+               size -= count;
+               offset = 0;
+       }
+       assert(!size);
+
+       return err;
+}
+
+static int fileio_sync(struct iet_volume *lu, struct tio *tio)
+{
+       struct fileio_data *p = lu->private;
+       struct inode *inode = p->filp->f_dentry->d_inode;
+       struct address_space *mapping = inode->i_mapping;
+       loff_t ppos, count;
+       int res;
+
+       if (tio) {
+               ppos = (loff_t) tio->idx << PAGE_CACHE_SHIFT;
+               count = tio->size;
+       } else {
+               ppos = 0;
+               count = lu->blk_cnt << lu->blk_shift;
+       }
+
+       res = filemap_write_and_wait_range(mapping, ppos, ppos + count - 1);
+       if (res) {
+               eprintk("I/O error: syncing pages failed: %d\n", res);
+               return -EIO;
+       } else
+               return 0;
+}
+
+static int open_path(struct iet_volume *volume, const char *path)
+{
+       int err = 0;
+       struct fileio_data *info = volume->private;
+       struct file *filp;
+       mm_segment_t oldfs;
+       int flags;
+
+       info->path = kstrdup(path, GFP_KERNEL);
+       if (!info->path)
+               return -ENOMEM;
+
+       oldfs = get_fs();
+       set_fs(get_ds());
+       flags = (LUReadonly(volume) ? O_RDONLY : O_RDWR) | O_LARGEFILE;
+       filp = filp_open(path, flags, 0);
+       set_fs(oldfs);
+
+       if (IS_ERR(filp)) {
+               err = PTR_ERR(filp);
+               eprintk("Can't open %s %d\n", path, err);
+               info->filp = NULL;
+       } else
+               info->filp = filp;
+
+       return err;
+}
+
+static int set_scsiid(struct iet_volume *volume, const char *id)
+{
+       size_t len;
+
+       if ((len = strlen(id)) > SCSI_ID_LEN - VENDOR_ID_LEN) {
+               eprintk("SCSI ID too long, %zd provided, %u max\n", len,
+                       SCSI_ID_LEN - VENDOR_ID_LEN);
+               return -EINVAL;
+       }
+
+       memcpy(volume->scsi_id + VENDOR_ID_LEN, id, len);
+
+       return 0;
+}
+
+static void gen_scsiid(struct iet_volume *volume, struct inode *inode)
+{
+       int i;
+       u32 *p;
+
+       strlcpy(volume->scsi_id, VENDOR_ID, VENDOR_ID_LEN);
+
+       for (i = VENDOR_ID_LEN; i < SCSI_ID_LEN; i++)
+               if (volume->scsi_id[i])
+                       return;
+
+       p = (u32 *) (volume->scsi_id + VENDOR_ID_LEN);
+       *(p + 0) = volume->target->trgt_param.target_type;
+       *(p + 1) = volume->target->tid;
+       *(p + 2) = (unsigned int) inode->i_ino;
+       *(p + 3) = (unsigned int) inode->i_sb->s_dev;
+}
+
+static int set_scsisn(struct iet_volume *volume, const char *sn)
+{
+       size_t len;
+
+       if ((len = strlen(sn)) > SCSI_SN_LEN) {
+               eprintk("SCSI SN too long, %zd provided, %u max\n", len,
+                       SCSI_SN_LEN);
+               return -EINVAL;
+       }
+       memcpy(volume->scsi_sn, sn, len);
+       return 0;
+}
+
+enum {
+       Opt_scsiid, Opt_scsisn, Opt_path, Opt_ignore, Opt_err,
+};
+
+static match_table_t tokens = {
+       {Opt_scsiid, "ScsiId=%s"},
+       {Opt_scsisn, "ScsiSN=%s"},
+       {Opt_path, "Path=%s"},
+       {Opt_ignore, "Type=%s"},
+       {Opt_ignore, "IOMode=%s"},
+       {Opt_err, NULL},
+};
+
+static int parse_fileio_params(struct iet_volume *volume, char *params)
+{
+       struct fileio_data *info = volume->private;
+       int err = 0;
+       char *p, *q;
+
+       while ((p = strsep(&params, ",")) != NULL) {
+               substring_t args[MAX_OPT_ARGS];
+               int token;
+               if (!*p)
+                       continue;
+               token = match_token(p, tokens, args);
+               switch (token) {
+               case Opt_scsiid:
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = set_scsiid(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_scsisn:
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = set_scsisn(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_path:
+                       if (info->path) {
+                               iprintk("Target %s, LUN %u: "
+                                       "duplicate \"Path\" param\n",
+                                       volume->target->name, volume->lun);
+                               err = -EINVAL;
+                               goto out;
+                       }
+                       if (!(q = match_strdup(&args[0]))) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       err = open_path(volume, q);
+                       kfree(q);
+                       if (err < 0)
+                               goto out;
+                       break;
+               case Opt_ignore:
+                       break;
+               default:
+                       iprintk("Target %s, LUN %u: unknown param %s\n",
+                               volume->target->name, volume->lun, p);
+                       return -EINVAL;
+               }
+       }
+
+       if (!info->path) {
+               iprintk("Target %s, LUN %u: missing \"Path\" param\n",
+                       volume->target->name, volume->lun);
+               err = -EINVAL;
+       }
+out:
+       return err;
+}
+
+static void fileio_detach(struct iet_volume *lu)
+{
+       struct fileio_data *p = lu->private;
+
+       kfree(p->path);
+       if (p->filp)
+               filp_close(p->filp, NULL);
+       kfree(p);
+       lu->private = NULL;
+}
+
+static int fileio_attach(struct iet_volume *lu, char *args)
+{
+       int err = 0;
+       struct fileio_data *p;
+       struct inode *inode;
+
+       if (lu->private) {
+               printk("already attached ? %d\n", lu->lun);
+               return -EBUSY;
+       }
+
+       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+
+       lu->private = p;
+
+       if ((err = parse_fileio_params(lu, args)) < 0) {
+               eprintk("%d\n", err);
+               goto out;
+       }
+       inode = p->filp->f_dentry->d_inode;
+
+       gen_scsiid(lu, inode);
+
+       if (S_ISREG(inode->i_mode))
+               ;
+       else if (S_ISBLK(inode->i_mode))
+               inode = inode->i_bdev->bd_inode;
+       else {
+               err = -EINVAL;
+               goto out;
+       }
+
+       lu->blk_shift = SECTOR_SIZE_BITS;
+       lu->blk_cnt = inode->i_size >> lu->blk_shift;
+
+       /* we're using the page cache */
+       SetLURCache(lu);
+out:
+       if (err < 0)
+               fileio_detach(lu);
+       return err;
+}
+
+static void fileio_show(struct iet_volume *lu, struct seq_file *seq)
+{
+       struct fileio_data *p = lu->private;
+       seq_printf(seq, " path:%s\n", p->path);
+}
+
+struct iotype fileio =
+{
+       .name = "fileio",
+       .attach = fileio_attach,
+       .make_request = fileio_make_request,
+       .sync = fileio_sync,
+       .detach = fileio_detach,
+       .show = fileio_show,
+};
diff --git a/ubuntu/iscsitarget/include/iet_u.h b/ubuntu/iscsitarget/include/iet_u.h
new file mode 100644 (file)
index 0000000..620b3c4
--- /dev/null
@@ -0,0 +1,149 @@
+#ifndef _IET_U_H
+#define _IET_U_H
+
+#define IET_VERSION_STRING     "1.4.19"
+
+/* The maximum length of 223 bytes in the RFC. */
+#define ISCSI_NAME_LEN 256
+#define ISCSI_ARGS_LEN 2048
+
+#define ISCSI_LISTEN_PORT      3260
+
+#define VENDOR_ID_LEN  8
+#define SCSI_ID_LEN    24
+#define SCSI_SN_LEN    16
+
+#ifndef aligned_u64
+#define aligned_u64 unsigned long long __attribute__((aligned(8)))
+#endif
+
+struct target_info {
+       u32 tid;
+       char name[ISCSI_NAME_LEN];
+};
+
+struct volume_info {
+       u32 tid;
+       u32 lun;
+       aligned_u64 args_ptr;
+       u32 args_len;
+};
+
+struct session_info {
+       u32 tid;
+
+       aligned_u64 sid;
+       char initiator_name[ISCSI_NAME_LEN];
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+};
+
+#define DIGEST_ALL     (DIGEST_NONE | DIGEST_CRC32C)
+#define DIGEST_NONE            (1 << 0)
+#define DIGEST_CRC32C           (1 << 1)
+
+struct conn_info {
+       u32 tid;
+       aligned_u64 sid;
+
+       u32 cid;
+       u32 stat_sn;
+       u32 exp_stat_sn;
+       int header_digest;
+       int data_digest;
+       int fd;
+};
+
+enum {
+       key_initial_r2t,
+       key_immediate_data,
+       key_max_connections,
+       key_max_recv_data_length,
+       key_max_xmit_data_length,
+       key_max_burst_length,
+       key_first_burst_length,
+       key_default_wait_time,
+       key_default_retain_time,
+       key_max_outstanding_r2t,
+       key_data_pdu_inorder,
+       key_data_sequence_inorder,
+       key_error_recovery_level,
+       key_header_digest,
+       key_data_digest,
+       key_ofmarker,
+       key_ifmarker,
+       key_ofmarkint,
+       key_ifmarkint,
+       session_key_last,
+};
+
+enum {
+       key_wthreads,
+       key_target_type,
+       key_queued_cmnds,
+       key_nop_interval,
+       key_nop_timeout,
+       target_key_last,
+};
+
+enum {
+       key_session,
+       key_target,
+};
+
+struct iscsi_param_info {
+       u32 tid;
+       aligned_u64 sid;
+
+       u32 param_type;
+       u32 partial;
+
+       u32 session_param[session_key_last];
+       u32 target_param[target_key_last];
+};
+
+enum iet_event_state {
+       E_CONN_CLOSE,
+};
+
+struct iet_event {
+       u32 tid;
+       aligned_u64 sid;
+       u32 cid;
+       u32 state;
+};
+
+#define        DEFAULT_NR_WTHREADS     8
+#define        MIN_NR_WTHREADS         1
+#define        MAX_NR_WTHREADS         128
+
+#define        DEFAULT_NR_QUEUED_CMNDS 32
+#define        MIN_NR_QUEUED_CMNDS     1
+#define        MAX_NR_QUEUED_CMNDS     256
+
+#define DEFAULT_NOP_INTERVAL   0
+#define MIN_NOP_INTERVAL       0
+#define MAX_NOP_INTERVAL       90
+
+#define        DEFAULT_NOP_TIMEOUT     0
+#define MIN_NOP_TIMEOUT        0
+#define MAX_NOP_TIMEOUT                90
+
+#define NETLINK_IET    21
+
+#define ADD_TARGET _IOW('i', 0, struct target_info)
+#define DEL_TARGET _IOW('i', 1, struct target_info)
+#define START_TARGET _IO('i', 2)
+#define STOP_TARGET _IO('i', 3)
+#define ADD_VOLUME _IOW('i', 4, struct volume_info)
+#define DEL_VOLUME _IOW('i', 5, struct volume_info)
+#define ADD_SESSION _IOW('i', 6, struct session_info)
+#define DEL_SESSION _IOW('i', 7, struct session_info)
+#define GET_SESSION_INFO _IOWR('i', 8, struct session_info)
+#define ADD_CONN _IOW('i', 9, struct conn_info)
+#define DEL_CONN _IOW('i', 10, struct conn_info)
+#define GET_CONN_INFO _IOWR('i', 11, struct conn_info)
+#define ISCSI_PARAM_SET _IOW('i', 12, struct iscsi_param_info)
+#define ISCSI_PARAM_GET _IOWR('i', 13, struct iscsi_param_info)
+
+#endif
diff --git a/ubuntu/iscsitarget/iotype.c b/ubuntu/iscsitarget/iotype.c
new file mode 100644 (file)
index 0000000..b3d6117
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Manager for various I/O types.
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include "iscsi.h"
+#include "iotype.h"
+#include "iscsi_dbg.h"
+
+static LIST_HEAD(iotypes);
+static rwlock_t iotypes_lock = RW_LOCK_UNLOCKED;
+
+static struct iotype *find_iotype(const char *name)
+{
+       struct iotype *iot = NULL;
+
+       list_for_each_entry(iot, &iotypes, iot_list) {
+               if (strcmp(iot->name, name) == 0)
+                       return iot;
+       }
+       return NULL;
+}
+
+struct iotype *get_iotype(const char *name)
+{
+       struct iotype *iot;
+
+       read_lock(&iotypes_lock);
+       iot = find_iotype(name);
+       read_unlock(&iotypes_lock);
+
+       return iot;
+}
+
+void put_iotype(struct iotype *iot)
+{
+       if (!iot)
+               return;
+       return;
+}
+
+static int register_iotype(struct iotype *iot)
+{
+       int err = 0;
+       struct iotype *p;
+
+       write_lock(&iotypes_lock);
+
+       p = find_iotype(iot->name);
+       if (p)
+               err = -EBUSY;
+       else
+               list_add_tail(&iot->iot_list, &iotypes);
+
+       write_unlock(&iotypes_lock);
+
+       return err;
+}
+
+static int unregister_iotype(struct iotype *iot)
+{
+       int err = 0;
+       struct iotype *p;
+
+       write_lock(&iotypes_lock);
+
+       p = find_iotype(iot->name);
+       if (p && p == iot)
+               list_del_init(&iot->iot_list);
+       else
+               err = -EINVAL;
+
+       write_unlock(&iotypes_lock);
+
+
+       return err;
+}
+
+struct iotype *iotype_array[] = {
+       &fileio,
+       &blockio,
+       &nullio,
+};
+
+int iotype_init(void)
+{
+       int i, err;
+
+       for (i = 0; i < ARRAY_SIZE(iotype_array); i++) {
+               if (!(err = register_iotype(iotype_array[i])))
+                       iprintk("Registered io type %s\n",
+                                               iotype_array[i]->name);
+               else {
+                       eprintk("Failed to register io type %s\n",
+                                               iotype_array[i]->name);
+                       break;
+               }
+       }
+
+       return err;
+}
+
+void iotype_exit(void)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(iotype_array); i++)
+               unregister_iotype(iotype_array[i]);
+}
diff --git a/ubuntu/iscsitarget/iotype.h b/ubuntu/iscsitarget/iotype.h
new file mode 100644 (file)
index 0000000..bd7fbd0
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include "iscsi.h"
+
+#ifndef __IOTYPE_H__
+#define __IOTYPE_H__
+
+struct iotype {
+       const char *name;
+       struct list_head iot_list;
+
+       int (*attach)(struct iet_volume *dev, char *args);
+       int (*make_request)(struct iet_volume *dev, struct tio *tio, int rw);
+       int (*sync)(struct iet_volume *dev, struct tio *tio);
+       void (*detach)(struct iet_volume *dev);
+       void (*show)(struct iet_volume *dev, struct seq_file *seq);
+};
+
+extern struct iotype fileio;
+extern struct iotype nullio;
+extern struct iotype blockio;
+
+extern int iotype_init(void);
+extern void iotype_exit(void);
+
+#endif
diff --git a/ubuntu/iscsitarget/iscsi.c b/ubuntu/iscsitarget/iscsi.c
new file mode 100644 (file)
index 0000000..4f62b8b
--- /dev/null
@@ -0,0 +1,1924 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ * Copyright (C) 2008 Arne Redlich <agr@powerkom-dd.de>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <linux/module.h>
+#include <linux/hash.h>
+#include <net/tcp.h>
+#include <scsi/scsi.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+unsigned long debug_enable_flags;
+unsigned long worker_thread_pool_size;
+
+static struct kmem_cache *iscsi_cmnd_cache;
+static u8 dummy_data[PAGE_SIZE];
+
+static int ctr_major;
+static char ctr_name[] = "ietctl";
+extern struct file_operations ctr_fops;
+
+static 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 u32 cmnd_read_size(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
+
+       if (hdr->flags & ISCSI_CMD_READ) {
+               struct iscsi_rlength_ahdr *ahdr =
+                       (struct iscsi_rlength_ahdr *)cmnd->pdu.ahs;
+
+               if (!(hdr->flags & ISCSI_CMD_WRITE))
+                       return be32_to_cpu(hdr->data_length);
+               if (ahdr && ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH)
+                       return be32_to_cpu(ahdr->read_length);
+       }
+       return 0;
+}
+
+static void iscsi_device_queue_cmnd(struct iscsi_cmnd *cmnd)
+{
+       set_cmnd_waitio(cmnd);
+       wthread_queue(cmnd);
+}
+
+static void iscsi_scsi_queuecmnd(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_queue *queue = &cmnd->lun->queue;
+
+       dprintk(D_GENERIC, "%p\n", cmnd);
+
+       if ((cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) != ISCSI_CMD_UNTAGGED &&
+           (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) != ISCSI_CMD_SIMPLE) {
+               cmnd->pdu.bhs.flags &= ~ISCSI_CMD_ATTR_MASK;
+               cmnd->pdu.bhs.flags |= ISCSI_CMD_UNTAGGED;
+       }
+
+       spin_lock(&queue->queue_lock);
+
+       set_cmnd_queued(cmnd);
+
+       switch (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) {
+       case ISCSI_CMD_UNTAGGED:
+       case ISCSI_CMD_SIMPLE:
+               if (!list_empty(&queue->wait_list) || queue->ordered_cmnd)
+                       goto pending;
+               queue->active_cnt++;
+               break;
+
+       default:
+               BUG();
+       }
+       spin_unlock(&queue->queue_lock);
+
+       iscsi_device_queue_cmnd(cmnd);
+       return;
+ pending:
+       assert(list_empty(&cmnd->list));
+
+       list_add_tail(&cmnd->list, &queue->wait_list);
+       spin_unlock(&queue->queue_lock);
+       return;
+}
+
+static void iscsi_scsi_dequeuecmnd(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_queue *queue;
+
+       if (!cmnd->lun)
+               return;
+       queue = &cmnd->lun->queue;
+       spin_lock(&queue->queue_lock);
+       switch (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) {
+       case ISCSI_CMD_UNTAGGED:
+       case ISCSI_CMD_SIMPLE:
+               --queue->active_cnt;
+               break;
+       case ISCSI_CMD_ORDERED:
+       case ISCSI_CMD_HEAD_OF_QUEUE:
+       case ISCSI_CMD_ACA:
+               BUG();
+       default:
+               /* Should the iscsi_scsi_queuecmnd func reject this ? */
+               break;
+       }
+
+       while (!list_empty(&queue->wait_list)) {
+               cmnd = list_entry(queue->wait_list.next, struct iscsi_cmnd, list);
+               switch ((cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK)) {
+               case ISCSI_CMD_UNTAGGED:
+               case ISCSI_CMD_SIMPLE:
+                       list_del_init(&cmnd->list);
+                       queue->active_cnt++;
+                       iscsi_device_queue_cmnd(cmnd);
+                       break;
+               case ISCSI_CMD_ORDERED:
+               case ISCSI_CMD_HEAD_OF_QUEUE:
+               case ISCSI_CMD_ACA:
+                       BUG();
+               }
+       }
+
+       spin_unlock(&queue->queue_lock);
+
+       return;
+}
+
+/**
+ * create a new command.
+ *
+ * iscsi_cmnd_create -
+ * @conn: ptr to connection (for i/o)
+ *
+ * @return    ptr to command or NULL
+ */
+
+struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn, int req)
+{
+       struct iscsi_cmnd *cmnd;
+
+       /* TODO: async interface is necessary ? */
+       cmnd = kmem_cache_alloc(iscsi_cmnd_cache, GFP_KERNEL|__GFP_NOFAIL);
+
+       memset(cmnd, 0, sizeof(*cmnd));
+       INIT_LIST_HEAD(&cmnd->list);
+       INIT_LIST_HEAD(&cmnd->pdu_list);
+       INIT_LIST_HEAD(&cmnd->conn_list);
+       INIT_LIST_HEAD(&cmnd->hash_list);
+       cmnd->conn = conn;
+       spin_lock(&conn->list_lock);
+       atomic_inc(&conn->nr_cmnds);
+       if (req)
+               list_add_tail(&cmnd->conn_list, &conn->pdu_list);
+       spin_unlock(&conn->list_lock);
+       cmnd->tio = NULL;
+
+       dprintk(D_GENERIC, "%p:%p\n", conn, cmnd);
+
+       return cmnd;
+}
+
+/**
+ * create a new command used as response.
+ *
+ * iscsi_cmnd_create_rsp_cmnd -
+ * @cmnd: ptr to request command
+ *
+ * @return    ptr to response command or NULL
+ */
+
+static struct iscsi_cmnd *iscsi_cmnd_create_rsp_cmnd(struct iscsi_cmnd *cmnd, int final)
+{
+       struct iscsi_cmnd *rsp = cmnd_alloc(cmnd->conn, 0);
+
+       if (final)
+               set_cmnd_final(rsp);
+       list_add_tail(&rsp->pdu_list, &cmnd->pdu_list);
+       rsp->req = cmnd;
+       return rsp;
+}
+
+static struct iscsi_cmnd *get_rsp_cmnd(struct iscsi_cmnd *req)
+{
+       return list_entry(req->pdu_list.prev, struct iscsi_cmnd, pdu_list);
+}
+
+static void iscsi_cmnds_init_write(struct list_head *send)
+{
+       struct iscsi_cmnd *cmnd = list_entry(send->next, struct iscsi_cmnd, list);
+       struct iscsi_conn *conn = cmnd->conn;
+       struct list_head *pos, *next;
+
+       spin_lock(&conn->list_lock);
+
+       list_for_each_safe(pos, next, send) {
+               cmnd = list_entry(pos, struct iscsi_cmnd, list);
+
+               dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
+
+               list_del_init(&cmnd->list);
+               assert(conn == cmnd->conn);
+               list_add_tail(&cmnd->list, &conn->write_list);
+       }
+
+       spin_unlock(&conn->list_lock);
+
+       nthread_wakeup(conn->session->target);
+}
+
+static void iscsi_cmnd_init_write(struct iscsi_cmnd *cmnd)
+{
+       LIST_HEAD(head);
+
+       if (!list_empty(&cmnd->list)) {
+               eprintk("%x %x %x %x %lx %u %u %u %u %u %u %u %d %d\n",
+                       cmnd_itt(cmnd), cmnd_ttt(cmnd), cmnd_opcode(cmnd),
+                       cmnd_scsicode(cmnd), cmnd->flags, cmnd->r2t_sn,
+                       cmnd->r2t_length, cmnd->is_unsolicited_data,
+                       cmnd->target_task_tag, cmnd->outstanding_r2t,
+                       cmnd->hdigest, cmnd->ddigest,
+                       list_empty(&cmnd->pdu_list), list_empty(&cmnd->hash_list));
+
+               assert(list_empty(&cmnd->list));
+       }
+       list_add(&cmnd->list, &head);
+       iscsi_cmnds_init_write(&head);
+}
+
+static void do_send_data_rsp(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       struct iscsi_cmnd *data_cmnd;
+       struct tio *tio = cmnd->tio;
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+       struct iscsi_data_in_hdr *rsp;
+       u32 pdusize, expsize, scsisize, size, offset, sn;
+       LIST_HEAD(send);
+
+       dprintk(D_GENERIC, "%p\n", cmnd);
+       pdusize = conn->session->param.max_xmit_data_length;
+       expsize = cmnd_read_size(cmnd);
+       size = min(expsize, tio->size);
+       offset = 0;
+       sn = 0;
+
+       while (1) {
+               data_cmnd = iscsi_cmnd_create_rsp_cmnd(cmnd, size <= pdusize);
+               tio_get(tio);
+               data_cmnd->tio = tio;
+               rsp = (struct iscsi_data_in_hdr *)&data_cmnd->pdu.bhs;
+
+               rsp->opcode = ISCSI_OP_SCSI_DATA_IN;
+               rsp->itt = req->itt;
+               rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
+               rsp->buffer_offset = offset;
+               rsp->data_sn = cpu_to_be32(sn);
+
+               if (size <= pdusize) {
+                       data_cmnd->pdu.datasize = size;
+                       rsp->flags = ISCSI_FLG_FINAL | ISCSI_FLG_STATUS;
+
+                       scsisize = tio->size;
+                       if (scsisize < expsize) {
+                               rsp->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+                               size = expsize - scsisize;
+                       } else if (scsisize > expsize) {
+                               rsp->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
+                               size = scsisize - expsize;
+                       } else
+                               size = 0;
+                       rsp->residual_count = cpu_to_be32(size);
+                       list_add_tail(&data_cmnd->list, &send);
+
+                       break;
+               }
+
+               data_cmnd->pdu.datasize = pdusize;
+
+               size -= pdusize;
+               offset += pdusize;
+               sn++;
+
+               list_add_tail(&data_cmnd->list, &send);
+       }
+
+       iscsi_cmnds_init_write(&send);
+}
+
+static struct iscsi_cmnd *create_scsi_rsp(struct iscsi_cmnd *req)
+{
+       struct iscsi_cmnd *rsp;
+       struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+       struct iscsi_scsi_rsp_hdr *rsp_hdr;
+       struct iscsi_sense_data *sense;
+
+       rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
+
+       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 = req->status;
+       rsp_hdr->itt = req_hdr->itt;
+
+       if (req->status == SAM_STAT_CHECK_CONDITION) {
+               assert(!rsp->tio);
+               rsp->tio = tio_alloc(1);
+               sense = (struct iscsi_sense_data *)
+                       page_address(rsp->tio->pvec[0]);
+
+               assert(sense);
+               clear_page(sense);
+               sense->length = cpu_to_be16(IET_SENSE_BUF_SIZE);
+
+               memcpy(sense->data, req->sense_buf, IET_SENSE_BUF_SIZE);
+               rsp->pdu.datasize = sizeof(struct iscsi_sense_data) +
+                       IET_SENSE_BUF_SIZE;
+
+               rsp->tio->size = (rsp->pdu.datasize + 3) & -4;
+               rsp->tio->offset = 0;
+       }
+
+       return rsp;
+}
+
+void iscsi_cmnd_set_sense(struct iscsi_cmnd *cmnd, u8 sense_key, u8 asc,
+                         u8 ascq)
+{
+       cmnd->status = SAM_STAT_CHECK_CONDITION;
+
+       cmnd->sense_buf[0] = 0xf0;
+       cmnd->sense_buf[2] = sense_key;
+       cmnd->sense_buf[7] = 6; // Additional sense length
+       cmnd->sense_buf[12] = asc;
+       cmnd->sense_buf[13] = ascq;
+}
+
+static struct iscsi_cmnd *create_sense_rsp(struct iscsi_cmnd *req,
+                                          u8 sense_key, u8 asc, u8 ascq)
+{
+       iscsi_cmnd_set_sense(req, sense_key, asc, ascq);
+       return create_scsi_rsp(req);
+}
+
+void send_scsi_rsp(struct iscsi_cmnd *req, void (*func)(struct iscsi_cmnd *))
+{
+       struct iscsi_cmnd *rsp;
+       struct iscsi_scsi_rsp_hdr *rsp_hdr;
+       u32 size;
+
+       func(req);
+       rsp = create_scsi_rsp(req);
+
+       switch (req->status) {
+       case SAM_STAT_GOOD:
+       case SAM_STAT_RESERVATION_CONFLICT:
+               rsp_hdr = (struct iscsi_scsi_rsp_hdr *) &rsp->pdu.bhs;
+               if ((size = cmnd_read_size(req)) != 0) {
+                       rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+                       rsp_hdr->residual_count = cpu_to_be32(size);
+               }
+               break;
+       default:
+               break;
+       }
+
+       iscsi_cmnd_init_write(rsp);
+}
+
+void send_data_rsp(struct iscsi_cmnd *req, void (*func)(struct iscsi_cmnd *))
+{
+       struct iscsi_cmnd *rsp;
+
+       func(req);
+
+       if (req->status == SAM_STAT_GOOD)
+               do_send_data_rsp(req);
+       else {
+               rsp = create_scsi_rsp(req);
+               iscsi_cmnd_init_write(rsp);
+       }
+}
+
+/**
+ * Free a command.
+ * Also frees the additional header.
+ *
+ * iscsi_cmnd_remove -
+ * @cmnd: ptr to command
+ */
+
+static void iscsi_cmnd_remove(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn;
+
+       if (!cmnd)
+               return;
+
+       if (cmnd_timer_active(cmnd)) {
+               clear_cmnd_timer_active(cmnd);
+               del_timer_sync(&cmnd->timer);
+       }
+
+       dprintk(D_GENERIC, "%p\n", cmnd);
+       conn = cmnd->conn;
+       kfree(cmnd->pdu.ahs);
+
+       if (!list_empty(&cmnd->list)) {
+               struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+
+               eprintk("cmnd %p still on some list?, %x, %x, %x, %x, %x, %x, %x %lx\n",
+                       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),
+                       conn->state);
+
+               if (cmnd->req) {
+                       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd->req);
+                       eprintk("%p %x %u\n", req, req->opcode, req->scb[0]);
+               }
+               dump_stack();
+               BUG();
+       }
+       list_del(&cmnd->list);
+       spin_lock(&conn->list_lock);
+       atomic_dec(&conn->nr_cmnds);
+       list_del(&cmnd->conn_list);
+       spin_unlock(&conn->list_lock);
+
+       if (cmnd->tio)
+               tio_put(cmnd->tio);
+
+       kmem_cache_free(iscsi_cmnd_cache, cmnd);
+}
+
+static void cmnd_skip_pdu(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       struct tio *tio = cmnd->tio;
+       char *addr;
+       u32 size;
+       int i;
+
+       eprintk("%x %x %x %u\n", cmnd_itt(cmnd), cmnd_opcode(cmnd),
+               cmnd_hdr(cmnd)->scb[0], cmnd->pdu.datasize);
+
+       if (!(size = cmnd->pdu.datasize))
+               return;
+
+       if (tio)
+               assert(tio->pg_cnt > 0);
+       else
+               tio = cmnd->tio = tio_alloc(1);
+
+       addr = page_address(tio->pvec[0]);
+       assert(addr);
+       size = (size + 3) & -4;
+       conn->read_size = size;
+       for (i = 0; size > PAGE_CACHE_SIZE; i++, size -= PAGE_CACHE_SIZE) {
+               assert(i < ISCSI_CONN_IOV_MAX);
+               conn->read_iov[i].iov_base = addr;
+               conn->read_iov[i].iov_len = PAGE_CACHE_SIZE;
+       }
+       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;
+}
+
+static void iscsi_cmnd_reject(struct iscsi_cmnd *req, int reason)
+{
+       struct iscsi_cmnd *rsp;
+       struct iscsi_reject_hdr *rsp_hdr;
+       struct tio *tio;
+       char *addr;
+
+       rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
+       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;
+
+       rsp->tio = tio = tio_alloc(1);
+       addr = page_address(tio->pvec[0]);
+       clear_page(addr);
+       memcpy(addr, &req->pdu.bhs, sizeof(struct iscsi_hdr));
+       tio->size = rsp->pdu.datasize = sizeof(struct iscsi_hdr);
+       cmnd_skip_pdu(req);
+
+       req->pdu.bhs.opcode = ISCSI_OP_PDU_REJECT;
+}
+
+static void cmnd_set_sn(struct iscsi_cmnd *cmnd, int set_stat_sn)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       struct iscsi_session *sess = conn->session;
+
+       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 + sess->max_queued_cmnds);
+}
+
+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);
+       dprintk(D_GENERIC, "%x,%x\n", 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;
+       }
+}
+
+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);
+       dprintk(D_GENERIC, "%d(%d)\n", cmd_sn, session->exp_cmd_sn);
+       if ((s32)(cmd_sn - session->exp_cmd_sn) >= 0)
+               return 0;
+       eprintk("sequence error (%x,%x)\n", cmd_sn, session->exp_cmd_sn);
+       return -ISCSI_REASON_PROTOCOL_ERROR;
+}
+
+static struct iscsi_cmnd *__cmnd_find_hash(struct iscsi_session *session, u32 itt, u32 ttt)
+{
+       struct list_head *head;
+       struct iscsi_cmnd *cmnd;
+
+       head = &session->cmnd_hash[cmnd_hashfn(itt)];
+
+       list_for_each_entry(cmnd, head, hash_list) {
+               if (cmnd->pdu.bhs.itt == itt) {
+                       if ((ttt != ISCSI_RESERVED_TAG) && (ttt != cmnd->target_task_tag))
+                               continue;
+                       return cmnd;
+               }
+       }
+
+       return NULL;
+}
+
+static struct iscsi_cmnd *cmnd_find_hash(struct iscsi_session *session, u32 itt, u32 ttt)
+{
+       struct iscsi_cmnd *cmnd;
+
+       spin_lock(&session->cmnd_hash_lock);
+
+       cmnd = __cmnd_find_hash(session, itt, ttt);
+
+       spin_unlock(&session->cmnd_hash_lock);
+
+       return cmnd;
+}
+
+static int cmnd_insert_hash_ttt(struct iscsi_cmnd *cmnd, u32 ttt)
+{
+       struct iscsi_session *session = cmnd->conn->session;
+       struct iscsi_cmnd *tmp;
+       struct list_head *head;
+       int err = 0;
+       u32 itt = cmnd->pdu.bhs.itt;
+
+       head = &session->cmnd_hash[cmnd_hashfn(itt)];
+
+       spin_lock(&session->cmnd_hash_lock);
+
+       tmp = __cmnd_find_hash(session, itt, ttt);
+       if (!tmp) {
+               list_add_tail(&cmnd->hash_list, head);
+               set_cmnd_hashed(cmnd);
+       } else
+               err = -ISCSI_REASON_TASK_IN_PROGRESS;
+
+       spin_unlock(&session->cmnd_hash_lock);
+
+       return err;
+}
+
+static int cmnd_insert_hash(struct iscsi_cmnd *cmnd)
+{
+       int err;
+
+       dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd->pdu.bhs.itt);
+
+       if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG)
+               return -ISCSI_REASON_PROTOCOL_ERROR;
+
+       err = cmnd_insert_hash_ttt(cmnd, ISCSI_RESERVED_TAG);
+       if (!err) {
+               update_stat_sn(cmnd);
+               err = check_cmd_sn(cmnd);
+       }
+
+       return err;
+}
+
+static void __cmnd_remove_hash(struct iscsi_cmnd *cmnd)
+{
+       list_del(&cmnd->hash_list);
+}
+
+static void cmnd_remove_hash(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_session *session = cmnd->conn->session;
+       struct iscsi_cmnd *tmp;
+
+       spin_lock(&session->cmnd_hash_lock);
+
+       tmp = __cmnd_find_hash(session, cmnd->pdu.bhs.itt,
+                              cmnd->target_task_tag);
+       if (tmp && tmp == cmnd)
+               __cmnd_remove_hash(tmp);
+       else
+               eprintk("%p:%x not found\n", cmnd, cmnd_itt(cmnd));
+
+       spin_unlock(&session->cmnd_hash_lock);
+}
+
+static void cmnd_skip_data(struct iscsi_cmnd *req)
+{
+       struct iscsi_cmnd *rsp;
+       struct iscsi_scsi_rsp_hdr *rsp_hdr;
+       u32 size;
+
+       rsp = get_rsp_cmnd(req);
+       rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
+       if (cmnd_opcode(rsp) != ISCSI_OP_SCSI_RSP) {
+               eprintk("unexpected response command %u\n", cmnd_opcode(rsp));
+               return;
+       }
+
+       size = cmnd_write_size(req);
+       if (size) {
+               rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+               rsp_hdr->residual_count = cpu_to_be32(size);
+       }
+       size = cmnd_read_size(req);
+       if (size) {
+               if (cmnd_hdr(req)->flags & ISCSI_CMD_WRITE) {
+                       rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
+                       rsp_hdr->bi_residual_count = cpu_to_be32(size);
+               } else {
+                       rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
+                       rsp_hdr->residual_count = cpu_to_be32(size);
+               }
+       }
+       req->pdu.bhs.opcode =
+               (req->pdu.bhs.opcode & ~ISCSI_OPCODE_MASK) | ISCSI_OP_SCSI_REJECT;
+
+       cmnd_skip_pdu(req);
+}
+
+static int cmnd_recv_pdu(struct iscsi_conn *conn, struct tio *tio, u32 offset, u32 size)
+{
+       int idx, i;
+       char *addr;
+
+       dprintk(D_GENERIC, "%p %u,%u\n", tio, offset, size);
+       offset += tio->offset;
+
+       if (!(offset < tio->offset + tio->size) ||
+           !(offset + size <= tio->offset + tio->size)) {
+               eprintk("%u %u %u %u", offset, size, tio->offset, tio->size);
+               return -EIO;
+       }
+       assert(offset < tio->offset + tio->size);
+       assert(offset + size <= tio->offset + tio->size);
+
+       idx = offset >> PAGE_CACHE_SHIFT;
+       offset &= ~PAGE_CACHE_MASK;
+
+       conn->read_msg.msg_iov = conn->read_iov;
+       conn->read_size = size = (size + 3) & -4;
+       conn->read_overflow = 0;
+
+       i = 0;
+       while (1) {
+               assert(tio->pvec[idx]);
+               addr = page_address(tio->pvec[idx]);
+               assert(addr);
+               conn->read_iov[i].iov_base =  addr + offset;
+               if (offset + size <= PAGE_CACHE_SIZE) {
+                       conn->read_iov[i].iov_len = size;
+                       conn->read_msg.msg_iovlen = ++i;
+                       break;
+               }
+               conn->read_iov[i].iov_len = PAGE_CACHE_SIZE - offset;
+               size -= conn->read_iov[i].iov_len;
+               offset = 0;
+               if (++i >= ISCSI_CONN_IOV_MAX) {
+                       conn->read_msg.msg_iovlen = i;
+                       conn->read_overflow = size;
+                       conn->read_size -= size;
+                       break;
+               }
+
+               idx++;
+       }
+
+       return 0;
+}
+
+static void set_offset_and_length(struct iet_volume *lu, u8 *cmd, loff_t *off, u32 *len)
+{
+       assert(lu);
+
+       switch (cmd[0]) {
+       case READ_6:
+       case WRITE_6:
+               *off = ((cmd[1] & 0x1f) << 16) + (cmd[2] << 8) + cmd[3];
+               *len = cmd[4];
+               if (!*len)
+                       *len = 256;
+               break;
+       case READ_10:
+       case WRITE_10:
+       case WRITE_VERIFY:
+               *off = (u32)cmd[2] << 24 | (u32)cmd[3] << 16 |
+                       (u32)cmd[4] << 8 | (u32)cmd[5];
+               *len = (cmd[7] << 8) + cmd[8];
+               break;
+       case READ_16:
+       case WRITE_16:
+               *off = (u64)cmd[2] << 56 | (u64)cmd[3] << 48 |
+                       (u64)cmd[4] << 40 | (u64)cmd[5] << 32 |
+                       (u64)cmd[6] << 24 | (u64)cmd[7] << 16 |
+                       (u64)cmd[8] << 8 | (u64)cmd[9];
+               *len = (u32)cmd[10] << 24 | (u32)cmd[11] << 16 |
+                       (u32)cmd[12] << 8 | (u32)cmd[13];
+               break;
+       default:
+               BUG();
+       }
+
+       *off <<= lu->blk_shift;
+       *len <<= lu->blk_shift;
+}
+
+static u32 translate_lun(u16 * data)
+{
+       u8 *p = (u8 *) data;
+       u32 lun = ~0U;
+
+       switch (*p >> 6) {
+       case 0:
+               lun = p[1];
+               break;
+       case 1:
+               lun = (0x3f & p[0]) << 8 | p[1];
+               break;
+       case 2:
+       case 3:
+       default:
+               eprintk("%u %u %u %u\n", data[0], data[1], data[2], data[3]);
+               break;
+       }
+
+       return lun;
+}
+
+static void send_r2t(struct iscsi_cmnd *req)
+{
+       struct iscsi_cmnd *rsp;
+       struct iscsi_r2t_hdr *rsp_hdr;
+       u32 length, offset, burst;
+       LIST_HEAD(send);
+
+       length = req->r2t_length;
+       burst = req->conn->session->param.max_burst_length;
+       offset = be32_to_cpu(cmnd_hdr(req)->data_length) - length;
+
+       do {
+               rsp = iscsi_cmnd_create_rsp_cmnd(req, 0);
+               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;
+               memcpy(rsp_hdr->lun, cmnd_hdr(req)->lun, 8);
+               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 (length > burst) {
+                       rsp_hdr->data_length = cpu_to_be32(burst);
+                       length -= burst;
+                       offset += burst;
+               } else {
+                       rsp_hdr->data_length = cpu_to_be32(length);
+                       length = 0;
+               }
+
+               dprintk(D_WRITE, "%x %u %u %u %u\n", cmnd_itt(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->list, &send);
+
+               if (++req->outstanding_r2t >= req->conn->session->param.max_outstanding_r2t)
+                       break;
+
+       } while (length);
+
+       iscsi_cmnds_init_write(&send);
+}
+
+static void scsi_cmnd_exec(struct iscsi_cmnd *cmnd)
+{
+       if (cmnd->r2t_length) {
+               if (!cmnd->is_unsolicited_data)
+                       send_r2t(cmnd);
+       } else {
+               if (cmnd->lun) {
+                       iscsi_scsi_queuecmnd(cmnd);
+               } else {
+                       iscsi_device_queue_cmnd(cmnd);
+               }
+       }
+}
+
+static int nop_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+{
+       u32 size, tmp;
+       int i, err = 0;
+
+       if (cmnd_ttt(cmnd) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
+               cmnd->req = cmnd_find_hash(conn->session, cmnd->pdu.bhs.itt,
+                                          cmnd->pdu.bhs.ttt);
+               if (!cmnd->req) {
+                       /*
+                        * We didn't request this NOP-Out (by sending a
+                        * NOP-In, see 10.18.2 of the RFC) or our fake NOP-Out
+                        * timed out.
+                        */
+                       eprintk("initiator bug %x\n", cmnd_itt(cmnd));
+                       err = -ISCSI_REASON_PROTOCOL_ERROR;
+                       goto out;
+               }
+
+               del_timer_sync(&cmnd->req->timer);
+               clear_cmnd_timer_active(cmnd->req);
+               dprintk(D_GENERIC, "NOP-Out: %p, ttt %x, timer %p\n",
+                       cmnd->req, cmnd_ttt(cmnd->req), &cmnd->req->timer);
+       }
+
+       if (cmnd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG)) {
+               if (!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE))
+                       eprintk("%s\n", "initiator bug!");
+               update_stat_sn(cmnd);
+               err = check_cmd_sn(cmnd);
+               if (err)
+                       goto out;
+       } else if ((err = cmnd_insert_hash(cmnd)) < 0) {
+               eprintk("ignore this request %x\n", cmnd_itt(cmnd));
+               goto out;
+       }
+
+       if ((size = cmnd->pdu.datasize)) {
+               size = (size + 3) & -4;
+               conn->read_msg.msg_iov = conn->read_iov;
+               if (cmnd->pdu.bhs.itt != cpu_to_be32(ISCSI_RESERVED_TAG)) {
+                       struct tio *tio;
+                       int pg_cnt = get_pgcnt(size, 0);
+
+                       assert(pg_cnt < ISCSI_CONN_IOV_MAX);
+                       cmnd->tio = tio = tio_alloc(pg_cnt);
+                       tio_set(tio, size, 0);
+
+                       for (i = 0; i < pg_cnt; i++) {
+                               conn->read_iov[i].iov_base
+                                       = page_address(tio->pvec[i]);
+                               tmp = min_t(u32, size, PAGE_CACHE_SIZE);
+                               conn->read_iov[i].iov_len = tmp;
+                               conn->read_size += tmp;
+                               size -= tmp;
+                       }
+               } else {
+                       for (i = 0; i < ISCSI_CONN_IOV_MAX; i++) {
+                               conn->read_iov[i].iov_base = dummy_data;
+                               tmp = min_t(u32, size, sizeof(dummy_data));
+                               conn->read_iov[i].iov_len = tmp;
+                               conn->read_size += tmp;
+                               size -= tmp;
+                       }
+               }
+               assert(!size);
+               conn->read_overflow = size;
+               conn->read_msg.msg_iovlen = i;
+       }
+
+out:
+       return err;
+}
+
+static u32 get_next_ttt(struct iscsi_session *session)
+{
+       u32 ttt;
+
+       if (session->next_ttt == ISCSI_RESERVED_TAG)
+               session->next_ttt++;
+       ttt = session->next_ttt++;
+
+       return cpu_to_be32(ttt);
+}
+
+static void scsi_cmnd_start(struct iscsi_conn *conn, struct iscsi_cmnd *req)
+{
+       struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
+
+       dprintk(D_GENERIC, "scsi command: %02x\n", req_hdr->scb[0]);
+
+       req->lun = volume_get(conn->session->target, translate_lun(req_hdr->lun));
+       if (!req->lun) {
+               switch (req_hdr->scb[0]) {
+               case INQUIRY:
+               case REPORT_LUNS:
+                       break;
+               default:
+                       eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
+                       create_sense_rsp(req, ILLEGAL_REQUEST, 0x25, 0x0);
+                       cmnd_skip_data(req);
+                       goto out;
+               }
+       } else
+               set_cmnd_lunit(req);
+
+       switch (req_hdr->scb[0]) {
+       case SERVICE_ACTION_IN:
+               if ((req_hdr->scb[1] & 0x1f) != 0x10)
+                       goto error;
+       case INQUIRY:
+       case REPORT_LUNS:
+       case TEST_UNIT_READY:
+       case SYNCHRONIZE_CACHE:
+       case VERIFY:
+       case VERIFY_16:
+       case START_STOP:
+       case READ_CAPACITY:
+       case MODE_SENSE:
+       case REQUEST_SENSE:
+       case RESERVE:
+       case RELEASE:
+       {
+               if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) {
+                       /* unexpected unsolicited data */
+                       eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
+                       create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc);
+                       cmnd_skip_data(req);
+               }
+               break;
+       }
+       case READ_6:
+       case READ_10:
+       case READ_16:
+       {
+               loff_t offset;
+               u32 length;
+
+               if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) {
+                       /* unexpected unsolicited data */
+                       eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
+                       create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc);
+                       cmnd_skip_data(req);
+                       break;
+               }
+
+               set_offset_and_length(req->lun, req_hdr->scb, &offset, &length);
+               req->tio = tio_alloc(get_pgcnt(length, offset));
+               tio_set(req->tio, length, offset);
+               break;
+       }
+       case WRITE_6:
+       case WRITE_10:
+       case WRITE_16:
+       case WRITE_VERIFY:
+       {
+               struct iscsi_sess_param *param = &conn->session->param;
+               loff_t offset;
+               u32 length;
+
+               req->r2t_length = be32_to_cpu(req_hdr->data_length) - req->pdu.datasize;
+               req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL);
+               req->target_task_tag = get_next_ttt(conn->session);
+
+               if (LUReadonly(req->lun)) {
+                       create_sense_rsp(req, DATA_PROTECT, 0x27, 0x0);
+                       cmnd_skip_data(req);
+                       break;
+               }
+
+               if (!param->immediate_data && req->pdu.datasize)
+                       eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
+
+               if (param->initial_r2t && !(req_hdr->flags & ISCSI_CMD_FINAL))
+                       eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
+
+               if (req_hdr->scb[0] == WRITE_VERIFY && req_hdr->scb[1] & 0x02)
+                       eprintk("Verification is ignored %x\n", cmnd_itt(req));
+
+               set_offset_and_length(req->lun, req_hdr->scb, &offset, &length);
+               if (cmnd_write_size(req) != length)
+                       eprintk("%x %u %u\n", cmnd_itt(req), cmnd_write_size(req), length);
+
+               req->tio = tio_alloc(get_pgcnt(length, offset));
+               tio_set(req->tio, length, offset);
+
+               if (req->pdu.datasize) {
+                       if (cmnd_recv_pdu(conn, req->tio, 0, req->pdu.datasize) < 0)
+                               assert(0);
+               }
+               break;
+       }
+       error:
+       default:
+               eprintk("Unsupported %x\n", req_hdr->scb[0]);
+               create_sense_rsp(req, ILLEGAL_REQUEST, 0x20, 0x0);
+               cmnd_skip_data(req);
+               break;
+       }
+
+out:
+       return;
+}
+
+static void data_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_data_out_hdr *req = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
+       struct iscsi_cmnd *scsi_cmnd = NULL;
+       u32 offset = be32_to_cpu(req->buffer_offset);
+
+       update_stat_sn(cmnd);
+
+       cmnd->req = scsi_cmnd = cmnd_find_hash(conn->session, req->itt, req->ttt);
+       if (!scsi_cmnd) {
+               eprintk("unable to find scsi task %x %x\n",
+                       cmnd_itt(cmnd), cmnd_ttt(cmnd));
+               goto skip_data;
+       }
+
+       if (scsi_cmnd->r2t_length < cmnd->pdu.datasize) {
+               eprintk("invalid data len %x %u %u\n",
+                       cmnd_itt(scsi_cmnd), cmnd->pdu.datasize, scsi_cmnd->r2t_length);
+               goto skip_data;
+       }
+
+       if (scsi_cmnd->r2t_length + offset != cmnd_write_size(scsi_cmnd)) {
+               eprintk("%x %u %u %u\n", cmnd_itt(scsi_cmnd), scsi_cmnd->r2t_length,
+                       offset, cmnd_write_size(scsi_cmnd));
+               goto skip_data;
+       }
+
+       scsi_cmnd->r2t_length -= cmnd->pdu.datasize;
+
+       if (req->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
+               /* unsolicited burst data */
+               if (scsi_cmnd->pdu.bhs.flags & ISCSI_FLG_FINAL) {
+                       eprintk("unexpected data from %x %x\n",
+                               cmnd_itt(cmnd), cmnd_ttt(cmnd));
+                       goto skip_data;
+               }
+       }
+
+       dprintk(D_WRITE, "%u %p %p %p %u %u\n", req->ttt, cmnd, scsi_cmnd,
+               scsi_cmnd->tio, offset, cmnd->pdu.datasize);
+
+       if (cmnd_recv_pdu(conn, scsi_cmnd->tio, offset, cmnd->pdu.datasize) < 0)
+               goto skip_data;
+       return;
+
+skip_data:
+       cmnd->pdu.bhs.opcode = ISCSI_OP_DATA_REJECT;
+       cmnd_skip_pdu(cmnd);
+       return;
+}
+
+static void data_out_end(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_data_out_hdr *req = (struct iscsi_data_out_hdr *) &cmnd->pdu.bhs;
+       struct iscsi_cmnd *scsi_cmnd;
+       u32 offset;
+
+       assert(cmnd);
+       scsi_cmnd = cmnd->req;
+       assert(scsi_cmnd);
+
+       if (conn->read_overflow) {
+               eprintk("%x %u\n", cmnd_itt(cmnd), conn->read_overflow);
+               assert(scsi_cmnd->tio);
+               offset = be32_to_cpu(req->buffer_offset);
+               offset += cmnd->pdu.datasize - conn->read_overflow;
+               if (cmnd_recv_pdu(conn, scsi_cmnd->tio, offset, conn->read_overflow) < 0)
+                       assert(0);
+               return;
+       }
+
+       if (req->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
+               if (req->flags & ISCSI_FLG_FINAL) {
+                       scsi_cmnd->is_unsolicited_data = 0;
+                       if (!cmnd_pending(scsi_cmnd))
+                               scsi_cmnd_exec(scsi_cmnd);
+               }
+       } else {
+               /* TODO : proper error handling */
+               if (!(req->flags & ISCSI_FLG_FINAL) && scsi_cmnd->r2t_length == 0)
+                       eprintk("initiator error %x\n", cmnd_itt(scsi_cmnd));
+
+               if (!(req->flags & ISCSI_FLG_FINAL))
+                       goto out;
+
+               scsi_cmnd->outstanding_r2t--;
+
+               if (scsi_cmnd->r2t_length == 0)
+                       assert(list_empty(&scsi_cmnd->pdu_list));
+
+               scsi_cmnd_exec(scsi_cmnd);
+       }
+
+out:
+       iscsi_cmnd_remove(cmnd);
+       return;
+}
+
+static int __cmnd_abort(struct iscsi_cmnd *cmnd)
+{
+       if (cmnd_waitio(cmnd))
+               return -ISCSI_RESPONSE_UNKNOWN_TASK;
+
+       if (cmnd->conn->read_cmnd != cmnd)
+               cmnd_release(cmnd, 1);
+       else if (cmnd_rxstart(cmnd))
+               set_cmnd_tmfabort(cmnd);
+       else
+               return -ISCSI_RESPONSE_UNKNOWN_TASK;
+
+       return 0;
+}
+
+static int cmnd_abort(struct iscsi_session *session, u32 itt)
+{
+       struct iscsi_cmnd *cmnd;
+       int err =  -ISCSI_RESPONSE_UNKNOWN_TASK;
+
+       if ((cmnd = cmnd_find_hash(session, itt, ISCSI_RESERVED_TAG))) {
+               eprintk("%x %x %x %u %u %u %u\n", cmnd_itt(cmnd), cmnd_opcode(cmnd),
+                       cmnd->r2t_length, cmnd_scsicode(cmnd),
+                       cmnd_write_size(cmnd), cmnd->is_unsolicited_data,
+                       cmnd->outstanding_r2t);
+               err = __cmnd_abort(cmnd);
+       }
+
+       return err;
+}
+
+static int target_reset(struct iscsi_cmnd *req, u32 lun, int all)
+{
+       struct iscsi_target *target = req->conn->session->target;
+       struct iscsi_session *session;
+       struct iscsi_conn *conn;
+       struct iscsi_cmnd *cmnd, *tmp;
+       struct iet_volume *volume;
+
+       list_for_each_entry(session, &target->session_list, list) {
+               list_for_each_entry(conn, &session->conn_list, list) {
+                       list_for_each_entry_safe(cmnd, tmp, &conn->pdu_list, conn_list) {
+                               if (cmnd == req)
+                                       continue;
+
+                               if (all)
+                                       __cmnd_abort(cmnd);
+                               else if (translate_lun(cmnd_hdr(cmnd)->lun) == lun)
+                                       __cmnd_abort(cmnd);
+                       }
+               }
+       }
+
+       list_for_each_entry(volume, &target->volumes, list) {
+               if (all || volume->lun == lun) {
+                       /* force release */
+                       volume_release(volume, 0, 1);
+                       /* power-on, reset, or bus device reset occurred */
+                       ua_establish_for_all_sessions(target, volume->lun,
+                                                     0x29, 0x0);
+               }
+       }
+
+       return 0;
+}
+
+static void task_set_abort(struct iscsi_cmnd *req)
+{
+       struct iscsi_session *session = req->conn->session;
+       struct iscsi_conn *conn;
+       struct iscsi_cmnd *cmnd, *tmp;
+
+       list_for_each_entry(conn, &session->conn_list, list) {
+               list_for_each_entry_safe(cmnd, tmp, &conn->pdu_list, conn_list) {
+                       if (cmnd != req)
+                               __cmnd_abort(cmnd);
+               }
+       }
+}
+
+static inline char *tmf_desc(int fun)
+{
+       static char *tmf_desc[] = {
+               "Unknown Function",
+               "Abort Task",
+               "Abort Task Set",
+               "Clear ACA",
+               "Clear Task Set",
+               "Logical Unit Reset",
+               "Target Warm Reset",
+               "Target Cold Reset",
+               "Task Reassign",
+        };
+
+       if ((fun < ISCSI_FUNCTION_ABORT_TASK) ||
+                               (fun > ISCSI_FUNCTION_TASK_REASSIGN))
+               fun = 0;
+
+       return tmf_desc[fun];
+}
+
+static inline char *rsp_desc(int rsp)
+{
+       static char *rsp_desc[] = {
+               "Function Complete",
+               "Unknown Task",
+               "Unknown LUN",
+               "Task Allegiant",
+               "Failover Unsupported",
+               "Function Unsupported",
+               "No Authorization",
+               "Function Rejected",
+               "Unknown Response",
+       };
+
+       if (((rsp < ISCSI_RESPONSE_FUNCTION_COMPLETE) ||
+                       (rsp > ISCSI_RESPONSE_NO_AUTHORIZATION)) &&
+                       (rsp != ISCSI_RESPONSE_FUNCTION_REJECTED))
+               rsp = 8;
+       else if (rsp == ISCSI_RESPONSE_FUNCTION_REJECTED)
+               rsp = 7;
+
+       return rsp_desc[rsp];
+}
+
+static void execute_task_management(struct iscsi_cmnd *req)
+{
+       struct iscsi_conn *conn = req->conn;
+       struct iscsi_session *session = conn->session;
+       struct iscsi_target *target = session->target;
+       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;
+       u32 lun;
+       int err, function = req_hdr->function & ISCSI_FUNCTION_MASK;
+
+       rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
+       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 = ISCSI_RESPONSE_FUNCTION_COMPLETE;
+
+       switch (function) {
+       case ISCSI_FUNCTION_ABORT_TASK:
+       case ISCSI_FUNCTION_ABORT_TASK_SET:
+       case ISCSI_FUNCTION_CLEAR_ACA:
+       case ISCSI_FUNCTION_CLEAR_TASK_SET:
+       case ISCSI_FUNCTION_LOGICAL_UNIT_RESET:
+               lun = translate_lun(req_hdr->lun);
+               if (!volume_lookup(target, lun)) {
+                       rsp_hdr->response = ISCSI_RESPONSE_UNKNOWN_LUN;
+                       goto out;
+               }
+       }
+
+       switch (function) {
+       case ISCSI_FUNCTION_ABORT_TASK:
+               if ((err = cmnd_abort(conn->session, req_hdr->rtt)) < 0)
+                       rsp_hdr->response = -err;
+               break;
+       case ISCSI_FUNCTION_ABORT_TASK_SET:
+               task_set_abort(req);
+               break;
+       case ISCSI_FUNCTION_CLEAR_ACA:
+               rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
+               break;
+       case ISCSI_FUNCTION_CLEAR_TASK_SET:
+               rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
+               break;
+       case ISCSI_FUNCTION_LOGICAL_UNIT_RESET:
+               target_reset(req, translate_lun(req_hdr->lun), 0);
+               break;
+       case ISCSI_FUNCTION_TARGET_WARM_RESET:
+       case ISCSI_FUNCTION_TARGET_COLD_RESET:
+               target_reset(req, 0, 1);
+               if (function == ISCSI_FUNCTION_TARGET_COLD_RESET)
+                       set_cmnd_close(rsp);
+               break;
+       case ISCSI_FUNCTION_TASK_REASSIGN:
+               rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
+               break;
+       default:
+               rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_REJECTED;
+               break;
+       }
+out:
+       iprintk("%s (%02x) issued on tid:%d lun:%d by sid:%llu (%s)\n",
+                               tmf_desc(function), function, target->tid,
+                               translate_lun(req_hdr->lun), session->sid,
+                               rsp_desc(rsp_hdr->response));
+
+       iscsi_cmnd_init_write(rsp);
+}
+
+static void nop_hdr_setup(struct iscsi_hdr *hdr, u8 opcode, __be32 itt,
+                         __be32 ttt)
+{
+       hdr->opcode = opcode;
+       hdr->flags = ISCSI_FLG_FINAL;
+       hdr->itt = itt;
+       hdr->ttt = ttt;
+}
+
+static void nop_out_exec(struct iscsi_cmnd *req)
+{
+       struct iscsi_cmnd *rsp;
+
+       if (cmnd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
+               rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
+
+               nop_hdr_setup(&rsp->pdu.bhs, ISCSI_OP_NOP_IN, req->pdu.bhs.itt,
+                             cpu_to_be32(ISCSI_RESERVED_TAG));
+
+               if (req->pdu.datasize)
+                       assert(req->tio);
+               else
+                       assert(!req->tio);
+
+               if (req->tio) {
+                       tio_get(req->tio);
+                       rsp->tio = req->tio;
+               }
+
+               assert(get_pgcnt(req->pdu.datasize, 0) < ISCSI_CONN_IOV_MAX);
+               rsp->pdu.datasize = req->pdu.datasize;
+               iscsi_cmnd_init_write(rsp);
+       } else {
+               if (req->req) {
+                       dprintk(D_GENERIC, "releasing NOP-Out %p, ttt %x; "
+                               "removing NOP-In %p, ttt %x\n", req->req,
+                               cmnd_ttt(req->req), req, cmnd_ttt(req));
+                       cmnd_release(req->req, 0);
+               }
+               iscsi_cmnd_remove(req);
+       }
+}
+
+static void nop_in_timeout(unsigned long data)
+{
+       struct iscsi_cmnd *req = (struct iscsi_cmnd *)data;
+
+       printk(KERN_INFO "NOP-In ping timed out - closing sid:cid %llu:%u\n",
+              req->conn->session->sid, req->conn->cid);
+       clear_cmnd_timer_active(req);
+       conn_close(req->conn);
+}
+
+/* create a fake NOP-Out req and treat the NOP-In as our rsp to it */
+void send_nop_in(struct iscsi_conn *conn)
+{
+       struct iscsi_cmnd *req = cmnd_alloc(conn, 1);
+       struct iscsi_cmnd *rsp = iscsi_cmnd_create_rsp_cmnd(req, 0);
+
+       req->target_task_tag = get_next_ttt(conn->session);
+
+
+       nop_hdr_setup(&req->pdu.bhs, ISCSI_OP_NOP_OUT,
+                     cpu_to_be32(ISCSI_RESERVED_TAG), req->target_task_tag);
+       nop_hdr_setup(&rsp->pdu.bhs, ISCSI_OP_NOP_IN,
+                     cpu_to_be32(ISCSI_RESERVED_TAG), req->target_task_tag);
+
+       dprintk(D_GENERIC, "NOP-Out: %p, ttt %x, timer %p; "
+               "NOP-In: %p, ttt %x;\n", req, cmnd_ttt(req), &req->timer, rsp,
+               cmnd_ttt(rsp));
+
+       init_timer(&req->timer);
+       req->timer.data = (unsigned long)req;
+       req->timer.function = nop_in_timeout;
+
+       if (cmnd_insert_hash_ttt(req, req->target_task_tag)) {
+               eprintk("%s\n",
+                       "failed to insert fake NOP-Out into hash table");
+               cmnd_release(rsp, 0);
+               cmnd_release(req, 0);
+       } else
+               iscsi_cmnd_init_write(rsp);
+}
+
+static void nop_in_tx_end(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       u32 t;
+
+       if (cmnd->pdu.bhs.ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+               return;
+
+       /*
+        * NOP-In ping issued by the target.
+        * FIXME: Sanitize the NOP timeout earlier, during configuration
+        */
+       t = conn->session->target->trgt_param.nop_timeout;
+
+       if (!t || t > conn->session->target->trgt_param.nop_interval) {
+               eprintk("Adjusting NOPTimeout of tid %u from %u to %u "
+                       "(== NOPInterval)\n", conn->session->target->tid,
+                       t,
+                       conn->session->target->trgt_param.nop_interval);
+               t = conn->session->target->trgt_param.nop_interval;
+               conn->session->target->trgt_param.nop_timeout = t;
+       }
+
+       dprintk(D_GENERIC, "NOP-In %p, %x: timer %p\n", cmnd, cmnd_ttt(cmnd),
+               &cmnd->req->timer);
+
+       set_cmnd_timer_active(cmnd->req);
+       mod_timer(&cmnd->req->timer, jiffies + HZ * t);
+}
+
+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;
+
+       req_hdr = (struct iscsi_logout_req_hdr *)&req->pdu.bhs;
+       rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
+       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;
+       set_cmnd_close(rsp);
+       iscsi_cmnd_init_write(rsp);
+}
+
+static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd)
+{
+       dprintk(D_GENERIC, "%p,%x,%u\n", cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn);
+
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_NOP_OUT:
+               nop_out_exec(cmnd);
+               break;
+       case ISCSI_OP_SCSI_CMD:
+               scsi_cmnd_exec(cmnd);
+               break;
+       case ISCSI_OP_SCSI_TASK_MGT_MSG:
+               execute_task_management(cmnd);
+               break;
+       case ISCSI_OP_LOGOUT_CMD:
+               logout_exec(cmnd);
+               break;
+       case ISCSI_OP_SCSI_REJECT:
+               iscsi_cmnd_init_write(get_rsp_cmnd(cmnd));
+               break;
+       case ISCSI_OP_TEXT_CMD:
+       case ISCSI_OP_SNACK_CMD:
+               break;
+       default:
+               eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
+               break;
+       }
+}
+
+static void __cmnd_send_pdu(struct iscsi_conn *conn, struct tio *tio, u32 offset, u32 size)
+{
+       dprintk(D_GENERIC, "%p %u,%u\n", tio, offset, size);
+       offset += tio->offset;
+
+       assert(offset <= tio->offset + tio->size);
+       assert(offset + size <= tio->offset + tio->size);
+
+       conn->write_tcmnd = tio;
+       conn->write_offset = offset;
+       conn->write_size += size;
+}
+
+static void cmnd_send_pdu(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+{
+       u32 size;
+       struct tio *tio;
+
+       if (!cmnd->pdu.datasize)
+               return;
+
+       size = (cmnd->pdu.datasize + 3) & -4;
+       tio = cmnd->tio;
+       assert(tio);
+       assert(tio->size == size);
+       __cmnd_send_pdu(conn, tio, 0, size);
+}
+
+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 *)&opt, sizeof(opt));
+       set_fs(oldfs);
+}
+
+void cmnd_release(struct iscsi_cmnd *cmnd, int force)
+{
+       struct iscsi_cmnd *req, *rsp;
+       int is_last = 0;
+
+       if (!cmnd)
+               return;
+
+/*     eprintk("%x %lx %d\n", cmnd_opcode(cmnd), cmnd->flags, force); */
+
+       req = cmnd->req;
+       is_last = cmnd_final(cmnd);
+
+       if (force) {
+               while (!list_empty(&cmnd->pdu_list)) {
+                       rsp = list_entry(cmnd->pdu_list.next, struct iscsi_cmnd, pdu_list);
+                       list_del_init(&rsp->list);
+                       list_del(&rsp->pdu_list);
+                       iscsi_cmnd_remove(rsp);
+               }
+               list_del_init(&cmnd->list);
+       } else
+               if (cmnd_queued(cmnd))
+                       iscsi_scsi_dequeuecmnd(cmnd);
+
+       if (cmnd_hashed(cmnd))
+               cmnd_remove_hash(cmnd);
+
+       if (cmnd_lunit(cmnd)) {
+               assert(cmnd->lun);
+               volume_put(cmnd->lun);
+       }
+
+       list_del_init(&cmnd->pdu_list);
+       iscsi_cmnd_remove(cmnd);
+
+       if (is_last) {
+               assert(!force);
+               assert(req);
+               cmnd_release(req, 0);
+       }
+
+       return;
+}
+
+void cmnd_tx_start(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       struct iovec *iop;
+
+       dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
+       assert(cmnd);
+       iscsi_cmnd_set_length(&cmnd->pdu);
+
+       set_cork(conn->sock, 1);
+
+       conn->write_iop = iop = conn->write_iov;
+       iop->iov_base = &cmnd->pdu.bhs;
+       iop->iov_len = sizeof(cmnd->pdu.bhs);
+       iop++;
+       conn->write_size = sizeof(cmnd->pdu.bhs);
+
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_NOP_IN:
+               if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG) {
+                       /* NOP-In ping generated by us. Don't advance StatSN. */
+                       cmnd_set_sn(cmnd, 0);
+                       cmnd_set_sn(cmnd->req, 0);
+                       cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn);
+                       cmnd->req->pdu.bhs.sn = cpu_to_be32(conn->stat_sn);
+               } else
+                       cmnd_set_sn(cmnd, 1);
+               cmnd_send_pdu(conn, cmnd);
+               break;
+       case ISCSI_OP_SCSI_RSP:
+               cmnd_set_sn(cmnd, 1);
+               cmnd_send_pdu(conn, cmnd);
+               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;
+
+               cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0);
+               offset = rsp->buffer_offset;
+               rsp->buffer_offset = cpu_to_be32(offset);
+               __cmnd_send_pdu(conn, cmnd->tio, offset, cmnd->pdu.datasize);
+               break;
+       }
+       case ISCSI_OP_LOGOUT_RSP:
+               cmnd_set_sn(cmnd, 1);
+               break;
+       case ISCSI_OP_R2T:
+               cmnd_set_sn(cmnd, 0);
+               cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn);
+               break;
+       case ISCSI_OP_ASYNC_MSG:
+               cmnd_set_sn(cmnd, 1);
+               break;
+       case ISCSI_OP_REJECT:
+               cmnd_set_sn(cmnd, 1);
+               cmnd_send_pdu(conn, cmnd);
+               break;
+       default:
+               eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
+               break;
+       }
+
+       iop->iov_len = 0;
+       // move this?
+       conn->write_size = (conn->write_size + 3) & -4;
+       iscsi_dump_pdu(&cmnd->pdu);
+}
+
+void cmnd_tx_end(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+
+       dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_NOP_IN:
+               nop_in_tx_end(cmnd);
+               break;
+       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:
+               eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
+               assert(0);
+               break;
+       }
+
+       if (cmnd_close(cmnd))
+               conn_close(conn);
+
+       list_del_init(&cmnd->list);
+       set_cork(cmnd->conn->sock, 0);
+}
+
+/**
+ * Push the command for execution.
+ * This functions reorders the commands.
+ * Called from the read thread.
+ *
+ * iscsi_session_push_cmnd -
+ * @cmnd: ptr to command
+ */
+
+static void iscsi_session_push_cmnd(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_session *session = cmnd->conn->session;
+       struct list_head *entry;
+       u32 cmd_sn;
+
+       dprintk(D_GENERIC, "%p:%x %u,%u\n",
+               cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn, session->exp_cmd_sn);
+
+       if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
+               iscsi_cmnd_exec(cmnd);
+               return;
+       }
+
+       cmd_sn = cmnd->pdu.bhs.sn;
+       if (cmd_sn == session->exp_cmd_sn) {
+               while (1) {
+                       session->exp_cmd_sn = ++cmd_sn;
+                       iscsi_cmnd_exec(cmnd);
+
+                       if (list_empty(&session->pending_list))
+                               break;
+                       cmnd = list_entry(session->pending_list.next, struct iscsi_cmnd, list);
+                       if (cmnd->pdu.bhs.sn != cmd_sn)
+                               break;
+/*                     eprintk("find out-of-order %x %u %u\n", */
+/*                             cmnd_itt(cmnd), cmd_sn, cmnd->pdu.bhs.sn); */
+                       list_del_init(&cmnd->list);
+                       clear_cmnd_pending(cmnd);
+               }
+       } else {
+/*             eprintk("out-of-order %x %u %u\n", */
+/*                     cmnd_itt(cmnd), cmd_sn, session->exp_cmd_sn); */
+
+               set_cmnd_pending(cmnd);
+               if (before(cmd_sn, session->exp_cmd_sn)) /* close the conn */
+                       eprintk("unexpected cmd_sn (%u,%u)\n", cmd_sn, session->exp_cmd_sn);
+
+               if (after(cmd_sn, session->exp_cmd_sn + session->max_queued_cmnds))
+                       eprintk("too large cmd_sn (%u,%u)\n", cmd_sn, session->exp_cmd_sn);
+
+               list_for_each(entry, &session->pending_list) {
+                       struct iscsi_cmnd *tmp = list_entry(entry, struct iscsi_cmnd, list);
+                       if (before(cmd_sn, tmp->pdu.bhs.sn))
+                               break;
+               }
+
+               assert(list_empty(&cmnd->list));
+
+               list_add_tail(&cmnd->list, entry);
+       }
+}
+
+static int check_segment_length(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       struct iscsi_sess_param *param = &conn->session->param;
+
+       if (cmnd->pdu.datasize > param->max_recv_data_length) {
+               eprintk("data too long %x %u %u\n", cmnd_itt(cmnd),
+                       cmnd->pdu.datasize, param->max_recv_data_length);
+
+               if (get_pgcnt(cmnd->pdu.datasize, 0) > ISCSI_CONN_IOV_MAX) {
+                       conn_close(conn);
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+void cmnd_rx_start(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+       int err = 0;
+
+       iscsi_dump_pdu(&cmnd->pdu);
+
+       set_cmnd_rxstart(cmnd);
+       if (check_segment_length(cmnd) < 0)
+               return;
+
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_NOP_OUT:
+               err = nop_out_start(conn, cmnd);
+               break;
+       case ISCSI_OP_SCSI_CMD:
+               if (!(err = cmnd_insert_hash(cmnd)))
+                       scsi_cmnd_start(conn, cmnd);
+               break;
+       case ISCSI_OP_SCSI_TASK_MGT_MSG:
+               err = cmnd_insert_hash(cmnd);
+               break;
+       case ISCSI_OP_SCSI_DATA_OUT:
+               data_out_start(conn, cmnd);
+               break;
+       case ISCSI_OP_LOGOUT_CMD:
+               err = cmnd_insert_hash(cmnd);
+               break;
+       case ISCSI_OP_TEXT_CMD:
+       case ISCSI_OP_SNACK_CMD:
+               err = -ISCSI_REASON_UNSUPPORTED_COMMAND;
+               break;
+       default:
+               err = -ISCSI_REASON_UNSUPPORTED_COMMAND;
+               break;
+       }
+
+       if (err < 0) {
+               eprintk("%x %x %d\n", cmnd_opcode(cmnd), cmnd_itt(cmnd), err);
+               iscsi_cmnd_reject(cmnd, -err);
+       }
+}
+
+void cmnd_rx_end(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_conn *conn = cmnd->conn;
+
+       if (cmnd_tmfabort(cmnd)) {
+               cmnd_release(cmnd, 1);
+               return;
+       }
+
+       dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
+       switch (cmnd_opcode(cmnd)) {
+       case ISCSI_OP_SCSI_REJECT:
+       case ISCSI_OP_NOP_OUT:
+       case ISCSI_OP_SCSI_CMD:
+       case ISCSI_OP_SCSI_TASK_MGT_MSG:
+       case ISCSI_OP_TEXT_CMD:
+       case ISCSI_OP_LOGOUT_CMD:
+               iscsi_session_push_cmnd(cmnd);
+               break;
+       case ISCSI_OP_SCSI_DATA_OUT:
+               data_out_end(conn, cmnd);
+               break;
+       case ISCSI_OP_SNACK_CMD:
+               break;
+       case ISCSI_OP_PDU_REJECT:
+               iscsi_cmnd_init_write(get_rsp_cmnd(cmnd));
+               break;
+       case ISCSI_OP_DATA_REJECT:
+               cmnd_release(cmnd, 0);
+               break;
+       default:
+               eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
+               BUG();
+               break;
+       }
+}
+
+static void iscsi_exit(void)
+{
+       wthread_module_exit();
+
+       unregister_chrdev(ctr_major, ctr_name);
+
+       iet_procfs_exit();
+
+       event_exit();
+
+       tio_exit();
+
+       iotype_exit();
+
+       ua_exit();
+
+       if (iscsi_cmnd_cache)
+               kmem_cache_destroy(iscsi_cmnd_cache);
+}
+
+static int iscsi_init(void)
+{
+       int err = -ENOMEM;
+
+       printk("iSCSI Enterprise Target Software - version %s\n", IET_VERSION_STRING);
+
+       if ((ctr_major = register_chrdev(0, ctr_name, &ctr_fops)) < 0) {
+               eprintk("failed to register the control device %d\n", ctr_major);
+               return ctr_major;
+       }
+
+       if ((err = iet_procfs_init()) < 0)
+               goto err;
+
+       if ((err = event_init()) < 0)
+               goto err;
+
+       iscsi_cmnd_cache = KMEM_CACHE(iscsi_cmnd, 0);
+       if (!iscsi_cmnd_cache)
+               goto err;
+
+       err = ua_init();
+       if (err < 0)
+               goto err;
+
+       if ((err = tio_init()) < 0)
+               goto err;
+
+       if ((err = iotype_init()) < 0)
+               goto err;
+
+       if ((err = wthread_module_init()) < 0)
+               goto err;
+
+       return 0;
+
+err:
+       iscsi_exit();
+       return err;
+}
+
+module_param(worker_thread_pool_size, ulong, S_IRUGO);
+MODULE_PARM_DESC(worker_thread_pool_size,
+                "Size of the worker thread pool "
+                "(0 = dedicated threads per target (default))");
+
+module_param(debug_enable_flags, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug_enable_flags,
+                "debug bitmask, low bits (0 ... 8) used, see iscsi_dbg.h");
+
+module_init(iscsi_init);
+module_exit(iscsi_exit);
+
+MODULE_VERSION(IET_VERSION_STRING);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("iSCSI Enterprise Target");
+MODULE_AUTHOR("IET development team <iscsitarget-devel@lists.sourceforge.net>");
diff --git a/ubuntu/iscsitarget/iscsi.h b/ubuntu/iscsitarget/iscsi.h
new file mode 100644 (file)
index 0000000..92ce252
--- /dev/null
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ * Copyright (C) 2008 Arne Redlich <agr@powerkom-dd.de>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#ifndef __ISCSI_H__
+#define __ISCSI_H__
+
+#include <linux/completion.h>
+#include <linux/pagemap.h>
+#include <linux/seq_file.h>
+#include <linux/mm.h>
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
+#include <net/sock.h>
+
+#include "iscsi_hdr.h"
+#include "iet_u.h"
+
+#define IET_SENSE_BUF_SIZE      18
+
+struct iscsi_sess_param {
+       int initial_r2t;
+       int immediate_data;
+       int max_connections;
+       int max_recv_data_length;
+       int max_xmit_data_length;
+       int max_burst_length;
+       int first_burst_length;
+       int default_wait_time;
+       int default_retain_time;
+       int max_outstanding_r2t;
+       int data_pdu_inorder;
+       int data_sequence_inorder;
+       int error_recovery_level;
+       int header_digest;
+       int data_digest;
+       int ofmarker;
+       int ifmarker;
+       int ofmarkint;
+       int ifmarkint;
+};
+
+struct iscsi_trgt_param {
+       int wthreads;
+       int target_type;
+       int queued_cmnds;
+       int nop_interval;
+       int nop_timeout;
+};
+
+struct tio {
+       u32 pg_cnt;
+
+       pgoff_t idx;
+       u32 offset;
+       u32 size;
+
+       struct page **pvec;
+
+       atomic_t count;
+};
+
+struct network_thread_info {
+       struct task_struct *task;
+       unsigned long flags;
+       struct list_head active_conns;
+
+       spinlock_t nthread_lock;
+
+       void (*old_state_change)(struct sock *);
+       void (*old_data_ready)(struct sock *, int);
+       void (*old_write_space)(struct sock *);
+};
+
+struct worker_thread_info;
+
+struct worker_thread {
+       struct task_struct *w_task;
+       struct list_head w_list;
+       struct worker_thread_info *w_info;
+};
+
+struct worker_thread_info {
+       spinlock_t wthread_lock;
+
+       u32 nr_running_wthreads;
+
+       struct list_head wthread_list;
+       struct list_head work_queue;
+
+       wait_queue_head_t wthread_sleep;
+};
+
+struct iscsi_cmnd;
+
+struct target_type {
+       int id;
+       int (*execute_cmnd) (struct iscsi_cmnd *);
+};
+
+enum iscsi_device_state {
+       IDEV_RUNNING,
+       IDEV_DEL,
+};
+
+struct iscsi_target {
+       struct list_head t_list;
+       u32 tid;
+
+       char name[ISCSI_NAME_LEN];
+
+       struct iscsi_sess_param sess_param;
+       struct iscsi_trgt_param trgt_param;
+
+       atomic_t nr_volumes;
+       struct list_head volumes;
+       struct list_head session_list;
+
+       /* Prevents races between add/del session and adding UAs */
+       spinlock_t session_list_lock;
+
+       struct network_thread_info nthread_info;
+       /* Points either to own list or global pool */
+       struct worker_thread_info * wthread_info;
+
+       struct semaphore target_sem;
+       struct completion *done;
+};
+
+struct iscsi_queue {
+       spinlock_t queue_lock;
+       struct iscsi_cmnd *ordered_cmnd;
+       struct list_head wait_list;
+       int active_cnt;
+};
+
+struct iet_volume {
+       u32 lun;
+
+       enum iscsi_device_state l_state;
+       atomic_t l_count;
+
+       struct iscsi_target *target;
+       struct list_head list;
+
+       struct iscsi_queue queue;
+
+       u8 scsi_id[SCSI_ID_LEN];
+       u8 scsi_sn[SCSI_SN_LEN];
+
+       u32 blk_shift;
+       u64 blk_cnt;
+
+       u64 reserve_sid;
+       spinlock_t reserve_lock;
+
+       unsigned long flags;
+
+       struct iotype *iotype;
+       void *private;
+};
+
+enum lu_flags {
+       LU_READONLY,
+       LU_WCACHE,
+       LU_RCACHE,
+};
+
+#define LUReadonly(lu) test_bit(LU_READONLY, &(lu)->flags)
+#define SetLUReadonly(lu) set_bit(LU_READONLY, &(lu)->flags)
+
+#define LUWCache(lu) test_bit(LU_WCACHE, &(lu)->flags)
+#define SetLUWCache(lu) set_bit(LU_WCACHE, &(lu)->flags)
+#define ClearLUWCache(lu) clear_bit(LU_WCACHE, &(lu)->flags)
+
+#define LURCache(lu) test_bit(LU_RCACHE, &(lu)->flags)
+#define SetLURCache(lu) set_bit(LU_RCACHE, &(lu)->flags)
+#define ClearLURCache(lu) clear_bit(LU_RCACHE, &(lu)->flags)
+
+#define IET_HASH_ORDER         8
+#define        cmnd_hashfn(itt)        hash_long((itt), IET_HASH_ORDER)
+
+#define UA_HASH_LEN 8
+
+struct iscsi_session {
+       struct list_head list;
+       struct iscsi_target *target;
+       struct completion *done;
+       char *initiator;
+       u64 sid;
+
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+
+       struct iscsi_sess_param param;
+       u32 max_queued_cmnds;
+
+       struct list_head conn_list;
+
+       struct list_head pending_list;
+
+       spinlock_t cmnd_hash_lock;
+       struct list_head cmnd_hash[1 << IET_HASH_ORDER];
+
+       spinlock_t ua_hash_lock;
+       struct list_head ua_hash[UA_HASH_LEN];
+
+       u32 next_ttt;
+};
+
+enum connection_state_bit {
+       CONN_ACTIVE,
+       CONN_CLOSING,
+       CONN_WSPACE_WAIT,
+       CONN_NEED_NOP_IN,
+};
+
+#define ISCSI_CONN_IOV_MAX     (((256 << 10) >> PAGE_SHIFT) + 1)
+
+struct iscsi_conn {
+       struct list_head list;                  /* list entry in session list */
+       struct iscsi_session *session;          /* owning session */
+
+       u16 cid;
+       unsigned long state;
+
+       u32 stat_sn;
+       u32 exp_stat_sn;
+
+       int hdigest_type;
+       int ddigest_type;
+
+       struct list_head poll_list;
+
+       struct file *file;
+       struct socket *sock;
+       spinlock_t list_lock;
+       atomic_t nr_cmnds;
+       atomic_t nr_busy_cmnds;
+       struct list_head pdu_list;              /* in/outcoming pdus */
+       struct list_head write_list;            /* list of data pdus to be sent */
+       struct timer_list nop_timer;
+
+       struct iscsi_cmnd *read_cmnd;
+       struct msghdr read_msg;
+       struct iovec read_iov[ISCSI_CONN_IOV_MAX];
+       u32 read_size;
+       u32 read_overflow;
+       int read_state;
+
+       struct iscsi_cmnd *write_cmnd;
+       struct iovec write_iov[ISCSI_CONN_IOV_MAX];
+       struct iovec *write_iop;
+       struct tio *write_tcmnd;
+       u32 write_size;
+       u32 write_offset;
+       int write_state;
+
+       struct hash_desc rx_hash;
+       struct hash_desc tx_hash;
+       struct scatterlist hash_sg[ISCSI_CONN_IOV_MAX];
+};
+
+struct iscsi_pdu {
+       struct iscsi_hdr bhs;
+       void *ahs;
+       unsigned int ahssize;
+       unsigned int datasize;
+};
+
+typedef void (iet_show_info_t)(struct seq_file *seq, struct iscsi_target *target);
+
+struct iscsi_cmnd {
+       struct list_head list;
+       struct list_head conn_list;
+       unsigned long flags;
+       struct iscsi_conn *conn;
+       struct iet_volume *lun;
+
+       struct iscsi_pdu pdu;
+       struct list_head pdu_list;
+
+       struct list_head hash_list;
+
+       struct tio *tio;
+
+       u8 status;
+
+       struct timer_list timer;
+
+       u32 r2t_sn;
+       u32 r2t_length;
+       u32 is_unsolicited_data;
+       u32 target_task_tag;
+       u32 outstanding_r2t;
+
+       u32 hdigest;
+       u32 ddigest;
+
+       struct iscsi_cmnd *req;
+
+       unsigned char sense_buf[IET_SENSE_BUF_SIZE];
+};
+
+struct ua_entry {
+       struct list_head entry;
+       struct iscsi_session *session; /* only used for debugging ATM */
+       u32 lun;
+       u8 asc;
+       u8 ascq;
+};
+
+#define ISCSI_OP_SCSI_REJECT   ISCSI_OP_VENDOR1_CMD
+#define ISCSI_OP_PDU_REJECT    ISCSI_OP_VENDOR2_CMD
+#define ISCSI_OP_DATA_REJECT   ISCSI_OP_VENDOR3_CMD
+#define ISCSI_OP_SCSI_ABORT    ISCSI_OP_VENDOR4_CMD
+
+/* iscsi.c */
+extern unsigned long worker_thread_pool_size;
+extern struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *, int);
+extern void cmnd_rx_start(struct iscsi_cmnd *);
+extern void cmnd_rx_end(struct iscsi_cmnd *);
+extern void cmnd_tx_start(struct iscsi_cmnd *);
+extern void cmnd_tx_end(struct iscsi_cmnd *);
+extern void cmnd_release(struct iscsi_cmnd *, int);
+extern void send_data_rsp(struct iscsi_cmnd *, void (*)(struct iscsi_cmnd *));
+extern void send_scsi_rsp(struct iscsi_cmnd *, void (*)(struct iscsi_cmnd *));
+extern void iscsi_cmnd_set_sense(struct iscsi_cmnd *, u8 sense_key, u8 asc,
+                                u8 ascq);
+extern void send_nop_in(struct iscsi_conn *);
+
+/* conn.c */
+extern struct iscsi_conn *conn_lookup(struct iscsi_session *, u16);
+extern int conn_add(struct iscsi_session *, struct conn_info *);
+extern int conn_del(struct iscsi_session *, struct conn_info *);
+extern void conn_del_all(struct iscsi_session *);
+extern int conn_free(struct iscsi_conn *);
+extern void conn_close(struct iscsi_conn *);
+extern void conn_info_show(struct seq_file *, struct iscsi_session *);
+
+/* nthread.c */
+extern int nthread_init(struct iscsi_target *);
+extern int nthread_start(struct iscsi_target *);
+extern int nthread_stop(struct iscsi_target *);
+extern void __nthread_wakeup(struct network_thread_info *);
+extern void nthread_wakeup(struct iscsi_target *);
+
+/* wthread.c */
+extern int wthread_init(struct worker_thread_info *info);
+extern int wthread_start(struct worker_thread_info *info, int wthreads, u32 tid);
+extern int wthread_stop(struct worker_thread_info *info);
+extern void wthread_queue(struct iscsi_cmnd *);
+extern struct target_type *target_type_array[];
+extern int wthread_module_init(void);
+extern void wthread_module_exit(void);
+extern struct worker_thread_info *worker_thread_pool;
+
+/* target.c */
+extern int target_lock(struct iscsi_target *, int);
+extern void target_unlock(struct iscsi_target *);
+struct iscsi_target *target_lookup_by_id(u32);
+extern int target_add(struct target_info *);
+extern int target_del(u32 id);
+extern void target_del_all(void);
+extern struct seq_operations iet_seq_op;
+
+/* config.c */
+extern int iet_procfs_init(void);
+extern void iet_procfs_exit(void);
+extern int iet_info_show(struct seq_file *, iet_show_info_t *);
+
+/* session.c */
+extern struct file_operations session_seq_fops;
+extern struct iscsi_session *session_lookup(struct iscsi_target *, u64);
+extern int session_add(struct iscsi_target *, struct session_info *);
+extern int session_del(struct iscsi_target *, u64);
+extern void session_del_all(struct iscsi_target *);
+
+/* volume.c */
+extern struct file_operations volume_seq_fops;
+extern int volume_add(struct iscsi_target *, struct volume_info *);
+extern int iscsi_volume_del(struct iscsi_target *, struct volume_info *);
+extern void iscsi_volume_destroy(struct iet_volume *);
+extern struct iet_volume *volume_lookup(struct iscsi_target *, u32);
+extern struct iet_volume *volume_get(struct iscsi_target *, u32);
+extern void volume_put(struct iet_volume *);
+extern int volume_reserve(struct iet_volume *volume, u64 sid);
+extern int volume_release(struct iet_volume *volume, u64 sid, int force);
+extern int is_volume_reserved(struct iet_volume *volume, u64 sid);
+
+/* tio.c */
+extern int tio_init(void);
+extern void tio_exit(void);
+extern struct tio *tio_alloc(int);
+extern void tio_get(struct tio *);
+extern void tio_put(struct tio *);
+extern void tio_set(struct tio *, u32, loff_t);
+extern int tio_read(struct iet_volume *, struct tio *);
+extern int tio_write(struct iet_volume *, struct tio *);
+extern int tio_sync(struct iet_volume *, struct tio *);
+
+/* iotype.c */
+extern struct iotype *get_iotype(const char *name);
+extern void put_iotype(struct iotype *iot);
+
+/* params.c */
+extern int iscsi_param_set(struct iscsi_target *, struct iscsi_param_info *, int);
+
+/* target_disk.c */
+extern struct target_type disk_ops;
+
+/* event.c */
+extern int event_send(u32, u64, u32, u32, int);
+extern int event_init(void);
+extern void event_exit(void);
+
+/* ua.c */
+int ua_init(void);
+void ua_exit(void);
+struct ua_entry * ua_get_first(struct iscsi_session *, u32 lun);
+struct ua_entry * ua_get_match(struct iscsi_session *, u32 lun, u8 asc,
+                              u8 ascq);
+void ua_free(struct ua_entry *);
+int ua_pending(struct iscsi_session *, u32 lun);
+void ua_establish_for_session(struct iscsi_session *, u32 lun, u8 asc,
+                            u8 ascq);
+void ua_establish_for_other_sessions(struct iscsi_session *, u32 lun, u8 asc,
+                                    u8 ascq);
+void ua_establish_for_all_sessions(struct iscsi_target *, u32 lun, u8 asc,
+                                  u8 ascq);
+
+#define get_pgcnt(size, offset)        ((((size) + ((offset) & ~PAGE_CACHE_MASK)) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT)
+
+static inline void iscsi_cmnd_get_length(struct iscsi_pdu *pdu)
+{
+#if defined(__BIG_ENDIAN)
+       pdu->ahssize = pdu->bhs.length.ahslength * 4;
+       pdu->datasize = pdu->bhs.length.datalength;
+#elif defined(__LITTLE_ENDIAN)
+       pdu->ahssize = (pdu->bhs.length & 0xff) * 4;
+       pdu->datasize = be32_to_cpu(pdu->bhs.length & ~0xff);
+#else
+#error
+#endif
+}
+
+static inline void iscsi_cmnd_set_length(struct iscsi_pdu *pdu)
+{
+#if defined(__BIG_ENDIAN)
+       pdu->bhs.length.ahslength = pdu->ahssize / 4;
+       pdu->bhs.length.datalength = pdu->datasize;
+#elif defined(__LITTLE_ENDIAN)
+       pdu->bhs.length = cpu_to_be32(pdu->datasize) | (pdu->ahssize / 4);
+#else
+#error
+#endif
+}
+
+#define cmnd_hdr(cmnd) ((struct iscsi_scsi_cmd_hdr *) (&((cmnd)->pdu.bhs)))
+#define cmnd_ttt(cmnd) cpu_to_be32((cmnd)->pdu.bhs.ttt)
+#define cmnd_itt(cmnd) cpu_to_be32((cmnd)->pdu.bhs.itt)
+#define cmnd_opcode(cmnd) ((cmnd)->pdu.bhs.opcode & ISCSI_OPCODE_MASK)
+#define cmnd_scsicode(cmnd) cmnd_hdr(cmnd)->scb[0]
+
+#define        SECTOR_SIZE_BITS        9
+
+enum cmnd_flags {
+       CMND_hashed,
+       CMND_queued,
+       CMND_final,
+       CMND_waitio,
+       CMND_close,
+       CMND_lunit,
+       CMND_pending,
+       CMND_tmfabort,
+       CMND_rxstart,
+       CMND_timer_active,
+};
+
+#define set_cmnd_hashed(cmnd)  set_bit(CMND_hashed, &(cmnd)->flags)
+#define cmnd_hashed(cmnd)      test_bit(CMND_hashed, &(cmnd)->flags)
+
+#define set_cmnd_queued(cmnd)  set_bit(CMND_queued, &(cmnd)->flags)
+#define cmnd_queued(cmnd)      test_bit(CMND_queued, &(cmnd)->flags)
+
+#define set_cmnd_final(cmnd)   set_bit(CMND_final, &(cmnd)->flags)
+#define cmnd_final(cmnd)       test_bit(CMND_final, &(cmnd)->flags)
+
+#define set_cmnd_waitio(cmnd)  set_bit(CMND_waitio, &(cmnd)->flags)
+#define cmnd_waitio(cmnd)      test_bit(CMND_waitio, &(cmnd)->flags)
+
+#define set_cmnd_close(cmnd)   set_bit(CMND_close, &(cmnd)->flags)
+#define cmnd_close(cmnd)       test_bit(CMND_close, &(cmnd)->flags)
+
+#define set_cmnd_lunit(cmnd)   set_bit(CMND_lunit, &(cmnd)->flags)
+#define cmnd_lunit(cmnd)       test_bit(CMND_lunit, &(cmnd)->flags)
+
+#define set_cmnd_pending(cmnd) set_bit(CMND_pending, &(cmnd)->flags)
+#define clear_cmnd_pending(cmnd)       clear_bit(CMND_pending, &(cmnd)->flags)
+#define cmnd_pending(cmnd)     test_bit(CMND_pending, &(cmnd)->flags)
+
+#define set_cmnd_tmfabort(cmnd)        set_bit(CMND_tmfabort, &(cmnd)->flags)
+#define cmnd_tmfabort(cmnd)    test_bit(CMND_tmfabort, &(cmnd)->flags)
+
+#define set_cmnd_rxstart(cmnd) set_bit(CMND_rxstart, &(cmnd)->flags)
+#define cmnd_rxstart(cmnd)     test_bit(CMND_rxstart, &(cmnd)->flags)
+
+#define set_cmnd_timer_active(cmnd)  set_bit(CMND_timer_active, &(cmnd)->flags)
+#define clear_cmnd_timer_active(cmnd) \
+                               clear_bit(CMND_timer_active, &(cmnd)->flags)
+#define cmnd_timer_active(cmnd) test_bit(CMND_timer_active, &(cmnd)->flags)
+
+#define VENDOR_ID      "IET"
+#define PRODUCT_ID     "VIRTUAL-DISK"
+#define PRODUCT_REV    "0"
+
+#endif /* __ISCSI_H__ */
diff --git a/ubuntu/iscsitarget/iscsi_dbg.h b/ubuntu/iscsitarget/iscsi_dbg.h
new file mode 100644 (file)
index 0000000..d8d5966
--- /dev/null
@@ -0,0 +1,137 @@
+#ifndef ISCSI_DBG_H
+#define ISCSI_DBG_H
+
+#define D_SETUP                (1UL << 0)
+#define D_EXIT         (1UL << 1)
+#define D_GENERIC      (1UL << 2)
+#define D_READ         (1UL << 3)
+#define D_WRITE        (1UL << 4)
+#define D_IOD          (1UL << 5)
+#define D_THREAD       (1UL << 6)
+#define D_TASK_MGT     (1UL << 7)
+#define D_IOMODE       (1UL << 8)
+#define D_UAC           (1UL << 9)
+
+#define D_DATA         (D_READ | D_WRITE)
+
+extern unsigned long debug_enable_flags;
+
+#define PFX "iscsi_trgt: "
+
+#define dprintk(debug, fmt, args...) do {                      \
+       if ((debug) & debug_enable_flags) {                     \
+               printk(KERN_DEBUG PFX "%s(%d) " fmt, __FUNCTION__,\
+                                               __LINE__, args);\
+       }                                                       \
+} while (0)
+
+#define dprintk_ua(ua, sess, lun)                                      \
+       dprintk(D_UAC, "sess %llu, lun %u: %p %x %x\n",                 \
+               (sess)->sid, lun, ua,                                   \
+               (ua) ? (ua)->asc : 0,                                   \
+               (ua) ? (ua)->ascq : 0)
+
+#define eprintk(fmt, args...) do {                             \
+       printk(KERN_ERR PFX "%s(%d) " fmt, __FUNCTION__,        \
+                                               __LINE__, args);\
+} while (0)
+
+#define iprintk(X...) printk(KERN_INFO PFX X)
+
+#define assert(p) do {                                         \
+       if (!(p)) {                                             \
+               printk(KERN_CRIT PFX "BUG at %s:%d assert(%s)\n",\
+                      __FILE__, __LINE__, #p);                 \
+               dump_stack();                                   \
+               BUG();                                          \
+       }                                                       \
+} while (0)
+
+#ifdef D_IOV
+static inline void iscsi_dump_iov(struct msghdr *msg)
+{
+       int i;
+       printk(PFX "%p, %d\n", msg->msg_iov, msg->msg_iovlen);
+       for (i = 0; i < min_t(size_t, msg->msg_iovlen, ISCSI_CONN_IOV_MAX); i++)
+               printk(PFX "%d: %p,%d\n", i, msg->msg_iov[i].iov_base,
+                                               msg->msg_iov[i].iov_len);
+}
+#else
+#define iscsi_dump_iov(x) do {} while (0)
+#endif
+
+#ifdef D_DUMP_PDU
+static void iscsi_dump_char(int ch)
+{
+       static unsigned char text[16];
+       static int i = 0;
+
+       if (ch < 0) {
+               while ((i % 16) != 0) {
+                       printk("   ");
+                       text[i] = ' ';
+                       i++;
+                       if ((i % 16) == 0)
+                               printk(" | %.16s |\n", text);
+                       else if ((i % 4) == 0)
+                               printk(" |");
+               }
+               i = 0;
+               return;
+       }
+
+       text[i] = (ch < 0x20 || (ch >= 0x80 && ch <= 0xa0)) ? ' ' : ch;
+       printk(" %02x", ch);
+       i++;
+       if ((i % 16) == 0) {
+               printk(" | %.16s |\n", text);
+               i = 0;
+       } else if ((i % 4) == 0)
+               printk(" |");
+}
+
+static inline void iscsi_dump_pdu(struct iscsi_pdu *pdu)
+{
+       unsigned char *buf;
+       int i;
+
+       buf = (void *)&pdu->bhs;
+       printk(PFX "BHS: (%p,%d)\n", buf, sizeof(pdu->bhs));
+       for (i = 0; i < sizeof(pdu->bhs); i++)
+               iscsi_dump_char(*buf++);
+       iscsi_dump_char(-1);
+
+       buf = (void *)pdu->ahs;
+       printk(PFX "AHS: (%p,%d)\n", buf, pdu->ahssize);
+       for (i = 0; i < pdu->ahssize; i++)
+               iscsi_dump_char(*buf++);
+       iscsi_dump_char(-1);
+
+       printk(PFX "Data: (%d)\n", pdu->datasize);
+}
+
+#else
+#define iscsi_dump_pdu(x) do {} while (0)
+#endif
+
+#define show_param(param)\
+{\
+       dprintk(D_SETUP, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",\
+               (param)->initial_r2t,\
+               (param)->immediate_data,\
+               (param)->max_connections,\
+               (param)->max_recv_data_length,\
+               (param)->max_xmit_data_length,\
+               (param)->max_burst_length,\
+               (param)->first_burst_length,\
+               (param)->default_wait_time,\
+               (param)->default_retain_time,\
+               (param)->max_outstanding_r2t,\
+               (param)->data_pdu_inorder,\
+               (param)->data_sequence_inorder,\
+               (param)->error_recovery_level,\
+               (param)->header_digest,\
+               (param)->data_digest);\
+}
+
+#endif
diff --git a/ubuntu/iscsitarget/iscsi_hdr.h b/ubuntu/iscsitarget/iscsi_hdr.h
new file mode 100644 (file)
index 0000000..2cbcd4f
--- /dev/null
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#ifndef __ISCSI_HDR_H__
+#define __ISCSI_HDR_H__
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+#define ISCSI_VERSION                  0
+
+#ifndef __packed
+#define __packed __attribute__ ((packed))
+#endif
+
+struct iscsi_hdr {
+       u8  opcode;                     /* 0 */
+       u8  flags;
+       u8  spec1[2];
+#if defined(__BIG_ENDIAN_BITFIELD)
+       struct {                        /* 4 */
+               unsigned ahslength : 8;
+               unsigned datalength : 24;
+       } length;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+       u32 length;                     /* 4 */
+#endif
+       u16 lun[4];                     /* 8 */
+       u32 itt;                        /* 16 */
+       u32 ttt;                        /* 20 */
+       u32 sn;                         /* 24 */
+       u32 exp_sn;                     /* 28 */
+       u32 max_sn;                     /* 32 */
+       u32 spec3[3];                   /* 36 */
+} __packed;                            /* 48 */
+
+/* Opcode encoding bits */
+#define ISCSI_OP_RETRY                 0x80
+#define ISCSI_OP_IMMEDIATE             0x40
+#define ISCSI_OPCODE_MASK              0x3F
+
+/* Client to Server Message Opcode values */
+#define ISCSI_OP_NOP_OUT               0x00
+#define ISCSI_OP_SCSI_CMD              0x01
+#define ISCSI_OP_SCSI_TASK_MGT_MSG     0x02
+#define ISCSI_OP_LOGIN_CMD             0x03
+#define ISCSI_OP_TEXT_CMD              0x04
+#define ISCSI_OP_SCSI_DATA_OUT         0x05
+#define ISCSI_OP_LOGOUT_CMD            0x06
+#define ISCSI_OP_SNACK_CMD             0x10
+
+#define ISCSI_OP_VENDOR1_CMD           0x1c
+#define ISCSI_OP_VENDOR2_CMD           0x1d
+#define ISCSI_OP_VENDOR3_CMD           0x1e
+#define ISCSI_OP_VENDOR4_CMD           0x1f
+
+/* Server to Client Message Opcode values */
+#define ISCSI_OP_NOP_IN                0x20
+#define ISCSI_OP_SCSI_RSP              0x21
+#define ISCSI_OP_SCSI_TASK_MGT_RSP     0x22
+#define ISCSI_OP_LOGIN_RSP             0x23
+#define ISCSI_OP_TEXT_RSP              0x24
+#define ISCSI_OP_SCSI_DATA_IN          0x25
+#define ISCSI_OP_LOGOUT_RSP            0x26
+#define ISCSI_OP_R2T                   0x31
+#define ISCSI_OP_ASYNC_MSG             0x32
+#define ISCSI_OP_REJECT                        0x3f
+
+struct iscsi_ahs_hdr {
+       u16 ahslength;
+       u8 ahstype;
+} __packed;
+
+#define ISCSI_AHSTYPE_CDB              1
+#define ISCSI_AHSTYPE_RLENGTH          2
+
+union iscsi_sid {
+       struct {
+               u8 isid[6];             /* Initiator Session ID */
+               u16 tsih;               /* Target Session ID */
+       } id;
+       u64 id64;
+} __packed;
+
+struct iscsi_scsi_cmd_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 data_length;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u8  scb[16];
+} __packed;
+
+#define ISCSI_CMD_FINAL                0x80
+#define ISCSI_CMD_READ         0x40
+#define ISCSI_CMD_WRITE                0x20
+#define ISCSI_CMD_ATTR_MASK    0x07
+#define ISCSI_CMD_UNTAGGED     0x00
+#define ISCSI_CMD_SIMPLE       0x01
+#define ISCSI_CMD_ORDERED      0x02
+#define ISCSI_CMD_HEAD_OF_QUEUE        0x03
+#define ISCSI_CMD_ACA          0x04
+
+struct iscsi_cdb_ahdr {
+       u16 ahslength;
+       u8  ahstype;
+       u8  reserved;
+       u8  cdb[0];
+} __packed;
+
+struct iscsi_rlength_ahdr {
+       u16 ahslength;
+       u8  ahstype;
+       u8  reserved;
+       u32 read_length;
+} __packed;
+
+struct iscsi_scsi_rsp_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  response;
+       u8  cmd_status;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd1[2];
+       u32 itt;
+       u32 snack;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 exp_data_sn;
+       u32 bi_residual_count;
+       u32 residual_count;
+} __packed;
+
+#define ISCSI_FLG_RESIDUAL_UNDERFLOW           0x02
+#define ISCSI_FLG_RESIDUAL_OVERFLOW            0x04
+#define ISCSI_FLG_BIRESIDUAL_UNDERFLOW         0x08
+#define ISCSI_FLG_BIRESIDUAL_OVERFLOW          0x10
+
+#define ISCSI_RESPONSE_COMMAND_COMPLETED       0x00
+#define ISCSI_RESPONSE_TARGET_FAILURE          0x01
+
+struct iscsi_sense_data {
+       u16 length;
+       u8  data[0];
+} __packed;
+
+struct iscsi_task_mgt_hdr {
+       u8  opcode;
+       u8  function;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 rtt;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u32 ref_cmd_sn;
+       u32 exp_data_sn;
+       u32 rsvd2[2];
+} __packed;
+
+#define ISCSI_FUNCTION_MASK                    0x7f
+
+#define ISCSI_FUNCTION_ABORT_TASK              1
+#define ISCSI_FUNCTION_ABORT_TASK_SET          2
+#define ISCSI_FUNCTION_CLEAR_ACA               3
+#define ISCSI_FUNCTION_CLEAR_TASK_SET          4
+#define ISCSI_FUNCTION_LOGICAL_UNIT_RESET      5
+#define ISCSI_FUNCTION_TARGET_WARM_RESET       6
+#define ISCSI_FUNCTION_TARGET_COLD_RESET       7
+#define ISCSI_FUNCTION_TASK_REASSIGN           8
+
+struct iscsi_task_rsp_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  response;
+       u8  rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 rsvd3;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 rsvd4[3];
+} __packed;
+
+#define ISCSI_RESPONSE_FUNCTION_COMPLETE       0
+#define ISCSI_RESPONSE_UNKNOWN_TASK            1
+#define ISCSI_RESPONSE_UNKNOWN_LUN             2
+#define ISCSI_RESPONSE_TASK_ALLEGIANT          3
+#define ISCSI_RESPONSE_FAILOVER_UNSUPPORTED    4
+#define ISCSI_RESPONSE_FUNCTION_UNSUPPORTED    5
+#define ISCSI_RESPONSE_NO_AUTHORIZATION                6
+#define ISCSI_RESPONSE_FUNCTION_REJECTED       255
+
+struct iscsi_data_out_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 ttt;
+       u32 rsvd2;
+       u32 exp_stat_sn;
+       u32 rsvd3;
+       u32 data_sn;
+       u32 buffer_offset;
+       u32 rsvd4;
+} __packed;
+
+struct iscsi_data_in_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  rsvd1;
+       u8  cmd_status;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 ttt;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 data_sn;
+       u32 buffer_offset;
+       u32 residual_count;
+} __packed;
+
+#define ISCSI_FLG_STATUS               0x01
+
+struct iscsi_r2t_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 ttt;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 r2t_sn;
+       u32 buffer_offset;
+       u32 data_length;
+} __packed;
+
+struct iscsi_async_msg_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 ffffffff;
+       u32 rsvd2;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u8  async_event;
+       u8  async_vcode;
+       u16 param1;
+       u16 param2;
+       u16 param3;
+       u32 rsvd3;
+} __packed;
+
+#define ISCSI_ASYNC_SCSI               0
+#define ISCSI_ASYNC_LOGOUT             1
+#define ISCSI_ASYNC_DROP_CONNECTION    2
+#define ISCSI_ASYNC_DROP_SESSION       3
+#define ISCSI_ASYNC_PARAM_REQUEST      4
+#define ISCSI_ASYNC_VENDOR             255
+
+struct iscsi_text_req_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 ttt;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u32 rsvd3[4];
+} __packed;
+
+struct iscsi_text_rsp_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 ttt;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 rsvd3[3];
+} __packed;
+
+struct iscsi_login_req_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  max_version;                /* Max. version supported */
+       u8  min_version;                /* Min. version supported */
+       u8  ahslength;
+       u8  datalength[3];
+       union iscsi_sid sid;
+       u32 itt;                        /* Initiator Task Tag */
+       u16 cid;                        /* Connection ID */
+       u16 rsvd1;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u32 rsvd2[4];
+} __packed;
+
+struct iscsi_login_rsp_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  max_version;                /* Max. version supported */
+       u8  active_version;             /* Active version */
+       u8  ahslength;
+       u8  datalength[3];
+       union iscsi_sid sid;
+       u32 itt;                        /* Initiator Task Tag */
+       u32 rsvd1;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u8  status_class;               /* see Login RSP ststus classes below */
+       u8  status_detail;              /* see Login RSP Status details below */
+       u8  rsvd2[10];
+} __packed;
+
+#define ISCSI_FLG_FINAL                        0x80
+#define ISCSI_FLG_TRANSIT              0x80
+#define ISCSI_FLG_CSG_SECURITY         0x00
+#define ISCSI_FLG_CSG_LOGIN            0x04
+#define ISCSI_FLG_CSG_FULL_FEATURE     0x0c
+#define ISCSI_FLG_CSG_MASK             0x0c
+#define ISCSI_FLG_NSG_SECURITY         0x00
+#define ISCSI_FLG_NSG_LOGIN            0x01
+#define ISCSI_FLG_NSG_FULL_FEATURE     0x03
+#define ISCSI_FLG_NSG_MASK             0x03
+
+/* Login Status response classes */
+#define ISCSI_STATUS_SUCCESS           0x00
+#define ISCSI_STATUS_REDIRECT          0x01
+#define ISCSI_STATUS_INITIATOR_ERR     0x02
+#define ISCSI_STATUS_TARGET_ERR                0x03
+
+/* Login Status response detail codes */
+/* Class-0 (Success) */
+#define ISCSI_STATUS_ACCEPT            0x00
+
+/* Class-1 (Redirection) */
+#define ISCSI_STATUS_TGT_MOVED_TEMP    0x01
+#define ISCSI_STATUS_TGT_MOVED_PERM    0x02
+
+/* Class-2 (Initiator Error) */
+#define ISCSI_STATUS_INIT_ERR          0x00
+#define ISCSI_STATUS_AUTH_FAILED       0x01
+#define ISCSI_STATUS_TGT_FORBIDDEN     0x02
+#define ISCSI_STATUS_TGT_NOT_FOUND     0x03
+#define ISCSI_STATUS_TGT_REMOVED       0x04
+#define ISCSI_STATUS_NO_VERSION                0x05
+#define ISCSI_STATUS_TOO_MANY_CONN     0x06
+#define ISCSI_STATUS_MISSING_FIELDS    0x07
+#define ISCSI_STATUS_CONN_ADD_FAILED   0x08
+#define ISCSI_STATUS_INV_SESSION_TYPE  0x09
+#define ISCSI_STATUS_SESSION_NOT_FOUND 0x0a
+#define ISCSI_STATUS_INV_REQ_TYPE      0x0b
+
+/* Class-3 (Target Error) */
+#define ISCSI_STATUS_TARGET_ERROR      0x00
+#define ISCSI_STATUS_SVC_UNAVAILABLE   0x01
+#define ISCSI_STATUS_NO_RESOURCES      0x02
+
+struct iscsi_logout_req_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u16 cid;
+       u16 rsvd3;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u32 rsvd4[4];
+} __packed;
+
+struct iscsi_logout_rsp_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  response;
+       u8  rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 rsvd3;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 rsvd4;
+       u16 time2wait;
+       u16 time2retain;
+       u32 rsvd5;
+} __packed;
+
+struct iscsi_snack_req_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 itt;
+       u32 ttt;
+       u32 rsvd3;
+       u32 exp_stat_sn;
+       u32 rsvd4[2];
+       u32 beg_run;
+       u32 run_length;
+} __packed;
+
+struct iscsi_reject_hdr {
+       u8  opcode;
+       u8  flags;
+       u8  reason;
+       u8  rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u32 rsvd2[2];
+       u32 ffffffff;
+       u32 rsvd3;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 data_sn;
+       u32 rsvd4[2];
+} __packed;
+
+#define ISCSI_REASON_NO_FULL_FEATURE_PHASE     0x01
+#define ISCSI_REASON_DATA_DIGEST_ERROR         0x02
+#define ISCSI_REASON_DATA_SNACK_REJECT         0x03
+#define ISCSI_REASON_PROTOCOL_ERROR            0x04
+#define ISCSI_REASON_UNSUPPORTED_COMMAND       0x05
+#define ISCSI_REASON_IMMEDIATE_COMMAND_REJECT  0x06
+#define ISCSI_REASON_TASK_IN_PROGRESS          0x07
+#define ISCSI_REASON_INVALID_SNACK             0x08
+#define ISCSI_REASON_NO_BOOKMARK               0x09
+#define ISCSI_REASON_BOOKMARK_REJECT           0x0a
+#define ISCSI_REASON_NEGOTIATION_RESET         0x0b
+#define ISCSI_REASON_WAITING_LOGOUT            0x0c
+
+
+struct iscsi_nop_out_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 ttt;
+       u32 cmd_sn;
+       u32 exp_stat_sn;
+       u32 rsvd2[4];
+} __packed;
+
+struct iscsi_nop_in_hdr {
+       u8  opcode;
+       u8  flags;
+       u16 rsvd1;
+       u8  ahslength;
+       u8  datalength[3];
+       u16 lun[4];
+       u32 itt;
+       u32 ttt;
+       u32 stat_sn;
+       u32 exp_cmd_sn;
+       u32 max_cmd_sn;
+       u32 rsvd2[3];
+} __packed;
+
+#define ISCSI_RESERVED_TAG     (0xffffffffU)
+
+#endif /* __ISCSI_HDR_H__ */
diff --git a/ubuntu/iscsitarget/nthread.c b/ubuntu/iscsitarget/nthread.c
new file mode 100644 (file)
index 0000000..ec54ead
--- /dev/null
@@ -0,0 +1,788 @@
+/*
+ * Network thread.
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * (C) 2008 Arne Redlich <agr@powerkom-dd.de>
+ *
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/kthread.h>
+#include <asm/ioctls.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "digest.h"
+
+enum daemon_state_bit {
+       D_ACTIVE,
+       D_DATA_READY,
+};
+
+void __nthread_wakeup(struct network_thread_info *info)
+{
+       set_bit(D_DATA_READY, &info->flags);
+       wake_up_process(info->task);
+}
+
+void nthread_wakeup(struct iscsi_target *target)
+{
+       struct network_thread_info *info = &target->nthread_info;
+
+       spin_lock_bh(&info->nthread_lock);
+       __nthread_wakeup(info);
+       spin_unlock_bh(&info->nthread_lock);
+}
+
+static inline void iscsi_conn_init_read(struct iscsi_conn *conn, void *data, size_t len)
+{
+       len = (len + 3) & -4; // XXX ???
+       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 + 3) & -4;
+}
+
+static void iscsi_conn_read_ahs(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+{
+       cmnd->pdu.ahs = kmalloc(cmnd->pdu.ahssize, __GFP_NOFAIL|GFP_KERNEL);
+       assert(cmnd->pdu.ahs);
+       iscsi_conn_init_read(conn, cmnd->pdu.ahs, cmnd->pdu.ahssize);
+}
+
+static struct iscsi_cmnd * iscsi_get_send_cmnd(struct iscsi_conn *conn)
+{
+       struct iscsi_cmnd *cmnd = NULL;
+
+       spin_lock(&conn->list_lock);
+       if (!list_empty(&conn->write_list)) {
+               cmnd = list_entry(conn->write_list.next, struct iscsi_cmnd, list);
+               list_del_init(&cmnd->list);
+       }
+       spin_unlock(&conn->list_lock);
+
+       return cmnd;
+}
+
+static int is_data_available(struct iscsi_conn *conn)
+{
+       int avail, res;
+       mm_segment_t oldfs;
+       struct socket *sock = conn->sock;
+
+       oldfs = get_fs();
+       set_fs(get_ds());
+       res = sock->ops->ioctl(sock, SIOCINQ, (unsigned long) &avail);
+       set_fs(oldfs);
+       return (res >= 0) ? avail : res;
+}
+
+static void forward_iov(struct msghdr *msg, int len)
+{
+       while (msg->msg_iov->iov_len <= len) {
+               len -= msg->msg_iov->iov_len;
+               msg->msg_iov++;
+               msg->msg_iovlen--;
+       }
+
+       msg->msg_iov->iov_base = (char *) msg->msg_iov->iov_base + len;
+       msg->msg_iov->iov_len -= len;
+}
+
+static int do_recv(struct iscsi_conn *conn, int state)
+{
+       mm_segment_t oldfs;
+       struct msghdr msg;
+       struct iovec iov[ISCSI_CONN_IOV_MAX];
+       int i, len, res;
+
+       if (!test_bit(CONN_ACTIVE, &conn->state)) {
+               res = -EIO;
+               goto out;
+       }
+
+       if (is_data_available(conn) <= 0) {
+               res = -EAGAIN;
+               goto out;
+       }
+
+       msg.msg_iov = iov;
+       msg.msg_iovlen = min_t(size_t, conn->read_msg.msg_iovlen, ISCSI_CONN_IOV_MAX);
+       for (i = 0, len = 0; i < msg.msg_iovlen; i++) {
+               iov[i] = conn->read_msg.msg_iov[i];
+               len += iov[i].iov_len;
+       }
+
+       oldfs = get_fs();
+       set_fs(get_ds());
+       res = sock_recvmsg(conn->sock, &msg, len, MSG_DONTWAIT | MSG_NOSIGNAL);
+       set_fs(oldfs);
+
+       if (res <= 0) {
+               switch (res) {
+               case -EAGAIN:
+               case -ERESTARTSYS:
+                       break;
+               default:
+                       eprintk("%d\n", res);
+                       conn_close(conn);
+                       break;
+               }
+       } else {
+               conn->read_size -= res;
+               if (conn->read_size)
+                       forward_iov(&conn->read_msg, res);
+               else
+                       conn->read_state = state;
+       }
+
+out:
+       dprintk(D_IOD, "%d\n", res);
+
+       return res;
+}
+
+enum rx_state {
+       RX_INIT_BHS, /* Must be zero. */
+       RX_BHS,
+
+       RX_INIT_AHS,
+       RX_AHS,
+
+       RX_INIT_HDIGEST,
+       RX_HDIGEST,
+       RX_CHECK_HDIGEST,
+
+       RX_INIT_DATA,
+       RX_DATA,
+
+       RX_INIT_DDIGEST,
+       RX_DDIGEST,
+       RX_CHECK_DDIGEST,
+
+       RX_END,
+};
+
+static void rx_ddigest(struct iscsi_conn *conn, int state)
+{
+       struct iscsi_cmnd *cmnd = conn->read_cmnd;
+       int res = digest_rx_data(cmnd);
+
+       if (!res)
+               conn->read_state = state;
+       else
+               conn_close(conn);
+}
+
+static void rx_hdigest(struct iscsi_conn *conn, int state)
+{
+       struct iscsi_cmnd *cmnd = conn->read_cmnd;
+       int res = digest_rx_header(cmnd);
+
+       if (!res)
+               conn->read_state = state;
+       else
+               conn_close(conn);
+}
+
+static struct iscsi_cmnd *create_cmnd(struct iscsi_conn *conn)
+{
+       struct iscsi_cmnd *cmnd;
+
+       cmnd = cmnd_alloc(conn, 1);
+       iscsi_conn_init_read(cmnd->conn, &cmnd->pdu.bhs, sizeof(cmnd->pdu.bhs));
+       conn->read_state = RX_BHS;
+
+       return cmnd;
+}
+
+static int recv(struct iscsi_conn *conn)
+{
+       struct iscsi_cmnd *cmnd = conn->read_cmnd;
+       int hdigest, ddigest, res = 1;
+
+       if (!test_bit(CONN_ACTIVE, &conn->state))
+               return -EIO;
+
+       hdigest = conn->hdigest_type & DIGEST_NONE ? 0 : 1;
+       ddigest = conn->ddigest_type & DIGEST_NONE ? 0 : 1;
+
+       switch (conn->read_state) {
+       case RX_INIT_BHS:
+               assert(!cmnd);
+               cmnd = conn->read_cmnd = create_cmnd(conn);
+       case RX_BHS:
+               res = do_recv(conn, RX_INIT_AHS);
+               if (res <= 0 || conn->read_state != RX_INIT_AHS)
+                       break;
+       case RX_INIT_AHS:
+               iscsi_cmnd_get_length(&cmnd->pdu);
+               if (cmnd->pdu.ahssize) {
+                       iscsi_conn_read_ahs(conn, cmnd);
+                       conn->read_state = RX_AHS;
+               } else
+                       conn->read_state = hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA;
+
+               if (conn->read_state != RX_AHS)
+                       break;
+       case RX_AHS:
+               res = do_recv(conn, hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA);
+               if (res <= 0 || conn->read_state != RX_INIT_HDIGEST)
+                       break;
+       case RX_INIT_HDIGEST:
+               iscsi_conn_init_read(conn, &cmnd->hdigest, sizeof(u32));
+               conn->read_state = RX_HDIGEST;
+       case RX_HDIGEST:
+               res = do_recv(conn, RX_CHECK_HDIGEST);
+               if (res <= 0 || conn->read_state != RX_CHECK_HDIGEST)
+                       break;
+       case RX_CHECK_HDIGEST:
+               rx_hdigest(conn, RX_INIT_DATA);
+               if (conn->read_state != RX_INIT_DATA)
+                       break;
+       case RX_INIT_DATA:
+               cmnd_rx_start(cmnd);
+               conn->read_state = cmnd->pdu.datasize ? RX_DATA : RX_END;
+               if (conn->read_state != RX_DATA)
+                       break;
+       case RX_DATA:
+               res = do_recv(conn, ddigest ? RX_INIT_DDIGEST : RX_END);
+               if (res <= 0 || conn->read_state != RX_INIT_DDIGEST)
+                       break;
+       case RX_INIT_DDIGEST:
+               iscsi_conn_init_read(conn, &cmnd->ddigest, sizeof(u32));
+               conn->read_state = RX_DDIGEST;
+       case RX_DDIGEST:
+               res = do_recv(conn, RX_CHECK_DDIGEST);
+               if (res <= 0 || conn->read_state != RX_CHECK_DDIGEST)
+                       break;
+       case RX_CHECK_DDIGEST:
+               rx_ddigest(conn, RX_END);
+               break;
+       default:
+               eprintk("%d %d %x\n", res, conn->read_state, cmnd_opcode(cmnd));
+               assert(0);
+       }
+
+       if (res <= 0)
+               return res;
+
+       if (conn->read_state != RX_END)
+               return res;
+
+       if (conn->read_size) {
+               eprintk("%d %x %d\n", res, cmnd_opcode(cmnd), conn->read_size);
+               assert(0);
+       }
+
+       cmnd_rx_end(cmnd);
+       if (conn->read_size) {
+               eprintk("%x %d\n", cmnd_opcode(cmnd), conn->read_size);
+               conn->read_state = RX_DATA;
+               return 1;
+       }
+
+       conn->read_cmnd = NULL;
+       conn->read_state = RX_INIT_BHS;
+
+       return 0;
+}
+
+/*
+ * @locking: grabs the target's nthread_lock to protect it from races with
+ * iet_write_space()
+ */
+static void set_conn_wspace_wait(struct iscsi_conn *conn)
+{
+       struct network_thread_info *info = &conn->session->target->nthread_info;
+       struct sock *sk = conn->sock->sk;
+
+       spin_lock_bh(&info->nthread_lock);
+
+       if (sk_stream_wspace(sk) < sk_stream_min_wspace(sk))
+               set_bit(CONN_WSPACE_WAIT, &conn->state);
+
+       spin_unlock_bh(&info->nthread_lock);
+}
+
+/* This is taken from the Ardis code. */
+static int write_data(struct iscsi_conn *conn)
+{
+       mm_segment_t oldfs;
+       struct file *file;
+       struct socket *sock;
+       ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
+       struct tio *tio;
+       struct iovec *iop;
+       int saved_size, size, sendsize;
+       int offset, idx;
+       int flags, res;
+
+       file = conn->file;
+       saved_size = size = conn->write_size;
+       iop = conn->write_iop;
+
+       if (iop) while (1) {
+               loff_t off = 0;
+               unsigned long count;
+               struct iovec *vec;
+               int rest;
+
+               vec = iop;
+               for (count = 0; vec->iov_len; count++, vec++)
+                       ;
+               oldfs = get_fs();
+               set_fs(KERNEL_DS);
+               res = vfs_writev(file, (struct iovec __user *) iop, count, &off);
+               set_fs(oldfs);
+               dprintk(D_DATA, "%#Lx:%u: %d(%ld)\n",
+                       (unsigned long long) conn->session->sid, conn->cid,
+                       res, (long) iop->iov_len);
+               if (unlikely(res <= 0)) {
+                       if (res == -EAGAIN || res == -EINTR) {
+                               conn->write_iop = iop;
+                               goto out_iov;
+                       }
+                       goto err;
+               }
+
+               rest = res;
+               size -= res;
+               while (iop->iov_len <= rest && rest) {
+                       rest -= iop->iov_len;
+                       iop++;
+               }
+               iop->iov_base += rest;
+               iop->iov_len -= rest;
+
+               if (!iop->iov_len) {
+                       conn->write_iop = NULL;
+                       if (size)
+                               break;
+                       goto out_iov;
+               }
+       }
+
+       if (!(tio = conn->write_tcmnd)) {
+               eprintk("%s\n", "warning data missing!");
+               return 0;
+       }
+       offset = conn->write_offset;
+       idx = offset >> PAGE_CACHE_SHIFT;
+       offset &= ~PAGE_CACHE_MASK;
+
+       sock = conn->sock;
+       sendpage = sock->ops->sendpage ? : sock_no_sendpage;
+       flags = MSG_DONTWAIT;
+
+       while (1) {
+               sendsize = PAGE_CACHE_SIZE - offset;
+               if (size <= sendsize) {
+                       res = sendpage(sock, tio->pvec[idx], offset, size, flags);
+                       dprintk(D_DATA, "%s %#Lx:%u: %d(%lu,%u,%u)\n",
+                               sock->ops->sendpage ? "sendpage" : "writepage",
+                               (unsigned long long ) conn->session->sid, conn->cid,
+                               res, tio->pvec[idx]->index, offset, size);
+                       if (unlikely(res <= 0)) {
+                               if (res == -EAGAIN || res == -EINTR) {
+                                       goto out;
+                               }
+                               goto err;
+                       }
+                       if (res == size) {
+                               conn->write_tcmnd = NULL;
+                               conn->write_size = 0;
+                               return saved_size;
+                       }
+                       offset += res;
+                       size -= res;
+                       continue;
+               }
+
+               res = sendpage(sock, tio->pvec[idx], offset,sendsize, flags | MSG_MORE);
+               dprintk(D_DATA, "%s %#Lx:%u: %d(%lu,%u,%u)\n",
+                       sock->ops->sendpage ? "sendpage" : "writepage",
+                       (unsigned long long ) conn->session->sid, conn->cid,
+                       res, tio->pvec[idx]->index, offset, sendsize);
+               if (unlikely(res <= 0)) {
+                       if (res == -EAGAIN || res == -EINTR) {
+                               goto out;
+                       }
+                       goto err;
+               }
+               if (res == sendsize) {
+                       idx++;
+                       offset = 0;
+               } else
+                       offset += res;
+               size -= res;
+       }
+ out:
+       conn->write_offset = (idx << PAGE_CACHE_SHIFT) + offset;
+ out_iov:
+       conn->write_size = size;
+       if (res == -EAGAIN) {
+               set_conn_wspace_wait(conn);
+               if (saved_size == size)
+                       return res;
+       }
+
+       return saved_size - size;
+
+ err:
+       eprintk("error %d at %#Lx:%u\n", res,
+               (unsigned long long) conn->session->sid, conn->cid);
+       return res;
+}
+
+static void exit_tx(struct iscsi_conn *conn, int res)
+{
+       if (res > 0)
+               return;
+
+       switch (res) {
+       case -EAGAIN:
+       case -ERESTARTSYS:
+               break;
+       default:
+               eprintk("%d %d %d\n", conn->write_size, conn->write_state, res);
+               conn_close(conn);
+               break;
+       }
+}
+
+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;
+
+       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
+               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;
+
+       if (conn->hdigest_type & DIGEST_NONE)
+               return;
+
+       digest_tx_header(cmnd);
+
+       for (iop = conn->write_iop; iop->iov_len; iop++)
+               ;
+       iop->iov_base = &(cmnd->hdigest);
+       iop->iov_len = sizeof(u32);
+       conn->write_size += sizeof(u32);
+       iop++;
+       iop->iov_len = 0;
+
+       return;
+}
+
+enum tx_state {
+       TX_INIT, /* Must be zero. */
+       TX_BHS_DATA,
+       TX_INIT_DDIGEST,
+       TX_DDIGEST,
+       TX_END,
+};
+
+static int do_send(struct iscsi_conn *conn, int state)
+{
+       int res;
+
+       res = write_data(conn);
+
+       if (res > 0) {
+               if (!conn->write_size)
+                       conn->write_state = state;
+       } else
+               exit_tx(conn, res);
+
+       return res;
+}
+
+static int send(struct iscsi_conn *conn)
+{
+       struct iscsi_cmnd *cmnd = conn->write_cmnd;
+       int ddigest, res = 0;
+
+       ddigest = conn->ddigest_type != DIGEST_NONE ? 1 : 0;
+
+       switch (conn->write_state) {
+       case TX_INIT:
+               assert(!cmnd);
+               cmnd = conn->write_cmnd = iscsi_get_send_cmnd(conn);
+               if (!cmnd)
+                       return 0;
+               cmnd_tx_start(cmnd);
+               init_tx_hdigest(cmnd);
+               conn->write_state = TX_BHS_DATA;
+       case TX_BHS_DATA:
+               res = do_send(conn, ddigest && cmnd->pdu.datasize ? TX_INIT_DDIGEST : TX_END);
+               if (res <= 0 || conn->write_state != TX_INIT_DDIGEST)
+                       break;
+       case TX_INIT_DDIGEST:
+               digest_tx_data(cmnd);
+               assert(!cmnd->conn->write_size);
+               cmnd->conn->write_size += sizeof(u32);
+               conn->write_state = TX_DDIGEST;
+       case TX_DDIGEST:
+               res = tx_ddigest(cmnd, TX_END);
+               break;
+       default:
+               eprintk("%d %d %x\n", res, conn->write_state, cmnd_opcode(cmnd));
+               assert(0);
+       }
+
+       if (res <= 0)
+               return res;
+
+       if (conn->write_state != TX_END)
+               return res;
+
+       if (conn->write_size) {
+               eprintk("%d %x %u\n", res, cmnd_opcode(cmnd), conn->write_size);
+               assert(!conn->write_size);
+       }
+       cmnd_tx_end(cmnd);
+       cmnd_release(cmnd, 0);
+       conn->write_cmnd = NULL;
+       conn->write_state = TX_INIT;
+
+       return 0;
+}
+
+static void conn_nop_timeout(unsigned long data)
+{
+       struct iscsi_conn *conn = (struct iscsi_conn *)data;
+
+       if (test_bit(CONN_ACTIVE, &conn->state))
+               set_bit(CONN_NEED_NOP_IN, &conn->state);
+
+       dprintk(D_THREAD, "conn %llu:%hu, NOP timer %p\n", conn->session->sid,
+               conn->cid, &conn->nop_timer);
+
+       nthread_wakeup(conn->session->target);
+}
+
+static void conn_reset_nop_timer(struct iscsi_conn *conn)
+{
+       struct iscsi_target *target = conn->session->target;
+
+       if (target->trgt_param.nop_interval)
+               mod_timer(&conn->nop_timer,
+                         jiffies + HZ * target->trgt_param.nop_interval);
+}
+
+static void conn_start_nop_timer(struct iscsi_conn *conn)
+{
+       struct iscsi_target *target = conn->session->target;
+
+       if (!target->trgt_param.nop_interval || timer_pending(&conn->nop_timer))
+               return;
+
+       conn->nop_timer.data = (unsigned long)conn;
+       conn->nop_timer.function = conn_nop_timeout;
+
+       dprintk(D_THREAD, "conn %llu:%hu, NOP timer %p\n", conn->session->sid,
+               conn->cid, &conn->nop_timer);
+
+       mod_timer(&conn->nop_timer,
+                 jiffies + HZ * target->trgt_param.nop_interval);
+}
+
+static void process_io(struct iscsi_conn *conn)
+{
+       struct iscsi_target *target = conn->session->target;
+       int res, wakeup = 0;
+
+       res = recv(conn);
+
+       if (is_data_available(conn) > 0 || res > 0) {
+               conn_reset_nop_timer(conn);
+               wakeup = 1;
+       }
+
+       if (!test_bit(CONN_ACTIVE, &conn->state)) {
+               wakeup = 1;
+               goto out;
+       }
+
+       if (test_bit(CONN_WSPACE_WAIT, &conn->state))
+               goto out;
+
+       res = send(conn);
+
+       if (!list_empty(&conn->write_list) || conn->write_cmnd) {
+               conn_reset_nop_timer(conn);
+               wakeup = 1;
+       }
+
+out:
+       if (wakeup)
+               nthread_wakeup(target);
+       else if (test_and_clear_bit(CONN_NEED_NOP_IN, &conn->state)) {
+               send_nop_in(conn);
+               nthread_wakeup(target);
+       } else
+               conn_start_nop_timer(conn);
+
+       return;
+}
+
+static void close_conn(struct iscsi_conn *conn)
+{
+       struct iscsi_session *session = conn->session;
+       struct iscsi_target *target = conn->session->target;
+       struct iscsi_cmnd *cmnd;
+
+       if (target->trgt_param.nop_interval)
+               del_timer_sync(&conn->nop_timer);
+
+       conn->sock->ops->shutdown(conn->sock, 2);
+
+       write_lock_bh(&conn->sock->sk->sk_callback_lock);
+       conn->sock->sk->sk_state_change = target->nthread_info.old_state_change;
+       conn->sock->sk->sk_data_ready = target->nthread_info.old_data_ready;
+       conn->sock->sk->sk_write_space = target->nthread_info.old_write_space;
+       write_unlock_bh(&conn->sock->sk->sk_callback_lock);
+
+       fput(conn->file);
+       conn->file = NULL;
+       conn->sock = NULL;
+
+       while (atomic_read(&conn->nr_busy_cmnds))
+               yield();
+
+       while (!list_empty(&conn->pdu_list)) {
+               cmnd = list_entry(conn->pdu_list.next, struct iscsi_cmnd, conn_list);
+
+               list_del_init(&cmnd->list);
+               cmnd_release(cmnd, 1);
+       }
+
+       if (atomic_read(&conn->nr_cmnds)) {
+               eprintk("%u\n", atomic_read(&conn->nr_cmnds));
+               list_for_each_entry(cmnd, &conn->pdu_list, conn_list)
+                       eprintk("%x %x\n", cmnd_opcode(cmnd), cmnd_itt(cmnd));
+               assert(0);
+       }
+
+       event_send(target->tid, session->sid, conn->cid, E_CONN_CLOSE, 0);
+       conn_free(conn);
+
+       if (list_empty(&session->conn_list)) {
+               if (session->done)
+                       complete(session->done);
+               else
+                       session_del(target, session->sid);
+       }
+}
+
+static int istd(void *arg)
+{
+       struct iscsi_target *target = arg;
+       struct network_thread_info *info = &target->nthread_info;
+       struct iscsi_conn *conn, *tmp;
+
+       __set_current_state(TASK_RUNNING);
+       do {
+               spin_lock_bh(&info->nthread_lock);
+               __set_current_state(TASK_INTERRUPTIBLE);
+
+               if (!test_bit(D_DATA_READY, &info->flags)) {
+                       spin_unlock_bh(&info->nthread_lock);
+                       schedule();
+                       spin_lock_bh(&info->nthread_lock);
+               }
+               __set_current_state(TASK_RUNNING);
+               clear_bit(D_DATA_READY, &info->flags);
+               spin_unlock_bh(&info->nthread_lock);
+
+               target_lock(target, 0);
+               list_for_each_entry_safe(conn, tmp, &info->active_conns, poll_list) {
+                       if (test_bit(CONN_ACTIVE, &conn->state))
+                               process_io(conn);
+                       else
+                               close_conn(conn);
+               }
+               target_unlock(target);
+
+       } while (!kthread_should_stop());
+
+       return 0;
+}
+
+int nthread_init(struct iscsi_target *target)
+{
+       struct network_thread_info *info = &target->nthread_info;
+
+       info->flags = 0;
+       info->task = NULL;
+
+       info->old_state_change = NULL;
+       info->old_data_ready = NULL;
+       info->old_write_space = NULL;
+
+       INIT_LIST_HEAD(&info->active_conns);
+
+       spin_lock_init(&info->nthread_lock);
+
+       return 0;
+}
+
+int nthread_start(struct iscsi_target *target)
+{
+       int err = 0;
+       struct network_thread_info *info = &target->nthread_info;
+       struct task_struct *task;
+
+       if (info->task) {
+               eprintk("Target (%u) already runs\n", target->tid);
+               return -EALREADY;
+       }
+
+       task = kthread_run(istd, target, "istd%d", target->tid);
+
+       if (IS_ERR(task))
+               err = PTR_ERR(task);
+       else
+               info->task = task;
+
+       return err;
+}
+
+int nthread_stop(struct iscsi_target *target)
+{
+       int err;
+       struct network_thread_info *info = &target->nthread_info;
+
+       if (!info->task)
+               return -ESRCH;
+
+       err = kthread_stop(info->task);
+
+       if (err < 0 && err != -EINTR)
+               return err;
+
+       info->task = NULL;
+
+       return 0;
+}
diff --git a/ubuntu/iscsitarget/null-io.c b/ubuntu/iscsitarget/null-io.c
new file mode 100644 (file)
index 0000000..cfa5899
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Target device null I/O.
+ * (C) 2005 MING Zhang <mingz@ele.uri.edu>
+ * This code is licenced under the GPL.
+ *
+ * The nullio mode will not return any meaningful or previous written
+ * data. It is only for performance measurement purpose.
+ */
+
+#include <linux/types.h>
+#include <linux/blkdev.h>
+#include <linux/parser.h>
+#include <linux/writeback.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+struct nullio_data {
+       u64 sectors;
+};
+
+enum {
+       Opt_sectors, Opt_ignore, Opt_err,
+};
+
+static match_table_t tokens = {
+       {Opt_sectors, "Sectors=%u"},
+       {Opt_ignore, "Type=%s"},
+       {Opt_err, NULL},
+};
+
+static int parse_nullio_params(struct iet_volume *volume, char *params)
+{
+       int err = 0;
+       char *p, *q;
+       struct nullio_data *data = volume->private;
+
+       while ((p = strsep(&params, ",")) != NULL) {
+               substring_t args[MAX_OPT_ARGS];
+               int token;
+               if (!*p)
+                       continue;
+               token = match_token(p, tokens, args);
+               switch (token) {
+               case Opt_sectors:
+                       q = match_strdup(&args[0]);
+                       if (!q)
+                               return -ENOMEM;
+                       data->sectors = simple_strtoull(q, NULL, 10);
+                       kfree(q);
+                       break;
+               case Opt_ignore:
+                       break;
+               default:
+                       eprintk("Unknown %s\n", p);
+                       return -EINVAL;
+                       break;
+               }
+       }
+       return err;
+}
+
+static void nullio_detach(struct iet_volume *lu)
+{
+       struct nullio_data *p = lu->private;
+
+       kfree(p);
+       lu->private = NULL;
+}
+
+static int nullio_attach(struct iet_volume *lu, char *args)
+{
+       int err = 0;
+       struct nullio_data *p;
+
+       if (lu->private) {
+               printk("already attached ? %d\n", lu->lun);
+               return -EBUSY;
+       }
+
+       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+
+       lu->private = p;
+
+       if ((err = parse_nullio_params(lu, args)) < 0) {
+               eprintk("%d\n", err);
+               goto out;
+       }
+
+       lu->blk_shift = SECTOR_SIZE_BITS;
+       lu->blk_cnt = (p->sectors = p->sectors ? : 1 << 27); /* 64 GB */
+
+out:
+       if (err < 0)
+               nullio_detach(lu);
+       return err;
+}
+
+void nullio_show(struct iet_volume *lu, struct seq_file *seq)
+{
+       struct nullio_data *p = lu->private;
+       seq_printf(seq, " sectors:%llu\n", p->sectors);
+}
+
+struct iotype nullio =
+{
+       .name = "nullio",
+       .attach = nullio_attach,
+       .detach = nullio_detach,
+       .show = nullio_show,
+};
diff --git a/ubuntu/iscsitarget/param.c b/ubuntu/iscsitarget/param.c
new file mode 100644 (file)
index 0000000..57ad301
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * (C) 2005 FUJITA Tomonori <tomof@acm.org>
+ *
+ * This code is licenced under the GPL.
+ */
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "digest.h"
+
+struct target_type *target_type_array[] = {
+       &disk_ops,
+};
+
+#define        CHECK_PARAM(info, iparam, word, min, max)                       \
+do {                                                                   \
+       if (!info->partial || (info->partial & 1 << key_##word))        \
+               if (iparam[key_##word] < min ||                         \
+                       iparam[key_##word] > max) {                     \
+                       eprintk("%s: %u is out of range (%u %u)\n",     \
+                               #word, iparam[key_##word], min, max);   \
+                       iparam[key_##word] = min;                       \
+               }                                                       \
+} while (0)
+
+#define        SET_PARAM(param, info, iparam, word)                            \
+({                                                                     \
+       int changed = 0;                                                \
+       if (!info->partial || (info->partial & 1 << key_##word)) {      \
+               if (param->word != iparam[key_##word])                  \
+                       changed = 1;                                    \
+               param->word = iparam[key_##word];                       \
+       }                                                               \
+       changed;                                                        \
+})
+
+#define        GET_PARAM(param, info, iparam, word)                            \
+do {                                                                   \
+       iparam[key_##word] = param->word;                               \
+} while (0)
+
+static void sess_param_check(struct iscsi_param_info *info)
+{
+       u32 *iparam = info->session_param;
+
+       CHECK_PARAM(info, iparam, max_connections, 1, 1);
+       CHECK_PARAM(info, iparam, max_recv_data_length, 512,
+                   (u32) ((ISCSI_CONN_IOV_MAX - 1) * PAGE_CACHE_SIZE));
+       CHECK_PARAM(info, iparam, max_xmit_data_length, 512,
+                   (u32) ((ISCSI_CONN_IOV_MAX - 1) * PAGE_CACHE_SIZE));
+       CHECK_PARAM(info, iparam, error_recovery_level, 0, 0);
+       CHECK_PARAM(info, iparam, data_pdu_inorder, 1, 1);
+       CHECK_PARAM(info, iparam, data_sequence_inorder, 1, 1);
+
+       digest_alg_available(&iparam[key_header_digest]);
+       digest_alg_available(&iparam[key_data_digest]);
+
+       CHECK_PARAM(info, iparam, ofmarker, 0, 0);
+       CHECK_PARAM(info, iparam, ifmarker, 0, 0);
+}
+
+static void sess_param_set(struct iscsi_sess_param *param, struct iscsi_param_info *info)
+{
+       u32 *iparam = info->session_param;
+
+       SET_PARAM(param, info, iparam, initial_r2t);
+       SET_PARAM(param, info, iparam, immediate_data);
+       SET_PARAM(param, info, iparam, max_connections);
+       SET_PARAM(param, info, iparam, max_recv_data_length);
+       SET_PARAM(param, info, iparam, max_xmit_data_length);
+       SET_PARAM(param, info, iparam, max_burst_length);
+       SET_PARAM(param, info, iparam, first_burst_length);
+       SET_PARAM(param, info, iparam, default_wait_time);
+       SET_PARAM(param, info, iparam, default_retain_time);
+       SET_PARAM(param, info, iparam, max_outstanding_r2t);
+       SET_PARAM(param, info, iparam, data_pdu_inorder);
+       SET_PARAM(param, info, iparam, data_sequence_inorder);
+       SET_PARAM(param, info, iparam, error_recovery_level);
+       SET_PARAM(param, info, iparam, header_digest);
+       SET_PARAM(param, info, iparam, data_digest);
+       SET_PARAM(param, info, iparam, ofmarker);
+       SET_PARAM(param, info, iparam, ifmarker);
+       SET_PARAM(param, info, iparam, ofmarkint);
+       SET_PARAM(param, info, iparam, ifmarkint);
+}
+
+static void sess_param_get(struct iscsi_sess_param *param, struct iscsi_param_info *info)
+{
+       u32 *iparam = info->session_param;
+
+       GET_PARAM(param, info, iparam, initial_r2t);
+       GET_PARAM(param, info, iparam, immediate_data);
+       GET_PARAM(param, info, iparam, max_connections);
+       GET_PARAM(param, info, iparam, max_recv_data_length);
+       GET_PARAM(param, info, iparam, max_xmit_data_length);
+       GET_PARAM(param, info, iparam, max_burst_length);
+       GET_PARAM(param, info, iparam, first_burst_length);
+       GET_PARAM(param, info, iparam, default_wait_time);
+       GET_PARAM(param, info, iparam, default_retain_time);
+       GET_PARAM(param, info, iparam, max_outstanding_r2t);
+       GET_PARAM(param, info, iparam, data_pdu_inorder);
+       GET_PARAM(param, info, iparam, data_sequence_inorder);
+       GET_PARAM(param, info, iparam, error_recovery_level);
+       GET_PARAM(param, info, iparam, header_digest);
+       GET_PARAM(param, info, iparam, data_digest);
+       GET_PARAM(param, info, iparam, ofmarker);
+       GET_PARAM(param, info, iparam, ifmarker);
+       GET_PARAM(param, info, iparam, ofmarkint);
+       GET_PARAM(param, info, iparam, ifmarkint);
+}
+
+static void trgt_param_check(struct iscsi_param_info *info)
+{
+       u32 *iparam = info->target_param;
+
+       CHECK_PARAM(info, iparam, wthreads, MIN_NR_WTHREADS, MAX_NR_WTHREADS);
+       CHECK_PARAM(info, iparam, target_type, 0,
+                   (unsigned int) ARRAY_SIZE(target_type_array) - 1);
+       CHECK_PARAM(info, iparam, queued_cmnds, MIN_NR_QUEUED_CMNDS,
+                   MAX_NR_QUEUED_CMNDS);
+       CHECK_PARAM(info, iparam, nop_interval, MIN_NOP_INTERVAL,
+                   MAX_NOP_INTERVAL);
+       CHECK_PARAM(info, iparam, nop_timeout, MIN_NOP_TIMEOUT,
+                   MAX_NOP_TIMEOUT);
+}
+
+static void trgt_param_set(struct iscsi_target *target, struct iscsi_param_info *info)
+{
+       struct iscsi_trgt_param *param = &target->trgt_param;
+       u32 *iparam = info->target_param;
+
+       if (!worker_thread_pool &&
+           SET_PARAM(param, info, iparam, wthreads))
+               wthread_start(target->wthread_info,
+                             target->trgt_param.wthreads, target->tid);
+       SET_PARAM(param, info, iparam, target_type);
+       SET_PARAM(param, info, iparam, queued_cmnds);
+       SET_PARAM(param, info, iparam, nop_interval);
+       SET_PARAM(param, info, iparam, nop_timeout);
+}
+
+static void trgt_param_get(struct iscsi_trgt_param *param, struct iscsi_param_info *info)
+{
+       u32 *iparam = info->target_param;
+
+       GET_PARAM(param, info, iparam, wthreads);
+       GET_PARAM(param, info, iparam, target_type);
+       GET_PARAM(param, info, iparam, queued_cmnds);
+       GET_PARAM(param, info, iparam, nop_interval);
+       GET_PARAM(param, info, iparam, nop_timeout);
+}
+
+static int trgt_param(struct iscsi_target *target, struct iscsi_param_info *info, int set)
+{
+
+       if (set) {
+               trgt_param_check(info);
+               trgt_param_set(target, info);
+       } else
+               trgt_param_get(&target->trgt_param, info);
+
+       return 0;
+}
+
+static int sess_param(struct iscsi_target *target, struct iscsi_param_info *info, int set)
+{
+       struct iscsi_session *session = NULL;
+       struct iscsi_sess_param *param;
+       int err = -ENOENT;
+
+       if (set)
+               sess_param_check(info);
+
+       if (info->sid) {
+               if (!(session = session_lookup(target, info->sid)))
+                       goto out;
+               param = &session->param;
+       } else {
+               param = &target->sess_param;
+       }
+
+       if (set) {
+               sess_param_set(param, info);
+               show_param(param);
+       } else
+               sess_param_get(param, info);
+
+       err = 0;
+out:
+       return err;
+}
+
+int iscsi_param_set(struct iscsi_target *target, struct iscsi_param_info *info, int set)
+{
+       int err;
+
+       if (info->param_type == key_session)
+               err = sess_param(target, info, set);
+       else if (info->param_type == key_target)
+               err = trgt_param(target, info, set);
+       else
+               err = -EINVAL;
+
+       return err;
+}
diff --git a/ubuntu/iscsitarget/session.c b/ubuntu/iscsitarget/session.c
new file mode 100644 (file)
index 0000000..6365373
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+struct iscsi_session *session_lookup(struct iscsi_target *target, u64 sid)
+{
+       struct iscsi_session *session;
+
+       list_for_each_entry(session, &target->session_list, list) {
+               if (session->sid == sid)
+                       return session;
+       }
+       return NULL;
+}
+
+static struct iscsi_session *
+iet_session_alloc(struct iscsi_target *target, struct session_info *info)
+{
+       int i;
+       struct iscsi_session *session;
+       struct iet_volume *vol;
+
+       dprintk(D_SETUP, "%p %u %#Lx\n", target, target->tid,
+               (unsigned long long) info->sid);
+
+       session = kzalloc(sizeof(*session), GFP_KERNEL);
+       if (!session)
+               return NULL;
+
+       session->target = target;
+       session->sid = info->sid;
+       memcpy(&session->param, &target->sess_param, sizeof(session->param));
+       session->max_queued_cmnds = target->trgt_param.queued_cmnds;
+
+       session->exp_cmd_sn = info->exp_cmd_sn;
+       session->max_cmd_sn = info->max_cmd_sn;
+
+       session->initiator = kstrdup(info->initiator_name, GFP_KERNEL);
+       if (!session->initiator) {
+               kfree(session);
+               return NULL;
+       }
+
+       INIT_LIST_HEAD(&session->conn_list);
+       INIT_LIST_HEAD(&session->pending_list);
+
+       spin_lock_init(&session->cmnd_hash_lock);
+       for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++)
+               INIT_LIST_HEAD(&session->cmnd_hash[i]);
+
+       spin_lock_init(&session->ua_hash_lock);
+       for (i = 0; i < ARRAY_SIZE(session->ua_hash); i++)
+               INIT_LIST_HEAD(&session->ua_hash[i]);
+
+       list_for_each_entry(vol, &target->volumes, list)
+               /* power-on, reset, or bus device reset occurred */
+               ua_establish_for_session(session, vol->lun, 0x29, 0x0);
+
+       session->next_ttt = 1;
+
+       spin_lock(&target->session_list_lock);
+       list_add(&session->list, &target->session_list);
+       spin_unlock(&target->session_list_lock);
+
+       return session;
+}
+
+static int session_free(struct iscsi_session *session)
+{
+       int i;
+       struct ua_entry *ua, *tmp;
+       struct list_head *l;
+       struct iscsi_target *target = session->target;
+
+       dprintk(D_SETUP, "%#Lx\n", (unsigned long long) session->sid);
+
+       spin_lock(&target->session_list_lock);
+
+       assert(list_empty(&session->conn_list));
+
+       for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) {
+               if (!list_empty(&session->cmnd_hash[i]))
+                       BUG();
+       }
+
+       for (i = 0; i < ARRAY_SIZE(session->ua_hash); i++) {
+               l = &session->ua_hash[i];
+               list_for_each_entry_safe(ua, tmp, l, entry) {
+                       list_del_init(&ua->entry);
+                       ua_free(ua);
+               }
+       }
+
+       list_del(&session->list);
+
+       kfree(session->initiator);
+       kfree(session);
+
+       spin_unlock(&target->session_list_lock);
+
+       return 0;
+}
+
+int session_add(struct iscsi_target *target, struct session_info *info)
+{
+       struct iscsi_session *session;
+
+       session = session_lookup(target, info->sid);
+       if (session)
+               return -EEXIST;
+
+       session = iet_session_alloc(target, info);
+       if (!session)
+               return -ENOMEM;
+
+       return 0;
+}
+
+int session_del(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)) {
+               eprintk("%llu still have connections\n", (unsigned long long) session->sid);
+               return -EBUSY;
+       }
+
+       return session_free(session);
+}
+
+void session_del_all(struct iscsi_target *target)
+{
+       DECLARE_COMPLETION_ONSTACK(done);
+       struct iscsi_session *sess;
+
+       while (!list_empty(&target->session_list)) {
+               init_completion(&done);
+               target_lock(target, 0);
+               sess = list_entry(target->session_list.next, struct
+                                 iscsi_session, list);
+               sess->done = &done;
+               conn_del_all(sess);
+               target_unlock(target);
+               wait_for_completion(&done);
+               target_lock(target, 0);
+               session_free(sess);
+               target_unlock(target);
+       }
+
+       if (target->done)
+               complete(target->done);
+}
+
+static void iet_session_info_show(struct seq_file *seq, struct iscsi_target *target)
+{
+       struct iscsi_session *session;
+
+       list_for_each_entry(session, &target->session_list, list) {
+               seq_printf(seq, "\tsid:%llu initiator:%s\n",
+                          (unsigned long long) session->sid, session->initiator);
+               conn_info_show(seq, session);
+       }
+}
+
+static int iet_session_seq_open(struct inode *inode, struct file *file)
+{
+       int res;
+       res = seq_open(file, &iet_seq_op);
+       if (!res)
+               ((struct seq_file *)file->private_data)->private =
+                       iet_session_info_show;
+       return res;
+}
+
+struct file_operations session_seq_fops = {
+       .owner          = THIS_MODULE,
+       .open           = iet_session_seq_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
diff --git a/ubuntu/iscsitarget/target.c b/ubuntu/iscsitarget/target.c
new file mode 100644 (file)
index 0000000..15c0715
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include "iscsi.h"
+#include "digest.h"
+#include "iscsi_dbg.h"
+
+#define        MAX_NR_TARGETS  (1UL << 30)
+
+static LIST_HEAD(target_list);
+static DECLARE_MUTEX(target_list_sem);
+static u32 next_target_id;
+static u32 nr_targets;
+
+static struct iscsi_sess_param default_session_param = {
+       .initial_r2t = 1,
+       .immediate_data = 1,
+       .max_connections = 1,
+       .max_recv_data_length = 8192,
+       .max_xmit_data_length = 8192,
+       .max_burst_length = 262144,
+       .first_burst_length = 65536,
+       .default_wait_time = 2,
+       .default_retain_time = 20,
+       .max_outstanding_r2t = 1,
+       .data_pdu_inorder = 1,
+       .data_sequence_inorder = 1,
+       .error_recovery_level = 0,
+       .header_digest = DIGEST_NONE,
+       .data_digest = DIGEST_NONE,
+       .ofmarker = 0,
+       .ifmarker = 0,
+       .ofmarkint = 2048,
+       .ifmarkint = 2048,
+};
+
+static struct iscsi_trgt_param default_target_param = {
+       .wthreads = DEFAULT_NR_WTHREADS,
+       .target_type = 0,
+       .queued_cmnds = DEFAULT_NR_QUEUED_CMNDS,
+};
+
+inline int target_lock(struct iscsi_target *target, int interruptible)
+{
+       int err = 0;
+
+       if (interruptible)
+               err = down_interruptible(&target->target_sem);
+       else
+               down(&target->target_sem);
+
+       return err;
+}
+
+inline void target_unlock(struct iscsi_target *target)
+{
+       up(&target->target_sem);
+}
+
+static struct iscsi_target *__target_lookup_by_id(u32 id)
+{
+       struct iscsi_target *target;
+
+       list_for_each_entry(target, &target_list, t_list) {
+               if (target->tid == id)
+                       return target;
+       }
+       return NULL;
+}
+
+static struct iscsi_target *__target_lookup_by_name(char *name)
+{
+       struct iscsi_target *target;
+
+       list_for_each_entry(target, &target_list, t_list) {
+               if (!strcmp(target->name, name))
+                       return target;
+       }
+       return NULL;
+}
+
+struct iscsi_target *target_lookup_by_id(u32 id)
+{
+       struct iscsi_target *target;
+
+       down(&target_list_sem);
+       target = __target_lookup_by_id(id);
+       up(&target_list_sem);
+
+       return target;
+}
+
+static int target_thread_start(struct iscsi_target *target)
+{
+       int err;
+
+       if ((err = nthread_start(target)) < 0)
+               return err;
+
+       if (!worker_thread_pool) {
+               err = wthread_start(target->wthread_info,
+                                   target->trgt_param.wthreads, target->tid);
+               if (err)
+                       nthread_stop(target);
+       }
+
+       return err;
+}
+
+static void target_thread_stop(struct iscsi_target *target)
+{
+       if (!worker_thread_pool)
+               wthread_stop(target->wthread_info);
+
+       nthread_stop(target);
+}
+
+static int iscsi_target_create(struct target_info *info, u32 tid)
+{
+       int err = -EINVAL, len;
+       char *name = info->name;
+       struct iscsi_target *target;
+
+       dprintk(D_SETUP, "%u %s\n", tid, name);
+
+       if (!(len = strlen(name))) {
+               eprintk("The length of the target name is zero %u\n", tid);
+               return err;
+       }
+
+       if (!try_module_get(THIS_MODULE)) {
+               eprintk("Fail to get module %u\n", tid);
+               return err;
+       }
+
+       target = kzalloc(sizeof(*target), GFP_KERNEL);
+       if (!target) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       if (!worker_thread_pool) {
+               target->wthread_info = kmalloc(sizeof(struct worker_thread_info), GFP_KERNEL);
+               if (!target->wthread_info) {
+                       err = -ENOMEM;
+                       goto out;
+               }
+       }
+
+       target->tid = info->tid = tid;
+
+       memcpy(&target->sess_param, &default_session_param, sizeof(default_session_param));
+       memcpy(&target->trgt_param, &default_target_param, sizeof(default_target_param));
+
+       strncpy(target->name, name, sizeof(target->name) - 1);
+
+       init_MUTEX(&target->target_sem);
+       spin_lock_init(&target->session_list_lock);
+
+       INIT_LIST_HEAD(&target->session_list);
+       INIT_LIST_HEAD(&target->volumes);
+
+       atomic_set(&target->nr_volumes, 0);
+
+       nthread_init(target);
+
+       if (!worker_thread_pool)
+               wthread_init(target->wthread_info);
+       else
+               target->wthread_info = worker_thread_pool;
+
+
+       if ((err = target_thread_start(target)) < 0) {
+               target_thread_stop(target);
+               goto out;
+       }
+
+       list_add(&target->t_list, &target_list);
+
+       return 0;
+out:
+       if (!worker_thread_pool)
+               kfree(target->wthread_info);
+       kfree(target);
+       module_put(THIS_MODULE);
+
+       return err;
+}
+
+int target_add(struct target_info *info)
+{
+       int err = -EEXIST;
+       u32 tid = info->tid;
+
+       down(&target_list_sem);
+
+       if (nr_targets > MAX_NR_TARGETS) {
+               err = -EBUSY;
+               goto out;
+       }
+
+       if (__target_lookup_by_name(info->name))
+               goto out;
+
+       if (tid && __target_lookup_by_id(tid))
+               goto out;
+
+       if (!tid) {
+               do {
+                       if (!++next_target_id)
+                               ++next_target_id;
+               } while (__target_lookup_by_id(next_target_id));
+
+               tid = next_target_id;
+       }
+
+       if (!(err = iscsi_target_create(info, tid)))
+               nr_targets++;
+out:
+       up(&target_list_sem);
+
+       return err;
+}
+
+static void target_destroy(struct iscsi_target *target)
+{
+       dprintk(D_SETUP, "%u\n", target->tid);
+
+       target_thread_stop(target);
+
+       while (!list_empty(&target->volumes)) {
+               struct iet_volume *volume;
+               volume = list_entry(target->volumes.next, struct iet_volume, list);
+               volume->l_state = IDEV_DEL;
+               iscsi_volume_destroy(volume);
+       }
+
+       if (!worker_thread_pool)
+               kfree(target->wthread_info);
+       kfree(target);
+
+       module_put(THIS_MODULE);
+}
+
+/* @locking: target_list_sem must be locked */
+int __target_del(struct iscsi_target *target)
+{
+       target_lock(target, 0);
+
+       if (!list_empty(&target->session_list)) {
+               target_unlock(target);
+               return -EBUSY;
+       }
+
+       list_del(&target->t_list);
+       nr_targets--;
+
+       target_unlock(target);
+       target_destroy(target);
+       return 0;
+}
+
+int target_del(u32 id)
+{
+       struct iscsi_target *target;
+       int err = down_interruptible(&target_list_sem);
+       if (err < 0)
+               return err;
+
+       target = __target_lookup_by_id(id);
+       if (!target) {
+               err = -ENOENT;
+               goto out;
+       }
+
+       err = __target_del(target);
+ out:
+       up(&target_list_sem);
+       return err;
+}
+
+void target_del_all(void)
+{
+       DECLARE_COMPLETION_ONSTACK(done);
+       struct iscsi_target *target, *tmp;
+
+       down(&target_list_sem);
+
+       if (!list_empty(&target_list))
+               iprintk("Removing all connections, sessions and targets\n");
+
+       list_for_each_entry_safe(target, tmp, &target_list, t_list) {
+               init_completion(&done);
+               target->done = &done;
+               session_del_all(target);
+               wait_for_completion(&done);
+               __target_del(target);
+       }
+
+       next_target_id = 0;
+
+       up(&target_list_sem);
+}
+
+static void *iet_seq_start(struct seq_file *m, loff_t *pos)
+{
+       int err;
+
+       /* are you sure this is to be interruptible? */
+       err = down_interruptible(&target_list_sem);
+       if (err < 0)
+               return ERR_PTR(err);
+
+       return seq_list_start(&target_list, *pos);
+}
+
+static void *iet_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+       return seq_list_next(v, &target_list, pos);
+}
+
+static void iet_seq_stop(struct seq_file *m, void *v)
+{
+       up(&target_list_sem);
+}
+
+static int iet_seq_show(struct seq_file *m, void *p)
+{
+       iet_show_info_t *func = (iet_show_info_t *)m->private;
+       struct iscsi_target *target =
+               list_entry(p, struct iscsi_target, t_list);
+       int err;
+
+       /* relly, interruptible?  I'd think target_lock(target, 0)
+        * would be more appropriate. --lge */
+       err = target_lock(target, 1);
+       if (err < 0)
+               return err;
+
+       seq_printf(m, "tid:%u name:%s\n", target->tid, target->name);
+
+       func(m, target);
+
+       target_unlock(target);
+
+       return 0;
+}
+
+struct seq_operations iet_seq_op = {
+       .start = iet_seq_start,
+       .next = iet_seq_next,
+       .stop = iet_seq_stop,
+       .show = iet_seq_show,
+};
diff --git a/ubuntu/iscsitarget/target_disk.c b/ubuntu/iscsitarget/target_disk.c
new file mode 100644 (file)
index 0000000..694edb2
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ *
+ * heavily based on code from kernel/iscsi.c:
+ *   Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>,
+ *   licensed under the terms of the GNU GPL v2.0,
+ */
+
+#include <linux/ctype.h>
+#include <scsi/scsi.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+static int insert_disconnect_pg(u8 *ptr)
+{
+       unsigned char disconnect_pg[] = {0x02, 0x0e, 0x80, 0x80, 0x00, 0x0a, 0x00, 0x00,
+                                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+       memcpy(ptr, disconnect_pg, sizeof(disconnect_pg));
+       return sizeof(disconnect_pg);
+}
+
+static int insert_caching_pg(u8 *ptr, int wcache, int rcache)
+{
+       unsigned char caching_pg[] = {0x08, 0x12, 0x10, 0x00, 0xff, 0xff, 0x00, 0x00,
+                                     0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0x00, 0x00,
+                                     0x00, 0x00, 0x00, 0x00};
+
+       memcpy(ptr, caching_pg, sizeof(caching_pg));
+       if (wcache)
+               ptr[2] |= 0x04; /* set WCE bit if we're caching writes */
+       if (!rcache)
+               ptr[2] |= 0x01; /* Read Cache Disable */
+
+       return sizeof(caching_pg);
+}
+
+static int insert_ctrl_m_pg(u8 *ptr)
+{
+       unsigned char ctrl_m_pg[] = {0x0a, 0x0a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0x02, 0x4b};
+
+       memcpy(ptr, ctrl_m_pg, sizeof(ctrl_m_pg));
+       return sizeof(ctrl_m_pg);
+}
+
+static int insert_iec_m_pg(u8 *ptr)
+{
+       unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00};
+
+       memcpy(ptr, iec_m_pg, sizeof(iec_m_pg));
+       return sizeof(iec_m_pg);
+}
+
+static int insert_format_m_pg(u8 *ptr, u32 sector_size)
+{
+       unsigned char format_m_pg[] = {0x03, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00};
+
+       memcpy(ptr, format_m_pg, sizeof(format_m_pg));
+       ptr[12] = (sector_size >> 8) & 0xff;
+       ptr[13] = sector_size & 0xff;
+       return sizeof(format_m_pg);
+}
+
+static int insert_geo_m_pg(u8 *ptr, u64 sec)
+{
+       unsigned char geo_m_pg[] = {0x04, 0x16, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00};
+       u32 ncyl;
+       u32 n;
+
+       /* assume 0xff heads, 15krpm. */
+       memcpy(ptr, geo_m_pg, sizeof(geo_m_pg));
+       ncyl = sec >> 14; /* 256 * 64 */
+       memcpy(&n, ptr+1, sizeof(u32));
+       n = n | cpu_to_be32(ncyl);
+       memcpy(ptr+1, &n, sizeof(u32));
+       return sizeof(geo_m_pg);
+}
+
+static void build_mode_sense_response(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+       struct tio *tio = cmnd->tio;
+       u8 *data, *scb = req->scb;
+       int len = 4, err = 0;
+       u8 pcode;
+
+       /* changeable parameter mode pages are unsupported */
+       if ((scb[2] & 0xc0) >> 6 == 0x1)
+               goto set_sense;
+
+       pcode = req->scb[2] & 0x3f;
+
+       assert(!tio);
+       tio = cmnd->tio = tio_alloc(1);
+       data = page_address(tio->pvec[0]);
+       assert(data);
+       clear_page(data);
+
+       if (LUReadonly(cmnd->lun))
+               data[2] = 0x80;
+
+       if ((scb[1] & 0x8))
+               data[3] = 0;
+       else {
+               data[3] = 8;
+               len += 8;
+               *(u32 *)(data + 4) = (cmnd->lun->blk_cnt >> 32) ?
+                       cpu_to_be32(0xffffffff) : cpu_to_be32(cmnd->lun->blk_cnt);
+               *(u32 *)(data + 8) = cpu_to_be32(1 << cmnd->lun->blk_shift);
+       }
+
+       switch (pcode) {
+       case 0x0:
+               break;
+       case 0x2:
+               len += insert_disconnect_pg(data + len);
+               break;
+       case 0x3:
+               len += insert_format_m_pg(data + len, 1 << cmnd->lun->blk_shift);
+               break;
+       case 0x4:
+               len += insert_geo_m_pg(data + len, cmnd->lun->blk_cnt);
+               break;
+       case 0x8:
+               len += insert_caching_pg(data + len, LUWCache(cmnd->lun),
+                                        LURCache(cmnd->lun));
+               break;
+       case 0xa:
+               len += insert_ctrl_m_pg(data + len);
+               break;
+       case 0x1c:
+               len += insert_iec_m_pg(data + len);
+               break;
+       case 0x3f:
+               len += insert_disconnect_pg(data + len);
+               len += insert_format_m_pg(data + len, 1 << cmnd->lun->blk_shift);
+               len += insert_geo_m_pg(data + len, cmnd->lun->blk_cnt);
+               len += insert_caching_pg(data + len, LUWCache(cmnd->lun),
+                                        LURCache(cmnd->lun));
+               len += insert_ctrl_m_pg(data + len);
+               len += insert_iec_m_pg(data + len);
+               break;
+       default:
+               err = -1;
+       }
+
+       if (!err) {
+               data[0] = len - 1;
+               tio_set(tio, len, 0);
+               return;
+       }
+
+       tio_put(tio);
+       cmnd->tio = NULL;
+ set_sense:
+       /* Invalid Field In CDB */
+       iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+}
+
+static void build_inquiry_response(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+       struct tio *tio = cmnd->tio;
+       u8 *data;
+       u8 *scb = req->scb;
+       int err = -1;
+
+       /*
+        * - CmdDt and EVPD both set or EVPD and Page Code set: illegal
+        * - CmdDt set: not supported
+        */
+       if ((scb[1] & 0x3) > 0x1 || (!(scb[1] & 0x3) && scb[2]))
+               goto set_sense;
+
+       assert(!tio);
+       tio = cmnd->tio = tio_alloc(1);
+       data = page_address(tio->pvec[0]);
+       assert(data);
+       clear_page(data);
+
+       if (!(scb[1] & 0x3)) {
+               data[2] = 4;
+               data[3] = 0x52;
+               data[4] = 59;
+               data[7] = 0x02;
+               memset(data + 8, 0x20, 28);
+               memcpy(data + 8,
+                      VENDOR_ID, min_t(size_t, strlen(VENDOR_ID), 8));
+               memcpy(data + 16,
+                      PRODUCT_ID, min_t(size_t, strlen(PRODUCT_ID), 16));
+               memcpy(data + 32,
+                      PRODUCT_REV, min_t(size_t, strlen(PRODUCT_REV), 4));
+               data[58] = 0x03;
+               data[59] = 0x20;
+               data[60] = 0x09;
+               data[61] = 0x60;
+               data[62] = 0x03;
+               data[63] = 0x00;
+               tio_set(tio, 64, 0);
+               err = 0;
+       } else if (scb[1] & 0x1) {
+               /* EVPD bit set */
+               if (scb[2] == 0x0) {
+                       data[1] = 0x0;
+                       data[3] = 3;
+                       data[4] = 0x0;
+                       data[5] = 0x80;
+                       data[6] = 0x83;
+                       tio_set(tio, 7, 0);
+                       err = 0;
+               } else if (scb[2] == 0x80) {
+                       int len = (cmnd->lun && strlen(cmnd->lun->scsi_sn)) ?
+                               SCSI_SN_LEN : 4;
+
+                       data[1] = 0x80;
+                       data[3] = len;
+                       memset(data + 4, 0x20, len);
+                       tio_set(tio, len + 4, 0);
+                       err = 0;
+
+                       if (len == SCSI_SN_LEN) {
+                               char *p, *q;
+
+                               p = data + 4 + len - 1;
+                               q = cmnd->lun->scsi_sn + len - 1;
+
+                               for (; len > 0; len--, q--)
+                                       if (isascii(*q) && isprint(*q))
+                                               *(p--) = *q;
+                       }
+               } else if (scb[2] == 0x83) {
+                       u32 len = SCSI_ID_LEN * sizeof(u8);
+
+                       data[1] = 0x83;
+                       data[3] = len + 4;
+                       data[4] = 0x1;
+                       data[5] = 0x1;
+                       data[7] = len;
+                       if (cmnd->lun) /* We need this ? */
+                               memcpy(data + 8, cmnd->lun->scsi_id, len);
+                       tio_set(tio, len + 8, 0);
+                       err = 0;
+               }
+       }
+
+       if (!err) {
+               tio_set(tio, min_t(u8, tio->size, scb[4]), 0);
+               if (!cmnd->lun)
+                       data[0] = TYPE_NO_LUN;
+               return;
+       }
+
+       tio_put(tio);
+       cmnd->tio = NULL;
+ set_sense:
+       /* Invalid Field In CDB */
+       iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+}
+
+static void build_report_luns_response(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+       struct tio *tio = cmnd->tio;
+       u32 *data, size, len;
+       struct iet_volume *lun;
+       int rest, idx = 0;
+
+       size = (u32)req->scb[6] << 24 | (u32)req->scb[7] << 16 |
+               (u32)req->scb[8] << 8 | (u32)req->scb[9];
+       if (size < 16) {
+               /* Invalid Field In CDB */
+               iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+               return;
+       }
+
+       len = atomic_read(&cmnd->conn->session->target->nr_volumes) * 8;
+       size = min(size & ~(8 - 1), len + 8);
+
+       assert(!tio);
+       tio = cmnd->tio = tio_alloc(get_pgcnt(size, 0));
+       tio_set(tio, size, 0);
+
+       data = page_address(tio->pvec[idx]);
+       assert(data);
+       *data++ = cpu_to_be32(len);
+       *data++ = 0;
+       size -= 8;
+       rest = PAGE_CACHE_SIZE - 8;
+       list_for_each_entry(lun, &cmnd->conn->session->target->volumes, list) {
+               if (lun->l_state != IDEV_RUNNING)
+                       continue;
+
+               *data++ = cpu_to_be32((0x3ff & lun->lun) << 16 |
+                                     ((lun->lun > 0xff) ? (0x1 << 30) : 0));
+               *data++ = 0;
+               if ((size -= 8) == 0)
+                       break;
+               if ((rest -= 8) == 0) {
+                       idx++;
+                       data = page_address(tio->pvec[idx]);
+                       rest = PAGE_CACHE_SIZE;
+               }
+       }
+}
+
+static void build_read_capacity_response(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio = cmnd->tio;
+       u32 *data;
+
+       assert(!tio);
+       tio = cmnd->tio = tio_alloc(1);
+       data = page_address(tio->pvec[0]);
+       assert(data);
+       clear_page(data);
+
+       data[0] = (cmnd->lun->blk_cnt >> 32) ?
+               cpu_to_be32(0xffffffff) : cpu_to_be32(cmnd->lun->blk_cnt - 1);
+       data[1] = cpu_to_be32(1U << cmnd->lun->blk_shift);
+
+       tio_set(tio, 8, 0);
+}
+
+static void build_request_sense_response(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio = cmnd->tio;
+       u8 *data;
+
+       assert(!tio);
+       tio = cmnd->tio = tio_alloc(1);
+       data = page_address(tio->pvec[0]);
+       assert(data);
+       memset(data, 0, 18);
+       data[0] = 0xf0;
+       data[1] = 0;
+       data[2] = NO_SENSE;
+       data[7] = 10;
+       tio_set(tio, 18, 0);
+}
+
+static void build_service_action_in_response(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio = cmnd->tio;
+       u32 *data;
+       u64 *data64;
+
+       assert(!tio);
+
+       /* only READ_CAPACITY_16 service action is currently supported */
+       if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) != 0x10) {
+               /* Invalid Field In CDB */
+               iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+               return;
+       }
+
+       tio = cmnd->tio = tio_alloc(1);
+       data = page_address(tio->pvec[0]);
+       assert(data);
+       clear_page(data);
+       data64 = (u64*) data;
+       data64[0] = cpu_to_be64(cmnd->lun->blk_cnt - 1);
+       data[2] = cpu_to_be32(1UL << cmnd->lun->blk_shift);
+
+       tio_set(tio, 12, 0);
+}
+
+static void build_read_response(struct iscsi_cmnd *cmnd)
+{
+       struct tio *tio = cmnd->tio;
+
+       assert(tio);
+       assert(cmnd->lun);
+
+       if (tio_read(cmnd->lun, tio))
+               /* Medium Error/Unrecovered Read Error */
+               iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x11, 0x0);
+}
+
+static void build_write_response(struct iscsi_cmnd *cmnd)
+{
+       int err;
+       struct tio *tio = cmnd->tio;
+
+       assert(tio);
+       assert(cmnd->lun);
+
+       list_del_init(&cmnd->list);
+       err = tio_write(cmnd->lun, tio);
+       if (!err && !LUWCache(cmnd->lun))
+               err = tio_sync(cmnd->lun, tio);
+
+       if (err)
+               /* Medium Error/Write Fault */
+               iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x03, 0x0);
+}
+
+static void build_sync_cache_response(struct iscsi_cmnd *cmnd)
+{
+       assert(cmnd->lun);
+       if (tio_sync(cmnd->lun, NULL))
+               /* Medium Error/Write Fault */
+               iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x03, 0x0);
+}
+
+static void build_generic_response(struct iscsi_cmnd *cmnd)
+{
+       return;
+}
+
+static void build_reserve_response(struct iscsi_cmnd *cmnd)
+{
+       switch (volume_reserve(cmnd->lun, cmnd->conn->session->sid)) {
+       case -ENOENT:
+               /* Logical Unit Not Supported (?) */
+               iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x25, 0x0);
+               break;
+       case -EBUSY:
+               cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
+               break;
+       default:
+               break;
+       }
+}
+
+static void build_release_response(struct iscsi_cmnd *cmnd)
+{
+       if (volume_release(cmnd->lun,
+                          cmnd->conn->session->sid, 0))
+               cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
+}
+
+static void build_reservation_conflict_response(struct iscsi_cmnd *cmnd)
+{
+       cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
+}
+
+static int disk_check_ua(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+       struct ua_entry *ua;
+
+       if (cmnd->lun && ua_pending(cmnd->conn->session, cmnd->lun->lun)) {
+               switch(req->scb[0]){
+               case INQUIRY:
+               case REQUEST_SENSE:
+                       break;
+               case REPORT_LUNS:
+                       ua = ua_get_match(cmnd->conn->session,
+                                         cmnd->lun->lun,
+                                         /* reported luns data has changed */
+                                         0x3f, 0x0e);
+                       ua_free(ua);
+                       break;
+               default:
+                       ua = ua_get_first(cmnd->conn->session, cmnd->lun->lun);
+                       iscsi_cmnd_set_sense(cmnd, UNIT_ATTENTION, ua->asc,
+                                            ua->ascq);
+                       ua_free(ua);
+                       send_scsi_rsp(cmnd, build_generic_response);
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static int disk_check_reservation(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+
+       if (is_volume_reserved(cmnd->lun,
+                              cmnd->conn->session->sid)) {
+               switch (req->scb[0]) {
+               case INQUIRY:
+               case RELEASE:
+               case REPORT_LUNS:
+               case REQUEST_SENSE:
+               case READ_CAPACITY:
+                       /* allowed commands when reserved */
+                       break;
+               case SERVICE_ACTION_IN:
+                       if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) == 0x10)
+                               break;
+                       /* fall through */
+               default:
+                       /* return reservation conflict for all others */
+                       send_scsi_rsp(cmnd,
+                                     build_reservation_conflict_response);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+static int disk_execute_cmnd(struct iscsi_cmnd *cmnd)
+{
+       struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+
+       req->opcode &= ISCSI_OPCODE_MASK;
+
+       if (disk_check_ua(cmnd))
+               return 0;
+
+       if (disk_check_reservation(cmnd))
+               return 0;
+
+       switch (req->scb[0]) {
+       case INQUIRY:
+               send_data_rsp(cmnd, build_inquiry_response);
+               break;
+       case REPORT_LUNS:
+               send_data_rsp(cmnd, build_report_luns_response);
+               break;
+       case READ_CAPACITY:
+               send_data_rsp(cmnd, build_read_capacity_response);
+               break;
+       case MODE_SENSE:
+               send_data_rsp(cmnd, build_mode_sense_response);
+               break;
+       case REQUEST_SENSE:
+               send_data_rsp(cmnd, build_request_sense_response);
+               break;
+       case SERVICE_ACTION_IN:
+               send_data_rsp(cmnd, build_service_action_in_response);
+               break;
+       case READ_6:
+       case READ_10:
+       case READ_16:
+               send_data_rsp(cmnd, build_read_response);
+               break;
+       case WRITE_6:
+       case WRITE_10:
+       case WRITE_16:
+       case WRITE_VERIFY:
+               send_scsi_rsp(cmnd, build_write_response);
+               break;
+       case SYNCHRONIZE_CACHE:
+               send_scsi_rsp(cmnd, build_sync_cache_response);
+               break;
+       case RESERVE:
+               send_scsi_rsp(cmnd, build_reserve_response);
+               break;
+       case RELEASE:
+               send_scsi_rsp(cmnd, build_release_response);
+               break;
+       case START_STOP:
+       case TEST_UNIT_READY:
+       case VERIFY:
+       case VERIFY_16:
+               send_scsi_rsp(cmnd, build_generic_response);
+               break;
+       default:
+               eprintk("%s\n", "we should not come here!");
+               break;
+       }
+
+       return 0;
+}
+
+struct target_type disk_ops =
+{
+       .id = 0,
+       .execute_cmnd = disk_execute_cmnd,
+};
diff --git a/ubuntu/iscsitarget/tio.c b/ubuntu/iscsitarget/tio.c
new file mode 100644 (file)
index 0000000..4220391
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Target I/O.
+ * (C) 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+static int tio_add_pages(struct tio *tio, int count)
+{
+       int i;
+       struct page *page;
+
+       dprintk(D_GENERIC, "%p %d (%d)\n", tio, count, tio->pg_cnt);
+
+       tio->pg_cnt = count;
+
+       count *= sizeof(struct page *);
+
+       do {
+               tio->pvec = kzalloc(count, GFP_KERNEL);
+               if (!tio->pvec)
+                       yield();
+       } while (!tio->pvec);
+
+       for (i = 0; i < tio->pg_cnt; i++) {
+               do {
+                       if (!(page = alloc_page(GFP_KERNEL)))
+                               yield();
+               } while (!page);
+               tio->pvec[i] = page;
+       }
+       return 0;
+}
+
+static struct kmem_cache *tio_cache;
+
+struct tio *tio_alloc(int count)
+{
+       struct tio *tio;
+
+       tio = kmem_cache_alloc(tio_cache, GFP_KERNEL | __GFP_NOFAIL);
+
+       tio->pg_cnt = 0;
+       tio->idx = 0;
+       tio->offset = 0;
+       tio->size = 0;
+       tio->pvec = NULL;
+
+       atomic_set(&tio->count, 1);
+
+       if (count)
+               tio_add_pages(tio, count);
+
+       return tio;
+}
+
+static void tio_free(struct tio *tio)
+{
+       int i;
+       for (i = 0; i < tio->pg_cnt; i++) {
+               assert(tio->pvec[i]);
+               __free_page(tio->pvec[i]);
+       }
+       kfree(tio->pvec);
+       kmem_cache_free(tio_cache, tio);
+}
+
+void tio_put(struct tio *tio)
+{
+       assert(atomic_read(&tio->count));
+       if (atomic_dec_and_test(&tio->count))
+               tio_free(tio);
+}
+
+void tio_get(struct tio *tio)
+{
+       atomic_inc(&tio->count);
+}
+
+void tio_set(struct tio *tio, u32 size, loff_t offset)
+{
+       tio->idx = offset >> PAGE_CACHE_SHIFT;
+       tio->offset = offset & ~PAGE_CACHE_MASK;
+       tio->size = size;
+}
+
+int tio_read(struct iet_volume *lu, struct tio *tio)
+{
+       struct iotype *iot = lu->iotype;
+       assert(iot);
+       return iot->make_request ? iot->make_request(lu, tio, READ) : 0;
+}
+
+int tio_write(struct iet_volume *lu, struct tio *tio)
+{
+       struct iotype *iot = lu->iotype;
+       assert(iot);
+       return iot->make_request ? iot->make_request(lu, tio, WRITE) : 0;
+}
+
+int tio_sync(struct iet_volume *lu, struct tio *tio)
+{
+       struct iotype *iot = lu->iotype;
+       assert(iot);
+       return iot->sync ? iot->sync(lu, tio) : 0;
+}
+
+int tio_init(void)
+{
+       tio_cache = KMEM_CACHE(tio, 0);
+       return  tio_cache ? 0 : -ENOMEM;
+}
+
+void tio_exit(void)
+{
+       if (tio_cache)
+               kmem_cache_destroy(tio_cache);
+}
diff --git a/ubuntu/iscsitarget/ua.c b/ubuntu/iscsitarget/ua.c
new file mode 100644 (file)
index 0000000..db08169
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * IET Unit Attention support
+ *
+ * Copyright (C) 2009 Xie Gang <xiegang112@gmail.com>
+ * Copyright (C) 2009 Arne Redlich <arne.redlich@googlemail.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <scsi/scsi.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+#define ua_hashfn(lun) ((lun % UA_HASH_LEN))
+
+static struct kmem_cache *ua_cache;
+
+int ua_init(void)
+{
+       ua_cache = KMEM_CACHE(ua_entry, 0);
+       if (!ua_cache) {
+               eprintk("%s", "Failed to create ua cache\n");
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+void ua_exit(void)
+{
+       if (ua_cache)
+               kmem_cache_destroy(ua_cache);
+}
+
+/* sess->ua_hash_lock needs to be held */
+static struct ua_entry * ua_find_hash(struct iscsi_session *sess, u32 lun,
+                                     u8 asc, u8 ascq, int match)
+{
+       struct ua_entry *ua;
+       struct list_head *h = &sess->ua_hash[ua_hashfn(lun)];
+
+       list_for_each_entry(ua, h, entry) {
+               if (ua->lun == lun) {
+                       if (!match)
+                               return ua;
+                       if (ua->asc == asc && ua->ascq == ascq)
+                               return ua;
+               }
+       }
+
+       return NULL;
+}
+
+int ua_pending(struct iscsi_session *sess, u32 lun)
+{
+       struct ua_entry *ua;
+
+       spin_lock(&sess->ua_hash_lock);
+       ua = ua_find_hash(sess, lun, 0, 0, 0);
+       spin_unlock(&sess->ua_hash_lock);
+
+       dprintk_ua(ua, sess, lun);
+
+       return ua ? 1 : 0;
+}
+
+/* sess->ua_hash_lock needs to be held */
+static struct ua_entry * __ua_get_hash(struct iscsi_session *sess, u32 lun,
+                                      u8 asc, u8 ascq, int match)
+{
+       struct ua_entry *ua = ua_find_hash(sess, lun, asc, ascq, match);
+
+       if (ua)
+               list_del_init(&ua->entry);
+
+       return ua;
+}
+
+struct ua_entry * ua_get_first(struct iscsi_session *sess, u32 lun)
+{
+       struct ua_entry *ua;
+
+       spin_lock(&sess->ua_hash_lock);
+       ua = __ua_get_hash(sess, lun, 0, 0, 0);
+       spin_unlock(&sess->ua_hash_lock);
+
+       dprintk_ua(ua, sess, lun);
+
+       return ua;
+}
+
+struct ua_entry * ua_get_match(struct iscsi_session *sess, u32 lun,
+                              u8 asc, u8 ascq)
+{
+       struct ua_entry *ua;
+
+       spin_lock(&sess->ua_hash_lock);
+       ua = __ua_get_hash(sess, lun, asc, ascq, 1);
+       spin_unlock(&sess->ua_hash_lock);
+
+       dprintk_ua(ua, sess, lun);
+
+       return ua;
+}
+
+void ua_establish_for_session(struct iscsi_session *sess, u32 lun,
+                             u8 asc, u8 ascq)
+{
+       struct list_head *l = &sess->ua_hash[ua_hashfn(lun)];
+       struct ua_entry *ua = kmem_cache_alloc(ua_cache, GFP_KERNEL);
+
+       if (!ua) {
+               eprintk("%s", "Failed to alloc ua");
+               return;
+       }
+
+       ua->asc = asc;
+       ua->ascq = ascq;
+       ua->lun = lun;
+       ua->session = sess;
+
+       spin_lock(&sess->ua_hash_lock);
+       list_add_tail(&ua->entry, l);
+       spin_unlock(&sess->ua_hash_lock);
+
+       dprintk_ua(ua, sess, lun);
+}
+
+void ua_establish_for_other_sessions(struct iscsi_session *sess, u32 lun,
+                                    u8 asc, u8 ascq)
+{
+       struct list_head *l = &sess->target->session_list;
+       struct iscsi_session *s;
+
+       spin_lock(&sess->target->session_list_lock);
+       list_for_each_entry(s, l, list)
+               if (s->sid != sess->sid)
+                       ua_establish_for_session(s, lun, asc, ascq);
+       spin_unlock(&sess->target->session_list_lock);
+}
+
+void ua_establish_for_all_sessions(struct iscsi_target *target, u32 lun,
+                                  u8 asc, u8 ascq)
+{
+       struct list_head *l = &target->session_list;
+       struct iscsi_session *s;
+
+       spin_lock(&target->session_list_lock);
+       list_for_each_entry(s, l, list)
+               ua_establish_for_session(s, lun, asc, ascq);
+       spin_unlock(&target->session_list_lock);
+
+}
+
+void ua_free(struct ua_entry *ua)
+{
+       if (!ua)
+               return;
+
+       dprintk_ua(ua, ua->session, ua->lun);
+       BUG_ON(!list_empty(&ua->entry));
+       kmem_cache_free(ua_cache, ua);
+}
diff --git a/ubuntu/iscsitarget/volume.c b/ubuntu/iscsitarget/volume.c
new file mode 100644 (file)
index 0000000..ca0d9ca
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Volume manager
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/types.h>
+#include <linux/parser.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+#include "iotype.h"
+
+struct iet_volume *volume_lookup(struct iscsi_target *target, u32 lun)
+{
+       struct iet_volume *volume;
+
+       list_for_each_entry(volume, &target->volumes, list) {
+               if (volume->lun == lun)
+                       return volume;
+       }
+       return NULL;
+}
+
+enum {
+       Opt_type,
+       Opt_iomode,
+       Opt_err,
+};
+
+static match_table_t tokens = {
+       {Opt_type, "Type=%s"},
+       {Opt_iomode, "IOMode=%s"},
+       {Opt_err, NULL},
+};
+
+static int set_iotype(struct iet_volume *volume, char *params)
+{
+       int err = 0;
+       substring_t args[MAX_OPT_ARGS];
+       char *p, *argp = NULL, *buf = (char *) get_zeroed_page(GFP_USER);
+
+       if (!buf)
+               return -ENOMEM;
+       strncpy(buf, params, PAGE_CACHE_SIZE);
+
+       while ((p = strsep(&buf, ",")) != NULL) {
+               int token;
+
+               if (!*p)
+                       continue;
+               token = match_token(p, tokens, args);
+               switch (token) {
+               case Opt_type:
+                       if (!(argp = match_strdup(&args[0])))
+                               err = -ENOMEM;
+                       if (argp && !(volume->iotype = get_iotype(argp)))
+                               err = -ENOENT;
+                       kfree(argp);
+                       break;
+               case Opt_iomode:
+                       if (!(argp = match_strdup(&args[0])))
+                               err = -ENOMEM;
+                       if (argp && !strcmp(argp, "ro"))
+                               SetLUReadonly(volume);
+                       else if (argp && !strcmp(argp, "wb"))
+                               SetLUWCache(volume);
+                       kfree(argp);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (!err && !volume->iotype && !(volume->iotype = get_iotype("fileio"))) {
+               eprintk("%s\n", "Cannot find fileio");
+               err = -EINVAL;
+       }
+
+       free_page((unsigned long) buf);
+
+       return err;
+}
+
+int volume_add(struct iscsi_target *target, struct volume_info *info)
+{
+       int ret;
+       struct iet_volume *volume;
+       char *args;
+
+       volume = volume_lookup(target, info->lun);
+       if (volume)
+               return -EEXIST;
+
+       if (info->lun > 0x3fff)
+               return -EINVAL;
+
+       volume = kzalloc(sizeof(*volume), GFP_KERNEL);
+       if (!volume)
+               return -ENOMEM;
+
+       volume->target = target;
+       volume->lun = info->lun;
+
+       args = kzalloc(info->args_len + 1, GFP_KERNEL);
+       if (!args) {
+               ret = -ENOMEM;
+               goto free_volume;
+       }
+
+       ret = copy_from_user(args, (void *)(unsigned long)info->args_ptr,
+                            info->args_len);
+       if (ret) {
+               ret = -EFAULT;
+               goto free_args;
+       }
+
+       ret = set_iotype(volume, args);
+       if (ret < 0)
+               goto free_args;
+
+       ret = volume->iotype->attach(volume, args);
+       if (ret < 0)
+               goto free_args;
+
+       INIT_LIST_HEAD(&volume->queue.wait_list);
+       spin_lock_init(&volume->queue.queue_lock);
+       spin_lock_init(&volume->reserve_lock);
+
+       volume->l_state = IDEV_RUNNING;
+       atomic_set(&volume->l_count, 0);
+
+       list_add_tail(&volume->list, &target->volumes);
+       atomic_inc(&target->nr_volumes);
+
+       kfree(args);
+
+       return 0;
+free_args:
+       kfree(args);
+free_volume:
+       put_iotype(volume->iotype);
+       kfree(volume);
+
+       return ret;
+}
+
+void iscsi_volume_destroy(struct iet_volume *volume)
+{
+       assert(volume->l_state == IDEV_DEL);
+       assert(!atomic_read(&volume->l_count));
+
+       volume->iotype->detach(volume);
+       put_iotype(volume->iotype);
+       list_del(&volume->list);
+       kfree(volume);
+}
+
+int iscsi_volume_del(struct iscsi_target *target, struct volume_info *info)
+{
+       struct iet_volume *volume;
+
+       eprintk("%x %x\n", target->tid, info->lun);
+       if (!(volume = volume_lookup(target, info->lun)))
+               return -ENOENT;
+
+       volume->l_state = IDEV_DEL;
+       atomic_dec(&target->nr_volumes);
+       if (!atomic_read(&volume->l_count))
+               iscsi_volume_destroy(volume);
+
+       return 0;
+}
+
+struct iet_volume *volume_get(struct iscsi_target *target, u32 lun)
+{
+       struct iet_volume *volume;
+
+       if ((volume = volume_lookup(target, lun))) {
+               if (volume->l_state == IDEV_RUNNING)
+                       atomic_inc(&volume->l_count);
+               else
+                       volume = NULL;
+       }
+       return volume;
+}
+
+void volume_put(struct iet_volume *volume)
+{
+       if (atomic_dec_and_test(&volume->l_count) && volume->l_state == IDEV_DEL)
+               iscsi_volume_destroy(volume);
+}
+
+int volume_reserve(struct iet_volume *volume, u64 sid)
+{
+       if (!volume)
+               return -ENOENT;
+
+       spin_lock(&volume->reserve_lock);
+       if (volume->reserve_sid && volume->reserve_sid != sid) {
+               spin_unlock(&volume->reserve_lock);
+               return -EBUSY;
+       }
+
+       volume->reserve_sid = sid;
+       spin_unlock(&volume->reserve_lock);
+
+       return 0;
+}
+
+int is_volume_reserved(struct iet_volume *volume, u64 sid)
+{
+       if (!volume || !volume->reserve_sid || volume->reserve_sid == sid)
+               return 0;
+
+       return -EBUSY;
+}
+
+int volume_release(struct iet_volume *volume, u64 sid, int force)
+{
+       if (force || volume->reserve_sid == sid)
+               volume->reserve_sid = 0;
+
+       return 0;
+}
+
+static void iet_volume_info_show(struct seq_file *seq, struct iscsi_target *target)
+{
+       struct iet_volume *volume;
+
+       list_for_each_entry(volume, &target->volumes, list) {
+               seq_printf(seq, "\tlun:%u state:%x iotype:%s",
+                          volume->lun, volume->l_state, volume->iotype->name);
+               if (LUReadonly(volume))
+                       seq_printf(seq, " iomode:ro");
+               else if (LUWCache(volume))
+                       seq_printf(seq, " iomode:wb");
+               else
+                       seq_printf(seq, " iomode:wt");
+
+               if (volume->iotype->show)
+                       volume->iotype->show(volume, seq);
+               else
+                       seq_printf(seq, "\n");
+       }
+}
+
+static int iet_volume_seq_open(struct inode *inode, struct file *file)
+{
+       int res;
+       res = seq_open(file, &iet_seq_op);
+       if (!res)
+               ((struct seq_file *)file->private_data)->private =
+                       iet_volume_info_show;
+       return res;
+}
+
+struct file_operations volume_seq_fops = {
+       .owner          = THIS_MODULE,
+       .open           = iet_volume_seq_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
diff --git a/ubuntu/iscsitarget/wthread.c b/ubuntu/iscsitarget/wthread.c
new file mode 100644 (file)
index 0000000..b49ddb7
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Worker thread.
+ * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * This code is licenced under the GPL.
+ */
+
+#include <linux/kthread.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+struct worker_thread_info *worker_thread_pool;
+
+void wthread_queue(struct iscsi_cmnd *cmnd)
+{
+       struct worker_thread_info *info = cmnd->conn->session->target->wthread_info;
+
+       if (!list_empty(&cmnd->list)) {
+               struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+               eprintk("%x %p %x %x %x %x %lx %x\n",
+                       cmnd_itt(cmnd), req, req->opcode, req->scb[0], cmnd->pdu.datasize,
+                       be32_to_cpu(req->data_length), cmnd->flags, req->flags);
+
+               if (cmnd->lun)
+                       eprintk("%u\n", cmnd->lun->lun);
+               assert(list_empty(&cmnd->list));
+       }
+
+       spin_lock(&info->wthread_lock);
+       list_add_tail(&cmnd->list, &info->work_queue);
+       spin_unlock(&info->wthread_lock);
+
+       atomic_inc(&cmnd->conn->nr_busy_cmnds);
+
+       wake_up(&info->wthread_sleep);
+}
+
+static struct iscsi_cmnd * get_ready_cmnd(struct worker_thread_info *info)
+{
+       struct iscsi_cmnd *cmnd = NULL;
+
+       spin_lock(&info->wthread_lock);
+       if (!list_empty(&info->work_queue)) {
+               cmnd = list_entry(info->work_queue.next, struct iscsi_cmnd, list);
+               list_del_init(&cmnd->list);
+
+               assert(cmnd->conn);
+       }
+       spin_unlock(&info->wthread_lock);
+
+       return cmnd;
+}
+
+static int cmnd_execute(struct iscsi_cmnd *cmnd)
+{
+       int type = cmnd->conn->session->target->trgt_param.target_type;
+
+       assert(target_type_array[type]->execute_cmnd);
+       return target_type_array[type]->execute_cmnd(cmnd);
+}
+
+static int worker_thread(void *arg)
+{
+       struct worker_thread *wt = (struct worker_thread *) arg;
+       struct worker_thread_info *info = wt->w_info;
+       struct iscsi_cmnd *cmnd;
+       struct iscsi_conn *conn;
+       DECLARE_WAITQUEUE(wait, current);
+
+       add_wait_queue(&info->wthread_sleep, &wait);
+
+       __set_current_state(TASK_RUNNING);
+       do {
+               while (!list_empty(&info->work_queue) &&
+                      (cmnd = get_ready_cmnd(info))) {
+                       conn = cmnd->conn;
+                       cmnd_execute(cmnd);
+                       assert(conn);
+                       atomic_dec(&conn->nr_busy_cmnds);
+               }
+
+               __set_current_state(TASK_INTERRUPTIBLE);
+               if (list_empty(&info->work_queue))
+                       schedule();
+
+               __set_current_state(TASK_RUNNING);
+       } while (!kthread_should_stop());
+
+       remove_wait_queue(&info->wthread_sleep, &wait);
+
+       return 0;
+}
+
+static int start_one_worker_thread(struct worker_thread_info *info, u32 tid)
+{
+       struct worker_thread *wt;
+       struct task_struct *task;
+
+       if (!(wt = kmalloc(sizeof(struct worker_thread), GFP_KERNEL)))
+               return -ENOMEM;
+
+       wt->w_info = info;
+       task = kthread_create(worker_thread, wt, "istiod%d", tid);
+       if (IS_ERR(task)) {
+               kfree(wt);
+               return PTR_ERR(task);
+       }
+
+       wt->w_task = task;
+       list_add(&wt->w_list, &info->wthread_list);
+       info->nr_running_wthreads++;
+
+       wake_up_process(task);
+
+       return 0;
+}
+
+static int stop_one_worker_thread(struct worker_thread *wt)
+{
+       struct worker_thread_info *info = wt->w_info;
+       int err;
+
+       assert(wt->w_task);
+       err = kthread_stop(wt->w_task);
+
+       if (err < 0 && err != -EINTR)
+               return err;
+
+       list_del(&wt->w_list);
+       kfree(wt);
+       info->nr_running_wthreads--;
+
+       return 0;
+}
+
+int wthread_init(struct worker_thread_info *info)
+{
+       spin_lock_init(&info->wthread_lock);
+
+       info->nr_running_wthreads = 0;
+
+       INIT_LIST_HEAD(&info->work_queue);
+       INIT_LIST_HEAD(&info->wthread_list);
+
+       init_waitqueue_head(&info->wthread_sleep);
+
+       return 0;
+}
+
+int wthread_start(struct worker_thread_info *info, int wthreads, u32 tid)
+{
+       int err = 0;
+
+       while (info->nr_running_wthreads < wthreads) {
+               if ((err = start_one_worker_thread(info, tid)) < 0) {
+                       eprintk("Fail to create a worker thread %d\n", err);
+                       goto out;
+               }
+       }
+
+       while (info->nr_running_wthreads > wthreads) {
+               struct worker_thread *wt;
+               wt = list_entry(info->wthread_list.next, struct worker_thread, w_list);
+               if ((err = stop_one_worker_thread(wt)) < 0) {
+                       eprintk("Fail to stop a worker thread %d\n", err);
+                       break;
+               }
+       }
+out:
+       return err;
+}
+
+int wthread_stop(struct worker_thread_info *info)
+{
+       struct worker_thread *wt, *tmp;
+       int err = 0;
+
+       list_for_each_entry_safe(wt, tmp, &info->wthread_list, w_list) {
+               if ((err = stop_one_worker_thread(wt)) < 0) {
+                       eprintk("Fail to stop a worker thread %d\n", err);
+                       return err;
+               }
+       }
+
+       return err;
+}
+
+int wthread_module_init()
+{
+       int err;
+
+       if (!worker_thread_pool_size)
+               return 0;
+
+       worker_thread_pool = kmalloc(sizeof(struct worker_thread_info),
+                                    GFP_KERNEL);
+       if (!worker_thread_pool)
+               return -ENOMEM;
+
+       wthread_init(worker_thread_pool);
+
+       err = wthread_start(worker_thread_pool, worker_thread_pool_size, 0);
+       if (err) {
+               kfree(worker_thread_pool);
+               worker_thread_pool = NULL;
+               return err;
+       }
+
+       iprintk("iscsi_trgt using worker thread pool; size = %ld\n",
+               worker_thread_pool_size);
+
+       return 0;
+}
+
+void wthread_module_exit()
+{
+       if (!worker_thread_pool_size)
+               return;
+
+       wthread_stop(worker_thread_pool);
+       kfree(worker_thread_pool);
+}