mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-17 18:51:27 +00:00
- #warning's cleanup git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@715 d57e44dd-8a1f-0410-8b47-8ef2f437770f
1078 lines
25 KiB
C
1078 lines
25 KiB
C
/*
|
|
* 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 (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25))
|
|
#define SCSI_MAX_SG_SEGMENTS 128
|
|
#endif
|
|
|
|
#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)
|
|
#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;
|
|
}
|
|
|
|
#if 0
|
|
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();
|
|
}
|
|
#endif
|
|
|
|
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_ABORT_TASK, 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;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25))
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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));
|
|
|
|
#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 pass to the thread context.
|
|
*/
|
|
scst_cmd_init_done(scst_cmd, SCST_CONTEXT_THREAD);
|
|
#endif
|
|
|
|
/*
|
|
* 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,
|
|
0, 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,
|
|
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25))
|
|
.eh_target_reset_handler = scst_local_target_reset,
|
|
#endif
|
|
.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
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23)
|
|
tgt_specific_pool = kmem_cache_create("scst_tgt_specific",
|
|
sizeof(struct scst_local_tgt_specific),
|
|
0, SCST_SLAB_FLAGS, NULL);
|
|
#else
|
|
tgt_specific_pool = kmem_cache_create("scst_tgt_specific",
|
|
sizeof(struct scst_local_tgt_specific),
|
|
0, SCST_SLAB_FLAGS, NULL, NULL);
|
|
#endif
|
|
|
|
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",
|
|
.sg_tablesize = 0xffff,
|
|
.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;
|
|
}
|
|
|