xref: /spdk/test/dma/test_dma/test_dma.c (revision cc6920a4763d4b9a43aa40583c8397d8f14fa100)
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
5  *
6  *   Redistribution and use in source and binary forms, with or without
7  *   modification, are permitted provided that the following conditions
8  *   are met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  *       notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in
14  *       the documentation and/or other materials provided with the
15  *       distribution.
16  *     * Neither the name of Nvidia Corporation nor the names of its
17  *       contributors may be used to endorse or promote products derived
18  *       from this software without specific prior written permission.
19  *
20  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "spdk/stdinc.h"
34 
35 #include "spdk/dma.h"
36 #include "spdk/bdev.h"
37 #include "spdk/env.h"
38 #include "spdk/event.h"
39 #include "spdk/likely.h"
40 #include "spdk/string.h"
41 #include "spdk/util.h"
42 
43 #include <infiniband/verbs.h>
44 
45 struct dma_test_task;
46 
47 struct dma_test_req {
48 	struct iovec iov;
49 	struct spdk_bdev_ext_io_opts io_opts;
50 	uint64_t submit_tsc;
51 	struct ibv_mr *mr;
52 	struct dma_test_task *task;
53 };
54 
55 struct dma_test_task_stats {
56 	uint64_t io_completed;
57 	uint64_t total_tsc;
58 	uint64_t min_tsc;
59 	uint64_t max_tsc;
60 };
61 
62 struct dma_test_task {
63 	struct spdk_bdev_desc *desc;
64 	struct spdk_io_channel *channel;
65 	uint64_t cur_io_offset;
66 	uint64_t max_offset_in_ios;
67 	uint64_t num_blocks_per_io;
68 	int rw_percentage;
69 	uint32_t seed;
70 	uint32_t io_inflight;
71 	struct dma_test_task_stats stats;
72 	struct dma_test_task_stats last_stats;
73 	bool is_draining;
74 	bool is_random;
75 	struct dma_test_req *reqs;
76 	struct spdk_thread *thread;
77 	const char *bdev_name;
78 	uint32_t lcore;
79 
80 	TAILQ_ENTRY(dma_test_task) link;
81 };
82 
83 TAILQ_HEAD(, dma_test_task) g_tasks = TAILQ_HEAD_INITIALIZER(g_tasks);
84 
85 /* User's input */
86 static char *g_bdev_name;
87 static const char *g_rw_mode_str;
88 static int g_rw_percentage = -1;
89 static uint32_t g_queue_depth;
90 static uint32_t g_io_size;
91 static uint32_t g_run_time_sec;
92 static uint32_t g_run_count;
93 static bool g_is_random;
94 
95 static struct spdk_thread *g_main_thread;
96 static struct spdk_poller *g_runtime_poller;
97 static struct spdk_memory_domain *g_domain;
98 static uint64_t g_num_blocks_per_io;
99 static uint32_t g_num_construct_tasks;
100 static uint32_t g_num_complete_tasks;
101 static uint64_t g_start_tsc;
102 static int g_run_rc;
103 
104 static void destroy_tasks(void);
105 static int dma_test_submit_io(struct dma_test_req *req);
106 
107 static void
108 print_total_stats(void)
109 {
110 	struct dma_test_task *task;
111 	uint64_t tsc_rate = spdk_get_ticks_hz();
112 	uint64_t test_time_usec = (spdk_get_ticks() - g_start_tsc) * SPDK_SEC_TO_USEC / tsc_rate;
113 	uint64_t total_tsc = 0, total_io_completed = 0;
114 	double task_iops, task_bw, task_min_lat, task_avg_lat, task_max_lat;
115 	double total_iops = 0, total_bw = 0, total_min_lat = (double)UINT64_MAX, total_max_lat = 0,
116 	       total_avg_lat;
117 
118 	printf("==========================================================================\n");
119 	printf("%*s\n", 55, "Latency [us]");
120 	printf("%*s %10s %10s %10s %10s\n", 19, "IOPS", "MiB/s", "Average", "min", "max");
121 
122 	TAILQ_FOREACH(task, &g_tasks, link) {
123 		if (!task->stats.io_completed) {
124 			continue;
125 		}
126 		task_iops = (double)task->stats.io_completed * SPDK_SEC_TO_USEC / test_time_usec;
127 		task_bw = task_iops * g_io_size / (1024 * 1024);
128 		task_avg_lat = (double)task->stats.total_tsc / task->stats.io_completed * SPDK_SEC_TO_USEC /
129 			       tsc_rate;
130 		task_min_lat = (double)task->stats.min_tsc * SPDK_SEC_TO_USEC / tsc_rate;
131 		task_max_lat = (double)task->stats.max_tsc * SPDK_SEC_TO_USEC / tsc_rate;
132 
133 		total_iops += task_iops;
134 		total_bw += task_bw;
135 		total_io_completed += task->stats.io_completed;
136 		total_tsc += task->stats.total_tsc;
137 		if (task_min_lat < total_min_lat) {
138 			total_min_lat = task_min_lat;
139 		}
140 		if (task_max_lat > total_max_lat) {
141 			total_max_lat = task_max_lat;
142 		}
143 		printf("Core %2u: %10.2f %10.2f %10.2f %10.2f %10.2f\n",
144 		       task->lcore, task_iops, task_bw, task_avg_lat, task_min_lat, task_max_lat);
145 	}
146 
147 	if (total_io_completed) {
148 		total_avg_lat = (double)total_tsc / total_io_completed  * SPDK_SEC_TO_USEC / tsc_rate;
149 		printf("==========================================================================\n");
150 		printf("%-*s %10.2f %10.2f %10.2f %10.2f %10.2f\n",
151 		       8, "Total  :", total_iops, total_bw, total_avg_lat, total_min_lat, total_max_lat);
152 		printf("\n");
153 	}
154 }
155 
156 static void
157 print_periodic_stats(void)
158 {
159 	struct dma_test_task *task;
160 	uint64_t io_last_sec = 0, tsc_last_sec = 0;
161 	double lat_last_sec, bw_last_sec;
162 
163 	TAILQ_FOREACH(task, &g_tasks, link) {
164 		io_last_sec += task->stats.io_completed - task->last_stats.io_completed;
165 		tsc_last_sec += task->stats.total_tsc - task->last_stats.total_tsc;
166 		memcpy(&task->last_stats, &task->stats, sizeof(task->stats));
167 	}
168 
169 	printf("Running %3u/%-3u sec", g_run_count, g_run_time_sec);
170 	if (io_last_sec) {
171 		lat_last_sec =	(double)tsc_last_sec / io_last_sec * SPDK_SEC_TO_USEC / spdk_get_ticks_hz();
172 		bw_last_sec = (double)io_last_sec * g_io_size / (1024 * 1024);
173 		printf(" IOPS: %-8"PRIu64" BW: %-6.2f [MiB/s] avg.lat %-5.2f [us]",
174 		       io_last_sec, bw_last_sec, lat_last_sec);
175 	}
176 
177 	printf("\r");
178 	fflush(stdout);
179 }
180 
181 static void
182 dma_test_task_complete(void *ctx)
183 {
184 	assert(g_num_complete_tasks > 0);
185 
186 	if (--g_num_complete_tasks == 0) {
187 		spdk_poller_unregister(&g_runtime_poller);
188 		print_total_stats();
189 		spdk_app_stop(g_run_rc);
190 	}
191 }
192 
193 static inline void
194 dma_test_check_and_signal_task_done(struct dma_test_task *task)
195 {
196 	if (task->io_inflight == 0) {
197 		spdk_put_io_channel(task->channel);
198 		spdk_bdev_close(task->desc);
199 		spdk_thread_send_msg(g_main_thread, dma_test_task_complete, task);
200 	}
201 }
202 
203 static inline void
204 dma_test_task_update_stats(struct dma_test_task *task, uint64_t submit_tsc)
205 {
206 	uint64_t tsc_diff = spdk_get_ticks() - submit_tsc;
207 
208 	task->stats.io_completed++;
209 	task->stats.total_tsc += tsc_diff;
210 	if (spdk_unlikely(tsc_diff < task->stats.min_tsc)) {
211 		task->stats.min_tsc = tsc_diff;
212 	}
213 	if (spdk_unlikely(tsc_diff > task->stats.max_tsc)) {
214 		task->stats.max_tsc = tsc_diff;
215 	}
216 }
217 
218 static void
219 dma_test_bdev_io_completion_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
220 {
221 	struct dma_test_req *req = cb_arg;
222 	struct dma_test_task *task = req->task;
223 
224 	assert(task->io_inflight > 0);
225 	--task->io_inflight;
226 	dma_test_task_update_stats(task, req->submit_tsc);
227 
228 	if (!success) {
229 		if (!g_run_rc) {
230 			fprintf(stderr, "IO completed with error\n");
231 			g_run_rc = -1;
232 		}
233 		task->is_draining = true;
234 	}
235 
236 	spdk_bdev_free_io(bdev_io);
237 
238 	if (spdk_unlikely(task->is_draining)) {
239 		dma_test_check_and_signal_task_done(task);
240 		return;
241 	}
242 
243 	dma_test_submit_io(req);
244 }
245 
246 static inline uint64_t
247 dma_test_get_offset_in_ios(struct dma_test_task *task)
248 {
249 	uint64_t offset;
250 
251 	if (task->is_random) {
252 		offset = rand_r(&task->seed) % task->max_offset_in_ios;
253 	} else {
254 		offset = task->cur_io_offset++;
255 		if (spdk_unlikely(task->cur_io_offset == task->max_offset_in_ios)) {
256 			task->cur_io_offset = 0;
257 		}
258 	}
259 
260 	return offset;
261 }
262 
263 static inline bool
264 dma_test_task_is_read(struct dma_test_task *task)
265 {
266 	if (task->rw_percentage == 100) {
267 		return true;
268 	}
269 	if (task->rw_percentage != 0 && (rand_r(&task->seed) % 100) <  task->rw_percentage) {
270 		return true;
271 	}
272 	return false;
273 }
274 
275 static int
276 dma_test_translate_memory_cb(struct spdk_memory_domain *src_domain, void *src_domain_ctx,
277 			     struct spdk_memory_domain *dst_domain, struct spdk_memory_domain_translation_ctx *dst_domain_ctx,
278 			     void *addr, size_t len, struct spdk_memory_domain_translation_result *result)
279 {
280 	struct dma_test_req *req = src_domain_ctx;
281 	struct ibv_qp *dst_domain_qp = (struct ibv_qp *)dst_domain_ctx->rdma.ibv_qp;
282 
283 	if (spdk_unlikely(!req->mr)) {
284 		req->mr = ibv_reg_mr(dst_domain_qp->pd, addr, len, IBV_ACCESS_LOCAL_WRITE |
285 				     IBV_ACCESS_REMOTE_READ |
286 				     IBV_ACCESS_REMOTE_WRITE);
287 		if (!req->mr) {
288 			fprintf(stderr, "Failed to register memory region, errno %d\n", errno);
289 			return -1;
290 		}
291 	}
292 
293 	result->iov.iov_base = addr;
294 	result->iov.iov_len = len;
295 	result->iov_count = 1;
296 	result->rdma.lkey = req->mr->lkey;
297 	result->rdma.rkey = req->mr->rkey;
298 	result->dst_domain = dst_domain;
299 
300 	return 0;
301 }
302 
303 static int
304 dma_test_submit_io(struct dma_test_req *req)
305 {
306 	struct dma_test_task *task = req->task;
307 	uint64_t offset_in_ios;
308 	int rc;
309 	bool is_read;
310 
311 	offset_in_ios = dma_test_get_offset_in_ios(task);
312 	is_read = dma_test_task_is_read(task);
313 	req->submit_tsc = spdk_get_ticks();
314 	if (is_read) {
315 		rc = spdk_bdev_readv_blocks_ext(task->desc, task->channel, &req->iov, 1,
316 						offset_in_ios * task->num_blocks_per_io, task->num_blocks_per_io,
317 						dma_test_bdev_io_completion_cb, req, &req->io_opts);
318 	} else {
319 		rc = spdk_bdev_writev_blocks_ext(task->desc, task->channel, &req->iov, 1,
320 						 offset_in_ios * task->num_blocks_per_io, task->num_blocks_per_io,
321 						 dma_test_bdev_io_completion_cb, req, &req->io_opts);
322 	}
323 
324 	if (spdk_unlikely(rc)) {
325 		if (!g_run_rc) {
326 			/* log an error only once */
327 			fprintf(stderr, "Failed to submit %s IO, rc %d, stop sending IO\n", is_read ? "read" : "write", rc);
328 			g_run_rc = rc;
329 		}
330 		task->is_draining = true;
331 		dma_test_check_and_signal_task_done(task);
332 		return rc;
333 	}
334 
335 	task->io_inflight++;
336 
337 	return 0;
338 }
339 
340 static void
341 dma_test_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx)
342 {
343 	struct dma_test_task *task = event_ctx;
344 
345 	if (type == SPDK_BDEV_EVENT_REMOVE) {
346 		task->is_draining = true;
347 	}
348 }
349 
350 static void
351 dma_test_bdev_dummy_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev,
352 			     void *event_ctx)
353 {
354 }
355 
356 static void dma_test_task_run(void *ctx)
357 {
358 	struct dma_test_task *task = ctx;
359 	uint32_t i;
360 	int rc = 0;
361 
362 	for (i = 0; i < g_queue_depth && rc == 0; i++) {
363 		rc = dma_test_submit_io(&task->reqs[i]);
364 	}
365 }
366 
367 static void
368 dma_test_drain_task(void *ctx)
369 {
370 	struct dma_test_task *task = ctx;
371 
372 	task->is_draining = true;
373 }
374 
375 static void
376 dma_test_shutdown_cb(void)
377 {
378 	struct dma_test_task *task;
379 
380 	spdk_poller_unregister(&g_runtime_poller);
381 
382 	TAILQ_FOREACH(task, &g_tasks, link) {
383 		spdk_thread_send_msg(task->thread, dma_test_drain_task, task);
384 	}
385 }
386 
387 static int
388 dma_test_run_time_poller(void *ctx)
389 {
390 	g_run_count++;
391 
392 	if (g_run_count < g_run_time_sec) {
393 		if (isatty(STDOUT_FILENO)) {
394 			print_periodic_stats();
395 		}
396 	} else {
397 		dma_test_shutdown_cb();
398 	}
399 
400 	return SPDK_POLLER_BUSY;
401 }
402 
403 static void
404 dma_test_construct_task_done(void *ctx)
405 {
406 	struct dma_test_task *task;
407 
408 	assert(g_num_construct_tasks > 0);
409 	--g_num_construct_tasks;
410 
411 	if (g_num_construct_tasks != 0) {
412 		return;
413 	}
414 
415 	if (g_run_rc) {
416 		fprintf(stderr, "Initialization failed with error %d\n", g_run_rc);
417 		spdk_app_stop(g_run_rc);
418 		return;
419 	}
420 
421 	g_runtime_poller = spdk_poller_register_named(dma_test_run_time_poller, NULL, 1 * 1000 * 1000,
422 			   "dma_test_run_time_poller");
423 	if (!g_runtime_poller) {
424 		fprintf(stderr, "Failed to run timer\n");
425 		spdk_app_stop(-1);
426 		return;
427 	}
428 
429 	printf("Initialization complete, running %s IO for %u sec on %u cores\n", g_rw_mode_str,
430 	       g_run_time_sec, spdk_env_get_core_count());
431 	g_start_tsc = spdk_get_ticks();
432 	TAILQ_FOREACH(task, &g_tasks, link) {
433 		spdk_thread_send_msg(task->thread, dma_test_task_run, task);
434 	}
435 }
436 
437 static void
438 dma_test_construct_task_on_thread(void *ctx)
439 {
440 	struct dma_test_task *task = ctx;
441 	int rc;
442 
443 	rc = spdk_bdev_open_ext(task->bdev_name, true, dma_test_bdev_event_cb, task, &task->desc);
444 	if (rc) {
445 		fprintf(stderr, "Failed to open bdev %s, rc %d\n", task->bdev_name, rc);
446 		g_run_rc = rc;
447 		spdk_thread_send_msg(g_main_thread, dma_test_construct_task_done, NULL);
448 		return;
449 	}
450 
451 	task->channel = spdk_bdev_get_io_channel(task->desc);
452 	if (!task->channel) {
453 		spdk_bdev_close(task->desc);
454 		task->desc = NULL;
455 		fprintf(stderr, "Failed to open bdev %s, rc %d\n", task->bdev_name, rc);
456 		g_run_rc = rc;
457 		spdk_thread_send_msg(g_main_thread, dma_test_construct_task_done, NULL);
458 		return;
459 	}
460 
461 	task->max_offset_in_ios = spdk_bdev_get_num_blocks(spdk_bdev_desc_get_bdev(
462 					  task->desc)) / task->num_blocks_per_io;
463 
464 	spdk_thread_send_msg(g_main_thread, dma_test_construct_task_done, task);
465 }
466 
467 static bool
468 dma_test_check_bdev_supports_rdma_memory_domain(struct spdk_bdev *bdev)
469 {
470 	struct spdk_memory_domain **bdev_domains;
471 	int bdev_domains_count, bdev_domains_count_tmp, i;
472 	bool rdma_domain_supported = false;
473 
474 	bdev_domains_count = spdk_bdev_get_memory_domains(bdev, NULL, 0);
475 
476 	if (bdev_domains_count < 0) {
477 		fprintf(stderr, "Failed to get bdev memory domains count, rc %d\n", bdev_domains_count);
478 		return false;
479 	} else if (bdev_domains_count == 0) {
480 		fprintf(stderr, "bdev %s doesn't support any memory domains\n", spdk_bdev_get_name(bdev));
481 		return false;
482 	}
483 
484 	fprintf(stdout, "bdev %s reports %d memory domains\n", spdk_bdev_get_name(bdev),
485 		bdev_domains_count);
486 
487 	bdev_domains = calloc((size_t)bdev_domains_count, sizeof(*bdev_domains));
488 	if (!bdev_domains) {
489 		fprintf(stderr, "Failed to allocate memory domains\n");
490 		return false;
491 	}
492 
493 	bdev_domains_count_tmp = spdk_bdev_get_memory_domains(bdev, bdev_domains, bdev_domains_count);
494 	if (bdev_domains_count_tmp != bdev_domains_count) {
495 		fprintf(stderr, "Unexpected bdev domains return value %d\n", bdev_domains_count_tmp);
496 		return false;
497 	}
498 
499 	for (i = 0; i < bdev_domains_count; i++) {
500 		if (spdk_memory_domain_get_dma_device_type(bdev_domains[i]) == SPDK_DMA_DEVICE_TYPE_RDMA) {
501 			/* Bdev supports memory domain of RDMA type, we can try to submit IO request to it using
502 			 * bdev ext API */
503 			rdma_domain_supported = true;
504 			break;
505 		}
506 	}
507 
508 	fprintf(stdout, "bdev %s %s RDMA memory domain\n", spdk_bdev_get_name(bdev),
509 		rdma_domain_supported ? "supports" : "doesn't support");
510 	free(bdev_domains);
511 
512 	return rdma_domain_supported;
513 }
514 
515 static int
516 allocate_task(uint32_t core, const char *bdev_name)
517 {
518 	char thread_name[32];
519 	struct spdk_cpuset cpu_set;
520 	uint32_t i;
521 	struct dma_test_task *task;
522 	struct dma_test_req *req;
523 
524 	task = calloc(1, sizeof(*task));
525 	if (!task) {
526 		fprintf(stderr, "Failed to allocate per thread task\n");
527 		return -ENOMEM;
528 	}
529 
530 	TAILQ_INSERT_TAIL(&g_tasks, task, link);
531 
532 	task->reqs = calloc(g_queue_depth, sizeof(*task->reqs));
533 	if (!task->reqs) {
534 		fprintf(stderr, "Failed to allocate requests\n");
535 		return -ENOMEM;
536 	}
537 
538 	for (i = 0; i < g_queue_depth; i++) {
539 		req = &task->reqs[i];
540 		req->task = task;
541 		req->iov.iov_len = g_io_size;
542 		req->iov.iov_base = malloc(req->iov.iov_len);
543 		if (!req->iov.iov_base) {
544 			fprintf(stderr, "Failed to allocate request data buffer\n");
545 			return -ENOMEM;
546 		}
547 		memset(req->iov.iov_base, 0xc, req->iov.iov_len);
548 		req->io_opts.size = sizeof(req->io_opts);
549 		req->io_opts.memory_domain = g_domain;
550 		req->io_opts.memory_domain_ctx = req;
551 	}
552 
553 	snprintf(thread_name, 32, "task_%u", core);
554 	spdk_cpuset_zero(&cpu_set);
555 	spdk_cpuset_set_cpu(&cpu_set, core, true);
556 	task->thread = spdk_thread_create(thread_name, &cpu_set);
557 	if (!task->thread) {
558 		fprintf(stderr, "Failed to create SPDK thread, core %u, cpu_mask %s\n", core,
559 			spdk_cpuset_fmt(&cpu_set));
560 		return -ENOMEM;
561 	}
562 
563 	task->seed = core;
564 	task->lcore = core;
565 	task->bdev_name = bdev_name;
566 	task->is_random = g_is_random;
567 	task->rw_percentage = g_rw_percentage;
568 	task->num_blocks_per_io = g_num_blocks_per_io;
569 	task->stats.min_tsc = UINT64_MAX;
570 
571 	return 0;
572 }
573 
574 static void
575 destroy_task(struct dma_test_task *task)
576 {
577 	struct dma_test_req *req;
578 	uint32_t i;
579 
580 	for (i = 0; i < g_queue_depth; i++) {
581 		req = &task->reqs[i];
582 		if (req->mr) {
583 			ibv_dereg_mr(req->mr);
584 		}
585 		free(req->iov.iov_base);
586 	}
587 	free(task->reqs);
588 	TAILQ_REMOVE(&g_tasks, task, link);
589 	free(task);
590 }
591 
592 static void
593 destroy_tasks(void)
594 {
595 	struct dma_test_task *task, *tmp_task;
596 
597 	TAILQ_FOREACH_SAFE(task, &g_tasks, link, tmp_task) {
598 		destroy_task(task);
599 	}
600 }
601 
602 static void
603 dma_test_start(void *arg)
604 {
605 	struct spdk_bdev_desc *desc;
606 	struct spdk_bdev *bdev;
607 	struct dma_test_task *task;
608 	uint32_t block_size, i;
609 	int rc;
610 
611 	rc = spdk_bdev_open_ext(g_bdev_name, true, dma_test_bdev_dummy_event_cb, NULL, &desc);
612 	if (rc) {
613 		fprintf(stderr, "Can't find bdev %s\n", g_bdev_name);
614 		spdk_app_stop(-ENODEV);
615 		return;
616 	}
617 	bdev = spdk_bdev_desc_get_bdev(desc);
618 	if (!dma_test_check_bdev_supports_rdma_memory_domain(bdev)) {
619 		spdk_bdev_close(desc);
620 		spdk_app_stop(-ENODEV);
621 		return;
622 	}
623 
624 	g_main_thread = spdk_get_thread();
625 
626 	block_size = spdk_bdev_get_block_size(bdev);
627 	if (g_io_size < block_size || g_io_size % block_size != 0) {
628 		fprintf(stderr, "Invalid io_size %u requested, bdev block size %u\n", g_io_size, block_size);
629 		spdk_bdev_close(desc);
630 		spdk_app_stop(-EINVAL);
631 		return;
632 	}
633 	g_num_blocks_per_io = g_io_size / block_size;
634 
635 	/* Create a memory domain to represent the source memory domain.
636 	 * Since we don't actually have a remote memory domain in this test, this will describe memory
637 	 * on the local system and the translation to the destination memory domain will be trivial.
638 	 * But this at least allows us to demonstrate the flow and test the functionality. */
639 	rc = spdk_memory_domain_create(&g_domain, SPDK_DMA_DEVICE_TYPE_RDMA, NULL, "test_dma");
640 	if (rc != 0) {
641 		spdk_bdev_close(desc);
642 		spdk_app_stop(rc);
643 		return;
644 	}
645 	spdk_memory_domain_set_translation(g_domain, dma_test_translate_memory_cb);
646 
647 	SPDK_ENV_FOREACH_CORE(i) {
648 		rc = allocate_task(i, g_bdev_name);
649 		if (rc) {
650 			destroy_tasks();
651 			spdk_bdev_close(desc);
652 			spdk_app_stop(rc);
653 			return;
654 		}
655 		g_num_construct_tasks++;
656 		g_num_complete_tasks++;
657 	}
658 
659 	TAILQ_FOREACH(task, &g_tasks, link) {
660 		spdk_thread_send_msg(task->thread, dma_test_construct_task_on_thread, task);
661 	}
662 
663 	spdk_bdev_close(desc);
664 }
665 
666 static void
667 print_usage(void)
668 {
669 	printf(" -b <bdev>         bdev name for test\n");
670 	printf(" -q <val>          io depth\n");
671 	printf(" -o <val>          io size in bytes\n");
672 	printf(" -t <val>          run time in seconds\n");
673 	printf(" -w <str>          io pattern (read, write, randread, randwrite, randrw)\n");
674 	printf(" -M <0-100>        rw percentage (100 for reads, 0 for writes)\n");
675 }
676 
677 static int
678 parse_arg(int ch, char *arg)
679 {
680 	long tmp;
681 
682 	switch (ch) {
683 	case 'q':
684 	case 'o':
685 	case 't':
686 	case 'M':
687 		tmp = spdk_strtol(arg, 10);
688 		if (tmp < 0) {
689 			fprintf(stderr, "Invalid option %c value %s\n", ch, arg);
690 			return 1;
691 		}
692 
693 		switch (ch) {
694 		case 'q':
695 			g_queue_depth = (uint32_t) tmp;
696 			break;
697 		case 'o':
698 			g_io_size = (uint32_t) tmp;
699 			break;
700 		case 't':
701 			g_run_time_sec = (uint32_t) tmp;
702 			break;
703 		case 'M':
704 			g_rw_percentage = (uint32_t) tmp;
705 			break;
706 		}
707 		break;
708 	case 'w':
709 		g_rw_mode_str = arg;
710 		break;
711 	case 'b':
712 		g_bdev_name = arg;
713 		break;
714 
715 	default:
716 		fprintf(stderr, "Unknown option %c\n", ch);
717 		return 1;
718 	}
719 
720 	return 0;
721 }
722 
723 static int
724 verify_args(void)
725 {
726 	const char *rw_mode = g_rw_mode_str;
727 
728 	if (g_queue_depth == 0) {
729 		fprintf(stderr, "queue depth (-q) is not set\n");
730 		return 1;
731 	}
732 	if (g_io_size == 0) {
733 		fprintf(stderr, "io size (-o) is not set\n");
734 		return 1;
735 	}
736 	if (g_run_time_sec == 0) {
737 		fprintf(stderr, "test run time (-t) is not set\n");
738 		return 1;
739 	}
740 	if (!rw_mode) {
741 		fprintf(stderr, "io pattern (-w) is not set\n");
742 		return 1;
743 	}
744 	if (strncmp(rw_mode, "rand", 4) == 0) {
745 		g_is_random = true;
746 		rw_mode = &rw_mode[4];
747 	}
748 	if (strcmp(rw_mode, "read") == 0 || strcmp(rw_mode, "write") == 0) {
749 		if (g_rw_percentage > 0) {
750 			fprintf(stderr, "Ignoring -M option\n");
751 		}
752 		g_rw_percentage = strcmp(rw_mode, "read") == 0 ? 100 : 0;
753 	} else if (strcmp(rw_mode, "rw") == 0) {
754 		if (g_rw_percentage < 0 || g_rw_percentage > 100) {
755 			fprintf(stderr, "Invalid -M value (%d) must be 0..100\n", g_rw_percentage);
756 			return 1;
757 		}
758 	} else {
759 		fprintf(stderr, "io pattern (-w) one of [read, write, randread, randwrite, rw, randrw]\n");
760 		return 1;
761 	}
762 	if (!g_bdev_name) {
763 		fprintf(stderr, "bdev name (-b) is not set\n");
764 		return 1;
765 	}
766 
767 	return 0;
768 }
769 
770 int
771 main(int argc, char **argv)
772 {
773 	struct spdk_app_opts opts = {};
774 	int rc;
775 
776 	spdk_app_opts_init(&opts, sizeof(opts));
777 	opts.name = "test_dma";
778 	opts.shutdown_cb = dma_test_shutdown_cb;
779 
780 	rc = spdk_app_parse_args(argc, argv, &opts, "b:q:o:t:w:M:", NULL, parse_arg, print_usage);
781 	if (rc != SPDK_APP_PARSE_ARGS_SUCCESS) {
782 		exit(rc);
783 	}
784 
785 	rc = verify_args();
786 	if (rc) {
787 		exit(rc);
788 	}
789 
790 	rc = spdk_app_start(&opts, dma_test_start, NULL);
791 	destroy_tasks();
792 	spdk_app_fini();
793 
794 	return rc;
795 }
796