Commit 7ac802e8 authored by Konrad Sztyber's avatar Konrad Sztyber Committed by Tomasz Zawadzki
Browse files

bdev/ocssd: get zone information command



Added zone information command translating Open Channel's chunk
information to spdk_bdev_zone_info structure.

Change-Id: Ifdb15f2e0ea2fb8422810fa64f18942fcb6e4582
Signed-off-by: default avatarKonrad Sztyber <konrad.sztyber@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/468213


Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Community-CI: Broadcom SPDK FC-NVMe CI <spdk-ci.pdl@broadcom.com>
Community-CI: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarShuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: default avatarWojciech Malikowski <wojciech.malikowski@intel.com>
Reviewed-by: default avatarTomasz Zawadzki <tomasz.zawadzki@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
parent 6e640cfc
Loading
Loading
Loading
Loading
+166 −15
Original line number Diff line number Diff line
@@ -52,11 +52,17 @@ struct bdev_ocssd_lba_offsets {
};

struct bdev_ocssd_io {
	union {
		struct {
			size_t		iov_pos;
			size_t		iov_off;
			uint64_t	lba[SPDK_NVME_OCSSD_MAX_LBAL_ENTRIES];
		} io;
		struct {
			size_t						chunk_offset;
			struct spdk_ocssd_chunk_information_entry	chunk_info;
		} zone_info;
	};
};

struct ocssd_bdev {
@@ -74,6 +80,12 @@ bdev_ocssd_get_ns_from_nvme(struct nvme_bdev_ns *nvme_ns)
	return nvme_ns->type_ctx;
}

static struct bdev_ocssd_ns *
bdev_ocssd_get_ns_from_bdev(struct ocssd_bdev *ocssd_bdev)
{
	return bdev_ocssd_get_ns_from_nvme(ocssd_bdev->nvme_bdev.nvme_ns);
}

static int
bdev_ocssd_library_init(void)
{
@@ -130,14 +142,13 @@ bdev_ocssd_destruct(void *ctx)
	return 0;
}

static uint64_t
bdev_ocssd_to_disk_lba(struct ocssd_bdev *ocssd_bdev, uint64_t lba)
static void
bdev_ocssd_translate_lba(struct ocssd_bdev *ocssd_bdev, uint64_t lba, uint64_t *grp,
			 uint64_t *pu, uint64_t *chk, uint64_t *lbk)
{
	struct nvme_bdev_ns *nvme_ns = ocssd_bdev->nvme_bdev.nvme_ns;
	struct bdev_ocssd_ns *ocssd_ns = bdev_ocssd_get_ns_from_nvme(nvme_ns);
	struct bdev_ocssd_ns *ocssd_ns = bdev_ocssd_get_ns_from_bdev(ocssd_bdev);
	const struct spdk_ocssd_geometry_data *geo = &ocssd_ns->geometry;
	const struct bdev_ocssd_lba_offsets *offsets = &ocssd_ns->lba_offsets;
	uint64_t addr_shift, lbk, chk, pu, grp;
	uint64_t addr_shift;

	/* To achieve best performance, we need to make sure that adjacent zones can be accessed
	 * in parallel.  We accomplish this by having the following addressing scheme:
@@ -147,16 +158,43 @@ bdev_ocssd_to_disk_lba(struct ocssd_bdev *ocssd_bdev, uint64_t lba)
	 *
	 * which means that neighbouring zones are placed in a different group and parallel unit.
	 */
	lbk = lba % geo->clba;
	*lbk = lba % geo->clba;
	addr_shift = geo->clba;

	pu = (lba / addr_shift) % geo->num_pu;
	*pu = (lba / addr_shift) % geo->num_pu;
	addr_shift *= geo->num_pu;

	grp = (lba / addr_shift) % geo->num_grp;
	*grp = (lba / addr_shift) % geo->num_grp;
	addr_shift *= geo->num_grp;

	chk = (lba / addr_shift) % geo->num_chk;
	*chk = (lba / addr_shift) % geo->num_chk;
}

