diff mbox series

[v3,08/15] scsi_debug: add zone commands

Message ID 20200220200838.15809-9-dgilbert@interlog.com (mailing list archive)
State Superseded
Headers show
Series host managed ZBC + doublestore | expand

Commit Message

Douglas Gilbert Feb. 20, 2020, 8:08 p.m. UTC
Add support for the 5 ZBC commands and enough functionality to emulate
a host-managed device with one conventional and a set of sequential
write required zones up to the disk capacity.

Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com>
---
 drivers/scsi/scsi_debug.c | 835 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 793 insertions(+), 42 deletions(-)

Comments

Johannes Thumshirn Feb. 21, 2020, 9:44 a.m. UTC | #1
On 20/02/2020 21:09, Douglas Gilbert wrote:
> +enum sdebug_z_cond {	/* enumeration names taken from table 12, zbc2r04 */
> +	zbc_not_write_pointer = 0x0, /* not in table 12; conventional zone */
> +	zc1_empty = 0x1,
> +	zc2_implicit_open,
> +	zc3_explicit_open,
> +	zc4_closed,
> +	zc5_full = 0xe,	/* values per Zone Condition field in Report Zones */
> +	zc6_read_only = 0xd,
> +	zc7_offline = 0xf,
> +};

Haven't checked the rest of scsi_debug, but I'd write these enum values 
in all caps. Furthermore I'd just call them EMPTY, OPEN, READ_ONLY, 
FULL, OFFLINE, etc..
Douglas Gilbert Feb. 23, 2020, 5:59 a.m. UTC | #2
On 2020-02-21 4:44 a.m., Johannes Thumshirn wrote:
> On 20/02/2020 21:09, Douglas Gilbert wrote:
>> +enum sdebug_z_cond {	/* enumeration names taken from table 12, zbc2r04 */
>> +	zbc_not_write_pointer = 0x0, /* not in table 12; conventional zone */
>> +	zc1_empty = 0x1,
>> +	zc2_implicit_open,
>> +	zc3_explicit_open,
>> +	zc4_closed,
>> +	zc5_full = 0xe,	/* values per Zone Condition field in Report Zones */
>> +	zc6_read_only = 0xd,
>> +	zc7_offline = 0xf,
>> +};
> 
> Haven't checked the rest of scsi_debug, but I'd write these enum values
> in all caps. Furthermore I'd just call them EMPTY, OPEN, READ_ONLY,
> FULL, OFFLINE, etc..

Yes, they should be in upper case. The ZC1, ZC2, etc are the names of
states in these T10 documents: zbc-r05.pdf and zbc2r04a.pdf . As for
the final suggestion, the C compiler thinks otherwise, so the
enumeration constants need something that is relatively unique, hence
the chosen scheme.

ZC6 and ZC7 are being dropped as they only appear in ZBC-2 which is still
at the draft level while we are trying to implement the original (and
currently only) ZBC standard.

Doug Gilbert
diff mbox series

Patch

diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c
index 5a720d2a14c4..10f8698031aa 100644
--- a/drivers/scsi/scsi_debug.c
+++ b/drivers/scsi/scsi_debug.c
@@ -91,6 +91,11 @@  static const char *sdebug_version_date = "20190125";
 #define MICROCODE_CHANGED_ASCQ 0x1	/* with TARGET_CHANGED_ASC */
 #define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
 #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) */
 #define ACK_NAK_TO 0x3
@@ -144,6 +149,10 @@  static const char *sdebug_version_date = "20190125";
 #define DEF_UUID_CTL 0
 #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
 
 /* bit mask values for sdebug_opts */
@@ -245,6 +254,23 @@  static const char *sdebug_version_date = "20190125";
 
 #define SDEBUG_MAX_CMD_LEN 32
 
