xref: /freebsd/sbin/nvmecontrol/nvmecontrol.c (revision 0957b409)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (C) 2012-2013 Intel Corporation
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/ioccom.h>
34 #include <sys/stat.h>
35 
36 #include <ctype.h>
37 #include <dlfcn.h>
38 #include <dirent.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <paths.h>
43 #include <stdbool.h>
44 #include <stddef.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #include "nvmecontrol.h"
51 
52 SET_CONCAT_DEF(top, struct nvme_function);
53 
54 static void
55 print_usage(const struct nvme_function *f)
56 {
57 	const char *cp;
58 	char ch;
59 	bool need_prefix = true;
60 
61 	cp = f->usage;
62 	while (*cp) {
63 		ch = *cp++;
64 		if (need_prefix) {
65 			if (ch != ' ')
66 				fputs("        nvmecontrol ", stderr);
67 			else
68 				fputs("                    ", stderr);
69 		}
70 		fputc(ch, stderr);
71 		need_prefix = (ch == '\n');
72 	}
73 	if (!need_prefix)
74 		fputc('\n', stderr);
75 }
76 
77 static void
78 gen_usage_set(const struct nvme_function * const *f, const struct nvme_function * const *flimit)
79 {
80 
81 	fprintf(stderr, "usage:\n");
82 	while (f < flimit) {
83 		print_usage(*f);
84 		f++;
85 	}
86 	exit(1);
87 }
88 
89 void
90 usage(const struct nvme_function *f)
91 {
92 
93 	fprintf(stderr, "usage:\n");
94 	print_usage(f);
95 	exit(1);
96 }
97 
98 void
99 dispatch_set(int argc, char *argv[], const struct nvme_function * const *tbl,
100     const struct nvme_function * const *tbl_limit)
101 {
102 	const struct nvme_function * const *f = tbl;
103 
104 	if (argv[1] == NULL) {
105 		gen_usage_set(tbl, tbl_limit);
106 		return;
107 	}
108 
109 	while (f < tbl_limit) {
110 		if (strcmp(argv[1], (*f)->name) == 0) {
111 			(*f)->fn(*f, argc-1, &argv[1]);
112 			return;
113 		}
114 		f++;
115 	}
116 
117 	fprintf(stderr, "Unknown command: %s\n", argv[1]);
118 	gen_usage_set(tbl, tbl_limit);
119 }
120 
121 void
122 set_concat_add(struct set_concat *m, void *b, void *e)
123 {
124 	void **bp, **ep;
125 	int add_n, cur_n;
126 
127 	if (b == NULL)
128 		return;
129 	/*
130 	 * Args are really pointers to arrays of pointers, but C's
131 	 * casting rules kinda suck since you can't directly cast
132 	 * struct foo ** to a void **.
133 	 */
134 	bp = (void **)b;
135 	ep = (void **)e;
136 	add_n = ep - bp;
137 	cur_n = 0;
138 	if (m->begin != NULL)
139 		cur_n = m->limit - m->begin;
140 	m->begin = reallocarray(m->begin, cur_n + add_n, sizeof(void *));
141 	if (m->begin == NULL)
142 		err(1, "expanding concat set");
143 	memcpy(m->begin + cur_n, bp, add_n * sizeof(void *));
144 	m->limit = m->begin + cur_n + add_n;
145 }
146 
147 static void
148 print_bytes(void *data, uint32_t length)
149 {
150 	uint32_t	i, j;
151 	uint8_t		*p, *end;
152 
153 	end = (uint8_t *)data + length;
154 
155 	for (i = 0; i < length; i++) {
156 		p = (uint8_t *)data + (i*16);
157 		printf("%03x: ", i*16);
158 		for (j = 0; j < 16 && p < end; j++)
159 			printf("%02x ", *p++);
160 		if (p >= end)
161 			break;
162 		printf("\n");
163 	}
164 	printf("\n");
165 }
166 
167 static void
168 print_dwords(void *data, uint32_t length)
169 {
170 	uint32_t	*p;
171 	uint32_t	i, j;
172 
173 	p = (uint32_t *)data;
174 	length /= sizeof(uint32_t);
175 
176 	for (i = 0; i < length; i+=8) {
177 		printf("%03x: ", i*4);
178 		for (j = 0; j < 8; j++)
179 			printf("%08x ", p[i+j]);
180 		printf("\n");
181 	}
182 
183 	printf("\n");
184 }
185 
186 void
187 print_hex(void *data, uint32_t length)
188 {
189 	if (length >= sizeof(uint32_t) || length % sizeof(uint32_t) == 0)
190 		print_dwords(data, length);
191 	else
192 		print_bytes(data, length);
193 }
194 
195 void
196 read_controller_data(int fd, struct nvme_controller_data *cdata)
197 {
198 	struct nvme_pt_command	pt;
199 
200 	memset(&pt, 0, sizeof(pt));
201 	pt.cmd.opc = NVME_OPC_IDENTIFY;
202 	pt.cmd.cdw10 = htole32(1);
203 	pt.buf = cdata;
204 	pt.len = sizeof(*cdata);
205 	pt.is_read = 1;
206 
207 	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
208 		err(1, "identify request failed");
209 
210 	/* Convert data to host endian */
211 	nvme_controller_data_swapbytes(cdata);
212 
213 	if (nvme_completion_is_error(&pt.cpl))
214 		errx(1, "identify request returned error");
215 }
216 
217 void
218 read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata)
219 {
220 	struct nvme_pt_command	pt;
221 
222 	memset(&pt, 0, sizeof(pt));
223 	pt.cmd.opc = NVME_OPC_IDENTIFY;
224 	pt.cmd.nsid = htole32(nsid);
225 	pt.buf = nsdata;
226 	pt.len = sizeof(*nsdata);
227 	pt.is_read = 1;
228 
229 	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
230 		err(1, "identify request failed");
231 
232 	/* Convert data to host endian */
233 	nvme_namespace_data_swapbytes(nsdata);
234 
235 	if (nvme_completion_is_error(&pt.cpl))
236 		errx(1, "identify request returned error");
237 }
238 
239 int
240 open_dev(const char *str, int *fd, int show_error, int exit_on_error)
241 {
242 	char		full_path[64];
243 
244 	if (!strnstr(str, NVME_CTRLR_PREFIX, strlen(NVME_CTRLR_PREFIX))) {
245 		if (show_error)
246 			warnx("controller/namespace ids must begin with '%s'",
247 			    NVME_CTRLR_PREFIX);
248 		if (exit_on_error)
249 			exit(1);
250 		else
251 			return (EINVAL);
252 	}
253 
254 	snprintf(full_path, sizeof(full_path), _PATH_DEV"%s", str);
255 	*fd = open(full_path, O_RDWR);
256 	if (*fd < 0) {
257 		if (show_error)
258 			warn("could not open %s", full_path);
259 		if (exit_on_error)
260 			exit(1);
261 		else
262 			return (errno);
263 	}
264 
265 	return (0);
266 }
267 
268 void
269 parse_ns_str(const char *ns_str, char *ctrlr_str, uint32_t *nsid)
270 {
271 	char	*nsloc;
272 
273 	/*
274 	 * Pull the namespace id from the string. +2 skips past the "ns" part
275 	 *  of the string.  Don't search past 10 characters into the string,
276 	 *  otherwise we know it is malformed.
277 	 */
278 	nsloc = strnstr(ns_str, NVME_NS_PREFIX, 10);
279 	if (nsloc != NULL)
280 		*nsid = strtol(nsloc + 2, NULL, 10);
281 	if (nsloc == NULL || (*nsid == 0 && errno != 0))
282 		errx(1, "invalid namespace ID '%s'", ns_str);
283 
284 	/*
285 	 * The controller string will include only the nvmX part of the
286 	 *  nvmeXnsY string.
287 	 */
288 	snprintf(ctrlr_str, nsloc - ns_str + 1, "%s", ns_str);
289 }
290 
291 /*
292  * Loads all the .so's from the specified directory.
293  */
294 static void
295 load_dir(const char *dir)
296 {
297 	DIR *d;
298 	struct dirent *dent;
299 	char *path = NULL;
300 	void *h;
301 
302 	d = opendir(dir);
303 	if (d == NULL)
304 		return;
305 	for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
306 		if (strcmp(".so", dent->d_name + dent->d_namlen - 3) != 0)
307 			continue;
308 		asprintf(&path, "%s/%s", dir, dent->d_name);
309 		if (path == NULL)
310 			err(1, "Can't malloc for path, giving up.");
311 		if ((h = dlopen(path, RTLD_NOW | RTLD_GLOBAL)) == NULL)
312 			warnx("Can't load %s: %s", path, dlerror());
313 		else {
314 			/*
315 			 * Add in the top (for cli commands)linker sets. We have
316 			 * to do this by hand because linker sets aren't
317 			 * automatically merged.
318 			 */
319 			void *begin, *limit;
320 
321 			begin = dlsym(h, "__start_set_top");
322 			limit = dlsym(h, "__stop_set_top");
323 			if (begin)
324 				add_to_top(begin, limit);
325 			/* log pages use constructors so are done on load */
326 		}
327 		free(path);
328 		path = NULL;
329 	}
330 	closedir(d);
331 }
332 
333 int
334 main(int argc, char *argv[])
335 {
336 
337 	add_to_top(NVME_CMD_BEGIN(top), NVME_CMD_LIMIT(top));
338 
339 	load_dir("/lib/nvmecontrol");
340 	load_dir("/usr/local/lib/nvmecontrol");
341 
342 	if (argc < 2)
343 		gen_usage_set(top_begin(), top_limit());
344 
345 	dispatch_set(argc, argv, top_begin(), top_limit());
346 
347 	return (0);
348 }
349