summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/scsi_debug.c168
1 files changed, 168 insertions, 0 deletions
diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c
index f2afe757c438..16e4f9626440 100644
--- a/drivers/scsi/scsi_debug.c
+++ b/drivers/scsi/scsi_debug.c
@@ -44,6 +44,7 @@
#include <linux/debugfs.h>
#include <linux/async.h>
#include <linux/cleanup.h>
+#include <linux/parser.h>
#include <net/checksum.h>
@@ -1168,6 +1169,95 @@ static const struct file_operations sdebug_error_fops = {
.release = single_release,
};
+enum corrupt_options {
+ Opt_lba = (1u << 0),
+ Opt_num = (1u << 1),
+ Opt_bit_errors = (1u << 2),
+ Opt_reftag_adjust = (1u << 3),
+
+ Opt_invalid,
+};
+
+static const match_table_t corrupt_tokens = {
+ { Opt_lba, "lba=%u" },
+ { Opt_num, "num=%u" },
+ { Opt_bit_errors, "bit_errors=%u" },
+ { Opt_reftag_adjust, "reftag_adjust=%d" },
+ { Opt_invalid, NULL, },
+};
+
+static int corrupt_lbas(struct sdebug_dev_info *devip, u64 lba, u32 num,
+ u32 nr_bit_errors, s32 reftag_adjust);
+static ssize_t sdebug_corrupt_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct scsi_device *sdev = file->f_inode->i_private;
+ struct sdebug_dev_info *devip = sdev->hostdata;
+ substring_t args[MAX_OPT_ARGS];
+ char *buf, *options, *p;
+ int error = 0;
+ u64 lba = 0;
+ u32 num = 1;
+ u32 nr_bit_errors = 0;
+ s32 reftag_adjust = 0;
+
+ buf = memdup_user_nul(ubuf, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ options = buf;
+ while ((p = strsep(&options, ",\n")) != NULL) {
+ if (!*p)
+ continue;
+ switch (match_token(p, corrupt_tokens, args)) {
+ case Opt_lba:
+ error = match_u64(args, &lba);
+ break;
+ case Opt_num:
+ error = match_uint(args, &num);
+ break;
+ case Opt_bit_errors:
+ error = match_uint(args, &nr_bit_errors);
+ break;
+ case Opt_reftag_adjust:
+ error = match_int(args, &reftag_adjust);
+ break;
+ default:
+ pr_warn("unknown parameter or missing value '%s'\n", p);
+ error = -EINVAL;
+ }
+ if (error)
+ goto out_free_buf;
+ }
+
+ if (num == 0) {
+ pr_warn("invalid number of logical blocks: %u\n", num);
+ error = -EINVAL;
+ goto out_free_buf;
+ }
+
+ if (reftag_adjust &&
+ (!sdebug_dix ||
+ (sdebug_dif != T10_PI_TYPE1_PROTECTION &&
+ sdebug_dif != T10_PI_TYPE2_PROTECTION))) {
+ pr_warn("reftag adjust requires type 1 or type 2 PI with DIX\n");
+ error = -EINVAL;
+ goto out_free_buf;
+ }
+
+ error = corrupt_lbas(devip, lba, num, nr_bit_errors, reftag_adjust);
+
+out_free_buf:
+ kfree(buf);
+ if (error)
+ return error;
+ return count;
+}
+
+static const struct file_operations sdebug_corrupt_fops = {
+ .write = sdebug_corrupt_write,
+};
+
static int sdebug_target_reset_fail_show(struct seq_file *m, void *p)
{
struct scsi_target *starget = (struct scsi_target *)m->private;
@@ -4862,6 +4952,82 @@ static int resp_write_tape(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
return 0;
}
+static int corrupt_lbas(struct sdebug_dev_info *devip, u64 lba, u32 num,
+ u32 nr_bit_errors, s32 reftag_adjust)
+{
+ struct sdeb_store_info *sip = devip2sip(devip, false);
+ bool meta_data_locked = false;
+ u32 block, num_mapped, b, i;
+ int error = 0;
+
+ if (sdebug_dev_is_zoned(devip) ||
+ sdebug_dix ||
+ scsi_debug_lbp()) {
+ sdeb_meta_write_lock(sip);
+ meta_data_locked = true;
+ }
+
+ if (!sip) {
+ pr_err("can't corrupt with fake_rw\n");
+ error = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (num > sdebug_capacity || lba > sdebug_capacity - num) {
+ pr_err("logical blocks out of bounds: %llu:%u", lba, num);
+ error = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (scsi_debug_lbp() &&
+ (!map_state(sip, lba, &num_mapped) || num > num_mapped)) {
+ pr_err("can't modify unmapped logical blocks: %llu:%u",
+ lba, num);
+ error = -EINVAL;
+ goto out_unlock;
+ }
+
+ /*
+ * For each possible bit error, flip the same bit in each corrupted
+ * block to simulate repeated patterns like errors in cables.
+ */
+ sdeb_data_lock(sip, false);
+ for (b = 0; b < nr_bit_errors; b++) {
+ unsigned int bit;
+
+ bit = get_random_u32_below(sdebug_sector_size * BITS_PER_BYTE);
+ pr_info("flipping bit %u in LBAs %llu:%llu\n",
+ bit, lba, lba + num - 1);
+
+ div_u64_rem(lba, sdebug_store_sectors, &block);
+ for (i = 0; i < num; i++) {
+ u8 *p = sip->storep + (block * sdebug_sector_size);
+
+ sdeb_data_sector_lock(sip, true);
+ p[bit / BITS_PER_BYTE] ^= (1 << (bit % BITS_PER_BYTE));
+ sdeb_data_sector_unlock(sip, true);
+ if (++block >= sdebug_store_sectors)
+ block = 0;
+ }
+ }
+
+ if (reftag_adjust) {
+ pr_info("adjusting reftag by %d sectors for LBAs %llu:%llu\n",
+ reftag_adjust, lba, lba + num - 1);
+ for (i = 0; i < num; i++) {
+ struct t10_pi_tuple *sdt = dif_store(sip, lba + i);
+
+ be32_add_cpu(&sdt->ref_tag, reftag_adjust);
+ }
+ }
+ sdeb_data_unlock(sip, false);
+
+out_unlock:
+ if (meta_data_locked)
+ sdeb_meta_write_unlock(sip);
+ return error;
+}
+
static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
bool check_prot;
@@ -6628,6 +6794,8 @@ static int scsi_debug_sdev_configure(struct scsi_device *sdp,
if (IS_ERR_OR_NULL(dentry))
pr_info("failed to create error file for device %s\n",
dev_name(&sdp->sdev_gendev));
+ debugfs_create_file("corrupt", 0600, devip->debugfs_entry, sdp,
+ &sdebug_corrupt_fops);
return 0;
}