Merge tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
[sfrench/cifs-2.6.git] / drivers / scsi / scsi_debug.c
index 914d9c12e7412de0123bcb8b04e73ffee33c430a..acf0592d63dae44f13ca9772a3938d08a136ef91 100644 (file)
@@ -43,6 +43,7 @@
 #include <linux/prefetch.h>
 #include <linux/debugfs.h>
 #include <linux/async.h>
+#include <linux/cleanup.h>
 
 #include <net/checksum.h>
 
@@ -532,6 +533,8 @@ 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 *);
+static int resp_get_stream_status(struct scsi_cmnd *scp,
+                                 struct sdebug_dev_info *devip);
 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_rsup_opcodes(struct scsi_cmnd *, struct sdebug_dev_info *);
@@ -606,6 +609,9 @@ static const struct opcode_info_t sa_in_16_iarr[] = {
        {0, 0x9e, 0x12, F_SA_LOW | F_D_IN, resp_get_lba_status, NULL,
            {16,  0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0, 0xc7} },      /* GET LBA STATUS(16) */
+       {0, 0x9e, 0x16, F_SA_LOW | F_D_IN, resp_get_stream_status, NULL,
+           {16, 0x16, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff,
+            0, 0} },   /* GET STREAM STATUS */
 };
 
 static const struct opcode_info_t vl_iarr[] = {        /* VARIABLE LENGTH */
@@ -896,6 +902,8 @@ static int sdeb_zbc_nr_conv = DEF_ZBC_NR_CONV_ZONES;
 static int submit_queues = DEF_SUBMIT_QUEUES;  /* > 1 for multi-queue (mq) */
 static int poll_queues; /* iouring iopoll interface.*/
 
+static atomic_long_t writes_by_group_number[64];
+
 static char sdebug_proc_name[] = MY_NAME;
 static const char *my_name = MY_NAME;
 
@@ -1867,6 +1875,19 @@ static int inquiry_vpd_b6(struct sdebug_dev_info *devip, unsigned char *arr)
        return 0x3c;
 }
 
+#define SDEBUG_BLE_LEN_AFTER_B4 28     /* thus vpage 32 bytes long */
+
+enum { MAXIMUM_NUMBER_OF_STREAMS = 6, PERMANENT_STREAM_COUNT = 5 };
+
+/* Block limits extension VPD page (SBC-4) */
+static int inquiry_vpd_b7(unsigned char *arrb4)
+{
+       memset(arrb4, 0, SDEBUG_BLE_LEN_AFTER_B4);
+       arrb4[1] = 1; /* Reduced stream control support (RSCS) */
+       put_unaligned_be16(MAXIMUM_NUMBER_OF_STREAMS, &arrb4[2]);
+       return SDEBUG_BLE_LEN_AFTER_B4;
+}
+
 #define SDEBUG_LONG_INQ_SZ 96
 #define SDEBUG_MAX_INQ_ARR_SZ 584
 
@@ -1903,7 +1924,8 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                u32 len;
                char lu_id_str[6];
                int host_no = devip->sdbg_host->shost->host_no;
-               
+
+               arr[1] = cmd[2];
                port_group_id = (((host_no + 1) & 0x7f) << 8) +
                    (devip->channel & 0x7f);
                if (sdebug_vpd_use_hostno == 0)
