Commit ecfc5087 authored by Alexey Marchuk's avatar Alexey Marchuk Committed by Tomasz Zawadzki
Browse files

test/dma: Add functional test to verify DMA



Signed-off-by: default avatarAlexey 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: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
Reviewed-by: default avatarShuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
parent fad59a28
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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

test/dma/Makefile

0 → 100644
+45 −0
Original line number Diff line number Diff line
#
#  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 −0
Original line number Diff line number Diff line
test_dma
+42 −0
Original line number Diff line number Diff line
#
#  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
+350 −0
Original line number Diff line number Diff line
/*-
 *   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;
}
Loading