static uint64_t
bdev_ocssd_from_disk_lba(struct ocssd_bdev *ocssd_bdev, uint64_t lba)
{
	struct bdev_ocssd_ns *ocssd_ns = bdev_ocssd_get_ns_from_bdev(ocssd_bdev);
	const struct spdk_ocssd_geometry_data *geometry = &ocssd_ns->geometry;
	const struct bdev_ocssd_lba_offsets *offsets = &ocssd_ns->lba_offsets;
	uint64_t lbk, chk, pu, grp;

	lbk = (lba >> offsets->lbk) & ((1 << geometry->lbaf.lbk_len) - 1);
	chk = (lba >> offsets->chk) & ((1 << geometry->lbaf.chk_len) - 1);
	pu  = (lba >> offsets->pu)  & ((1 << geometry->lbaf.pu_len)  - 1);
	grp = (lba >> offsets->grp) & ((1 << geometry->lbaf.grp_len) - 1);

	return lbk + pu * geometry->clba + grp * geometry->num_pu * geometry->clba +
	       chk * geometry->num_pu * geometry->num_grp * geometry->clba;
}

static uint64_t
bdev_ocssd_to_disk_lba(struct ocssd_bdev *ocssd_bdev, uint64_t lba)
{
	struct bdev_ocssd_ns *ocssd_ns = bdev_ocssd_get_ns_from_bdev(ocssd_bdev);
	const struct bdev_ocssd_lba_offsets *offsets = &ocssd_ns->lba_offsets;
	uint64_t lbk, chk, pu, grp;

	bdev_ocssd_translate_lba(ocssd_bdev, lba, &grp, &pu, &chk, &lbk);

	return (lbk << offsets->lbk) |
	       (chk << offsets->chk) |
@@ -333,6 +371,115 @@ bdev_ocssd_reset_zone(struct spdk_io_channel *ioch, struct spdk_bdev_io *bdev_io
			ocdev_io->io.lba, num_zones, NULL, bdev_ocssd_reset_zone_cb, bdev_io);
}

static int _bdev_ocssd_get_zone_info(struct spdk_bdev_io *bdev_io);

static void
bdev_ocssd_fill_zone_info(struct ocssd_bdev *ocssd_bdev, struct spdk_bdev_zone_info *zone_info,
			  const struct spdk_ocssd_chunk_information_entry *chunk_info)
{
	struct nvme_bdev *nvme_bdev = &ocssd_bdev->nvme_bdev;

	zone_info->zone_id = bdev_ocssd_from_disk_lba(ocssd_bdev, chunk_info->slba);
	zone_info->write_pointer = zone_info->zone_id;

	if (chunk_info->cs.free) {
		zone_info->state = SPDK_BDEV_ZONE_STATE_EMPTY;
	} else if (chunk_info->cs.closed) {
		zone_info->state = SPDK_BDEV_ZONE_STATE_FULL;
	} else if (chunk_info->cs.open) {
		zone_info->state = SPDK_BDEV_ZONE_STATE_OPEN;
		zone_info->write_pointer += chunk_info->wp % nvme_bdev->disk.zone_size;
	} else if (chunk_info->cs.offline) {
		zone_info->state = SPDK_BDEV_ZONE_STATE_OFFLINE;
	} else {
		SPDK_ERRLOG("Unknown chunk state, assuming offline\n");
		zone_info->state = SPDK_BDEV_ZONE_STATE_OFFLINE;
	}

	if (chunk_info->ct.size_deviate) {
		zone_info->capacity = chunk_info->cnlb;
	} else {
		zone_info->capacity = nvme_bdev->disk.zone_size;
	}
}

static void
bdev_ocssd_zone_info_cb(void *ctx, const struct spdk_nvme_cpl *cpl)
{
	struct spdk_bdev_io *bdev_io = ctx;
	struct ocssd_bdev *ocssd_bdev = bdev_io->bdev->ctxt;
	struct bdev_ocssd_io *ocdev_io = (struct bdev_ocssd_io *)bdev_io->driver_ctx;
	struct spdk_ocssd_chunk_information_entry *chunk_info = &ocdev_io->zone_info.chunk_info;
	struct spdk_bdev_zone_info *zone_info;
	int rc;

	if (spdk_unlikely(spdk_nvme_cpl_is_error(cpl))) {
		spdk_bdev_io_complete_nvme_status(bdev_io, 0, cpl->status.sct, cpl->status.sc);
		return;
	}

	zone_info = ((struct spdk_bdev_zone_info *)bdev_io->u.zone_mgmt.buf) +
		    ocdev_io->zone_info.chunk_offset;
	bdev_ocssd_fill_zone_info(ocssd_bdev, zone_info, chunk_info);

	if (++ocdev_io->zone_info.chunk_offset == bdev_io->u.zone_mgmt.num_zones) {
		spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS);
	} else {
		rc = _bdev_ocssd_get_zone_info(bdev_io);
		if (spdk_unlikely(rc != 0)) {
			if (rc == -ENOMEM) {
				spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_NOMEM);
			} else {
				spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
			}
		}
	}
}