@@ -1914,7 +1936,6 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                                 (devip->target * 1000) - 3;
                len = scnprintf(lu_id_str, 6, "%d", lu_id_num);
                if (0 == cmd[2]) { /* supported vital product data pages */
-                       arr[1] = cmd[2];        /*sanity */
                        n = 4;
                        arr[n++] = 0x0;   /* this page */
                        arr[n++] = 0x80;  /* unit serial number */
@@ -1932,26 +1953,22 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                                        arr[n++] = 0xb2;  /* LB Provisioning */
                                if (is_zbc)
                                        arr[n++] = 0xb6;  /* ZB dev. char. */
+                               arr[n++] = 0xb7;  /* Block limits extension */
                        }
                        arr[3] = n - 4;   /* number of supported VPD pages */
                } else if (0x80 == cmd[2]) { /* unit serial number */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = len;
                        memcpy(&arr[4], lu_id_str, len);
                } else if (0x83 == cmd[2]) { /* device identification */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_83(&arr[4], port_group_id,
                                                target_dev_id, lu_id_num,
                                                lu_id_str, len,
                                                &devip->lu_name);
                } else if (0x84 == cmd[2]) { /* Software interface ident. */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_84(&arr[4]);
                } else if (0x85 == cmd[2]) { /* Management network addresses */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_85(&arr[4]);
                } else if (0x86 == cmd[2]) { /* extended inquiry */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = 0x3c;  /* number of following entries */
                        if (sdebug_dif == T10_PI_TYPE3_PROTECTION)
                                arr[4] = 0x4;   /* SPT: GRD_CHK:1 */
@@ -1959,33 +1976,32 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                                arr[4] = 0x5;   /* SPT: GRD_CHK:1, REF_CHK:1 */
                        else
                                arr[4] = 0x0;   /* no protection stuff */
-                       arr[5] = 0x7;   /* head of q, ordered + simple q's */
+                       /*
+                        * GROUP_SUP=1; HEADSUP=1 (HEAD OF QUEUE); ORDSUP=1
+                        * (ORDERED queuing); SIMPSUP=1 (SIMPLE queuing).
+                        */
+                       arr[5] = 0x17;
                } else if (0x87 == cmd[2]) { /* mode page policy */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = 0x8;   /* number of following entries */
                        arr[4] = 0x2;   /* disconnect-reconnect mp */
                        arr[6] = 0x80;  /* mlus, shared */
                        arr[8] = 0x18;   /* protocol specific lu */
                        arr[10] = 0x82;  /* mlus, per initiator port */
                } else if (0x88 == cmd[2]) { /* SCSI Ports */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_88(&arr[4], target_dev_id);
                } else if (is_disk_zbc && 0x89 == cmd[2]) { /* ATA info */
-                       arr[1] = cmd[2];        /*sanity */
                        n = inquiry_vpd_89(&arr[4]);
                        put_unaligned_be16(n, arr + 2);
                } else if (is_disk_zbc && 0xb0 == cmd[2]) { /* Block limits */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_b0(&arr[4]);
                } else if (is_disk_zbc && 0xb1 == cmd[2]) { /* Block char. */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_b1(devip, &arr[4]);
                } else if (is_disk && 0xb2 == cmd[2]) { /* LB Prov. */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_b2(&arr[4]);
                } else if (is_zbc && cmd[2] == 0xb6) { /* ZB dev. charact. */
-                       arr[1] = cmd[2];        /*sanity */
                        arr[3] = inquiry_vpd_b6(devip, &arr[4]);
+               } else if (cmd[2] == 0xb7) { /* block limits extension page */
+                       arr[3] = inquiry_vpd_b7(&arr[4]);
                } else {
                        mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, -1);
                        kfree(arr);
@@ -2554,6 +2570,40 @@ static int resp_ctrl_m_pg(unsigned char *p, int pcontrol, int target)
        return sizeof(ctrl_m_pg);
 }
 
+/* IO Advice Hints Grouping mode page */
+static int resp_grouping_m_pg(unsigned char *p, int pcontrol, int target)
+{
+       /* IO Advice Hints Grouping mode page */
+       struct grouping_m_pg {
+               u8 page_code;   /* OR 0x40 when subpage_code > 0 */
+               u8 subpage_code;
+               __be16 page_length;
+               u8 reserved[12];
+               struct scsi_io_group_descriptor descr[MAXIMUM_NUMBER_OF_STREAMS];
+       };
+       static const struct grouping_m_pg gr_m_pg = {
+               .page_code = 0xa | 0x40,
+               .subpage_code = 5,
+               .page_length = cpu_to_be16(sizeof(gr_m_pg) - 4),
+               .descr = {
+                       { .st_enble = 1 },
+                       { .st_enble = 1 },
+                       { .st_enble = 1 },
+                       { .st_enble = 1 },
+                       { .st_enble = 1 },
+                       { .st_enble = 0 },
+               }
+       };
+
+       BUILD_BUG_ON(sizeof(struct grouping_m_pg) !=
+                    16 + MAXIMUM_NUMBER_OF_STREAMS * 16);
+       memcpy(p, &gr_m_pg, sizeof(gr_m_pg));
+       if (1 == pcontrol) {
+               /* There are no changeable values so clear from byte 4 on. */
+               memset(p + 4, 0, sizeof(gr_m_pg) - 4);
+       }
+       return sizeof(gr_m_pg);
+}
 
 static int resp_iec_m_pg(unsigned char *p, int pcontrol, int target)
 {      /* Informational Exceptions control mode page for mode_sense */
@@ -2627,7 +2677,8 @@ static int resp_sas_sha_m_spg(unsigned char *p, int pcontrol)
        return sizeof(sas_sha_m_pg);
 }
 
