Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (C) 2020 Intel Corporation.
3 : : * Copyright (c) Eideticom Inc.
4 : : * All rights reserved.
5 : : */
6 : :
7 : : #include "spdk/stdinc.h"
8 : :
9 : : #include "spdk/env.h"
10 : : #include "spdk/nvme.h"
11 : : #include "spdk/string.h"
12 : :
13 : : #define CMB_COPY_DELIM "-"
14 : : #define CMB_COPY_READ 0
15 : : #define CMB_COPY_WRITE 1
16 : :
17 : : struct nvme_io {
18 : : struct spdk_nvme_ctrlr *ctrlr;
19 : : struct spdk_nvme_transport_id trid;
20 : : struct spdk_nvme_qpair *qpair;
21 : : struct spdk_nvme_ns *ns;
22 : : unsigned nsid;
23 : : unsigned slba;
24 : : unsigned nlbas;
25 : : uint32_t lba_size;
26 : : unsigned done;
27 : : };
28 : :
29 : : struct cmb_t {
30 : : struct spdk_nvme_transport_id trid;
31 : : struct spdk_nvme_ctrlr *ctrlr;
32 : : };
33 : :
34 : : struct config {
35 : : struct nvme_io read;
36 : : struct nvme_io write;
37 : : struct cmb_t cmb;
38 : : size_t copy_size;
39 : : };
40 : :
41 : : static struct config g_config;
42 : :
43 : : /* Namespaces index from 1. Return 0 to invoke an error */
44 : : static unsigned
45 : 4 : get_nsid(const struct spdk_nvme_transport_id *trid)
46 : : {
47 [ + + # # : 4 : if (!strcmp(trid->traddr, g_config.read.trid.traddr)) {
# # # # #
# ]
48 [ # # ]: 2 : return g_config.read.nsid;
49 : : }
50 [ + - # # : 2 : if (!strcmp(trid->traddr, g_config.write.trid.traddr)) {
# # # # #
# # # ]
51 [ # # # # ]: 2 : return g_config.write.nsid;
52 : : }
53 : 0 : return 0;
54 : 0 : }
55 : :
56 : : static int
57 : 4 : get_rw(const struct spdk_nvme_transport_id *trid)
58 : : {
59 [ + + # # : 4 : if (!strcmp(trid->traddr, g_config.read.trid.traddr)) {
# # # # #
# ]
60 : 2 : return CMB_COPY_READ;
61 : : }
62 [ + - # # : 2 : if (!strcmp(trid->traddr, g_config.write.trid.traddr)) {
# # # # #
# # # ]
63 : 2 : return CMB_COPY_WRITE;
64 : : }
65 : 0 : return -1;
66 : 0 : }
67 : :
68 : : static void
69 : 4 : check_io(void *arg, const struct spdk_nvme_cpl *completion)
70 : : {
71 : 4 : int *rw = (unsigned *)arg;
72 : :
73 [ + + # # ]: 4 : if (*rw == CMB_COPY_READ) {
74 [ # # ]: 2 : g_config.read.done = 1;
75 : 0 : } else {
76 [ # # # # ]: 2 : g_config.write.done = 1;
77 : : }
78 : 4 : }
79 : :
80 : : static int
81 : 2 : cmb_copy(void)
82 : : {
83 : 2 : int rc = 0, rw;
84 : : void *buf;
85 : 0 : size_t sz;
86 : :
87 : : /* Allocate QPs for the read and write controllers */
88 [ # # ]: 2 : g_config.read.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_config.read.ctrlr, NULL, 0);
89 [ # # # # : 2 : g_config.write.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_config.write.ctrlr, NULL, 0);
# # # # ]
90 [ + - - + : 2 : if (g_config.read.qpair == NULL || g_config.read.qpair == NULL) {
# # # # ]
91 [ # # ]: 0 : printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed\n");
92 : 0 : return -ENOMEM;
93 : : }
94 : :
95 : : /* Allocate a buffer from our CMB */
96 [ # # # # ]: 2 : buf = spdk_nvme_ctrlr_map_cmb(g_config.cmb.ctrlr, &sz);
97 [ + - - + : 2 : if (buf == NULL || sz < g_config.copy_size) {
# # ]
98 [ # # ]: 0 : printf("ERROR: buffer allocation failed\n");
99 [ # # ]: 0 : printf("Are you sure %s has a valid CMB?\n",
100 [ # # # # ]: 0 : g_config.cmb.trid.traddr);
101 : 0 : return -ENOMEM;
102 : : }
103 : :
104 : : /* Clear the done flags */
105 [ # # ]: 2 : g_config.read.done = 0;
106 [ # # # # ]: 2 : g_config.write.done = 0;
107 : :
108 : 2 : rw = CMB_COPY_READ;
109 : : /* Do the read to the CMB IO buffer */
110 [ # # # # ]: 2 : rc = spdk_nvme_ns_cmd_read(g_config.read.ns, g_config.read.qpair, buf,
111 [ # # # # ]: 2 : g_config.read.slba, g_config.read.nlbas,
112 : : check_io, &rw, 0);
113 [ - + ]: 2 : if (rc != 0) {
114 [ # # # # ]: 0 : fprintf(stderr, "starting read I/O failed\n");
115 : 0 : return -EIO;
116 : : }
117 [ + + # # ]: 881 : while (!g_config.read.done) {
118 [ # # ]: 879 : spdk_nvme_qpair_process_completions(g_config.read.qpair, 0);
119 : : }
120 : :
121 : : /* Do the write from the CMB IO buffer */
122 : 2 : rw = CMB_COPY_WRITE;
123 [ # # # # : 2 : rc = spdk_nvme_ns_cmd_write(g_config.write.ns, g_config.write.qpair, buf,
# # # # ]
124 [ # # # # : 2 : g_config.write.slba, g_config.write.nlbas,
# # # # ]
125 : : check_io, &rw, 0);
126 [ - + ]: 2 : if (rc != 0) {
127 [ # # # # ]: 0 : fprintf(stderr, "starting write I/O failed\n");
128 : 0 : return -EIO;
129 : : }
130 [ + + # # : 1521 : while (!g_config.write.done) {
# # ]
131 [ # # # # ]: 1519 : spdk_nvme_qpair_process_completions(g_config.write.qpair, 0);
132 : : }
133 : :
134 : : /* Clear the done flags */
135 [ # # ]: 2 : g_config.read.done = 0;
136 [ # # # # ]: 2 : g_config.write.done = 0;
137 : :
138 : : /* Free CMB buffer */
139 [ # # # # ]: 2 : spdk_nvme_ctrlr_unmap_cmb(g_config.cmb.ctrlr);
140 : :
141 : : /* Free the queues */
142 [ # # ]: 2 : spdk_nvme_ctrlr_free_io_qpair(g_config.read.qpair);
143 [ # # # # ]: 2 : spdk_nvme_ctrlr_free_io_qpair(g_config.write.qpair);
144 : :
145 : 2 : return rc;
146 : 0 : }
147 : :
148 : : static bool
149 : 4 : probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
150 : : struct spdk_nvme_ctrlr_opts *opts)
151 : : {
152 : : /* We will only attach to the read or write controller */
153 [ + + # # : 4 : if (strcmp(trid->traddr, g_config.read.trid.traddr) &&
# # # # #
# # # ]
154 [ - + # # : 2 : strcmp(trid->traddr, g_config.write.trid.traddr)) {
# # # # #
# ]
155 [ # # ]: 0 : printf("%s - not probed %s!\n", __func__, trid->traddr);
156 : 0 : return 0;
157 : : }
158 : :
159 [ # # # # ]: 4 : opts->use_cmb_sqs = false;
160 : :
161 [ # # ]: 4 : printf("%s - probed %s!\n", __func__, trid->traddr);
162 : 4 : return 1;
163 : 0 : }
164 : :
165 : : static void
166 : 4 : attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
167 : : struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts)
168 : : {
169 : : struct spdk_nvme_ns *ns;
170 : :
171 : 4 : ns = spdk_nvme_ctrlr_get_ns(ctrlr, get_nsid(trid));
172 [ - + ]: 4 : if (ns == NULL) {
173 [ # # # # ]: 0 : fprintf(stderr, "Could not locate namespace %d on controller %s.\n",
174 [ # # ]: 0 : get_nsid(trid), trid->traddr);
175 [ # # ]: 0 : exit(-1);
176 : : }
177 [ + + ]: 4 : if (get_rw(trid) == CMB_COPY_READ) {
178 : 2 : g_config.read.ctrlr = ctrlr;
179 [ # # ]: 2 : g_config.read.ns = ns;
180 [ # # ]: 2 : g_config.read.lba_size = spdk_nvme_ns_get_sector_size(ns);
181 : 0 : } else {
182 [ # # # # ]: 2 : g_config.write.ctrlr = ctrlr;
183 [ # # # # ]: 2 : g_config.write.ns = ns;
184 [ # # # # ]: 2 : g_config.write.lba_size = spdk_nvme_ns_get_sector_size(ns);
185 : : }
186 [ # # ]: 4 : printf("%s - attached %s!\n", __func__, trid->traddr);
187 : :
188 : 4 : return;
189 : : }
190 : :
191 : : static void
192 : 0 : usage(char *program_name)
193 : : {
194 [ # # ]: 0 : printf("%s options (all mandatory)", program_name);
195 : 0 : printf("\n");
196 [ # # ]: 0 : printf("\t[-r NVMe read parameters]\n");
197 [ # # ]: 0 : printf("\t[-w NVMe write parameters]\n");
198 [ # # ]: 0 : printf("\t[-c CMB to use for data buffers]\n");
199 : 0 : printf("\n");
200 [ # # ]: 0 : printf("Read/Write params:\n");
201 [ # # ]: 0 : printf(" <pci id>-<namespace>-<start LBA>-<number of LBAs>\n");
202 : 0 : }
203 : :
204 : : static void
205 : 4 : parse(char *in, struct nvme_io *io)
206 : : {
207 : 4 : char *tok = NULL;
208 : 4 : char *sp = NULL;
209 : : long int val;
210 : :
211 [ # # # # ]: 4 : tok = strtok_r(in, CMB_COPY_DELIM, &sp);
212 [ - + ]: 4 : if (tok == NULL) {
213 : 0 : goto err;
214 : : }
215 [ # # # # : 4 : snprintf(&io->trid.traddr[0], SPDK_NVMF_TRADDR_MAX_LEN + 1,
# # # # ]
216 : 0 : "%s", tok);
217 : :
218 [ # # # # ]: 4 : tok = strtok_r(NULL, CMB_COPY_DELIM, &sp);
219 [ - + ]: 4 : if (tok == NULL) {
220 : 0 : goto err;
221 : : }
222 : 4 : val = spdk_strtol(tok, 10);
223 [ - + ]: 4 : if (val < 0) {
224 : 0 : goto err;
225 : : }
226 [ # # # # ]: 4 : io->nsid = (unsigned)val;
227 : :
228 [ # # # # ]: 4 : tok = strtok_r(NULL, CMB_COPY_DELIM, &sp);
229 [ - + ]: 4 : if (tok == NULL) {
230 : 0 : goto err;
231 : : }
232 : 4 : val = spdk_strtol(tok, 10);
233 [ - + ]: 4 : if (val < 0) {
234 : 0 : goto err;
235 : : }
236 [ # # # # ]: 4 : io->slba = (unsigned)val;
237 : :
238 [ # # # # ]: 4 : tok = strtok_r(NULL, CMB_COPY_DELIM, &sp);
239 [ - + ]: 4 : if (tok == NULL) {
240 : 0 : goto err;
241 : : }
242 : 4 : val = spdk_strtol(tok, 10);
243 [ - + ]: 4 : if (val < 0) {
244 : 0 : goto err;
245 : : }
246 [ # # # # ]: 4 : io->nlbas = (unsigned)val;
247 : :
248 [ # # # # ]: 4 : tok = strtok_r(NULL, CMB_COPY_DELIM, &sp);
249 [ - + ]: 4 : if (tok != NULL) {
250 : 0 : goto err;
251 : : }
252 : 4 : return;
253 : :
254 : 0 : err:
255 [ # # # # ]: 0 : fprintf(stderr, "%s: error parsing %s\n", __func__, in);
256 [ # # ]: 0 : exit(-1);
257 : :
258 : : }
259 : :
260 : : static int
261 : 2 : parse_args(int argc, char **argv)
262 : : {
263 : : int op;
264 : 2 : unsigned read = 0, write = 0, cmb = 0;
265 : :
266 [ + + # # : 8 : while ((op = getopt(argc, argv, "r:w:c:")) != -1) {
# # ]
267 [ + + + - ]: 6 : switch (op) {
268 : 2 : case 'r':
269 : 2 : parse(optarg, &g_config.read);
270 : 2 : read = 1;
271 : 2 : break;
272 : 2 : case 'w':
273 : 2 : parse(optarg, &g_config.write);
274 : 2 : write = 1;
275 : 2 : break;
276 : 2 : case 'c':
277 [ # # # # ]: 2 : snprintf(g_config.cmb.trid.traddr, SPDK_NVMF_TRADDR_MAX_LEN + 1,
278 : 0 : "%s", optarg);
279 : 2 : cmb = 1;
280 : 2 : break;
281 : 0 : default:
282 [ # # # # ]: 0 : usage(argv[0]);
283 : 0 : return 1;
284 : : }
285 : : }
286 : :
287 [ + - + - : 2 : if ((!read || !write || !cmb)) {
- + ]
288 [ # # # # ]: 0 : usage(argv[0]);
289 : 0 : return 1;
290 : : }
291 : :
292 : 2 : return 0;
293 : 0 : }
294 : :
295 : : int
296 : 2 : main(int argc, char **argv)
297 : : {
298 : 2 : int rc = 0;
299 : 0 : struct spdk_env_opts opts;
300 : :
301 : : /*
302 : : * Parse the input arguments. For now we use the following
303 : : * format list:
304 : : *
305 : : * <pci id>-<namespace>-<start LBA>-<number of LBAs>
306 : : *
307 : : */
308 : 2 : rc = parse_args(argc, argv);
309 [ - + ]: 2 : if (rc) {
310 [ # # ]: 0 : fprintf(stderr, "Error in parse_args(): %d\n",
311 : 0 : rc);
312 : 0 : return -1;
313 : : }
314 : :
315 : : /*
316 : : * SPDK relies on an abstraction around the local environment
317 : : * named env that handles memory allocation and PCI device operations.
318 : : * This library must be initialized first.
319 : : *
320 : : */
321 [ # # ]: 2 : opts.opts_size = sizeof(opts);
322 : 2 : spdk_env_opts_init(&opts);
323 : 2 : opts.name = "cmb_copy";
324 [ # # ]: 2 : opts.shm_id = 0;
325 [ - + ]: 2 : if (spdk_env_init(&opts) < 0) {
326 [ # # ]: 0 : fprintf(stderr, "Unable to initialize SPDK env\n");
327 : 0 : return 1;
328 : : }
329 : :
330 : : /*
331 : : * CMBs only apply to PCIe attached NVMe controllers so we
332 : : * only probe the PCIe bus. This is the default when we pass
333 : : * in NULL for the first argument.
334 : : */
335 : :
336 : 2 : rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL);
337 [ - + ]: 2 : if (rc) {
338 [ # # ]: 0 : fprintf(stderr, "Error in spdk_nvme_probe(): %d\n",
339 : 0 : rc);
340 : 0 : return -1;
341 : : }
342 : :
343 : : /*
344 : : * For now enforce that the read and write controller are not
345 : : * the same. This avoids an internal only DMA.
346 : : */
347 [ - + # # : 2 : if (!strcmp(g_config.write.trid.traddr, g_config.read.trid.traddr)) {
# # # # #
# # # ]
348 [ # # ]: 0 : fprintf(stderr, "Read and Write controllers must differ!\n");
349 : 0 : return -1;
350 : : }
351 : :
352 : : /*
353 : : * Perform a few sanity checks and set the buffer size for the
354 : : * CMB.
355 : : */
356 [ # # # # : 2 : if (g_config.read.nlbas * g_config.read.lba_size !=
# # ]
357 [ - + # # : 2 : g_config.write.nlbas * g_config.write.lba_size) {
# # # # ]
358 [ # # ]: 0 : fprintf(stderr, "Read and write sizes do not match!\n");
359 : 0 : return -1;
360 : : }
361 [ # # # # : 2 : g_config.copy_size = g_config.read.nlbas * g_config.read.lba_size;
# # ]
362 : :
363 : : /*
364 : : * Get the ctrlr pointer for the CMB. For now we assume this
365 : : * is either the read or write NVMe controller though in
366 : : * theory that is not a necessary condition.
367 : : */
368 : :
369 [ + - # # : 2 : if (!strcmp(g_config.cmb.trid.traddr, g_config.read.trid.traddr)) {
# # # # #
# # # ]
370 [ # # # # ]: 2 : g_config.cmb.ctrlr = g_config.read.ctrlr;
371 : 0 : }
372 [ - + # # : 2 : if (!strcmp(g_config.cmb.trid.traddr, g_config.write.trid.traddr)) {
# # # # #
# # # #
# ]
373 [ # # # # : 0 : g_config.cmb.ctrlr = g_config.write.ctrlr;
# # # # ]
374 : 0 : }
375 : :
376 [ + - - + : 2 : if (!g_config.read.ctrlr || !g_config.write.ctrlr) {
# # # # ]
377 [ # # ]: 0 : fprintf(stderr, "No NVMe controller that support CMB was found!\n");
378 : 0 : return -1;
379 : : }
380 : :
381 : : /*
382 : : * Call the cmb_copy() function which performs the CMB
383 : : * based copy or returns an error code if it fails.
384 : : */
385 : 2 : rc = cmb_copy();
386 [ - + ]: 2 : if (rc) {
387 [ # # ]: 0 : fprintf(stderr, "Error in spdk_cmb_copy(): %d\n",
388 : 0 : rc);
389 : 0 : return -1;
390 : : }
391 : :
392 : 2 : return rc;
393 : 0 : }
|