[PATCH][RFC 19/23]: scst_local target driver

Vladislav Bolkhovitin
2008-12-10T19:00:28Z

This patch contains a driver which allows the creation of user space
target drivers.

It was written by Richard Sharpe based on scst_debug driver. It allows
to access devices that are exported via SCST directly on the same Linux
system that they are exported from. Those devices appears as a regular
/dev/sg, sd, st, etc. devices, so a user space target driver can use
these device nodes to execute SCSI commands on. A possible work flow is
as follows:

- A target driver receives a new connection.

- Using the corresponding interface of scst_local driver (see below)
it will create a new SCST session, which will be assigned to the
corresponding ACL, and get a set of the corresponding sg devices.

- It will open them exclusively, so no other programs is able to
intermix with it.

- It will start SCSI commands exchange using the open sg devices.

- Then, when the initiator closed the session, the target driver will
delete the corresponding SCST session.

ACL is an "Access Control Group" also called "security group" in the
SCST documentation. It allows different initiators to see different set
of devices with different access permissions. This feature is also often
called "LUN masking".

At the moment scst_local driver isn't fully completed in the area of the
interface to allow each initiator to have own dedicated session assigned
to the corresponding ACL. Only basic functionality via
scst_ini_targ_debug /sys entry implemented.

There are two possible approaches. We need your advice about which
approach is better.

On the load scst_local module would register SCST target and session
with target and initiator names scst_local_targ_tmpl.name. Then it would
create all available from SCST devices. There would be possibility to
override both target and initiator names on load the scst_local module
via corresponding module parameters.

Each created SCSI host would have three attributes in sysfs, namely
target_name, initiator_name and host_no. Host_no should have host number
of the created SCSI hosts.

Possible approaches are the following:

1. Sysfs-based.

- scst_local_add_host_store() would accept 2 parameters, divided by
',': target name and initiator name. If target with that name already
exists, it would be reused, otherwise created. Then
scst_local_add_host_store() would create a new SCSI host and add to it
available from SCST devices.

- If scst_local_add_host_store() would get the first non-empty symbol
'-', it would remove the corresponding SCST session. If the target after
the session removal has no other sessions, it would be removed too.

For instance, on a new connection (session) from remote initiator, an
user space target driver:

- Using "add_host" command would create a new SCST session in a new
target, if it's the first session.

- Then it would search through sysfs and find using target and
initiator names host number of the newly created SCSI host.

- Then, e.g. by using lsscsi utility, it would find the corresponding
sg devices, then open them.

- Then it would start commands exchange using the open sg devices.

- Then, when the initiator closed the session, it would delete the
corresponding SCST session and, for the last session, target.

2. IOCTL-based.

- Scst_local would have an IOCTL functions: create session and delete
session. Both would accept 2 parameters: target name and initiator name.

- Create session would accept 2 parameters: target name and initiator
name. If target with that name already exists, it would be reused,
otherwise created. Then it would create a new SCSI host and add to it
available from SCST devices. It would return the created SCSI number on
success and -1 on failure.

- Delete session would remove the corresponding SCST session. If the
target after the session removal has no other sessions, it would be
removed too.

For instance, on a new connection (session) from remote initiator, an
user space target driver:

- Using "create session" IOCTL would create a new SCST session in a
new target, if it's the first session. It would return the new SCSI host
number.

- Then, e.g. by using lsscsi utility, it would find the corresponding
sg devices, then open them.

- Then it would start commands exchange using the open sg devices.

- Then, when the initiator closed the session, it would delete the
corresponding SCST session using "delete session" IOCTL and, for the
last session, target.

I personally would prefer the IOCTL approach as easier to use.

Configfs-based approach isn't considered, because it's better to keep
all SCSI target related entries in one place in /sys/scsi_target, not in
two places in /sys/scsi_target and /sys/config/scst_local.

Signed-off-by: Richard Sharpe <realrichardsharpe@gmail.com>
Signed-off-by: Vladislav Bolkhovitin <vst@vlnb.net>
---
drivers/scst/scst_local/Kconfig | 10
drivers/scst/scst_local/Makefile | 10
drivers/scst/scst_local/scst_local.c | 1054 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1074 insertions(+)