-#define SDEBUG_MAX_MSENSE_SZ 256
+/* PAGE_SIZE is more than necessary but provides room for future expansion. */
+#define SDEBUG_MAX_MSENSE_SZ PAGE_SIZE
 
 static int resp_mode_sense(struct scsi_cmnd *scp,
                           struct sdebug_dev_info *devip)
@@ -2638,10 +2689,13 @@ static int resp_mode_sense(struct scsi_cmnd *scp,
        int target_dev_id;
        int target = scp->device->id;
        unsigned char *ap;
-       unsigned char arr[SDEBUG_MAX_MSENSE_SZ];
+       unsigned char *arr __free(kfree);
        unsigned char *cmd = scp->cmnd;
-       bool dbd, llbaa, msense_6, is_disk, is_zbc, bad_pcode;
+       bool dbd, llbaa, msense_6, is_disk, is_zbc;
 
+       arr = kzalloc(SDEBUG_MAX_MSENSE_SZ, GFP_ATOMIC);
+       if (!arr)
+               return -ENOMEM;
        dbd = !!(cmd[1] & 0x8);         /* disable block descriptors */
        pcontrol = (cmd[2] & 0xc0) >> 6;
        pcode = cmd[2] & 0x3f;
@@ -2699,45 +2753,63 @@ static int resp_mode_sense(struct scsi_cmnd *scp,
                ap = arr + offset;
        }
 
-       if ((subpcode > 0x0) && (subpcode < 0xff) && (0x19 != pcode)) {
-               /* TODO: Control Extension page */
-               mk_sense_invalid_fld(scp, SDEB_IN_CDB, 3, -1);
-               return check_condition_result;
-       }
-       bad_pcode = false;
-
+       /*
+        * N.B. If len>0 before resp_*_pg() call, then form of that call should be:
+        *        len += resp_*_pg(ap + len, pcontrol, target);
+        */
        switch (pcode) {
        case 0x1:       /* Read-Write error recovery page, direct access */
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
                len = resp_err_recov_pg(ap, pcontrol, target);
                offset += len;
                break;
        case 0x2:       /* Disconnect-Reconnect page, all devices */
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
                len = resp_disconnect_pg(ap, pcontrol, target);
                offset += len;
                break;
        case 0x3:       /* Format device page, direct access */
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
                if (is_disk) {
                        len = resp_format_pg(ap, pcontrol, target);
                        offset += len;
-               } else
-                       bad_pcode = true;
+               } else {
+                       goto bad_pcode;
+               }
                break;
        case 0x8:       /* Caching page, direct access */
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
                if (is_disk || is_zbc) {
                        len = resp_caching_pg(ap, pcontrol, target);
                        offset += len;
-               } else
-                       bad_pcode = true;
+               } else {
+                       goto bad_pcode;
+               }
                break;
        case 0xa:       /* Control Mode page, all devices */
-               len = resp_ctrl_m_pg(ap, pcontrol, target);
+               switch (subpcode) {
+               case 0:
+                       len = resp_ctrl_m_pg(ap, pcontrol, target);
+                       break;
+               case 0x05:
+                       len = resp_grouping_m_pg(ap, pcontrol, target);
+                       break;
+               case 0xff:
+                       len = resp_ctrl_m_pg(ap, pcontrol, target);
+                       len += resp_grouping_m_pg(ap + len, pcontrol, target);
+                       break;
+               default:
+                       goto bad_subpcode;
+               }
                offset += len;
                break;
        case 0x19:      /* if spc==1 then sas phy, control+discover */
