Commit 38d5c833 authored by Douglas Gilbert's avatar Douglas Gilbert Committed by Christoph Hellwig

scsi_debug: add Report supported opcodes+tmfs; Compare and write

The Report supported operation codes command is very closely integrated
into the table driven parser and very useful for testing it. Its cdb
masks form the basis of the 'strict' parameter's checks. The Report
supported TMFs command is a simple extension. The Compare and write
command may even be useful, as it should be atomic due to the read-write
lock that the driver uses on its backing store (ram).
Signed-off-by: default avatarDouglas Gilbert <dgilbert@interlog.com>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
parent c2248fc9
...@@ -307,7 +307,6 @@ static const unsigned char opcode_ind_arr[256] = { ...@@ -307,7 +307,6 @@ static const unsigned char opcode_ind_arr[256] = {
#define FF_SA (F_SA_HIGH | F_SA_LOW) #define FF_SA (F_SA_HIGH | F_SA_LOW)
struct sdebug_dev_info; struct sdebug_dev_info;
static int scsi_debug_queuecommand(struct scsi_cmnd *scp);
static int resp_inquiry(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_inquiry(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_report_luns(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_report_luns(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_requests(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_requests(struct scsi_cmnd *, struct sdebug_dev_info *);
...@@ -322,9 +321,12 @@ static int resp_readcap16(struct scsi_cmnd *, struct sdebug_dev_info *); ...@@ -322,9 +321,12 @@ static int resp_readcap16(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_get_lba_status(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_get_lba_status(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_report_tgtpgs(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_report_tgtpgs(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_unmap(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_unmap(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_rsup_opcodes(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_rsup_tmfs(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_write_same_10(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_write_same_10(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_write_same_16(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_write_same_16(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_xdwriteread_10(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_xdwriteread_10(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_comp_write(struct scsi_cmnd *, struct sdebug_dev_info *);
struct opcode_info_t { struct opcode_info_t {
u8 num_attached; /* 0 if this is it (i.e. a leaf); use 0xff u8 num_attached; /* 0 if this is it (i.e. a leaf); use 0xff
...@@ -383,10 +385,10 @@ static const struct opcode_info_t vl_iarr[1] = { /* VARIABLE LENGTH */ ...@@ -383,10 +385,10 @@ static const struct opcode_info_t vl_iarr[1] = { /* VARIABLE LENGTH */
}; };
static const struct opcode_info_t maint_in_iarr[2] = { static const struct opcode_info_t maint_in_iarr[2] = {
{0, 0xa3, 0xc, F_SA_LOW | F_D_IN, NULL, NULL, {0, 0xa3, 0xc, F_SA_LOW | F_D_IN, resp_rsup_opcodes, NULL,
{12, 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, {12, 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0,
0xc7, 0, 0, 0, 0} }, 0xc7, 0, 0, 0, 0} },
{0, 0xa3, 0xd, F_SA_LOW | F_D_IN, NULL, NULL, {0, 0xa3, 0xd, F_SA_LOW | F_D_IN, resp_rsup_tmfs, NULL,
{12, 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, {12, 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0,
0, 0} }, 0, 0} },
}; };
...@@ -487,7 +489,7 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = { ...@@ -487,7 +489,7 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
{0, 0x35, 0, F_DELAY_OVERR | FF_DIRECT_IO, NULL, NULL, /* SYNC_CACHE */ {0, 0x35, 0, F_DELAY_OVERR | FF_DIRECT_IO, NULL, NULL, /* SYNC_CACHE */
{10, 0x7, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xc7, 0, 0, {10, 0x7, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xc7, 0, 0,
0, 0, 0, 0} }, 0, 0, 0, 0} },
{0, 0x89, 0, F_D_OUT | FF_DIRECT_IO, NULL, NULL, {0, 0x89, 0, F_D_OUT | FF_DIRECT_IO, resp_comp_write, NULL,
{16, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, {16, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0,
0, 0xff, 0x1f, 0xc7} }, /* COMPARE AND WRITE */ 0, 0xff, 0x1f, 0xc7} }, /* COMPARE AND WRITE */
...@@ -1603,6 +1605,184 @@ static int resp_report_tgtpgs(struct scsi_cmnd * scp, ...@@ -1603,6 +1605,184 @@ static int resp_report_tgtpgs(struct scsi_cmnd * scp,
return ret; return ret;
} }
static int
resp_rsup_opcodes(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
bool rctd;
u8 reporting_opts, req_opcode, sdeb_i, supp;
u16 req_sa, u;
u32 alloc_len, a_len;
int k, offset, len, errsts, count, bump, na;
const struct opcode_info_t *oip;
const struct opcode_info_t *r_oip;
u8 *arr;
u8 *cmd = scp->cmnd;
rctd = !!(cmd[2] & 0x80);
reporting_opts = cmd[2] & 0x7;
req_opcode = cmd[3];
req_sa = get_unaligned_be16(cmd + 4);
alloc_len = get_unaligned_be32(cmd + 6);
if (alloc_len < 4 && alloc_len > 0xffff) {
mk_sense_invalid_fld(scp, SDEB_IN_CDB, 6, -1);
return check_condition_result;
}
if (alloc_len > 8192)
a_len = 8192;
else
a_len = alloc_len;
arr = kzalloc((a_len < 256) ? 320 : a_len + 64, GFP_KERNEL);
if (NULL == arr) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INSUFF_RES_ASC,
INSUFF_RES_ASCQ);
return check_condition_result;
}
switch (reporting_opts) {
case 0: /* all commands */
/* count number of commands */
for (count = 0, oip = opcode_info_arr;
oip->num_attached != 0xff; ++oip) {
if (F_INV_OP & oip->flags)
continue;
count += (oip->num_attached + 1);
}
bump = rctd ? 20 : 8;
put_unaligned_be32(count * bump, arr);
for (offset = 4, oip = opcode_info_arr;
oip->num_attached != 0xff && offset < a_len; ++oip) {
if (F_INV_OP & oip->flags)
continue;
na = oip->num_attached;
arr[offset] = oip->opcode;
put_unaligned_be16(oip->sa, arr + offset + 2);
if (rctd)
arr[offset + 5] |= 0x2;
if (FF_SA & oip->flags)
arr[offset + 5] |= 0x1;
put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
if (rctd)
put_unaligned_be16(0xa, arr + offset + 8);
r_oip = oip;
for (k = 0, oip = oip->arrp; k < na; ++k, ++oip) {
if (F_INV_OP & oip->flags)
continue;
offset += bump;
arr[offset] = oip->opcode;
put_unaligned_be16(oip->sa, arr + offset + 2);
if (rctd)
arr[offset + 5] |= 0x2;
if (FF_SA & oip->flags)
arr[offset + 5] |= 0x1;
put_unaligned_be16(oip->len_mask[0],
arr + offset + 6);
if (rctd)
put_unaligned_be16(0xa,
arr + offset + 8);
}
oip = r_oip;
offset += bump;
}
break;
case 1: /* one command: opcode only */
case 2: /* one command: opcode plus service action */
case 3: /* one command: if sa==0 then opcode only else opcode+sa */
sdeb_i = opcode_ind_arr[req_opcode];
oip = &opcode_info_arr[sdeb_i];
if (F_INV_OP & oip->flags) {
supp = 1;
offset = 4;
} else {
if (1 == reporting_opts) {
if (FF_SA & oip->flags) {
mk_sense_invalid_fld(scp, SDEB_IN_CDB,
2, 2);
kfree(arr);
return check_condition_result;
}
req_sa = 0;
} else if (2 == reporting_opts &&
0 == (FF_SA & oip->flags)) {
mk_sense_invalid_fld(scp, SDEB_IN_CDB, 4, -1);
kfree(arr); /* point at requested sa */
return check_condition_result;
}
if (0 == (FF_SA & oip->flags) &&
req_opcode == oip->opcode)
supp = 3;
else if (0 == (FF_SA & oip->flags)) {
na = oip->num_attached;
for (k = 0, oip = oip->arrp; k < na;
++k, ++oip) {
if (req_opcode == oip->opcode)
break;
}
supp = (k >= na) ? 1 : 3;
} else if (req_sa != oip->sa) {
na = oip->num_attached;
for (k = 0, oip = oip->arrp; k < na;
++k, ++oip) {
if (req_sa == oip->sa)
break;
}
supp = (k >= na) ? 1 : 3;
} else
supp = 3;
if (3 == supp) {
u = oip->len_mask[0];
put_unaligned_be16(u, arr + 2);
arr[4] = oip->opcode;
for (k = 1; k < u; ++k)
arr[4 + k] = (k < 16) ?
oip->len_mask[k] : 0xff;
offset = 4 + u;
} else
offset = 4;
}
arr[1] = (rctd ? 0x80 : 0) | supp;
if (rctd) {
put_unaligned_be16(0xa, arr + offset);
offset += 12;
}
break;
default:
mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, 2);
kfree(arr);
return check_condition_result;
}
offset = (offset < a_len) ? offset : a_len;
len = (offset < alloc_len) ? offset : alloc_len;
errsts = fill_from_dev_buffer(scp, arr, len);
kfree(arr);
return errsts;
}
static int
resp_rsup_tmfs(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
bool repd;
u32 alloc_len, len;
u8 arr[16];
u8 *cmd = scp->cmnd;
memset(arr, 0, sizeof(arr));
repd = !!(cmd[2] & 0x80);
alloc_len = get_unaligned_be32(cmd + 6);
if (alloc_len < 4) {
mk_sense_invalid_fld(scp, SDEB_IN_CDB, 6, -1);
return check_condition_result;
}
arr[0] = 0xc8; /* ATS | ATSS | LURS */
arr[1] = 0x1; /* ITNRS */
if (repd) {
arr[3] = 0xc;
len = 16;
} else
len = 4;
len = (len < alloc_len) ? len : alloc_len;
return fill_from_dev_buffer(scp, arr, len);
}
/* <<Following mode page info copied from ST318451LW>> */ /* <<Following mode page info copied from ST318451LW>> */
static int resp_err_recov_pg(unsigned char * p, int pcontrol, int target) static int resp_err_recov_pg(unsigned char * p, int pcontrol, int target)
...@@ -2165,6 +2345,38 @@ do_device_access(struct scsi_cmnd *scmd, u64 lba, u32 num, bool do_write) ...@@ -2165,6 +2345,38 @@ do_device_access(struct scsi_cmnd *scmd, u64 lba, u32 num, bool do_write)
return ret; return ret;
} }
/* If fake_store(lba,num) compares equal to arr(num), then copy top half of
* arr into fake_store(lba,num) and return true. If comparison fails then
* return false. */
static bool
comp_write_worker(u64 lba, u32 num, const u8 *arr)
{
bool res;
u64 block, rest = 0;
u32 store_blks = sdebug_store_sectors;
u32 lb_size = scsi_debug_sector_size;
block = do_div(lba, store_blks);
if (block + num > store_blks)
rest = block + num - store_blks;
res = !memcmp(fake_storep + (block * lb_size), arr,
(num - rest) * lb_size);
if (!res)
return res;
if (rest)
res = memcmp(fake_storep, arr + ((num - rest) * lb_size),
rest * lb_size);
if (!res)
return res;
arr += num * lb_size;
memcpy(fake_storep + (block * lb_size), arr, (num - rest) * lb_size);
if (rest)
memcpy(fake_storep, arr + ((num - rest) * lb_size),
rest * lb_size);
return res;
}
static __be16 dif_compute_csum(const void *buf, int len) static __be16 dif_compute_csum(const void *buf, int len)
{ {
__be16 csum; __be16 csum;
...@@ -2821,6 +3033,82 @@ resp_write_same_16(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -2821,6 +3033,82 @@ resp_write_same_16(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
return resp_write_same(scp, lba, num, ei_lba, unmap, ndob); return resp_write_same(scp, lba, num, ei_lba, unmap, ndob);
} }
static int
resp_comp_write(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
u8 *cmd = scp->cmnd;
u8 *arr;
u8 *fake_storep_hold;
u64 lba;
u32 dnum;
u32 lb_size = scsi_debug_sector_size;
u8 num;
unsigned long iflags;
int ret;
lba = get_unaligned_be32(cmd + 2);
num = cmd[13]; /* 1 to a maximum of 255 logical blocks */
if (0 == num)
return 0; /* degenerate case, not an error */
dnum = 2 * num;
arr = kzalloc(dnum * lb_size, GFP_ATOMIC);
if (NULL == arr) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INSUFF_RES_ASC,
INSUFF_RES_ASCQ);
return check_condition_result;
}
if (scsi_debug_dif == SD_DIF_TYPE2_PROTECTION &&
(cmd[1] & 0xe0)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
if ((scsi_debug_dif == SD_DIF_TYPE1_PROTECTION ||
scsi_debug_dif == SD_DIF_TYPE3_PROTECTION) &&
(cmd[1] & 0xe0) == 0)
sdev_printk(KERN_ERR, scp->device, "Unprotected WR "
"to DIF device\n");
/* inline check_device_access_params() */
if (lba + num > sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
return check_condition_result;
}
/* transfer length excessive (tie in to block limits VPD page) */
if (num > sdebug_store_sectors) {
/* needs work to find which cdb byte 'num' comes from */
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
return check_condition_result;
}
write_lock_irqsave(&atomic_rw, iflags);
/* trick do_device_access() to fetch both compare and write buffers
* from data-in into arr. Safe (atomic) since write_lock held. */
fake_storep_hold = fake_storep;
fake_storep = arr;
ret = do_device_access(scp, 0, dnum, true);
fake_storep = fake_storep_hold;
if (ret == -1) {
write_unlock_irqrestore(&atomic_rw, iflags);
kfree(arr);
return DID_ERROR << 16;
} else if ((ret < (dnum * lb_size)) &&
(SCSI_DEBUG_OPT_NOISE & scsi_debug_opts))
sdev_printk(KERN_INFO, scp->device, "%s: compare_write: cdb "
"indicated=%u, IO sent=%d bytes\n", my_name,
dnum * lb_size, ret);
if (!comp_write_worker(lba, num, arr)) {
write_unlock_irqrestore(&atomic_rw, iflags);
kfree(arr);
mk_sense_buffer(scp, MISCOMPARE, MISCOMPARE_VERIFY_ASC, 0);
return check_condition_result;
}
if (scsi_debug_lbp())
map_region(lba, num);
write_unlock_irqrestore(&atomic_rw, iflags);
return 0;
}
struct unmap_block_desc { struct unmap_block_desc {
__be64 lba; __be64 lba;
__be32 blocks; __be32 blocks;
...@@ -4668,21 +4956,6 @@ static void sdebug_remove_adapter(void) ...@@ -4668,21 +4956,6 @@ static void sdebug_remove_adapter(void)
--scsi_debug_add_host; --scsi_debug_add_host;
} }
static int
sdebug_queuecommand_lock_or_not(struct Scsi_Host *shost, struct scsi_cmnd *cmd)
{
if (scsi_debug_host_lock) {
unsigned long iflags;
int rc;
spin_lock_irqsave(shost->host_lock, iflags);
rc = scsi_debug_queuecommand(cmd);
spin_unlock_irqrestore(shost->host_lock, iflags);
return rc;
} else
return scsi_debug_queuecommand(cmd);
}
static int static int
sdebug_change_qdepth(struct scsi_device *sdev, int qdepth) sdebug_change_qdepth(struct scsi_device *sdev, int qdepth)
{ {
...@@ -4912,6 +5185,21 @@ scsi_debug_queuecommand(struct scsi_cmnd *scp) ...@@ -4912,6 +5185,21 @@ scsi_debug_queuecommand(struct scsi_cmnd *scp)
return schedule_resp(scp, devip, check_condition_result, 0); return schedule_resp(scp, devip, check_condition_result, 0);
} }
static int
sdebug_queuecommand_lock_or_not(struct Scsi_Host *shost, struct scsi_cmnd *cmd)
{
if (scsi_debug_host_lock) {
unsigned long iflags;
int rc;
spin_lock_irqsave(shost->host_lock, iflags);
rc = scsi_debug_queuecommand(cmd);
spin_unlock_irqrestore(shost->host_lock, iflags);
return rc;
} else
return scsi_debug_queuecommand(cmd);
}
static struct scsi_host_template sdebug_driver_template = { static struct scsi_host_template sdebug_driver_template = {
.show_info = scsi_debug_show_info, .show_info = scsi_debug_show_info,
.write_info = scsi_debug_write_info, .write_info = scsi_debug_write_info,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment