summaryrefslogtreecommitdiffstats
path: root/src/spdk/test/nvme
diff options
context:
space:
mode:
Diffstat (limited to 'src/spdk/test/nvme')
-rw-r--r--src/spdk/test/nvme/Makefile44
-rw-r--r--src/spdk/test/nvme/aer/.gitignore1
-rw-r--r--src/spdk/test/nvme/aer/Makefile39
-rw-r--r--src/spdk/test/nvme/aer/aer.c580
-rw-r--r--src/spdk/test/nvme/deallocated_value/.gitignore1
-rw-r--r--src/spdk/test/nvme/deallocated_value/Makefile39
-rw-r--r--src/spdk/test/nvme/deallocated_value/deallocated_value.c445
-rw-r--r--src/spdk/test/nvme/e2edp/.gitignore1
-rw-r--r--src/spdk/test/nvme/e2edp/Makefile39
-rw-r--r--src/spdk/test/nvme/e2edp/nvme_dp.c659
-rw-r--r--src/spdk/test/nvme/err_injection/.gitignore1
-rw-r--r--src/spdk/test/nvme/err_injection/Makefile39
-rw-r--r--src/spdk/test/nvme/err_injection/err_injection.c279
-rwxr-xr-xsrc/spdk/test/nvme/hotplug.sh147
-rwxr-xr-xsrc/spdk/test/nvme/nvme.sh187
-rw-r--r--src/spdk/test/nvme/overhead/.gitignore1
-rw-r--r--src/spdk/test/nvme/overhead/Makefile44
-rw-r--r--src/spdk/test/nvme/overhead/README24
-rw-r--r--src/spdk/test/nvme/overhead/overhead.c720
-rw-r--r--src/spdk/test/nvme/reset/.gitignore1
-rw-r--r--src/spdk/test/nvme/reset/Makefile39
-rw-r--r--src/spdk/test/nvme/reset/reset.c689
-rw-r--r--src/spdk/test/nvme/sgl/.gitignore1
-rw-r--r--src/spdk/test/nvme/sgl/Makefile39
-rw-r--r--src/spdk/test/nvme/sgl/sgl.c542
-rwxr-xr-xsrc/spdk/test/nvme/spdk_nvme_cli.sh58
26 files changed, 4659 insertions, 0 deletions
diff --git a/src/spdk/test/nvme/Makefile b/src/spdk/test/nvme/Makefile
new file mode 100644
index 00000000..9fae60f6
--- /dev/null
+++ b/src/spdk/test/nvme/Makefile
@@ -0,0 +1,44 @@
+#
+# 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
+
+DIRS-y = aer reset sgl e2edp overhead deallocated_value err_injection
+
+.PHONY: all clean $(DIRS-y)
+
+all: $(DIRS-y)
+clean: $(DIRS-y)
+
+include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk
diff --git a/src/spdk/test/nvme/aer/.gitignore b/src/spdk/test/nvme/aer/.gitignore
new file mode 100644
index 00000000..31379617
--- /dev/null
+++ b/src/spdk/test/nvme/aer/.gitignore
@@ -0,0 +1 @@
+aer
diff --git a/src/spdk/test/nvme/aer/Makefile b/src/spdk/test/nvme/aer/Makefile
new file mode 100644
index 00000000..77acabd0
--- /dev/null
+++ b/src/spdk/test/nvme/aer/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = aer
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/aer/aer.c b/src/spdk/test/nvme/aer/aer.c
new file mode 100644
index 00000000..e102813f
--- /dev/null
+++ b/src/spdk/test/nvme/aer/aer.c
@@ -0,0 +1,580 @@
+/*-
+ * 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/log.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+
+#define MAX_DEVS 64
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_health_information_page *health_page;
+ struct spdk_nvme_ns_list *changed_ns_list;
+ uint32_t orig_temp_threshold;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static void get_feature_test(struct dev *dev);
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int outstanding_commands = 0;
+static int aer_done = 0;
+static int temperature_done = 0;
+static int failed = 0;
+static struct spdk_nvme_transport_id g_trid;
+
+/* Enable AER temperature test */
+static int enable_temp_test = 0;
+/* Enable AER namespace attribute notice test, this variable holds
+ * the NSID that is expected to be in the Changed NS List.
+ */
+static uint32_t expected_ns_test = 0;
+
+static void
+set_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: set feature (temp threshold) failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ /* Admin command completions are synchronized by the NVMe driver,
+ * so we don't need to do any special locking here. */
+ temperature_done++;
+}
+
+static int
+set_temp_threshold(struct dev *dev, uint32_t temp)
+{
+ struct spdk_nvme_cmd cmd = {};
+ int rc;
+
+ cmd.opc = SPDK_NVME_OPC_SET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD;
+ cmd.cdw11 = temp;
+
+ rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, set_temp_completion, dev);
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+get_temp_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get feature (temp threshold) failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ dev->orig_temp_threshold = cpl->cdw0;
+ printf("%s: original temperature threshold: %u Kelvin (%d Celsius)\n",
+ dev->name, dev->orig_temp_threshold, dev->orig_temp_threshold - 273);
+
+ temperature_done++;
+}
+
+static int
+get_temp_threshold(struct dev *dev)
+{
+ struct spdk_nvme_cmd cmd = {};
+ int rc;
+
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD;
+
+ rc = spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0, get_temp_completion, dev);
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+print_health_page(struct dev *dev, struct spdk_nvme_health_information_page *hip)
+{
+ printf("%s: Current Temperature: %u Kelvin (%d Celsius)\n",
+ dev->name, hip->temperature, hip->temperature - 273);
+}
+
+static void
+get_health_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands --;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get log page failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ print_health_page(dev, dev->health_page);
+ aer_done++;
+}
+
+static void
+get_changed_ns_log_page_completion(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+ bool found = false;
+ uint32_t i;
+
+ outstanding_commands --;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get log page failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ /* Let's compare the expected namespce ID is
+ * in changed namespace list
+ */
+ if (dev->changed_ns_list->ns_list[0] != 0xffffffffu) {
+ for (i = 0; i < sizeof(*dev->changed_ns_list) / sizeof(uint32_t); i++) {
+ if (expected_ns_test == dev->changed_ns_list->ns_list[i]) {
+ printf("%s: changed NS list contains expected NSID: %u\n",
+ dev->name, expected_ns_test);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ printf("%s: Error: Can't find expected NSID %u\n", dev->name, expected_ns_test);
+ failed = 1;
+ }
+
+ aer_done++;
+}
+
+static int
+get_health_log_page(struct dev *dev)
+{
+ int rc;
+
+ rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_HEALTH_INFORMATION,
+ SPDK_NVME_GLOBAL_NS_TAG, dev->health_page, sizeof(*dev->health_page), 0,
+ get_health_log_page_completion, dev);
+
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static int
+get_changed_ns_log_page(struct dev *dev)
+{
+ int rc;
+
+ rc = spdk_nvme_ctrlr_cmd_get_log_page(dev->ctrlr, SPDK_NVME_LOG_CHANGED_NS_LIST,
+ SPDK_NVME_GLOBAL_NS_TAG, dev->changed_ns_list,
+ sizeof(*dev->changed_ns_list), 0,
+ get_changed_ns_log_page_completion, dev);
+
+ if (rc == 0) {
+ outstanding_commands++;
+ }
+
+ return rc;
+}
+
+static void
+cleanup(void)
+{
+ struct dev *dev;
+
+ foreach_dev(dev) {
+ if (dev->health_page) {
+ spdk_dma_free(dev->health_page);
+ }
+ if (dev->changed_ns_list) {
+ spdk_dma_free(dev->changed_ns_list);
+ }
+ }
+}
+
+static void
+aer_cb(void *arg, const struct spdk_nvme_cpl *cpl)
+{
+ uint32_t log_page_id = (cpl->cdw0 & 0xFF0000) >> 16;
+ struct dev *dev = arg;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: AER failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ printf("%s: aer_cb for log page %d\n", dev->name, log_page_id);
+
+ if (log_page_id == SPDK_NVME_LOG_HEALTH_INFORMATION) {
+ /* Set the temperature threshold back to the original value
+ * so the AER doesn't trigger again.
+ */
+ set_temp_threshold(dev, dev->orig_temp_threshold);
+ get_health_log_page(dev);
+ } else if (log_page_id == SPDK_NVME_LOG_CHANGED_NS_LIST) {
+ get_changed_ns_log_page(dev);
+ }
+}
+
+static void
+usage(const char *program_name)
+{
+ printf("%s [options]", program_name);
+ printf("\n");
+ printf("options:\n");
+ printf(" -T enable temperature tests\n");
+ printf(" -n expected Namespace attribute notice ID\n");
+ printf(" -r trid remote NVMe over Fabrics target address\n");
+ printf(" Format: 'key:value [key:value] ...'\n");
+ printf(" Keys:\n");
+ printf(" trtype Transport type (e.g. RDMA)\n");
+ printf(" adrfam Address family (e.g. IPv4, IPv6)\n");
+ printf(" traddr Transport address (e.g. 192.168.100.8)\n");
+ printf(" trsvcid Transport service identifier (e.g. 4420)\n");
+ printf(" subnqn Subsystem NQN (default: %s)\n", SPDK_NVMF_DISCOVERY_NQN);
+ printf(" Example: -r 'trtype:RDMA adrfam:IPv4 traddr:192.168.100.8 trsvcid:4420'\n");
+
+ spdk_tracelog_usage(stdout, "-L");
+
+ printf(" -v verbose (enable warnings)\n");
+ printf(" -H show this usage\n");
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ int op, rc;
+
+ g_trid.trtype = SPDK_NVME_TRANSPORT_PCIE;
+ snprintf(g_trid.subnqn, sizeof(g_trid.subnqn), "%s", SPDK_NVMF_DISCOVERY_NQN);
+
+ while ((op = getopt(argc, argv, "n:r:HL:T")) != -1) {
+ switch (op) {
+ case 'n':
+ expected_ns_test = atoi(optarg);
+ break;
+ case 'r':
+ if (spdk_nvme_transport_id_parse(&g_trid, optarg) != 0) {
+ fprintf(stderr, "Error parsing transport address\n");
+ return 1;
+ }
+ break;
+ case 'L':
+ rc = spdk_log_set_trace_flag(optarg);
+ if (rc < 0) {
+ fprintf(stderr, "unknown flag\n");
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ spdk_log_set_print_level(SPDK_LOG_DEBUG);
+#ifndef DEBUG
+ fprintf(stderr, "%s must be rebuilt with CONFIG_DEBUG=y for -L flag.\n",
+ argv[0]);
+ usage(argv[0]);
+ return 0;
+#endif
+ break;
+ case 'T':
+ enable_temp_test = 1;
+ break;
+ case 'H':
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+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)
+{
+ struct dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+
+ dev->health_page = spdk_dma_zmalloc(sizeof(*dev->health_page), 4096, NULL);
+ if (dev->health_page == NULL) {
+ printf("Allocation error (health page)\n");
+ failed = 1;
+ }
+ dev->changed_ns_list = spdk_dma_zmalloc(sizeof(*dev->changed_ns_list), 4096, NULL);
+ if (dev->changed_ns_list == NULL) {
+ printf("Allocation error (changed namespace list page)\n");
+ failed = 1;
+ }
+}
+
+static void
+get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ printf("%s: get number of queues failed\n", dev->name);
+ failed = 1;
+ return;
+ }
+
+ if (aer_done < num_devs) {
+ /*
+ * Resubmit Get Features command to continue filling admin queue
+ * while the test is running.
+ */
+ get_feature_test(dev);
+ }
+}
+
+static void
+get_feature_test(struct dev *dev)
+{
+ struct spdk_nvme_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES;
+ if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0,
+ get_feature_test_cb, dev) != 0) {
+ printf("Failed to send Get Features command for dev=%p\n", dev);
+ failed = 1;
+ return;
+ }
+
+ outstanding_commands++;
+}
+
+static int
+spdk_aer_temperature_test(void)
+{
+ struct dev *dev;
+
+ printf("Getting temperature thresholds of all controllers...\n");
+ foreach_dev(dev) {
+ /* Get the original temperature threshold */
+ get_temp_threshold(dev);
+ }
+
+ while (!failed && temperature_done < num_devs) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+ temperature_done = 0;
+ aer_done = 0;
+
+ /* Send admin commands to test admin queue wraparound while waiting for the AER */
+ foreach_dev(dev) {
+ get_feature_test(dev);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ printf("Waiting for all controllers to trigger AER...\n");
+ foreach_dev(dev) {
+ /* Set the temperature threshold to a low value */
+ set_temp_threshold(dev, 200);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ while (!failed && (aer_done < num_devs || temperature_done < num_devs)) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ return 0;
+}
+
+static int
+spdk_aer_changed_ns_test(void)
+{
+ struct dev *dev;
+
+ aer_done = 0;
+
+ printf("Starting namespce attribute notice tests for all controllers...\n");
+
+ foreach_dev(dev) {
+ get_feature_test(dev);
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ while (!failed && (aer_done < num_devs)) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ if (failed) {
+ return failed;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *dev;
+ int i;
+ struct spdk_env_opts opts;
+ int rc;
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "aer";
+ opts.core_mask = "0x1";
+ opts.mem_size = 64;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("Asynchronous Event Request test\n");
+
+ if (spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (failed) {
+ goto done;
+ }
+
+ printf("Registering asynchronous event callbacks...\n");
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_register_aer_callback(dev->ctrlr, aer_cb, dev);
+ }
+
+ /* AER temperature test */
+ if (enable_temp_test) {
+ if (spdk_aer_temperature_test()) {
+ goto done;
+ }
+ }
+
+ /* AER changed namespace list test */
+ if (expected_ns_test) {
+ if (spdk_aer_changed_ns_test()) {
+ goto done;
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+done:
+ cleanup();
+
+ return failed;
+}
diff --git a/src/spdk/test/nvme/deallocated_value/.gitignore b/src/spdk/test/nvme/deallocated_value/.gitignore
new file mode 100644
index 00000000..8460e82e
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/.gitignore
@@ -0,0 +1 @@
+deallocated_value
diff --git a/src/spdk/test/nvme/deallocated_value/Makefile b/src/spdk/test/nvme/deallocated_value/Makefile
new file mode 100644
index 00000000..bff11cad
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/Makefile
@@ -0,0 +1,39 @@
+#
+# 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
diff --git a/src/spdk/test/nvme/deallocated_value/deallocated_value.c b/src/spdk/test/nvme/deallocated_value/deallocated_value.c
new file mode 100644
index 00000000..e1d1c9f6
--- /dev/null
+++ b/src/spdk/test/nvme/deallocated_value/deallocated_value.c
@@ -0,0 +1,445 @@
+/*-
+ * 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;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ 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;
+}
diff --git a/src/spdk/test/nvme/e2edp/.gitignore b/src/spdk/test/nvme/e2edp/.gitignore
new file mode 100644
index 00000000..df095820
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/.gitignore
@@ -0,0 +1 @@
+nvme_dp
diff --git a/src/spdk/test/nvme/e2edp/Makefile b/src/spdk/test/nvme/e2edp/Makefile
new file mode 100644
index 00000000..226a50e0
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = nvme_dp
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/e2edp/nvme_dp.c b/src/spdk/test/nvme/e2edp/nvme_dp.c
new file mode 100644
index 00000000..eaf2bd32
--- /dev/null
+++ b/src/spdk/test/nvme/e2edp/nvme_dp.c
@@ -0,0 +1,659 @@
+/*-
+ * 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.
+ */
+
+/*
+ * NVMe end-to-end data protection test
+ */
+
+#include "spdk/stdinc.h"
+
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+#include "spdk/crc16.h"
+#include "spdk/endian.h"
+
+#define MAX_DEVS 64
+
+#define DATA_PATTERN 0x5A
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int io_complete_flag = 0;
+
+struct io_request {
+ void *contig;
+ void *metadata;
+ bool use_extended_lba;
+ bool use_sgl;
+ uint32_t sgl_offset;
+ uint32_t buf_size;
+ uint64_t lba;
+ uint32_t lba_count;
+ uint16_t apptag_mask;
+ uint16_t apptag;
+};
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *cpl)
+{
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ io_complete_flag = 2;
+ } else {
+ io_complete_flag = 1;
+ }
+}
+
+static void
+ns_data_buffer_reset(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern)
+{
+ uint32_t md_size, sector_size;
+ uint32_t i, offset = 0;
+ uint8_t *buf;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+
+ for (i = 0; i < req->lba_count; i++) {
+ if (req->use_extended_lba) {
+ offset = (sector_size + md_size) * i;
+ } else {
+ offset = sector_size * i;
+ }
+
+ buf = (uint8_t *)req->contig + offset;
+ memset(buf, data_pattern, sector_size);
+ }
+}
+
+static void nvme_req_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ req->sgl_offset = sgl_offset;
+ return;
+}
+
+static int nvme_req_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+ void *payload;
+
+ payload = req->contig + req->sgl_offset;
+ *address = payload;
+
+ *length = req->buf_size - req->sgl_offset;
+
+ return 0;
+}
+
+/* CRC-16 Guard checked for extended lba format */
+static uint32_t dp_guard_check_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x200000;
+ req->use_extended_lba = true;
+ req->use_sgl = true;
+ req->buf_size = (sector_size + md_size) * req->lba_count;
+ req->metadata = NULL;
+ ns_data_buffer_reset(ns, req, DATA_PATTERN);
+ pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8);
+ /* big-endian for guard */
+ to_be16(&pi->guard, spdk_crc16_t10dif(req->contig, sector_size));
+
+ pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8);
+ to_be16(&pi->guard, spdk_crc16_t10dif(req->contig + sector_size + md_size, sector_size));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD;
+
+ return req->lba_count;
+}
+
+/*
+ * No protection information with PRACT setting to 1,
+ * both extended LBA format and separate metadata can
+ * run the test case.
+ */
+static uint32_t dp_with_pract_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t sector_size;
+
+ req->lba_count = 8;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ /* No additional metadata buffer provided */
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRACT;
+ break;
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE1:
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE2:
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_GUARD | SPDK_NVME_IO_FLAGS_PRCHK_REFTAG |
+ SPDK_NVME_IO_FLAGS_PRACT;
+ break;
+ default:
+ *io_flags = 0;
+ break;
+ }
+
+ req->lba = 0x100000;
+ req->use_extended_lba = false;
+ req->metadata = NULL;
+
+ return req->lba_count;
+}
+
+/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */
+static uint32_t dp_without_pract_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ return 0;
+ default:
+ break;
+ }
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x200000;
+ req->use_extended_lba = true;
+ req->metadata = NULL;
+ pi = (struct spdk_nvme_protection_info *)(req->contig + sector_size + md_size - 8);
+ /* big-endian for reference tag */
+ to_be32(&pi->ref_tag, (uint32_t)req->lba);
+
+ pi = (struct spdk_nvme_protection_info *)(req->contig + (sector_size + md_size) * 2 - 8);
+ /* is incremented for each subsequent logical block */
+ to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG;
+
+ return req->lba_count;
+}
+
+/* LBA + Metadata without data protection bits setting */
+static uint32_t dp_without_flags_extended_lba_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 16;
+
+ /* extended LBA only for the test case */
+ if (!(spdk_nvme_ns_supports_extended_lba(ns))) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc((sector_size + md_size) * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->lba = 0x400000;
+ req->use_extended_lba = true;
+ req->metadata = NULL;
+ *io_flags = 0;
+
+ return req->lba_count;
+}
+
+/* Block Reference Tag checked for TYPE1 and TYPE2 with PRACT setting to 0 */
+static uint32_t dp_without_pract_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 2;
+
+ switch (spdk_nvme_ns_get_pi_type(ns)) {
+ case SPDK_NVME_FMT_NVM_PROTECTION_TYPE3:
+ return 0;
+ default:
+ break;
+ }
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x400000;
+ req->use_extended_lba = false;
+
+ /* last 8 bytes if the metadata size bigger than 8 */
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8);
+ /* big-endian for reference tag */
+ to_be32(&pi->ref_tag, (uint32_t)req->lba);
+
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size * 2 - 8);
+ /* is incremented for each subsequent logical block */
+ to_be32(&pi->ref_tag, (uint32_t)(req->lba + 1));
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_REFTAG;
+
+ return req->lba_count;
+}
+
+/* Application Tag checked with PRACT setting to 0 */
+static uint32_t dp_without_pract_separate_meta_apptag_test(struct spdk_nvme_ns *ns,
+ struct io_request *req,
+ uint32_t *io_flags)
+{
+ struct spdk_nvme_protection_info *pi;
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 1;
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x500000;
+ req->use_extended_lba = false;
+ req->apptag_mask = 0xFFFF;
+ req->apptag = req->lba_count;
+
+ /* last 8 bytes if the metadata size bigger than 8 */
+ pi = (struct spdk_nvme_protection_info *)(req->metadata + md_size - 8);
+ to_be16(&pi->app_tag, req->lba_count);
+
+ *io_flags = SPDK_NVME_IO_FLAGS_PRCHK_APPTAG;
+
+ return req->lba_count;
+}
+
+/*
+ * LBA + Metadata without data protection bits setting,
+ * separate metadata payload for the test case.
+ */
+static uint32_t dp_without_flags_separate_meta_test(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *io_flags)
+{
+ uint32_t md_size, sector_size;
+
+ req->lba_count = 16;
+
+ /* separate metadata payload for the test case */
+ if (spdk_nvme_ns_supports_extended_lba(ns)) {
+ return 0;
+ }
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+ req->contig = spdk_dma_zmalloc(sector_size * req->lba_count, 0x1000, NULL);
+ if (!req->contig) {
+ return 0;
+ }
+
+ req->metadata = spdk_dma_zmalloc(md_size * req->lba_count, 0x1000, NULL);
+ if (!req->metadata) {
+ spdk_dma_free(req->contig);
+ return 0;
+ }
+
+ req->lba = 0x600000;
+ req->use_extended_lba = false;
+ *io_flags = 0;
+
+ return req->lba_count;
+}
+
+typedef uint32_t (*nvme_build_io_req_fn_t)(struct spdk_nvme_ns *ns, struct io_request *req,
+ uint32_t *lba_count);
+
+static void
+free_req(struct io_request *req)
+{
+ if (req == NULL) {
+ return;
+ }
+
+ if (req->contig) {
+ spdk_dma_free(req->contig);
+ }
+
+ if (req->metadata) {
+ spdk_dma_free(req->metadata);
+ }
+
+ spdk_dma_free(req);
+}
+
+static int
+ns_data_buffer_compare(struct spdk_nvme_ns *ns, struct io_request *req, uint8_t data_pattern)
+{
+ uint32_t md_size, sector_size;
+ uint32_t i, j, offset = 0;
+ uint8_t *buf;
+
+ sector_size = spdk_nvme_ns_get_sector_size(ns);
+ md_size = spdk_nvme_ns_get_md_size(ns);
+
+ for (i = 0; i < req->lba_count; i++) {
+ if (req->use_extended_lba) {
+ offset = (sector_size + md_size) * i;
+ } else {
+ offset = sector_size * i;
+ }
+
+ buf = (uint8_t *)req->contig + offset;
+ for (j = 0; j < sector_size; j++) {
+ if (buf[j] != data_pattern) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+write_read_e2e_dp_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name)
+{
+ int rc = 0;
+ uint32_t lba_count;
+ uint32_t io_flags = 0;
+
+ struct io_request *req;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ const struct spdk_nvme_ns_data *nsdata;
+
+ ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1);
+ if (!ns) {
+ fprintf(stderr, "Null namespace\n");
+ return 0;
+ }
+
+ if (!(spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED)) {
+ return 0;
+ }
+
+ nsdata = spdk_nvme_ns_get_data(ns);
+ if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) {
+ fprintf(stderr, "Empty nsdata or wrong sector size\n");
+ return 0;
+ }
+
+ req = spdk_dma_zmalloc(sizeof(*req), 0, NULL);
+ if (!req) {
+ fprintf(stderr, "Allocate request failed\n");
+ return 0;
+ }
+
+ /* IO parameters setting */
+ lba_count = build_io_fn(ns, req, &io_flags);
+
+ if (!lba_count) {
+ fprintf(stderr, "%s: %s bypass the test case\n", dev->name, test_name);
+ free_req(req);
+ return 0;
+ }
+
+ qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0);
+ if (!qpair) {
+ free_req(req);
+ return -1;
+ }
+
+ ns_data_buffer_reset(ns, req, DATA_PATTERN);
+ if (req->use_extended_lba && req->use_sgl) {
+ rc = spdk_nvme_ns_cmd_writev(ns, qpair, req->lba, lba_count, io_complete, req, io_flags,
+ nvme_req_reset_sgl, nvme_req_next_sge);
+ } else if (req->use_extended_lba) {
+ rc = spdk_nvme_ns_cmd_write(ns, qpair, req->contig, req->lba, lba_count,
+ io_complete, req, io_flags);
+ } else {
+ rc = spdk_nvme_ns_cmd_write_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count,
+ io_complete, req, io_flags, req->apptag_mask, req->apptag);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s write submit failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ io_complete_flag = 0;
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s write exec failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ /* reset completion flag */
+ io_complete_flag = 0;
+
+ ns_data_buffer_reset(ns, req, 0);
+ if (req->use_extended_lba && req->use_sgl) {
+ rc = spdk_nvme_ns_cmd_readv(ns, qpair, req->lba, lba_count, io_complete, req, io_flags,
+ nvme_req_reset_sgl, nvme_req_next_sge);
+
+ } else if (req->use_extended_lba) {
+ rc = spdk_nvme_ns_cmd_read(ns, qpair, req->contig, req->lba, lba_count,
+ io_complete, req, io_flags);
+ } else {
+ rc = spdk_nvme_ns_cmd_read_with_md(ns, qpair, req->contig, req->metadata, req->lba, lba_count,
+ io_complete, req, io_flags, req->apptag_mask, req->apptag);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s read failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s read failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ rc = ns_data_buffer_compare(ns, req, DATA_PATTERN);
+ if (rc < 0) {
+ fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ fprintf(stdout, "%s: %s test passed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return rc;
+}
+
+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)
+{
+ struct dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *iter;
+ int rc, i;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "nvme_dp";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Write/Read with End-to-End data protection test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "nvme_probe() failed\n");
+ exit(1);
+ }
+
+ rc = 0;
+ foreach_dev(iter) {
+#define TEST(x) write_read_e2e_dp_tests(iter, x, #x)
+ if (TEST(dp_with_pract_test)
+ || TEST(dp_guard_check_extended_lba_test)
+ || TEST(dp_without_pract_extended_lba_test)
+ || TEST(dp_without_flags_extended_lba_test)
+ || TEST(dp_without_pract_separate_meta_test)
+ || TEST(dp_without_pract_separate_meta_apptag_test)
+ || TEST(dp_without_flags_separate_meta_test)) {
+#undef TEST
+ rc = 1;
+ printf("%s: failed End-to-End data protection tests\n", iter->name);
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/err_injection/.gitignore b/src/spdk/test/nvme/err_injection/.gitignore
new file mode 100644
index 00000000..3572a8e7
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/.gitignore
@@ -0,0 +1 @@
+err_injection
diff --git a/src/spdk/test/nvme/err_injection/Makefile b/src/spdk/test/nvme/err_injection/Makefile
new file mode 100644
index 00000000..4f5f1851
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = err_injection
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/err_injection/err_injection.c b/src/spdk/test/nvme/err_injection/err_injection.c
new file mode 100644
index 00000000..50d67092
--- /dev/null
+++ b/src/spdk/test/nvme/err_injection/err_injection.c
@@ -0,0 +1,279 @@
+/*-
+ * 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/log.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+
+#define MAX_DEVS 64
+
+struct dev {
+ bool error_expected;
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ void *data;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int outstanding_commands = 0;
+static int failed = 0;
+
+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)
+{
+ struct dev *dev;
+ uint32_t nsid;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+ if (num_devs >= MAX_DEVS) {
+ return;
+ }
+
+ dev->ctrlr = ctrlr;
+ nsid = spdk_nvme_ctrlr_get_first_active_ns(ctrlr);
+ dev->ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid);
+ if (dev->ns == NULL) {
+ failed = 1;
+ return;
+ }
+ dev->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0);
+ if (dev->qpair == NULL) {
+ failed = 1;
+ return;
+ }
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+static void
+get_feature_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+
+ if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) {
+ if (cpl->status.sct != SPDK_NVME_SCT_GENERIC ||
+ cpl->status.sc != SPDK_NVME_SC_INVALID_FIELD) {
+ failed = 1;
+ }
+ printf("%s: get features failed as expected\n", dev->name);
+ return;
+ }
+
+ if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) {
+ printf("%s: get features successfully as expected\n", dev->name);
+ return;
+ }
+
+ failed = 1;
+}
+
+static void
+get_feature_test(bool error_expected)
+{
+ struct dev *dev;
+ struct spdk_nvme_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opc = SPDK_NVME_OPC_GET_FEATURES;
+ cmd.cdw10 = SPDK_NVME_FEAT_NUMBER_OF_QUEUES;
+
+ foreach_dev(dev) {
+ dev->error_expected = error_expected;
+ if (spdk_nvme_ctrlr_cmd_admin_raw(dev->ctrlr, &cmd, NULL, 0,
+ get_feature_test_cb, dev) != 0) {
+ printf("Error: failed to send Get Features command for dev=%p\n", dev);
+ failed = 1;
+ goto cleanup;
+ }
+ outstanding_commands++;
+ }
+
+cleanup:
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_ctrlr_process_admin_completions(dev->ctrlr);
+ }
+ }
+}
+
+static void
+read_test_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl)
+{
+ struct dev *dev = cb_arg;
+
+ outstanding_commands--;
+ spdk_dma_free(dev->data);
+
+ if (spdk_nvme_cpl_is_error(cpl) && dev->error_expected) {
+ if (cpl->status.sct != SPDK_NVME_SCT_MEDIA_ERROR ||
+ cpl->status.sc != SPDK_NVME_SC_UNRECOVERED_READ_ERROR) {
+ failed = 1;
+ }
+ printf("%s: read failed as expected\n", dev->name);
+ return;
+ }
+
+ if (!spdk_nvme_cpl_is_error(cpl) && !dev->error_expected) {
+ printf("%s: read successfully as expected\n", dev->name);
+ return;
+ }
+
+ failed = 1;
+}
+
+static void
+read_test(bool error_expected)
+{
+ struct dev *dev;
+
+ foreach_dev(dev) {
+ dev->error_expected = error_expected;
+ dev->data = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ if (!dev->data) {
+ failed = 1;
+ goto cleanup;
+ }
+
+ if (spdk_nvme_ns_cmd_read(dev->ns, dev->qpair, dev->data,
+ 0, 1, read_test_cb, dev, 0) != 0) {
+ printf("Error: failed to send Read command for dev=%p\n", dev);
+ failed = 1;
+ goto cleanup;
+ }
+
+ outstanding_commands++;
+ }
+
+cleanup:
+
+ while (outstanding_commands) {
+ foreach_dev(dev) {
+ spdk_nvme_qpair_process_completions(dev->qpair, 0);
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *dev;
+ int i;
+ struct spdk_env_opts opts;
+ int rc;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "err_injection";
+ opts.core_mask = "0x1";
+ opts.mem_size = 64;
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Error Injection test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (failed) {
+ goto exit;
+ }
+
+ if (!num_devs) {
+ printf("No NVMe controller found, %s exiting\n", argv[0]);
+ return 1;
+ }
+
+ foreach_dev(dev) {
+ /* Admin error injection at submission path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, NULL,
+ SPDK_NVME_OPC_GET_FEATURES, true, 5000, 1,
+ SPDK_NVME_SCT_GENERIC, SPDK_NVME_SC_INVALID_FIELD);
+ failed += rc;
+ /* IO error injection at completion path */
+ rc = spdk_nvme_qpair_add_cmd_error_injection(dev->ctrlr, dev->qpair,
+ SPDK_NVME_OPC_READ, false, 0, 1,
+ SPDK_NVME_SCT_MEDIA_ERROR, SPDK_NVME_SC_UNRECOVERED_READ_ERROR);
+ failed += rc;
+ }
+
+ if (failed) {
+ goto exit;
+ }
+
+ /* Admin Get Feature, expect error return */
+ get_feature_test(true);
+ /* Admin Get Feature, expect successful return */
+ get_feature_test(false);
+ /* Read, expect error return */
+ read_test(true);
+ /* Read, expect successful return */
+ read_test(false);
+
+exit:
+ printf("Cleaning up...\n");
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return failed;
+}
diff --git a/src/spdk/test/nvme/hotplug.sh b/src/spdk/test/nvme/hotplug.sh
new file mode 100755
index 00000000..ca661b6f
--- /dev/null
+++ b/src/spdk/test/nvme/hotplug.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+
+set -xe
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/test/common/autotest_common.sh
+
+if [ -z "${DEPENDENCY_DIR}" ]; then
+ echo DEPENDENCY_DIR not defined!
+ exit 1
+fi
+
+function ssh_vm() {
+ sshpass -p "$password" ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -p 10022 root@localhost "$@"
+}
+
+function monitor_cmd() {
+ rc=0
+ if ! (echo "$@" | nc localhost 4444 > mon.log); then
+ rc=1
+ cat mon.log
+ fi
+ rm mon.log
+ return $rc
+}
+
+function get_online_devices_count() {
+ ssh_vm "lspci | grep -c NVM"
+}
+
+function wait_for_devices_ready() {
+ count=$(get_online_devices_count)
+
+ while [ $count -ne 4 ]; do
+ echo "waitting for all devices online"
+ count=$(get_online_devices_count)
+ done
+}
+
+function devices_initialization() {
+ timing_enter devices_initialization
+ dd if=/dev/zero of=/root/test0 bs=1M count=1024
+ dd if=/dev/zero of=/root/test1 bs=1M count=1024
+ dd if=/dev/zero of=/root/test2 bs=1M count=1024
+ dd if=/dev/zero of=/root/test3 bs=1M count=1024
+ monitor_cmd "drive_add 0 file=/root/test0,format=raw,id=drive0,if=none"
+ monitor_cmd "drive_add 1 file=/root/test1,format=raw,id=drive1,if=none"
+ monitor_cmd "drive_add 2 file=/root/test2,format=raw,id=drive2,if=none"
+ monitor_cmd "drive_add 3 file=/root/test3,format=raw,id=drive3,if=none"
+ timing_exit devices_initialization
+}
+
+function insert_devices() {
+ monitor_cmd "device_add nvme,drive=drive0,id=nvme0,serial=nvme0"
+ monitor_cmd "device_add nvme,drive=drive1,id=nvme1,serial=nvme1"
+ monitor_cmd "device_add nvme,drive=drive2,id=nvme2,serial=nvme2"
+ monitor_cmd "device_add nvme,drive=drive3,id=nvme3,serial=nvme3"
+ wait_for_devices_ready
+ ssh_vm "scripts/setup.sh"
+}
+
+function remove_devices() {
+ monitor_cmd "device_del nvme0"
+ monitor_cmd "device_del nvme1"
+ monitor_cmd "device_del nvme2"
+ monitor_cmd "device_del nvme3"
+}
+
+function devices_delete() {
+ timing_enter devices_delete
+ rm /root/test0
+ rm /root/test1
+ rm /root/test2
+ rm /root/test3
+ timing_exit devices_delete
+}
+
+password=$1
+base_img=${DEPENDENCY_DIR}/fedora24.img
+test_img=${DEPENDENCY_DIR}/fedora24_test.img
+qemu_pidfile=${DEPENDENCY_DIR}/qemupid
+
+if [ ! -e "$base_img" ]; then
+ echo "Hotplug VM image not found; skipping test"
+ exit 0
+fi
+
+timing_enter hotplug
+
+timing_enter start_qemu
+
+qemu-img create -b "$base_img" -f qcow2 "$test_img"
+
+qemu-system-x86_64 \
+ -daemonize -display none -m 8192 \
+ -pidfile "$qemu_pidfile" \
+ -hda "$test_img" \
+ -net user,hostfwd=tcp::10022-:22 \
+ -net nic \
+ -cpu host \
+ -smp cores=16,sockets=1 \
+ --enable-kvm \
+ -chardev socket,id=mon0,host=localhost,port=4444,server,nowait \
+ -mon chardev=mon0,mode=readline
+
+timing_exit start_qemu
+
+timing_enter wait_for_vm
+ssh_vm 'echo ready'
+timing_exit wait_for_vm
+
+timing_enter copy_repo
+(cd "$rootdir"; tar -cf - .) | (ssh_vm 'tar -xf -')
+timing_exit copy_repo
+
+devices_initialization
+insert_devices
+
+timing_enter hotplug_test
+
+ssh_vm "examples/nvme/hotplug/hotplug -i 0 -t 25 -n 4 -r 8" &
+example_pid=$!
+
+sleep 4
+remove_devices
+sleep 4
+insert_devices
+sleep 4
+remove_devices
+devices_delete
+
+timing_enter wait_for_example
+wait $example_pid
+timing_exit wait_for_example
+
+trap - SIGINT SIGTERM EXIT
+
+qemupid=`cat "$qemu_pidfile" | awk '{printf $0}'`
+kill -9 $qemupid
+rm "$qemu_pidfile"
+rm "$test_img"
+
+report_test_completion "nvme_hotplug"
+timing_exit hotplug_test
+
+timing_exit hotplug
diff --git a/src/spdk/test/nvme/nvme.sh b/src/spdk/test/nvme/nvme.sh
new file mode 100755
index 00000000..c52b926e
--- /dev/null
+++ b/src/spdk/test/nvme/nvme.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+
+set -e
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/scripts/common.sh
+source $rootdir/test/common/autotest_common.sh
+
+function get_nvme_name_from_bdf {
+ lsblk -d --output NAME
+ nvme_devs=$(lsblk -d --output NAME | grep "^nvme") || true
+ if [ -z "$nvme_devs" ]; then
+ return
+ fi
+ for dev in $nvme_devs; do
+ link_name=$(readlink /sys/block/$dev/device/device) || true
+ if [ -z "$link_name" ]; then
+ link_name=$(readlink /sys/block/$dev/device)
+ fi
+ bdf=$(basename "$link_name")
+ if [ "$bdf" = "$1" ]; then
+ eval "$2=$dev"
+ return
+ fi
+ done
+}
+
+timing_enter nvme
+
+if [ `uname` = Linux ]; then
+ # check that our setup.sh script does not bind NVMe devices to uio/vfio if they
+ # have an active mountpoint
+ $rootdir/scripts/setup.sh reset
+ # give kernel nvme driver some time to create the block devices before we start looking for them
+ sleep 1
+ blkname=''
+ # first, find an NVMe device that does not have an active mountpoint already;
+ # this covers rare case where someone is running this test script on a system
+ # that has a mounted NVMe filesystem
+ #
+ # note: more work probably needs to be done to properly handle devices with multiple
+ # namespaces
+ for bdf in $(iter_pci_class_code 01 08 02); do
+ get_nvme_name_from_bdf "$bdf" blkname
+ if [ "$blkname" != "" ]; then
+ mountpoints=$(lsblk /dev/$blkname --output MOUNTPOINT -n | wc -w)
+ if [ "$mountpoints" = "0" ]; then
+ break
+ else
+ blkname=''
+ fi
+ fi
+ done
+
+ # if we found an NVMe block device without an active mountpoint, create and mount
+ # a filesystem on it for purposes of testing the setup.sh script
+ if [ "$blkname" != "" ]; then
+ parted -s /dev/$blkname mklabel gpt
+ # just create a 100MB partition - this tests our ability to detect mountpoints
+ # on partitions of the device, not just the device itself; it also is faster
+ # since we don't trim and initialize the whole namespace
+ parted -s /dev/$blkname mkpart primary 1 100
+ sleep 1
+ mkfs.ext4 -F /dev/${blkname}p1
+ mkdir -p /tmp/nvmetest
+ mount /dev/${blkname}p1 /tmp/nvmetest
+ $rootdir/scripts/setup.sh
+ driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver))
+ # check that the nvme driver is still loaded against the device
+ if [ "$driver" != "nvme" ]; then
+ exit 1
+ fi
+ umount /tmp/nvmetest
+ rmdir /tmp/nvmetest
+ # write zeroes to the device to blow away the partition table and filesystem
+ dd if=/dev/zero of=/dev/$blkname oflag=direct bs=1M count=1
+ $rootdir/scripts/setup.sh
+ driver=$(basename $(readlink /sys/bus/pci/devices/$bdf/driver))
+ # check that the nvme driver is not loaded against the device
+ if [ "$driver" = "nvme" ]; then
+ exit 1
+ fi
+ else
+ $rootdir/scripts/setup.sh
+ fi
+fi
+
+if [ `uname` = Linux ]; then
+ start_stub "-s 4096 -i 0 -m 0xF"
+ trap "kill_stub; exit 1" SIGINT SIGTERM EXIT
+fi
+
+if [ $RUN_NIGHTLY -eq 1 ]; then
+ # TODO: temporarily disabled - temperature AER doesn't fire on emulated controllers
+ #timing_enter aer
+ #$testdir/aer/aer
+ #timing_exit aer
+
+ timing_enter reset
+ $testdir/reset/reset -q 64 -w write -s 4096 -t 2
+ report_test_completion "nightly_nvme_reset"
+ timing_exit reset
+fi
+
+timing_enter identify
+$rootdir/examples/nvme/identify/identify -i 0
+for bdf in $(iter_pci_class_code 01 08 02); do
+ $rootdir/examples/nvme/identify/identify -r "trtype:PCIe traddr:${bdf}" -i 0
+done
+timing_exit identify
+
+timing_enter perf
+$rootdir/examples/nvme/perf/perf -q 128 -w read -o 12288 -t 1 -LL -i 0
+if [ -b /dev/ram0 ]; then
+ # Test perf with AIO device
+ $rootdir/examples/nvme/perf/perf /dev/ram0 -q 128 -w read -o 12288 -t 1 -LL -i 0
+ report_test_completion "nvme_perf"
+fi
+timing_exit perf
+
+timing_enter reserve
+$rootdir/examples/nvme/reserve/reserve
+timing_exit reserve
+
+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
+
+timing_enter e2edp
+$testdir/e2edp/nvme_dp
+timing_exit e2edp
+
+timing_enter err_injection
+$testdir/err_injection/err_injection
+timing_exit err_injection
+
+timing_enter overhead
+$testdir/overhead/overhead -s 4096 -t 1 -H
+timing_exit overhead
+
+timing_enter arbitration
+$rootdir/examples/nvme/arbitration/arbitration -t 3 -i 0
+timing_exit arbitration
+
+if [ `uname` = Linux ]; then
+ timing_enter multi_secondary
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x1 &
+ pid0=$!
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x2 &
+ pid1=$!
+ $rootdir/examples/nvme/perf/perf -i 0 -q 16 -w read -o 4096 -t 3 -c 0x4
+ wait $pid0
+ wait $pid1
+ report_test_completion "nvme_multi_secondary"
+ timing_exit multi_secondary
+fi
+
+if [ `uname` = Linux ]; then
+ trap - SIGINT SIGTERM EXIT
+ kill_stub
+fi
+PLUGIN_DIR=$rootdir/examples/nvme/fio_plugin
+
+if [ -d /usr/src/fio ]; then
+ timing_enter fio_plugin
+ for bdf in $(iter_pci_class_code 01 08 02); do
+ # Only test when ASAN is not enabled. If ASAN is enabled, we cannot test.
+ if [ $SPDK_RUN_ASAN -eq 0 ]; then
+ LD_PRELOAD=$PLUGIN_DIR/fio_plugin /usr/src/fio/fio $PLUGIN_DIR/example_config.fio --filename="trtype=PCIe traddr=${bdf//:/.} ns=1"
+ report_test_completion "bdev_fio"
+ fi
+ break
+ done
+
+ timing_exit fio_plugin
+fi
+
+timing_exit nvme
diff --git a/src/spdk/test/nvme/overhead/.gitignore b/src/spdk/test/nvme/overhead/.gitignore
new file mode 100644
index 00000000..d5a7d6f4
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/.gitignore
@@ -0,0 +1 @@
+overhead
diff --git a/src/spdk/test/nvme/overhead/Makefile b/src/spdk/test/nvme/overhead/Makefile
new file mode 100644
index 00000000..bcb7f38d
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/Makefile
@@ -0,0 +1,44 @@
+#
+# 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 = overhead
+
+ifeq ($(OS),Linux)
+SYS_LIBS += -laio
+CFLAGS += -DHAVE_LIBAIO
+endif
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/overhead/README b/src/spdk/test/nvme/overhead/README
new file mode 100644
index 00000000..b88c4217
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/README
@@ -0,0 +1,24 @@
+This application measures the software overhead of I/O submission
+and completion for both the SPDK NVMe driver and an AIO file handle.
+It runs a random read, queue depth = 1 workload to a single device,
+and captures TSC as follows:
+
+* Submission: capture TSC before and after the I/O submission
+ call (SPDK or AIO).
+* Completion: capture TSC before and after the I/O completion
+ check. Only record the TSC delta if the I/O completion check
+ resulted in a completed I/O. Also use heuristics in the AIO
+ case to account for time spent in interrupt handling outside
+ of the actual I/O completion check.
+
+Usage:
+
+To test software overhead for a 4KB I/O over a 10 second period:
+
+SPDK: overhead -s 4096 -t 10
+AIO: overhead -s 4096 -t 10 /dev/nvme0n1
+
+Note that for the SPDK case, it will only use the first namespace
+on the first controller found by SPDK. If a different namespace is
+desired, attach controllers individually to the kernel NVMe driver
+to ensure they will not be enumerated by SPDK.
diff --git a/src/spdk/test/nvme/overhead/overhead.c b/src/spdk/test/nvme/overhead/overhead.c
new file mode 100644
index 00000000..f35247a2
--- /dev/null
+++ b/src/spdk/test/nvme/overhead/overhead.c
@@ -0,0 +1,720 @@
+/*-
+ * 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/barrier.h"
+#include "spdk/fd.h"
+#include "spdk/nvme.h"
+#include "spdk/env.h"
+#include "spdk/string.h"
+#include "spdk/nvme_intel.h"
+#include "spdk/histogram_data.h"
+
+#if HAVE_LIBAIO
+#include <libaio.h>
+#endif
+
+struct ctrlr_entry {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ctrlr_entry *next;
+ char name[1024];
+};
+
+enum entry_type {
+ ENTRY_TYPE_NVME_NS,
+ ENTRY_TYPE_AIO_FILE,
+};
+
+struct ns_entry {
+ enum entry_type type;
+
+ union {
+ struct {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ } nvme;
+#if HAVE_LIBAIO
+ struct {
+ int fd;
+ struct io_event *events;
+ io_context_t ctx;
+ } aio;
+#endif
+ } u;
+
+ uint32_t io_size_blocks;
+ uint64_t size_in_ios;
+ bool is_draining;
+ uint32_t current_queue_depth;
+ char name[1024];
+ struct ns_entry *next;
+
+ struct spdk_histogram_data *submit_histogram;
+ struct spdk_histogram_data *complete_histogram;
+};
+
+struct perf_task {
+ void *buf;
+ uint64_t submit_tsc;
+#if HAVE_LIBAIO
+ struct iocb iocb;
+#endif
+};
+
+static bool g_enable_histogram = false;
+
+static struct ctrlr_entry *g_ctrlr = NULL;
+static struct ns_entry *g_ns = NULL;
+
+static uint64_t g_tsc_rate;
+
+static uint32_t g_io_size_bytes;
+static int g_time_in_sec;
+
+static int g_aio_optind; /* Index of first AIO filename in argv */
+
+struct perf_task *g_task;
+uint64_t g_tsc_submit = 0;
+uint64_t g_tsc_submit_min = UINT64_MAX;
+uint64_t g_tsc_submit_max = 0;
+uint64_t g_tsc_complete = 0;
+uint64_t g_tsc_complete_min = UINT64_MAX;
+uint64_t g_tsc_complete_max = 0;
+uint64_t g_io_completed = 0;
+
+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;
+ }
+
+ if (spdk_nvme_ns_get_size(ns) < g_io_size_bytes ||
+ spdk_nvme_ns_get_sector_size(ns) > g_io_size_bytes) {
+ printf("WARNING: controller %-20.20s (%-20.20s) ns %u has invalid "
+ "ns size %" PRIu64 " / block size %u for I/O size %u\n",
+ cdata->mn, cdata->sn, spdk_nvme_ns_get_id(ns),
+ spdk_nvme_ns_get_size(ns), spdk_nvme_ns_get_sector_size(ns), g_io_size_bytes);
+ return;
+ }
+
+ entry = calloc(1, sizeof(struct ns_entry));
+ if (entry == NULL) {
+ perror("ns_entry malloc");
+ exit(1);
+ }
+
+ entry->type = ENTRY_TYPE_NVME_NS;
+ entry->u.nvme.ctrlr = ctrlr;
+ entry->u.nvme.ns = ns;
+
+ entry->size_in_ios = spdk_nvme_ns_get_size(ns) /
+ g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns);
+ entry->submit_histogram = spdk_histogram_data_alloc();
+ entry->complete_histogram = spdk_histogram_data_alloc();
+
+ snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ entry->next = g_ns;
+ g_ns = entry;
+}
+
+static void
+register_ctrlr(struct spdk_nvme_ctrlr *ctrlr)
+{
+ int num_ns;
+ struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry));
+ const struct spdk_nvme_ctrlr_data *cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ if (entry == NULL) {
+ perror("ctrlr_entry malloc");
+ exit(1);
+ }
+
+ snprintf(entry->name, sizeof(entry->name), "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ entry->ctrlr = ctrlr;
+
+ entry->next = g_ctrlr;
+ g_ctrlr = entry;
+
+ num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
+ /* Only register the first namespace. */
+ if (num_ns < 1) {
+ fprintf(stderr, "controller found with no namespaces\n");
+ exit(1);
+ }
+
+ register_ns(ctrlr, spdk_nvme_ctrlr_get_ns(ctrlr, 1));
+}
+
+#if HAVE_LIBAIO
+static int
+register_aio_file(const char *path)
+{
+ struct ns_entry *entry;
+
+ int fd;
+ uint64_t size;
+ uint32_t blklen;
+
+ fd = open(path, O_RDWR | O_DIRECT);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open AIO device %s: %s\n", path, strerror(errno));
+ return -1;
+ }
+
+ size = spdk_fd_get_size(fd);
+ if (size == 0) {
+ fprintf(stderr, "Could not determine size of AIO device %s\n", path);
+ close(fd);
+ return -1;
+ }
+
+ blklen = spdk_fd_get_blocklen(fd);
+ if (blklen == 0) {
+ fprintf(stderr, "Could not determine block size of AIO device %s\n", path);
+ close(fd);
+ return -1;
+ }
+
+ entry = calloc(1, sizeof(struct ns_entry));
+ if (entry == NULL) {
+ close(fd);
+ perror("aio ns_entry malloc");
+ return -1;
+ }
+
+ entry->type = ENTRY_TYPE_AIO_FILE;
+ entry->u.aio.fd = fd;
+ entry->size_in_ios = size / g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / blklen;
+ entry->submit_histogram = spdk_histogram_data_alloc();
+ entry->complete_histogram = spdk_histogram_data_alloc();
+
+ snprintf(entry->name, sizeof(entry->name), "%s", path);
+
+ g_ns = entry;
+
+ return 0;
+}
+
+static int
+aio_submit(io_context_t aio_ctx, struct iocb *iocb, int fd, enum io_iocb_cmd cmd, void *buf,
+ unsigned long nbytes, uint64_t offset, void *cb_ctx)
+{
+ iocb->aio_fildes = fd;
+ iocb->aio_reqprio = 0;
+ iocb->aio_lio_opcode = cmd;
+ iocb->u.c.buf = buf;
+ iocb->u.c.nbytes = nbytes;
+ iocb->u.c.offset = offset;
+ iocb->data = cb_ctx;
+
+ if (io_submit(aio_ctx, 1, &iocb) < 0) {
+ printf("io_submit");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+aio_check_io(void)
+{
+ int count, i;
+ struct timespec timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ count = io_getevents(g_ns->u.aio.ctx, 1, 1, g_ns->u.aio.events, &timeout);
+ if (count < 0) {
+ fprintf(stderr, "io_getevents error\n");
+ exit(1);
+ }
+
+ for (i = 0; i < count; i++) {
+ g_ns->current_queue_depth--;
+ }
+}
+#endif /* HAVE_LIBAIO */
+
+static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion);
+
+static __thread unsigned int seed = 0;
+
+static void
+submit_single_io(void)
+{
+ uint64_t offset_in_ios;
+ uint64_t start;
+ int rc;
+ struct ns_entry *entry = g_ns;
+ uint64_t tsc_submit;
+
+ offset_in_ios = rand_r(&seed) % entry->size_in_ios;
+
+ start = spdk_get_ticks();
+ spdk_rmb();
+#if HAVE_LIBAIO
+ if (entry->type == ENTRY_TYPE_AIO_FILE) {
+ rc = aio_submit(g_ns->u.aio.ctx, &g_task->iocb, entry->u.aio.fd, IO_CMD_PREAD, g_task->buf,
+ g_io_size_bytes, offset_in_ios * g_io_size_bytes, g_task);
+ } else
+#endif
+ {
+ rc = spdk_nvme_ns_cmd_read(entry->u.nvme.ns, g_ns->u.nvme.qpair, g_task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, g_task, 0);
+ }
+
+ spdk_rmb();
+ tsc_submit = spdk_get_ticks() - start;
+ g_tsc_submit += tsc_submit;
+ if (tsc_submit < g_tsc_submit_min) {
+ g_tsc_submit_min = tsc_submit;
+ }
+ if (tsc_submit > g_tsc_submit_max) {
+ g_tsc_submit_max = tsc_submit;
+ }
+ if (g_enable_histogram) {
+ spdk_histogram_data_tally(entry->submit_histogram, tsc_submit);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "starting I/O failed\n");
+ }
+
+ g_ns->current_queue_depth++;
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *completion)
+{
+ g_ns->current_queue_depth--;
+}
+
+uint64_t g_complete_tsc_start;
+
+static uint64_t
+check_io(void)
+{
+ uint64_t end, tsc_complete;
+
+ spdk_rmb();
+#if HAVE_LIBAIO
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+ aio_check_io();
+ } else
+#endif
+ {
+ spdk_nvme_qpair_process_completions(g_ns->u.nvme.qpair, 0);
+ }
+ spdk_rmb();
+ end = spdk_get_ticks();
+ if (g_ns->current_queue_depth == 1) {
+ /*
+ * Account for race condition in AIO case where interrupt occurs
+ * after checking for queue depth. If the timestamp capture
+ * is too big compared to the last capture, assume that an
+ * interrupt fired, and do not bump the start tsc forward. This
+ * will ensure this extra time is accounted for next time through
+ * when we see current_queue_depth drop to 0.
+ */
+ if (g_ns->type == ENTRY_TYPE_NVME_NS || (end - g_complete_tsc_start) < 500) {
+ g_complete_tsc_start = end;
+ }
+ } else {
+ tsc_complete = end - g_complete_tsc_start;
+ g_tsc_complete += tsc_complete;
+ if (tsc_complete < g_tsc_complete_min) {
+ g_tsc_complete_min = tsc_complete;
+ }
+ if (tsc_complete > g_tsc_complete_max) {
+ g_tsc_complete_max = tsc_complete;
+ }
+ if (g_enable_histogram) {
+ spdk_histogram_data_tally(g_ns->complete_histogram, tsc_complete);
+ }
+ g_io_completed++;
+ if (!g_ns->is_draining) {
+ submit_single_io();
+ }
+ end = g_complete_tsc_start = spdk_get_ticks();
+ }
+
+ return end;
+}
+
+static void
+drain_io(void)
+{
+ g_ns->is_draining = true;
+ while (g_ns->current_queue_depth > 0) {
+ check_io();
+ }
+}
+
+static int
+init_ns_worker_ctx(void)
+{
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+#ifdef HAVE_LIBAIO
+ g_ns->u.aio.events = calloc(1, sizeof(struct io_event));
+ if (!g_ns->u.aio.events) {
+ return -1;
+ }
+ g_ns->u.aio.ctx = 0;
+ if (io_setup(1, &g_ns->u.aio.ctx) < 0) {
+ free(g_ns->u.aio.events);
+ perror("io_setup");
+ return -1;
+ }
+#endif
+ } else {
+ /*
+ * TODO: If a controller has multiple namespaces, they could all use the same queue.
+ * For now, give each namespace/thread combination its own queue.
+ */
+ g_ns->u.nvme.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_ns->u.nvme.ctrlr, NULL, 0);
+ if (!g_ns->u.nvme.qpair) {
+ printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair failed\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+cleanup_ns_worker_ctx(void)
+{
+ if (g_ns->type == ENTRY_TYPE_AIO_FILE) {
+#ifdef HAVE_LIBAIO
+ io_destroy(g_ns->u.aio.ctx);
+ free(g_ns->u.aio.events);
+#endif
+ } else {
+ spdk_nvme_ctrlr_free_io_qpair(g_ns->u.nvme.qpair);
+ }
+}
+
+static int
+work_fn(void)
+{
+ uint64_t tsc_end, current;
+
+ /* Allocate a queue pair for each namespace. */
+ if (init_ns_worker_ctx() != 0) {
+ printf("ERROR: init_ns_worker_ctx() failed\n");
+ return 1;
+ }
+
+ tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
+
+ /* Submit initial I/O for each namespace. */
+ submit_single_io();
+ g_complete_tsc_start = spdk_get_ticks();
+
+ while (1) {
+ /*
+ * Check for completed I/O for each controller. A new
+ * I/O will be submitted in the io_complete callback
+ * to replace each I/O that is completed.
+ */
+ current = check_io();
+
+ if (current > tsc_end) {
+ break;
+ }
+ }
+
+ drain_io();
+ cleanup_ns_worker_ctx();
+
+ return 0;
+}
+
+static void usage(char *program_name)
+{
+ printf("%s options", program_name);
+#if HAVE_LIBAIO
+ printf(" [AIO device(s)]...");
+#endif
+ printf("\n");
+ printf("\t[-s io size in bytes]\n");
+ printf("\t[-t time in seconds]\n");
+ printf("\t\t(default: 1)]\n");
+ printf("\t[-H enable histograms]\n");
+}
+
+static void
+print_bucket(void *ctx, uint64_t start, uint64_t end, uint64_t count,
+ uint64_t total, uint64_t so_far)
+{
+ double so_far_pct;
+
+ if (count == 0) {
+ return;
+ }
+
+ so_far_pct = (double)so_far * 100 / total;
+
+ printf("%9.3f - %9.3f: %9.4f%% (%9ju)\n",
+ (double)start * 1000 * 1000 / g_tsc_rate,
+ (double)end * 1000 * 1000 / g_tsc_rate,
+ so_far_pct, count);
+}
+
+static void
+print_stats(void)
+{
+ double divisor = (double)g_tsc_rate / (1000 * 1000 * 1000);
+
+ printf("submit (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n",
+ (double)g_tsc_submit / g_io_completed / divisor,
+ (double)g_tsc_submit_min / divisor,
+ (double)g_tsc_submit_max / divisor);
+ printf("complete (in ns) avg, min, max = %8.1f, %8.1f, %8.1f\n",
+ (double)g_tsc_complete / g_io_completed / divisor,
+ (double)g_tsc_complete_min / divisor,
+ (double)g_tsc_complete_max / divisor);
+
+ if (!g_enable_histogram) {
+ return;
+ }
+
+ printf("\n");
+ printf("Submit histogram\n");
+ printf("================\n");
+ printf(" Range in us Cumulative Count\n");
+ spdk_histogram_data_iterate(g_ns->submit_histogram, print_bucket, NULL);
+ printf("\n");
+
+ printf("Complete histogram\n");
+ printf("==================\n");
+ printf(" Range in us Cumulative Count\n");
+ spdk_histogram_data_iterate(g_ns->complete_histogram, print_bucket, NULL);
+ printf("\n");
+
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ int op;
+
+ /* default value */
+ g_io_size_bytes = 0;
+ g_time_in_sec = 0;
+
+ while ((op = getopt(argc, argv, "hs:t:H")) != -1) {
+ switch (op) {
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+ case 's':
+ g_io_size_bytes = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ case 'H':
+ g_enable_histogram = true;
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (!g_io_size_bytes) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ g_aio_optind = optind;
+
+ return 0;
+}
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ static uint32_t ctrlr_found = 0;
+
+ if (ctrlr_found == 1) {
+ fprintf(stderr, "only attaching to one controller, so skipping\n");
+ fprintf(stderr, " controller at PCI address %s\n",
+ trid->traddr);
+ return false;
+ }
+ ctrlr_found = 1;
+
+ 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)
+{
+ printf("Attached to %s\n", trid->traddr);
+
+ register_ctrlr(ctrlr);
+}
+
+static int
+register_controllers(void)
+{
+ printf("Initializing NVMe Controllers\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ if (g_ns == NULL) {
+ fprintf(stderr, "no NVMe controller found - check that device is bound to uio/vfio\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+cleanup(void)
+{
+ struct ns_entry *ns_entry = g_ns;
+ struct ctrlr_entry *ctrlr_entry = g_ctrlr;
+
+ while (ns_entry) {
+ struct ns_entry *next = ns_entry->next;
+
+ spdk_histogram_data_free(ns_entry->submit_histogram);
+ spdk_histogram_data_free(ns_entry->complete_histogram);
+ free(ns_entry);
+ ns_entry = next;
+ }
+
+ while (ctrlr_entry) {
+ struct ctrlr_entry *next = ctrlr_entry->next;
+
+ spdk_nvme_detach(ctrlr_entry->ctrlr);
+ free(ctrlr_entry);
+ ctrlr_entry = next;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct spdk_env_opts opts;
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "overhead";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ g_task = spdk_dma_zmalloc(sizeof(struct perf_task), 0, NULL);
+ if (g_task == NULL) {
+ fprintf(stderr, "g_task alloc failed\n");
+ exit(1);
+ }
+
+ g_task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x1000, NULL);
+ if (g_task->buf == NULL) {
+ fprintf(stderr, "g_task->buf spdk_dma_zmalloc failed\n");
+ exit(1);
+ }
+
+ g_tsc_rate = spdk_get_ticks_hz();
+
+#if HAVE_LIBAIO
+ if (g_aio_optind < argc) {
+ printf("Measuring overhead for AIO device %s.\n", argv[g_aio_optind]);
+ if (register_aio_file(argv[g_aio_optind]) != 0) {
+ cleanup();
+ return -1;
+ }
+ } else
+#endif
+ {
+ if (register_controllers() != 0) {
+ cleanup();
+ return -1;
+ }
+ }
+
+ printf("Initialization complete. Launching workers.\n");
+
+ rc = work_fn();
+
+ print_stats();
+
+ cleanup();
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: errors occured\n", argv[0]);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/reset/.gitignore b/src/spdk/test/nvme/reset/.gitignore
new file mode 100644
index 00000000..a16781b1
--- /dev/null
+++ b/src/spdk/test/nvme/reset/.gitignore
@@ -0,0 +1 @@
+reset
diff --git a/src/spdk/test/nvme/reset/Makefile b/src/spdk/test/nvme/reset/Makefile
new file mode 100644
index 00000000..440f385c
--- /dev/null
+++ b/src/spdk/test/nvme/reset/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = reset
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/reset/reset.c b/src/spdk/test/nvme/reset/reset.c
new file mode 100644
index 00000000..fe2004e8
--- /dev/null
+++ b/src/spdk/test/nvme/reset/reset.c
@@ -0,0 +1,689 @@
+/*-
+ * 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"
+#include "spdk/string.h"
+
+struct ctrlr_entry {
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ctrlr_entry *next;
+ char name[1024];
+};
+
+struct ns_entry {
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_ctrlr *ctrlr;
+ struct ns_entry *next;
+ uint32_t io_size_blocks;
+ uint64_t size_in_ios;
+ char name[1024];
+};
+
+struct ns_worker_ctx {
+ struct ns_entry *entry;
+ struct spdk_nvme_qpair *qpair;
+ uint64_t io_completed;
+ uint64_t io_completed_error;
+ uint64_t io_submitted;
+ uint64_t current_queue_depth;
+ uint64_t offset_in_ios;
+ bool is_draining;
+
+ struct ns_worker_ctx *next;
+};
+
+struct reset_task {
+ struct ns_worker_ctx *ns_ctx;
+ void *buf;
+};
+
+struct worker_thread {
+ struct ns_worker_ctx *ns_ctx;
+ unsigned lcore;
+};
+
+static struct spdk_mempool *task_pool;
+
+static struct ctrlr_entry *g_controllers = NULL;
+static struct ns_entry *g_namespaces = NULL;
+static int g_num_namespaces = 0;
+static struct worker_thread *g_workers = NULL;
+
+static uint64_t g_tsc_rate;
+
+static int g_io_size_bytes;
+static int g_rw_percentage;
+static int g_is_random;
+static int g_queue_depth;
+static int g_time_in_sec;
+
+#define TASK_POOL_NUM 8192
+
+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;
+
+ if (!spdk_nvme_ns_is_active(ns)) {
+ printf("Skipping inactive NS %u\n", spdk_nvme_ns_get_id(ns));
+ return;
+ }
+
+ entry = malloc(sizeof(struct ns_entry));
+ if (entry == NULL) {
+ perror("ns_entry malloc");
+ exit(1);
+ }
+
+ cdata = spdk_nvme_ctrlr_get_data(ctrlr);
+
+ entry->ns = ns;
+ entry->ctrlr = ctrlr;
+ entry->size_in_ios = spdk_nvme_ns_get_size(ns) /
+ g_io_size_bytes;
+ entry->io_size_blocks = g_io_size_bytes / spdk_nvme_ns_get_sector_size(ns);
+
+ snprintf(entry->name, 44, "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);
+
+ g_num_namespaces++;
+ entry->next = g_namespaces;
+ g_namespaces = entry;
+}
+
+static void
+register_ctrlr(struct spdk_nvme_ctrlr *ctrlr)
+{
+ int nsid, num_ns;
+ struct spdk_nvme_ns *ns;
+ struct ctrlr_entry *entry = malloc(sizeof(struct ctrlr_entry));
+
+ if (entry == NULL) {
+ perror("ctrlr_entry malloc");
+ exit(1);
+ }
+
+ entry->ctrlr = ctrlr;
+ entry->next = g_controllers;
+ g_controllers = entry;
+
+ num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
+ for (nsid = 1; nsid <= num_ns; nsid++) {
+ ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid);
+ if (ns == NULL) {
+ continue;
+ }
+ register_ns(ctrlr, ns);
+ }
+}
+
+static void io_complete(void *ctx, const struct spdk_nvme_cpl *completion);
+
+static __thread unsigned int seed = 0;
+
+static void
+submit_single_io(struct ns_worker_ctx *ns_ctx)
+{
+ struct reset_task *task = NULL;
+ uint64_t offset_in_ios;
+ int rc;
+ struct ns_entry *entry = ns_ctx->entry;
+
+ task = spdk_mempool_get(task_pool);
+ if (!task) {
+ fprintf(stderr, "Failed to get task from task_pool\n");
+ exit(1);
+ }
+
+ task->buf = spdk_dma_zmalloc(g_io_size_bytes, 0x200, NULL);
+ if (!task->buf) {
+ spdk_dma_free(task->buf);
+ fprintf(stderr, "task->buf spdk_dma_zmalloc failed\n");
+ exit(1);
+ }
+
+ task->ns_ctx = ns_ctx;
+ task->ns_ctx->io_submitted++;
+
+ if (g_is_random) {
+ offset_in_ios = rand_r(&seed) % entry->size_in_ios;
+ } else {
+ offset_in_ios = ns_ctx->offset_in_ios++;
+ if (ns_ctx->offset_in_ios == entry->size_in_ios) {
+ ns_ctx->offset_in_ios = 0;
+ }
+ }
+
+ if ((g_rw_percentage == 100) ||
+ (g_rw_percentage != 0 && ((rand_r(&seed) % 100) < g_rw_percentage))) {
+ rc = spdk_nvme_ns_cmd_read(entry->ns, ns_ctx->qpair, task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, task, 0);
+ } else {
+ rc = spdk_nvme_ns_cmd_write(entry->ns, ns_ctx->qpair, task->buf,
+ offset_in_ios * entry->io_size_blocks,
+ entry->io_size_blocks, io_complete, task, 0);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "starting I/O failed\n");
+ }
+
+ ns_ctx->current_queue_depth++;
+}
+
+static void
+task_complete(struct reset_task *task, const struct spdk_nvme_cpl *completion)
+{
+ struct ns_worker_ctx *ns_ctx;
+
+ ns_ctx = task->ns_ctx;
+ ns_ctx->current_queue_depth--;
+
+ if (spdk_nvme_cpl_is_error(completion)) {
+ ns_ctx->io_completed_error++;
+ } else {
+ ns_ctx->io_completed++;
+ }
+
+ spdk_dma_free(task->buf);
+ spdk_mempool_put(task_pool, task);
+
+ /*
+ * is_draining indicates when time has expired for the test run
+ * and we are just waiting for the previously submitted I/O
+ * to complete. In this case, do not submit a new I/O to replace
+ * the one just completed.
+ */
+ if (!ns_ctx->is_draining) {
+ submit_single_io(ns_ctx);
+ }
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *completion)
+{
+ task_complete((struct reset_task *)ctx, completion);
+}
+
+static void
+check_io(struct ns_worker_ctx *ns_ctx)
+{
+ spdk_nvme_qpair_process_completions(ns_ctx->qpair, 0);
+}
+
+static void
+submit_io(struct ns_worker_ctx *ns_ctx, int queue_depth)
+{
+ while (queue_depth-- > 0) {
+ submit_single_io(ns_ctx);
+ }
+}
+
+static void
+drain_io(struct ns_worker_ctx *ns_ctx)
+{
+ ns_ctx->is_draining = true;
+ while (ns_ctx->current_queue_depth > 0) {
+ check_io(ns_ctx);
+ }
+}
+
+static int
+work_fn(void *arg)
+{
+ uint64_t tsc_end = spdk_get_ticks() + g_time_in_sec * g_tsc_rate;
+ struct worker_thread *worker = (struct worker_thread *)arg;
+ struct ns_worker_ctx *ns_ctx = NULL;
+ bool did_reset = false;
+
+ printf("Starting thread on core %u\n", worker->lcore);
+
+ /* Submit initial I/O for each namespace. */
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ ns_ctx->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_ctx->entry->ctrlr, NULL, 0);
+ if (ns_ctx->qpair == NULL) {
+ fprintf(stderr, "spdk_nvme_ctrlr_alloc_io_qpair() failed on core %u\n", worker->lcore);
+ return -1;
+ }
+ submit_io(ns_ctx, g_queue_depth);
+ ns_ctx = ns_ctx->next;
+ }
+
+ while (1) {
+ /*
+ * Check for completed I/O for each controller. A new
+ * I/O will be submitted in the io_complete callback
+ * to replace each I/O that is completed.
+ */
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ check_io(ns_ctx);
+ ns_ctx = ns_ctx->next;
+ }
+
+ if (!did_reset && ((tsc_end - spdk_get_ticks()) / g_tsc_rate) > (uint64_t)g_time_in_sec / 2) {
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ if (spdk_nvme_ctrlr_reset(ns_ctx->entry->ctrlr) < 0) {
+ fprintf(stderr, "nvme reset failed.\n");
+ return -1;
+ }
+ ns_ctx = ns_ctx->next;
+ }
+ did_reset = true;
+ }
+
+ if (spdk_get_ticks() > tsc_end) {
+ break;
+ }
+ }
+
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ drain_io(ns_ctx);
+ spdk_nvme_ctrlr_free_io_qpair(ns_ctx->qpair);
+ ns_ctx = ns_ctx->next;
+ }
+
+ return 0;
+}
+
+static void usage(char *program_name)
+{
+ printf("%s options", program_name);
+ printf("\n");
+ printf("\t[-q io depth]\n");
+ printf("\t[-s io size in bytes]\n");
+ printf("\t[-w io pattern type, must be one of\n");
+ printf("\t\t(read, write, randread, randwrite, rw, randrw)]\n");
+ printf("\t[-M rwmixread (100 for reads, 0 for writes)]\n");
+ printf("\t[-t time in seconds(should be larger than 15 seconds)]\n");
+ printf("\t[-m max completions per poll]\n");
+ printf("\t\t(default:0 - unlimited)\n");
+}
+
+static int
+print_stats(void)
+{
+ uint64_t io_completed, io_submitted, io_completed_error;
+ uint64_t total_completed_io, total_submitted_io, total_completed_err_io;
+ struct worker_thread *worker;
+ struct ns_worker_ctx *ns_ctx;
+
+ total_completed_io = 0;
+ total_submitted_io = 0;
+ total_completed_err_io = 0;
+
+ worker = g_workers;
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx) {
+ io_completed = ns_ctx->io_completed;
+ io_submitted = ns_ctx->io_submitted;
+ io_completed_error = ns_ctx->io_completed_error;
+ total_completed_io += io_completed;
+ total_submitted_io += io_submitted;
+ total_completed_err_io += io_completed_error;
+ ns_ctx = ns_ctx->next;
+ }
+
+ printf("========================================================\n");
+ printf("%16lu IO completed successfully\n", total_completed_io);
+ printf("%16lu IO completed with error\n", total_completed_err_io);
+ printf("--------------------------------------------------------\n");
+ printf("%16lu IO completed total\n", total_completed_io + total_completed_err_io);
+ printf("%16lu IO submitted\n", total_submitted_io);
+
+ if (total_submitted_io != (total_completed_io + total_completed_err_io)) {
+ fprintf(stderr, "Some IO are missing......\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ const char *workload_type;
+ int op;
+ bool mix_specified = false;
+
+ /* default value */
+ g_queue_depth = 0;
+ g_io_size_bytes = 0;
+ workload_type = NULL;
+ g_time_in_sec = 0;
+ g_rw_percentage = -1;
+
+ while ((op = getopt(argc, argv, "m:q:s:t:w:M:")) != -1) {
+ switch (op) {
+ case 'q':
+ g_queue_depth = atoi(optarg);
+ break;
+ case 's':
+ g_io_size_bytes = atoi(optarg);
+ break;
+ case 't':
+ g_time_in_sec = atoi(optarg);
+ break;
+ case 'w':
+ workload_type = optarg;
+ break;
+ case 'M':
+ g_rw_percentage = atoi(optarg);
+ mix_specified = true;
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (!g_queue_depth) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_io_size_bytes) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!workload_type) {
+ usage(argv[0]);
+ return 1;
+ }
+ if (!g_time_in_sec) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ if (strcmp(workload_type, "read") &&
+ strcmp(workload_type, "write") &&
+ strcmp(workload_type, "randread") &&
+ strcmp(workload_type, "randwrite") &&
+ strcmp(workload_type, "rw") &&
+ strcmp(workload_type, "randrw")) {
+ fprintf(stderr,
+ "io pattern type must be one of\n"
+ "(read, write, randread, randwrite, rw, randrw)\n");
+ return 1;
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "randread")) {
+ g_rw_percentage = 100;
+ }
+
+ if (!strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "randwrite")) {
+ g_rw_percentage = 0;
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "randread") ||
+ !strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "randwrite")) {
+ if (mix_specified) {
+ fprintf(stderr, "Ignoring -M option... Please use -M option"
+ " only when using rw or randrw.\n");
+ }
+ }
+
+ if (!strcmp(workload_type, "rw") ||
+ !strcmp(workload_type, "randrw")) {
+ if (g_rw_percentage < 0 || g_rw_percentage > 100) {
+ fprintf(stderr,
+ "-M must be specified to value from 0 to 100 "
+ "for rw or randrw.\n");
+ return 1;
+ }
+ }
+
+ if (!strcmp(workload_type, "read") ||
+ !strcmp(workload_type, "write") ||
+ !strcmp(workload_type, "rw")) {
+ g_is_random = 0;
+ } else {
+ g_is_random = 1;
+ }
+
+ return 0;
+}
+
+static int
+register_workers(void)
+{
+ struct worker_thread *worker;
+
+ worker = malloc(sizeof(struct worker_thread));
+ if (worker == NULL) {
+ perror("worker_thread malloc");
+ return -1;
+ }
+
+ memset(worker, 0, sizeof(struct worker_thread));
+ worker->lcore = spdk_env_get_current_core();
+
+ g_workers = worker;
+
+ return 0;
+}
+
+
+static bool
+probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
+ struct spdk_nvme_ctrlr_opts *opts)
+{
+ 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)
+{
+ register_ctrlr(ctrlr);
+}
+
+static int
+register_controllers(void)
+{
+ printf("Initializing NVMe Controllers\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "spdk_nvme_probe() failed\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+unregister_controllers(void)
+{
+ struct ctrlr_entry *entry = g_controllers;
+
+ while (entry) {
+ struct ctrlr_entry *next = entry->next;
+ spdk_nvme_detach(entry->ctrlr);
+ free(entry);
+ entry = next;
+ }
+}
+
+static int
+associate_workers_with_ns(void)
+{
+ struct ns_entry *entry = g_namespaces;
+ struct worker_thread *worker = g_workers;
+ struct ns_worker_ctx *ns_ctx;
+ int i, count;
+
+ count = g_num_namespaces;
+
+ for (i = 0; i < count; i++) {
+ if (entry == NULL) {
+ break;
+ }
+ ns_ctx = malloc(sizeof(struct ns_worker_ctx));
+ if (!ns_ctx) {
+ return -1;
+ }
+ memset(ns_ctx, 0, sizeof(*ns_ctx));
+
+ printf("Associating %s with lcore %d\n", entry->name, worker->lcore);
+ ns_ctx->entry = entry;
+ ns_ctx->next = worker->ns_ctx;
+ worker->ns_ctx = ns_ctx;
+
+ worker = g_workers;
+
+ entry = entry->next;
+ if (entry == NULL) {
+ entry = g_namespaces;
+ }
+ }
+
+ return 0;
+}
+
+static int
+run_nvme_reset_cycle(int retry_count)
+{
+ struct worker_thread *worker;
+ struct ns_worker_ctx *ns_ctx;
+
+ spdk_nvme_retry_count = retry_count;
+
+ if (work_fn(g_workers) != 0) {
+ return -1;
+ }
+
+ if (print_stats() != 0) {
+ return -1;
+ }
+
+ worker = g_workers;
+ ns_ctx = worker->ns_ctx;
+ while (ns_ctx != NULL) {
+ ns_ctx->io_completed = 0;
+ ns_ctx->io_completed_error = 0;
+ ns_ctx->io_submitted = 0;
+ ns_ctx->is_draining = false;
+ ns_ctx = ns_ctx->next;
+ }
+
+ return 0;
+}
+
+static void
+spdk_reset_free_tasks(void)
+{
+ if (spdk_mempool_count(task_pool) != TASK_POOL_NUM) {
+ fprintf(stderr, "task_pool count is %zu but should be %d\n",
+ spdk_mempool_count(task_pool), TASK_POOL_NUM);
+ }
+ spdk_mempool_free(task_pool);
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ int i;
+ struct spdk_env_opts opts;
+
+
+ rc = parse_args(argc, argv);
+ if (rc != 0) {
+ return rc;
+ }
+
+ spdk_env_opts_init(&opts);
+ opts.name = "reset";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ if (register_controllers() != 0) {
+ return 1;
+ }
+
+ if (!g_controllers) {
+ printf("No NVMe controller found, %s exiting\n", argv[0]);
+ return 1;
+ }
+
+ task_pool = spdk_mempool_create("task_pool", TASK_POOL_NUM,
+ sizeof(struct reset_task),
+ 64, SPDK_ENV_SOCKET_ID_ANY);
+ if (!task_pool) {
+ fprintf(stderr, "Cannot create task pool\n");
+ return 1;
+ }
+
+ g_tsc_rate = spdk_get_ticks_hz();
+
+ if (register_workers() != 0) {
+ return 1;
+ }
+
+ if (associate_workers_with_ns() != 0) {
+ rc = 1;
+ goto cleanup;
+ }
+
+ printf("Initialization complete. Launching workers.\n");
+
+ for (i = 2; i >= 0; i--) {
+ rc = run_nvme_reset_cycle(i);
+ if (rc != 0) {
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ unregister_controllers();
+ spdk_reset_free_tasks();
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: errors occured\n", argv[0]);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/sgl/.gitignore b/src/spdk/test/nvme/sgl/.gitignore
new file mode 100644
index 00000000..d1cebd68
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/.gitignore
@@ -0,0 +1 @@
+sgl
diff --git a/src/spdk/test/nvme/sgl/Makefile b/src/spdk/test/nvme/sgl/Makefile
new file mode 100644
index 00000000..f0e0fc50
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/Makefile
@@ -0,0 +1,39 @@
+#
+# 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 = sgl
+
+include $(SPDK_ROOT_DIR)/mk/nvme.libtest.mk
diff --git a/src/spdk/test/nvme/sgl/sgl.c b/src/spdk/test/nvme/sgl/sgl.c
new file mode 100644
index 00000000..ccff652c
--- /dev/null
+++ b/src/spdk/test/nvme/sgl/sgl.c
@@ -0,0 +1,542 @@
+/*-
+ * 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"
+#include "spdk/util.h"
+
+#define MAX_DEVS 64
+
+#define MAX_IOVS 128
+
+#define DATA_PATTERN 0x5A
+
+#define BASE_LBA_START 0x100000
+
+struct dev {
+ struct spdk_nvme_ctrlr *ctrlr;
+ char name[SPDK_NVMF_TRADDR_MAX_LEN + 1];
+};
+
+static struct dev devs[MAX_DEVS];
+static int num_devs = 0;
+
+#define foreach_dev(iter) \
+ for (iter = devs; iter - devs < num_devs; iter++)
+
+static int io_complete_flag = 0;
+
+struct sgl_element {
+ void *base;
+ size_t offset;
+ size_t len;
+};
+
+struct io_request {
+ uint32_t current_iov_index;
+ uint32_t current_iov_bytes_left;
+ struct sgl_element iovs[MAX_IOVS];
+ uint32_t nseg;
+ uint32_t misalign;
+};
+
+static void nvme_request_reset_sgl(void *cb_arg, uint32_t sgl_offset)
+{
+ uint32_t i;
+ uint32_t offset = 0;
+ struct sgl_element *iov;
+ struct io_request *req = (struct io_request *)cb_arg;
+
+ for (i = 0; i < req->nseg; i++) {
+ iov = &req->iovs[i];
+ offset += iov->len;
+ if (offset > sgl_offset) {
+ break;
+ }
+ }
+ req->current_iov_index = i;
+ req->current_iov_bytes_left = offset - sgl_offset;
+ return;
+}
+
+static int nvme_request_next_sge(void *cb_arg, void **address, uint32_t *length)
+{
+ struct io_request *req = (struct io_request *)cb_arg;
+ struct sgl_element *iov;
+
+ if (req->current_iov_index >= req->nseg) {
+ *length = 0;
+ *address = NULL;
+ return 0;
+ }
+
+ iov = &req->iovs[req->current_iov_index];
+
+ if (req->current_iov_bytes_left) {
+ *address = iov->base + iov->offset + iov->len - req->current_iov_bytes_left;
+ *length = req->current_iov_bytes_left;
+ req->current_iov_bytes_left = 0;
+ } else {
+ *address = iov->base + iov->offset;
+ *length = iov->len;
+ }
+
+ req->current_iov_index++;
+
+ return 0;
+}
+
+static void
+io_complete(void *ctx, const struct spdk_nvme_cpl *cpl)
+{
+ if (spdk_nvme_cpl_is_error(cpl)) {
+ io_complete_flag = 2;
+ } else {
+ io_complete_flag = 1;
+ }
+}
+
+static void build_io_request_0(struct io_request *req)
+{
+ req->nseg = 1;
+
+ req->iovs[0].base = spdk_dma_zmalloc(0x800, 4, NULL);
+ req->iovs[0].len = 0x800;
+}
+
+static void build_io_request_1(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 512B for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x200, 0x200, NULL);
+ req->iovs[0].len = 0x200;
+}
+
+static void build_io_request_2(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 256KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x40000, 0x1000, NULL);
+ req->iovs[0].len = 0x40000;
+}
+
+static void build_io_request_3(struct io_request *req)
+{
+ req->nseg = 3;
+
+ /* 2KB for 1st sge, make sure the iov address start at 0x800 boundary,
+ * and end with 0x1000 boundary */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].offset = 0x800;
+ req->iovs[0].len = 0x800;
+
+ /* 4KB for 2th sge */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].len = 0x1000;
+
+ /* 12KB for 3th sge */
+ req->iovs[2].base = spdk_dma_zmalloc(0x3000, 0x1000, NULL);
+ req->iovs[2].len = 0x3000;
+}
+
+static void build_io_request_4(struct io_request *req)
+{
+ uint32_t i;
+
+ req->nseg = 32;
+
+ /* 4KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].len = 0x1000;
+
+ /* 8KB for the rest 31 sge */
+ for (i = 1; i < req->nseg; i++) {
+ req->iovs[i].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL);
+ req->iovs[i].len = 0x2000;
+ }
+}
+
+static void build_io_request_5(struct io_request *req)
+{
+ req->nseg = 1;
+
+ /* 8KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x2000, 0x1000, NULL);
+ req->iovs[0].len = 0x2000;
+}
+
+static void build_io_request_6(struct io_request *req)
+{
+ req->nseg = 2;
+
+ /* 4KB for 1st sge */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].len = 0x1000;
+
+ /* 4KB for 2st sge */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].len = 0x1000;
+}
+
+static void build_io_request_7(struct io_request *req)
+{
+ uint8_t *base;
+
+ req->nseg = 1;
+
+ /*
+ * Create a 64KB sge, but ensure it is *not* aligned on a 4KB
+ * boundary. This is valid for single element buffers with PRP.
+ */
+ base = spdk_dma_zmalloc(0x11000, 0x1000, NULL);
+ req->misalign = 64;
+ req->iovs[0].base = base + req->misalign;
+ req->iovs[0].len = 0x10000;
+}
+
+static void build_io_request_8(struct io_request *req)
+{
+ req->nseg = 2;
+
+ /*
+ * 1KB for 1st sge, make sure the iov address does not start and end
+ * at 0x1000 boundary
+ */
+ req->iovs[0].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[0].offset = 0x400;
+ req->iovs[0].len = 0x400;
+
+ /*
+ * 1KB for 1st sge, make sure the iov address does not start and end
+ * at 0x1000 boundary
+ */
+ req->iovs[1].base = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
+ req->iovs[1].offset = 0x400;
+ req->iovs[1].len = 0x400;
+}
+
+static void build_io_request_9(struct io_request *req)
+{
+ /*
+ * Check if mixed PRP complaint and not complaint requests are handled
+ * properly by splitting them into subrequests.
+ * Construct buffers with following theme:
+ */
+ const size_t req_len[] = { 2048, 4096, 2048, 4096, 2048, 1024 };
+ const size_t req_off[] = { 0x800, 0x0, 0x0, 0x100, 0x800, 0x800 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+static void build_io_request_10(struct io_request *req)
+{
+ /*
+ * Test the case where we have a valid PRP list, but the first and last
+ * elements are not exact multiples of the logical block size.
+ */
+ const size_t req_len[] = { 4004, 4096, 92 };
+ const size_t req_off[] = { 0x5c, 0x0, 0x0 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+static void build_io_request_11(struct io_request *req)
+{
+ /* This test case focuses on the last element not starting on a page boundary. */
+ const size_t req_len[] = { 512, 512 };
+ const size_t req_off[] = { 0xe00, 0x800 };
+ struct sgl_element *iovs = req->iovs;
+ uint32_t i;
+ req->nseg = SPDK_COUNTOF(req_len);
+ assert(SPDK_COUNTOF(req_len) == SPDK_COUNTOF(req_off));
+
+ for (i = 0; i < req->nseg; i++) {
+ iovs[i].base = spdk_dma_zmalloc(req_off[i] + req_len[i], 0x4000, NULL);
+ iovs[i].offset = req_off[i];
+ iovs[i].len = req_len[i];
+ }
+}
+
+typedef void (*nvme_build_io_req_fn_t)(struct io_request *req);
+
+static void
+free_req(struct io_request *req)
+{
+ uint32_t i;
+
+ if (req == NULL) {
+ return;
+ }
+
+ for (i = 0; i < req->nseg; i++) {
+ spdk_dma_free(req->iovs[i].base - req->misalign);
+ }
+
+ spdk_dma_free(req);
+}
+
+static int
+writev_readv_tests(struct dev *dev, nvme_build_io_req_fn_t build_io_fn, const char *test_name)
+{
+ int rc = 0;
+ uint32_t len, lba_count;
+ uint32_t i, j, nseg, remainder;
+ char *buf;
+
+ struct io_request *req;
+ struct spdk_nvme_ns *ns;
+ struct spdk_nvme_qpair *qpair;
+ const struct spdk_nvme_ns_data *nsdata;
+
+ ns = spdk_nvme_ctrlr_get_ns(dev->ctrlr, 1);
+ if (!ns) {
+ fprintf(stderr, "Null namespace\n");
+ return 0;
+ }
+ nsdata = spdk_nvme_ns_get_data(ns);
+ if (!nsdata || !spdk_nvme_ns_get_sector_size(ns)) {
+ fprintf(stderr, "Empty nsdata or wrong sector size\n");
+ return 0;
+ }
+
+ if (spdk_nvme_ns_get_flags(ns) & SPDK_NVME_NS_DPS_PI_SUPPORTED) {
+ return 0;
+ }
+
+ req = spdk_dma_zmalloc(sizeof(*req), 0, NULL);
+ if (!req) {
+ fprintf(stderr, "Allocate request failed\n");
+ return 0;
+ }
+
+ /* IO parameters setting */
+ build_io_fn(req);
+
+ len = 0;
+ for (i = 0; i < req->nseg; i++) {
+ struct sgl_element *sge = &req->iovs[i];
+
+ len += sge->len;
+ }
+
+ lba_count = len / spdk_nvme_ns_get_sector_size(ns);
+ remainder = len % spdk_nvme_ns_get_sector_size(ns);
+ if (!lba_count || remainder || (BASE_LBA_START + lba_count > (uint32_t)nsdata->nsze)) {
+ fprintf(stderr, "%s: %s Invalid IO length parameter\n", dev->name, test_name);
+ free_req(req);
+ return 0;
+ }
+
+ qpair = spdk_nvme_ctrlr_alloc_io_qpair(dev->ctrlr, NULL, 0);
+ if (!qpair) {
+ free_req(req);
+ return -1;
+ }
+
+ nseg = req->nseg;
+ for (i = 0; i < nseg; i++) {
+ memset(req->iovs[i].base + req->iovs[i].offset, DATA_PATTERN, req->iovs[i].len);
+ }
+
+ rc = spdk_nvme_ns_cmd_writev(ns, qpair, BASE_LBA_START, lba_count,
+ io_complete, req, 0,
+ nvme_request_reset_sgl,
+ nvme_request_next_sge);
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ io_complete_flag = 0;
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s writev failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ /* reset completion flag */
+ io_complete_flag = 0;
+
+ for (i = 0; i < nseg; i++) {
+ memset(req->iovs[i].base + req->iovs[i].offset, 0, req->iovs[i].len);
+ }
+
+ rc = spdk_nvme_ns_cmd_readv(ns, qpair, BASE_LBA_START, lba_count,
+ io_complete, req, 0,
+ nvme_request_reset_sgl,
+ nvme_request_next_sge);
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ while (!io_complete_flag) {
+ spdk_nvme_qpair_process_completions(qpair, 1);
+ }
+
+ if (io_complete_flag != 1) {
+ fprintf(stderr, "%s: %s readv failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+
+ for (i = 0; i < nseg; i++) {
+ buf = (char *)req->iovs[i].base + req->iovs[i].offset;
+ for (j = 0; j < req->iovs[i].len; j++) {
+ if (buf[j] != DATA_PATTERN) {
+ fprintf(stderr, "%s: %s write/read success, but memcmp Failed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return -1;
+ }
+ }
+ }
+
+ fprintf(stdout, "%s: %s test passed\n", dev->name, test_name);
+ spdk_nvme_ctrlr_free_io_qpair(qpair);
+ free_req(req);
+ return rc;
+}
+
+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)
+{
+ struct dev *dev;
+
+ /* add to dev list */
+ dev = &devs[num_devs++];
+
+ dev->ctrlr = ctrlr;
+
+ snprintf(dev->name, sizeof(dev->name), "%s",
+ trid->traddr);
+
+ printf("Attached to %s\n", dev->name);
+}
+
+int main(int argc, char **argv)
+{
+ struct dev *iter;
+ int rc, i;
+ struct spdk_env_opts opts;
+
+ spdk_env_opts_init(&opts);
+ opts.name = "nvme_sgl";
+ opts.core_mask = "0x1";
+ opts.shm_id = 0;
+ if (spdk_env_init(&opts) < 0) {
+ fprintf(stderr, "Unable to initialize SPDK env\n");
+ return 1;
+ }
+
+ printf("NVMe Readv/Writev Request test\n");
+
+ if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
+ fprintf(stderr, "nvme_probe() failed\n");
+ exit(1);
+ }
+
+ rc = 0;
+ foreach_dev(iter) {
+#define TEST(x) writev_readv_tests(iter, x, #x)
+ if (TEST(build_io_request_0)
+ || TEST(build_io_request_1)
+ || TEST(build_io_request_2)
+ || TEST(build_io_request_3)
+ || TEST(build_io_request_4)
+ || TEST(build_io_request_5)
+ || TEST(build_io_request_6)
+ || TEST(build_io_request_7)
+ || TEST(build_io_request_8)
+ || TEST(build_io_request_9)
+ || TEST(build_io_request_10)
+ || TEST(build_io_request_11)) {
+#undef TEST
+ rc = 1;
+ printf("%s: failed sgl tests\n", iter->name);
+ }
+ }
+
+ printf("Cleaning up...\n");
+
+ for (i = 0; i < num_devs; i++) {
+ struct dev *dev = &devs[i];
+
+ spdk_nvme_detach(dev->ctrlr);
+ }
+
+ return rc;
+}
diff --git a/src/spdk/test/nvme/spdk_nvme_cli.sh b/src/spdk/test/nvme/spdk_nvme_cli.sh
new file mode 100755
index 00000000..0051df1d
--- /dev/null
+++ b/src/spdk/test/nvme/spdk_nvme_cli.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+set -ex
+
+testdir=$(readlink -f $(dirname $0))
+rootdir=$(readlink -f $testdir/../..)
+source $rootdir/scripts/common.sh
+source $rootdir/test/common/autotest_common.sh
+
+if [ -z "${DEPENDENCY_DIR}" ]; then
+ echo DEPENDENCY_DIR not defined!
+ exit 1
+fi
+
+spdk_nvme_cli="${DEPENDENCY_DIR}/nvme-cli"
+
+if [ ! -d $spdk_nvme_cli ]; then
+ echo "nvme-cli repository not found at $spdk_nvme_cli; skipping tests."
+ exit 0
+fi
+
+timing_enter nvme_cli
+
+if [ `uname` = Linux ]; then
+ start_stub "-s 2048 -i 0 -m 0xF"
+ trap "kill_stub; exit 1" SIGINT SIGTERM EXIT
+fi
+
+# Build against the version of SPDK under test
+rm -f "$spdk_nvme_cli/spdk"
+ln -sf "$rootdir" "$spdk_nvme_cli/spdk"
+
+bdfs=$(iter_pci_class_code 01 08 02)
+bdf=$(echo $bdfs|awk '{ print $1 }')
+
+cd $spdk_nvme_cli
+make clean && make -j$(nproc) LDFLAGS="$(make -s -C $spdk_nvme_cli/spdk ldflags)"
+sed -i 's/spdk=0/spdk=1/g' spdk.conf
+sed -i 's/shm_id=1/shm_id=0/g' spdk.conf
+./nvme list
+./nvme id-ctrl $bdf
+./nvme list-ctrl $bdf
+./nvme get-ns-id $bdf
+./nvme id-ns $bdf
+./nvme fw-log $bdf
+./nvme smart-log $bdf
+./nvme error-log $bdf
+./nvme list-ns $bdf -n 1
+./nvme get-feature $bdf -n 1 -f 1 -s 1 -l 100
+./nvme get-log $bdf -n 1 -i 1 -l 100
+./nvme reset $bdf
+if [ `uname` = Linux ]; then
+ trap - SIGINT SIGTERM EXIT
+ kill_stub
+fi
+
+report_test_completion spdk_nvme_cli
+timing_exit nvme_cli