+enum sdebug_z_cond {	/* enumeration names taken from table 12, zbc2r04 */
+	zbc_not_write_pointer = 0x0, /* not in table 12; conventional zone */
+	zc1_empty = 0x1,
+	zc2_implicit_open,
+	zc3_explicit_open,
+	zc4_closed,
+	zc5_full = 0xe,	/* values per Zone Condition field in Report Zones */
+	zc6_read_only = 0xd,
+	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 list_head dev_list;
@@ -258,6 +284,16 @@  struct sdebug_dev_info {
 	atomic_t stopped;
 	int sdg_devnum;
 	bool used;
+
+	/* For ZBC devices */
+	sector_t zsize;
+	sector_t 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 {
@@ -356,10 +392,11 @@  enum sdeb_opcode_index {
 	SDEB_I_SYNC_CACHE = 27,		/* 10, 16 */
 	SDEB_I_COMP_WRITE = 28,
 	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) */
 };
 
-
 static const unsigned char opcode_ind_arr[256] = {
 /* 0x0; 0x0->0x1f: 6 byte cdbs */
 	SDEB_I_TEST_UNIT_READY, SDEB_I_REZERO_UNIT, 0, SDEB_I_REQUEST_SENSE,
@@ -388,7 +425,8 @@  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, 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,
 /* 0xa0; 0xa0->0xbf: 12 byte cdbs */
 	SDEB_I_REPORT_LUNS, SDEB_I_ATA_PT, 0, SDEB_I_MAINT_IN,
@@ -436,6 +474,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_sync_cache(struct scsi_cmnd *, struct sdebug_dev_info *);
 static int resp_pre_fetch(struct scsi_cmnd *, struct sdebug_dev_info *);
+static int resp_rep_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 *);
 
 /*
  * The following are overflow arrays for cdbs that "hit" the same index in
@@ -523,16 +566,34 @@  static const struct opcode_info_t release_iarr[] = {
 
 static const struct opcode_info_t sync_cache_iarr[] = {
 	{0, 0x91, 0, F_SYNC_DELAY | F_M_ACCESS, resp_sync_cache, NULL,
-	    {16,  0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	    {16, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
 	     0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} },	/* SYNC_CACHE (16) */
 };
 
 static const struct opcode_info_t pre_fetch_iarr[] = {
 	{0, 0x90, 0, F_SYNC_DELAY | F_M_ACCESS, resp_pre_fetch, NULL,
-	    {16,  0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	    {16, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
 	     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 REALMS */
+};
+
 
 /* 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
@@ -635,6 +696,15 @@  static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEM_P1 + 1] = {
 	     0, 0, 0, 0} },			/* PRE-FETCH (10) */
 
 /* 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_rep_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 */
 	    {0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
 };
@@ -720,6 +790,11 @@  static int dix_writes;
 static int dix_reads;
 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 struct sdebug_queue *sdebug_q_arr;  /* ptr to array of submit queues */
 
@@ -1417,20 +1492,22 @@  static int inquiry_vpd_b2(unsigned char *arr)
 }
 
 /* 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);
 	arr[0] = 0x1; /* set URSWRZ (unrestricted read in seq. wr req zone) */
 	/*
 	 * Set Optimal number of open sequential write preferred zones and
 	 * Optimal number of non-sequentially written sequential write
-	 * preferred zones and Maximum number of open sequential write
-	 * required zones fields to 'not reported' (0xffffffff). Leave other
-	 * fields set to zero.
+	 * preferred zones fields to 'not reported' (0xffffffff). Leave other
+	 * fields set to zero, apart from Max. number of open swrz_s field.
 	 */
 	put_unaligned_be32(0xffffffff, &arr[4]);
 	put_unaligned_be32(0xffffffff, &arr[8]);
-	put_unaligned_be32(0xffffffff, &arr[12]);
+	if (devip->max_open)
+		put_unaligned_be32(devip->max_open, &arr[12]);
+	else
+		put_unaligned_be32(0xffffffff, &arr[12]);
 	return 0x3c;
 }
 
@@ -1550,7 +1627,7 @@  static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 			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(&arr[4]);
+			arr[3] = inquiry_vpd_b6(devip, &arr[4]);
 		} else {
 			mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, -1);
 			kfree(arr);
@@ -2521,9 +2598,109 @@  static int resp_log_sense(struct scsi_cmnd *scp,
 		    min_t(int, len, SDEBUG_MAX_INQ_ARR_SZ));
 }
 
