xref: /freebsd/sbin/nvmecontrol/passthru.c (revision d5b0e70f7e04d971691517ce1304d86a1e367e2e)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019-2021 Netflix, Inc
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  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/ioccom.h>
33 
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sysexits.h>
42 #include <unistd.h>
43 
44 #include "nvmecontrol.h"
45 #include "comnd.h"
46 
47 static struct options {
48 	uint8_t		opcode;
49 	uint8_t		flags;
50 	uint16_t	rsvd;
51 	uint32_t	nsid;
52 	uint32_t	data_len;
53 	uint32_t	metadata_len;
54 	uint32_t	timeout;
55 	uint32_t	cdw2;
56 	uint32_t	cdw3;
57 	uint32_t	cdw10;
58 	uint32_t	cdw11;
59 	uint32_t	cdw12;
60 	uint32_t	cdw13;
61 	uint32_t	cdw14;
62 	uint32_t	cdw15;
63 	const char	*ifn;
64 	bool		binary;
65 	bool		show_command;
66 	bool		dry_run;
67 	bool		read;
68 	bool		write;
69 	uint8_t		prefill;
70 	const char	*dev;
71 } opt = {
72 	.binary = false,
73 	.cdw10 = 0,
74 	.cdw11 = 0,
75 	.cdw12 = 0,
76 	.cdw13 = 0,
77 	.cdw14 = 0,
78 	.cdw15 = 0,
79 	.cdw2 = 0,
80 	.cdw3 = 0,
81 	.data_len = 0,
82 	.dry_run = false,
83 	.flags = 0,
84 	.ifn = "",
85 	.metadata_len = 0,
86 	.nsid = 0,
87 	.opcode = 0,
88 	.prefill = 0,
89 	.read = false,
90 	.rsvd = 0,
91 	.show_command = false,
92 	.timeout = 0,
93 	.write = false,
94 	.dev = NULL,
95 };
96 
97 /*
98  * Argument names and short names selected to match the nvme-cli program
99  * so vendor-siupplied formulas work out of the box on FreeBSD with a simple
100  * s/nvme/nvmecontrol/.
101  */
102 #define ARG(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
103 
104 static struct opts opts[] = {
105 	ARG("opcode",		'o',	arg_uint8,	opt, opcode,
106 	    "NVMe command opcode (required)"),
107 	ARG("cdw2",		'2',	arg_uint32,	opt, cdw2,
108 	    "Command dword 2 value"),
109 	ARG("cdw3",		'3',	arg_uint32,	opt, cdw3,
110 	    "Command dword 3 value"),
111 	ARG("cdw10",		'4',	arg_uint32,	opt, cdw10,
112 	    "Command dword 10 value"),
113 	ARG("cdw11",		'5',	arg_uint32,	opt, cdw11,
114 	    "Command dword 11 value"),
115 	ARG("cdw12",		'6',	arg_uint32,	opt, cdw12,
116 	    "Command dword 12 value"),
117 	ARG("cdw13",		'7',	arg_uint32,	opt, cdw13,
118 	    "Command dword 13 value"),
119 	ARG("cdw14",		'8',	arg_uint32,	opt, cdw14,
120 	    "Command dword 14 value"),
121 	ARG("cdw15",		'9',	arg_uint32,	opt, cdw15,
122 	    "Command dword 15 value"),
123 	ARG("data-len",		'l',	arg_uint32,	opt, data_len,
124 	    "Length of data for I/O (bytes)"),
125 	ARG("metadata-len",	'm',	arg_uint32,	opt, metadata_len,
126 	    "Length of metadata segment (bytes) (igored)"),
127 	ARG("flags",		'f',	arg_uint8,	opt, flags,
128 	    "NVMe command flags"),
129 	ARG("input-file",	'i',	arg_path,	opt, ifn,
130 	    "Input file to send (default stdin)"),
131 	ARG("namespace-id",	'n',	arg_uint32,	opt, nsid,
132 	    "Namespace id (ignored on FreeBSD)"),
133 	ARG("prefill",		'p',	arg_uint8,	opt, prefill,
134 	    "Value to prefill payload with"),
135 	ARG("rsvd",		'R',	arg_uint16,	opt, rsvd,
136 	    "Reserved field value"),
137 	ARG("timeout",		't',	arg_uint32,	opt, timeout,
138 	    "Command timeout (ms)"),
139 	ARG("raw-binary",	'b',	arg_none,	opt, binary,
140 	    "Output in binary format"),
141 	ARG("dry-run",		'd',	arg_none,	opt, dry_run,
142 	    "Don't actually execute the command"),
143 	ARG("read",		'r',	arg_none,	opt, read,
144 	    "Command reads data from device"),
145 	ARG("show-command",	's',	arg_none,	opt, show_command,
146 	    "Show all the command values on stdout"),
147 	ARG("write",		'w',	arg_none,	opt, write,
148 	    "Command writes data to device"),
149 	{ NULL, 0, arg_none, NULL, NULL }
150 };
151 
152 static const struct args args[] = {
153 	{ arg_string, &opt.dev, "controller-id|namespace-id" },
154 	{ arg_none, NULL, NULL },
155 };
156 
157 static void
158 passthru(const struct cmd *f, int argc, char *argv[])
159 {
160 	int	fd = -1, ifd = -1;
161 	size_t	bytes_read;
162 	void	*data = NULL, *metadata = NULL;
163 	struct nvme_pt_command	pt;
164 
165 	if (arg_parse(argc, argv, f))
166 		return;
167 	open_dev(opt.dev, &fd, 1, 1);
168 
169 	if (opt.read && opt.write)
170 		errx(EX_USAGE, "need exactly one of --read or --write");
171 	if (opt.data_len != 0 && !opt.read && !opt.write)
172 		errx(EX_USAGE, "need exactly one of --read or --write");
173 	if (*opt.ifn && (ifd = open(opt.ifn, O_RDONLY)) == -1) {
174 		warn("open %s", opt.ifn);
175 		goto cleanup;
176 	}
177 #if notyet	/* No support in kernel for this */
178 	if (opt.metadata_len != 0) {
179 		if (posix_memalign(&metadata, getpagesize(), opt.metadata_len)) {
180 			warn("can't allocate %d bytes for metadata", metadata_len);
181 			goto cleanup;
182 		}
183 	}
184 #else
185 	if (opt.metadata_len != 0)
186 		errx(EX_UNAVAILABLE, "metadata not supported on FreeBSD");
187 #endif
188 	if (opt.data_len) {
189 		if (posix_memalign(&data, getpagesize(), opt.data_len)) {
190 			warn("can't allocate %d bytes for data", opt.data_len);
191 			goto cleanup;
192 		}
193 		memset(data, opt.prefill, opt.data_len);
194 		if (opt.write &&
195 		    (bytes_read = read(ifd, data, opt.data_len)) !=
196 		    opt.data_len) {
197 			warn("read %s; expected %u bytes; got %zd",
198 			     *opt.ifn ? opt.ifn : "stdin",
199 			     opt.data_len, bytes_read);
200 			goto cleanup;
201 		}
202 	}
203 	if (opt.show_command) {
204 		fprintf(stderr, "opcode       : %#02x\n", opt.opcode);
205 		fprintf(stderr, "flags        : %#02x\n", opt.flags);
206 		fprintf(stderr, "rsvd1        : %#04x\n", opt.rsvd);
207 		fprintf(stderr, "nsid         : %#04x\n", opt.nsid);
208 		fprintf(stderr, "cdw2         : %#08x\n", opt.cdw2);
209 		fprintf(stderr, "cdw3         : %#08x\n", opt.cdw3);
210 		fprintf(stderr, "data_len     : %#08x\n", opt.data_len);
211 		fprintf(stderr, "metadata_len : %#08x\n", opt.metadata_len);
212 		fprintf(stderr, "data         : %p\n", data);
213 		fprintf(stderr, "metadata     : %p\n", metadata);
214 		fprintf(stderr, "cdw10        : %#08x\n", opt.cdw10);
215 		fprintf(stderr, "cdw11        : %#08x\n", opt.cdw11);
216 		fprintf(stderr, "cdw12        : %#08x\n", opt.cdw12);
217 		fprintf(stderr, "cdw13        : %#08x\n", opt.cdw13);
218 		fprintf(stderr, "cdw14        : %#08x\n", opt.cdw14);
219 		fprintf(stderr, "cdw15        : %#08x\n", opt.cdw15);
220 		fprintf(stderr, "timeout_ms   : %d\n", opt.timeout);
221 	}
222 	if (opt.dry_run) {
223 		errno = 0;
224 		warn("Doing a dry-run, no actual I/O");
225 		goto cleanup;
226 	}
227 
228 	memset(&pt, 0, sizeof(pt));
229 	pt.cmd.opc = opt.opcode;
230 	pt.cmd.fuse = opt.flags;
231 	pt.cmd.cid = htole16(opt.rsvd);
232 	pt.cmd.nsid = opt.nsid;				/* XXX note: kernel overrides this */
233 	pt.cmd.rsvd2 = htole32(opt.cdw2);
234 	pt.cmd.rsvd3 = htole32(opt.cdw3);
235 	pt.cmd.cdw10 = htole32(opt.cdw10);
236 	pt.cmd.cdw11 = htole32(opt.cdw11);
237 	pt.cmd.cdw12 = htole32(opt.cdw12);
238 	pt.cmd.cdw13 = htole32(opt.cdw13);
239 	pt.cmd.cdw14 = htole32(opt.cdw14);
240 	pt.cmd.cdw15 = htole32(opt.cdw15);
241 	pt.buf = data;
242 	pt.len = opt.data_len;
243 	pt.is_read = opt.read;
244 
245 	errno = 0;
246 	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
247 		err(EX_IOERR, "passthrough request failed");
248 	if (!opt.binary)
249 		printf("DWORD0 status= %#x\n", pt.cpl.cdw0);
250 	if (opt.read) {
251 		if (opt.binary)
252 			write(STDOUT_FILENO, data, opt.data_len);
253 		else {
254 			/* print status here */
255 			print_hex(data, opt.data_len);
256 		}
257 	}
258 cleanup:
259 	free(data);
260 	close(fd);
261 	if (ifd > -1)
262 		close(ifd);
263 	if (errno)
264 		exit(EX_IOERR);
265 }
266 
267 static void
268 admin_passthru(const struct cmd *nf, int argc, char *argv[])
269 {
270 
271 	passthru(nf, argc, argv);
272 }
273 
274 static void
275 io_passthru(const struct cmd *nf, int argc, char *argv[])
276 {
277 
278 	passthru(nf, argc, argv);
279 }
280 
281 static struct cmd admin_pass_cmd = {
282 	.name = "admin-passthru",
283 	.fn = admin_passthru,
284 	.ctx_size = sizeof(struct options),
285 	.opts = opts,
286 	.args = args,
287 	.descr = "Send a pass through Admin command to the specified device",
288 };
289 
290 static struct cmd io_pass_cmd = {
291 	.name = "io-passthru",
292 	.fn = io_passthru,
293 	.ctx_size = sizeof(struct options),
294 	.opts = opts,
295 	.args = args,
296 	.descr = "Send a pass through I/O command to the specified device",
297 };
298 
299 CMD_COMMAND(admin_pass_cmd);
300 CMD_COMMAND(io_pass_cmd);
301