/*- * BSD LICENSE * * Copyright (c) Eideticom Inc. * 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 Eideticom Inc, 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/env.h" #include "spdk/nvme.h" #define CMB_COPY_DELIM "-" #define CMB_COPY_READ 0 #define CMB_COPY_WRITE 1 struct nvme_io { struct spdk_nvme_ctrlr *ctrlr; struct spdk_nvme_transport_id trid; struct spdk_nvme_qpair *qpair; struct spdk_nvme_ns *ns; unsigned nsid; unsigned slba; unsigned nlbas; uint32_t lba_size; unsigned done; }; struct cmb_t { struct spdk_nvme_transport_id trid; struct spdk_nvme_ctrlr *ctrlr; }; struct config { struct nvme_io read; struct nvme_io write; struct cmb_t cmb; size_t copy_size; }; static struct config g_config; /* Namespaces index from 1. Return 0 to invoke an error */ static unsigned get_nsid(const struct spdk_nvme_transport_id *trid) { if (!strcmp(trid->traddr, g_config.read.trid.traddr)) { return g_config.read.nsid; } if (!strcmp(trid->traddr, g_config.write.trid.traddr)) { return g_config.write.nsid; } return 0; } static int get_rw(const struct spdk_nvme_transport_id *trid) { if (!strcmp(trid->traddr, g_config.read.trid.traddr)) { return CMB_COPY_READ; } if (!strcmp(trid->traddr, g_config.write.trid.traddr)) { return CMB_COPY_WRITE; } return -1; } static void check_io(void *arg, const struct spdk_nvme_cpl *completion) { int *rw = (unsigned *)arg; if (*rw == CMB_COPY_READ) { g_config.read.done = 1; } else { g_config.write.done = 1; } } static int cmb_copy(void) { int rc = 0, rw; void *buf; /* Allocate QPs for the read and write controllers */ g_config.read.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_config.read.ctrlr, NULL, 0); g_config.write.qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_config.write.ctrlr, NULL, 0); if (g_config.read.qpair == NULL || g_config.read.qpair == NULL) { printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed\n"); return -ENOMEM; } /* Allocate a buffer from our CMB */ buf = spdk_nvme_ctrlr_alloc_cmb_io_buffer(g_config.cmb.ctrlr, g_config.copy_size); if (buf == NULL) { printf("ERROR: buffer allocation failed\n"); printf("Are you sure %s has a valid CMB?\n", g_config.cmb.trid.traddr); return -ENOMEM; } /* Clear the done flags */ g_config.read.done = 0; g_config.write.done = 0; rw = CMB_COPY_READ; /* Do the read to the CMB IO buffer */ rc = spdk_nvme_ns_cmd_read(g_config.read.ns, g_config.read.qpair, buf, g_config.read.slba, g_config.read.nlbas, check_io, &rw, 0); if (rc != 0) { fprintf(stderr, "starting read I/O failed\n"); return -EIO; } while (!g_config.read.done) { spdk_nvme_qpair_process_completions(g_config.read.qpair, 0); } /* Do the write from the CMB IO buffer */ rw = CMB_COPY_WRITE; rc = spdk_nvme_ns_cmd_write(g_config.write.ns, g_config.write.qpair, buf, g_config.write.slba, g_config.write.nlbas, check_io, &rw, 0); if (rc != 0) { fprintf(stderr, "starting write I/O failed\n"); return -EIO; } while (!g_config.write.done) { spdk_nvme_qpair_process_completions(g_config.write.qpair, 0); } /* Clear the done flags */ g_config.read.done = 0; g_config.write.done = 0; /* Free CMB buffer */ spdk_nvme_ctrlr_free_cmb_io_buffer(g_config.cmb.ctrlr, buf, g_config.copy_size); /* Free the queues */ spdk_nvme_ctrlr_free_io_qpair(g_config.read.qpair); spdk_nvme_ctrlr_free_io_qpair(g_config.write.qpair); return rc; } static bool probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr_opts *opts) { /* We will only attach to the read or write controller */ if (strcmp(trid->traddr, g_config.read.trid.traddr) && strcmp(trid->traddr, g_config.write.trid.traddr)) { printf("%s - not probed %s!\n", __func__, trid->traddr); return 0; } printf("%s - probed %s!\n", __func__, trid->traddr); return 1; } static void attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) { struct spdk_nvme_ns *ns; ns = spdk_nvme_ctrlr_get_ns(ctrlr, get_nsid(trid)); if (ns == NULL) { fprintf(stderr, "Could not locate namespace %d on controller %s.\n", get_nsid(trid), trid->traddr); exit(-1); } if (get_rw(trid) == CMB_COPY_READ) { g_config.read.ctrlr = ctrlr; g_config.read.ns = ns; g_config.read.lba_size = spdk_nvme_ns_get_sector_size(ns); } else { g_config.write.ctrlr = ctrlr; g_config.write.ns = ns; g_config.write.lba_size = spdk_nvme_ns_get_sector_size(ns); } printf("%s - attached %s!\n", __func__, trid->traddr); return; } static void usage(char *program_name) { printf("%s options (all mandatory)", program_name); printf("\n"); printf("\t[-r NVMe read parameters]\n"); printf("\t[-w NVMe write parameters]\n"); printf("\t[-c CMB to use for data buffers]\n"); printf("\n"); printf("Read/Write params:\n"); printf(" ---\n"); } static void parse(char *in, struct nvme_io *io) { char *tok = NULL; tok = strtok(in, CMB_COPY_DELIM); if (tok == NULL) { goto err; } snprintf(&io->trid.traddr[0], SPDK_NVMF_TRADDR_MAX_LEN + 1, "%s", tok); tok = strtok(NULL, CMB_COPY_DELIM); if (tok == NULL) { goto err; } io->nsid = atoi(tok); tok = strtok(NULL, CMB_COPY_DELIM); if (tok == NULL) { goto err; } io->slba = atoi(tok); tok = strtok(NULL, CMB_COPY_DELIM); if (tok == NULL) { goto err; } io->nlbas = atoi(tok); tok = strtok(NULL, CMB_COPY_DELIM); if (tok != NULL) { goto err; } return; err: fprintf(stderr, "%s: error parsing %s\n", __func__, in); exit(-1); } static int parse_args(int argc, char **argv) { int op; unsigned read = 0, write = 0, cmb = 0; while ((op = getopt(argc, argv, "r:w:c:")) != -1) { switch (op) { case 'r': parse(optarg, &g_config.read); read = 1; break; case 'w': parse(optarg, &g_config.write); write = 1; break; case 'c': snprintf(g_config.cmb.trid.traddr, SPDK_NVMF_TRADDR_MAX_LEN + 1, "%s", optarg); cmb = 1; break; default: usage(argv[0]); return 1; } } if ((!read || !write || !cmb)) { usage(argv[0]); return 1; } return 0; } int main(int argc, char **argv) { int rc = 0; struct spdk_env_opts opts; /* * Parse the input arguments. For now we use the following * format list: * * --- * */ rc = parse_args(argc, argv); if (rc) { fprintf(stderr, "Error in parse_args(): %d\n", rc); return -1; } /* * SPDK relies on an abstraction around the local environment * named env that handles memory allocation and PCI device operations. * This library must be initialized first. * */ spdk_env_opts_init(&opts); opts.name = "cmb_copy"; opts.shm_id = 0; if (spdk_env_init(&opts) < 0) { fprintf(stderr, "Unable to initialize SPDK env\n"); return 1; } /* * CMBs only apply to PCIe attached NVMe controllers so we * only probe the PCIe bus. This is the default when we pass * in NULL for the first argument. */ rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL); if (rc) { fprintf(stderr, "Error in spdk_nvme_probe(): %d\n", rc); return -1; } /* * For now enforce that the read and write controller are not * the same. This avoids an internal only DMA. */ if (!strcmp(g_config.write.trid.traddr, g_config.read.trid.traddr)) { fprintf(stderr, "Read and Write controllers must differ!\n"); return -1; } /* * Perform a few sanity checks and set the buffer size for the * CMB. */ if (g_config.read.nlbas * g_config.read.lba_size != g_config.write.nlbas * g_config.write.lba_size) { fprintf(stderr, "Read and write sizes do not match!\n"); return -1; } g_config.copy_size = g_config.read.nlbas * g_config.read.lba_size; /* * Get the ctrlr pointer for the CMB. For now we assume this * is either the read or write NVMe controller though in * theory that is not a necessary condition. */ if (!strcmp(g_config.cmb.trid.traddr, g_config.read.trid.traddr)) { g_config.cmb.ctrlr = g_config.read.ctrlr; } if (!strcmp(g_config.cmb.trid.traddr, g_config.write.trid.traddr)) { g_config.cmb.ctrlr = g_config.write.ctrlr; } /* * Call the cmb_copy() function which performs the CMB * based copy or returns an error code if it fails. */ rc = cmb_copy(); if (rc) { fprintf(stderr, "Error in spdk_cmb_copy(): %d\n", rc); return -1; } return rc; }