Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (C) 2021 Intel Corporation.
3 : : * All rights reserved.
4 : : */
5 : :
6 : : #include "spdk/idxd.h"
7 : : #include "spdk/stdinc.h"
8 : : #include "spdk/env.h"
9 : : #include "spdk/event.h"
10 : : #include "spdk/log.h"
11 : : #include "spdk/string.h"
12 : : #include "spdk/crc32.h"
13 : : #include "spdk/util.h"
14 : :
15 : : enum idxd_capability {
16 : : IDXD_COPY = 1,
17 : : IDXD_FILL,
18 : : IDXD_DUALCAST,
19 : : IDXD_COMPARE,
20 : : IDXD_CRC32C,
21 : : IDXD_DIF,
22 : : IDXD_COPY_CRC32C,
23 : : };
24 : :
25 : : #define DATA_PATTERN 0x5a
26 : : #define ALIGN_4K 0x1000
27 : :
28 : : static int g_xfer_size_bytes = 4096;
29 : :
30 : : /* g_allocate_depth indicates how many tasks we allocate per work_chan. It will
31 : : * be at least as much as the queue depth.
32 : : */
33 : : static int g_queue_depth = 32;
34 : : static int g_idxd_max_per_core = 1;
35 : : static char *g_core_mask = "0x1";
36 : : static bool g_idxd_kernel_mode = false;
37 : : static int g_allocate_depth = 0;
38 : : static int g_time_in_sec = 5;
39 : : static uint32_t g_crc32c_seed = 0;
40 : : static uint32_t g_crc32c_chained_count = 1;
41 : : static int g_fail_percent_goal = 0;
42 : : static uint8_t g_fill_pattern = 255;
43 : : static bool g_verify = false;
44 : : static const char *g_workload_type = NULL;
45 : : static enum idxd_capability g_workload_selection;
46 : : static struct worker_thread *g_workers = NULL;
47 : : static int g_num_workers = 0;
48 : :
49 : : struct worker_thread;
50 : : struct idxd_chan_entry;
51 : : static void idxd_done(void *ref, int status);
52 : :
53 : : struct idxd_device {
54 : : struct spdk_idxd_device *idxd;
55 : : TAILQ_ENTRY(idxd_device) tailq;
56 : : };
57 : : static uint32_t g_num_devices = 0;
58 : :
59 : : static TAILQ_HEAD(, idxd_device) g_idxd_devices = TAILQ_HEAD_INITIALIZER(g_idxd_devices);
60 : : static struct idxd_device *g_next_device;
61 : :
62 : : struct idxd_task {
63 : : void *src;
64 : : struct iovec *iovs;
65 : : uint32_t iov_cnt;
66 : : void *dst;
67 : : void *dst2;
68 : : uint32_t crc_dst;
69 : : struct idxd_chan_entry *worker_chan;
70 : : int status;
71 : : int expected_status; /* used for the compare operation */
72 : : TAILQ_ENTRY(idxd_task) link;
73 : : };
74 : :
75 : : struct idxd_chan_entry {
76 : : int idxd_chan_id;
77 : : struct spdk_idxd_io_channel *ch;
78 : : uint64_t xfer_completed;
79 : : uint64_t xfer_failed;
80 : : uint64_t injected_miscompares;
81 : : uint64_t current_queue_depth;
82 : : TAILQ_HEAD(, idxd_task) tasks_pool_head;
83 : : TAILQ_HEAD(, idxd_task) resubmits;
84 : : unsigned core;
85 : : bool is_draining;
86 : : void *task_base;
87 : : struct idxd_chan_entry *next;
88 : : };
89 : :
90 : : struct worker_thread {
91 : : struct idxd_chan_entry *ctx;
92 : : struct worker_thread *next;
93 : : int chan_num;
94 : : unsigned core;
95 : : };
96 : :
97 : : static void
98 : 0 : dump_user_config(void)
99 : : {
100 : 0 : printf("SPDK Configuration:\n");
101 : 0 : printf("Core mask: %s\n\n", g_core_mask);
102 : 0 : printf("Idxd Perf Configuration:\n");
103 : 0 : printf("Workload Type: %s\n", g_workload_type);
104 [ # # # # ]: 0 : if (g_workload_selection == IDXD_CRC32C || g_workload_selection == IDXD_COPY_CRC32C) {
105 : 0 : printf("CRC-32C seed: %u\n", g_crc32c_seed);
106 : 0 : printf("vector count %u\n", g_crc32c_chained_count);
107 [ # # ]: 0 : } else if (g_workload_selection == IDXD_FILL) {
108 : 0 : printf("Fill pattern: 0x%x\n", g_fill_pattern);
109 [ # # # # ]: 0 : } else if ((g_workload_selection == IDXD_COMPARE) && g_fail_percent_goal > 0) {
110 : 0 : printf("Failure inject: %u percent\n", g_fail_percent_goal);
111 : : }
112 [ # # ]: 0 : if (g_workload_selection == IDXD_COPY_CRC32C) {
113 : 0 : printf("Vector size: %u bytes\n", g_xfer_size_bytes);
114 : 0 : printf("Transfer size: %u bytes\n", g_xfer_size_bytes * g_crc32c_chained_count);
115 : : } else {
116 : 0 : printf("Transfer size: %u bytes\n", g_xfer_size_bytes);
117 : : }
118 : 0 : printf("Queue depth: %u\n", g_queue_depth);
119 : 0 : printf("Allocated depth: %u\n", g_allocate_depth);
120 : 0 : printf("Run time: %u seconds\n", g_time_in_sec);
121 [ # # ]: 0 : printf("Verify: %s\n\n", g_verify ? "Yes" : "No");
122 : 0 : }
123 : :
124 : : static void
125 : 0 : attach_cb(void *cb_ctx, struct spdk_idxd_device *idxd)
126 : : {
127 : : struct idxd_device *dev;
128 : :
129 : 0 : dev = calloc(1, sizeof(*dev));
130 [ # # ]: 0 : if (dev == NULL) {
131 : 0 : fprintf(stderr, "Failed to allocate device struct\n");
132 : 0 : return;
133 : : }
134 : :
135 : 0 : dev->idxd = idxd;
136 : :
137 : 0 : TAILQ_INSERT_TAIL(&g_idxd_devices, dev, tailq);
138 : 0 : g_num_devices++;
139 : : }
140 : :
141 : : static bool
142 : 0 : probe_cb(void *cb_ctx, struct spdk_pci_device *dev)
143 : : {
144 : : /* this tool will gladly claim all types of IDXD devices. */
145 : 0 : return true;
146 : : }
147 : :
148 : : static int
149 : 0 : idxd_init(void)
150 : : {
151 : 0 : spdk_idxd_set_config(g_idxd_kernel_mode);
152 : :
153 [ # # ]: 0 : if (spdk_idxd_probe(NULL, attach_cb, probe_cb) != 0) {
154 : 0 : fprintf(stderr, "spdk_idxd_probe() failed\n");
155 : 0 : return 1;
156 : : }
157 : :
158 : 0 : return 0;
159 : : }
160 : :
161 : : static void
162 : 0 : idxd_exit(void)
163 : : {
164 : : struct idxd_device *dev;
165 : :
166 [ # # ]: 0 : while (!TAILQ_EMPTY(&g_idxd_devices)) {
167 : 0 : dev = TAILQ_FIRST(&g_idxd_devices);
168 [ # # ]: 0 : TAILQ_REMOVE(&g_idxd_devices, dev, tailq);
169 [ # # ]: 0 : if (dev->idxd) {
170 : 0 : spdk_idxd_detach(dev->idxd);
171 : : }
172 : 0 : free(dev);
173 : : }
174 : 0 : }
175 : :
176 : : static void
177 : 0 : usage(void)
178 : : {
179 : 0 : printf("idxd_perf options:\n");
180 : 0 : printf("\t[-h help message]\n");
181 : 0 : printf("\t[-a tasks to allocate per core (default: same value as -q)]\n");
182 : 0 : printf("\t[-C for crc32c workload, use this value to configure the io vector size to test (default 1)\n");
183 : 0 : printf("\t[-f for fill workload, use this BYTE value (default 255)\n");
184 : 0 : printf("\t[-k use kernel idxd driver]\n");
185 : 0 : printf("\t[-m core mask for distributing I/O submission/completion work]\n");
186 : 0 : printf("\t[-o transfer size in bytes]\n");
187 : 0 : printf("\t[-P for compare workload, percentage of operations that should miscompare (percent, default 0)\n");
188 : 0 : printf("\t[-q queue depth per core]\n");
189 : 0 : printf("\t[-r max idxd devices per core can drive (default 1)]\n");
190 : 0 : printf("\t[-s for crc32c workload, use this seed value (default 0)\n");
191 : 0 : printf("\t[-t time in seconds]\n");
192 : 0 : printf("\t[-w workload type must be one of these: copy, fill, crc32c, copy_crc32c, compare, dualcast\n");
193 : 0 : printf("\t[-y verify result if this switch is on]\n");
194 : 0 : printf("\t\tCan be used to spread operations across a wider range of memory.\n");
195 : 0 : }
196 : :
197 : : static int
198 : 0 : parse_args(int argc, char **argv)
199 : : {
200 : 0 : int argval = 0;
201 : : int op;
202 : :
203 [ # # ]: 0 : while ((op = getopt(argc, argv, "a:C:f:hkm:o:P:q:r:t:yw:")) != -1) {
204 [ # # ]: 0 : switch (op) {
205 : 0 : case 'a':
206 : : case 'C':
207 : : case 'f':
208 : : case 'o':
209 : : case 'P':
210 : : case 'q':
211 : : case 'r':
212 : : case 's':
213 : : case 't':
214 : 0 : argval = spdk_strtol(optarg, 10);
215 [ # # ]: 0 : if (argval < 0) {
216 : 0 : fprintf(stderr, "-%c option must be non-negative.\n", argc);
217 : 0 : usage();
218 : 0 : return 1;
219 : : }
220 : 0 : break;
221 : 0 : default:
222 : 0 : break;
223 : : };
224 : :
225 [ # # # # : 0 : switch (op) {
# # # # #
# # # # #
# ]
226 : 0 : case 'a':
227 : 0 : g_allocate_depth = argval;
228 : 0 : break;
229 : 0 : case 'C':
230 : 0 : g_crc32c_chained_count = argval;
231 : 0 : break;
232 : 0 : case 'f':
233 : 0 : g_fill_pattern = (uint8_t)argval;
234 : 0 : break;
235 : 0 : case 'k':
236 : 0 : g_idxd_kernel_mode = true;
237 : 0 : break;
238 : 0 : case 'm':
239 : 0 : g_core_mask = optarg;
240 : 0 : break;
241 : 0 : case 'o':
242 : 0 : g_xfer_size_bytes = argval;
243 : 0 : break;
244 : 0 : case 'P':
245 : 0 : g_fail_percent_goal = argval;
246 : 0 : break;
247 : 0 : case 'q':
248 : 0 : g_queue_depth = argval;
249 : 0 : break;
250 : 0 : case 'r':
251 : 0 : g_idxd_max_per_core = argval;
252 : 0 : break;
253 : 0 : case 's':
254 : 0 : g_crc32c_seed = argval;
255 : 0 : break;
256 : 0 : case 't':
257 : 0 : g_time_in_sec = argval;
258 : 0 : break;
259 : 0 : case 'y':
260 : 0 : g_verify = true;
261 : 0 : break;
262 : 0 : case 'w':
263 : 0 : g_workload_type = optarg;
264 [ # # ]: 0 : if (!strcmp(g_workload_type, "copy")) {
265 : 0 : g_workload_selection = IDXD_COPY;
266 [ # # ]: 0 : } else if (!strcmp(g_workload_type, "fill")) {
267 : 0 : g_workload_selection = IDXD_FILL;
268 [ # # ]: 0 : } else if (!strcmp(g_workload_type, "crc32c")) {
269 : 0 : g_workload_selection = IDXD_CRC32C;
270 [ # # ]: 0 : } else if (!strcmp(g_workload_type, "copy_crc32c")) {
271 : 0 : g_workload_selection = IDXD_COPY_CRC32C;
272 [ # # ]: 0 : } else if (!strcmp(g_workload_type, "compare")) {
273 : 0 : g_workload_selection = IDXD_COMPARE;
274 [ # # ]: 0 : } else if (!strcmp(g_workload_type, "dualcast")) {
275 : 0 : g_workload_selection = IDXD_DUALCAST;
276 : : }
277 : 0 : break;
278 : 0 : case 'h':
279 : 0 : usage();
280 : 0 : exit(0);
281 : 0 : default:
282 : 0 : usage();
283 : 0 : return 1;
284 : : }
285 : : }
286 : :
287 : 0 : return 0;
288 : : }
289 : :
290 : : static int
291 : 0 : register_workers(void)
292 : : {
293 : : uint32_t i;
294 : : struct worker_thread *worker;
295 : :
296 : 0 : g_workers = NULL;
297 : 0 : g_num_workers = 0;
298 : :
299 [ # # ]: 0 : SPDK_ENV_FOREACH_CORE(i) {
300 : 0 : worker = calloc(1, sizeof(*worker));
301 [ # # ]: 0 : if (worker == NULL) {
302 : 0 : fprintf(stderr, "Unable to allocate worker\n");
303 : 0 : return 1;
304 : : }
305 : :
306 : 0 : worker->core = i;
307 : 0 : worker->next = g_workers;
308 : 0 : g_workers = worker;
309 : 0 : g_num_workers++;
310 : : }
311 : :
312 : 0 : return 0;
313 : : }
314 : :
315 : : static void
316 : 0 : _free_task_buffers(struct idxd_task *task)
317 : : {
318 : : uint32_t i;
319 : :
320 [ # # ]: 0 : if (g_workload_selection == IDXD_CRC32C) {
321 [ # # ]: 0 : if (task->iovs) {
322 [ # # ]: 0 : for (i = 0; i < task->iov_cnt; i++) {
323 [ # # ]: 0 : if (task->iovs[i].iov_base) {
324 : 0 : spdk_dma_free(task->iovs[i].iov_base);
325 : : }
326 : : }
327 : 0 : free(task->iovs);
328 : : }
329 : : } else {
330 : 0 : spdk_dma_free(task->src);
331 : : }
332 : :
333 : 0 : spdk_dma_free(task->dst);
334 [ # # ]: 0 : if (g_workload_selection == IDXD_DUALCAST) {
335 : 0 : spdk_dma_free(task->dst2);
336 : : }
337 : 0 : }
338 : :
339 : : static inline void
340 : 0 : _free_task_buffers_in_pool(struct idxd_chan_entry *t)
341 : : {
342 : : struct idxd_task *task;
343 : :
344 [ # # ]: 0 : assert(t);
345 [ # # ]: 0 : while ((task = TAILQ_FIRST(&t->tasks_pool_head))) {
346 [ # # ]: 0 : TAILQ_REMOVE(&t->tasks_pool_head, task, link);
347 : 0 : _free_task_buffers(task);
348 : : }
349 : 0 : }
350 : :
351 : : static void
352 : 0 : free_idxd_chan_entry_resource(struct idxd_chan_entry *entry)
353 : : {
354 [ # # ]: 0 : assert(entry != NULL);
355 : :
356 [ # # ]: 0 : if (entry->ch) {
357 : 0 : spdk_idxd_put_channel(entry->ch);
358 : : }
359 : :
360 : 0 : _free_task_buffers_in_pool(entry);
361 : 0 : free(entry->task_base);
362 : 0 : free(entry);
363 : 0 : }
364 : :
365 : : static void
366 : 0 : unregister_workers(void)
367 : : {
368 : 0 : struct worker_thread *worker = g_workers, *next_worker;
369 : : struct idxd_chan_entry *entry, *entry1;
370 : :
371 : : /* Free worker thread */
372 [ # # ]: 0 : while (worker) {
373 : 0 : next_worker = worker->next;
374 : :
375 : 0 : entry = worker->ctx;
376 [ # # ]: 0 : while (entry) {
377 : 0 : entry1 = entry->next;
378 : 0 : free_idxd_chan_entry_resource(entry);
379 : 0 : entry = entry1;
380 : : }
381 : :
382 : 0 : free(worker);
383 : 0 : worker = next_worker;
384 : 0 : g_num_workers--;
385 : : }
386 : :
387 [ # # ]: 0 : assert(g_num_workers == 0);
388 : 0 : }
389 : :
390 : : static int
391 : 0 : _get_task_data_bufs(struct idxd_task *task)
392 : : {
393 : 0 : uint32_t align = 0;
394 : 0 : uint32_t i = 0;
395 : 0 : int dst_buff_len = g_xfer_size_bytes;
396 : :
397 : : /* For dualcast, the DSA HW requires 4K alignment on destination addresses but
398 : : * we do this for all engines to keep it simple.
399 : : */
400 [ # # ]: 0 : if (g_workload_selection == IDXD_DUALCAST) {
401 : 0 : align = ALIGN_4K;
402 : : }
403 : :
404 [ # # # # ]: 0 : if (g_workload_selection == IDXD_CRC32C || g_workload_selection == IDXD_COPY_CRC32C) {
405 [ # # ]: 0 : assert(g_crc32c_chained_count > 0);
406 : 0 : task->iov_cnt = g_crc32c_chained_count;
407 : 0 : task->iovs = calloc(task->iov_cnt, sizeof(struct iovec));
408 [ # # ]: 0 : if (!task->iovs) {
409 : 0 : fprintf(stderr, "cannot allocated task->iovs fot task=%p\n", task);
410 : 0 : return -ENOMEM;
411 : : }
412 : :
413 [ # # ]: 0 : if (g_workload_selection == IDXD_COPY_CRC32C) {
414 : 0 : dst_buff_len = g_xfer_size_bytes * g_crc32c_chained_count;
415 : : }
416 : :
417 [ # # ]: 0 : for (i = 0; i < task->iov_cnt; i++) {
418 : 0 : task->iovs[i].iov_base = spdk_dma_zmalloc(g_xfer_size_bytes, 0, NULL);
419 [ # # ]: 0 : if (task->iovs[i].iov_base == NULL) {
420 : 0 : return -ENOMEM;
421 : : }
422 : 0 : memset(task->iovs[i].iov_base, DATA_PATTERN, g_xfer_size_bytes);
423 : 0 : task->iovs[i].iov_len = g_xfer_size_bytes;
424 : : }
425 : :
426 : : } else {
427 : 0 : task->src = spdk_dma_zmalloc(g_xfer_size_bytes, 0, NULL);
428 [ # # ]: 0 : if (task->src == NULL) {
429 : 0 : fprintf(stderr, "Unable to alloc src buffer\n");
430 : 0 : return -ENOMEM;
431 : : }
432 : :
433 : : /* For fill, set the entire src buffer so we can check if verify is enabled. */
434 [ # # ]: 0 : if (g_workload_selection == IDXD_FILL) {
435 : 0 : memset(task->src, g_fill_pattern, g_xfer_size_bytes);
436 : : } else {
437 : 0 : memset(task->src, DATA_PATTERN, g_xfer_size_bytes);
438 : : }
439 : : }
440 : :
441 [ # # ]: 0 : if (g_workload_selection != IDXD_CRC32C) {
442 : 0 : task->dst = spdk_dma_zmalloc(dst_buff_len, align, NULL);
443 [ # # ]: 0 : if (task->dst == NULL) {
444 : 0 : fprintf(stderr, "Unable to alloc dst buffer\n");
445 : 0 : return -ENOMEM;
446 : : }
447 : :
448 : : /* For compare we want the buffers to match, otherwise not. */
449 [ # # ]: 0 : if (g_workload_selection == IDXD_COMPARE) {
450 : 0 : memset(task->dst, DATA_PATTERN, dst_buff_len);
451 : : } else {
452 : 0 : memset(task->dst, ~DATA_PATTERN, dst_buff_len);
453 : : }
454 : : }
455 : :
456 [ # # ]: 0 : if (g_workload_selection == IDXD_DUALCAST) {
457 : 0 : task->dst2 = spdk_dma_zmalloc(g_xfer_size_bytes, align, NULL);
458 [ # # ]: 0 : if (task->dst2 == NULL) {
459 : 0 : fprintf(stderr, "Unable to alloc dst buffer\n");
460 : 0 : return -ENOMEM;
461 : : }
462 : 0 : memset(task->dst2, ~DATA_PATTERN, g_xfer_size_bytes);
463 : : }
464 : :
465 : 0 : return 0;
466 : : }
467 : :
468 : : inline static struct idxd_task *
469 : 0 : _get_task(struct idxd_chan_entry *t)
470 : : {
471 : : struct idxd_task *task;
472 : :
473 [ # # ]: 0 : if (!TAILQ_EMPTY(&t->tasks_pool_head)) {
474 : 0 : task = TAILQ_FIRST(&t->tasks_pool_head);
475 [ # # ]: 0 : TAILQ_REMOVE(&t->tasks_pool_head, task, link);
476 : : } else {
477 : 0 : fprintf(stderr, "Unable to get idxd_task\n");
478 : 0 : return NULL;
479 : : }
480 : :
481 : 0 : return task;
482 : : }
483 : :
484 : : static int idxd_chan_poll(struct idxd_chan_entry *chan);
485 : :
486 : : static void
487 : 0 : drain_io(struct idxd_chan_entry *t)
488 : : {
489 [ # # ]: 0 : while (t->current_queue_depth > 0) {
490 : 0 : idxd_chan_poll(t);
491 : : }
492 : 0 : }
493 : :
494 : : /* Submit one operation using the same idxd task that just completed. */
495 : : static void
496 : 0 : _submit_single(struct idxd_chan_entry *t, struct idxd_task *task)
497 : : {
498 : : int random_num;
499 : 0 : int rc = 0;
500 : 0 : struct iovec siov = {};
501 : 0 : struct iovec diov = {};
502 : 0 : int flags = 0;
503 : :
504 [ # # ]: 0 : assert(t);
505 : :
506 : 0 : t->current_queue_depth++;
507 : :
508 [ # # ]: 0 : if (!TAILQ_EMPTY(&t->resubmits)) {
509 : 0 : rc = -EBUSY;
510 : 0 : goto queue;
511 : : }
512 : :
513 [ # # # # : 0 : switch (g_workload_selection) {
# # # ]
514 : 0 : case IDXD_COPY:
515 : 0 : siov.iov_base = task->src;
516 : 0 : siov.iov_len = g_xfer_size_bytes;
517 : 0 : diov.iov_base = task->dst;
518 : 0 : diov.iov_len = g_xfer_size_bytes;
519 : 0 : rc = spdk_idxd_submit_copy(t->ch, &diov, 1, &siov, 1, flags,
520 : : idxd_done, task);
521 : 0 : break;
522 : 0 : case IDXD_FILL:
523 : : /* For fill use the first byte of the task->dst buffer */
524 : 0 : diov.iov_base = task->dst;
525 : 0 : diov.iov_len = g_xfer_size_bytes;
526 : 0 : rc = spdk_idxd_submit_fill(t->ch, &diov, 1, *(uint64_t *)task->src,
527 : : flags, idxd_done, task);
528 : 0 : break;
529 : 0 : case IDXD_CRC32C:
530 [ # # ]: 0 : assert(task->iovs != NULL);
531 [ # # ]: 0 : assert(task->iov_cnt > 0);
532 : 0 : rc = spdk_idxd_submit_crc32c(t->ch, task->iovs, task->iov_cnt,
533 : : g_crc32c_seed, &task->crc_dst,
534 : : flags, idxd_done, task);
535 : 0 : break;
536 : 0 : case IDXD_COMPARE:
537 : 0 : random_num = rand() % 100;
538 [ # # ]: 0 : assert(task->dst != NULL);
539 [ # # ]: 0 : if (random_num < g_fail_percent_goal) {
540 : 0 : task->expected_status = -EILSEQ;
541 : 0 : *(uint8_t *)task->dst = ~DATA_PATTERN;
542 : : } else {
543 : 0 : task->expected_status = 0;
544 : 0 : *(uint8_t *)task->dst = DATA_PATTERN;
545 : : }
546 : 0 : siov.iov_base = task->src;
547 : 0 : siov.iov_len = g_xfer_size_bytes;
548 : 0 : diov.iov_base = task->dst;
549 : 0 : diov.iov_len = g_xfer_size_bytes;
550 : 0 : rc = spdk_idxd_submit_compare(t->ch, &siov, 1, &diov, 1, flags, idxd_done, task);
551 : 0 : break;
552 : 0 : case IDXD_DUALCAST:
553 : 0 : rc = spdk_idxd_submit_dualcast(t->ch, task->dst, task->dst2,
554 : 0 : task->src, g_xfer_size_bytes, flags, idxd_done, task);
555 : 0 : break;
556 : 0 : case IDXD_COPY_CRC32C:
557 : 0 : diov.iov_base = task->dst;
558 : 0 : diov.iov_len = g_xfer_size_bytes;
559 : 0 : rc = spdk_idxd_submit_copy_crc32c(t->ch, &diov, 1, task->iovs, task->iov_cnt, g_crc32c_seed,
560 : : &task->crc_dst,
561 : : flags, idxd_done, task);
562 : 0 : break;
563 : 0 : default:
564 : 0 : assert(false);
565 : : break;
566 : : }
567 : :
568 : 0 : queue:
569 [ # # ]: 0 : if (rc) {
570 : : /* Queue the task to be resubmitted on the next poll. */
571 [ # # # # ]: 0 : if (rc != -EBUSY && rc != -EAGAIN) {
572 : 0 : t->xfer_failed++;
573 : : }
574 : :
575 : 0 : TAILQ_INSERT_TAIL(&t->resubmits, task, link);
576 : : }
577 : 0 : }
578 : :
579 : : static int
580 : 0 : _vector_memcmp(void *_dst, struct iovec *src_iovs, uint32_t iovcnt)
581 : : {
582 : : uint32_t i;
583 : 0 : uint32_t ttl_len = 0;
584 : 0 : uint8_t *dst = (uint8_t *)_dst;
585 : :
586 [ # # ]: 0 : for (i = 0; i < iovcnt; i++) {
587 [ # # ]: 0 : if (memcmp(dst, src_iovs[i].iov_base, src_iovs[i].iov_len)) {
588 : 0 : return -1;
589 : : }
590 : 0 : dst += src_iovs[i].iov_len;
591 : 0 : ttl_len += src_iovs[i].iov_len;
592 : : }
593 : :
594 [ # # ]: 0 : if (ttl_len != iovcnt * g_xfer_size_bytes) {
595 : 0 : return -1;
596 : : }
597 : :
598 : 0 : return 0;
599 : : }
600 : :
601 : : static void
602 : 0 : idxd_done(void *arg1, int status)
603 : : {
604 : 0 : struct idxd_task *task = arg1;
605 : 0 : struct idxd_chan_entry *chan = task->worker_chan;
606 : : uint32_t sw_crc32c;
607 : :
608 [ # # ]: 0 : assert(chan);
609 [ # # ]: 0 : assert(chan->current_queue_depth > 0);
610 : :
611 [ # # # # ]: 0 : if (g_verify && status == 0) {
612 [ # # # # : 0 : switch (g_workload_selection) {
# # # ]
613 : 0 : case IDXD_COPY_CRC32C:
614 : 0 : sw_crc32c = spdk_crc32c_iov_update(task->iovs, task->iov_cnt, ~g_crc32c_seed);
615 [ # # ]: 0 : if (task->crc_dst != sw_crc32c) {
616 : 0 : SPDK_NOTICELOG("CRC-32C miscompare\n");
617 : 0 : chan->xfer_failed++;
618 : : }
619 [ # # ]: 0 : if (_vector_memcmp(task->dst, task->iovs, task->iov_cnt)) {
620 : 0 : SPDK_NOTICELOG("Data miscompare\n");
621 : 0 : chan->xfer_failed++;
622 : : }
623 : 0 : break;
624 : 0 : case IDXD_CRC32C:
625 : 0 : sw_crc32c = spdk_crc32c_iov_update(task->iovs, task->iov_cnt, ~g_crc32c_seed);
626 [ # # ]: 0 : if (task->crc_dst != sw_crc32c) {
627 : 0 : SPDK_NOTICELOG("CRC-32C miscompare\n");
628 : 0 : chan->xfer_failed++;
629 : : }
630 : 0 : break;
631 : 0 : case IDXD_COPY:
632 [ # # ]: 0 : if (memcmp(task->src, task->dst, g_xfer_size_bytes)) {
633 : 0 : SPDK_NOTICELOG("Data miscompare\n");
634 : 0 : chan->xfer_failed++;
635 : : }
636 : 0 : break;
637 : 0 : case IDXD_DUALCAST:
638 [ # # ]: 0 : if (memcmp(task->src, task->dst, g_xfer_size_bytes)) {
639 : 0 : SPDK_NOTICELOG("Data miscompare, first destination\n");
640 : 0 : chan->xfer_failed++;
641 : : }
642 [ # # ]: 0 : if (memcmp(task->src, task->dst2, g_xfer_size_bytes)) {
643 : 0 : SPDK_NOTICELOG("Data miscompare, second destination\n");
644 : 0 : chan->xfer_failed++;
645 : : }
646 : 0 : break;
647 : 0 : case IDXD_FILL:
648 [ # # ]: 0 : if (memcmp(task->dst, task->src, g_xfer_size_bytes)) {
649 : 0 : SPDK_NOTICELOG("Data miscompare\n");
650 : 0 : chan->xfer_failed++;
651 : : }
652 : 0 : break;
653 : 0 : case IDXD_COMPARE:
654 : 0 : break;
655 : 0 : default:
656 : 0 : assert(false);
657 : : break;
658 : : }
659 : 0 : }
660 : :
661 [ # # ]: 0 : if (task->expected_status == -EILSEQ) {
662 [ # # ]: 0 : assert(status != 0);
663 : 0 : chan->injected_miscompares++;
664 [ # # ]: 0 : } else if (status) {
665 : : /* Expected to pass but the idxd module reported an error (ex: COMPARE operation). */
666 : 0 : chan->xfer_failed++;
667 : : }
668 : :
669 : 0 : chan->xfer_completed++;
670 : 0 : chan->current_queue_depth--;
671 : :
672 [ # # ]: 0 : if (!chan->is_draining) {
673 : 0 : _submit_single(chan, task);
674 : : } else {
675 : 0 : TAILQ_INSERT_TAIL(&chan->tasks_pool_head, task, link);
676 : : }
677 : 0 : }
678 : :
679 : : static int
680 : 0 : dump_result(void)
681 : : {
682 : 0 : uint64_t total_completed = 0;
683 : 0 : uint64_t total_failed = 0;
684 : 0 : uint64_t total_miscompared = 0;
685 : : uint64_t total_xfer_per_sec, total_bw_in_MiBps;
686 : 0 : struct worker_thread *worker = g_workers;
687 : : struct idxd_chan_entry *t;
688 : :
689 : 0 : printf("\nIDXD_ChanID Core Transfers Bandwidth Failed Miscompares\n");
690 : 0 : printf("---------------------------------------------------------------------------\n");
691 [ # # ]: 0 : while (worker != NULL) {
692 : 0 : t = worker->ctx;
693 [ # # ]: 0 : while (t) {
694 : 0 : uint64_t xfer_per_sec = t->xfer_completed / g_time_in_sec;
695 : 0 : uint64_t bw_in_MiBps = (t->xfer_completed * g_xfer_size_bytes) /
696 : 0 : (g_time_in_sec * 1024 * 1024);
697 : :
698 : 0 : total_completed += t->xfer_completed;
699 : 0 : total_failed += t->xfer_failed;
700 : 0 : total_miscompared += t->injected_miscompares;
701 : :
702 [ # # ]: 0 : if (xfer_per_sec) {
703 : 0 : printf("%10d%5u%16" PRIu64 "/s%9" PRIu64 " MiB/s%11" PRIu64 " %15" PRIu64 "\n",
704 : : t->idxd_chan_id, worker->core, xfer_per_sec, bw_in_MiBps, t->xfer_failed,
705 : : t->injected_miscompares);
706 : : }
707 : 0 : t = t->next;
708 : : }
709 : :
710 : 0 : worker = worker->next;
711 : : }
712 : :
713 : 0 : total_xfer_per_sec = total_completed / g_time_in_sec;
714 : 0 : total_bw_in_MiBps = (total_completed * g_xfer_size_bytes) /
715 : 0 : (g_time_in_sec * 1024 * 1024);
716 : :
717 : 0 : printf("===========================================================================\n");
718 : 0 : printf("Total:%25" PRIu64 "/s%9" PRIu64 " MiB/s%11" PRIu64 " %15" PRIu64"\n\n",
719 : : total_xfer_per_sec, total_bw_in_MiBps, total_failed, total_miscompared);
720 : :
721 : 0 : return total_failed ? 1 : 0;
722 : : }
723 : :
724 : : static int
725 : 0 : submit_all(struct idxd_chan_entry *t)
726 : : {
727 : : int i;
728 : 0 : int remaining = g_queue_depth;
729 : : struct idxd_task *task;
730 : :
731 [ # # ]: 0 : for (i = 0; i < remaining; i++) {
732 : 0 : task = _get_task(t);
733 [ # # ]: 0 : if (task == NULL) {
734 : 0 : _free_task_buffers_in_pool(t);
735 : 0 : return -1;
736 : : }
737 : :
738 : : /* Submit as single task */
739 : 0 : _submit_single(t, task);
740 : : }
741 : :
742 : 0 : return 0;
743 : : }
744 : :
745 : : static int
746 : 0 : idxd_chan_poll(struct idxd_chan_entry *chan)
747 : : {
748 : : int rc;
749 : : struct idxd_task *task, *tmp;
750 : 0 : TAILQ_HEAD(, idxd_task) swap;
751 : :
752 : 0 : rc = spdk_idxd_process_events(chan->ch);
753 [ # # ]: 0 : if (rc < 0) {
754 : 0 : return rc;
755 : : }
756 : :
757 [ # # ]: 0 : if (!TAILQ_EMPTY(&chan->resubmits)) {
758 : 0 : TAILQ_INIT(&swap);
759 [ # # # # ]: 0 : TAILQ_SWAP(&swap, &chan->resubmits, idxd_task, link);
760 [ # # ]: 0 : TAILQ_FOREACH_SAFE(task, &swap, link, tmp) {
761 [ # # ]: 0 : TAILQ_REMOVE(&swap, task, link);
762 : 0 : chan->current_queue_depth--;
763 [ # # ]: 0 : if (!chan->is_draining) {
764 : 0 : _submit_single(chan, task);
765 : : } else {
766 : 0 : TAILQ_INSERT_TAIL(&chan->tasks_pool_head, task, link);
767 : : }
768 : : }
769 : : }
770 : :
771 : 0 : return rc;
772 : : }
773 : :
774 : : static int
775 : 0 : work_fn(void *arg)
776 : : {
777 : : uint64_t tsc_end;
778 : 0 : struct worker_thread *worker = (struct worker_thread *)arg;
779 : 0 : struct idxd_chan_entry *t = NULL;
780 : :
781 : 0 : printf("Starting thread on core %u\n", worker->core);
782 : :
783 : 0 : tsc_end = spdk_get_ticks() + g_time_in_sec * spdk_get_ticks_hz();
784 : :
785 : 0 : t = worker->ctx;
786 [ # # ]: 0 : while (t != NULL) {
787 [ # # ]: 0 : if (submit_all(t) != 0) {
788 : 0 : return -1;
789 : : }
790 : 0 : t = t->next;
791 : : }
792 : :
793 : : while (1) {
794 : 0 : t = worker->ctx;
795 [ # # ]: 0 : while (t != NULL) {
796 : 0 : idxd_chan_poll(t);
797 : 0 : t = t->next;
798 : : }
799 : :
800 [ # # ]: 0 : if (spdk_get_ticks() > tsc_end) {
801 : 0 : break;
802 : : }
803 : : }
804 : :
805 : 0 : t = worker->ctx;
806 [ # # ]: 0 : while (t != NULL) {
807 : : /* begin to drain io */
808 : 0 : t->is_draining = true;
809 : 0 : drain_io(t);
810 : 0 : t = t->next;
811 : : }
812 : :
813 : 0 : return 0;
814 : : }
815 : :
816 : : static int
817 : 0 : init_env(void)
818 : : {
819 : 0 : struct spdk_env_opts opts;
820 : :
821 : 0 : spdk_env_opts_init(&opts);
822 : 0 : opts.name = "idxd_perf";
823 : 0 : opts.core_mask = g_core_mask;
824 [ # # ]: 0 : if (spdk_env_init(&opts) < 0) {
825 : 0 : return 1;
826 : : }
827 : :
828 : 0 : return 0;
829 : : }
830 : :
831 : : static struct spdk_idxd_device *
832 : 0 : get_next_idxd(void)
833 : : {
834 : : struct spdk_idxd_device *idxd;
835 : :
836 [ # # ]: 0 : if (g_next_device == NULL) {
837 : 0 : return NULL;
838 : : }
839 : :
840 : 0 : idxd = g_next_device->idxd;
841 : :
842 : 0 : g_next_device = TAILQ_NEXT(g_next_device, tailq);
843 : :
844 : 0 : return idxd;
845 : : }
846 : :
847 : : static int
848 : 0 : init_idxd_chan_entry(struct idxd_chan_entry *t, struct spdk_idxd_device *idxd)
849 : : {
850 : 0 : int num_tasks = g_allocate_depth;
851 : : struct idxd_task *task;
852 : : int i;
853 : :
854 [ # # ]: 0 : assert(t != NULL);
855 : :
856 : 0 : TAILQ_INIT(&t->tasks_pool_head);
857 : 0 : TAILQ_INIT(&t->resubmits);
858 : 0 : t->ch = spdk_idxd_get_channel(idxd);
859 [ # # ]: 0 : if (t->ch == NULL) {
860 : 0 : fprintf(stderr, "Failed to get channel\n");
861 : 0 : goto err;
862 : : }
863 : :
864 : 0 : t->task_base = calloc(g_allocate_depth, sizeof(struct idxd_task));
865 [ # # ]: 0 : if (t->task_base == NULL) {
866 : 0 : fprintf(stderr, "Could not allocate task base.\n");
867 : 0 : goto err;
868 : : }
869 : :
870 : 0 : task = t->task_base;
871 [ # # ]: 0 : for (i = 0; i < num_tasks; i++) {
872 : 0 : TAILQ_INSERT_TAIL(&t->tasks_pool_head, task, link);
873 : 0 : task->worker_chan = t;
874 [ # # ]: 0 : if (_get_task_data_bufs(task)) {
875 : 0 : fprintf(stderr, "Unable to get data bufs\n");
876 : 0 : goto err;
877 : : }
878 : 0 : task++;
879 : : }
880 : :
881 : 0 : return 0;
882 : :
883 : 0 : err:
884 : 0 : free_idxd_chan_entry_resource(t);
885 : 0 : return -1;
886 : : }
887 : :
888 : : static int
889 : 0 : associate_workers_with_idxd_device(void)
890 : : {
891 : 0 : struct spdk_idxd_device *idxd = get_next_idxd();
892 : 0 : struct worker_thread *worker = g_workers;
893 : 0 : int i = 0;
894 : : struct idxd_chan_entry *t;
895 : :
896 [ # # ]: 0 : while (idxd != NULL) {
897 [ # # ]: 0 : if (worker->chan_num >= g_idxd_max_per_core) {
898 : 0 : fprintf(stdout, "Notice: we cannot let single worker assign idxd devices\n"
899 : : "more than %d, you need use -r while starting app to change this value\n",
900 : : g_idxd_max_per_core);
901 : 0 : break;
902 : : }
903 : :
904 : 0 : t = calloc(1, sizeof(struct idxd_chan_entry));
905 [ # # ]: 0 : if (!t) {
906 : 0 : return -1;
907 : : }
908 : :
909 : 0 : t->idxd_chan_id = i;
910 : :
911 [ # # ]: 0 : if (init_idxd_chan_entry(t, idxd)) {
912 : 0 : fprintf(stdout, "idxd device=%p is bound on core=%d\n", idxd, worker->core);
913 : 0 : return -1;
914 : :
915 : : }
916 : 0 : fprintf(stdout, "idxd device=%p is bound on core=%d\n", idxd, worker->core);
917 : :
918 : 0 : t->next = worker->ctx;
919 : 0 : worker->ctx = t;
920 : 0 : worker->chan_num++;
921 : :
922 : 0 : worker = worker->next;
923 [ # # ]: 0 : if (worker == NULL) {
924 : 0 : worker = g_workers;
925 : : }
926 : :
927 : 0 : idxd = get_next_idxd();
928 : 0 : i++;
929 : : }
930 : :
931 : 0 : return 0;
932 : : }
933 : :
934 : : int
935 : 0 : main(int argc, char **argv)
936 : : {
937 : : int rc;
938 : : struct worker_thread *worker, *main_worker;
939 : : unsigned main_core;
940 : :
941 [ # # ]: 0 : if (parse_args(argc, argv) != 0) {
942 : 0 : return -1;
943 : : }
944 : :
945 [ # # ]: 0 : if (init_env() != 0) {
946 : 0 : return -1;
947 : : }
948 : :
949 [ # # ]: 0 : if (register_workers() != 0) {
950 : 0 : rc = -1;
951 : 0 : goto cleanup;
952 : : }
953 : :
954 [ # # ]: 0 : if (idxd_init() != 0) {
955 : 0 : rc = -1;
956 : 0 : goto cleanup;
957 : : }
958 : :
959 [ # # ]: 0 : if (g_num_devices == 0) {
960 : 0 : printf("No idxd device found\n");
961 : 0 : rc = -1;
962 : 0 : goto cleanup;
963 : : }
964 : :
965 [ # # ]: 0 : if ((g_workload_selection != IDXD_COPY) &&
966 [ # # ]: 0 : (g_workload_selection != IDXD_FILL) &&
967 [ # # ]: 0 : (g_workload_selection != IDXD_CRC32C) &&
968 [ # # ]: 0 : (g_workload_selection != IDXD_COPY_CRC32C) &&
969 [ # # ]: 0 : (g_workload_selection != IDXD_COMPARE) &&
970 [ # # ]: 0 : (g_workload_selection != IDXD_DUALCAST)) {
971 : 0 : usage();
972 : 0 : rc = -1;
973 : 0 : goto cleanup;
974 : : }
975 : :
976 [ # # # # ]: 0 : if (g_allocate_depth > 0 && g_queue_depth > g_allocate_depth) {
977 : 0 : fprintf(stdout, "allocate depth must be at least as big as queue depth\n");
978 : 0 : usage();
979 : 0 : rc = -1;
980 : 0 : goto cleanup;
981 : : }
982 : :
983 [ # # ]: 0 : if (g_allocate_depth == 0) {
984 : 0 : g_allocate_depth = g_queue_depth;
985 : : }
986 : :
987 [ # # # # ]: 0 : if ((g_workload_selection == IDXD_CRC32C || g_workload_selection == IDXD_COPY_CRC32C) &&
988 [ # # ]: 0 : g_crc32c_chained_count == 0) {
989 : 0 : usage();
990 : 0 : rc = -1;
991 : 0 : goto cleanup;
992 : : }
993 : :
994 : 0 : g_next_device = TAILQ_FIRST(&g_idxd_devices);
995 [ # # ]: 0 : if (associate_workers_with_idxd_device() != 0) {
996 : 0 : rc = -1;
997 : 0 : goto cleanup;
998 : : }
999 : :
1000 : 0 : dump_user_config();
1001 : : /* Launch all of the secondary workers */
1002 : 0 : main_core = spdk_env_get_current_core();
1003 : 0 : main_worker = NULL;
1004 : 0 : worker = g_workers;
1005 [ # # ]: 0 : while (worker != NULL) {
1006 [ # # ]: 0 : if (worker->core != main_core) {
1007 : 0 : spdk_env_thread_launch_pinned(worker->core, work_fn, worker);
1008 : : } else {
1009 [ # # ]: 0 : assert(main_worker == NULL);
1010 : 0 : main_worker = worker;
1011 : : }
1012 : 0 : worker = worker->next;
1013 : : }
1014 : :
1015 [ # # ]: 0 : assert(main_worker != NULL);
1016 : 0 : rc = work_fn(main_worker);
1017 [ # # ]: 0 : if (rc != 0) {
1018 : 0 : goto cleanup;
1019 : : }
1020 : :
1021 : 0 : spdk_env_thread_wait_all();
1022 : :
1023 : 0 : rc = dump_result();
1024 : 0 : cleanup:
1025 : 0 : unregister_workers();
1026 : 0 : idxd_exit();
1027 : :
1028 : 0 : spdk_env_fini();
1029 : 0 : return rc;
1030 : : }
|