diff mbox

[4/4] scsi_debug: add resp_write_scat function

Message ID 20171212011008.27689-5-dgilbert@interlog.com (mailing list archive)
State Superseded
Headers show

Commit Message

Douglas Gilbert Dec. 12, 2017, 1:10 a.m. UTC
Add resp_write_scat() function to support decoding WRITE SCATTERED
(16 and 32). Also weave resp_write_scat() into the cdb decoding
logic. Split SDEB_I_SERV_ACT_IN into 12 and 16 byte variants
(similarly for SERV_ACT_OUT). As yet the driver doesn't need
either of the 12 byte variants.

Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
---
 drivers/scsi/scsi_debug.c | 207 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 192 insertions(+), 15 deletions(-)

Comments

Bart Van Assche Dec. 12, 2017, 7:40 p.m. UTC | #1
On Mon, 2017-12-11 at 20:10 -0500, Douglas Gilbert wrote:
> -static const struct opcode_info_t vl_iarr[1] = {	/* VARIABLE LENGTH */

> +static const struct opcode_info_t vl_iarr[2] = {	/* VARIABLE LENGTH */


Please leave out the array size and let the compiler determine the array size.

>  	{0, 0x7f, 0xb, F_SA_HIGH | F_D_OUT | FF_DIRECT_IO, resp_write_dt0,

> -	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0xb, 0xfa,

> -		   0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */

> +	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0xb, 0xfa,

> +		0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */


Shouldn't this change have been included in the patch that fixes the group
number mask?
 
>  static const struct opcode_info_t maint_in_iarr[2] = {

> @@ -518,9 +523,10 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {

>  	    {6,  0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },

>  	{1, 0x9e, 0x10, F_SA_LOW | F_D_IN, resp_readcap16, sa_in_iarr,

>  	    {16,  0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

> -	     0xff, 0xff, 0xff, 0x1, 0xc7} },	/* READ CAPACITY(16) */

> -	{0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* SA OUT */

> -	    {0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },

> +	     0xff, 0xff, 0xff, 0x1, 0xc7} },/* SA_IN(16), READ CAPACITY(16) */


Shouldn't the above change be folded into one of the other patches?

> @@ -529,9 +535,9 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {

>  	{0, 0x2f, 0, F_D_OUT_MAYBE | FF_DIRECT_IO, NULL, NULL, /* VERIFY(10) */

>  	    {10,  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7,

>  	     0, 0, 0, 0, 0, 0} },

> -	{1, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,

> -	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0x9, 0xfe, 0,

> -		      0xff, 0xff, 0xff, 0xff} },/* VARIABLE LENGTH, READ(32) */

> +	{2, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,

> +	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x9, 0xfe, 0,

> +		0xff, 0xff, 0xff, 0xff} },    /* VARIABLE LENGTH, READ(32) */


Have you considered to use ARRAY_SIZE(vl_iarr) instead of hard-coding the array
size?

> +	if (cmd[0] == VARIABLE_LENGTH_CMD) {

> +		is_16 = false;

> +		wrprotect = (cmd[10] >> 5) & 0x7;

> +		lbdof = get_unaligned_be16(cmd + 12);

> +		num_lrd = get_unaligned_be16(cmd + 16);

> +		bt_len = get_unaligned_be32(cmd + 28);

> +		check_prot = false;

> +	} else {        /* that leaves WRITE SCATTERED(16) */

> +		is_16 = true;

> +		wrprotect = (cmd[2] >> 5) & 0x7;

> +		lbdof = get_unaligned_be16(cmd + 4);

> +		num_lrd = get_unaligned_be16(cmd + 8);

> +		bt_len = get_unaligned_be32(cmd + 10);

> +		check_prot = true;

> +	}


It's not clear to me why check_prot is set to false for WRITE SCATTERED(32)
and set to true for WRITE SCATTERED(16)?

Bart.
Douglas Gilbert Dec. 12, 2017, 8:47 p.m. UTC | #2
On 2017-12-12 02:40 PM, Bart Van Assche wrote:
> On Mon, 2017-12-11 at 20:10 -0500, Douglas Gilbert wrote:
>> -static const struct opcode_info_t vl_iarr[1] = {	/* VARIABLE LENGTH */
>> +static const struct opcode_info_t vl_iarr[2] = {	/* VARIABLE LENGTH */
> 
> Please leave out the array size and let the compiler determine the array size.

I like the "belts and braces" approach. Especially if offsetting an
array element by one position would silently destroy the parser
(which is not the case with vl_iarr but is the case with a few
other arrays).

> 
>>   	{0, 0x7f, 0xb, F_SA_HIGH | F_D_OUT | FF_DIRECT_IO, resp_write_dt0,
>> -	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0xb, 0xfa,
>> -		   0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */
>> +	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0xb, 0xfa,
>> +		0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */
> 
> Shouldn't this change have been included in the patch that fixes the group
> number mask?

git add --patch

was used to break up a monolithic patch into 4 parts. However in
the above case it refused to "split" the group_number part (shown)
from the renaming of service actions (not shown above). At that
point I swear at git and move on.

If folks think that hours are spent testing a kernel with 1/4 and
2/4 applied while 3/4 and 4/4 have not been applied then I think
they are dreaming. IMO The split up is eye candy for reviewers.

>>   static const struct opcode_info_t maint_in_iarr[2] = {
>> @@ -518,9 +523,10 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
>>   	    {6,  0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
>>   	{1, 0x9e, 0x10, F_SA_LOW | F_D_IN, resp_readcap16, sa_in_iarr,
>>   	    {16,  0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
>> -	     0xff, 0xff, 0xff, 0x1, 0xc7} },	/* READ CAPACITY(16) */
>> -	{0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* SA OUT */
>> -	    {0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
>> +	     0xff, 0xff, 0xff, 0x1, 0xc7} },/* SA_IN(16), READ CAPACITY(16) */
> 
> Shouldn't the above change be folded into one of the other patches?

I considered the patch adding resp_write_scat to the parsing table
and the splitting SDEB_I_SERV_ACT_IN/OUT to its 12 and 16 byte cdb
components as very closely related, so I put them together.

>> @@ -529,9 +535,9 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
>>   	{0, 0x2f, 0, F_D_OUT_MAYBE | FF_DIRECT_IO, NULL, NULL, /* VERIFY(10) */
>>   	    {10,  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7,
>>   	     0, 0, 0, 0, 0, 0} },
>> -	{1, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
>> -	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0x9, 0xfe, 0,
>> -		      0xff, 0xff, 0xff, 0xff} },/* VARIABLE LENGTH, READ(32) */
>> +	{2, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
>> +	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x9, 0xfe, 0,
>> +		0xff, 0xff, 0xff, 0xff} },    /* VARIABLE LENGTH, READ(32) */
> 
> Have you considered to use ARRAY_SIZE(vl_iarr) instead of hard-coding the array
> size?

No. It could be done with the advantage of making the code
a bit safer for someone who changes it, but it obfuscates
the number elements in the bucket list (array) making it
harder for the reader of the code (maybe).

>> +	if (cmd[0] == VARIABLE_LENGTH_CMD) {
>> +		is_16 = false;
>> +		wrprotect = (cmd[10] >> 5) & 0x7;
>> +		lbdof = get_unaligned_be16(cmd + 12);
>> +		num_lrd = get_unaligned_be16(cmd + 16);
>> +		bt_len = get_unaligned_be32(cmd + 28);
>> +		check_prot = false;
>> +	} else {        /* that leaves WRITE SCATTERED(16) */
>> +		is_16 = true;
>> +		wrprotect = (cmd[2] >> 5) & 0x7;
>> +		lbdof = get_unaligned_be16(cmd + 4);
>> +		num_lrd = get_unaligned_be16(cmd + 8);
>> +		bt_len = get_unaligned_be32(cmd + 10);
>> +		check_prot = true;
>> +	}
> 
> It's not clear to me why check_prot is set to false for WRITE SCATTERED(32)
> and set to true for WRITE SCATTERED(16)?

Me neither. I was just following what the code for WRITE(16 and 32)
does. My guess is that the code in question is written by Martin P.
who is effectively co-maintainer of this driver having written all
the DIF and DIX stuff. Martin ... ?

Doug Gilbert
Douglas Gilbert Dec. 13, 2017, 7:35 p.m. UTC | #3
v2 coming, addressing some of the points below. Also there is an
issue when fake_rw=1 .

Doug Gilbert

On 2017-12-12 03:47 PM, Douglas Gilbert wrote:
> On 2017-12-12 02:40 PM, Bart Van Assche wrote:
>> On Mon, 2017-12-11 at 20:10 -0500, Douglas Gilbert wrote:
>>> -static const struct opcode_info_t vl_iarr[1] = {    /* VARIABLE LENGTH */
>>> +static const struct opcode_info_t vl_iarr[2] = {    /* VARIABLE LENGTH */
>>
>> Please leave out the array size and let the compiler determine the array size.
> 
> I like the "belts and braces" approach. Especially if offsetting an
> array element by one position would silently destroy the parser
> (which is not the case with vl_iarr but is the case with a few
> other arrays).
> 
>>
>>>       {0, 0x7f, 0xb, F_SA_HIGH | F_D_OUT | FF_DIRECT_IO, resp_write_dt0,
>>> -        NULL, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0xb, 0xfa,
>>> -           0, 0xff, 0xff, 0xff, 0xff} },    /* WRITE(32) */
>>> +        NULL, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0xb, 0xfa,
>>> +        0, 0xff, 0xff, 0xff, 0xff} },    /* WRITE(32) */
>>
>> Shouldn't this change have been included in the patch that fixes the group
>> number mask?
> 
> git add --patch
> 
> was used to break up a monolithic patch into 4 parts. However in
> the above case it refused to "split" the group_number part (shown)
> from the renaming of service actions (not shown above). At that
> point I swear at git and move on.
> 
> If folks think that hours are spent testing a kernel with 1/4 and
> 2/4 applied while 3/4 and 4/4 have not been applied then I think
> they are dreaming. IMO The split up is eye candy for reviewers.
> 
>>>   static const struct opcode_info_t maint_in_iarr[2] = {
>>> @@ -518,9 +523,10 @@ static const struct opcode_info_t 
>>> opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
>>>           {6,  0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
>>>       {1, 0x9e, 0x10, F_SA_LOW | F_D_IN, resp_readcap16, sa_in_iarr,
>>>           {16,  0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
>>> -         0xff, 0xff, 0xff, 0x1, 0xc7} },    /* READ CAPACITY(16) */
>>> -    {0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* SA OUT */
>>> -        {0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
>>> +         0xff, 0xff, 0xff, 0x1, 0xc7} },/* SA_IN(16), READ CAPACITY(16) */
>>
>> Shouldn't the above change be folded into one of the other patches?
> 
> I considered the patch adding resp_write_scat to the parsing table
> and the splitting SDEB_I_SERV_ACT_IN/OUT to its 12 and 16 byte cdb
> components as very closely related, so I put them together.
> 
>>> @@ -529,9 +535,9 @@ static const struct opcode_info_t 
>>> opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
>>>       {0, 0x2f, 0, F_D_OUT_MAYBE | FF_DIRECT_IO, NULL, NULL, /* VERIFY(10) */
>>>           {10,  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7,
>>>            0, 0, 0, 0, 0, 0} },
>>> -    {1, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
>>> -        vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0x9, 0xfe, 0,
>>> -              0xff, 0xff, 0xff, 0xff} },/* VARIABLE LENGTH, READ(32) */
>>> +    {2, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
>>> +        vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x9, 0xfe, 0,
>>> +        0xff, 0xff, 0xff, 0xff} },    /* VARIABLE LENGTH, READ(32) */
>>
>> Have you considered to use ARRAY_SIZE(vl_iarr) instead of hard-coding the array
>> size?
> 
> No. It could be done with the advantage of making the code
> a bit safer for someone who changes it, but it obfuscates
> the number elements in the bucket list (array) making it
> harder for the reader of the code (maybe).
> 
>>> +    if (cmd[0] == VARIABLE_LENGTH_CMD) {
>>> +        is_16 = false;
>>> +        wrprotect = (cmd[10] >> 5) & 0x7;
>>> +        lbdof = get_unaligned_be16(cmd + 12);
>>> +        num_lrd = get_unaligned_be16(cmd + 16);
>>> +        bt_len = get_unaligned_be32(cmd + 28);
>>> +        check_prot = false;
>>> +    } else {        /* that leaves WRITE SCATTERED(16) */
>>> +        is_16 = true;
>>> +        wrprotect = (cmd[2] >> 5) & 0x7;
>>> +        lbdof = get_unaligned_be16(cmd + 4);
>>> +        num_lrd = get_unaligned_be16(cmd + 8);
>>> +        bt_len = get_unaligned_be32(cmd + 10);
>>> +        check_prot = true;
>>> +    }
>>
>> It's not clear to me why check_prot is set to false for WRITE SCATTERED(32)
>> and set to true for WRITE SCATTERED(16)?
> 
> Me neither. I was just following what the code for WRITE(16 and 32)
> does. My guess is that the code in question is written by Martin P.
> who is effectively co-maintainer of this driver having written all
> the DIF and DIX stuff. Martin ... ?
> 
> Doug Gilbert
> 
>
diff mbox

Patch

diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c
index 707c24155fd0..a0f61e7df4b4 100644
--- a/drivers/scsi/scsi_debug.c
+++ b/drivers/scsi/scsi_debug.c
@@ -93,6 +93,7 @@  static const char *sdebug_version_date = "20171202";
 #define MISCOMPARE_VERIFY_ASC 0x1d
 #define MICROCODE_CHANGED_ASCQ 0x1	/* with TARGET_CHANGED_ASC */
 #define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
+#define WRITE_ERROR_ASC 0xc
 
 /* Additional Sense Code Qualifier (ASCQ) */
 #define ACK_NAK_TO 0x3
@@ -323,12 +324,12 @@  enum sdeb_opcode_index {
 	SDEB_I_READ = 9,		/* 6, 10, 12, 16 */
 	SDEB_I_WRITE = 10,		/* 6, 10, 12, 16 */
 	SDEB_I_START_STOP = 11,
-	SDEB_I_SERV_ACT_IN = 12,	/* 12, 16 */
-	SDEB_I_SERV_ACT_OUT = 13,	/* 12, 16 */
+	SDEB_I_SERV_ACT_IN_16 = 12,	/* add ...SERV_ACT_IN_12 if needed */
+	SDEB_I_SERV_ACT_OUT_16 = 13,	/* add ...SERV_ACT_OUT_12 if needed */
 	SDEB_I_MAINT_IN = 14,
 	SDEB_I_MAINT_OUT = 15,
 	SDEB_I_VERIFY = 16,		/* 10 only */
-	SDEB_I_VARIABLE_LEN = 17,
+	SDEB_I_VARIABLE_LEN = 17,	/* READ(32), WRITE(32), WR_SCAT(32) */
 	SDEB_I_RESERVE = 18,		/* 6, 10 */
 	SDEB_I_RELEASE = 19,		/* 6, 10 */
 	SDEB_I_ALLOW_REMOVAL = 20,	/* PREVENT ALLOW MEDIUM REMOVAL */
@@ -373,12 +374,12 @@  static const unsigned char opcode_ind_arr[256] = {
 	0, 0, 0, 0, 0, SDEB_I_ATA_PT, 0, 0,
 	SDEB_I_READ, SDEB_I_COMP_WRITE, SDEB_I_WRITE, 0, 0, 0, 0, 0,
 	0, 0, 0, SDEB_I_WRITE_SAME, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, SDEB_I_SERV_ACT_IN, SDEB_I_SERV_ACT_OUT,
+	0, 0, 0, 0, 0, 0, SDEB_I_SERV_ACT_IN_16, SDEB_I_SERV_ACT_OUT_16,
 /* 0xa0; 0xa0->0xbf: 12 byte cdbs */
 	SDEB_I_REPORT_LUNS, SDEB_I_ATA_PT, 0, SDEB_I_MAINT_IN,
 	     SDEB_I_MAINT_OUT, 0, 0, 0,
-	SDEB_I_READ, SDEB_I_SERV_ACT_OUT, SDEB_I_WRITE, SDEB_I_SERV_ACT_IN,
-	     0, 0, 0, 0,
+	SDEB_I_READ, 0 /* SDEB_I_SERV_ACT_OUT_12 */, SDEB_I_WRITE,
+	     0 /* SDEB_I_SERV_ACT_IN_12 */, 0, 0, 0, 0,
 	0, 0, 0, 0, 0, 0, 0, 0,
 	0, 0, 0, 0, 0, 0, 0, 0,
 /* 0xc0; 0xc0->0xff: vendor specific */
@@ -397,6 +398,7 @@  static int resp_log_sense(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_readcap(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_read_dt0(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_write_dt0(struct scsi_cmnd *, struct sdebug_dev_info *);
+static int resp_write_scat(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_start_stop(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_readcap16(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_get_lba_status(struct scsi_cmnd *, struct sdebug_dev_info *);
@@ -448,10 +450,13 @@  static const struct opcode_info_t sa_in_iarr[1] = {
 	     0xff, 0xff, 0xff, 0, 0xc7} },
 };
 
-static const struct opcode_info_t vl_iarr[1] = {	/* VARIABLE LENGTH */
+static const struct opcode_info_t vl_iarr[2] = {	/* VARIABLE LENGTH */
 	{0, 0x7f, 0xb, F_SA_HIGH | F_D_OUT | FF_DIRECT_IO, resp_write_dt0,
-	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0xb, 0xfa,
-		   0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */
+	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0xb, 0xfa,
+		0, 0xff, 0xff, 0xff, 0xff} },	/* WRITE(32) */
+	{0, 0x7f, 0x11, F_SA_HIGH | F_D_OUT | FF_DIRECT_IO, resp_write_scat,
+	    NULL, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x11, 0xf8,
+		0, 0xff, 0xff, 0x0, 0x0} },   /* WRITE SCATTERED(32) */
 };
 
 static const struct opcode_info_t maint_in_iarr[2] = {
@@ -518,9 +523,10 @@  static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
 	    {6,  0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
 	{1, 0x9e, 0x10, F_SA_LOW | F_D_IN, resp_readcap16, sa_in_iarr,
 	    {16,  0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-	     0xff, 0xff, 0xff, 0x1, 0xc7} },	/* READ CAPACITY(16) */
-	{0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* SA OUT */
-	    {0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+	     0xff, 0xff, 0xff, 0x1, 0xc7} },/* SA_IN(16), READ CAPACITY(16) */
+	{0, 0x9f, 0x12, F_SA_LOW | F_D_OUT, resp_write_scat, NULL,
+	    {16,  0x12, 0xf9, 0x0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff,
+	     0xff, 0xff, 0xff, 0xc7} },  /* SA_OUT(16), WRITE SCATTERED(16) */
 	{2, 0xa3, 0xa, F_SA_LOW | F_D_IN, resp_report_tgtpgs, maint_in_iarr,
 	    {12,  0xea, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0,
 	     0} },
@@ -529,9 +535,9 @@  static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEMENT + 1] = {
 	{0, 0x2f, 0, F_D_OUT_MAYBE | FF_DIRECT_IO, NULL, NULL, /* VERIFY(10) */
 	    {10,  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7,
 	     0, 0, 0, 0, 0, 0} },
-	{1, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
-	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x1f, 0x18, 0x0, 0x9, 0xfe, 0,
-		      0xff, 0xff, 0xff, 0xff} },/* VARIABLE LENGTH, READ(32) */
+	{2, 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_DIRECT_IO, resp_read_dt0,
+	    vl_iarr, {32,  0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x9, 0xfe, 0,
+		0xff, 0xff, 0xff, 0xff} },    /* VARIABLE LENGTH, READ(32) */
 	{1, 0x56, 0, F_D_OUT, NULL, reserve_iarr, /* RESERVE(10) */
 	    {10,  0xff, 0xff, 0xff, 0, 0, 0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0,
 	     0} },
@@ -3030,6 +3036,177 @@  static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 	return 0;
 }
 
+/*
+ * T10 has only specified WRITE SCATTERED(16) and WRITE SCATTERED(32).
+ * No READ GATHERED yet (requires bidi or long cdb holding gather list).
+ */
+static int resp_write_scat(struct scsi_cmnd *scp,
+			   struct sdebug_dev_info *devip)
+{
+	u8 *cmd = scp->cmnd;
+	u8 *lrdp = NULL;
+	u8 *up;
+	u8 wrprotect;
+	u16 lbdof, num_lrd, k;
+	u32 num, num_by, bt_len, lbdof_blen, sg_off, cum_lb;
+	u32 lb_size = sdebug_sector_size;
+	u32 ei_lba;
+	u64 lba;
+	unsigned long iflags;
+	int ret, res;
+	bool check_prot, is_16;
+	static const u32 lrd_size = 32; /* + parameter list header size */
+
+	if (cmd[0] == VARIABLE_LENGTH_CMD) {
+		is_16 = false;
+		wrprotect = (cmd[10] >> 5) & 0x7;
+		lbdof = get_unaligned_be16(cmd + 12);
+		num_lrd = get_unaligned_be16(cmd + 16);
+		bt_len = get_unaligned_be32(cmd + 28);
+		check_prot = false;
+	} else {        /* that leaves WRITE SCATTERED(16) */
+		is_16 = true;
+		wrprotect = (cmd[2] >> 5) & 0x7;
+		lbdof = get_unaligned_be16(cmd + 4);
+		num_lrd = get_unaligned_be16(cmd + 8);
+		bt_len = get_unaligned_be32(cmd + 10);
+		check_prot = true;
+	}
+	if (unlikely(have_dif_prot && check_prot)) {
+		if (sdebug_dif == T10_PI_TYPE2_PROTECTION && wrprotect) {
+			mk_sense_invalid_opcode(scp);
+			return illegal_condition_result;
+		}
+		if ((sdebug_dif == T10_PI_TYPE1_PROTECTION ||
+		     sdebug_dif == T10_PI_TYPE3_PROTECTION) &&
+		     wrprotect == 0)
+			sdev_printk(KERN_ERR, scp->device,
+				    "Unprotected WR to DIF device\n");
+	}
+	if ((num_lrd == 0) || (bt_len == 0))
+		return 0;       /* T10 says these do-nothings are not errors */
+	if ((lbdof == 0) || (lbdof >= bt_len)) {
+		if (sdebug_verbose)
+			sdev_printk(KERN_INFO, scp->device,
+				"%s: %s: LB Data Offset field bad\n",
+				my_name, __func__);
+		mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
+		return illegal_condition_result;
+	}
+	lbdof_blen = lbdof * lb_size;
+	if ((lrd_size + (num_lrd * lrd_size)) > lbdof_blen) {
+		if (sdebug_verbose)
+			sdev_printk(KERN_INFO, scp->device,
+				"%s: %s: LBA range descriptors don't fit\n",
+				my_name, __func__);
+		mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
+		return illegal_condition_result;
+	}
+	lrdp = kzalloc(lbdof_blen, GFP_ATOMIC);
+	if (lrdp == NULL) {
+		mk_sense_buffer(scp, ILLEGAL_REQUEST, INSUFF_RES_ASC,
+				INSUFF_RES_ASCQ);
+		return illegal_condition_result;
+	}
+	if (sdebug_verbose)
+		sdev_printk(KERN_INFO, scp->device,
+			"%s: %s: Fetch header+scatter_list, lbdof_blen=%u\n",
+			my_name, __func__, lbdof_blen);
+	res = fetch_to_dev_buffer(scp, lrdp, lbdof_blen);
+	if (res == -1) {
+		ret = DID_ERROR << 16;
+		goto err_out;
+	}
+
+	write_lock_irqsave(&atomic_rw, iflags);
+	sg_off = lbdof_blen;
+	/* Spec says Buffer xfer Length field in number of LBs in dout */
+	cum_lb = 0;
+	for (k = 0, up = lrdp + lrd_size; k < num_lrd; ++k, up += lrd_size) {
+		lba = get_unaligned_be64(up + 0);
+		num = get_unaligned_be32(up + 8);
+		if (sdebug_verbose)
+			sdev_printk(KERN_INFO, scp->device,
+				"%s: %s: k=%d  LBA=0x%llx num=%u  sg_off=%u\n",
+				my_name, __func__, k, lba, num, sg_off);
+		if (num == 0)
+			continue;
+		ret = check_device_access_params(scp, lba, num);
+		if (ret)
+			goto err_out_unlock;
+		num_by = num * lb_size;
+		ei_lba = is_16 ? 0 : get_unaligned_be32(up + 12);
+
+		if ((cum_lb + num) > bt_len) {
+			if (sdebug_verbose)
+				sdev_printk(KERN_INFO, scp->device,
+				    "%s: %s: sum of blocks > data provided\n",
+				    my_name, __func__);
+			mk_sense_buffer(scp, ILLEGAL_REQUEST, WRITE_ERROR_ASC,
+					0);
+			ret = illegal_condition_result;
+			goto err_out_unlock;
+		}
+
+		/* DIX + T10 DIF */
+		if (unlikely(sdebug_dix && scsi_prot_sg_count(scp))) {
+			int prot_ret = prot_verify_write(scp, lba, num,
+							 ei_lba);
+
+			if (prot_ret) {
+				mk_sense_buffer(scp, ILLEGAL_REQUEST, 0x10,
+						prot_ret);
+				ret = illegal_condition_result;
+				goto err_out_unlock;
+			}
+		}
+
+		ret = do_device_access(scp, sg_off, lba, num, true);
+		if (unlikely(scsi_debug_lbp()))
+			map_region(lba, num);
+		if (unlikely(-1 == ret)) {
+			ret = DID_ERROR << 16;
+			goto err_out_unlock;
+		} else if (unlikely(sdebug_verbose && (ret < num_by)))
+			sdev_printk(KERN_INFO, scp->device,
+			    "%s: write: cdb indicated=%u, IO sent=%d bytes\n",
+			    my_name, num_by, ret);
+
+		if (unlikely(sdebug_any_injecting_opt)) {
+			struct sdebug_queued_cmd *sqcp =
+				(struct sdebug_queued_cmd *)scp->host_scribble;
+
+			if (sqcp) {
+				if (sqcp->inj_recovered) {
+					mk_sense_buffer(scp, RECOVERED_ERROR,
+							THRESHOLD_EXCEEDED, 0);
+					ret = illegal_condition_result;
+					goto err_out_unlock;
+				} else if (sqcp->inj_dif) {
+					/* Logical block guard check failed */
+					mk_sense_buffer(scp, ABORTED_COMMAND,
+							0x10, 1);
+					ret = illegal_condition_result;
+					goto err_out_unlock;
+				} else if (sqcp->inj_dix) {
+					mk_sense_buffer(scp, ILLEGAL_REQUEST,
+							0x10, 1);
+					ret = illegal_condition_result;
+					goto err_out_unlock;
+				}
+			}
+		}
+		sg_off += num_by;
+		cum_lb += num;
+	}
+	ret = 0;
+err_out_unlock:
+	write_unlock_irqrestore(&atomic_rw, iflags);
+err_out:
+	kfree(lrdp);
+	return ret;
+}
+
 static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 			   u32 ei_lba, bool unmap, bool ndob)
 {