static int
_bdev_ocssd_get_zone_info(struct spdk_bdev_io *bdev_io)
{
	struct ocssd_bdev *ocssd_bdev = bdev_io->bdev->ctxt;
	struct nvme_bdev *nvme_bdev = &ocssd_bdev->nvme_bdev;
	struct bdev_ocssd_ns *ocssd_ns = bdev_ocssd_get_ns_from_bdev(ocssd_bdev);
	const struct spdk_ocssd_geometry_data *geo = &ocssd_ns->geometry;
	struct bdev_ocssd_io *ocdev_io = (struct bdev_ocssd_io *)bdev_io->driver_ctx;
	uint64_t lba, grp, pu, chk, lbk, offset;

	lba = bdev_io->u.zone_mgmt.zone_id + ocdev_io->zone_info.chunk_offset *
	      nvme_bdev->disk.zone_size;
	bdev_ocssd_translate_lba(ocssd_bdev, lba, &grp, &pu, &chk, &lbk);
	offset = grp * geo->num_pu * geo->num_chk + pu * geo->num_chk + chk;

	return spdk_nvme_ctrlr_cmd_get_log_page(nvme_bdev->nvme_bdev_ctrlr->ctrlr,
						SPDK_OCSSD_LOG_CHUNK_INFO,
						spdk_nvme_ns_get_id(nvme_bdev->nvme_ns->ns),
						&ocdev_io->zone_info.chunk_info,
						sizeof(ocdev_io->zone_info.chunk_info),
						offset * sizeof(ocdev_io->zone_info.chunk_info),
						bdev_ocssd_zone_info_cb, (void *)bdev_io);
}

static int
bdev_ocssd_get_zone_info(struct spdk_io_channel *ioch, struct spdk_bdev_io *bdev_io)
{
	struct bdev_ocssd_io *ocdev_io = (struct bdev_ocssd_io *)bdev_io->driver_ctx;

	if (bdev_io->u.zone_mgmt.num_zones < 1) {
		SPDK_ERRLOG("Invalid number of zones: %"PRIu32"\n", bdev_io->u.zone_mgmt.num_zones);
		return -EINVAL;
	}

	if (bdev_io->u.zone_mgmt.zone_id % bdev_io->bdev->zone_size != 0) {
		SPDK_ERRLOG("Unaligned zone LBA: %"PRIu64"\n", bdev_io->u.zone_mgmt.zone_id);
		return -EINVAL;
	}

	ocdev_io->zone_info.chunk_offset = 0;

	return _bdev_ocssd_get_zone_info(bdev_io);
}

