Commit 64e14ece authored by Damien Le Moal's avatar Damien Le Moal Committed by Martin K. Petersen

scsi: scsi_debug: Implement ZBC host-aware emulation

Implement ZBC host-aware device model emulation. The main changes from the
host-managed emulation are the device type (TYPE_DISK is used), relaxation
of access checks for read and write operations and different handling of a
sequential write preferred zone write pointer as mandated by the ZBC r05
specifications.

To facilitate the implementation and avoid a lot of "if" statement, the
zmodel field is added to the device information and the z_type field to the
zone state data structure.

Link: https://lore.kernel.org/r/20200422104221.378203-8-damien.lemoal@wdc.comTested-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 98e0a689
...@@ -262,6 +262,13 @@ static const char *sdebug_version_date = "20200421"; ...@@ -262,6 +262,13 @@ 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
/* Zone types (zbcr05 table 25) */
enum sdebug_z_type {
ZBC_ZONE_TYPE_CNV = 0x1,
ZBC_ZONE_TYPE_SWR = 0x2,
ZBC_ZONE_TYPE_SWP = 0x3,
};
/* enumeration names taken from table 26, zbcr05 */ /* enumeration names taken from table 26, zbcr05 */
enum sdebug_z_cond { enum sdebug_z_cond {
ZBC_NOT_WRITE_POINTER = 0x0, ZBC_NOT_WRITE_POINTER = 0x0,
...@@ -275,7 +282,9 @@ enum sdebug_z_cond { ...@@ -275,7 +282,9 @@ enum sdebug_z_cond {
}; };
struct sdeb_zone_state { /* ZBC: per zone state */ struct sdeb_zone_state { /* ZBC: per zone state */
enum sdebug_z_type z_type;
enum sdebug_z_cond z_cond; enum sdebug_z_cond z_cond;
bool z_non_seq_resource;
unsigned int z_size; unsigned int z_size;
sector_t z_start; sector_t z_start;
sector_t z_wp; sector_t z_wp;
...@@ -294,6 +303,7 @@ struct sdebug_dev_info { ...@@ -294,6 +303,7 @@ struct sdebug_dev_info {
bool used; bool used;
/* For ZBC devices */ /* For ZBC devices */
enum blk_zoned_model zmodel;
unsigned int zsize; unsigned int zsize;
unsigned int zsize_shift; unsigned int zsize_shift;
unsigned int nr_zones; unsigned int nr_zones;
...@@ -822,7 +832,7 @@ static int dix_reads; ...@@ -822,7 +832,7 @@ static int dix_reads;
static int dif_errors; static int dif_errors;
/* ZBC global data */ /* ZBC global data */
static bool sdeb_zbc_in_use; /* true when ptype=TYPE_ZBC [0x14] */ static bool sdeb_zbc_in_use; /* true for host-aware and host-managed disks */
static int sdeb_zbc_zone_size_mb; static int sdeb_zbc_zone_size_mb;
static int sdeb_zbc_max_open = DEF_ZBC_MAX_OPEN_ZONES; static int sdeb_zbc_max_open = DEF_ZBC_MAX_OPEN_ZONES;
static int sdeb_zbc_nr_conv = DEF_ZBC_NR_CONV_ZONES; static int sdeb_zbc_nr_conv = DEF_ZBC_NR_CONV_ZONES;
...@@ -1500,13 +1510,15 @@ static int inquiry_vpd_b0(unsigned char *arr) ...@@ -1500,13 +1510,15 @@ static int inquiry_vpd_b0(unsigned char *arr)
} }
/* Block device characteristics VPD page (SBC-3) */ /* Block device characteristics VPD page (SBC-3) */
static int inquiry_vpd_b1(unsigned char *arr) static int inquiry_vpd_b1(struct sdebug_dev_info *devip, unsigned char *arr)
{ {
memset(arr, 0, 0x3c); memset(arr, 0, 0x3c);
arr[0] = 0; arr[0] = 0;
arr[1] = 1; /* non rotating medium (e.g. solid state) */ arr[1] = 1; /* non rotating medium (e.g. solid state) */
arr[2] = 0; arr[2] = 0;
arr[3] = 5; /* less than 1.8" */ arr[3] = 5; /* less than 1.8" */
if (devip->zmodel == BLK_ZONED_HA)
arr[4] = 1 << 4; /* zoned field = 01b */
return 0x3c; return 0x3c;
} }
...@@ -1543,7 +1555,7 @@ static int inquiry_vpd_b6(struct sdebug_dev_info *devip, unsigned char *arr) ...@@ -1543,7 +1555,7 @@ static int inquiry_vpd_b6(struct sdebug_dev_info *devip, unsigned char *arr)
*/ */
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) if (sdeb_zbc_model == BLK_ZONED_HM && devip->max_open)
put_unaligned_be32(devip->max_open, &arr[12]); put_unaligned_be32(devip->max_open, &arr[12]);
else else
put_unaligned_be32(0xffffffff, &arr[12]); put_unaligned_be32(0xffffffff, &arr[12]);
...@@ -1566,7 +1578,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -1566,7 +1578,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
if (! arr) if (! arr)
return DID_REQUEUE << 16; return DID_REQUEUE << 16;
is_disk = (sdebug_ptype == TYPE_DISK); is_disk = (sdebug_ptype == TYPE_DISK);
is_zbc = (sdebug_ptype == TYPE_ZBC); is_zbc = (devip->zmodel != BLK_ZONED_NONE);
is_disk_zbc = (is_disk || is_zbc); is_disk_zbc = (is_disk || is_zbc);
have_wlun = scsi_is_wlun(scp->device->lun); have_wlun = scsi_is_wlun(scp->device->lun);
if (have_wlun) if (have_wlun)
...@@ -1611,7 +1623,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -1611,7 +1623,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
arr[n++] = 0xb1; /* Block characteristics */ arr[n++] = 0xb1; /* Block characteristics */
if (is_disk) if (is_disk)
arr[n++] = 0xb2; /* LB Provisioning */ arr[n++] = 0xb2; /* LB Provisioning */
else if (is_zbc) if (is_zbc)
arr[n++] = 0xb6; /* ZB dev. char. */ arr[n++] = 0xb6; /* ZB dev. char. */
} }
arr[3] = n - 4; /* number of supported VPD pages */ arr[3] = n - 4; /* number of supported VPD pages */
...@@ -1660,7 +1672,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) ...@@ -1660,7 +1672,7 @@ static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
arr[3] = inquiry_vpd_b0(&arr[4]); arr[3] = inquiry_vpd_b0(&arr[4]);
} else if (is_disk_zbc && 0xb1 == cmd[2]) { /* Block char. */ } else if (is_disk_zbc && 0xb1 == cmd[2]) { /* Block char. */
arr[1] = cmd[2]; /*sanity */ arr[1] = cmd[2]; /*sanity */
arr[3] = inquiry_vpd_b1(&arr[4]); arr[3] = inquiry_vpd_b1(devip, &arr[4]);
} else if (is_disk && 0xb2 == cmd[2]) { /* LB Prov. */ } else if (is_disk && 0xb2 == cmd[2]) { /* LB Prov. */
arr[1] = cmd[2]; /*sanity */ arr[1] = cmd[2]; /*sanity */
arr[3] = inquiry_vpd_b2(&arr[4]); arr[3] = inquiry_vpd_b2(&arr[4]);
...@@ -2305,7 +2317,7 @@ static int resp_mode_sense(struct scsi_cmnd *scp, ...@@ -2305,7 +2317,7 @@ static int resp_mode_sense(struct scsi_cmnd *scp,
msense_6 = (MODE_SENSE == cmd[0]); msense_6 = (MODE_SENSE == cmd[0]);
llbaa = msense_6 ? false : !!(cmd[1] & 0x10); llbaa = msense_6 ? false : !!(cmd[1] & 0x10);
is_disk = (sdebug_ptype == TYPE_DISK); is_disk = (sdebug_ptype == TYPE_DISK);
is_zbc = (sdebug_ptype == TYPE_ZBC); is_zbc = (devip->zmodel != BLK_ZONED_NONE);
if ((is_disk || is_zbc) && !dbd) if ((is_disk || is_zbc) && !dbd)
bd_len = llbaa ? 16 : 8; bd_len = llbaa ? 16 : 8;
else else
...@@ -2656,7 +2668,7 @@ static struct sdeb_zone_state *zbc_zone(struct sdebug_dev_info *devip, ...@@ -2656,7 +2668,7 @@ static struct sdeb_zone_state *zbc_zone(struct sdebug_dev_info *devip,
static inline bool zbc_zone_is_conv(struct sdeb_zone_state *zsp) static inline bool zbc_zone_is_conv(struct sdeb_zone_state *zsp)
{ {
return zsp->z_cond == ZBC_NOT_WRITE_POINTER; return zsp->z_type == ZBC_ZONE_TYPE_CNV;
} }
static void zbc_close_zone(struct sdebug_dev_info *devip, static void zbc_close_zone(struct sdebug_dev_info *devip,
...@@ -2732,13 +2744,42 @@ static void zbc_inc_wp(struct sdebug_dev_info *devip, ...@@ -2732,13 +2744,42 @@ static void zbc_inc_wp(struct sdebug_dev_info *devip,
unsigned long long lba, unsigned int num) unsigned long long lba, unsigned int num)
{ {
struct sdeb_zone_state *zsp = zbc_zone(devip, lba); struct sdeb_zone_state *zsp = zbc_zone(devip, lba);
unsigned long long n, end, zend = zsp->z_start + zsp->z_size;
if (zbc_zone_is_conv(zsp)) if (zbc_zone_is_conv(zsp))
return; return;
zsp->z_wp += num; if (zsp->z_type == ZBC_ZONE_TYPE_SWR) {
if (zsp->z_wp >= zsp->z_start + zsp->z_size) zsp->z_wp += num;
zsp->z_cond = ZC5_FULL; if (zsp->z_wp >= zend)
zsp->z_cond = ZC5_FULL;
return;
}
while (num) {
if (lba != zsp->z_wp)
zsp->z_non_seq_resource = true;
end = lba + num;
if (end >= zend) {
n = zend - lba;
zsp->z_wp = zend;
} else if (end > zsp->z_wp) {
n = num;
zsp->z_wp = end;
} else {
n = num;
}
if (zsp->z_wp >= zend)
zsp->z_cond = ZC5_FULL;
num -= n;
lba += n;
if (num) {
zsp++;
zend = zsp->z_start + zsp->z_size;
}
}
} }
static int check_zbc_access_params(struct scsi_cmnd *scp, static int check_zbc_access_params(struct scsi_cmnd *scp,
...@@ -2750,7 +2791,9 @@ static int check_zbc_access_params(struct scsi_cmnd *scp, ...@@ -2750,7 +2791,9 @@ static int check_zbc_access_params(struct scsi_cmnd *scp,
struct sdeb_zone_state *zsp_end = zbc_zone(devip, lba + num - 1); struct sdeb_zone_state *zsp_end = zbc_zone(devip, lba + num - 1);
if (!write) { if (!write) {
/* Reads cannot cross zone types boundaries */ if (devip->zmodel == BLK_ZONED_HA)
return 0;
/* For host-managed, reads cannot cross zone types boundaries */
if (zsp_end != zsp && if (zsp_end != zsp &&
zbc_zone_is_conv(zsp) && zbc_zone_is_conv(zsp) &&
!zbc_zone_is_conv(zsp_end)) { !zbc_zone_is_conv(zsp_end)) {
...@@ -2773,25 +2816,27 @@ static int check_zbc_access_params(struct scsi_cmnd *scp, ...@@ -2773,25 +2816,27 @@ static int check_zbc_access_params(struct scsi_cmnd *scp,
return 0; return 0;
} }
/* Writes cannot cross sequential zone boundaries */ if (zsp->z_type == ZBC_ZONE_TYPE_SWR) {
if (zsp_end != zsp) { /* Writes cannot cross sequential zone boundaries */
mk_sense_buffer(scp, ILLEGAL_REQUEST, if (zsp_end != zsp) {
LBA_OUT_OF_RANGE, mk_sense_buffer(scp, ILLEGAL_REQUEST,
WRITE_BOUNDARY_ASCQ); LBA_OUT_OF_RANGE,
return check_condition_result; WRITE_BOUNDARY_ASCQ);
} return check_condition_result;
/* Cannot write full zones */ }
if (zsp->z_cond == ZC5_FULL) { /* Cannot write full zones */
mk_sense_buffer(scp, ILLEGAL_REQUEST, if (zsp->z_cond == ZC5_FULL) {
INVALID_FIELD_IN_CDB, 0); mk_sense_buffer(scp, ILLEGAL_REQUEST,
return check_condition_result; INVALID_FIELD_IN_CDB, 0);
} return check_condition_result;
/* Writes must be aligned to the zone WP */ }
if (lba != zsp->z_wp) { /* Writes must be aligned to the zone WP */
mk_sense_buffer(scp, ILLEGAL_REQUEST, if (lba != zsp->z_wp) {
LBA_OUT_OF_RANGE, mk_sense_buffer(scp, ILLEGAL_REQUEST,
UNALIGNED_WRITE_ASCQ); LBA_OUT_OF_RANGE,
return check_condition_result; UNALIGNED_WRITE_ASCQ);
return check_condition_result;
}
} }
/* Handle implicit open of closed and empty zones */ /* Handle implicit open of closed and empty zones */
...@@ -4312,13 +4357,16 @@ static int resp_report_zones(struct scsi_cmnd *scp, ...@@ -4312,13 +4357,16 @@ static int resp_report_zones(struct scsi_cmnd *scp,
case 0x06: case 0x06:
case 0x07: case 0x07:
case 0x10: case 0x10:
case 0x11:
/* /*
* Read-only, offline, reset WP recommended and * Read-only, offline, reset WP recommended are
* non-seq-resource-used are not emulated: no zones * not emulated: no zones to report;
* to report;
*/ */
continue; continue;
case 0x11:
/* non-seq-resource set */
if (!zsp->z_non_seq_resource)
continue;
break;
case 0x3f: case 0x3f:
/* Not write pointer (conventional) zones */ /* Not write pointer (conventional) zones */
if (!zbc_zone_is_conv(zsp)) if (!zbc_zone_is_conv(zsp))
...@@ -4333,11 +4381,10 @@ static int resp_report_zones(struct scsi_cmnd *scp, ...@@ -4333,11 +4381,10 @@ static int resp_report_zones(struct scsi_cmnd *scp,
if (nrz < rep_max_zones) { if (nrz < rep_max_zones) {
/* Fill zone descriptor */ /* Fill zone descriptor */
if (zbc_zone_is_conv(zsp)) desc[0] = zsp->z_type;
desc[0] = 0x1;
else
desc[0] = 0x2;
desc[1] = zsp->z_cond << 4; desc[1] = zsp->z_cond << 4;
if (zsp->z_non_seq_resource)
desc[1] |= 1 << 1;
put_unaligned_be64((u64)zsp->z_size, desc + 8); put_unaligned_be64((u64)zsp->z_size, desc + 8);
put_unaligned_be64((u64)zsp->z_start, desc + 16); put_unaligned_be64((u64)zsp->z_start, desc + 16);
put_unaligned_be64((u64)zsp->z_wp, desc + 24); put_unaligned_be64((u64)zsp->z_wp, desc + 24);
...@@ -4591,6 +4638,7 @@ static void zbc_rwp_zone(struct sdebug_dev_info *devip, ...@@ -4591,6 +4638,7 @@ static void zbc_rwp_zone(struct sdebug_dev_info *devip,
if (zsp->z_cond == ZC4_CLOSED) if (zsp->z_cond == ZC4_CLOSED)
devip->nr_closed--; devip->nr_closed--;
zsp->z_non_seq_resource = false;
zsp->z_wp = zsp->z_start; zsp->z_wp = zsp->z_start;
zsp->z_cond = ZC1_EMPTY; zsp->z_cond = ZC1_EMPTY;
} }
...@@ -4796,11 +4844,13 @@ static int sdebug_device_create_zones(struct sdebug_dev_info *devip) ...@@ -4796,11 +4844,13 @@ static int sdebug_device_create_zones(struct sdebug_dev_info *devip)
} }
devip->nr_conv_zones = sdeb_zbc_nr_conv; devip->nr_conv_zones = sdeb_zbc_nr_conv;
/* zbc_max_open_zones can be 0, meaning "not reported" (no limit) */ if (devip->zmodel == BLK_ZONED_HM) {
if (sdeb_zbc_max_open >= devip->nr_zones - 1) /* zbc_max_open_zones can be 0, meaning "not reported" */
devip->max_open = (devip->nr_zones - 1) / 2; if (sdeb_zbc_max_open >= devip->nr_zones - 1)
else devip->max_open = (devip->nr_zones - 1) / 2;
devip->max_open = sdeb_zbc_max_open; else
devip->max_open = sdeb_zbc_max_open;
}
devip->zstate = kcalloc(devip->nr_zones, devip->zstate = kcalloc(devip->nr_zones,
sizeof(struct sdeb_zone_state), GFP_KERNEL); sizeof(struct sdeb_zone_state), GFP_KERNEL);
...@@ -4813,9 +4863,14 @@ static int sdebug_device_create_zones(struct sdebug_dev_info *devip) ...@@ -4813,9 +4863,14 @@ static int sdebug_device_create_zones(struct sdebug_dev_info *devip)
zsp->z_start = zstart; zsp->z_start = zstart;
if (i < devip->nr_conv_zones) { if (i < devip->nr_conv_zones) {
zsp->z_type = ZBC_ZONE_TYPE_CNV;
zsp->z_cond = ZBC_NOT_WRITE_POINTER; zsp->z_cond = ZBC_NOT_WRITE_POINTER;
zsp->z_wp = (sector_t)-1; zsp->z_wp = (sector_t)-1;
} else { } else {
if (devip->zmodel == BLK_ZONED_HM)
zsp->z_type = ZBC_ZONE_TYPE_SWR;
else
zsp->z_type = ZBC_ZONE_TYPE_SWP;
zsp->z_cond = ZC1_EMPTY; zsp->z_cond = ZC1_EMPTY;
zsp->z_wp = zsp->z_start; zsp->z_wp = zsp->z_start;
} }
...@@ -4851,10 +4906,13 @@ static struct sdebug_dev_info *sdebug_device_create( ...@@ -4851,10 +4906,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 (sdeb_zbc_in_use) {
devip->zmodel = sdeb_zbc_model;
if (sdebug_device_create_zones(devip)) { if (sdebug_device_create_zones(devip)) {
kfree(devip); kfree(devip);
return NULL; return NULL;
} }
} else {
devip->zmodel = BLK_ZONED_NONE;
} }
devip->sdbg_host = sdbg_host; 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);
...@@ -6564,12 +6622,12 @@ static int __init scsi_debug_init(void) ...@@ -6564,12 +6622,12 @@ static int __init scsi_debug_init(void)
sdeb_zbc_model = k; sdeb_zbc_model = k;
switch (sdeb_zbc_model) { switch (sdeb_zbc_model) {
case BLK_ZONED_NONE: case BLK_ZONED_NONE:
case BLK_ZONED_HA:
sdebug_ptype = TYPE_DISK; sdebug_ptype = TYPE_DISK;
break; break;
case BLK_ZONED_HM: case BLK_ZONED_HM:
sdebug_ptype = TYPE_ZBC; sdebug_ptype = TYPE_ZBC;
break; break;
case BLK_ZONED_HA:
default: default:
pr_err("Invalid ZBC model\n"); pr_err("Invalid ZBC model\n");
return -EINVAL; return -EINVAL;
......
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