diff -uprN orig/linux-2.6.27/drivers/scst/scst_local/Kconfig linux-2.6.27/drivers/scst/scst_local/Kconfig
--- orig/linux-2.6.27/drivers/scst/scst_local/Kconfig
+++ linux-2.6.27/drivers/scst/scst_local/Kconfig
@@ -0,0 +1,10 @@
+config SCST_LOCAL
+ tristate "SCST Local driver"
+ depends on SCST
+ ---help---
+ This module provides a LLD SCSI driver that connects to
+ the SCST target mode subsystem in a loop-back manner.
+ It allows you to test target-mode device-handlers locally.
+ You will need the SCST subsystem as well.
+
+ If unsure whether you really want or need this, say N.
diff -uprN orig/linux-2.6.27/drivers/scst/scst_local/Makefile linux-2.6.27/drivers/scst/scst_local/Makefile
--- orig/linux-2.6.27/drivers/scst/scst_local/Makefile
+++ linux-2.6.27/drivers/scst/scst_local/Makefile
@@ -0,0 +1,10 @@
+SCST_INC_DIR := include/scst
+SCST_DIR := drivers/scst
+EXTRA_CFLAGS += -I$(SCST_INC_DIR) -I$(SCST_DIR)
+
+#EXTRA_CFLAGS += -DCONFIG_SCST_EXTRACHECKS
+#EXTRA_CFLAGS += -DCONFIG_SCST_TRACING
+#EXTRA_CFLAGS += -DCONFIG_SCST_DEBUG
+
+obj-$(CONFIG_SCST_LOCAL) += scst_local.o
+
diff -uprN orig/linux-2.6.27/drivers/scst/scst_local/scst_local.c linux-2.6.27/drivers/scst/scst_local/scst_local.c
--- orig/linux-2.6.27/drivers/scst/scst_local/scst_local.c
+++ linux-2.6.27/drivers/scst/scst_local/scst_local.c
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2008 Richard Sharpe
+ * Copyright (C) 1992 Eric Youngdale
+ *
+ * Simulate a host adapter and an SCST target adapter back to back
+ *
+ * Based on the scsi_debug.c driver originally by Eric Youngdale and
+ * others, including D Gilbert et al
+ *
+ */
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/genhd.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <linux/scatterlist.h>
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/stat.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsicam.h>
+#include <scsi/scsi_eh.h>
+
+/* SCST includes ... */
+#include <scst_const.h>
+#include <scst.h>
+
+#define LOG_PREFIX "scst_local"
+
+#include <scst_debug.h>
+
+
+#if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G)
+#warning "HIGHMEM kernel configurations are not supported by this module, \
+ because nowadays it isn't worth the effort. Consider changing \
+ VMSPLIT option or use a 64-bit configuration instead. See SCST core \
+ README file for details."
+#endif
+
+#ifdef CONFIG_SCST_DEBUG
+#define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_PID | \
+ TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_MINOR | \
+ TRACE_MGMT_DEBUG | TRACE_MINOR | TRACE_SPECIAL)
+#else
+# ifdef CONFIG_SCST_TRACING
+#define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
+ TRACE_MINOR | TRACE_SPECIAL)
+# endif
+#endif
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+#define trace_flag scst_local_trace_flag
+static unsigned long scst_local_trace_flag = SCST_LOCAL_DEFAULT_LOG_FLAGS;
+#endif
+
+#define TRUE 1
+#define FALSE 0
+
+/*
+ * Some definitions needed by the scst portion
+ */
+static void scst_local_remove_adapter(void);
+static int scst_local_add_adapter(void);
+
+#define SCST_LOCAL_VERSION "0.9"
+static const char *scst_local_version_date = "20081130";
+
+/*
+ * Target structures that are shared between the two pieces
+ * This will have to change if we have more than one target
+ */
+static struct scst_tgt_template scst_local_targ_tmpl;
+
+/*
+ * Some max values
+ */
+#define DEF_NUM_HOST 1
+#define DEF_NUM_TGTS 1
+#define SCST_LOCAL_MAX_TARGETS 16
+#define DEF_MAX_LUNS 256
+
+/*
+ * These following defines are the SCSI Host LLD (the initiator).
+ * SCST Target Driver is below
+ */
+
+static int scst_local_add_host = DEF_NUM_HOST;
+static int scst_local_num_tgts = DEF_NUM_TGTS;
+static int scst_local_max_luns = DEF_MAX_LUNS;
+
+static int num_aborts;
+static int num_dev_resets;
+static int num_target_resets;
+
+/*
+ * Each host has multiple targets, each of which has a separate session
+ * to SCST.
+ */
+
+struct scst_local_host_info {
+ struct list_head host_list;
+ struct Scsi_Host *shost;
+ struct scst_tgt *target;
+ struct scst_session *session[SCST_LOCAL_MAX_TARGETS];
+ struct device dev;
+};
+
+#define to_scst_lcl_host(d) \
+ container_of(d, struct scst_local_host_info, dev)
+
+/*
+ * Maintains data that is needed during command processing ...
+ */
+struct scst_local_tgt_specific {
+ struct scsi_cmnd *cmnd;
+ void (*done)(struct scsi_cmnd *);
+};
+
+/*
+ * We use a pool of objects maintaind by the kernel so that it is less
+ * likely to have to allocate them when we are in the data path.
+ */
+static struct kmem_cache *tgt_specific_pool;
+
+static LIST_HEAD(scst_local_host_list);
+static DEFINE_SPINLOCK(scst_local_host_list_lock);
+
+static char scst_local_proc_name[] = "scst_ini_targ_debug";
+
+static struct bus_type scst_fake_lld_bus;
+static struct device scst_fake_primary;
+
+static struct device_driver scst_local_driverfs_driver = {
+ .name = scst_local_proc_name,
+ .bus = &scst_fake_lld_bus,
+};
+
+module_param_named(add_host, scst_local_add_host, int, S_IRUGO | S_IWUSR);
+module_param_named(num_tgts, scst_local_num_tgts, int, S_IRUGO | S_IWUSR);
+module_param_named(max_luns, scst_local_max_luns, int, S_IRUGO | S_IWUSR);
+
+MODULE_AUTHOR("Richard Sharpe + ideas from SCSI_DEBUG");
+MODULE_DESCRIPTION("SCSI+SCST local adapter driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SCST_LOCAL_VERSION);
+
+MODULE_PARM_DESC(add_host, "0..127 hosts can be created (def=1)");
+MODULE_PARM_DESC(num_tgts, "mumber of targets per host (def=1)");
+MODULE_PARM_DESC(max_luns, "number of luns per target (def=1)");
+
+static int scst_local_target_register(void);
+
+static int scst_local_proc_info(struct Scsi_Host *host, char *buffer,
+ char **start, off_t offset, int length,
+ int inout)
+{
+ int len, pos, begin;
+
+ TRACE_ENTRY();
+
+ if (inout == 1)
+ return -EACCES;
+
+ begin = 0;
+ pos = len = sprintf(buffer, "scst_local adapter driver, version "
+ "%s [%s]\n"
+ "num_tgts=%d, Aborts=%d, Device Resets=%d, "
+ "Target Resets=%d\n",
+ SCST_LOCAL_VERSION, scst_local_version_date,
+ scst_local_num_tgts, num_aborts, num_dev_resets,
+ num_target_resets);
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (start)
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+
+ TRACE_EXIT_RES(len);
+ return len;
+}
+
+static ssize_t scst_local_add_host_show(struct device_driver *ddp, char *buf)
+{
+ int len = 0;
+ struct scst_local_host_info *scst_lcl_host, *tmp;
+
+ TRACE_ENTRY();
+
+ list_for_each_entry_safe(scst_lcl_host, tmp, &scst_local_host_list,
+ host_list) {
+ len += scnprintf(&buf[len], PAGE_SIZE - len, " Initiator: %s\n",
+ scst_lcl_host->session[0]->initiator_name);
+ }
+
+ TRACE_EXIT_RES(len);
+ return len;
+}
+
+static ssize_t scst_local_add_host_store(struct device_driver *ddp,
+ const char *buf, size_t count)
+{
+ int delta_hosts;
+
+ TRACE_ENTRY();
+
+ if (sscanf(buf, "%d", &delta_hosts) != 1)
+ return -EINVAL;
+ if (delta_hosts > 0) {
+ do {
+ scst_local_add_adapter();
+ } while (--delta_hosts);
+ } else if (delta_hosts < 0) {
+ do {
+ scst_local_remove_adapter();
+ } while (++delta_hosts);
+ }
+
+ TRACE_EXIT_RES(count);
+ return count;
+}
+
+static DRIVER_ATTR(add_host, S_IRUGO | S_IWUSR, scst_local_add_host_show,
+ scst_local_add_host_store);
+
+static int do_create_driverfs_files(void)
+{
+ int ret;
+
+ TRACE_ENTRY();
+
+ ret = driver_create_file(&scst_local_driverfs_driver,
+ &driver_attr_add_host);
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+static void do_remove_driverfs_files(void)
+{
+ driver_remove_file(&scst_local_driverfs_driver,
+ &driver_attr_add_host);
+}
+
+static char scst_local_info_buf[256];
+
+static const char *scst_local_info(struct Scsi_Host *shp)
+{
+ TRACE_ENTRY();
+
+ sprintf(scst_local_info_buf, "scst_local, version %s [%s], "
+ "Aborts: %d, Device Resets: %d, Target Resets: %d",
+ SCST_LOCAL_VERSION, scst_local_version_date,
+ num_aborts, num_dev_resets, num_target_resets);
+
+ TRACE_EXIT();
+ return scst_local_info_buf;
+}
+
+/*
+static int scst_local_ioctl(struct scsi_device *dev, int cmd, void __user *arg)
+{
+ TRACE_ENTRY();
+
+ if (scst_local_opt_noise & SCST_LOCAL_OPT_LLD_NOISE)
+ printk(KERN_INFO "scst_local: ioctl: cmd=0x%x\n", cmd);
+ return -EINVAL;
+
+ TRACE_EXIT();
+}
+*/
+
+static int scst_local_abort(struct scsi_cmnd *SCpnt)
+{
+ struct scst_local_host_info *scst_lcl_host;
+ int ret = 0;
+ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = to_scst_lcl_host(scsi_get_device(SCpnt->device->host));
+
+ ret = scst_rx_mgmt_fn_tag(scst_lcl_host->session[SCpnt->device->id],
+ SCST_LUN_RESET, SCpnt->tag, FALSE,
+ &dev_reset_completion);
+
+ wait_for_completion_interruptible(&dev_reset_completion);
+
+ ++num_aborts;
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+/*
+ * We issue a mgmt function. We should pass a structure to the function
+ * that contains our private data, so we can retrieve the status from the
+ * done routine ... TODO
+ */
+static int scst_local_device_reset(struct scsi_cmnd *SCpnt)
+{
+ struct scst_local_host_info *scst_lcl_host;
+ int lun;
+ int ret = 0;
+ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = to_scst_lcl_host(scsi_get_device(SCpnt->device->host));
+
+ lun = SCpnt->device->lun;
+ lun = (lun & 0xFF) << 8 | ((lun & 0xFF00) >> 8); /* FIXME: LE only */
+
+ ret = scst_rx_mgmt_fn_lun(scst_lcl_host->session[SCpnt->device->id],
+ SCST_LUN_RESET,
+ (const uint8_t *)&lun,
+ sizeof(lun), FALSE,
+ &dev_reset_completion);
+
+ /*
+ * Now wait for the completion ...
+ */
+ wait_for_completion_interruptible(&dev_reset_completion);
+
+ ++num_dev_resets;
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+static int scst_local_target_reset(struct scsi_cmnd *SCpnt)
+{
+ struct scst_local_host_info *scst_lcl_host;
+ int lun;
+ int ret = 0;
+ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = to_scst_lcl_host(scsi_get_device(SCpnt->device->host));
+
+ lun = SCpnt->device->lun;
+ lun = (lun & 0xFF) << 8 | ((lun & 0xFF00) >> 8); /* FIXME: LE only */
+
+ ret = scst_rx_mgmt_fn_lun(scst_lcl_host->session[SCpnt->device->id],
+ SCST_TARGET_RESET,
+ (const uint8_t *)&lun,
+ sizeof(lun), FALSE,
+ &dev_reset_completion);
+
+ /*
+ * Now wait for the completion ...
+ */
+ wait_for_completion_interruptible(&dev_reset_completion);
+
+ ++num_target_resets;
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+static void copy_sense(struct scsi_cmnd *cmnd, struct scst_cmd *scst_cmnd)
+{
+ int scst_cmnd_sense_len = scst_cmd_get_sense_buffer_len(scst_cmnd);
+
+ TRACE_ENTRY();
+
+ scst_cmnd_sense_len = (SCSI_SENSE_BUFFERSIZE < scst_cmnd_sense_len ?
+ SCSI_SENSE_BUFFERSIZE : scst_cmnd_sense_len);
+ memcpy(cmnd->sense_buffer, scst_cmd_get_sense_buffer(scst_cmnd),
+ scst_cmnd_sense_len);
+
+ TRACE_BUFFER("Sense set", cmnd->sense_buffer, scst_cmnd_sense_len);
+
+ TRACE_EXIT();
+ return;
+}
+
+/*
+ * Utility function to handle processing of done and allow
+ * easy insertion of error injection if desired
+ */
+static int scst_local_send_resp(struct scsi_cmnd *cmnd,
+ struct scst_cmd *scst_cmnd,
+ void (*done)(struct scsi_cmnd *),
+ int scsi_result)
+{
+ int ret = 0;
+
+ TRACE_ENTRY();
+
+ if (cmnd && scst_cmnd) {
+ /* Simulate autosense by this driver */
+ if (SAM_STAT_CHECK_CONDITION == (scsi_result & 0xFF))
+ copy_sense(cmnd, scst_cmnd);
+ }
+
+ if (cmnd)
+ cmnd->result = scsi_result;
+ if (done)
+ done(cmnd);
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+/*
+ * This does the heavy lifting ... we pass all the commands on to the
+ * target driver and have it do its magic ...
+ */
+static int scst_local_queuecommand(struct scsi_cmnd *SCpnt,
+ void (*done)(struct scsi_cmnd *))
+{
+ struct scst_local_tgt_specific *tgt_specific = NULL;
+ struct scst_local_host_info *scst_lcl_host;
+ int target = SCpnt->device->id;
+ int lun;
+ struct scst_cmd *scst_cmd = NULL;
+ scst_data_direction dir;
+
+ TRACE_ENTRY();
+
+ TRACE_DBG("targ: %d, init id %d, lun %d, cmd: 0X%02X\n",
+ target, SCpnt->device->host->hostt->this_id, SCpnt->device->lun,
+ SCpnt->cmnd[0]);
+
+ scst_lcl_host = to_scst_lcl_host(scsi_get_device(SCpnt->device->host));
+
+ scsi_set_resid(SCpnt, 0);
+
+ if (target == SCpnt->device->host->hostt->this_id) {
+ printk(KERN_ERR "%s: initiator's id used as target\n",
+ __func__);
+ return scst_local_send_resp(SCpnt, NULL, done,
+ DID_NO_CONNECT << 16);
+ }
+
+ /*
+ * Tell the target that we have a command ... but first we need
+ * to get the LUN into a format that SCST understand
+ */
+ lun = SCpnt->device->lun;
+ lun = (lun & 0xFF) << 8 | ((lun & 0xFF00) >> 8); /* FIXME: LE only */
+ scst_cmd = scst_rx_cmd(scst_lcl_host->session[SCpnt->device->id],
+ (const uint8_t *)&lun,
+ sizeof(lun), SCpnt->cmnd,
+ SCpnt->cmd_len, TRUE);
+ if (!scst_cmd) {
+ printk(KERN_ERR "%s out of memory at line %d\n",
+ __func__, __LINE__);
+ return -ENOMEM;
+ }
+
+ scst_cmd_set_tag(scst_cmd, SCpnt->tag);
+ switch (scsi_get_tag_type(SCpnt->device)) {
+ case MSG_SIMPLE_TAG:
+ scst_cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
+ break;
+ case MSG_HEAD_TAG:
+ scst_cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
+ break;
+ case MSG_ORDERED_TAG:
+ scst_cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
+ break;
+ case SCSI_NO_TAG:
+ default:
+ scst_cmd->queue_type = SCST_CMD_QUEUE_UNTAGGED;
+ break;
+ }
+
+ dir = SCST_DATA_NONE;
+ switch (SCpnt->sc_data_direction) {
+ case DMA_TO_DEVICE:
+ dir = SCST_DATA_WRITE;
+ break;
+ case DMA_FROM_DEVICE:
+ dir = SCST_DATA_READ;
+ break;
+ case DMA_BIDIRECTIONAL:
+ printk(KERN_ERR "%s: DMA_BIDIRECTIONAL not allowed!\n",
+ __func__);
+ return scst_local_send_resp(SCpnt, NULL, done,
+ DID_ERROR << 16);
+ /*dir = SCST_DATA_UNKNOWN;*/
+ break;
+ case DMA_NONE:
+ default:
+ dir = SCST_DATA_NONE;
+ break;
+ }
+ scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt));
+
+ /*
+ * Defer allocating memory until all error paths are done
+ */
+ tgt_specific = kmem_cache_alloc(tgt_specific_pool, GFP_ATOMIC);
+ if (!tgt_specific) {
+ printk(KERN_ERR "%s out of memory at line %d\n",
+ __func__, __LINE__);
+ return -ENOMEM;
+ }
+ tgt_specific->cmnd = SCpnt;
+ tgt_specific->done = done;
+
+ scst_cmd_set_tgt_priv(scst_cmd, tgt_specific);
+
+ /* Set the SGL things directly ... */
+ scst_cmd_set_tgt_sg(scst_cmd, scsi_sglist(SCpnt), scsi_sg_count(SCpnt));
+
+ /*
+ * Unfortunately, we called with IRQs disabled, so have no choice,
+ * except pass to the thread context.
+ */
+ scst_cmd_init_done(scst_cmd, SCST_CONTEXT_THREAD);
+
+ /*
+ * We are done here I think. Other callbacks move us forward.
+ */
+ TRACE_EXIT();
+ return 0;
+}
+
+static void scst_local_release_adapter(struct device *dev)
+{
+ struct scst_local_host_info *scst_lcl_host;
+ int i = 0;
+
+ TRACE_ENTRY();
+ scst_lcl_host = to_scst_lcl_host(dev);
+ if (scst_lcl_host) {
+ for (i = 0; i < scst_local_num_tgts; i++)
+ if (scst_lcl_host->session[i])
+ scst_unregister_session(
+ scst_lcl_host->session[i], TRUE, NULL);
+ scst_unregister(scst_lcl_host->target);
+ kfree(scst_lcl_host);
+ }
+
+ TRACE_EXIT();
+}
+
+/*
+ * Add an adapter on the host side ... We add the target before we add
+ * the host (initiator) so that we don't get any requests before we are
+ * ready for them.
+ *
+ * I want to convert this so we can map many hosts to a smaller number of
+ * targets to support the simulation of multiple initiators.
+ */
+static int scst_local_add_adapter(void)
+{
+ int error = 0, i = 0;
+ struct scst_local_host_info *scst_lcl_host;
+ char name[32];
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = kzalloc(sizeof(struct scst_local_host_info),
+ GFP_KERNEL);
+ if (NULL == scst_lcl_host) {
+ printk(KERN_ERR "%s out of memory at line %d\n",
+ __func__, __LINE__);
+ return -ENOMEM;
+ }
+
+ spin_lock(&scst_local_host_list_lock);
+ list_add_tail(&scst_lcl_host->host_list, &scst_local_host_list);
+ spin_unlock(&scst_local_host_list_lock);
+
+ /*
+ * Register a target with SCST and add a session
+ */
+ sprintf(name, "scstlcltgt%d", scst_local_add_host);
+ scst_lcl_host->target = scst_register(&scst_local_targ_tmpl, name);
+ if (!scst_lcl_host) {
+ printk(KERN_WARNING "scst_register_target failed:\n");
+ error = -1;
+ goto cleanup;
+ }
+
+ /*
+ * Create a session for each device
+ */
+ for (i = 0; i < scst_local_num_tgts; i++) {
+ sprintf(name, "scstlclhst%d:%d", scst_local_add_host, i);
+ scst_lcl_host->session[i] = scst_register_session(
+ scst_lcl_host->target,
+ TRUE, name, NULL, NULL);
+ if (!scst_lcl_host->session[i]) {
+ printk(KERN_WARNING "scst_register_session failed:\n");
+ error = -1;
+ goto unregister_target;
+ }
+ }
+
+ scst_lcl_host->dev.bus = &scst_fake_lld_bus;
+ scst_lcl_host->dev.parent = &scst_fake_primary;
+ scst_lcl_host->dev.release = &scst_local_release_adapter;
+ sprintf(scst_lcl_host->dev.bus_id, "scst_adp_%d", scst_local_add_host);
+
+ error = device_register(&scst_lcl_host->dev);
+ if (error)
+ goto unregister_session;
+
+ scst_local_add_host++; /* keep count of what we have added */
+
+ TRACE_EXIT();
+ return error;
+
+unregister_session:
+ for (i = 0; i < scst_local_num_tgts; i++) {
+ if (scst_lcl_host->session[i])
+ scst_unregister_session(scst_lcl_host->session[i],
+ TRUE, NULL);
+ }
+unregister_target:
+ scst_unregister(scst_lcl_host->target);
+cleanup:
+ kfree(scst_lcl_host);
+ TRACE_EXIT();
+ return error;
+}
+
+/*
+ * Remove an adapter ...
+ */
+static void scst_local_remove_adapter(void)
+{
+ struct scst_local_host_info *scst_lcl_host = NULL;
+
+ TRACE_ENTRY();
+
+ spin_lock(&scst_local_host_list_lock);
+ if (!list_empty(&scst_local_host_list)) {
+ scst_lcl_host = list_entry(scst_local_host_list.prev,
+ struct scst_local_host_info,
+ host_list);
+ list_del(&scst_lcl_host->host_list);
+ }
+ spin_unlock(&scst_local_host_list_lock);
+
+ if (!scst_lcl_host)
+ return;
+
+ device_unregister(&scst_lcl_host->dev);
+
+ --scst_local_add_host;
+
+ TRACE_EXIT();
+}
+
+static struct scsi_host_template scst_lcl_ini_driver_template = {
+ .proc_info = scst_local_proc_info,
+ .proc_name = scst_local_proc_name,
+ .name = SCST_LOCAL_NAME,
+ .info = scst_local_info,
+/* .ioctl = scst_local_ioctl, */
+ .queuecommand = scst_local_queuecommand,
+ .eh_abort_handler = scst_local_abort,
+ .eh_device_reset_handler = scst_local_device_reset,
+ .eh_target_reset_handler = scst_local_target_reset,
+ .can_queue = 256,
+ .this_id = SCST_LOCAL_MAX_TARGETS,
+ /* SCST doesn't support sg chaining */
+ .sg_tablesize = SCSI_MAX_SG_SEGMENTS,
+ .cmd_per_lun = 32,
+ .max_sectors = 0xffff,
+ /*
+ * There's no gain to merge requests on this level. If necessary,
+ * they will be merged at the backstorage level.
+ */
+ .use_clustering = DISABLE_CLUSTERING,
+ .skip_settle_delay = 1,
+ .module = THIS_MODULE,
+};
+
+static void scst_fake_0_release(struct device *dev)
+{
+ TRACE_ENTRY();
+
+ TRACE_EXIT();
+}
+
+static struct device scst_fake_primary = {
+ .bus_id = "scst_fake_0",
+ .release = scst_fake_0_release,
+};
+
+static int __init scst_local_init(void)
+{
+ int ret, k, adapters;
+
+ TRACE_ENTRY();
+
+#if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G)
+ PRINT_ERROR("%s", "HIGHMEM kernel configurations are not supported. "
+ "Consider changing VMSPLIT option or use a 64-bit "
+ "configuration instead. See SCST core README file for "
+ "details.");
+ ret = -EINVAL;
+ goto out;
+#endif
+
+ TRACE_DBG("Adapters: %d\n", scst_local_add_host);
+
+ if (scst_local_num_tgts > SCST_LOCAL_MAX_TARGETS)
+ scst_local_num_tgts = SCST_LOCAL_MAX_TARGETS;
+
+ /*
+ * Allocate a pool of structures for tgt_specific structures
+ */
+ tgt_specific_pool = kmem_cache_create("scst_tgt_specific",
+ sizeof(struct scst_local_tgt_specific),
+ 0, SCST_SLAB_FLAGS, NULL);
+
+ if (!tgt_specific_pool) {
+ printk(KERN_WARNING "%s: out of memory for "
+ "tgt_specific structs",
+ __func__);
+ return -ENOMEM;
+ }
+
+ ret = device_register(&scst_fake_primary);
+ if (ret < 0) {
+ printk(KERN_WARNING "%s: device_register error: %d\n",
+ __func__, ret);
+ goto destroy_kmem;
+ }
+ ret = bus_register(&scst_fake_lld_bus);
+ if (ret < 0) {
+ printk(KERN_WARNING "%s: bus_register error: %d\n",
+ __func__, ret);
+ goto dev_unreg;
+ }
+ ret = driver_register(&scst_local_driverfs_driver);
+ if (ret < 0) {
+ printk(KERN_WARNING "%s: driver_register error: %d\n",
+ __func__, ret);
+ goto bus_unreg;
+ }
+ ret = do_create_driverfs_files();
+ if (ret < 0) {
+ printk(KERN_WARNING "%s: create_files error: %d\n",
+ __func__, ret);
+ goto driver_unregister;
+ }
+
+
+ /*
+ * register the target driver and then create a host. This makes sure
+ * that we see any targets that are there. Gotta figure out how to
+ * tell the system that there are new targets when SCST creates them.
+ */
+
+ ret = scst_local_target_register();
+ if (ret < 0) {
+ printk(KERN_WARNING "%s: unable to register targ griver: %d\n",
+ __func__, ret);
+ goto del_files;
+ }
+
+ /*
+ * Add adapters ...
+ */
+ adapters = scst_local_add_host;
+ scst_local_add_host = 0;
+ for (k = 0; k < adapters; k++) {
+ if (scst_local_add_adapter()) {
+ printk(KERN_ERR "%s: "
+ "scst_local_add_adapter failed: %d\n",
+ __func__, k);
+ break;
+ }
+ }
+
+out:
+ TRACE_EXIT_RES(ret);
+ return ret;
+
+del_files:
+ do_remove_driverfs_files();
+driver_unregister:
+ driver_unregister(&scst_local_driverfs_driver);
+bus_unreg:
+ bus_unregister(&scst_fake_lld_bus);
+dev_unreg:
+ device_unregister(&scst_fake_primary);
+destroy_kmem:
+ kmem_cache_destroy(tgt_specific_pool);
+ goto out;
+}
+
+static void __exit scst_local_exit(void)
+{
+ int k = scst_local_add_host;
+
+ TRACE_ENTRY();
+
+ for (; k; k--) {
+ printk(KERN_INFO "removing adapter in %s\n", __func__);
+ scst_local_remove_adapter();
+ }
+ do_remove_driverfs_files();
+ driver_unregister(&scst_local_driverfs_driver);
+ bus_unregister(&scst_fake_lld_bus);
+ device_unregister(&scst_fake_primary);
+
+ /*
+ * Now unregister the target template
+ */
+ scst_unregister_target_template(&scst_local_targ_tmpl);
+
+ /*
+ * Free the pool we allocated
+ */
+ if (tgt_specific_pool)
+ kmem_cache_destroy(tgt_specific_pool);
+
+ TRACE_EXIT();
+}
+
+device_initcall(scst_local_init);
+module_exit(scst_local_exit);
+
+/*
+ * Fake LLD Bus and functions
+ */
+
+static int scst_fake_lld_driver_probe(struct device *dev)
+{
+ int ret = 0;
+ struct scst_local_host_info *scst_lcl_host;
+ struct Scsi_Host *hpnt;
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = to_scst_lcl_host(dev);
+
+ hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template,
+ sizeof(scst_lcl_host));
+ if (NULL == hpnt) {
+ printk(KERN_ERR "%s: scsi_register failed\n", __func__);
+ ret = -ENODEV;
+ return ret;
+ }
+
+ scst_lcl_host->shost = hpnt;
+
+ /*
+ * We are going to have to register with SCST here I think
+ * and fill in some of these from that info?
+ */
+
+ *((struct scst_local_host_info **)hpnt->hostdata) = scst_lcl_host;
+ if ((hpnt->this_id >= 0) && (scst_local_num_tgts > hpnt->this_id))
+ hpnt->max_id = scst_local_num_tgts + 1;
+ else
+ hpnt->max_id = scst_local_num_tgts;
+ hpnt->max_lun = scst_local_max_luns - 1;
+
+ ret = scsi_add_host(hpnt, &scst_lcl_host->dev);
+ if (ret) {
+ printk(KERN_ERR "%s: scsi_add_host failed\n", __func__);
+ ret = -ENODEV;
+ scsi_host_put(hpnt);
+ } else
+ scsi_scan_host(hpnt);
+
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+
+static int scst_fake_lld_driver_remove(struct device *dev)
+{
+ struct scst_local_host_info *scst_lcl_host;
+
+ TRACE_ENTRY();
+
+ scst_lcl_host = to_scst_lcl_host(dev);
+
+ if (!scst_lcl_host) {
+ printk(KERN_ERR "%s: Unable to locate host info\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ scsi_remove_host(scst_lcl_host->shost);
+
+ scsi_host_put(scst_lcl_host->shost);
+
+ TRACE_EXIT();
+ return 0;
+}
+
+static int scst_fake_lld_bus_match(struct device *dev,
+ struct device_driver *dev_driver)
+{
+ TRACE_ENTRY();
+
+ TRACE_EXIT();
+ return 1;
+}
+
+static struct bus_type scst_fake_lld_bus = {
+ .name = "scst_fake_bus",
+ .match = scst_fake_lld_bus_match,
+ .probe = scst_fake_lld_driver_probe,
+ .remove = scst_fake_lld_driver_remove,
+};
+
+/*
+ * SCST Target driver from here ... there are some forward declarations
+ * above
+ */
+
+static int scst_local_targ_detect(struct scst_tgt_template *tgt_template)
+{
+ int adapter_count;
+
+ TRACE_ENTRY();
+
+ /*
+ * Register the adapter(s)
+ */
+
+ adapter_count = scst_local_add_host;
+
+ TRACE_EXIT_RES(adapter_count);
+ return adapter_count;
+};
+
+static int scst_local_targ_release(struct scst_tgt *tgt)
+{
+ TRACE_ENTRY();
+
+ TRACE_EXIT();
+ return 0;
+}
+
+static int scst_local_targ_xmit_response(struct scst_cmd *scst_cmd)
+{
+ struct scst_local_tgt_specific *tgt_specific;
+
+ TRACE_ENTRY();
+
+ if (unlikely(scst_cmd_aborted(scst_cmd))) {
+ scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED);
+ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME);
+ printk(KERN_INFO "%s aborted command handled\n", __func__);
+ return SCST_TGT_RES_SUCCESS;
+ }
+
+ tgt_specific = scst_cmd_get_tgt_priv(scst_cmd);
+
+ /*
+ * This might have to change to use the two status flags
+ */
+ if (scst_cmd_get_is_send_status(scst_cmd)) {
+ (void)scst_local_send_resp(tgt_specific->cmnd, scst_cmd,
+ tgt_specific->done,
+ scst_cmd_get_status(scst_cmd));
+ }
+
+ /*
+ * Now tell SCST that the command is done ...
+ */
+ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME);
+
+ TRACE_EXIT();
+
+ return SCST_TGT_RES_SUCCESS;
+}
+
+static void scst_local_targ_on_free_cmd(struct scst_cmd *scst_cmd)
+{
+ struct scst_local_tgt_specific *tgt_specific;
+
+ TRACE_ENTRY();
+
+ tgt_specific = scst_cmd_get_tgt_priv(scst_cmd);
+ kmem_cache_free(tgt_specific_pool, tgt_specific);
+
+ TRACE_EXIT();
+ return;
+}
+
+static void scst_local_targ_task_mgmt_done(struct scst_mgmt_cmd *mgmt_cmd)
+{
+ struct completion *tgt_specific;
+
+ TRACE_ENTRY();
+
+ tgt_specific = (struct completion *)
+ scst_mgmt_cmd_get_tgt_priv(mgmt_cmd);
+
+ if (tgt_specific)
+ complete(tgt_specific);
+
+ TRACE_EXIT();
+ return;
+}
+
+static struct scst_tgt_template scst_local_targ_tmpl = {
+ .name = "scst_local_tgt",
+ .xmit_response_atomic = 1,
+ .detect = scst_local_targ_detect,
+ .release = scst_local_targ_release,
+ .xmit_response = scst_local_targ_xmit_response,
+ .on_free_cmd = scst_local_targ_on_free_cmd,
+ .task_mgmt_fn_done = scst_local_targ_task_mgmt_done,
+};
+
+/*
+ * Register the target driver ... to get things going
+ */
+static int scst_local_target_register(void)
+{
+ int ret;
+
+ TRACE_ENTRY();
+
+ ret = scst_register_target_template(&scst_local_targ_tmpl);
+ if (ret < 0) {
+ printk(KERN_WARNING "scst_register_target_template "
+ "failed: %d\n",
+ ret);
+ goto error;
+ }
+
+ TRACE_EXIT();
+ return 0;
+
+error:
+ TRACE_EXIT_RES(ret);
+ return ret;
+}
+