static int
bdev_ocssd_zone_management(struct spdk_io_channel *ioch, struct spdk_bdev_io *bdev_io)
{
@@ -364,6 +511,10 @@ bdev_ocssd_submit_request(struct spdk_io_channel *ioch, struct spdk_bdev_io *bde
		rc = bdev_ocssd_zone_management(ioch, bdev_io);
		break;

	case SPDK_BDEV_IO_TYPE_GET_ZONE_INFO:
		rc = bdev_ocssd_get_zone_info(ioch, bdev_io);
		break;

	default:
		rc = -EINVAL;
		break;
+253 −3
Original line number Diff line number Diff line
@@ -73,6 +73,8 @@ struct spdk_nvme_ctrlr {
	struct spdk_nvme_qpair *admin_qpair;
	struct spdk_nvme_ns *ns;
	uint32_t ns_count;
	struct spdk_ocssd_chunk_information_entry *chunk_info;
	uint64_t num_chunks;

	LIST_ENTRY(spdk_nvme_ctrlr) list;
};
@@ -101,6 +103,7 @@ free_controller(struct spdk_nvme_ctrlr *ctrlr)
	CU_ASSERT(!nvme_bdev_ctrlr_get(&ctrlr->trid));
	LIST_REMOVE(ctrlr, list);
	spdk_nvme_ctrlr_free_io_qpair(ctrlr->admin_qpair);
	free(ctrlr->chunk_info);
	free(ctrlr->ns);
	free(ctrlr);
}
@@ -120,6 +123,10 @@ create_controller(const struct spdk_nvme_transport_id *trid, uint32_t ns_count,
	ctrlr->ns = calloc(ns_count, sizeof(*ctrlr->ns));
	SPDK_CU_ASSERT_FATAL(ctrlr->ns != NULL);

	ctrlr->num_chunks = geo->num_grp * geo->num_pu * geo->num_chk;
	ctrlr->chunk_info = calloc(ctrlr->num_chunks, sizeof(*ctrlr->chunk_info));
	SPDK_CU_ASSERT_FATAL(ctrlr->chunk_info != NULL);

	for (nsid = 0; nsid < ns_count; ++nsid) {
		ctrlr->ns[nsid].nsid = nsid + 1;
	}
@@ -218,6 +225,12 @@ spdk_nvme_ctrlr_get_num_ns(struct spdk_nvme_ctrlr *ctrlr)
	return ctrlr->ns_count;
}

uint32_t
spdk_nvme_ns_get_id(struct spdk_nvme_ns *ns)
{
	return ns->nsid;
}

struct spdk_nvme_ns *
spdk_nvme_ctrlr_get_ns(struct spdk_nvme_ctrlr *ctrlr, uint32_t nsid)
{
@@ -420,6 +433,24 @@ spdk_nvme_ocssd_ns_cmd_vector_reset(struct spdk_nvme_ns *ns,
	return 0;
}

static struct spdk_nvme_cpl g_chunk_info_cpl;
static bool g_zone_info_status = true;

int
spdk_nvme_ctrlr_cmd_get_log_page(struct spdk_nvme_ctrlr *ctrlr,
				 uint8_t log_page, uint32_t nsid,
				 void *payload, uint32_t payload_size,
				 uint64_t offset,
				 spdk_nvme_cmd_cb cb_fn, void *cb_arg)
{
	SPDK_CU_ASSERT_FATAL(offset + payload_size <= sizeof(*ctrlr->chunk_info) * ctrlr->num_chunks);
	memcpy(payload, ((char *)ctrlr->chunk_info) + offset, payload_size);

	cb_fn(cb_arg, &g_chunk_info_cpl);

	return 0;
}

static void
create_bdev_cb(const char *bdev_name, int status, void *ctx)
{
@@ -629,21 +660,27 @@ test_lba_translation(void)

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, 0);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), 0);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size - 1);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, bdev->zone_size - 1, 0, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size - 1);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, 1, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * geometry.num_pu);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, 0, 1));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size * geometry.num_pu);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * geometry.num_pu + 68);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 68, 0, 0, 1));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size * geometry.num_pu + 68);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size + 68);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 68, 0, 1, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size + 68);

	delete_nvme_bdev_controller(nvme_bdev_ctrlr);
	free_controller(ctrlr);
@@ -673,24 +710,236 @@ test_lba_translation(void)

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, 0);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), 0);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size - 1);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, bdev->zone_size - 1, 0, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size - 1);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, 1, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * (geometry.num_pu - 1));
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 0, geometry.num_pu - 1, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba), bdev->zone_size * (geometry.num_pu - 1));

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * (geometry.num_pu));
	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * geometry.num_pu * geometry.num_grp);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 0, 1, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba),
			bdev->zone_size * geometry.num_pu * geometry.num_grp);

	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * (geometry.num_pu) + 68);
	lba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev->zone_size * geometry.num_pu * geometry.num_grp + 68);
	CU_ASSERT_EQUAL(lba, generate_lba(&geometry, 68, 1, 0, 0));
	CU_ASSERT_EQUAL(bdev_ocssd_from_disk_lba(ocssd_bdev, lba),
			bdev->zone_size * geometry.num_pu * geometry.num_grp + 68);

	delete_nvme_bdev_controller(nvme_bdev_ctrlr);

	free_controller(ctrlr);
}

static void
get_zone_info_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
	CU_ASSERT_EQUAL(g_zone_info_status, success);
}

static uint64_t
generate_chunk_offset(const struct spdk_ocssd_geometry_data *geo, uint64_t chk,
		      uint64_t pu, uint64_t grp)
{
	return grp * geo->num_pu * geo->num_chk +
	       pu * geo->num_chk + chk;
}

static struct spdk_bdev_io *
alloc_ocssd_io(void)
{
	struct spdk_bdev_io *bdev_io;

	bdev_io = calloc(1, sizeof(struct spdk_bdev_io) + sizeof(struct bdev_ocssd_io));
	SPDK_CU_ASSERT_FATAL(bdev_io != NULL);

	return bdev_io;
}