+/* acc_num is modulo 2 as that is how it is used */
+static int scp2acc_num(struct scsi_cmnd *scp)
+{
+	if (sdebug_doublestore) {
+		struct scsi_device *sdp = scp->device;
+		struct sdebug_dev_info *devip =
+				(struct sdebug_dev_info *)sdp->hostdata;
+
+		return devip->sdg_devnum % 2;
+	}
+	return 0;
+}
+
+static inline bool sdebug_dev_is_zoned(struct sdebug_dev_info *devip)
+{
+	return devip->nr_zones != 0;
+}
+static struct sdeb_zone_state *zbc_zone(unsigned long long lba,
+					struct sdebug_dev_info *devip)
+{
+	return &devip->zstate[lba >> devip->zsize_shift];
+}
+
+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 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) {
 		mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
 		return check_condition_result;
@@ -2538,17 +2715,57 @@  static inline int check_device_access_params(struct scsi_cmnd *scp,
 		mk_sense_buffer(scp, DATA_PROTECT, WRITE_PROTECTED, 0x2);
 		return check_condition_result;
 	}
-	return 0;
-}
 
-static int scp2acc_num(struct scsi_cmnd *scp)
-{
-	if (sdebug_doublestore) {
-		struct scsi_device *sdp = scp->device;
-		struct sdebug_dev_info *devip =
-				(struct sdebug_dev_info *)sdp->hostdata;
+	if (sdebug_dev_is_zoned(devip)) {
+		struct sdeb_zone_state *zsp = zbc_zone(lba, devip);
+		struct sdeb_zone_state *zsp_end =
+			zbc_zone(lba + num - 1, devip);
 
-		return devip->sdg_devnum;
+		if (write) {
+			/* No restrictions for writes within conv zones */
+			if (zbc_zone_is_conv(zsp)) {
+				if (zbc_zone_is_conv(zsp_end))
+					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);
+			}
+		} else if (zsp_end != zsp &&
+			   zbc_zone_is_conv(zsp) &&
+			   !zbc_zone_is_conv(zsp_end)) {
+			/* Reads cannot cross zone types boundaries */
+			mk_sense_buffer(scp, ILLEGAL_REQUEST,
+					LBA_OUT_OF_RANGE, READ_INVDATA_ASCQ);
+			return check_condition_result;
+		}
 	}
 	return 0;
 }
