root/sbin/nvmecontrol/passthru.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2019-2021 Netflix, Inc
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/param.h>
#include <sys/ioccom.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

#include "nvmecontrol.h"
#include "comnd.h"

static struct options {
        uint8_t         opcode;
        uint8_t         flags;
        uint16_t        rsvd;
        uint32_t        nsid;
        uint32_t        data_len;
        uint32_t        metadata_len;
        uint32_t        timeout;
        uint32_t        cdw2;
        uint32_t        cdw3;
        uint32_t        cdw10;
        uint32_t        cdw11;
        uint32_t        cdw12;
        uint32_t        cdw13;
        uint32_t        cdw14;
        uint32_t        cdw15;
        const char      *ifn;
        bool            binary;
        bool            show_command;
        bool            dry_run;
        bool            read;
        bool            write;
        uint8_t         prefill;
        const char      *dev;
} opt = {
        .binary = false,
        .cdw10 = 0,
        .cdw11 = 0,
        .cdw12 = 0,
        .cdw13 = 0,
        .cdw14 = 0,
        .cdw15 = 0,
        .cdw2 = 0,
        .cdw3 = 0,
        .data_len = 0,
        .dry_run = false,
        .flags = 0,
        .ifn = "",
        .metadata_len = 0,
        .nsid = 0,
        .opcode = 0,
        .prefill = 0,
        .read = false,
        .rsvd = 0,
        .show_command = false,
        .timeout = 0,
        .write = false,
        .dev = NULL,
};

/*
 * Argument names and short names selected to match the nvme-cli program
 * so vendor-siupplied formulas work out of the box on FreeBSD with a simple
 * s/nvme/nvmecontrol/.
 */
#define ARG(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }

static struct opts opts[] = {
        ARG("opcode",           'o',    arg_uint8,      opt, opcode,
            "NVMe command opcode (required)"),
        ARG("cdw2",             '2',    arg_uint32,     opt, cdw2,
            "Command dword 2 value"),
        ARG("cdw3",             '3',    arg_uint32,     opt, cdw3,
            "Command dword 3 value"),
        ARG("cdw10",            '4',    arg_uint32,     opt, cdw10,
            "Command dword 10 value"),
        ARG("cdw11",            '5',    arg_uint32,     opt, cdw11,
            "Command dword 11 value"),
        ARG("cdw12",            '6',    arg_uint32,     opt, cdw12,
            "Command dword 12 value"),
        ARG("cdw13",            '7',    arg_uint32,     opt, cdw13,
            "Command dword 13 value"),
        ARG("cdw14",            '8',    arg_uint32,     opt, cdw14,
            "Command dword 14 value"),
        ARG("cdw15",            '9',    arg_uint32,     opt, cdw15,
            "Command dword 15 value"),
        ARG("data-len",         'l',    arg_uint32,     opt, data_len,
            "Length of data for I/O (bytes)"),
        ARG("metadata-len",     'm',    arg_uint32,     opt, metadata_len,
            "Length of metadata segment (bytes) (ignored)"),
        ARG("flags",            'f',    arg_uint8,      opt, flags,
            "NVMe command flags"),
        ARG("input-file",       'i',    arg_path,       opt, ifn,
            "Input file to send (default stdin)"),
        ARG("namespace-id",     'n',    arg_uint32,     opt, nsid,
            "Namespace id (ignored on FreeBSD)"),
        ARG("prefill",          'p',    arg_uint8,      opt, prefill,
            "Value to prefill payload with"),
        ARG("rsvd",             'R',    arg_uint16,     opt, rsvd,
            "Reserved field value"),
        ARG("timeout",          't',    arg_uint32,     opt, timeout,
            "Command timeout (ms)"),
        ARG("raw-binary",       'b',    arg_none,       opt, binary,
            "Output in binary format"),
        ARG("dry-run",          'd',    arg_none,       opt, dry_run,
            "Don't actually execute the command"),
        ARG("read",             'r',    arg_none,       opt, read,
            "Command reads data from device"),
        ARG("show-command",     's',    arg_none,       opt, show_command,
            "Show all the command values on stdout"),
        ARG("write",            'w',    arg_none,       opt, write,
            "Command writes data to device"),
        { NULL, 0, arg_none, NULL, NULL }
};

static const struct args args[] = {
        { arg_string, &opt.dev, "controller-id|namespace-id" },
        { arg_none, NULL, NULL },
};