-               if ((subpcode > 0x2) && (subpcode < 0xff)) {
-                       mk_sense_invalid_fld(scp, SDEB_IN_CDB, 3, -1);
-                       return check_condition_result;
-               }
+               if (subpcode > 0x2 && subpcode < 0xff)
+                       goto bad_subpcode;
                len = 0;
                if ((0x0 == subpcode) || (0xff == subpcode))
                        len += resp_sas_sf_m_pg(ap + len, pcontrol, target);
@@ -2749,49 +2821,50 @@ static int resp_mode_sense(struct scsi_cmnd *scp,
                offset += len;
                break;
        case 0x1c:      /* Informational Exceptions Mode page, all devices */
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
                len = resp_iec_m_pg(ap, pcontrol, target);
                offset += len;
                break;
        case 0x3f:      /* Read all Mode pages */
-               if ((0 == subpcode) || (0xff == subpcode)) {
-                       len = resp_err_recov_pg(ap, pcontrol, target);
-                       len += resp_disconnect_pg(ap + len, pcontrol, target);
-                       if (is_disk) {
-                               len += resp_format_pg(ap + len, pcontrol,
-                                                     target);
-                               len += resp_caching_pg(ap + len, pcontrol,
-                                                      target);
-                       } else if (is_zbc) {
-                               len += resp_caching_pg(ap + len, pcontrol,
-                                                      target);
-                       }
-                       len += resp_ctrl_m_pg(ap + len, pcontrol, target);
-                       len += resp_sas_sf_m_pg(ap + len, pcontrol, target);
-                       if (0xff == subpcode) {
-                               len += resp_sas_pcd_m_spg(ap + len, pcontrol,
-                                                 target, target_dev_id);
-                               len += resp_sas_sha_m_spg(ap + len, pcontrol);
-                       }
-                       len += resp_iec_m_pg(ap + len, pcontrol, target);
-                       offset += len;
-               } else {
-                       mk_sense_invalid_fld(scp, SDEB_IN_CDB, 3, -1);
-                       return check_condition_result;
+               if (subpcode > 0x0 && subpcode < 0xff)
+                       goto bad_subpcode;
+               len = resp_err_recov_pg(ap, pcontrol, target);
+               len += resp_disconnect_pg(ap + len, pcontrol, target);
+               if (is_disk) {
+                       len += resp_format_pg(ap + len, pcontrol, target);
+                       len += resp_caching_pg(ap + len, pcontrol, target);
+               } else if (is_zbc) {
+                       len += resp_caching_pg(ap + len, pcontrol, target);
                }
+               len += resp_ctrl_m_pg(ap + len, pcontrol, target);
+               if (0xff == subpcode)
+                       len += resp_grouping_m_pg(ap + len, pcontrol, target);
+               len += resp_sas_sf_m_pg(ap + len, pcontrol, target);
+               if (0xff == subpcode) {
+                       len += resp_sas_pcd_m_spg(ap + len, pcontrol, target,
+                                                 target_dev_id);
+                       len += resp_sas_sha_m_spg(ap + len, pcontrol);
+               }
+               len += resp_iec_m_pg(ap + len, pcontrol, target);
+               offset += len;
                break;
        default:
-               bad_pcode = true;
-               break;
-       }
-       if (bad_pcode) {
-               mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, 5);
-               return check_condition_result;
+               goto bad_pcode;
        }
        if (msense_6)
                arr[0] = offset - 1;
        else
                put_unaligned_be16((offset - 2), arr + 0);
        return fill_from_dev_buffer(scp, arr, min_t(u32, alloc_len, offset));
+
+bad_pcode:
+       mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, 5);
+       return check_condition_result;
+
+bad_subpcode:
+       mk_sense_invalid_fld(scp, SDEB_IN_CDB, 3, -1);
+       return check_condition_result;
 }
 
 #define SDEBUG_MAX_MSELECT_SZ 512
