##
##
##
+source "ubuntu/iscsitarget/Kconfig"
+##
+##
+##
##
##
##
##
##
##
+obj-$(CONFIG_SCSI_ISCSITARGET) += iscsitarget/
+##
+##
+##
##
##
##
--- /dev/null
+Downloaded from: svn://svn.berlios.de/iscsitarget/trunk
+Current Version: 1.4.19
--- /dev/null
+config SCSI_ISCSITARGET
+ tristate "iSCSI Target Driver"
+ depends on SCSI
--- /dev/null
+#
+# 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
+
--- /dev/null
+/*
+ * 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(¶ms, ",")) != 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,
+};
--- /dev/null
+/*
+ * (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
+};
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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(¶ms, ",")) != 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,
+};
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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]);
+}
--- /dev/null
+/*
+ * (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
--- /dev/null
+/*
+ * 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>");
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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(¶ms, ",")) != 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,
+};
--- /dev/null
+/*
+ * (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;
+}
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * (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,
+};
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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);
+}