Commit f0d1cf93 authored by Douglas Gilbert's avatar Douglas Gilbert Committed by Martin K. Petersen

scsi: scsi_debug: Add ZBC zone commands

Add support for the 5 ZBC commands and enough functionality to emulate a
host-managed device with one conventional zone and a set of sequential
write-required zones up to the disk capacity.

Link: https://lore.kernel.org/r/20200422104221.378203-3-damien.lemoal@wdc.comSigned-off-by: default avatarDouglas Gilbert <dgilbert@interlog.com>
Signed-off-by: default avatarDamien Le Moal <damien.lemoal@wdc.com>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent d36da305
...@@ -94,6 +94,11 @@ static const char *sdebug_version_date = "20200421"; ...@@ -94,6 +94,11 @@ static const char *sdebug_version_date = "20200421";
#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ #define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */
#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 #define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
#define WRITE_ERROR_ASC 0xc #define WRITE_ERROR_ASC 0xc
#define UNALIGNED_WRITE_ASCQ 0x4
#define WRITE_BOUNDARY_ASCQ 0x5
#define READ_INVDATA_ASCQ 0x6
#define READ_BOUNDARY_ASCQ 0x7
#define INSUFF_ZONE_ASCQ 0xe
/* Additional Sense Code Qualifier (ASCQ) */ /* Additional Sense Code Qualifier (ASCQ) */
#define ACK_NAK_TO 0x3 #define ACK_NAK_TO 0x3
...@@ -147,6 +152,10 @@ static const char *sdebug_version_date = "20200421"; ...@@ -147,6 +152,10 @@ static const char *sdebug_version_date = "20200421";
#define DEF_UUID_CTL 0 #define DEF_UUID_CTL 0
#define JDELAY_OVERRIDDEN -9999 #define JDELAY_OVERRIDDEN -9999
/* Default parameters for ZBC drives */
#define DEF_ZBC_ZONE_SIZE_MB 128
#define DEF_ZBC_MAX_OPEN_ZONES 8
#define SDEBUG_LUN_0_VAL 0 #define SDEBUG_LUN_0_VAL 0
/* bit mask values for sdebug_opts */ /* bit mask values for sdebug_opts */
...@@ -250,6 +259,24 @@ static const char *sdebug_version_date = "20200421"; ...@@ -250,6 +259,24 @@ static const char *sdebug_version_date = "20200421";
#define SDEB_XA_NOT_IN_USE XA_MARK_1 #define SDEB_XA_NOT_IN_USE XA_MARK_1
/* enumeration names taken from table 26, zbcr05 */
enum sdebug_z_cond {
ZBC_NOT_WRITE_POINTER = 0x0,
ZC1_EMPTY = 0x1,
ZC2_IMPLICIT_OPEN = 0x2,
ZC3_EXPLICIT_OPEN = 0x3,
ZC4_CLOSED = 0x4,
ZC6_READ_ONLY = 0xd,
ZC5_FULL = 0xe,
ZC7_OFFLINE = 0xf,
};
struct sdeb_zone_state { /* ZBC: per zone state */
enum sdebug_z_cond z_cond;
unsigned int z_size;
sector_t z_start;
sector_t z_wp;
};
struct sdebug_dev_info { struct sdebug_dev_info {
struct list_head dev_list; struct list_head dev_list;
...@@ -262,6 +289,16 @@ struct sdebug_dev_info { ...@@ -262,6 +289,16 @@ struct sdebug_dev_info {
atomic_t num_in_q; atomic_t num_in_q;
atomic_t stopped; atomic_t stopped;
bool used; bool used;
/* For ZBC devices */
unsigned int zsize;
unsigned int zsize_shift;
unsigned int nr_zones;
unsigned int nr_imp_open;
unsigned int nr_exp_open;
unsigned int nr_closed;
unsigned int max_open;
struct sdeb_zone_state *zstate;
}; };
struct sdebug_host_info { struct sdebug_host_info {
...@@ -369,7 +406,9 @@ enum sdeb_opcode_index { ...@@ -369,7 +406,9 @@ enum sdeb_opcode_index {
SDEB_I_SYNC_CACHE = 27, /* 10, 16 */ SDEB_I_SYNC_CACHE = 27, /* 10, 16 */
SDEB_I_COMP_WRITE = 28, SDEB_I_COMP_WRITE = 28,
SDEB_I_PRE_FETCH = 29, /* 10, 16 */ SDEB_I_PRE_FETCH = 29, /* 10, 16 */
SDEB_I_LAST_ELEM_P1 = 30, /* keep this last (previous + 1) */ SDEB_I_ZONE_OUT = 30, /* 0x94+SA; includes no data xfer */
SDEB_I_ZONE_IN = 31, /* 0x95+SA; all have data-in */
SDEB_I_LAST_ELEM_P1 = 32, /* keep this last (previous + 1) */
}; };
...@@ -401,7 +440,8 @@ static const unsigned char opcode_ind_arr[256] = { ...@@ -401,7 +440,8 @@ static const unsigned char opcode_ind_arr[256] = {
0, 0, 0, 0, 0, SDEB_I_ATA_PT, 0, 0, 0, 0, 0, 0, 0, SDEB_I_ATA_PT, 0, 0,
SDEB_I_READ, SDEB_I_COMP_WRITE, SDEB_I_WRITE, 0, SDEB_I_READ, SDEB_I_COMP_WRITE, SDEB_I_WRITE, 0,
0, 0, 0, SDEB_I_VERIFY, 0, 0, 0, SDEB_I_VERIFY,
SDEB_I_PRE_FETCH, SDEB_I_SYNC_CACHE, 0, SDEB_I_WRITE_SAME, 0, 0, 0, 0, SDEB_I_PRE_FETCH, SDEB_I_SYNC_CACHE, 0, SDEB_I_WRITE_SAME,
SDEB_I_ZONE_OUT, SDEB_I_ZONE_IN, 0, 0,
0, 0, 0, 0, 0, 0, SDEB_I_SERV_ACT_IN_16, SDEB_I_SERV_ACT_OUT_16, 0, 0, 0, 0, 0, 0, SDEB_I_SERV_ACT_IN_16, SDEB_I_SERV_ACT_OUT_16,
/* 0xa0; 0xa0->0xbf: 12 byte cdbs */ /* 0xa0; 0xa0->0xbf: 12 byte cdbs */
SDEB_I_REPORT_LUNS, SDEB_I_ATA_PT, 0, SDEB_I_MAINT_IN, SDEB_I_REPORT_LUNS, SDEB_I_ATA_PT, 0, SDEB_I_MAINT_IN,
...@@ -449,6 +489,11 @@ static int resp_comp_write(struct scsi_cmnd *, struct sdebug_dev_info *); ...@@ -449,6 +489,11 @@ static int resp_comp_write(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_write_buffer(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_write_buffer(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_sync_cache(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_sync_cache(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_pre_fetch(struct scsi_cmnd *, struct sdebug_dev_info *); static int resp_pre_fetch(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_report_zones(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_open_zone(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_close_zone(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_finish_zone(struct scsi_cmnd *, struct sdebug_dev_info *);
static int resp_rwp_zone(struct scsi_cmnd *, struct sdebug_dev_info *);
static int sdebug_do_add_host(bool mk_new_store); static int sdebug_do_add_host(bool mk_new_store);
static int sdebug_add_host_helper(int per_host_idx); static int sdebug_add_host_helper(int per_host_idx);
...@@ -553,6 +598,24 @@ static const struct opcode_info_t pre_fetch_iarr[] = { ...@@ -553,6 +598,24 @@ static const struct opcode_info_t pre_fetch_iarr[] = {
0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* PRE-FETCH (16) */ 0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* PRE-FETCH (16) */
}; };
static const struct opcode_info_t zone_out_iarr[] = { /* ZONE OUT(16) */
{0, 0x94, 0x1, F_SA_LOW, resp_close_zone, NULL,
{16, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* CLOSE ZONE */
{0, 0x94, 0x2, F_SA_LOW, resp_finish_zone, NULL,
{16, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* FINISH ZONE */
{0, 0x94, 0x4, F_SA_LOW, resp_rwp_zone, NULL,
{16, 0x4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* RESET WRITE POINTER */
};
static const struct opcode_info_t zone_in_iarr[] = { /* ZONE IN(16) */
{0, 0x95, 0x6, F_SA_LOW | F_D_IN, NULL, NULL,
{16, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* REPORT ZONES */
};
/* This array is accessed via SDEB_I_* values. Make sure all are mapped, /* This array is accessed via SDEB_I_* values. Make sure all are mapped,
* plus the terminating elements for logic that scans this table such as * plus the terminating elements for logic that scans this table such as
...@@ -655,6 +718,15 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEM_P1 + 1] = { ...@@ -655,6 +718,15 @@ static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEM_P1 + 1] = {
0, 0, 0, 0} }, /* PRE-FETCH (10) */ 0, 0, 0, 0} }, /* PRE-FETCH (10) */
/* 30 */ /* 30 */
{ARRAY_SIZE(zone_out_iarr), 0x94, 0x3, F_SA_LOW,
resp_open_zone, zone_out_iarr, /* ZONE_OUT(16), OPEN ZONE) */
{16, 0x3 /* SA */, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0xff, 0xff, 0x1, 0xc7} },
{ARRAY_SIZE(zone_in_iarr), 0x95, 0x0, F_SA_LOW | F_D_IN,
resp_report_zones, zone_in_iarr, /* ZONE_IN(16), REPORT ZONES) */
{16, 0x0 /* SA */, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xc7} },
/* sentinel */
{0xff, 0, 0, 0, NULL, NULL, /* terminating element */ {0xff, 0, 0, 0, NULL, NULL, /* terminating element */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
}; };
...@@ -742,6 +814,11 @@ static int dix_writes; ...@@ -742,6 +814,11 @@ static int dix_writes;
static int dix_reads; static int dix_reads;
static int dif_errors; static int dif_errors;
/* ZBC global data */
static bool sdeb_zbc_in_use; /* true when ptype=TYPE_ZBC [0x14] */
static const int zbc_zone_size_mb;
static const int zbc_max_open_zones = DEF_ZBC_MAX_OPEN_ZONES;
static int submit_queues = DEF_SUBMIT_QUEUES; /* > 1 for multi-queue (mq) */ static int submit_queues = DEF_SUBMIT_QUEUES; /* > 1 for multi-queue (mq) */
static struct sdebug_queue *sdebug_q_arr; /* ptr to array of submit queues */ static struct sdebug_queue *sdebug_q_arr; /* ptr to array of submit queues */
...@@ -1446,19 +1523,21 @@ static int inquiry_vpd_b2(unsigned char *arr) ...@@ -1446,19 +1523,21 @@ static int inquiry_vpd_b2(unsigned char *arr)
} }
/* Zoned block device characteristics VPD page (ZBC mandatory) */ /* Zoned block device characteristics VPD page (ZBC mandatory) */
static int inquiry_vpd_b6(unsigned char *arr) static int inquiry_vpd_b6(struct sdebug_dev_info *devip, unsigned char *arr)
{ {
memset(arr, 0, 0x3c); memset(arr, 0, 0x3c);
arr[0] = 0x1; /* set URSWRZ (unrestricted read in seq. wr req zone) */ arr[0] = 0x1; /* set URSWRZ (unrestricted read in seq. wr req zone) */
/* /*
* Set Optimal number of open sequential write preferred zones and * Set Optimal number of open sequential write preferred zones and
* Optimal number of non-sequentially written sequential write * Optimal number of non-sequentially written sequential write
* preferred zones and Maximum number of open sequential write * preferred zones fields to 'not reported' (0xffffffff). Leave other
* required zones fields to 'not reported' (0xffffffff). Leave other * fields set to zero, apart from Max. number of open swrz_s field.
* fields set to zero.
*/ */
put_unaligned_be32(0xffffffff, &arr[4]); put_unaligned_be32(0xffffffff, &arr[4]);
put_unaligned_be32(0xffffffff, &arr[8]); put_unaligned_be32(0xffffffff, &arr[8]);
if (devip->max_open)
put_unaligned_be32(devip->max_open, &arr[12]);
else
put_unaligned_be32(0xffffffff, &arr[12]); put_unaligned_be32(0xffffffff, &arr[12]);
return 0x3c; return 0x3c;
} }
...@@ -1579,7 +1658,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -1579,7 +1658,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
arr[3] = inquiry_vpd_b2(&arr[4]); arr[3] = inquiry_vpd_b2(&arr[4]);
} else if (is_zbc && cmd[2] == 0xb6) { /* ZB dev. charact. */ } else if (is_zbc && cmd[2] == 0xb6) { /* ZB dev. charact. */
arr[1] = cmd[2]; /*sanity */ arr[1] = cmd[2]; /*sanity */
arr[3] = inquiry_vpd_b6(&arr[4]); arr[3] = inquiry_vpd_b6(devip, &arr[4]);
} else { } else {
mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, -1); mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, -1);
kfree(arr); kfree(arr);
...@@ -2550,9 +2629,185 @@ static int resp_log_sense(struct scsi_cmnd *scp, ...@@ -2550,9 +2629,185 @@ static int resp_log_sense(struct scsi_cmnd *scp,
min_t(int, len, SDEBUG_MAX_INQ_ARR_SZ)); min_t(int, len, SDEBUG_MAX_INQ_ARR_SZ));
} }
static inline int check_device_access_params(struct scsi_cmnd *scp, static inline bool sdebug_dev_is_zoned(struct sdebug_dev_info *devip)
{
return devip->nr_zones != 0;
}
static struct sdeb_zone_state *zbc_zone(struct sdebug_dev_info *devip,
unsigned long long lba)
{
unsigned int zno;
if (devip->zsize_shift)
zno = lba >> devip->zsize_shift;
else
zno = lba / devip->zsize;
return &devip->zstate[zno];
}
static inline bool zbc_zone_is_conv(struct sdeb_zone_state *zsp)
{
return zsp->z_cond == ZBC_NOT_WRITE_POINTER;
}
static void zbc_close_zone(struct sdebug_dev_info *devip,
struct sdeb_zone_state *zsp)
{
enum sdebug_z_cond zc;
if (zbc_zone_is_conv(zsp))
return;
zc = zsp->z_cond;
if (!(zc == ZC2_IMPLICIT_OPEN || zc == ZC3_EXPLICIT_OPEN))
return;
if (zc == ZC2_IMPLICIT_OPEN)
devip->nr_imp_open--;
else
devip->nr_exp_open--;
if (zsp->z_wp == zsp->z_start) {
zsp->z_cond = ZC1_EMPTY;
} else {
zsp->z_cond = ZC4_CLOSED;
devip->nr_closed++;
}
}
static void zbc_close_imp_open_zone(struct sdebug_dev_info *devip)
{
struct sdeb_zone_state *zsp = &devip->zstate[0];
unsigned int i;
for (i = 0; i < devip->nr_zones; i++, zsp++) {
if (zsp->z_cond == ZC2_IMPLICIT_OPEN) {
zbc_close_zone(devip, zsp);
return;
}
}
}
static void zbc_open_zone(struct sdebug_dev_info *devip,
struct sdeb_zone_state *zsp, bool explicit)
{
enum sdebug_z_cond zc;
if (zbc_zone_is_conv(zsp))
return;
zc = zsp->z_cond;
if ((explicit && zc == ZC3_EXPLICIT_OPEN) ||
(!explicit && zc == ZC2_IMPLICIT_OPEN))
return;
/* Close an implicit open zone if necessary */
if (explicit && zsp->z_cond == ZC2_IMPLICIT_OPEN)
zbc_close_zone(devip, zsp);
else if (devip->max_open &&
devip->nr_imp_open + devip->nr_exp_open >= devip->max_open)
zbc_close_imp_open_zone(devip);
if (zsp->z_cond == ZC4_CLOSED)
devip->nr_closed--;
if (explicit) {
zsp->z_cond = ZC3_EXPLICIT_OPEN;
devip->nr_exp_open++;
} else {
zsp->z_cond = ZC2_IMPLICIT_OPEN;
devip->nr_imp_open++;
}
}
static void zbc_inc_wp(struct sdebug_dev_info *devip,
unsigned long long lba, unsigned int num)
{
struct sdeb_zone_state *zsp = zbc_zone(devip, lba);
if (zbc_zone_is_conv(zsp))
return;
zsp->z_wp += num;
if (zsp->z_wp >= zsp->z_start + zsp->z_size)
zsp->z_cond = ZC5_FULL;
}
static int check_zbc_access_params(struct scsi_cmnd *scp,
unsigned long long lba, unsigned int num, bool write) unsigned long long lba, unsigned int num, bool write)
{ {
struct scsi_device *sdp = scp->device;
struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
struct sdeb_zone_state *zsp = zbc_zone(devip, lba);
struct sdeb_zone_state *zsp_end = zbc_zone(devip, lba + num - 1);
if (!write) {
/* Reads cannot cross zone types boundaries */
if (zsp_end != zsp &&
zbc_zone_is_conv(zsp) &&
!zbc_zone_is_conv(zsp_end)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST,
LBA_OUT_OF_RANGE,
READ_INVDATA_ASCQ);
return check_condition_result;
}
return 0;
}
/* No restrictions for writes within conventional zones */
if (zbc_zone_is_conv(zsp)) {
if (!zbc_zone_is_conv(zsp_end)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST,
LBA_OUT_OF_RANGE,
WRITE_BOUNDARY_ASCQ);
return check_condition_result;
}
return 0;
}
/* Writes cannot cross sequential zone boundaries */
if (zsp_end != zsp) {
mk_sense_buffer(scp, ILLEGAL_REQUEST,
LBA_OUT_OF_RANGE,
WRITE_BOUNDARY_ASCQ);
return check_condition_result;
}
/* Cannot write full zones */
if (zsp->z_cond == ZC5_FULL) {
mk_sense_buffer(scp, ILLEGAL_REQUEST,
INVALID_FIELD_IN_CDB, 0);
return check_condition_result;
}
/* Writes must be aligned to the zone WP */
if (lba != zsp->z_wp) {
mk_sense_buffer(scp, ILLEGAL_REQUEST,
LBA_OUT_OF_RANGE,
UNALIGNED_WRITE_ASCQ);
return check_condition_result;
}
/* Handle implicit open of closed and empty zones */
if (zsp->z_cond == ZC1_EMPTY || zsp->z_cond == ZC4_CLOSED) {
if (devip->max_open &&
devip->nr_exp_open >= devip->max_open) {
mk_sense_buffer(scp, DATA_PROTECT,
INSUFF_RES_ASC,
INSUFF_ZONE_ASCQ);
return check_condition_result;
}
zbc_open_zone(devip, zsp, false);
}
return 0;
}
static inline int check_device_access_params
(struct scsi_cmnd *scp, unsigned long long lba,
unsigned int num, bool write)
{
struct scsi_device *sdp = scp->device;
struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
if (lba + num > sdebug_capacity) { if (lba + num > sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0); mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
return check_condition_result; return check_condition_result;
...@@ -2567,6 +2822,9 @@ static inline int check_device_access_params(struct scsi_cmnd *scp, ...@@ -2567,6 +2822,9 @@ static inline int check_device_access_params(struct scsi_cmnd *scp,
mk_sense_buffer(scp, DATA_PROTECT, WRITE_PROTECTED, 0x2); mk_sense_buffer(scp, DATA_PROTECT, WRITE_PROTECTED, 0x2);
return check_condition_result; return check_condition_result;
} }
if (sdebug_dev_is_zoned(devip))
return check_zbc_access_params(scp, lba, num, write);
return 0; return 0;
} }
...@@ -3153,10 +3411,13 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -3153,10 +3411,13 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
sdev_printk(KERN_ERR, scp->device, "Unprotected WR " sdev_printk(KERN_ERR, scp->device, "Unprotected WR "
"to DIF device\n"); "to DIF device\n");
} }
write_lock(macc_lckp);
ret = check_device_access_params(scp, lba, num, true); ret = check_device_access_params(scp, lba, num, true);
if (ret) if (ret) {
write_unlock(macc_lckp);
return ret; return ret;
write_lock(macc_lckp); }
/* DIX + T10 DIF */ /* DIX + T10 DIF */
if (unlikely(sdebug_dix && scsi_prot_sg_count(scp))) { if (unlikely(sdebug_dix && scsi_prot_sg_count(scp))) {
...@@ -3172,6 +3433,9 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -3172,6 +3433,9 @@ 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);
if (unlikely(scsi_debug_lbp())) if (unlikely(scsi_debug_lbp()))
map_region(sip, lba, num); map_region(sip, lba, num);
/* If ZBC zone then bump its write pointer */
if (sdebug_dev_is_zoned(devip))
zbc_inc_wp(devip, lba, num);
write_unlock(macc_lckp); write_unlock(macc_lckp);
if (unlikely(-1 == ret)) if (unlikely(-1 == ret))
return DID_ERROR << 16; return DID_ERROR << 16;
...@@ -3326,6 +3590,9 @@ static int resp_write_scat(struct scsi_cmnd *scp, ...@@ -3326,6 +3590,9 @@ 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);
/* If ZBC zone then bump its write pointer */
if (sdebug_dev_is_zoned(devip))
zbc_inc_wp(devip, lba, num);
if (unlikely(scsi_debug_lbp())) if (unlikely(scsi_debug_lbp()))
map_region(sip, lba, num); map_region(sip, lba, num);
if (unlikely(-1 == ret)) { if (unlikely(-1 == ret)) {
...@@ -3374,6 +3641,8 @@ static int resp_write_scat(struct scsi_cmnd *scp, ...@@ -3374,6 +3641,8 @@ static int resp_write_scat(struct scsi_cmnd *scp,
static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num, static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
u32 ei_lba, bool unmap, bool ndob) u32 ei_lba, bool unmap, bool ndob)
{ {
struct scsi_device *sdp = scp->device;
struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdp->hostdata;
unsigned long long i; unsigned long long i;
u64 block, lbaa; u64 block, lbaa;
u32 lb_size = sdebug_sector_size; u32 lb_size = sdebug_sector_size;
...@@ -3384,11 +3653,13 @@ static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num, ...@@ -3384,11 +3653,13 @@ static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
u8 *fs1p; u8 *fs1p;
u8 *fsp; u8 *fsp;
write_lock(macc_lckp);
ret = check_device_access_params(scp, lba, num, true); ret = check_device_access_params(scp, lba, num, true);
if (ret) if (ret) {
write_unlock(macc_lckp);
return ret; return ret;
}
write_lock(macc_lckp);
if (unmap && scsi_debug_lbp()) { if (unmap && scsi_debug_lbp()) {
unmap_region(sip, lba, num); unmap_region(sip, lba, num);
...@@ -3421,6 +3692,9 @@ static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num, ...@@ -3421,6 +3692,9 @@ static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
} }
if (scsi_debug_lbp()) if (scsi_debug_lbp())
map_region(sip, lba, num); map_region(sip, lba, num);
/* If ZBC zone then bump its write pointer */
if (sdebug_dev_is_zoned(devip))
zbc_inc_wp(devip, lba, num);
out: out:
write_unlock(macc_lckp); write_unlock(macc_lckp);
...@@ -3948,6 +4222,426 @@ static int resp_verify(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -3948,6 +4222,426 @@ static int resp_verify(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
return ret; return ret;
} }
#define RZONES_DESC_HD 64
/* Report zones depending on start LBA nad reporting options */
static int resp_report_zones(struct scsi_cmnd *scp,
struct sdebug_dev_info *devip)
{
unsigned int i, max_zones, rep_max_zones, nrz = 0;
int ret = 0;
u32 alloc_len, rep_opts, rep_len;
bool partial;
u64 lba, zs_lba;
u8 *arr = NULL, *desc;
u8 *cmd = scp->cmnd;
struct sdeb_zone_state *zsp;
struct sdeb_store_info *sip = devip2sip(devip);
rwlock_t *macc_lckp = sip ? &sip->macc_lck : &sdeb_fake_rw_lck;
if (!sdebug_dev_is_zoned(devip)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
zs_lba = get_unaligned_be64(cmd + 2);
alloc_len = get_unaligned_be32(cmd + 10);
rep_opts = cmd[14] & 0x3f;
partial = cmd[14] & 0x80;
if (zs_lba >= sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
return check_condition_result;
}
max_zones = devip->nr_zones - zs_lba / devip->zsize;
rep_max_zones = min((alloc_len - 64) >> ilog2(RZONES_DESC_HD),
max_zones);
arr = kcalloc(RZONES_DESC_HD, alloc_len, GFP_ATOMIC);
if (!arr) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INSUFF_RES_ASC,
INSUFF_RES_ASCQ);
return check_condition_result;
}
read_lock(macc_lckp);
desc = arr + 64;
for (i = 0; i < max_zones; i++) {
lba = zs_lba + devip->zsize * i;
if (lba > sdebug_capacity)
break;
zsp = zbc_zone(devip, lba);
switch (rep_opts) {
case 0x00:
/* All zones */
break;
case 0x01:
/* Empty zones */
if (zsp->z_cond != ZC1_EMPTY)
continue;
break;
case 0x02:
/* Implicit open zones */
if (zsp->z_cond != ZC2_IMPLICIT_OPEN)
continue;
break;
case 0x03:
/* Explicit open zones */
if (zsp->z_cond != ZC3_EXPLICIT_OPEN)
continue;
break;
case 0x04:
/* Closed zones */
if (zsp->z_cond != ZC4_CLOSED)
continue;
break;
case 0x05:
/* Full zones */
if (zsp->z_cond != ZC5_FULL)
continue;
break;
case 0x06:
case 0x07:
case 0x10:
case 0x11:
/*
* Read-only, offline, reset WP recommended and
* non-seq-resource-used are not emulated: no zones
* to report;
*/
continue;
case 0x3f:
/* Not write pointer (conventional) zones */
if (!zbc_zone_is_conv(zsp))
continue;
break;
default:
mk_sense_buffer(scp, ILLEGAL_REQUEST,
INVALID_FIELD_IN_CDB, 0);
ret = check_condition_result;
goto fini;
}
if (nrz < rep_max_zones) {
/* Fill zone descriptor */
if (zbc_zone_is_conv(zsp))
desc[0] = 0x1;
else
desc[0] = 0x2;
desc[1] = zsp->z_cond << 4;
put_unaligned_be64((u64)zsp->z_size, desc + 8);
put_unaligned_be64((u64)zsp->z_start, desc + 16);
put_unaligned_be64((u64)zsp->z_wp, desc + 24);
desc += 64;
}
if (partial && nrz >= rep_max_zones)
break;
nrz++;
}
/* Report header */
put_unaligned_be32(nrz * RZONES_DESC_HD, arr + 0);
put_unaligned_be64(sdebug_capacity - 1, arr + 8);
rep_len = (unsigned long)desc - (unsigned long)arr;
ret = fill_from_dev_buffer(scp, arr, min_t(int, alloc_len, rep_len));
fini:
read_unlock(macc_lckp);
kfree(arr);
return ret;
}
/* Logic transplanted from tcmu-runner, file_zbc.c */
static void zbc_open_all(struct sdebug_dev_info *devip)
{
struct sdeb_zone_state *zsp = &devip->zstate[0];
unsigned int i;
for (i = 0; i < devip->nr_zones; i++, zsp++) {
if (zsp->z_cond == ZC4_CLOSED)
zbc_open_zone(devip, &devip->zstate[i], true);
}
}
static int resp_open_zone(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
int res = 0;
u64 z_id;
enum sdebug_z_cond zc;
u8 *cmd = scp->cmnd;
struct sdeb_zone_state *zsp;
bool all = cmd[14] & 0x01;
struct sdeb_store_info *sip = devip2sip(devip);
rwlock_t *macc_lckp = sip ? &sip->macc_lck : &sdeb_fake_rw_lck;
if (!sdebug_dev_is_zoned(devip)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
write_lock(macc_lckp);
if (all) {
/* Check if all closed zones can be open */
if (devip->max_open &&
devip->nr_exp_open + devip->nr_closed > devip->max_open) {
mk_sense_buffer(scp, DATA_PROTECT, INSUFF_RES_ASC,
INSUFF_ZONE_ASCQ);
res = check_condition_result;
goto fini;
}
/* Open all closed zones */
zbc_open_all(devip);
goto fini;
}
/* Open the specified zone */
z_id = get_unaligned_be64(cmd + 2);
if (z_id >= sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
res = check_condition_result;
goto fini;
}
zsp = zbc_zone(devip, z_id);
if (z_id != zsp->z_start) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
if (zbc_zone_is_conv(zsp)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
zc = zsp->z_cond;
if (zc == ZC3_EXPLICIT_OPEN || zc == ZC5_FULL)
goto fini;
if (devip->max_open && devip->nr_exp_open >= devip->max_open) {
mk_sense_buffer(scp, DATA_PROTECT, INSUFF_RES_ASC,
INSUFF_ZONE_ASCQ);
res = check_condition_result;
goto fini;
}
if (zc == ZC2_IMPLICIT_OPEN)
zbc_close_zone(devip, zsp);
zbc_open_zone(devip, zsp, true);
fini:
write_unlock(macc_lckp);
return res;
}
static void zbc_close_all(struct sdebug_dev_info *devip)
{
unsigned int i;
for (i = 0; i < devip->nr_zones; i++)
zbc_close_zone(devip, &devip->zstate[i]);
}
static int resp_close_zone(struct scsi_cmnd *scp,
struct sdebug_dev_info *devip)
{
int res = 0;
u64 z_id;
u8 *cmd = scp->cmnd;
struct sdeb_zone_state *zsp;
bool all = cmd[14] & 0x01;
struct sdeb_store_info *sip = devip2sip(devip);
rwlock_t *macc_lckp = sip ? &sip->macc_lck : &sdeb_fake_rw_lck;
if (!sdebug_dev_is_zoned(devip)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
write_lock(macc_lckp);
if (all) {
zbc_close_all(devip);
goto fini;
}
/* Close specified zone */
z_id = get_unaligned_be64(cmd + 2);
if (z_id >= sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
res = check_condition_result;
goto fini;
}
zsp = zbc_zone(devip, z_id);
if (z_id != zsp->z_start) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
if (zbc_zone_is_conv(zsp)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
zbc_close_zone(devip, zsp);
fini:
write_unlock(macc_lckp);
return res;
}
static void zbc_finish_zone(struct sdebug_dev_info *devip,
struct sdeb_zone_state *zsp, bool empty)
{
enum sdebug_z_cond zc = zsp->z_cond;
if (zc == ZC4_CLOSED || zc == ZC2_IMPLICIT_OPEN ||
zc == ZC3_EXPLICIT_OPEN || (empty && zc == ZC1_EMPTY)) {
if (zc == ZC2_IMPLICIT_OPEN || zc == ZC3_EXPLICIT_OPEN)
zbc_close_zone(devip, zsp);
if (zsp->z_cond == ZC4_CLOSED)
devip->nr_closed--;
zsp->z_wp = zsp->z_start + zsp->z_size;
zsp->z_cond = ZC5_FULL;
}
}
static void zbc_finish_all(struct sdebug_dev_info *devip)
{
unsigned int i;
for (i = 0; i < devip->nr_zones; i++)
zbc_finish_zone(devip, &devip->zstate[i], false);
}
static int resp_finish_zone(struct scsi_cmnd *scp,
struct sdebug_dev_info *devip)
{
struct sdeb_zone_state *zsp;
int res = 0;
u64 z_id;
u8 *cmd = scp->cmnd;
bool all = cmd[14] & 0x01;
struct sdeb_store_info *sip = devip2sip(devip);
rwlock_t *macc_lckp = sip ? &sip->macc_lck : &sdeb_fake_rw_lck;
if (!sdebug_dev_is_zoned(devip)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
write_lock(macc_lckp);
if (all) {
zbc_finish_all(devip);
goto fini;
}
/* Finish the specified zone */
z_id = get_unaligned_be64(cmd + 2);
if (z_id >= sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
res = check_condition_result;
goto fini;
}
zsp = zbc_zone(devip, z_id);
if (z_id != zsp->z_start) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
if (zbc_zone_is_conv(zsp)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
zbc_finish_zone(devip, zsp, true);
fini:
write_unlock(macc_lckp);
return res;
}
static void zbc_rwp_zone(struct sdebug_dev_info *devip,
struct sdeb_zone_state *zsp)
{
enum sdebug_z_cond zc;
if (zbc_zone_is_conv(zsp))
return;
zc = zsp->z_cond;
if (zc == ZC2_IMPLICIT_OPEN || zc == ZC3_EXPLICIT_OPEN)
zbc_close_zone(devip, zsp);
if (zsp->z_cond == ZC4_CLOSED)
devip->nr_closed--;
zsp->z_wp = zsp->z_start;
zsp->z_cond = ZC1_EMPTY;
}
static void zbc_rwp_all(struct sdebug_dev_info *devip)
{
unsigned int i;
for (i = 0; i < devip->nr_zones; i++)
zbc_rwp_zone(devip, &devip->zstate[i]);
}
static int resp_rwp_zone(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
{
struct sdeb_zone_state *zsp;
int res = 0;
u64 z_id;
u8 *cmd = scp->cmnd;
bool all = cmd[14] & 0x01;
struct sdeb_store_info *sip = devip2sip(devip);
rwlock_t *macc_lckp = sip ? &sip->macc_lck : &sdeb_fake_rw_lck;
if (!sdebug_dev_is_zoned(devip)) {
mk_sense_invalid_opcode(scp);
return check_condition_result;
}
write_lock(macc_lckp);
if (all) {
zbc_rwp_all(devip);
goto fini;
}
z_id = get_unaligned_be64(cmd + 2);
if (z_id >= sdebug_capacity) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
res = check_condition_result;
goto fini;
}
zsp = zbc_zone(devip, z_id);
if (z_id != zsp->z_start) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
if (zbc_zone_is_conv(zsp)) {
mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
res = check_condition_result;
goto fini;
}
zbc_rwp_zone(devip, zsp);
fini:
write_unlock(macc_lckp);
return res;
}
static struct sdebug_queue *get_queue(struct scsi_cmnd *cmnd) static struct sdebug_queue *get_queue(struct scsi_cmnd *cmnd)
{ {
u32 tag = blk_mq_unique_tag(cmnd->request); u32 tag = blk_mq_unique_tag(cmnd->request);
...@@ -4053,6 +4747,76 @@ static void sdebug_q_cmd_wq_complete(struct work_struct *work) ...@@ -4053,6 +4747,76 @@ static void sdebug_q_cmd_wq_complete(struct work_struct *work)
static bool got_shared_uuid; static bool got_shared_uuid;
static uuid_t shared_uuid; static uuid_t shared_uuid;
static int sdebug_device_create_zones(struct sdebug_dev_info *devip)
{
struct sdeb_zone_state *zsp;
sector_t capacity = get_sdebug_capacity();
sector_t zstart = 0;
unsigned int i;
/*
* Set the zone size: if zbc_zone_size_mb is not set, figure out a
* zone size allowing for at least 4 zones on the device. Otherwise,
* use the specified zone size checking that at least 2 zones can be
* created for the device.
*/
if (!zbc_zone_size_mb) {
devip->zsize = (DEF_ZBC_ZONE_SIZE_MB * SZ_1M)
>> ilog2(sdebug_sector_size);
while (capacity < devip->zsize << 2 && devip->zsize >= 2)
devip->zsize >>= 1;
if (devip->zsize < 2) {
pr_err("Device capacity too small\n");
return -EINVAL;
}
} else {
devip->zsize = (zbc_zone_size_mb * SZ_1M)
>> ilog2(sdebug_sector_size);
if (devip->zsize >= capacity) {
pr_err("Zone size too large for device capacity\n");
return -EINVAL;
}
}
if (is_power_of_2(devip->zsize))
devip->zsize_shift = ilog2(devip->zsize);
devip->nr_zones = (capacity + devip->zsize - 1) >> devip->zsize_shift;
/* zbc_max_open_zones can be 0, meaning "not reported" (no limit) */
if (zbc_max_open_zones >= devip->nr_zones - 1)
devip->max_open = (devip->nr_zones - 1) / 2;
else
devip->max_open = zbc_max_open_zones;
devip->zstate = kcalloc(devip->nr_zones,
sizeof(struct sdeb_zone_state), GFP_KERNEL);
if (!devip->zstate)
return -ENOMEM;
for (i = 0; i < devip->nr_zones; i++) {
zsp = &devip->zstate[i];
zsp->z_start = zstart;
if (i == 0) {
zsp->z_cond = ZBC_NOT_WRITE_POINTER;
zsp->z_wp = (sector_t)-1;
} else {
zsp->z_cond = ZC1_EMPTY;
zsp->z_wp = zsp->z_start;
}
if (zsp->z_start + devip->zsize < capacity)
zsp->z_size = devip->zsize;
else
zsp->z_size = capacity - zsp->z_start;
zstart += zsp->z_size;
}
return 0;
}
static struct sdebug_dev_info *sdebug_device_create( static struct sdebug_dev_info *sdebug_device_create(
struct sdebug_host_info *sdbg_host, gfp_t flags) struct sdebug_host_info *sdbg_host, gfp_t flags)
{ {
...@@ -4072,6 +4836,13 @@ static struct sdebug_dev_info *sdebug_device_create( ...@@ -4072,6 +4836,13 @@ static struct sdebug_dev_info *sdebug_device_create(
} }
} }
devip->sdbg_host = sdbg_host; devip->sdbg_host = sdbg_host;
if (sdeb_zbc_in_use) {
if (sdebug_device_create_zones(devip)) {
kfree(devip);
return NULL;
}
}
devip->sdbg_host = sdbg_host;
list_add_tail(&devip->dev_list, &sdbg_host->dev_info_list); list_add_tail(&devip->dev_list, &sdbg_host->dev_info_list);
} }
return devip; return devip;
...@@ -5059,7 +5830,13 @@ static ssize_t ptype_store(struct device_driver *ddp, const char *buf, ...@@ -5059,7 +5830,13 @@ static ssize_t ptype_store(struct device_driver *ddp, const char *buf,
{ {
int n; int n;
/* Cannot change from or to TYPE_ZBC with sysfs */
if (sdebug_ptype == TYPE_ZBC)
return -EINVAL;
if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) { if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) {
if (n == TYPE_ZBC)
return -EINVAL;
sdebug_ptype = n; sdebug_ptype = n;
return count; return count;
} }
...@@ -5316,6 +6093,10 @@ static ssize_t virtual_gb_store(struct device_driver *ddp, const char *buf, ...@@ -5316,6 +6093,10 @@ static ssize_t virtual_gb_store(struct device_driver *ddp, const char *buf,
int n; int n;
bool changed; bool changed;
/* Ignore capacity change for ZBC drives for now */
if (sdeb_zbc_in_use)
return -ENOTSUPP;
if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) { if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) {
changed = (sdebug_virtual_gb != n); changed = (sdebug_virtual_gb != n);
sdebug_virtual_gb = n; sdebug_virtual_gb = n;
...@@ -5706,6 +6487,12 @@ static int __init scsi_debug_init(void) ...@@ -5706,6 +6487,12 @@ static int __init scsi_debug_init(void)
for (k = 0; k < submit_queues; ++k) for (k = 0; k < submit_queues; ++k)
spin_lock_init(&sdebug_q_arr[k].qc_lock); spin_lock_init(&sdebug_q_arr[k].qc_lock);
/*
* check for host managed zoned block device specified with ptype=0x14.
*/
if (sdebug_ptype == TYPE_ZBC)
sdeb_zbc_in_use = true;
if (sdebug_dev_size_mb < 1) if (sdebug_dev_size_mb < 1)
sdebug_dev_size_mb = 1; /* force minimum 1 MB ramdisk */ sdebug_dev_size_mb = 1; /* force minimum 1 MB ramdisk */
sz = (unsigned long)sdebug_dev_size_mb * 1048576; sz = (unsigned long)sdebug_dev_size_mb * 1048576;
...@@ -5997,6 +6784,7 @@ static int sdebug_add_host_helper(int per_host_idx) ...@@ -5997,6 +6784,7 @@ static int sdebug_add_host_helper(int per_host_idx)
list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list, list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list,
dev_list) { dev_list) {
list_del(&sdbg_devinfo->dev_list); list_del(&sdbg_devinfo->dev_list);
kfree(sdbg_devinfo->zstate);
kfree(sdbg_devinfo); kfree(sdbg_devinfo);
} }
kfree(sdbg_host); kfree(sdbg_host);
...@@ -6408,6 +7196,7 @@ static int sdebug_driver_remove(struct device *dev) ...@@ -6408,6 +7196,7 @@ static int sdebug_driver_remove(struct device *dev)
list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list, list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list,
dev_list) { dev_list) {
list_del(&sdbg_devinfo->dev_list); list_del(&sdbg_devinfo->dev_list);
kfree(sdbg_devinfo->zstate);
kfree(sdbg_devinfo); kfree(sdbg_devinfo);
} }
......
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