static void
set_chunk_info(struct spdk_nvme_ctrlr *ctrlr, uint64_t offset,
	       const struct spdk_ocssd_chunk_information_entry *chunk_info)
{
	assert(offset < ctrlr->num_chunks);
	SPDK_CU_ASSERT_FATAL(offset < ctrlr->num_chunks);
	ctrlr->chunk_info[offset] = *chunk_info;
}

static void
clear_chunk_info(struct spdk_nvme_ctrlr *ctrlr)
{
	memset(ctrlr->chunk_info, 0, sizeof(*ctrlr->chunk_info) * ctrlr->num_chunks);
}

static void
test_get_zone_info(void)
{
	struct spdk_nvme_ctrlr *ctrlr;
	struct nvme_bdev_ctrlr *nvme_bdev_ctrlr;
	struct spdk_nvme_transport_id trid = { .traddr = "00:00:00" };
	struct ocssd_bdev *ocssd_bdev;
	const char *controller_name = "nvme0";
	const char *bdev_name = "nvme0n1";
	struct spdk_bdev *bdev;
	struct spdk_bdev_io *bdev_io;
#define MAX_ZONE_INFO_COUNT 64
	struct spdk_bdev_zone_info zone_info[MAX_ZONE_INFO_COUNT];
	struct spdk_ocssd_chunk_information_entry chunk_info = {};
	struct spdk_ocssd_geometry_data geometry;
	uint64_t chunk_offset;
	int rc, offset;

	geometry = (struct spdk_ocssd_geometry_data) {
		.clba = 512,
		.num_chk = 64,
		.num_pu = 8,
		.num_grp = 4,
		.lbaf = {
			.lbk_len = 9,
			.chk_len = 6,
			.pu_len = 3,
			.grp_len = 2,
		}
	};

	ctrlr = create_controller(&trid, 1, &geometry);
	nvme_bdev_ctrlr = create_nvme_bdev_controller(&trid, controller_name);

	rc = create_bdev(controller_name, bdev_name, 1);
	CU_ASSERT_EQUAL(rc, 0);

	bdev = spdk_bdev_get_by_name(bdev_name);
	SPDK_CU_ASSERT_FATAL(bdev != NULL);
	ocssd_bdev = SPDK_CONTAINEROF(bdev, struct ocssd_bdev, nvme_bdev.disk);

	bdev_io = alloc_ocssd_io();
	bdev_io->internal.cb = get_zone_info_cb;
	bdev_io->bdev = bdev;

	/* Verify empty zone */
	bdev_io->u.zone_mgmt.zone_id = 0;
	bdev_io->u.zone_mgmt.num_zones = 1;
	bdev_io->u.zone_mgmt.buf = &zone_info;
	chunk_info.cs.free = 1;
	chunk_info.wp = 0;
	set_chunk_info(ctrlr, 0, &chunk_info);

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, 0);
	clear_chunk_info(ctrlr);

	CU_ASSERT_EQUAL(zone_info[0].state, SPDK_BDEV_ZONE_STATE_EMPTY);
	CU_ASSERT_EQUAL(zone_info[0].zone_id, 0);
	CU_ASSERT_EQUAL(zone_info[0].write_pointer, 0);
	CU_ASSERT_EQUAL(zone_info[0].capacity, geometry.clba);

	/* Verify open zone */
	bdev_io->u.zone_mgmt.zone_id = bdev->zone_size;
	bdev_io->u.zone_mgmt.num_zones = 1;
	bdev_io->u.zone_mgmt.buf = &zone_info;
	memset(&chunk_info, 0, sizeof(chunk_info));
	chunk_info.cs.open = 1;
	chunk_info.wp = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev_io->u.zone_mgmt.zone_id + 68);
	chunk_info.slba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev_io->u.zone_mgmt.zone_id);
	chunk_info.cnlb = 511;
	chunk_info.ct.size_deviate = 1;
	set_chunk_info(ctrlr, generate_chunk_offset(&geometry, 0, 1, 0), &chunk_info);

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, 0);
	clear_chunk_info(ctrlr);

	CU_ASSERT_EQUAL(zone_info[0].state, SPDK_BDEV_ZONE_STATE_OPEN);
	CU_ASSERT_EQUAL(zone_info[0].zone_id, bdev->zone_size);
	CU_ASSERT_EQUAL(zone_info[0].write_pointer, bdev->zone_size + 68);
	CU_ASSERT_EQUAL(zone_info[0].capacity, chunk_info.cnlb);

	/* Verify offline zone at 2nd chunk */
	bdev_io->u.zone_mgmt.zone_id = bdev->zone_size * geometry.num_pu * geometry.num_grp;
	bdev_io->u.zone_mgmt.num_zones = 1;
	bdev_io->u.zone_mgmt.buf = &zone_info;
	memset(&chunk_info, 0, sizeof(chunk_info));
	chunk_info.cs.offline = 1;
	chunk_info.wp = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev_io->u.zone_mgmt.zone_id);
	chunk_info.slba = bdev_ocssd_to_disk_lba(ocssd_bdev, bdev_io->u.zone_mgmt.zone_id);
	set_chunk_info(ctrlr, generate_chunk_offset(&geometry, 1, 0, 0), &chunk_info);

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, 0);
	clear_chunk_info(ctrlr);

	CU_ASSERT_EQUAL(zone_info[0].state, SPDK_BDEV_ZONE_STATE_OFFLINE);
	CU_ASSERT_EQUAL(zone_info[0].zone_id, bdev_io->u.zone_mgmt.zone_id);
	CU_ASSERT_EQUAL(zone_info[0].write_pointer, bdev_io->u.zone_mgmt.zone_id);

	/* Verify multiple zones at a time */
	bdev_io->u.zone_mgmt.zone_id = 0;
	bdev_io->u.zone_mgmt.num_zones = MAX_ZONE_INFO_COUNT;
	bdev_io->u.zone_mgmt.buf = &zone_info;

	for (offset = 0; offset < MAX_ZONE_INFO_COUNT; ++offset) {
		chunk_offset = generate_chunk_offset(&geometry,
						     (offset / (geometry.num_grp * geometry.num_pu)) % geometry.num_chk,
						     offset % geometry.num_pu,
						     (offset / geometry.num_pu) % geometry.num_grp);


		memset(&chunk_info, 0, sizeof(chunk_info));
		chunk_info.cs.open = 1;
		chunk_info.wp = bdev_ocssd_to_disk_lba(ocssd_bdev, offset * bdev->zone_size + 68);
		chunk_info.slba = bdev_ocssd_to_disk_lba(ocssd_bdev, offset * bdev->zone_size);
		set_chunk_info(ctrlr, chunk_offset, &chunk_info);
	}

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, 0);

	for (offset = 0; offset < MAX_ZONE_INFO_COUNT; ++offset) {
		CU_ASSERT_EQUAL(zone_info[offset].state, SPDK_BDEV_ZONE_STATE_OPEN);
		CU_ASSERT_EQUAL(zone_info[offset].zone_id, bdev->zone_size * offset);
		CU_ASSERT_EQUAL(zone_info[offset].write_pointer, bdev->zone_size * offset + 68);
		CU_ASSERT_EQUAL(zone_info[offset].capacity, geometry.clba);
	}

	clear_chunk_info(ctrlr);

	/* Verify misaligned start zone LBA */
	bdev_io->u.zone_mgmt.zone_id = 1;
	bdev_io->u.zone_mgmt.num_zones = MAX_ZONE_INFO_COUNT;
	bdev_io->u.zone_mgmt.buf = &zone_info;

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, -EINVAL);

	/* Verify correct NVMe error forwarding */
	bdev_io->u.zone_mgmt.zone_id = 0;
	bdev_io->u.zone_mgmt.num_zones = MAX_ZONE_INFO_COUNT;
	bdev_io->u.zone_mgmt.buf = &zone_info;

	rc = bdev_ocssd_get_zone_info(NULL, bdev_io);
	CU_ASSERT_EQUAL(rc, 0);
	g_chunk_info_cpl = (struct spdk_nvme_cpl) {
		.status = {
			.sct = SPDK_NVME_SCT_GENERIC,
			.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR
		}
	};
	g_zone_info_status = false;

	g_chunk_info_cpl = (struct spdk_nvme_cpl) {};
	g_zone_info_status = true;

	delete_nvme_bdev_controller(nvme_bdev_ctrlr);

	free(bdev_io);
	free_controller(ctrlr);
}

@@ -713,7 +962,8 @@ main(int argc, const char **argv)
	if (
		CU_add_test(suite, "test_create_controller", test_create_controller) == NULL ||
		CU_add_test(suite, "test_device_geometry", test_device_geometry) == NULL ||
		CU_add_test(suite, "test_lba_translation", test_lba_translation) == NULL
		CU_add_test(suite, "test_lba_translation", test_lba_translation) == NULL ||
		CU_add_test(suite, "test_get_zone_info", test_get_zone_info) == NULL
	) {
		CU_cleanup_registry();
		return CU_get_error();