Commit 072d90cc authored by Seth Howell's avatar Seth Howell Committed by Jim Harris
Browse files

test/nvme: add app to check value of deallocated blocks



Useful in the case of drives that don't indicate this in their
information.

Change-Id: I128fb613cbc4638b9dcbe234b6bae6d056e48f71
Signed-off-by: default avatarSeth Howell <seth.howell@intel.com>
Reviewed-on: https://review.gerrithub.io/379689


Tested-by: default avatarSPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Reviewed-by: default avatarDaniel Verkamp <daniel.verkamp@intel.com>
parent 75e31e26
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@
SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..)
include $(SPDK_ROOT_DIR)/mk/spdk.common.mk

DIRS-y = aer reset sgl e2edp overhead
DIRS-y = aer reset sgl e2edp overhead deallocated_value

.PHONY: all clean $(DIRS-y)

+1 −0
Original line number Diff line number Diff line
deallocated_value
+39 −0
Original line number Diff line number Diff line
#
#  BSD LICENSE
#
#  Copyright (c) Intel Corporation.
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in
#      the documentation and/or other materials provided with the
#      distribution.
#    * Neither the name of Intel Corporation nor the names of its
#      contributors may be used to endorse or promote products derived
#      from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..)
include $(SPDK_ROOT_DIR)/mk/spdk.common.mk

APP = deallocated_value

include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
+442 −0
Original line number Diff line number Diff line
/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "spdk/stdinc.h"

#include "spdk/nvme.h"
#include "spdk/env.h"

#define NUM_BLOCKS 100

/*
 * The purpose of this sample app is to determine the read value of deallocated logical blocks
 * from a given NVMe Controller. The NVMe 1.3 spec requires the controller to list this value,
 * but controllers adhering to the NVMe 1.2 spec may not report this value. According to the spec,
 * "The values read from a deallocated logical block and its metadata (excluding protection information) shall
 * be all bytes set to 00h, all bytes set to FFh, or the last data written to the associated logical block".
 */

struct ns_entry {
	struct spdk_nvme_ctrlr	*ctrlr;
	struct spdk_nvme_ns	*ns;
	struct ns_entry		*next;
	struct spdk_nvme_qpair	*qpair;
};

struct deallocate_context {
	struct ns_entry	*ns_entry;
	char		**write_buf;
	char		**read_buf;
	char		*zero_buf;
	char		*FFh_buf;
	int		writes_completed;
	int		reads_completed;
	int		deallocate_completed;
	int		flush_complete;
	int		matches_zeroes;
	int		matches_previous_data;
	int		matches_FFh;
};

static struct ns_entry *g_namespaces = NULL;

static void cleanup(struct deallocate_context *context);

static void
fill_random(char *buf, size_t num_bytes)
{
	size_t	i;

	srand((unsigned) time(NULL));
	for (i = 0; i < num_bytes; i++) {
		buf[i] = rand() % 0x100;
	}
}

static void
register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns)
{
	struct ns_entry				*entry;
	const struct spdk_nvme_ctrlr_data	*cdata;

	cdata = spdk_nvme_ctrlr_get_data(ctrlr);

	if (!spdk_nvme_ns_is_active(ns)) {
		printf("Controller %-20.20s (%-20.20s): Skipping inactive NS %u\n",
		       cdata->mn, cdata->sn,
		       spdk_nvme_ns_get_id(ns));
		return;
	}

	entry = malloc(sizeof(struct ns_entry));
	if (entry == NULL) {
		perror("ns_entry malloc");
		exit(1);
	}

	entry->ctrlr = ctrlr;
	entry->ns = ns;
	entry->next = g_namespaces;
	g_namespaces = entry;

	printf("  Namespace ID: %d size: %juGB\n", spdk_nvme_ns_get_id(ns),
	       spdk_nvme_ns_get_size(ns) / 1000000000);
}

static uint32_t
get_max_block_size(void)
{
	struct ns_entry	*ns;
	uint32_t	max_block_size, temp_block_size;

	ns = g_namespaces;
	max_block_size = 0;

	while (ns != NULL) {
		temp_block_size = spdk_nvme_ns_get_sector_size(ns->ns);
		max_block_size = temp_block_size > max_block_size ? temp_block_size : max_block_size;
		ns = ns->next;
	}

	return max_block_size;
}

static void
write_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
	struct deallocate_context	*context = arg;

	context->writes_completed++;
}

