diff --git a/test/Makefile b/test/Makefile index 43fcdc06ea..b0d6ce948d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -35,7 +35,7 @@ SPDK_ROOT_DIR := $(abspath $(CURDIR)/..) include $(SPDK_ROOT_DIR)/mk/spdk.common.mk # These directories contain tests. -TESTDIRS = app bdev blobfs cpp_headers env event nvme rpc_client thread +TESTDIRS = app bdev blobfs cpp_headers dma env event nvme rpc_client thread DIRS-$(CONFIG_TESTS) += $(TESTDIRS) DIRS-$(CONFIG_UNIT_TESTS) += unit diff --git a/test/dma/Makefile b/test/dma/Makefile new file mode 100644 index 0000000000..22ae3d1d21 --- /dev/null +++ b/test/dma/Makefile @@ -0,0 +1,45 @@ +# +# BSD LICENSE +# +# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. 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 Nvidia 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 + +ifeq ($(CONFIG_RDMA),y) +DIRS-y = test_dma +endif + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/test/dma/test_dma/.gitignore b/test/dma/test_dma/.gitignore new file mode 100644 index 0000000000..92186b99f1 --- /dev/null +++ b/test/dma/test_dma/.gitignore @@ -0,0 +1 @@ +test_dma diff --git a/test/dma/test_dma/Makefile b/test/dma/test_dma/Makefile new file mode 100644 index 0000000000..09fb747978 --- /dev/null +++ b/test/dma/test_dma/Makefile @@ -0,0 +1,42 @@ +# +# BSD LICENSE +# +# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. 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 Nvidia 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 +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = test_dma +C_SRCS := test_dma.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) dma event event_bdev + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/test/dma/test_dma/test_dma.c b/test/dma/test_dma/test_dma.c new file mode 100644 index 0000000000..911de7e979 --- /dev/null +++ b/test/dma/test_dma/test_dma.c @@ -0,0 +1,350 @@ +/*- + * BSD LICENSE + * + * Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. 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 Nvidia 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/bdev.h" +#include "spdk/event.h" +#include "spdk/dma.h" +#include + +#define DMA_TEST_IO_BUFFER_SIZE 4096 + +static char *g_bdev_name; + +static int +parse_arg(int ch, char *arg) +{ + if (ch == 'b') { + g_bdev_name = optarg; + } else { + fprintf(stderr, "Unknown option %c\n", ch); + return 1; + } + return 0; +} + +static void +print_usage(void) +{ + printf(" -b bdev name for test\n"); +} + +static void +dma_test_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx) +{ +} + +struct dma_test_ctx { + const char *bdev_name; + struct spdk_bdev_desc *desc; + struct spdk_io_channel *ch; + struct spdk_memory_domain *memory_domain; + void *write_io_buffer; + void *read_io_buffer; + struct spdk_bdev_ext_io_opts ext_io_opts; + struct ibv_mr *mr; + uint64_t num_blocks; +}; + +static int +dma_test_translate_memory_cb(struct spdk_memory_domain *src_domain, void *src_domain_ctx, + struct spdk_memory_domain *dst_domain, struct spdk_memory_domain_translation_ctx *dst_domain_ctx, + void *addr, size_t len, struct spdk_memory_domain_translation_result *result) +{ + struct dma_test_ctx *ctx = src_domain_ctx; + struct ibv_qp *dst_domain_qp = (struct ibv_qp *)dst_domain_ctx->rdma.ibv_qp; + + fprintf(stdout, "Translating memory\n"); + + ctx->mr = ibv_reg_mr(dst_domain_qp->pd, addr, len, IBV_ACCESS_LOCAL_WRITE | + IBV_ACCESS_REMOTE_READ | + IBV_ACCESS_REMOTE_WRITE); + if (!ctx->mr) { + fprintf(stderr, "Failed to register memory region, errno %d\n", errno); + return -1; + } + + result->len = len; + result->addr = addr; + result->rdma.lkey = ctx->mr->lkey; + result->rdma.rkey = ctx->mr->rkey; + result->dst_domain = dst_domain; + + return 0; +} + +static void +dma_test_cleanup(struct dma_test_ctx *ctx) +{ + if (ctx->ch) { + spdk_put_io_channel(ctx->ch); + ctx->ch = NULL; + } + if (ctx->desc) { + spdk_bdev_close(ctx->desc); + ctx->desc = NULL; + } + spdk_memory_domain_destroy(ctx->memory_domain); + ctx->memory_domain = NULL; + if (ctx->mr) { + ibv_dereg_mr(ctx->mr); + ctx->mr = NULL; + } + free(ctx->write_io_buffer); + ctx->write_io_buffer = NULL; + free(ctx->read_io_buffer); + ctx->read_io_buffer = NULL; +} + +static void +dma_test_read_completed(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct dma_test_ctx *ctx = cb_arg; + int sct, sc; + uint32_t cdw0; + + if (success) { + spdk_bdev_free_io(bdev_io); + } else { + spdk_bdev_io_get_nvme_status(bdev_io, &cdw0, &sct, &sc); + fprintf(stderr, "bdev read IO failed, cdw0 %x, sct %d, sc %d\n", cdw0, sct, sc); + spdk_app_stop(-1); + return; + } + + if (memcmp(ctx->write_io_buffer, ctx->read_io_buffer, DMA_TEST_IO_BUFFER_SIZE)) { + fprintf(stderr, "Read buffer doesn't match written data!\n"); + spdk_app_stop(-1); + return; + } + + fprintf(stdout, "DMA test completed successfully\n"); + + dma_test_cleanup(ctx); + + spdk_app_stop(0); +} + +static void +dma_test_write_completed(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct dma_test_ctx *ctx = cb_arg; + struct iovec iov; + int sct, sc, rc; + uint32_t cdw0; + + if (success) { + spdk_bdev_free_io(bdev_io); + } else { + spdk_bdev_io_get_nvme_status(bdev_io, &cdw0, &sct, &sc); + fprintf(stderr, "bdev write IO failed, cdw0 %x, sct %d, sc %d\n", cdw0, sct, sc); + spdk_app_stop(-1); + return; + } + + fprintf(stdout, "Write IO completed, submitting read IO\n"); + + ibv_dereg_mr(ctx->mr); + + iov.iov_base = ctx->read_io_buffer; + iov.iov_len = DMA_TEST_IO_BUFFER_SIZE; + + rc = spdk_bdev_readv_blocks_ext(ctx->desc, ctx->ch, &iov, 1, 0, ctx->num_blocks, + dma_test_read_completed, ctx, &ctx->ext_io_opts); + if (rc) { + fprintf(stderr, "Falied to submit read operation"); + spdk_app_stop(-1); + } +} + +static bool +dma_test_check_bdev_supports_rdma_memory_domain(struct dma_test_ctx *ctx) +{ + struct spdk_memory_domain **bdev_domains; + int bdev_domains_count, bdev_domains_count_tmp, i; + bool rdma_domain_supported = false; + + bdev_domains_count = spdk_bdev_get_memory_domains(spdk_bdev_desc_get_bdev(ctx->desc), NULL, 0); + + if (bdev_domains_count < 0) { + fprintf(stderr, "Failed to get bdev memory domains count, rc %d\n", bdev_domains_count); + return false; + } else if (bdev_domains_count == 0) { + fprintf(stderr, "bdev %s doesn't support any memory domains\n", ctx->bdev_name); + return false; + } + + fprintf(stdout, "bdev %s reports %d memory domains\n", ctx->bdev_name, bdev_domains_count); + + bdev_domains = calloc((size_t)bdev_domains_count, sizeof(*bdev_domains)); + if (!bdev_domains) { + fprintf(stderr, "Failed to allocate memory domains\n"); + return false; + } + + bdev_domains_count_tmp = spdk_bdev_get_memory_domains(spdk_bdev_desc_get_bdev(ctx->desc), + bdev_domains, bdev_domains_count); + if (bdev_domains_count_tmp != bdev_domains_count) { + fprintf(stderr, "Unexpected bdev domains return value %d\n", bdev_domains_count_tmp); + return false; + } + + for (i = 0; i < bdev_domains_count; i++) { + if (spdk_memory_domain_get_dma_device_type(bdev_domains[i]) == SPDK_DMA_DEVICE_TYPE_RDMA) { + /* Bdev supports memory domain of RDMA type, we can try to submit IO request to it using + * bdev ext API */ + rdma_domain_supported = true; + break; + } + } + + fprintf(stdout, "bdev %s %s RDMA memory domain\n", ctx->bdev_name, + rdma_domain_supported ? "supports" : "doesn't support"); + free(bdev_domains); + + return rdma_domain_supported; +} + +static void +dma_test_run(void *arg) +{ + struct dma_test_ctx *ctx = arg; + + struct iovec iov; + int rc; + + /* Test scenario: + * 1. Open bdev, check that it supports RDMA memory domain + * 2. Allocate IO buffer using regular malloc. In that case SPDK NVME_RDMA driver won't create a + * memory region for this IO and won't be able to find memory keys + * 3. Create dma memory domain which translation callback creates a memory region and + * returns memory keys to NVME RDMA driver + * 4. Do the same for read operation, compare buffers when done */ + + /* Prepare bdev */ + rc = spdk_bdev_open_ext(ctx->bdev_name, true, dma_test_bdev_event_cb, NULL, &ctx->desc); + if (rc) { + fprintf(stderr, "Failed to open bdev %s\n", ctx->bdev_name); + spdk_app_stop(-1); + return; + } + + ctx->ch = spdk_bdev_get_io_channel(ctx->desc); + if (!ctx->ch) { + fprintf(stderr, "Failed to get io chanel for bdev %s\n", ctx->bdev_name); + spdk_bdev_close(ctx->desc); + spdk_app_stop(-1); + return; + } + + if (!dma_test_check_bdev_supports_rdma_memory_domain(ctx)) { + spdk_bdev_close(ctx->desc); + spdk_app_stop(-1); + return; + } + + ctx->num_blocks = DMA_TEST_IO_BUFFER_SIZE / spdk_bdev_get_block_size(spdk_bdev_desc_get_bdev( + ctx->desc)); + + /* Create a memory domain to represent the source memory domain. + * Since we don't actually have a remote memory domain in this test, this will describe memory + * on the local system and the translation to the destination memory domain will be trivial. + * But this at least allows us to demonstrate the flow and test the functionality. */ + rc = spdk_memory_domain_create(&ctx->memory_domain, SPDK_DMA_DEVICE_TYPE_RDMA, NULL, "test_dma"); + if (rc) { + fprintf(stderr, "Can't create memory domain, rc %d\n", rc); + spdk_app_stop(-1); + return; + } + + spdk_memory_domain_set_translation(ctx->memory_domain, dma_test_translate_memory_cb); + + ctx->write_io_buffer = malloc(DMA_TEST_IO_BUFFER_SIZE); + if (!ctx->write_io_buffer) { + fprintf(stderr, "IO buffer allocation failed"); + spdk_app_stop(-1);; + return; + } + memset(ctx->write_io_buffer, 0xd, DMA_TEST_IO_BUFFER_SIZE); + + ctx->read_io_buffer = malloc(DMA_TEST_IO_BUFFER_SIZE); + if (!ctx->read_io_buffer) { + fprintf(stderr, "IO buffer allocation failed"); + spdk_app_stop(-1);; + return; + } + + ctx->ext_io_opts.memory_domain = ctx->memory_domain; + ctx->ext_io_opts.memory_domain_ctx = ctx; + iov.iov_base = ctx->write_io_buffer; + iov.iov_len = DMA_TEST_IO_BUFFER_SIZE; + + fprintf(stdout, "Submitting write IO\n"); + + rc = spdk_bdev_writev_blocks_ext(ctx->desc, ctx->ch, &iov, 1, 0, ctx->num_blocks, + dma_test_write_completed, ctx, &ctx->ext_io_opts); + if (rc) { + fprintf(stderr, "Falied to submit write operation"); + spdk_app_stop(-1); + } +} + +int +main(int argc, char **argv) +{ + struct dma_test_ctx ctx = {}; + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts, sizeof(opts)); + opts.name = "test_dma"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "b:", NULL, parse_arg, print_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + if (!g_bdev_name) { + fprintf(stderr, "bdev name for test is not set\n"); + exit(1); + } + ctx.bdev_name = g_bdev_name; + + rc = spdk_app_start(&opts, dma_test_run, &ctx); + + dma_test_cleanup(&ctx); + + spdk_app_fini(); + + return rc; +} diff --git a/test/nvmf/host/dma.sh b/test/nvmf/host/dma.sh new file mode 100755 index 0000000000..e2e818b6cc --- /dev/null +++ b/test/nvmf/host/dma.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh +source $rootdir/test/nvmf/common.sh + +if [ "$TEST_TRANSPORT" != "rdma" ]; then + exit 0 +fi + +MALLOC_BDEV_SIZE=64 +MALLOC_BLOCK_SIZE=512 +subsystem="0" +rpc_py="$rootdir/scripts/rpc.py" + +nvmftestinit +nvmfappstart -m 0x1 + +$rpc_py nvmf_create_transport $NVMF_TRANSPORT_OPTS +$rpc_py bdev_malloc_create $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE -b Malloc0 +$rpc_py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode$subsystem -a -s SPDK00000000000001 +$rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$subsystem Malloc0 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode$subsystem -t $TEST_TRANSPORT -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +"$rootdir/test/dma/test_dma/test_dma" --json <(gen_nvmf_target_json $subsystem) -b "Nvme${subsystem}n1" +test_dmapid=$! + +wait $test_dmapid +sync + +trap - SIGINT SIGTERM EXIT + +nvmftestfini diff --git a/test/nvmf/nvmf.sh b/test/nvmf/nvmf.sh index cad38013e1..2167b6198b 100755 --- a/test/nvmf/nvmf.sh +++ b/test/nvmf/nvmf.sh @@ -78,6 +78,8 @@ if ! check_ip_is_soft_roce $NVMF_FIRST_TARGET_IP; then run_test "nvmf_target_disconnect" test/nvmf/host/target_disconnect.sh "${TEST_ARGS[@]}" fi +run_test "dma" test/nvmf/host/dma.sh "${TEST_ARGS[@]}" + timing_exit host trap - SIGINT SIGTERM EXIT