scst_local makes virtual SCST devices available as local SCSI devices. Signed-off-by: Richard Sharpe <realrichardsharpe@xxxxxxxxx> Signed-off-by: Vladislav Bolkhovitin <vst@xxxxxxxx> --- Documentation/scst/README.scst_local | 263 ++++++ drivers/scst/Kconfig | 2 + drivers/scst/Makefile | 2 +- drivers/scst/scst_local/Kconfig | 22 + drivers/scst/scst_local/Makefile | 2 + drivers/scst/scst_local/scst_local.c | 1471 ++++++++++++++++++++++++++++++++++ 6 files changed, 1761 insertions(+), 1 deletions(-) create mode 100644 Documentation/scst/README.scst_local create mode 100644 drivers/scst/scst_local/Kconfig create mode 100644 drivers/scst/scst_local/Makefile create mode 100644 drivers/scst/scst_local/scst_local.c diff --git a/Documentation/scst/README.scst_local b/Documentation/scst/README.scst_local new file mode 100644 index 0000000..da24bab --- /dev/null +++ b/Documentation/scst/README.scst_local @@ -0,0 +1,263 @@ +SCST Local ... +Richard Sharpe, 30-Nov-2008 + +This is the SCST Local driver. Its function is to allow you to access devices +that are exported via SCST directly on the same Linux system that they are +exported from. + +No assumptions are made in the code about the device types on the target, so +any device handlers that you load in SCST should be visible, including tapes +and so forth. + +You can freely use any sg, sd, st, etc. devices imported from target, +except the following: you can't mount file systems or put swap on them. +This is a limitation of Linux memory/cache manager. See SCST README file +for details. + +To build, simply issue 'make' in the scst_local directory. + +Try 'modinfo scst_local' for a listing of module parameters so far. + +Here is how I have used it so far: + +1. Load up scst: + + modprobe scst + modprobe scst_vdisk + +2. Create a virtual disk (or your own device handler): + + dd if=/dev/zero of=/some/path/vdisk1.img bs=16384 count=1000000 + echo "add_device vm_disk1 filename=/some/path/vdisk1.img" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt + +3. Load the scst_local driver: + + insmod scst_local + echo "add vm_disk1 0" >/sys/kernel/scst_tgt/targets/scst_local/scst_local_tgt/luns/mgmt + +4. Check what you have + + cat /proc/scsi/scsi + Attached devices: + Host: scsi0 Channel: 00 Id: 00 Lun: 00 + Vendor: ATA Model: ST9320320AS Rev: 0303 + Type: Direct-Access ANSI SCSI revision: 05 + Host: scsi4 Channel: 00 Id: 00 Lun: 00 + Vendor: TSSTcorp Model: CD/DVDW TS-L632D Rev: TO04 + Type: CD-ROM ANSI SCSI revision: 05 + Host: scsi7 Channel: 00 Id: 00 Lun: 00 + Vendor: SCST_FIO Model: vm_disk1 Rev: 200 + Type: Direct-Access ANSI SCSI revision: 04 + +Or instead of manually "add_device" in (2) and step (3) write a +scstadmin config: + +HANDLER vdisk_fileio { + DEVICE vm_disk1 { + filename /some/path/vdisk1.img + } +} + +TARGET_DRIVER scst_local { + TARGET scst_local_tgt { + LUN 0 vm_disk1 + } +} + +then: + + insmod scst_local + scstadmin -config conf_file.cfg + +More advanced examples: + +For (3) you can: + + insmod scst_local add_default_tgt=0 + echo "add_target scst_local_tgt session_name=scst_local_host" >/sys/kernel/scst_tgt/targets/scst_local//mgmt + echo "add vm_disk1 0" >/sys/kernel/scst_tgt/targets/scst_local/scst_local_tgt/luns/mgmt + +Scst_local module's parameter add_default_tgt disables creation of +default target "scst_local_tgt" and session "scst_local_host", so you +needed to create it manually. + +There can be any number of targets and sessions created. Each SCST +session corresponds to SCSI host. You can change which LUNs assigned to +each session by using SCST access control. This mode is intended for +user space target drivers (see below). + +Alternatively, you can write an scstadmin's config file conf_file.cfg: + +HANDLER vdisk_fileio { + DEVICE vm_disk1 { + filename /some/path/vdisk1.img + } +} + +TARGET_DRIVER scst_local { + TARGET scst_local_tgt { + session_name scst_local_host + + LUN 0 vm_disk1 + } +} + +then: + + insmod scst_local add_default_tgt=0 + scstadmin -config conf_file.cfg + +NOTE! Although scstadmin allows to create scst_local's sessions using +"session_name" expression, it doesn't save existing sessions during +writing config file by "write_config" command. If you need this +functionality, feel free to send a request for it in SCST development +mailing list. + +5. Have fun. + +Some of this was coded while in Santa Clara, some in Bangalore, and some in +Hyderabad. Noe doubt some will be coded on the way back to Santa Clara. + +The code still has bugs, so if you encounter any, email me the fixes at: + + realrichardsharpe@xxxxxxxxx + +I am thinking of renaming this to something more interesting. + + +Sysfs interface +=============== + +See SCST's README for a common SCST sysfs description. + +Root of this driver is /sys/kernel/scst_tgt/targets/scst_local. It has +the following additional entry: + + - stats - read-only attribute with some statistical information. + +Each target subdirectory contains the following additional entries: + + - phys_transport_version - contains and allows to change physical + transport version descriptor. It determines by which phisical + interface this target will look like. See SPC for more details. By + default, it is not defined (0). + + - scsi_transport_version - contains and allows to change SCSI + transport version descriptor. It determines by which SCSI + transport this target will look like. See SPC for more details. By + default, it is SAS. + +Each session subdirectory contains the following additional entries: + + - transport_id - contains this host's TransportID. This TransportID + used to identify initiator in Persisten Reservation commands. If you + change scsi_transport_version for a target, make sure you set for all + its sessions correct TransportID. See SPC for more details. + + - host - links to the corresponding SCSI host. Using it you can find + local sg/bsg/sd/etc. devices of this session. For instance, this + links points out to host12, so you can find your sg devices by: + +$ lsscsi -g|grep "\[12:" +[12:0:0:0] disk SCST_FIO rd1 200 /dev/sdc /dev/sg2 +[12:0:0:1] disk SCST_FIO nullio 200 /dev/sdd /dev/sg3 + +They are /dev/sg2 and /dev/sg3. + +The following management commands available via /sys/kernel/scst_tgt/targets/scst_local/mgmt: + + - add_target target_name [session_name=sess_name; [session_name=sess_name1;] [...]] - + creates a target with optionally one or more sessions. + + - del_target target_name - deletes a target. + + - add_session target_name session_name - adds to target target_name + session (SCSI host) with name session_name. + + - del_session target_name session_name - deletes session session_name + from target target_name. + + +Note on performance +=================== + +Although this driver implemented in the most performance effective way, +including zero-copy passing data between SCSI/block subsystems and SCST, +in many cases it is NOT suited to measure performance as a NULL link. +For example, it is not suited for max IOPS measurements. This is because +for such cases not performance of the link between the target and +initiator is the bottleneck, but CPU or memory speed on the target or +initiator. For scst_local you have both initiator and target on the same +system, which means each your initiator and target are much less +CPU/memory powerful. + + +User space target drivers +========================= + +Scst_local can be used to write full featured SCST target drivers in +user space: + +1. For each SCSI target a user space target driver should create an + scst_local's target using "add_target" command. + +2. Then the user space target driver should, if needed, set its SCSI and + physical transport version descriptors using attributes + scsi_transport_version and phys_transport_version correspondingly in + /sys/kernel/scst_tgt/targets/scst_local/target_name directory. + +3. For incoming session (I_T nexus) from an initiator the user space + target driver should create scst_local's session using "add_session" + command. + +4. Then, if needed, the user space target driver should set TransportID + for this session (I_T nexus) using attribute + /sys/kernel/scst_tgt/targets/scst_local/target_name/sessions/session_name/transport_id + +5. Then the user space target driver should find out sg/bsg devices for + the LUNs the created session has using link + /sys/kernel/scst_tgt/targets/scst_local/target_name/sessions/session_name/host + as described above. + +6. Then the user space target driver can start serving the initiator using + found sg/bsg devices. + +For other connected initiators steps 3-6 should be repeated. + + +Change log +========== + +V0.1 24-Sep-2008 (Hyderabad) Initial coding, pretty chatty and messy, + but worked. + +V0.2 25-Sep-2008 (Hong Kong) Cleaned up the code a lot, reduced the log + chatter, fixed a bug where multiple LUNs did not + work. Also, added logging control. Tested with + five virtual disks. They all came up as /dev/sdb + through /dev/sdf and I could dd to them. Also + fixed a bug preventing multiple adapters. + +V0.3 26-Sep-2008 (Santa Clara) Added back a copyright plus cleaned up some + unused functions and structures. + +V0.4 5-Oct-2008 (Santa Clara) Changed name to scst_local as suggested, cleaned + up some unused variables (made them used) and + change allocation to a kmem_cache pool. + +V0.5 5-Oct-2008 (Santa Clara) Added mgmt commands to handle dev reset and + aborts. Not sure if aborts works. Also corrected + the version info and renamed readme to README. + +V0.6 7-Oct-2008 (Santa Clara) Removed some redundant code and made some + changes suggested by Vladislav. + +V0.7 11-Oct-2008 (Santa Clara) Moved into the scst tree. Cleaned up some + unused functions, used TRACE macros etc. + +V0.9 30-Nov-2008 (Mtn View) Cleaned up an additional problem with symbols not + being defined in older version of the kernel. Also + fixed some English and cleaned up this doc. + +V1.0 10-Sep-2010 (Moscow) Sysfs management added. Reviewed and cleaned up. + diff --git a/drivers/scst/Kconfig b/drivers/scst/Kconfig index fd24ec3..0263931 100644 --- a/drivers/scst/Kconfig +++ b/drivers/scst/Kconfig @@ -248,4 +248,6 @@ config SCST_MEASURE_LATENCY If unsure, say "N". +source "drivers/scst/scst_local/Kconfig" + endmenu diff --git a/drivers/scst/Makefile b/drivers/scst/Makefile index 2ffddc8..ac87362 100644 --- a/drivers/scst/Makefile +++ b/drivers/scst/Makefile @@ -8,4 +8,4 @@ scst-y += scst_sysfs.o scst-y += scst_mem.o scst-y += scst_debug.o -obj-$(CONFIG_SCST) += scst.o dev_handlers/ +obj-$(CONFIG_SCST) += scst.o dev_handlers/ scst_local/ diff --git a/drivers/scst/scst_local/Kconfig b/drivers/scst/scst_local/Kconfig new file mode 100644 index 0000000..da40460 --- /dev/null +++ b/drivers/scst/scst_local/Kconfig @@ -0,0 +1,22 @@ +config SCST_LOCAL + tristate "SCST Local driver" + depends on SCST && !HIGHMEM4G && !HIGHMEM64G + ---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. + +config SCST_LOCAL_FORCE_DIRECT_PROCESSING + bool "Force local processing" + depends on SCST_LOCAL + help + This experimental option forces scst_local to make SCST process + SCSI commands in the same context, in which they was submitted. + Otherwise, they will be processed in SCST threads. Setting this + option to "Y" will give some performance increase, but might be + unsafe. + + If unsure, say "N". diff --git a/drivers/scst/scst_local/Makefile b/drivers/scst/scst_local/Makefile new file mode 100644 index 0000000..8cbbbff --- /dev/null +++ b/drivers/scst/scst_local/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SCST_LOCAL) += scst_local.o + diff --git a/drivers/scst/scst_local/scst_local.c b/drivers/scst/scst_local/scst_local.c new file mode 100644 index 0000000..c84c08e --- /dev/null +++ b/drivers/scst/scst_local/scst_local.c @@ -0,0 +1,1471 @@ +/* + * Copyright (C) 2008 - 2010 Richard Sharpe + * Copyright (C) 1992 Eric Youngdale + * Copyright (C) 2008 - 2010 Vladislav Bolkhovitin <vst@xxxxxxxx> + * + * 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/types.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/completion.h> +#include <linux/spinlock.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_tcq.h> + +#define LOG_PREFIX "scst_local" + +/* SCST includes ... */ +#include <scst/scst_const.h> +#include <scst/scst.h> +#include <scst/scst_debug.h> + +#ifdef CONFIG_SCST_DEBUG +#define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_PID | \ + TRACE_LINE | TRACE_OUT_OF_MEM | TRACE_MGMT | 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_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 + +#define SCST_LOCAL_VERSION "1.0.0" +static const char *scst_local_version_date = "20100910"; + +/* Some statistics */ +static atomic_t num_aborts = ATOMIC_INIT(0); +static atomic_t num_dev_resets = ATOMIC_INIT(0); +static atomic_t num_target_resets = ATOMIC_INIT(0); + +static bool scst_local_add_default_tgt = true; +module_param_named(add_default_tgt, scst_local_add_default_tgt, bool, S_IRUGO); +MODULE_PARM_DESC(add_default_tgt, "add (default) or not on start default " + "target scst_local_tgt with default session scst_local_host"); + +struct scst_aen_work_item { + struct list_head work_list_entry; + struct scst_aen *aen; +}; + +struct scst_local_tgt { + struct scst_tgt *scst_tgt; + struct list_head sessions_list; /* protected by scst_local_mutex */ + struct list_head tgts_list_entry; + + /* SCSI version descriptors */ + uint16_t scsi_transport_version; + uint16_t phys_transport_version; +}; + +struct scst_local_sess { + struct scst_session *scst_sess; + + unsigned int unregistering:1; + + struct device dev; + struct Scsi_Host *shost; + struct scst_local_tgt *tgt; + + int number; + + struct mutex tr_id_mutex; + uint8_t *transport_id; + int transport_id_len; + + struct work_struct aen_work; + spinlock_t aen_lock; + struct list_head aen_work_list; /* protected by aen_lock */ + + struct list_head sessions_list_entry; +}; + +#define to_scst_lcl_sess(d) \ + container_of(d, struct scst_local_sess, dev) + +static int __scst_local_add_adapter(struct scst_local_tgt *tgt, + const char *initiator_name, struct scst_local_sess **out_sess, + bool locked); +static int scst_local_add_adapter(struct scst_local_tgt *tgt, + const char *initiator_name, struct scst_local_sess **out_sess); +static void scst_local_remove_adapter(struct scst_local_sess *sess); +static int scst_local_add_target(const char *target_name, + struct scst_local_tgt **out_tgt); +static void __scst_local_remove_target(struct scst_local_tgt *tgt); +static void scst_local_remove_target(struct scst_local_tgt *tgt); + +static atomic_t scst_local_sess_num = ATOMIC_INIT(0); + +static LIST_HEAD(scst_local_tgts_list); +static DEFINE_MUTEX(scst_local_mutex); + +static DECLARE_RWSEM(scst_local_exit_rwsem); + +MODULE_AUTHOR("Richard Sharpe, Vladislav Bolkhovitin + ideas from SCSI_DEBUG"); +MODULE_DESCRIPTION("SCSI+SCST local adapter driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(SCST_LOCAL_VERSION); + +static int scst_local_get_sas_transport_id(struct scst_local_sess *sess, + uint8_t **transport_id, int *len) +{ + int res = 0; + int tr_id_size = 0; + uint8_t *tr_id = NULL; + + tr_id_size = 24; /* A SAS TransportID */ + + tr_id = kzalloc(tr_id_size, GFP_KERNEL); + if (tr_id == NULL) { + PRINT_ERROR("Allocation of TransportID (size %d) failed", + tr_id_size); + res = -ENOMEM; + goto out; + } + + tr_id[0] = 0x00 | SCSI_TRANSPORTID_PROTOCOLID_SAS; + + /* + * Assemble a valid SAS address = 0x5OOUUIIR12345678 ... Does SCST + * have one? + */ + + tr_id[4] = 0x5F; + tr_id[5] = 0xEE; + tr_id[6] = 0xDE; + tr_id[7] = 0x40 | ((sess->number >> 4) & 0x0F); + tr_id[8] = 0x0F | (sess->number & 0xF0); + tr_id[9] = 0xAD; + tr_id[10] = 0xE0; + tr_id[11] = 0x50; + + *transport_id = tr_id; + *len = tr_id_size; + + TRACE_DBG("Created tid '%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'", + tr_id[4], tr_id[5], tr_id[6], tr_id[7], + tr_id[8], tr_id[9], tr_id[10], tr_id[11]); + +out: + return res; +} + +static int scst_local_get_initiator_port_transport_id( + struct scst_session *scst_sess, uint8_t **transport_id) +{ + int res = 0; + int tr_id_size = 0; + uint8_t *tr_id = NULL; + struct scst_local_sess *sess; + + if (scst_sess == NULL) { + res = SCSI_TRANSPORTID_PROTOCOLID_SAS; + goto out; + } + + sess = (struct scst_local_sess *)scst_sess_get_tgt_priv(scst_sess); + + mutex_lock(&sess->tr_id_mutex); + + if (sess->transport_id == NULL) { + res = scst_local_get_sas_transport_id(sess, + transport_id, &tr_id_size); + goto out_unlock; + } + + tr_id_size = sess->transport_id_len; + BUG_ON(tr_id_size == 0); + + tr_id = kzalloc(tr_id_size, GFP_KERNEL); + if (tr_id == NULL) { + PRINT_ERROR("Allocation of TransportID (size %d) failed", + tr_id_size); + res = -ENOMEM; + goto out; + } + + memcpy(tr_id, sess->transport_id, sess->transport_id_len); + +out_unlock: + mutex_unlock(&sess->tr_id_mutex); + +out: + return res; +} + +/** + ** Tgtt attributes + **/ + +static ssize_t scst_local_version_show(struct device_driver *drv, char *buf) +{ + sprintf(buf, "%s/%s\n", SCST_LOCAL_VERSION, scst_local_version_date); + +#ifdef CONFIG_SCST_EXTRACHECKS + strcat(buf, "EXTRACHECKS\n"); +#endif + +#ifdef CONFIG_SCST_TRACING + strcat(buf, "TRACING\n"); +#endif + +#ifdef CONFIG_SCST_DEBUG + strcat(buf, "DEBUG\n"); +#endif + return strlen(buf); +} + +static struct driver_attribute scst_local_version_attr = + __ATTR(version, S_IRUGO, scst_local_version_show, NULL); + +static ssize_t scst_local_stats_show(struct device_driver *drv, char *buf) +{ + return sprintf(buf, "Aborts: %d, Device Resets: %d, Target Resets: %d", + atomic_read(&num_aborts), atomic_read(&num_dev_resets), + atomic_read(&num_target_resets)); +} + +static struct driver_attribute scst_local_stats_attr = + __ATTR(stats, S_IRUGO, scst_local_stats_show, NULL); + +static const struct driver_attribute *scst_local_tgtt_attrs[] = { + &scst_local_version_attr, + &scst_local_stats_attr, + NULL, +}; + +/** + ** Tgt attributes + **/ + +static ssize_t scst_local_scsi_transport_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scst_tgt *scst_tgt; + struct scst_local_tgt *tgt; + ssize_t res = -ENOENT; + + scst_tgt = scst_dev_to_tgt(dev); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out; + + tgt = scst_tgt_get_tgt_priv(scst_tgt); + if (!tgt) + goto out_up; + + if (tgt->scsi_transport_version != 0) + res = sprintf(buf, "0x%x\n%s", tgt->scsi_transport_version, + SCST_SYSFS_KEY_MARK "\n"); + else + res = sprintf(buf, "0x%x\n", 0x0BE0); /* SAS */ + +out_up: + up_read(&scst_local_exit_rwsem); +out: + return res; +} + +static ssize_t scst_local_scsi_transport_version_store(struct device *dev, + struct device_attribute *attr, const char *buffer, size_t size) +{ + ssize_t res = -ENOENT; + struct scst_tgt *scst_tgt; + struct scst_local_tgt *tgt; + unsigned long val; + + scst_tgt = scst_dev_to_tgt(dev); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out; + + tgt = scst_tgt_get_tgt_priv(scst_tgt); + if (!tgt) + goto out_up; + + res = strict_strtoul(buffer, 0, &val); + if (res != 0) { + PRINT_ERROR("strict_strtoul() for %s failed: %zd", buffer, res); + goto out_up; + } + + tgt->scsi_transport_version = val; + + res = size; + +out_up: + up_read(&scst_local_exit_rwsem); +out: + return res; +} + +static struct device_attribute scst_local_scsi_transport_version_attr = + __ATTR(scsi_transport_version, S_IRUGO | S_IWUSR, + scst_local_scsi_transport_version_show, + scst_local_scsi_transport_version_store); + +static ssize_t scst_local_phys_transport_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scst_tgt *scst_tgt; + struct scst_local_tgt *tgt; + ssize_t res = -ENOENT; + + scst_tgt = scst_dev_to_tgt(dev); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out; + + tgt = scst_tgt_get_tgt_priv(scst_tgt); + if (!tgt) + goto out_up; + + res = sprintf(buf, "0x%x\n%s", tgt->phys_transport_version, + (tgt->phys_transport_version != 0) ? + SCST_SYSFS_KEY_MARK "\n" : ""); + +out_up: + up_read(&scst_local_exit_rwsem); +out: + return res; +} + +static ssize_t scst_local_phys_transport_version_store(struct device *dev, + struct device_attribute *attr, const char *buffer, size_t size) +{ + ssize_t res = -ENOENT; + struct scst_tgt *scst_tgt; + struct scst_local_tgt *tgt; + unsigned long val; + + scst_tgt = scst_dev_to_tgt(dev); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out; + + tgt = scst_tgt_get_tgt_priv(scst_tgt); + if (!tgt) + goto out_up; + + res = strict_strtoul(buffer, 0, &val); + if (res != 0) { + PRINT_ERROR("strict_strtoul() for %s failed: %zd", buffer, res); + goto out_up; + } + + tgt->phys_transport_version = val; + + res = size; + +out_up: + up_read(&scst_local_exit_rwsem); +out: + return res; +} + +static struct device_attribute scst_local_phys_transport_version_attr = + __ATTR(phys_transport_version, S_IRUGO | S_IWUSR, + scst_local_phys_transport_version_show, + scst_local_phys_transport_version_store); + +static const struct device_attribute *scst_local_tgt_attrs[] = { + &scst_local_scsi_transport_version_attr, + &scst_local_phys_transport_version_attr, + NULL, +}; + +/** + ** Session attributes + **/ + +static ssize_t scst_local_transport_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t res = -ENOENT; + struct scst_session *scst_sess; + struct scst_local_sess *sess; + uint8_t *tr_id; + int tr_id_len, i; + + scst_sess = scst_kobj_to_sess(kobj); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out; + + sess = scst_sess_get_tgt_priv(scst_sess); + + mutex_lock(&sess->tr_id_mutex); + + if (sess->transport_id != NULL) { + tr_id = sess->transport_id; + tr_id_len = sess->transport_id_len; + } else { + res = scst_local_get_sas_transport_id(sess, &tr_id, &tr_id_len); + if (res != 0) + goto out_unlock; + } + + res = 0; + for (i = 0; i < tr_id_len; i++) + res += sprintf(&buf[res], "%c", tr_id[i]); + + if (sess->transport_id == NULL) + kfree(tr_id); + +out_unlock: + mutex_unlock(&sess->tr_id_mutex); + up_read(&scst_local_exit_rwsem); +out: + return res; +} + +static ssize_t scst_local_transport_id_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buffer, size_t size) +{ + ssize_t res = -ENOENT; + struct scst_session *scst_sess; + struct scst_local_sess *sess; + + scst_sess = scst_kobj_to_sess(kobj); + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + goto out_err; + + sess = scst_sess_get_tgt_priv(scst_sess); + + mutex_lock(&sess->tr_id_mutex); + + if (sess->transport_id != NULL) { + kfree(sess->transport_id); + sess->transport_id = NULL; + sess->transport_id_len = 0; + } + + if (size == 0) + goto out_res; + + sess->transport_id = kzalloc(size, GFP_KERNEL); + if (sess->transport_id == NULL) { + PRINT_ERROR("Allocation of transport_id (size %zd) failed", + size); + res = -ENOMEM; + goto out_unlock; + } + + sess->transport_id_len = size; + + memcpy(sess->transport_id, buffer, sess->transport_id_len); + +out_res: + res = size; + +out_unlock: + mutex_unlock(&sess->tr_id_mutex); + up_read(&scst_local_exit_rwsem); +out_err: + return res; +} + +static struct kobj_attribute scst_local_transport_id_attr = + __ATTR(transport_id, S_IRUGO | S_IWUSR, + scst_local_transport_id_show, + scst_local_transport_id_store); + +static const struct attribute *scst_local_sess_attrs[] = { + &scst_local_transport_id_attr.attr, + NULL, +}; + +static ssize_t scst_local_sysfs_add_target(const char *target_name, char *params) +{ + int res; + struct scst_local_tgt *tgt; + char *param, *p; + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + return -ENOENT; + + res = scst_local_add_target(target_name, &tgt); + if (res != 0) + goto out_up; + + while (1) { + param = scst_get_next_token_str(¶ms); + if (param == NULL) + break; + + p = scst_get_next_lexem(¶m); + if (*p == '\0') + break; + + if (strcasecmp("session_name", p) != 0) { + PRINT_ERROR("Unknown parameter %s", p); + res = -EINVAL; + goto out_remove; + } + + p = scst_get_next_lexem(¶m); + if (*p == '\0') { + PRINT_ERROR("Wrong session name %s", p); + res = -EINVAL; + goto out_remove; + } + + res = scst_local_add_adapter(tgt, p, NULL); + if (res != 0) + goto out_remove; + } + +out_up: + up_read(&scst_local_exit_rwsem); + return res; + +out_remove: + scst_local_remove_target(tgt); + goto out_up; +} + +static ssize_t scst_local_sysfs_del_target(const char *target_name) +{ + int res; + struct scst_local_tgt *tgt; + bool deleted = false; + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + return -ENOENT; + + mutex_lock(&scst_local_mutex); + list_for_each_entry(tgt, &scst_local_tgts_list, tgts_list_entry) { + if (strcmp(target_name, tgt->scst_tgt->tgt_name) == 0) { + __scst_local_remove_target(tgt); + deleted = true; + break; + } + } + mutex_unlock(&scst_local_mutex); + + if (!deleted) { + PRINT_ERROR("Target %s not found", target_name); + res = -ENOENT; + goto out_up; + } + + res = 0; + +out_up: + up_read(&scst_local_exit_rwsem); + return res; +} + +static ssize_t scst_local_sysfs_mgmt_cmd(char *buf) +{ + ssize_t res; + char *command, *target_name, *session_name; + struct scst_local_tgt *t, *tgt; + + if (down_read_trylock(&scst_local_exit_rwsem) == 0) + return -ENOENT; + + command = scst_get_next_lexem(&buf); + + target_name = scst_get_next_lexem(&buf); + if (*target_name == '\0') { + PRINT_ERROR("%s", "Target name required"); + res = -EINVAL; + goto out_up; + } + + mutex_lock(&scst_local_mutex); + + tgt = NULL; + list_for_each_entry(t, &scst_local_tgts_list, tgts_list_entry) { + if (strcmp(t->scst_tgt->tgt_name, target_name) == 0) { + tgt = t; + break; + } + } + if (tgt == NULL) { + PRINT_ERROR("Target %s not found", target_name); + res = -EINVAL; + goto out_unlock; + } + + session_name = scst_get_next_lexem(&buf); + if (*session_name == '\0') { + PRINT_ERROR("%s", "Session name required"); + res = -EINVAL; + goto out_unlock; + } + + if (strcasecmp("add_session", command) == 0) { + res = __scst_local_add_adapter(tgt, session_name, NULL, true); + } else if (strcasecmp("del_session", command) == 0) { + struct scst_local_sess *s, *sess = NULL; + list_for_each_entry(s, &tgt->sessions_list, + sessions_list_entry) { + if (strcmp(s->scst_sess->initiator_name, session_name) == 0) { + sess = s; + break; + } + } + if (sess == NULL) { + PRINT_ERROR("Session %s not found (target %s)", + session_name, target_name); + res = -EINVAL; + goto out_unlock; + } + scst_local_remove_adapter(sess); + } + + res = 0; + +out_unlock: + mutex_unlock(&scst_local_mutex); + +out_up: + up_read(&scst_local_exit_rwsem); + return res; +} + +static int scst_local_abort(struct scsi_cmnd *SCpnt) +{ + struct scst_local_sess *sess; + int ret; + DECLARE_COMPLETION_ONSTACK(dev_reset_completion); + + sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host)); + + ret = scst_rx_mgmt_fn_tag(sess->scst_sess, SCST_ABORT_TASK, SCpnt->tag, + FALSE, &dev_reset_completion); + + /* Now wait for the completion ... */ + wait_for_completion_interruptible(&dev_reset_completion); + + atomic_inc(&num_aborts); + + if (ret == 0) + ret = SUCCESS; + return ret; +} + +static int scst_local_device_reset(struct scsi_cmnd *SCpnt) +{ + struct scst_local_sess *sess; + __be16 lun; + int ret; + DECLARE_COMPLETION_ONSTACK(dev_reset_completion); + + sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host)); + + lun = cpu_to_be16(SCpnt->device->lun); + + ret = scst_rx_mgmt_fn_lun(sess->scst_sess, 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); + + atomic_inc(&num_dev_resets); + + if (ret == 0) + ret = SUCCESS; + return ret; +} + +static int scst_local_target_reset(struct scsi_cmnd *SCpnt) +{ + struct scst_local_sess *sess; + __be16 lun; + int ret; + DECLARE_COMPLETION_ONSTACK(dev_reset_completion); + + sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host)); + + lun = cpu_to_be16(SCpnt->device->lun); + + ret = scst_rx_mgmt_fn_lun(sess->scst_sess, 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); + + atomic_inc(&num_target_resets); + + if (ret == 0) + ret = SUCCESS; + 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); + + 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); + 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; + + if (scst_cmnd) { + /* The buffer isn't ours, so let's be safe and restore it */ + scst_check_restore_sg_buff(scst_cmnd); + + /* Simulate autosense by this driver */ + if (unlikely(SCST_SENSE_VALID(scst_cmnd->sense))) + copy_sense(cmnd, scst_cmnd); + } + + cmnd->result = scsi_result; + + done(cmnd); + 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_Host *SChost, + struct scsi_cmnd *SCpnt) + __acquires(&h->host_lock) + __releases(&h->host_lock) +{ + struct scst_local_sess *sess; + struct scatterlist *sgl = NULL; + int sgl_count = 0; + __be16 lun; + struct scst_cmd *scst_cmd = NULL; + scst_data_direction dir; + + TRACE_DBG("lun %d, cmd: 0x%02X", SCpnt->device->lun, SCpnt->cmnd[0]); + + sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host)); + + scsi_set_resid(SCpnt, 0); + + WARN_ON_ONCE(!SCpnt->scsi_done); + + /* + * Tell the target that we have a command ... but first we need + * to get the LUN into a format that SCST understand + */ + lun = cpu_to_be16(SCpnt->device->lun); + scst_cmd = scst_rx_cmd(sess->scst_sess, (const uint8_t *)&lun, + sizeof(lun), SCpnt->cmnd, SCpnt->cmd_len, TRUE); + if (!scst_cmd) { + PRINT_ERROR("%s", "scst_rx_cmd() failed"); + return -ENOMEM; + } + + scst_cmd_set_tag(scst_cmd, SCpnt->tag); + switch (scsi_get_tag_type(SCpnt->device)) { + case MSG_SIMPLE_TAG: + scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE); + break; + case MSG_HEAD_TAG: + scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE); + break; + case MSG_ORDERED_TAG: + scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED); + break; + case SCSI_NO_TAG: + default: + scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED); + break; + } + + sgl = scsi_sglist(SCpnt); + sgl_count = scsi_sg_count(SCpnt); + + dir = SCST_DATA_NONE; + switch (SCpnt->sc_data_direction) { + case DMA_TO_DEVICE: + dir = SCST_DATA_WRITE; + scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt)); + scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count); + break; + case DMA_FROM_DEVICE: + dir = SCST_DATA_READ; + scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt)); + scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count); + break; + case DMA_BIDIRECTIONAL: + /* Some of these symbols are only defined after 2.6.24 */ + dir = SCST_DATA_BIDI; + scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt)); + scst_cmd_set_expected_out_transfer_len(scst_cmd, + scsi_in(SCpnt)->length); + scst_cmd_set_tgt_sg(scst_cmd, scsi_in(SCpnt)->table.sgl, + scsi_in(SCpnt)->table.nents); + scst_cmd_set_tgt_out_sg(scst_cmd, sgl, sgl_count); + break; + case DMA_NONE: + default: + dir = SCST_DATA_NONE; + scst_cmd_set_expected(scst_cmd, dir, 0); + break; + } + + /* Save the correct thing below depending on version */ + scst_cmd_set_tgt_priv(scst_cmd, SCpnt); + +#ifdef CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING + { + struct Scsi_Host *h = SCpnt->device->host; + spin_unlock_irq(h->host_lock); + scst_cmd_init_done(scst_cmd, scst_estimate_context_direct()); + spin_lock_irq(h->host_lock); + } +#else + /* + * Unfortunately, we called with IRQs disabled, so have no choice, + * except to pass to the thread context. + */ + scst_cmd_init_done(scst_cmd, SCST_CONTEXT_THREAD); +#endif + return 0; +} + +static int scst_local_targ_pre_exec(struct scst_cmd *scst_cmd) +{ + int res = SCST_PREPROCESS_STATUS_SUCCESS; + + if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) && + (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_WRITE)) + scst_copy_sg(scst_cmd, SCST_SG_COPY_FROM_TARGET); + return res; +} + +/* Must be called under sess->aen_lock. Drops then reacquires it inside. */ +static void scst_process_aens(struct scst_local_sess *sess, + bool cleanup_only) + __releases(&sess->aen_lock) + __acquires(&sess->aen_lock) +{ + struct scst_aen_work_item *work_item = NULL; + + TRACE_DBG("Target work sess %p", sess); + + while (!list_empty(&sess->aen_work_list)) { + work_item = list_entry(sess->aen_work_list.next, + struct scst_aen_work_item, work_list_entry); + list_del(&work_item->work_list_entry); + + spin_unlock(&sess->aen_lock); + + if (cleanup_only) + goto done; + + BUG_ON(work_item->aen->event_fn != SCST_AEN_SCSI); + + /* Let's always rescan */ + scsi_scan_target(&sess->shost->shost_gendev, 0, 0, + SCAN_WILD_CARD, 1); + +done: + scst_aen_done(work_item->aen); + kfree(work_item); + + spin_lock(&sess->aen_lock); + } + return; +} + +static void scst_aen_work_fn(struct work_struct *work) +{ + struct scst_local_sess *sess = + container_of(work, struct scst_local_sess, aen_work); + + TRACE_MGMT_DBG("Target work %p)", sess); + + spin_lock(&sess->aen_lock); + scst_process_aens(sess, false); + spin_unlock(&sess->aen_lock); + return; +} + +static int scst_local_report_aen(struct scst_aen *aen) +{ + int res = 0; + int event_fn = scst_aen_get_event_fn(aen); + struct scst_local_sess *sess; + struct scst_aen_work_item *work_item = NULL; + + sess = (struct scst_local_sess *)scst_sess_get_tgt_priv( + scst_aen_get_sess(aen)); + switch (event_fn) { + case SCST_AEN_SCSI: + /* + * Allocate a work item and place it on the queue + */ + work_item = kzalloc(sizeof(*work_item), GFP_KERNEL); + if (!work_item) { + PRINT_ERROR("%s", "Unable to allocate work item " + "to handle AEN!"); + return -ENOMEM; + } + + spin_lock(&sess->aen_lock); + + if (unlikely(sess->unregistering)) { + spin_unlock(&sess->aen_lock); + kfree(work_item); + res = SCST_AEN_RES_NOT_SUPPORTED; + goto out; + } + + list_add_tail(&work_item->work_list_entry, &sess->aen_work_list); + work_item->aen = aen; + + spin_unlock(&sess->aen_lock); + + schedule_work(&sess->aen_work); + break; + + default: + TRACE_MGMT_DBG("Unsupported AEN %d", event_fn); + res = SCST_AEN_RES_NOT_SUPPORTED; + break; + } + +out: + return res; +} + +static int scst_local_targ_detect(struct scst_tgt_template *tgt_template) +{ + return 0; +}; + +static int scst_local_targ_release(struct scst_tgt *tgt) +{ + return 0; +} + +static int scst_local_targ_xmit_response(struct scst_cmd *scst_cmd) +{ + struct scsi_cmnd *SCpnt = NULL; + void (*done)(struct scsi_cmnd *); + + 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); + return SCST_TGT_RES_SUCCESS; + } + + if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) && + (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_READ)) + scst_copy_sg(scst_cmd, SCST_SG_COPY_TO_TARGET); + + SCpnt = scst_cmd_get_tgt_priv(scst_cmd); + done = SCpnt->scsi_done; + + /* + * This might have to change to use the two status flags + */ + if (scst_cmd_get_is_send_status(scst_cmd)) { + int resid = 0, out_resid = 0; + + /* Calculate the residual ... */ + if (likely(!scst_get_resid(scst_cmd, &resid, &out_resid))) { + TRACE_DBG("No residuals for request %p", SCpnt); + } else { + if (out_resid != 0) + PRINT_ERROR("Unable to return OUT residual %d " + "(op %02x)", out_resid, SCpnt->cmnd[0]); + } + + scsi_set_resid(SCpnt, resid); + + /* + * It seems like there is no way to set out_resid ... + */ + + (void)scst_local_send_resp(SCpnt, scst_cmd, done, + scst_cmd_get_status(scst_cmd)); + } + + /* Now tell SCST that the command is done ... */ + scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME); + return SCST_TGT_RES_SUCCESS; +} + +static void scst_local_targ_task_mgmt_done(struct scst_mgmt_cmd *mgmt_cmd) +{ + struct completion *compl; + + compl = (struct completion *)scst_mgmt_cmd_get_tgt_priv(mgmt_cmd); + if (compl) + complete(compl); + return; +} + +static uint16_t scst_local_get_scsi_transport_version(struct scst_tgt *scst_tgt) +{ + struct scst_local_tgt *tgt; + + tgt = (struct scst_local_tgt *)scst_tgt_get_tgt_priv(scst_tgt); + + if (tgt->scsi_transport_version == 0) + return 0x0BE0; /* SAS */ + else + return tgt->scsi_transport_version; +} + +static uint16_t scst_local_get_phys_transport_version(struct scst_tgt *scst_tgt) +{ + struct scst_local_tgt *tgt; + + tgt = (struct scst_local_tgt *)scst_tgt_get_tgt_priv(scst_tgt); + + return tgt->phys_transport_version; +} + +static const char *add_target_parameters[] = { + "session_name", NULL +}; + +static struct scst_tgt_template scst_local_targ_tmpl = { + .name = "scst_local", + .owner = THIS_MODULE, + .sg_tablesize = 0xffff, + .xmit_response_atomic = 1, + .enabled_attr_not_needed = 1, + .tgtt_attrs = scst_local_tgtt_attrs, + .tgt_attrs = scst_local_tgt_attrs, + .sess_attrs = scst_local_sess_attrs, + .add_target = scst_local_sysfs_add_target, + .del_target = scst_local_sysfs_del_target, + .mgmt_cmd = scst_local_sysfs_mgmt_cmd, + .add_target_parameters = add_target_parameters, + .mgmt_cmd_help = " echo \"add_session target_name session_name\" >mgmt\n" + " echo \"del_session target_name session_name\" >mgmt\n", + .detect = scst_local_targ_detect, + .release = scst_local_targ_release, + .pre_exec = scst_local_targ_pre_exec, + .xmit_response = scst_local_targ_xmit_response, + .task_mgmt_fn_done = scst_local_targ_task_mgmt_done, + .report_aen = scst_local_report_aen, + .get_initiator_port_transport_id = scst_local_get_initiator_port_transport_id, + .get_scsi_transport_version = scst_local_get_scsi_transport_version, + .get_phys_transport_version = scst_local_get_phys_transport_version, +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + .default_trace_flags = SCST_LOCAL_DEFAULT_LOG_FLAGS, + .trace_flags = &trace_flag, +#endif +}; + +static struct scsi_host_template scst_lcl_ini_driver_template = { + .name = SCST_LOCAL_NAME, + .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 = -1, + .sg_tablesize = 0xFFFF, + .cmd_per_lun = 32, + .max_sectors = 0xffff, + /* Possible pass-through backend device may not support clustering */ + .use_clustering = DISABLE_CLUSTERING, + .skip_settle_delay = 1, + .module = THIS_MODULE, +}; + +/* + * LLD Bus and functions + */ + +static int scst_local_driver_probe(struct device *dev) +{ + int ret; + struct scst_local_sess *sess; + struct Scsi_Host *hpnt; + + sess = to_scst_lcl_sess(dev); + + TRACE_DBG("sess %p", sess); + + hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template, sizeof(*sess)); + if (NULL == hpnt) { + PRINT_ERROR("%s", "scsi_register() failed"); + ret = -ENODEV; + goto out; + } + + sess->shost = hpnt; + + hpnt->max_id = 0; /* Don't want more than one id */ + hpnt->max_lun = 0xFFFF; + + /* + * Because of a change in the size of this field at 2.6.26 + * we use this check ... it allows us to work on earlier + * kernels. If we don't, max_cmd_size gets set to 4 (and we get + * a compiler warning) so a scan never occurs. + */ + hpnt->max_cmd_len = 260; + + ret = scsi_add_host(hpnt, &sess->dev); + if (ret) { + PRINT_ERROR("%s", "scsi_add_host() failed"); + ret = -ENODEV; + scsi_host_put(hpnt); + goto out; + } + +out: + return ret; +} + +static int scst_local_driver_remove(struct device *dev) +{ + struct scst_local_sess *sess; + + sess = to_scst_lcl_sess(dev); + if (!sess) { + PRINT_ERROR("%s", "Unable to locate sess info"); + return -ENODEV; + } + + scsi_remove_host(sess->shost); + scsi_host_put(sess->shost); + return 0; +} + +static int scst_local_bus_match(struct device *dev, + struct device_driver *dev_driver) +{ + return 1; +} + +static struct bus_type scst_local_lld_bus = { + .name = "scst_local_bus", + .match = scst_local_bus_match, + .probe = scst_local_driver_probe, + .remove = scst_local_driver_remove, +}; + +static struct device_driver scst_local_driver = { + .name = SCST_LOCAL_NAME, + .bus = &scst_local_lld_bus, +}; + +static struct device *scst_local_root; + +static void scst_local_release_adapter(struct device *dev) +{ + struct scst_local_sess *sess; + + sess = to_scst_lcl_sess(dev); + if (sess == NULL) + goto out; + + spin_lock(&sess->aen_lock); + sess->unregistering = 1; + scst_process_aens(sess, true); + spin_unlock(&sess->aen_lock); + + cancel_work_sync(&sess->aen_work); + + scst_unregister_session(sess->scst_sess, TRUE, NULL); + + kfree(sess); + +out: + return; +} + +static int __scst_local_add_adapter(struct scst_local_tgt *tgt, + const char *initiator_name, struct scst_local_sess **out_sess, + bool locked) +{ + int res; + struct scst_local_sess *sess; + + sess = kzalloc(sizeof(*sess), GFP_KERNEL); + if (NULL == sess) { + PRINT_ERROR("Unable to alloc scst_lcl_host (size %zu)", + sizeof(*sess)); + res = -ENOMEM; + goto out; + } + + sess->tgt = tgt; + sess->number = atomic_inc_return(&scst_local_sess_num); + mutex_init(&sess->tr_id_mutex); + + /* + * Init this stuff we need for scheduling AEN work + */ + INIT_WORK(&sess->aen_work, scst_aen_work_fn); + spin_lock_init(&sess->aen_lock); + INIT_LIST_HEAD(&sess->aen_work_list); + + sess->scst_sess = scst_register_session(tgt->scst_tgt, 0, + initiator_name, (void *)sess, NULL, NULL); + if (sess->scst_sess == NULL) { + PRINT_ERROR("%s", "scst_register_session failed"); + kfree(sess); + res = -EFAULT; + goto out_free; + } + + sess->dev.bus = &scst_local_lld_bus; + sess->dev.parent = scst_local_root; + sess->dev.release = &scst_local_release_adapter; + sess->dev.init_name = + kobject_name(scst_sysfs_get_sess_kobj(sess->scst_sess)); + + res = device_register(&sess->dev); + if (res != 0) + goto unregister_session; + + res = sysfs_create_link(scst_sysfs_get_sess_kobj(sess->scst_sess), + &sess->shost->shost_dev.kobj, "host"); + if (res != 0) { + PRINT_ERROR("Unable to create \"host\" link for target " + "%s", scst_get_tgt_name(tgt->scst_tgt)); + goto unregister_dev; + } + + if (!locked) + mutex_lock(&scst_local_mutex); + list_add_tail(&sess->sessions_list_entry, &tgt->sessions_list); + if (!locked) + mutex_unlock(&scst_local_mutex); + + if (scst_initiator_has_luns(tgt->scst_tgt, initiator_name)) + scsi_scan_target(&sess->shost->shost_gendev, 0, 0, + SCAN_WILD_CARD, 1); + +out: + return res; + +unregister_dev: + device_unregister(&sess->dev); + +unregister_session: + scst_unregister_session(sess->scst_sess, TRUE, NULL); + +out_free: + kfree(sess); + goto out; +} + +static int scst_local_add_adapter(struct scst_local_tgt *tgt, + const char *initiator_name, struct scst_local_sess **out_sess) +{ + return __scst_local_add_adapter(tgt, initiator_name, out_sess, false); +} + +/* Must be called under scst_local_mutex */ +static void scst_local_remove_adapter(struct scst_local_sess *sess) +{ + list_del(&sess->sessions_list_entry); + + device_unregister(&sess->dev); + return; +} + +static int scst_local_add_target(const char *target_name, + struct scst_local_tgt **out_tgt) +{ + int res; + struct scst_local_tgt *tgt; + + tgt = kzalloc(sizeof(*tgt), GFP_KERNEL); + if (NULL == tgt) { + PRINT_ERROR("Unable to alloc tgt (size %zu)", sizeof(*tgt)); + res = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&tgt->sessions_list); + + tgt->scst_tgt = scst_register_target(&scst_local_targ_tmpl, target_name); + if (tgt->scst_tgt == NULL) { + PRINT_ERROR("%s", "scst_register_target() failed:"); + res = -EFAULT; + goto out_free; + } + + scst_tgt_set_tgt_priv(tgt->scst_tgt, tgt); + + mutex_lock(&scst_local_mutex); + list_add_tail(&tgt->tgts_list_entry, &scst_local_tgts_list); + mutex_unlock(&scst_local_mutex); + + if (out_tgt != NULL) + *out_tgt = tgt; + + res = 0; + +out: + return res; + +out_free: + kfree(tgt); + goto out; +} + +/* Must be called under scst_local_mutex */ +static void __scst_local_remove_target(struct scst_local_tgt *tgt) +{ + struct scst_local_sess *sess, *ts; + + list_for_each_entry_safe(sess, ts, &tgt->sessions_list, + sessions_list_entry) { + scst_local_remove_adapter(sess); + } + + list_del(&tgt->tgts_list_entry); + + scst_unregister_target(tgt->scst_tgt); + + kfree(tgt); + return; +} + +static void scst_local_remove_target(struct scst_local_tgt *tgt) +{ + mutex_lock(&scst_local_mutex); + __scst_local_remove_target(tgt); + mutex_unlock(&scst_local_mutex); + return; +} + +static int __init scst_local_init(void) +{ + int ret; + struct scst_local_tgt *tgt; + + scst_local_root = root_device_register(SCST_LOCAL_NAME); + if (IS_ERR(scst_local_root)) { + ret = PTR_ERR(scst_local_root); + goto out; + } + + ret = bus_register(&scst_local_lld_bus); + if (ret < 0) { + PRINT_ERROR("bus_register() error: %d", ret); + goto dev_unreg; + } + + ret = driver_register(&scst_local_driver); + if (ret < 0) { + PRINT_ERROR("driver_register() error: %d", ret); + goto bus_unreg; + } + + ret = scst_register_target_template(&scst_local_targ_tmpl); + if (ret != 0) { + PRINT_ERROR("Unable to register target template: %d", ret); + goto driver_unreg; + } + + /* + * If we are using sysfs, then don't add a default target unless + * we are told to do so. When using procfs, we always add a default + * target because that was what the earliest versions did. Just + * remove the preprocessor directives when no longer needed. + */ + if (!scst_local_add_default_tgt) + goto out; + + ret = scst_local_add_target("scst_local_tgt", &tgt); + if (ret != 0) + goto tgt_templ_unreg; + + ret = scst_local_add_adapter(tgt, "scst_local_host", NULL); + if (ret != 0) + goto tgt_unreg; + +out: + return ret; + +tgt_unreg: + scst_local_remove_target(tgt); + +tgt_templ_unreg: + scst_unregister_target_template(&scst_local_targ_tmpl); + +driver_unreg: + driver_unregister(&scst_local_driver); + +bus_unreg: + bus_unregister(&scst_local_lld_bus); + +dev_unreg: + root_device_unregister(scst_local_root); + + goto out; +} + +static void __exit scst_local_exit(void) +{ + struct scst_local_tgt *tgt, *tt; + + down_write(&scst_local_exit_rwsem); + + mutex_lock(&scst_local_mutex); + list_for_each_entry_safe(tgt, tt, &scst_local_tgts_list, + tgts_list_entry) { + __scst_local_remove_target(tgt); + } + mutex_unlock(&scst_local_mutex); + + driver_unregister(&scst_local_driver); + bus_unregister(&scst_local_lld_bus); + root_device_unregister(scst_local_root); + + /* Now unregister the target template */ + scst_unregister_target_template(&scst_local_targ_tmpl); + + /* To make lockdep happy */ + up_write(&scst_local_exit_rwsem); + return; +} + +device_initcall(scst_local_init); +module_exit(scst_local_exit); + -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html