diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:24:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:24:20 +0000 |
commit | 483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch) | |
tree | e5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/spdk/test/nvme | |
parent | Initial commit. (diff) | |
download | ceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.tar.xz ceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.zip |
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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 |