static void
read_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
	struct deallocate_context	*context = arg;
	struct ns_entry			*ns_entry = context->ns_entry;
	int				rc;

	rc = memcmp(context->write_buf[context->reads_completed],
		    context->read_buf[context->reads_completed], spdk_nvme_ns_get_sector_size(ns_entry->ns));
	if (rc == 0) {
		context->matches_previous_data++;
	}

	rc = memcmp(context->zero_buf, context->read_buf[context->reads_completed],
		    spdk_nvme_ns_get_sector_size(ns_entry->ns));
	if (rc == 0) {
		context->matches_zeroes++;
	}

	rc = memcmp(context->FFh_buf, context->read_buf[context->reads_completed],
		    spdk_nvme_ns_get_sector_size(ns_entry->ns));
	if (rc == 0) {
		context->matches_FFh++;
	}
	context->reads_completed++;
}

static void
deallocate_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
	struct deallocate_context	*context = arg;

	printf("blocks matching previous data: %d\n", context->matches_previous_data);
	printf("blocks matching zeroes: %d\n", context->matches_zeroes);
	printf("blocks matching 0xFF: %d\n", context->matches_FFh);
	printf("Deallocating Blocks 0 to %d with random data.\n", NUM_BLOCKS - 1);
	printf("On next read, read value will match deallocated block read value.\n");
	context->deallocate_completed = 1;
	context->reads_completed = 0;
	context->matches_previous_data = 0;
	context->matches_zeroes = 0;
	context->matches_FFh = 0;
}

static void
flush_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
	struct deallocate_context	*context = arg;

	context->flush_complete = 1;
}

static void
deallocate_test(void)
{
	struct ns_entry				*ns_entry;
	struct spdk_nvme_ctrlr			*ctrlr;
	const struct spdk_nvme_ctrlr_data	*data;
	struct deallocate_context		context;
	struct spdk_nvme_dsm_range		range;
	uint32_t				max_block_size;
	int					rc, i;

	memset(&context, 0, sizeof(struct deallocate_context));
	max_block_size = get_max_block_size();
	ns_entry = g_namespaces;

	if (max_block_size > 0) {
		context.zero_buf = malloc(max_block_size);
	} else {
		printf("Unable to determine max block size.\n");
		return;
	}

	if (context.zero_buf == NULL) {
		printf("could not allocate buffer for test.\n");
		return;
	}

	context.FFh_buf = malloc(max_block_size);
	if (context.FFh_buf == NULL) {
		cleanup(&context);
		printf("could not allocate buffer for test.\n");
		return;
	}

	context.write_buf = calloc(NUM_BLOCKS, sizeof(char *));
	if (context.write_buf == NULL) {
		cleanup(&context);
		return;
	}

	context.read_buf = calloc(NUM_BLOCKS, sizeof(char *));
	if (context.read_buf == NULL) {
		printf("could not allocate buffer for test.\n");
		cleanup(&context);
		return;
	}

	memset(context.zero_buf, 0x00, max_block_size);
	memset(context.FFh_buf, 0xFF, max_block_size);

	for (i = 0; i < NUM_BLOCKS; i++) {
		context.write_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL);
		if (context.write_buf[i] == NULL) {
			printf("could not allocate buffer for test.\n");
			cleanup(&context);
			return;
		}

		fill_random(context.write_buf[i], 0x1000);
		context.read_buf[i] = spdk_dma_zmalloc(0x1000, max_block_size, NULL);
		if (context.read_buf[i] == NULL) {
			printf("could not allocate buffer for test.\n");
			cleanup(&context);
			return;
		}
	}

	while (ns_entry != NULL) {

		ns_entry->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, NULL, 0);
		if (ns_entry->qpair == NULL) {
			printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed.\n");
			return;
		}

		ctrlr = spdk_nvme_ns_get_ctrlr(ns_entry->ns);
		data = spdk_nvme_ctrlr_get_data(ctrlr);

		printf("\nController %-20.20s (%-20.20s)\n", data->mn, data->sn);
		printf("Controller PCI vendor:%u PCI subsystem vendor:%u\n", data->vid, data->ssvid);
		printf("Namespace Block Size:%u\n", spdk_nvme_ns_get_sector_size(ns_entry->ns));
		printf("Writing Blocks 0 to %d with random data.\n", NUM_BLOCKS);
		printf("On next read, read value will match random data.\n");

		context.ns_entry = ns_entry;

		for (i = 0; i < NUM_BLOCKS; i++) {
			rc = spdk_nvme_ns_cmd_write(ns_entry->ns, ns_entry->qpair, context.write_buf[i],
						    i,
						    1,
						    write_complete, &context, 0);
			if (rc) {
				printf("Error in nvme command completion, values may be inaccurate.\n");
			}
		}
		while (context.writes_completed < NUM_BLOCKS) {
			spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
		}

		spdk_nvme_ns_cmd_flush(ns_entry->ns, ns_entry->qpair, flush_complete, &context);
		while (!context.flush_complete) {
			spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
		}

		for (i = 0; i < NUM_BLOCKS; i++) {
			rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i],
						   i, /* LBA start */
						   1, /* number of LBAs */
						   read_complete, &context, 0);
			if (rc) {
				printf("Error in nvme command completion, values may be inaccurate.\n");
			}

			/* block after each read command so that we can match the block to the write buffer. */
			while (context.reads_completed <= i) {
				spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
			}
		}

		context.flush_complete = 0;
		range.length = NUM_BLOCKS;
		range.starting_lba = 0;
		rc = spdk_nvme_ns_cmd_dataset_management(ns_entry->ns, ns_entry->qpair,
				SPDK_NVME_DSM_ATTR_DEALLOCATE, &range, 1, deallocate_complete, &context);
		if (rc) {
			printf("Error in nvme command completion, values may be inaccurate.\n");
		}

		while (!context.deallocate_completed) {
			spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
		}

		for (i = 0; i < NUM_BLOCKS; i++) {
			rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, context.read_buf[i],
						   i, /* LBA start */
						   1, /* number of LBAs */
						   read_complete, &context, 0);
			if (rc) {
				printf("Error in nvme command completion, values may be inaccurate.\n");
			}
			while (context.reads_completed <= i) {
				spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
			}
		}

		printf("blocks matching previous data: %d\n", context.matches_previous_data);
		printf("blocks matching zeroes: %d\n", context.matches_zeroes);
		printf("blocks matching FFh: %d\n", context.matches_FFh);

		/* reset counters in between each namespace. */
		context.matches_previous_data = 0;
		context.matches_zeroes = 0;
		context.matches_FFh = 0;
		context.writes_completed = 0;
		context.reads_completed = 0;
		context.deallocate_completed = 0;

		spdk_nvme_ctrlr_free_io_qpair(ns_entry->qpair);
		ns_entry = ns_entry->next;
	}
	cleanup(&context);
}

