test/dma: Add functional test to verify DMA

Signed-off-by: Alexey Marchuk <alexeymar@mellanox.com>
Change-Id: If7eb7da6c3356aae9e8654372ce1c9a89bf25de6
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/8855
Community-CI: Broadcom CI <spdk-ci.pdl@broadcom.com>
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
This commit is contained in:
Alexey Marchuk 2021-07-20 21:38:49 +03:00 committed by Tomasz Zawadzki
parent fad59a283f
commit ecfc50872f
7 changed files with 475 additions and 1 deletions

View File

@ -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

45
test/dma/Makefile Normal file
View File

@ -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

1
test/dma/test_dma/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test_dma

View File

@ -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

View File

@ -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 <infiniband/verbs.h>
#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> 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;
}

34
test/nvmf/host/dma.sh Executable file
View File

@ -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

View File

@ -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