@@ -3306,7 +3379,8 @@ static inline struct sdeb_store_info *devip2sip(struct sdebug_dev_info *devip,
 
 /* Returns number of bytes copied or -1 if error. */
 static int do_device_access(struct sdeb_store_info *sip, struct scsi_cmnd *scp,
-                           u32 sg_skip, u64 lba, u32 num, bool do_write)
+                           u32 sg_skip, u64 lba, u32 num, bool do_write,
+                           u8 group_number)
 {
        int ret;
        u64 block, rest = 0;
@@ -3325,6 +3399,10 @@ static int do_device_access(struct sdeb_store_info *sip, struct scsi_cmnd *scp,
                return 0;
        if (scp->sc_data_direction != dir)
                return -1;
+
+       if (do_write && group_number < ARRAY_SIZE(writes_by_group_number))
+               atomic_long_inc(&writes_by_group_number[group_number]);
+
        fsp = sip->storep;
 
        block = do_div(lba, sdebug_store_sectors);
@@ -3698,7 +3776,7 @@ static int resp_read_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                }
        }
 
-       ret = do_device_access(sip, scp, 0, lba, num, false);
+       ret = do_device_access(sip, scp, 0, lba, num, false, 0);
        sdeb_read_unlock(sip);
        if (unlikely(ret == -1))
                return DID_ERROR << 16;
@@ -3883,6 +3961,7 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 {
        bool check_prot;
        u32 num;
+       u8 group = 0;
        u32 ei_lba;
        int ret;
        u64 lba;
@@ -3894,11 +3973,13 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                ei_lba = 0;
                lba = get_unaligned_be64(cmd + 2);
                num = get_unaligned_be32(cmd + 10);
+               group = cmd[14] & 0x3f;
                check_prot = true;
                break;
        case WRITE_10:
                ei_lba = 0;
                lba = get_unaligned_be32(cmd + 2);
+               group = cmd[6] & 0x3f;
                num = get_unaligned_be16(cmd + 7);
                check_prot = true;
                break;
@@ -3913,15 +3994,18 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                ei_lba = 0;
                lba = get_unaligned_be32(cmd + 2);
                num = get_unaligned_be32(cmd + 6);
+               group = cmd[6] & 0x3f;
                check_prot = true;
                break;
        case 0x53:      /* XDWRITEREAD(10) */
                ei_lba = 0;
                lba = get_unaligned_be32(cmd + 2);
+               group = cmd[6] & 0x1f;
                num = get_unaligned_be16(cmd + 7);
                check_prot = false;
                break;
        default:        /* assume WRITE(32) */
+               group = cmd[6] & 0x3f;
                lba = get_unaligned_be64(cmd + 12);
                ei_lba = get_unaligned_be32(cmd + 20);
                num = get_unaligned_be32(cmd + 28);
@@ -3976,7 +4060,7 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
                }
        }
 
-       ret = do_device_access(sip, scp, 0, lba, num, true);
+       ret = do_device_access(sip, scp, 0, lba, num, true, group);
        if (unlikely(scsi_debug_lbp()))
                map_region(sip, lba, num);
        /* If ZBC zone then bump its write pointer */
