summaryrefslogtreecommitdiff
path: root/drivers/scsi
diff options
context:
space:
mode:
authorMartin K. Petersen <martin.petersen@oracle.com>2023-10-16 20:51:40 -0400
committerMartin K. Petersen <martin.petersen@oracle.com>2023-10-16 20:51:40 -0400
commit058676b513f6608819ead505ac4f4a59836a6287 (patch)
treef6d0e750e48e6ee54e12656f84c5a396d0550025 /drivers/scsi
parentaf46076d6640a5404ed65e1160df810fed54e7ac (diff)
parent573c2d066eb950dd9bd6e8735d3a859bbc21b3cc (diff)
Merge patch series "scsi: scsi_debug: Add error injection for single device"
Wenchao Hao <haowenchao2@huawei.com> says: The original error injection mechanism was based on scsi_host which could not inject fault for a single SCSI device. This patchset provides the ability to inject errors for a single SCSI device. Now we support inject timeout errors, queuecommand errors, and hostbyte, driverbyte, statusbyte, and sense data for specific SCSI Command. Two new error injection is defined to make abort command or reset LUN failed. Besides error injection for single device, this patchset add a new interface to make reset target failed for each scsi_target. The first two patch add a debugfs interface to add and inquiry single device's error injection info; the third patch defined how to remove an injection which has been added. The following 5 patches use the injection info and generate the related error type. The last two just add a new interface to make reset target failed and control scsi_device's allow_restart flag. Link: https://lore.kernel.org/r/20231010092051.608007-1-haowenchao2@huawei.com Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/scsi')
-rw-r--r--drivers/scsi/scsi_debug.c575
1 files changed, 570 insertions, 5 deletions
diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c
index 9c0af50501f9..67922e2c4c19 100644
--- a/drivers/scsi/scsi_debug.c
+++ b/drivers/scsi/scsi_debug.c
@@ -41,6 +41,8 @@
#include <linux/random.h>
#include <linux/xarray.h>
#include <linux/prefetch.h>
+#include <linux/debugfs.h>
+#include <linux/async.h>
#include <net/checksum.h>
@@ -285,6 +287,46 @@ struct sdeb_zone_state { /* ZBC: per zone state */
sector_t z_wp;
};
+enum sdebug_err_type {
+ ERR_TMOUT_CMD = 0, /* make specific scsi command timeout */
+ ERR_FAIL_QUEUE_CMD = 1, /* make specific scsi command's */
+ /* queuecmd return failed */
+ ERR_FAIL_CMD = 2, /* make specific scsi command's */
+ /* queuecmd return succeed but */
+ /* with errors set in scsi_cmnd */
+ ERR_ABORT_CMD_FAILED = 3, /* control return FAILED from */
+ /* scsi_debug_abort() */
+ ERR_LUN_RESET_FAILED = 4, /* control return FAILED from */
+ /* scsi_debug_device_reseLUN_RESET_FAILEDt() */
+};
+
+struct sdebug_err_inject {
+ int type;
+ struct list_head list;
+ int cnt;
+ unsigned char cmd;
+ struct rcu_head rcu;
+
+ union {
+ /*
+ * For ERR_FAIL_QUEUE_CMD
+ */
+ int queuecmd_ret;
+
+ /*
+ * For ERR_FAIL_CMD
+ */
+ struct {
+ unsigned char host_byte;
+ unsigned char driver_byte;
+ unsigned char status_byte;
+ unsigned char sense_key;
+ unsigned char asc;
+ unsigned char asq;
+ };
+ };
+};
+
struct sdebug_dev_info {
struct list_head dev_list;
unsigned int channel;
@@ -310,6 +352,15 @@ struct sdebug_dev_info {
unsigned int max_open;
ktime_t create_ts; /* time since bootup that this device was created */
struct sdeb_zone_state *zstate;
+
+ struct dentry *debugfs_entry;
+ struct spinlock list_lock;
+ struct list_head inject_err_list;
+};
+
+struct sdebug_target_info {
+ bool reset_fail;
+ struct dentry *debugfs_entry;
};
struct sdebug_host_info {
@@ -792,6 +843,7 @@ static bool have_dif_prot;
static bool write_since_sync;
static bool sdebug_statistics = DEF_STATISTICS;
static bool sdebug_wp;
+static bool sdebug_allow_restart;
/* Following enum: 0: no zbc, def; 1: host aware; 2: host managed */
static enum blk_zoned_model sdeb_zbc_model = BLK_ZONED_NONE;
static char *sdeb_zbc_model_s;
@@ -862,6 +914,265 @@ static const int device_qfull_result =
static const int condition_met_result = SAM_STAT_CONDITION_MET;
+static struct dentry *sdebug_debugfs_root;
+
+static void sdebug_err_free(struct rcu_head *head)
+{
+ struct sdebug_err_inject *inject =
+ container_of(head, typeof(*inject), rcu);
+
+ kfree(inject);
+}
+
+static void sdebug_err_add(struct scsi_device *sdev, struct sdebug_err_inject *new)
+{
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata;
+ struct sdebug_err_inject *err;
+
+ spin_lock(&devip->list_lock);
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == new->type && err->cmd == new->cmd) {
+ list_del_rcu(&err->list);
+ call_rcu(&err->rcu, sdebug_err_free);
+ }
+ }
+
+ list_add_tail_rcu(&new->list, &devip->inject_err_list);
+ spin_unlock(&devip->list_lock);
+}
+
+static int sdebug_err_remove(struct scsi_device *sdev, const char *buf, size_t count)
+{
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata;
+ struct sdebug_err_inject *err;
+ int type;
+ unsigned char cmd;
+
+ if (sscanf(buf, "- %d %hhx", &type, &cmd) != 2) {
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ spin_lock(&devip->list_lock);
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == type && err->cmd == cmd) {
+ list_del_rcu(&err->list);
+ call_rcu(&err->rcu, sdebug_err_free);
+ spin_unlock(&devip->list_lock);
+ kfree(buf);
+ return count;
+ }
+ }
+ spin_unlock(&devip->list_lock);
+
+ kfree(buf);
+ return -EINVAL;
+}
+
+static int sdebug_error_show(struct seq_file *m, void *p)
+{
+ struct scsi_device *sdev = (struct scsi_device *)m->private;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata;
+ struct sdebug_err_inject *err;
+
+ seq_puts(m, "Type\tCount\tCommand\n");
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ switch (err->type) {
+ case ERR_TMOUT_CMD:
+ case ERR_ABORT_CMD_FAILED:
+ case ERR_LUN_RESET_FAILED:
+ seq_printf(m, "%d\t%d\t0x%x\n", err->type, err->cnt,
+ err->cmd);
+ break;
+
+ case ERR_FAIL_QUEUE_CMD:
+ seq_printf(m, "%d\t%d\t0x%x\t0x%x\n", err->type,
+ err->cnt, err->cmd, err->queuecmd_ret);
+ break;
+
+ case ERR_FAIL_CMD:
+ seq_printf(m, "%d\t%d\t0x%x\t0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+ err->type, err->cnt, err->cmd,
+ err->host_byte, err->driver_byte,
+ err->status_byte, err->sense_key,
+ err->asc, err->asq);
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int sdebug_error_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sdebug_error_show, inode->i_private);
+}
+
+static ssize_t sdebug_error_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ char *buf;
+ unsigned int inject_type;
+ struct sdebug_err_inject *inject;
+ struct scsi_device *sdev = (struct scsi_device *)file->f_inode->i_private;
+
+ buf = kmalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, ubuf, count)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ if (buf[0] == '-')
+ return sdebug_err_remove(sdev, buf, count);
+
+ if (sscanf(buf, "%d", &inject_type) != 1) {
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ inject = kzalloc(sizeof(struct sdebug_err_inject), GFP_KERNEL);
+ if (!inject) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ switch (inject_type) {
+ case ERR_TMOUT_CMD:
+ case ERR_ABORT_CMD_FAILED:
+ case ERR_LUN_RESET_FAILED:
+ if (sscanf(buf, "%d %d %hhx", &inject->type, &inject->cnt,
+ &inject->cmd) != 3)
+ goto out_error;
+ break;
+
+ case ERR_FAIL_QUEUE_CMD:
+ if (sscanf(buf, "%d %d %hhx %x", &inject->type, &inject->cnt,
+ &inject->cmd, &inject->queuecmd_ret) != 4)
+ goto out_error;
+ break;
+
+ case ERR_FAIL_CMD:
+ if (sscanf(buf, "%d %d %hhx %hhx %hhx %hhx %hhx %hhx %hhx",
+ &inject->type, &inject->cnt, &inject->cmd,
+ &inject->host_byte, &inject->driver_byte,
+ &inject->status_byte, &inject->sense_key,
+ &inject->asc, &inject->asq) != 9)
+ goto out_error;
+ break;
+
+ default:
+ goto out_error;
+ break;
+ }
+
+ kfree(buf);
+ sdebug_err_add(sdev, inject);
+
+ return count;
+
+out_error:
+ kfree(buf);
+ kfree(inject);
+ return -EINVAL;
+}
+
+static const struct file_operations sdebug_error_fops = {
+ .open = sdebug_error_open,
+ .read = seq_read,
+ .write = sdebug_error_write,
+ .release = single_release,
+};
+
+static int sdebug_target_reset_fail_show(struct seq_file *m, void *p)
+{
+ struct scsi_target *starget = (struct scsi_target *)m->private;
+ struct sdebug_target_info *targetip =
+ (struct sdebug_target_info *)starget->hostdata;
+
+ if (targetip)
+ seq_printf(m, "%c\n", targetip->reset_fail ? 'Y' : 'N');
+
+ return 0;
+}
+
+static int sdebug_target_reset_fail_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sdebug_target_reset_fail_show, inode->i_private);
+}
+
+static ssize_t sdebug_target_reset_fail_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ int ret;
+ struct scsi_target *starget =
+ (struct scsi_target *)file->f_inode->i_private;
+ struct sdebug_target_info *targetip =
+ (struct sdebug_target_info *)starget->hostdata;
+
+ if (targetip) {
+ ret = kstrtobool_from_user(ubuf, count, &targetip->reset_fail);
+ return ret < 0 ? ret : count;
+ }
+ return -ENODEV;
+}
+
+static const struct file_operations sdebug_target_reset_fail_fops = {
+ .open = sdebug_target_reset_fail_open,
+ .read = seq_read,
+ .write = sdebug_target_reset_fail_write,
+ .release = single_release,
+};
+
+static int sdebug_target_alloc(struct scsi_target *starget)
+{
+ struct sdebug_target_info *targetip;
+ struct dentry *dentry;
+
+ targetip = kzalloc(sizeof(struct sdebug_target_info), GFP_KERNEL);
+ if (!targetip)
+ return -ENOMEM;
+
+ targetip->debugfs_entry = debugfs_create_dir(dev_name(&starget->dev),
+ sdebug_debugfs_root);
+ if (IS_ERR_OR_NULL(targetip->debugfs_entry))
+ pr_info("%s: failed to create debugfs directory for target %s\n",
+ __func__, dev_name(&starget->dev));
+
+ debugfs_create_file("fail_reset", 0600, targetip->debugfs_entry, starget,
+ &sdebug_target_reset_fail_fops);
+ if (IS_ERR_OR_NULL(dentry))
+ pr_info("%s: failed to create fail_reset file for target %s\n",
+ __func__, dev_name(&starget->dev));
+
+ starget->hostdata = targetip;
+
+ return 0;
+}
+
+static void sdebug_tartget_cleanup_async(void *data, async_cookie_t cookie)
+{
+ struct sdebug_target_info *targetip = data;
+
+ debugfs_remove(targetip->debugfs_entry);
+ kfree(targetip);
+}
+
+static void sdebug_target_destroy(struct scsi_target *starget)
+{
+ struct sdebug_target_info *targetip;
+
+ targetip = (struct sdebug_target_info *)starget->hostdata;
+ if (targetip) {
+ starget->hostdata = NULL;
+ async_schedule(sdebug_tartget_cleanup_async, targetip);
+ }
+}
/* Only do the extra work involved in logical block provisioning if one or
* more of the lbpu, lbpws or lbpws10 parameters are given and we are doing
@@ -5096,6 +5407,8 @@ static struct sdebug_dev_info *sdebug_device_create(
}
devip->create_ts = ktime_get_boottime();
atomic_set(&devip->stopped, (sdeb_tur_ms_to_ready > 0 ? 2 : 0));
+ spin_lock_init(&devip->list_lock);
+ INIT_LIST_HEAD(&devip->inject_err_list);
list_add_tail(&devip->dev_list, &sdbg_host->dev_info_list);
}
return devip;
@@ -5141,6 +5454,7 @@ static int scsi_debug_slave_alloc(struct scsi_device *sdp)
if (sdebug_verbose)
pr_info("slave_alloc <%u %u %u %llu>\n",
sdp->host->host_no, sdp->channel, sdp->id, sdp->lun);
+
return 0;
}
@@ -5148,6 +5462,7 @@ static int scsi_debug_slave_configure(struct scsi_device *sdp)
{
struct sdebug_dev_info *devip =
(struct sdebug_dev_info *)sdp->hostdata;
+ struct dentry *dentry;
if (sdebug_verbose)
pr_info("slave_configure <%u %u %u %llu>\n",
@@ -5163,6 +5478,22 @@ static int scsi_debug_slave_configure(struct scsi_device *sdp)
if (sdebug_no_uld)
sdp->no_uld_attach = 1;
config_cdb_len(sdp);
+
+ if (sdebug_allow_restart)
+ sdp->allow_restart = 1;
+
+ devip->debugfs_entry = debugfs_create_dir(dev_name(&sdp->sdev_dev),
+ sdebug_debugfs_root);
+ if (IS_ERR_OR_NULL(devip->debugfs_entry))
+ pr_info("%s: failed to create debugfs directory for device %s\n",
+ __func__, dev_name(&sdp->sdev_gendev));
+
+ dentry = debugfs_create_file("error", 0600, devip->debugfs_entry, sdp,
+ &sdebug_error_fops);
+ if (IS_ERR_OR_NULL(dentry))
+ pr_info("%s: failed to create error file for device %s\n",
+ __func__, dev_name(&sdp->sdev_gendev));
+
return 0;
}
@@ -5170,15 +5501,27 @@ static void scsi_debug_slave_destroy(struct scsi_device *sdp)
{
struct sdebug_dev_info *devip =
(struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
if (sdebug_verbose)
pr_info("slave_destroy <%u %u %u %llu>\n",
sdp->host->host_no, sdp->channel, sdp->id, sdp->lun);
- if (devip) {
- /* make this slot available for re-use */
- devip->used = false;
- sdp->hostdata = NULL;
+
+ if (!devip)
+ return;
+
+ spin_lock(&devip->list_lock);
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ list_del_rcu(&err->list);
+ call_rcu(&err->rcu, sdebug_err_free);
}
+ spin_unlock(&devip->list_lock);
+
+ debugfs_remove(devip->debugfs_entry);
+
+ /* make this slot available for re-use */
+ devip->used = false;
+ sdp->hostdata = NULL;
}
/* Returns true if we require the queued memory to be freed by the caller. */
@@ -5272,9 +5615,39 @@ static void stop_all_queued(void)
mutex_unlock(&sdebug_host_list_mutex);
}
+static int sdebug_fail_abort(struct scsi_cmnd *cmnd)
+{
+ struct scsi_device *sdp = cmnd->device;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
+ unsigned char *cmd = cmnd->cmnd;
+ int ret = 0;
+
+ if (devip == NULL)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == ERR_ABORT_CMD_FAILED &&
+ (err->cmd == cmd[0] || err->cmd == 0xff)) {
+ ret = !!err->cnt;
+ if (err->cnt < 0)
+ err->cnt++;
+
+ rcu_read_unlock();
+ return ret;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
static int scsi_debug_abort(struct scsi_cmnd *SCpnt)
{
bool ok = scsi_debug_abort_cmnd(SCpnt);
+ u8 *cmd = SCpnt->cmnd;
+ u8 opcode = cmd[0];
++num_aborts;
@@ -5283,6 +5656,12 @@ static int scsi_debug_abort(struct scsi_cmnd *SCpnt)
"%s: command%s found\n", __func__,
ok ? "" : " not");
+ if (sdebug_fail_abort(SCpnt)) {
+ scmd_printk(KERN_INFO, SCpnt, "fail abort command 0x%x\n",
+ opcode);
+ return FAILED;
+ }
+
return SUCCESS;
}
@@ -5306,10 +5685,40 @@ static void scsi_debug_stop_all_queued(struct scsi_device *sdp)
scsi_debug_stop_all_queued_iter, sdp);
}
+static int sdebug_fail_lun_reset(struct scsi_cmnd *cmnd)
+{
+ struct scsi_device *sdp = cmnd->device;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
+ unsigned char *cmd = cmnd->cmnd;
+ int ret = 0;
+
+ if (devip == NULL)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == ERR_LUN_RESET_FAILED &&
+ (err->cmd == cmd[0] || err->cmd == 0xff)) {
+ ret = !!err->cnt;
+ if (err->cnt < 0)
+ err->cnt++;
+
+ rcu_read_unlock();
+ return ret;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
static int scsi_debug_device_reset(struct scsi_cmnd *SCpnt)
{
struct scsi_device *sdp = SCpnt->device;
struct sdebug_dev_info *devip = sdp->hostdata;
+ u8 *cmd = SCpnt->cmnd;
+ u8 opcode = cmd[0];
++num_dev_resets;
@@ -5320,14 +5729,33 @@ static int scsi_debug_device_reset(struct scsi_cmnd *SCpnt)
if (devip)
set_bit(SDEBUG_UA_POR, devip->uas_bm);
+ if (sdebug_fail_lun_reset(SCpnt)) {
+ scmd_printk(KERN_INFO, SCpnt, "fail lun reset 0x%x\n", opcode);
+ return FAILED;
+ }
+
return SUCCESS;
}
+static int sdebug_fail_target_reset(struct scsi_cmnd *cmnd)
+{
+ struct scsi_target *starget = scsi_target(cmnd->device);
+ struct sdebug_target_info *targetip =
+ (struct sdebug_target_info *)starget->hostdata;
+
+ if (targetip)
+ return targetip->reset_fail;
+
+ return 0;
+}
+
static int scsi_debug_target_reset(struct scsi_cmnd *SCpnt)
{
struct scsi_device *sdp = SCpnt->device;
struct sdebug_host_info *sdbg_host = shost_to_sdebug_host(sdp->host);
struct sdebug_dev_info *devip;
+ u8 *cmd = SCpnt->cmnd;
+ u8 opcode = cmd[0];
int k = 0;
++num_target_resets;
@@ -5345,6 +5773,12 @@ static int scsi_debug_target_reset(struct scsi_cmnd *SCpnt)
sdev_printk(KERN_INFO, sdp,
"%s: %d device(s) found in target\n", __func__, k);
+ if (sdebug_fail_target_reset(SCpnt)) {
+ scmd_printk(KERN_INFO, SCpnt, "fail target reset 0x%x\n",
+ opcode);
+ return FAILED;
+ }
+
return SUCCESS;
}
@@ -5772,6 +6206,7 @@ module_param_named(zone_cap_mb, sdeb_zbc_zone_cap_mb, int, S_IRUGO);
module_param_named(zone_max_open, sdeb_zbc_max_open, int, S_IRUGO);
module_param_named(zone_nr_conv, sdeb_zbc_nr_conv, int, S_IRUGO);
module_param_named(zone_size_mb, sdeb_zbc_zone_size_mb, int, S_IRUGO);
+module_param_named(allow_restart, sdebug_allow_restart, bool, S_IRUGO | S_IWUSR);
MODULE_AUTHOR("Eric Youngdale + Douglas Gilbert");
MODULE_DESCRIPTION("SCSI debug adapter driver");
@@ -5844,6 +6279,7 @@ MODULE_PARM_DESC(zone_cap_mb, "Zone capacity in MiB (def=zone size)");
MODULE_PARM_DESC(zone_max_open, "Maximum number of open zones; [0] for no limit (def=auto)");
MODULE_PARM_DESC(zone_nr_conv, "Number of conventional zones (def=1)");
MODULE_PARM_DESC(zone_size_mb, "Zone size in MiB (def=auto)");
+MODULE_PARM_DESC(allow_restart, "Set scsi_device's allow_restart flag(def=0)");
#define SDEBUG_INFO_LEN 256
static char sdebug_info[SDEBUG_INFO_LEN];
@@ -7011,6 +7447,10 @@ static int __init scsi_debug_init(void)
goto driver_unreg;
}
+ sdebug_debugfs_root = debugfs_create_dir("scsi_debug", NULL);
+ if (IS_ERR_OR_NULL(sdebug_debugfs_root))
+ pr_info("%s: failed to create initial debugfs directory\n", __func__);
+
for (k = 0; k < hosts_to_add; k++) {
if (want_store && k == 0) {
ret = sdebug_add_host_helper(idx);
@@ -7057,6 +7497,7 @@ static void __exit scsi_debug_exit(void)
sdebug_erase_all_stores(false);
xa_destroy(per_store_ap);
+ debugfs_remove(sdebug_debugfs_root);
}
device_initcall(scsi_debug_init);
@@ -7496,6 +7937,104 @@ static int sdebug_blk_mq_poll(struct Scsi_Host *shost, unsigned int queue_num)
return num_entries;
}
+static int sdebug_timeout_cmd(struct scsi_cmnd *cmnd)
+{
+ struct scsi_device *sdp = cmnd->device;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
+ unsigned char *cmd = cmnd->cmnd;
+ int ret = 0;
+
+ if (devip == NULL)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == ERR_TMOUT_CMD &&
+ (err->cmd == cmd[0] || err->cmd == 0xff)) {
+ ret = !!err->cnt;
+ if (err->cnt < 0)
+ err->cnt++;
+
+ rcu_read_unlock();
+ return ret;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int sdebug_fail_queue_cmd(struct scsi_cmnd *cmnd)
+{
+ struct scsi_device *sdp = cmnd->device;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
+ unsigned char *cmd = cmnd->cmnd;
+ int ret = 0;
+
+ if (devip == NULL)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == ERR_FAIL_QUEUE_CMD &&
+ (err->cmd == cmd[0] || err->cmd == 0xff)) {
+ ret = err->cnt ? err->queuecmd_ret : 0;
+ if (err->cnt < 0)
+ err->cnt++;
+
+ rcu_read_unlock();
+ return ret;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int sdebug_fail_cmd(struct scsi_cmnd *cmnd, int *retval,
+ struct sdebug_err_inject *info)
+{
+ struct scsi_device *sdp = cmnd->device;
+ struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
+ struct sdebug_err_inject *err;
+ unsigned char *cmd = cmnd->cmnd;
+ int ret = 0;
+ int result;
+
+ if (devip == NULL)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(err, &devip->inject_err_list, list) {
+ if (err->type == ERR_FAIL_CMD &&
+ (err->cmd == cmd[0] || err->cmd == 0xff)) {
+ if (!err->cnt) {
+ rcu_read_unlock();
+ return 0;
+ }
+
+ ret = !!err->cnt;
+ rcu_read_unlock();
+ goto out_handle;
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+
+out_handle:
+ if (err->cnt < 0)
+ err->cnt++;
+ mk_sense_buffer(cmnd, err->sense_key, err->asc, err->asq);
+ result = err->status_byte | err->host_byte << 16 | err->driver_byte << 24;
+ *info = *err;
+ *retval = schedule_resp(cmnd, devip, result, NULL, 0, 0);
+
+ return ret;
+}
+
static int scsi_debug_queuecommand(struct Scsi_Host *shost,
struct scsi_cmnd *scp)
{
@@ -7515,6 +8054,8 @@ static int scsi_debug_queuecommand(struct Scsi_Host *shost,
u8 opcode = cmd[0];
bool has_wlun_rl;
bool inject_now;
+ int ret = 0;
+ struct sdebug_err_inject err;
scsi_set_resid(scp, 0);
if (sdebug_statistics) {
@@ -7554,6 +8095,29 @@ static int scsi_debug_queuecommand(struct Scsi_Host *shost,
if (NULL == devip)
goto err_out;
}
+
+ if (sdebug_timeout_cmd(scp)) {
+ scmd_printk(KERN_INFO, scp, "timeout command 0x%x\n", opcode);
+ return 0;
+ }
+
+ ret = sdebug_fail_queue_cmd(scp);
+ if (ret) {
+ scmd_printk(KERN_INFO, scp, "fail queue command 0x%x with 0x%x\n",
+ opcode, ret);
+ return ret;
+ }
+
+ if (sdebug_fail_cmd(scp, &ret, &err)) {
+ scmd_printk(KERN_INFO, scp,
+ "fail command 0x%x with hostbyte=0x%x, "
+ "driverbyte=0x%x, statusbyte=0x%x, "
+ "sense_key=0x%x, asc=0x%x, asq=0x%x\n",
+ opcode, err.host_byte, err.driver_byte,
+ err.status_byte, err.sense_key, err.asc, err.asq);
+ return ret;
+ }
+
if (unlikely(inject_now && !atomic_read(&sdeb_inject_pending)))
atomic_set(&sdeb_inject_pending, 1);
@@ -7672,7 +8236,6 @@ static int sdebug_init_cmd_priv(struct Scsi_Host *shost, struct scsi_cmnd *cmd)
return 0;
}
-
static struct scsi_host_template sdebug_driver_template = {
.show_info = scsi_debug_show_info,
.write_info = scsi_debug_write_info,
@@ -7702,6 +8265,8 @@ static struct scsi_host_template sdebug_driver_template = {
.track_queue_depth = 1,
.cmd_size = sizeof(struct sdebug_scsi_cmd),
.init_cmd_priv = sdebug_init_cmd_priv,
+ .target_alloc = sdebug_target_alloc,
+ .target_destroy = sdebug_target_destroy,
};
static int sdebug_driver_probe(struct device *dev)