static void
passthru(const struct cmd *f, int argc, char *argv[])
{
        int     fd = -1, ifd = -1;
        size_t  bytes_read;
        void    *data = NULL, *metadata = NULL;
        struct nvme_pt_command  pt;

        if (arg_parse(argc, argv, f))
                return;
        open_dev(opt.dev, &fd, 1, 1);

        if (opt.read && opt.write)
                errx(EX_USAGE, "need exactly one of --read or --write");
        if (opt.data_len != 0 && !opt.read && !opt.write)
                errx(EX_USAGE, "need exactly one of --read or --write");
        if (*opt.ifn && (ifd = open(opt.ifn, O_RDONLY)) == -1) {
                warn("open %s", opt.ifn);
                goto cleanup;
        }
#if notyet      /* No support in kernel for this */
        if (opt.metadata_len != 0) {
                if (posix_memalign(&metadata, getpagesize(), opt.metadata_len)) {
                        warn("can't allocate %d bytes for metadata", metadata_len);
                        goto cleanup;
                }
        }
#else
        if (opt.metadata_len != 0)
                errx(EX_UNAVAILABLE, "metadata not supported on FreeBSD");
#endif
        if (opt.data_len) {
                if (posix_memalign(&data, getpagesize(), opt.data_len)) {
                        warn("can't allocate %d bytes for data", opt.data_len);
                        goto cleanup;
                }
                memset(data, opt.prefill, opt.data_len);
                if (opt.write &&
                    (bytes_read = read(ifd, data, opt.data_len)) !=
                    opt.data_len) {
                        warn("read %s; expected %u bytes; got %zd",
                             *opt.ifn ? opt.ifn : "stdin",
                             opt.data_len, bytes_read);
                        goto cleanup;
                }
        }
        if (opt.show_command) {
                fprintf(stderr, "opcode       : %#02x\n", opt.opcode);
                fprintf(stderr, "flags        : %#02x\n", opt.flags);
                fprintf(stderr, "rsvd1        : %#04x\n", opt.rsvd);
                fprintf(stderr, "nsid         : %#04x\n", opt.nsid);
                fprintf(stderr, "cdw2         : %#08x\n", opt.cdw2);
                fprintf(stderr, "cdw3         : %#08x\n", opt.cdw3);
                fprintf(stderr, "data_len     : %#08x\n", opt.data_len);
                fprintf(stderr, "metadata_len : %#08x\n", opt.metadata_len);
                fprintf(stderr, "data         : %p\n", data);
                fprintf(stderr, "metadata     : %p\n", metadata);
                fprintf(stderr, "cdw10        : %#08x\n", opt.cdw10);
                fprintf(stderr, "cdw11        : %#08x\n", opt.cdw11);
                fprintf(stderr, "cdw12        : %#08x\n", opt.cdw12);
                fprintf(stderr, "cdw13        : %#08x\n", opt.cdw13);
                fprintf(stderr, "cdw14        : %#08x\n", opt.cdw14);
                fprintf(stderr, "cdw15        : %#08x\n", opt.cdw15);
                fprintf(stderr, "timeout_ms   : %d\n", opt.timeout);
        }
        if (opt.dry_run) {
                errno = 0;
                warn("Doing a dry-run, no actual I/O");
                goto cleanup;
        }

        memset(&pt, 0, sizeof(pt));
        pt.cmd.opc = opt.opcode;
        pt.cmd.fuse = opt.flags;
        pt.cmd.cid = htole16(opt.rsvd);
        pt.cmd.nsid = opt.nsid;                         /* XXX note: kernel overrides this */
        pt.cmd.rsvd2 = htole32(opt.cdw2);
        pt.cmd.rsvd3 = htole32(opt.cdw3);
        pt.cmd.cdw10 = htole32(opt.cdw10);
        pt.cmd.cdw11 = htole32(opt.cdw11);
        pt.cmd.cdw12 = htole32(opt.cdw12);
        pt.cmd.cdw13 = htole32(opt.cdw13);
        pt.cmd.cdw14 = htole32(opt.cdw14);
        pt.cmd.cdw15 = htole32(opt.cdw15);
        pt.buf = data;
        pt.len = opt.data_len;
        pt.is_read = opt.read;

        errno = 0;
        if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
                err(EX_IOERR, "passthrough request failed");
        if (!opt.binary)
                printf("DWORD0 status= %#x\n", pt.cpl.cdw0);
        if (opt.read) {
                if (opt.binary)
                        write(STDOUT_FILENO, data, opt.data_len);
                else {
                        /* print status here */
                        print_hex(data, opt.data_len);
                }
        }
cleanup:
        free(data);
        close(fd);
        if (ifd > -1)
                close(ifd);
        if (errno)
                exit(EX_IOERR);
}

static void
admin_passthru(const struct cmd *nf, int argc, char *argv[])
{

        passthru(nf, argc, argv);
}

static void
io_passthru(const struct cmd *nf, int argc, char *argv[])
{

        passthru(nf, argc, argv);
}

static struct cmd admin_pass_cmd = {
        .name = "admin-passthru",
        .fn = admin_passthru,
        .ctx_size = sizeof(struct options),
        .opts = opts,
        .args = args,
        .descr = "Send a pass through Admin command to the specified device",
};

static struct cmd io_pass_cmd = {
        .name = "io-passthru",
        .fn = io_passthru,
        .ctx_size = sizeof(struct options),
        .opts = opts,
        .args = args,
        .descr = "Send a pass through I/O command to the specified device",
};

CMD_COMMAND(admin_pass_cmd);
CMD_COMMAND(io_pass_cmd);