@@ -4028,12 +4112,14 @@ static int resp_write_scat(struct scsi_cmnd *scp,
        u32 lb_size = sdebug_sector_size;
        u32 ei_lba;
        u64 lba;
+       u8 group;
        int ret, res;
        bool is_16;
        static const u32 lrd_size = 32; /* + parameter list header size */
 
        if (cmd[0] == VARIABLE_LENGTH_CMD) {
                is_16 = false;
+               group = cmd[6] & 0x3f;
                wrprotect = (cmd[10] >> 5) & 0x7;
                lbdof = get_unaligned_be16(cmd + 12);
                num_lrd = get_unaligned_be16(cmd + 16);
@@ -4044,6 +4130,7 @@ static int resp_write_scat(struct scsi_cmnd *scp,
                lbdof = get_unaligned_be16(cmd + 4);
                num_lrd = get_unaligned_be16(cmd + 8);
                bt_len = get_unaligned_be32(cmd + 10);
+               group = cmd[14] & 0x3f;
                if (unlikely(have_dif_prot)) {
                        if (sdebug_dif == T10_PI_TYPE2_PROTECTION &&
                            wrprotect) {
@@ -4132,7 +4219,7 @@ static int resp_write_scat(struct scsi_cmnd *scp,
                        }
                }
 
-               ret = do_device_access(sip, scp, sg_off, lba, num, true);
+               ret = do_device_access(sip, scp, sg_off, lba, num, true, group);
                /* If ZBC zone then bump its write pointer */
                if (sdebug_dev_is_zoned(devip))
                        zbc_inc_wp(devip, lba, num);
@@ -4507,6 +4594,51 @@ static int resp_get_lba_status(struct scsi_cmnd *scp,
        return fill_from_dev_buffer(scp, arr, SDEBUG_GET_LBA_STATUS_LEN);
 }
 
+static int resp_get_stream_status(struct scsi_cmnd *scp,
+                                 struct sdebug_dev_info *devip)
+{
+       u16 starting_stream_id, stream_id;
+       const u8 *cmd = scp->cmnd;
+       u32 alloc_len, offset;
+       u8 arr[256] = {};
+       struct scsi_stream_status_header *h = (void *)arr;
+
+       starting_stream_id = get_unaligned_be16(cmd + 4);
+       alloc_len = get_unaligned_be32(cmd + 10);
+
+       if (alloc_len < 8) {
+               mk_sense_invalid_fld(scp, SDEB_IN_CDB, 10, -1);
+               return check_condition_result;
+       }
+
+       if (starting_stream_id >= MAXIMUM_NUMBER_OF_STREAMS) {
+               mk_sense_invalid_fld(scp, SDEB_IN_CDB, 4, -1);
+               return check_condition_result;
+       }
+
+       /*
+        * The GET STREAM STATUS command only reports status information
+        * about open streams. Treat the non-permanent stream as open.
+        */
+       put_unaligned_be16(MAXIMUM_NUMBER_OF_STREAMS,
+                          &h->number_of_open_streams);
+
+       for (offset = 8, stream_id = starting_stream_id;
+            offset + 8 <= min_t(u32, alloc_len, sizeof(arr)) &&
+                    stream_id < MAXIMUM_NUMBER_OF_STREAMS;
+            offset += 8, stream_id++) {
+               struct scsi_stream_status *stream_status = (void *)arr + offset;
+
+               stream_status->perm = stream_id < PERMANENT_STREAM_COUNT;
+               put_unaligned_be16(stream_id,
+                                  &stream_status->stream_identifier);
+               stream_status->rel_lifetime = stream_id + 1;
+       }
+       put_unaligned_be32(offset - 8, &h->len); /* PARAMETER DATA LENGTH */
+
+       return fill_from_dev_buffer(scp, arr, min(offset, alloc_len));
+}
+
 static int resp_sync_cache(struct scsi_cmnd *scp,
                           struct sdebug_dev_info *devip)
 {
@@ -7182,6 +7314,30 @@ static ssize_t tur_ms_to_ready_show(struct device_driver *ddp, char *buf)
 }
 static DRIVER_ATTR_RO(tur_ms_to_ready);
 
+static ssize_t group_number_stats_show(struct device_driver *ddp, char *buf)
+{
+       char *p = buf, *end = buf + PAGE_SIZE;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(writes_by_group_number); i++)
+               p += scnprintf(p, end - p, "%d %ld\n", i,
+                              atomic_long_read(&writes_by_group_number[i]));
+
+       return p - buf;
+}
+
+static ssize_t group_number_stats_store(struct device_driver *ddp,
+                                       const char *buf, size_t count)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(writes_by_group_number); i++)
+               atomic_long_set(&writes_by_group_number[i], 0);
+
+       return count;
+}
+static DRIVER_ATTR_RW(group_number_stats);
+
 /* Note: The following array creates attribute files in the
    /sys/bus/pseudo/drivers/scsi_debug directory. The advantage of these
    files (over those found in the /sys/module/scsi_debug/parameters
@@ -7228,6 +7384,7 @@ static struct attribute *sdebug_drv_attrs[] = {
        &driver_attr_cdb_len.attr,
        &driver_attr_tur_ms_to_ready.attr,
        &driver_attr_zbc.attr,
+       &driver_attr_group_number_stats.attr,
        NULL,
 };
 ATTRIBUTE_GROUPS(sdebug_drv);