static bool
probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
	 struct spdk_nvme_ctrlr_opts *opts)
{
	printf("Attaching to %s\n", trid->traddr);

	return true;
}

static void
attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
	  struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts)
{
	int			num_ns;
	struct spdk_nvme_ns	*ns;

	printf("Attached to %s\n", trid->traddr);
	/*
	 * Use only the first namespace from each controller since we are testing controller level functionality.
	 */
	num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
	if (num_ns < 1) {
		printf("No valid namespaces in controller\n");
	} else {
		ns = spdk_nvme_ctrlr_get_ns(ctrlr, 1);
		register_ns(ctrlr, ns);
	}
}

static void
cleanup(struct deallocate_context *context)
{
	struct ns_entry	*ns_entry = g_namespaces;
	int		i;

	while (ns_entry) {
		struct ns_entry *next = ns_entry->next;
		free(ns_entry);
		ns_entry = next;
	}
	for (i = 0; i < NUM_BLOCKS; i++) {
		if (context->write_buf[i]) {
			spdk_dma_free(context->write_buf[i]);
		} else {
			break;
		}
		if (context->read_buf[i]) {
			spdk_dma_free(context->read_buf[i]);
		} else {
			break;
		}
	}

	free(context->write_buf);
	free(context->read_buf);
	free(context->zero_buf);
	free(context->FFh_buf);
}

int main(int argc, char **argv)
{
	int			rc;
	struct spdk_env_opts	opts;

	spdk_env_opts_init(&opts);
	opts.name = "deallocate_test";
	opts.shm_id = 0;
	spdk_env_init(&opts);

	printf("Initializing NVMe Controllers\n");

	rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL);
	if (rc != 0) {
		fprintf(stderr, "spdk_nvme_probe() failed\n");
		return 1;
	}

	if (g_namespaces == NULL) {
		fprintf(stderr, "no NVMe controllers found\n");
		return 1;
	}

	printf("Initialization complete.\n");
	deallocate_test();
	return 0;
}
+4 −0
Original line number Diff line number Diff line
@@ -46,6 +46,10 @@ timing_enter hello_world
$rootdir/examples/nvme/hello_world/hello_world
timing_exit

timing_enter deallocated_value
$testdir/deallocated_value/deallocated_value
timing_exit deallocated_value

timing_enter sgl
$testdir/sgl/sgl
timing_exit sgl