@@ -2574,7 +2791,7 @@  static int do_device_access(struct scsi_cmnd *scmd, u32 sg_skip, u64 lba,
 		return 0;
 	if (scmd->sc_data_direction != dir)
 		return -1;
-	fsp = fake_store_a[scp2acc_num(scmd) % 2];
+	fsp = fake_store_a[scp2acc_num(scmd)];
 
 	block = do_div(lba, sdebug_store_sectors);
 	if (block + num > sdebug_store_sectors)
@@ -2625,7 +2842,7 @@  static bool comp_write_worker(u64 lba, u32 num, const u8 *arr, int acc_num,
 	if (block + num > store_blks)
 		rest = block + num - store_blks;
 
-	fsp = fake_store_a[acc_num % 2];
+	fsp = fake_store_a[acc_num];
 
 	res = !memcmp(fsp + (block * lb_size), arr, (num - rest) * lb_size);
 	if (!res)
@@ -2847,21 +3064,21 @@  static int resp_read_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 		return check_condition_result;
 	}
 
-	read_lock(ramdisk_lck_a[acc_num % 2]);
+	read_lock(ramdisk_lck_a[acc_num]);
 
 	/* DIX + T10 DIF */
 	if (unlikely(sdebug_dix && scsi_prot_sg_count(scp))) {
 		int prot_ret = prot_verify_read(scp, lba, num, ei_lba);
 
 		if (prot_ret) {
-			read_unlock(ramdisk_lck_a[acc_num % 2]);
+			read_unlock(ramdisk_lck_a[acc_num]);
 			mk_sense_buffer(scp, ABORTED_COMMAND, 0x10, prot_ret);
 			return illegal_condition_result;
 		}
 	}
 
 	ret = do_device_access(scp, 0, lba, num, false);
-	read_unlock(ramdisk_lck_a[acc_num % 2]);
+	read_unlock(ramdisk_lck_a[acc_num]);
 	if (unlikely(ret == -1))
 		return DID_ERROR << 16;
 
@@ -3036,7 +3253,7 @@  static void map_region(sector_t lba, unsigned int len)
 static void unmap_region(sector_t lba, unsigned int len, int acc_num)
 {
 	sector_t end = lba + len;
-	u8 *fsp = fake_store_a[acc_num % 2];
+	u8 *fsp = fake_store_a[acc_num];
 
 	while (lba < end) {
 		unsigned long index = lba_to_map_index(lba);
@@ -3125,14 +3342,14 @@  static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 	ret = check_device_access_params(scp, lba, num, true);
 	if (ret)
 		return ret;
-	write_lock(ramdisk_lck_a[acc_num % 2]);
+	write_lock(ramdisk_lck_a[acc_num]);
 
 	/* 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) {
-			write_unlock(ramdisk_lck_a[acc_num % 2]);
+			write_unlock(ramdisk_lck_a[acc_num]);
 			mk_sense_buffer(scp, ILLEGAL_REQUEST, 0x10, prot_ret);
 			return illegal_condition_result;
 		}
@@ -3141,7 +3358,17 @@  static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 	ret = do_device_access(scp, 0, lba, num, true);
 	if (unlikely(scsi_debug_lbp()))
 		map_region(lba, num);
-	write_unlock(ramdisk_lck_a[acc_num % 2]);
+	/* If ZBC zone then bump its write pointer */
+	if (sdebug_dev_is_zoned(devip)) {
+		struct sdeb_zone_state *zsp = zbc_zone(lba, devip);
+
+		if (!zbc_zone_is_conv(zsp)) {
+			zsp->z_wp += num;
+			if (zsp->z_wp >= zsp->z_start + zsp->z_size)
+				zsp->z_cond = zc5_full;
+		}
+	}
+	write_unlock(ramdisk_lck_a[acc_num]);
 	if (unlikely(-1 == ret))
 		return DID_ERROR << 16;
 	else if (unlikely(sdebug_verbose &&
@@ -3250,7 +3477,7 @@  static int resp_write_scat(struct scsi_cmnd *scp,
 		goto err_out;
 	}
 
-	write_lock(ramdisk_lck_a[acc_num % 2]);
+	write_lock(ramdisk_lck_a[acc_num]);
 	sg_off = lbdof_blen;
 	/* Spec says Buffer xfer Length field in number of LBs in dout */
 	cum_lb = 0;
@@ -3294,6 +3521,16 @@  static int resp_write_scat(struct scsi_cmnd *scp,
 		}
 
 		ret = do_device_access(scp, sg_off, lba, num, true);
+		/* If ZBC zone then bump its write pointer */
+		if (sdebug_dev_is_zoned(devip)) {
+			struct sdeb_zone_state *zsp = zbc_zone(lba, devip);
+
+			if (!zbc_zone_is_conv(zsp)) {
+				zsp->z_wp += num;
+				if (zsp->z_wp >= zsp->z_start + zsp->z_size)
+					zsp->z_cond = zc5_full;
+			}
+		}
 		if (unlikely(scsi_debug_lbp()))
 			map_region(lba, num);
 		if (unlikely(-1 == ret)) {
@@ -3333,7 +3570,7 @@  static int resp_write_scat(struct scsi_cmnd *scp,
 	}
 	ret = 0;
 err_out_unlock:
-	write_unlock(ramdisk_lck_a[acc_num % 2]);
+	write_unlock(ramdisk_lck_a[acc_num]);
 err_out:
 	kfree(lrdp);
 	return ret;
@@ -3342,6 +3579,8 @@  static int resp_write_scat(struct scsi_cmnd *scp,
 static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 			   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;
 	u64 block, lbaa;
 	u32 lb_size = sdebug_sector_size;
@@ -3354,7 +3593,7 @@  static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 	if (ret)
 		return ret;
 
-	write_lock(ramdisk_lck_a[acc_num % 2]);
+	write_lock(ramdisk_lck_a[acc_num]);
 
 	if (unmap && scsi_debug_lbp()) {
 		unmap_region(lba, num, acc_num);
@@ -3363,7 +3602,7 @@  static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 	lbaa = lba;
 	block = do_div(lbaa, sdebug_store_sectors);
 	/* if ndob then zero 1 logical block, else fetch 1 logical block */
-	fsp = fake_store_a[acc_num % 2];
+	fsp = fake_store_a[acc_num];
 	fs1p = fsp + (block * lb_size);
 	if (ndob) {
 		memset(fs1p, 0, lb_size);
@@ -3372,7 +3611,7 @@  static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 		ret = fetch_to_dev_buffer(scp, fs1p, lb_size);
 
 	if (-1 == ret) {
-		write_unlock(ramdisk_lck_a[acc_num % 2]);
+		write_unlock(ramdisk_lck_a[acc_num]);
 		return DID_ERROR << 16;
 	} else if (sdebug_verbose && !ndob && (ret < lb_size))
 		sdev_printk(KERN_INFO, scp->device,
@@ -3387,8 +3626,18 @@  static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,
 	}
 	if (scsi_debug_lbp())
 		map_region(lba, num);
+	/* If ZBC zone then bump its write pointer */
+	if (sdebug_dev_is_zoned(devip)) {
+		struct sdeb_zone_state *zsp = zbc_zone(lba, devip);
+
+		if (!zbc_zone_is_conv(zsp)) {
+			zsp->z_wp += num;
+			if (zsp->z_wp >= zsp->z_start + zsp->z_size)
+				zsp->z_cond = zc5_full;
+		}
+	}
 out:
-	write_unlock(ramdisk_lck_a[acc_num % 2]);
+	write_unlock(ramdisk_lck_a[acc_num]);
 
 	return 0;
 }
@@ -3533,7 +3782,7 @@  static int resp_comp_write(struct scsi_cmnd *scp,
 		return check_condition_result;
 	}
 
-	write_lock(ramdisk_lck_a[acc_num % 2]);
+	write_lock(ramdisk_lck_a[acc_num]);
 
 	ret = do_dout_fetch(scp, dnum, arr);
 	if (ret == -1) {
@@ -3551,7 +3800,7 @@  static int resp_comp_write(struct scsi_cmnd *scp,
 	if (scsi_debug_lbp())
 		map_region(lba, num);
 cleanup:
-	write_unlock(ramdisk_lck_a[acc_num % 2]);
+	write_unlock(ramdisk_lck_a[acc_num]);
 	kfree(arr);
 	return retval;
 }
@@ -3595,7 +3844,7 @@  static int resp_unmap(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 
 	desc = (void *)&buf[8];
 
-	write_lock(ramdisk_lck_a[acc_num % 2]);
+	write_lock(ramdisk_lck_a[acc_num]);
 
 	for (i = 0 ; i < descriptors ; i++) {
 		unsigned long long lba = get_unaligned_be64(&desc[i].lba);
@@ -3611,7 +3860,7 @@  static int resp_unmap(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 	ret = 0;
 
 out:
-	write_unlock(ramdisk_lck_a[acc_num % 2]);
+	write_unlock(ramdisk_lck_a[acc_num]);
 	kfree(buf);
 
 	return ret;
@@ -3863,7 +4112,7 @@  static int resp_verify(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 		return check_condition_result;
 	}
 	/* Not changing store, so only need read access */
-	read_lock(ramdisk_lck_a[acc_num % 2]);
+	read_lock(ramdisk_lck_a[acc_num]);
 
 	ret = do_dout_fetch(scp, a_num, arr);
 	if (ret == -1) {
@@ -3885,11 +4134,428 @@  static int resp_verify(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
 		goto cleanup;
 	}
 cleanup:
-	read_unlock(ramdisk_lck_a[acc_num % 2]);
+	read_unlock(ramdisk_lck_a[acc_num]);
+	kfree(arr);
+	return ret;
+}
+
+#define RZONES_DESC_HD 64
+
+/*
+ * Report two zones, the first: conventional; the second: sequential write
+ * required. The available storage is divided in two for these zones.
+ */
+static int resp_rep_zones(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)
+{
+	unsigned int i, max_zones, rep_max_zones, nrz = 0;
+	int ret = 0;
+	int acc_num = scp2acc_num(scp);
+	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;
+
+	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;
+
+	lba = sdebug_capacity;
+	if (zs_lba >= lba) {
+		mk_sense_buffer(scp, ILLEGAL_REQUEST, LBA_OUT_OF_RANGE, 0);
+		ret = check_condition_result;
+		goto fini;
+	}
+
+	max_zones = devip->nr_zones - (zs_lba >> devip->zsize_shift);
+        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(ramdisk_lck_a[acc_num]);
+
+	desc = arr + 64;
+	for (i = 0; i < max_zones; i++) {
+
+		zsp = zbc_zone(zs_lba + ((sector_t)i << devip->zsize_shift),
+			       devip);
+		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;
+			read_unlock(ramdisk_lck_a[acc_num]);
+			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++;
+	}
+	read_unlock(ramdisk_lck_a[acc_num]);
+
+	/* Report header */
+	put_unaligned_be32(nrz * RZONES_DESC_HD, arr + 0);
+	put_unaligned_be64(lba - 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:
 	kfree(arr);
 	return ret;
 }
 
+/* Logic transplanted from tcmu-runner, file_zbc.c __zbc_close_zone() */
+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 acc_num = scp2acc_num(scp);
+	int res = 0;
+	u64 z_id;
+	u8 *cmd = scp->cmnd;
+	struct sdeb_zone_state *zsp;
+	enum sdebug_z_cond zc;
+	bool all = cmd[14] & 0x01;
+
+	if (!sdebug_dev_is_zoned(devip)) {
+		mk_sense_invalid_opcode(scp);
+		return check_condition_result;
+	}
+
+	write_lock(ramdisk_lck_a[acc_num]);
+	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(z_id, devip);
+	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(ramdisk_lck_a[acc_num]);
+	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 acc_num = scp2acc_num(scp);
+	int res = 0;
+	u64 z_id;
+	u8 *cmd = scp->cmnd;
+	struct sdeb_zone_state *zsp;
+	bool all = cmd[14] & 0x01;
+
+	if (!sdebug_dev_is_zoned(devip)) {
+		mk_sense_invalid_opcode(scp);
+		return check_condition_result;
+	}
+
+	write_lock(ramdisk_lck_a[acc_num]);
+
+	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(z_id, devip);
+	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(ramdisk_lck_a[acc_num]);
+	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)
+{
+	int acc_num = scp2acc_num(scp);
+	struct sdeb_zone_state *zsp;
+	int res = 0;
+	u64 z_id;
+	u8 *cmd = scp->cmnd;
+	bool all = cmd[14] & 0x01;
+
+	if (!sdebug_dev_is_zoned(devip)) {
+		mk_sense_invalid_opcode(scp);
+		return check_condition_result;
+	}
+
+	write_lock(ramdisk_lck_a[acc_num]);
+	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(z_id, devip);
+	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(ramdisk_lck_a[acc_num]);
+	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)
+{
+	int acc_num = scp2acc_num(scp);
+	struct sdeb_zone_state *zsp;
+	int res = 0;
+	u64 z_id;
+	u8 *cmd = scp->cmnd;
+	bool all = cmd[14] & 0x01;
+
+	if (!sdebug_dev_is_zoned(devip)) {
+		mk_sense_invalid_opcode(scp);
+		return check_condition_result;
+	}
+
+	write_lock(ramdisk_lck_a[acc_num]);
+	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(z_id, devip);
+	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(ramdisk_lck_a[acc_num]);
+	return res;
+}
+
 static struct sdebug_queue *get_queue(struct scsi_cmnd *cmnd)
 {
 	u32 tag = blk_mq_unique_tag(cmnd->request);
@@ -3995,6 +4661,73 @@  static void sdebug_q_cmd_wq_complete(struct work_struct *work)
 static bool got_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 zone size: if zbc_zone_size_mb was not set, figure out a zone
+	 * size allowing for at least 4 zones on the device.
+	 */
+	if (!zbc_zone_size_mb) {
+		devip->zsize = (DEF_ZBC_ZONE_SIZE_MB * SZ_1M)
+			>> ilog2(sdebug_sector_size);
+		while (capacity < devip->zsize * 4 && 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;
+		}
+	}
+
+	devip->zsize_shift = ilog2(devip->zsize);
+	devip->nr_zones = (capacity + devip->zsize - 1) >> devip->zsize_shift;
+
+	/* zbc_max_open_zones can be 0, meaning "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(sizeof(struct sdeb_zone_state),
+				devip->nr_zones, 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(
 			struct sdebug_host_info *sdbg_host, gfp_t flags)
 {
@@ -4014,6 +4747,13 @@  static struct sdebug_dev_info *sdebug_device_create(
 			}
 		}
 		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);
 	}
 	return devip;
@@ -4968,10 +5708,12 @@  static ssize_t ptype_show(struct device_driver *ddp, char *buf)
 static ssize_t ptype_store(struct device_driver *ddp, const char *buf,
 			   size_t count)
 {
-	int n;
+	int n, prev_pdt;
 
 	if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) {
+		prev_pdt = sdebug_ptype;
 		sdebug_ptype = n;
+		sdeb_zbc_in_use = (sdebug_ptype == TYPE_ZBC);
 		return count;
 	}
 	return -EINVAL;
@@ -5240,6 +5982,10 @@  static ssize_t virtual_gb_store(struct device_driver *ddp, const char *buf,
 	int n;
 	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)) {
 		changed = (sdebug_virtual_gb != n);
 		sdebug_virtual_gb = n;
@@ -5709,6 +6455,9 @@  static int __init scsi_debug_init(void)
 		if (sdebug_num_parts)
 			map_region(0, 2);
 	}
+	/* check for host managed zoned block device [ptype=0x14] */
+	if (sdebug_ptype == TYPE_ZBC)
+		sdeb_zbc_in_use = true;
 
 	pseudo_primary = root_device_register("pseudo_0");
 	if (IS_ERR(pseudo_primary)) {
@@ -5832,6 +6581,7 @@  static int sdebug_add_adapter(void)
 	list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list,
 				 dev_list) {
 		list_del(&sdbg_devinfo->dev_list);
+		kfree(sdbg_devinfo->zstate);
 		kfree(sdbg_devinfo);
 	}
 
@@ -6208,6 +6958,7 @@  static int sdebug_driver_remove(struct device *dev)
 	list_for_each_entry_safe(sdbg_devinfo, tmp, &sdbg_host->dev_info_list,
 				 dev_list) {
 		list_del(&sdbg_devinfo->dev_list);
+		kfree(sdbg_devinfo->zstate);
 		kfree(sdbg_devinfo);
 	}