xref: /dragonfly/sbin/nvmectl/nvmectl.c (revision 9348a738)
1 /*
2  * Copyright (c) 2016 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
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  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include "nvmectl.h"
36 
37 typedef int (*cmd_t)(int ac, char **av, const char *id, int fd);
38 
39 static cmd_t parsecmd(int ac, char **av, int *globokp);
40 static int cmd_info(int ac, char **av, const char *id, int fd);
41 static int cmd_errors(int ac, char **av, const char *id, int fd);
42 static void usage(int rc);
43 
44 int VerboseOpt;
45 
46 int
47 main(int ac, char **av)
48 {
49 	int rc = 0;
50 	int ch;
51 	int nvmei;
52 	int globok;
53 	int i;
54 	cmd_t cmd;
55 
56 	while ((ch = getopt(ac, av, "v")) != -1) {
57 		switch(ch) {
58 		case 'v':
59 			++VerboseOpt;
60 			break;
61 		default:
62 			usage(1);
63 			break;
64 		}
65 	}
66 
67 	ac -= optind;
68 	av += optind;
69 
70 	for (nvmei = ac; nvmei > 0; --nvmei) {
71 		if (strncmp(av[nvmei - 1], "nvme", 4) != 0)
72 			break;
73 	}
74 	if (nvmei == 0)
75 		usage(1);
76 
77 	globok = 0;
78 	cmd = parsecmd(nvmei, av, &globok);
79 
80 	if (nvmei == ac && globok) {
81 		i = 0;
82 		for (;;) {
83 			char *path;
84 			int fd;
85 
86 			if (i)
87 				printf("\n");
88 			asprintf(&path, "/dev/nvme%d", i);
89 			fd = open(path, O_RDWR);
90 			free(path);
91 			if (fd < 0)
92 				break;
93 			rc += cmd(nvmei, av, path + 5, fd);
94 			close(fd);
95 			++i;
96 		}
97 	} else if (nvmei == ac && !globok) {
98 		fprintf(stderr, "must specify nvmeX device for command\n");
99 	} else {
100 		for (i = nvmei; i < ac; ++i) {
101 			char *path;
102 			int fd;
103 
104 			if (i != nvmei)
105 				printf("\n");
106 
107 			asprintf(&path, "/dev/%s", av[i]);
108 			fd = open(path, O_RDWR);
109 			if (fd < 0) {
110 				fprintf(stderr, "open \"%s\": %s\n",
111 					path,
112 					strerror(errno));
113 			} else {
114 				rc += cmd(nvmei, av, path + 5, fd);
115 				close(fd);
116 			}
117 			free(path);
118 		}
119 	}
120 	return (rc ? 1 : 0);
121 }
122 
123 static
124 cmd_t
125 parsecmd(int ac, char **av, int *globokp)
126 {
127 	if (ac == 0)
128 		usage(1);
129 	if (strcmp(av[0], "info") == 0) {
130 		*globokp = 1;
131 		return cmd_info;
132 	}
133 	if (strcmp(av[0], "errors") == 0) {
134 		*globokp = 1;
135 		return cmd_errors;
136 	}
137 	fprintf(stderr, "Command %s not recognized\n", av[0]);
138 
139 	usage(1);
140 	return NULL;	/* NOT REACHED */
141 }
142 
143 static
144 int
145 cmd_info(int ac __unused, char **av __unused, const char *id, int fd)
146 {
147 	nvme_getlog_ioctl_t ioc;
148 	nvme_log_smart_data_t *smart;
149 	int count;
150 	int i;
151 
152 	bzero(&ioc, sizeof(ioc));
153 	ioc.lid = NVME_LID_SMART;
154 	ioc.ret_size = sizeof(ioc.info.logsmart);
155 
156 	if (ioctl(fd, NVMEIOCGETLOG, &ioc) < 0) {
157 		fprintf(stderr, "ioctl failed: %s\n", strerror(errno));
158 		return 1;
159 	}
160 	if (NVME_COMQ_STATUS_CODE_GET(ioc.status)) {
161 		fprintf(stderr, "%s: type %d code 0x%02x\n",
162 			id,
163 			NVME_COMQ_STATUS_TYPE_GET(ioc.status),
164 			NVME_COMQ_STATUS_CODE_GET(ioc.status));
165 		return 1;
166 	}
167 	printf("%s:\n", id);
168 	smart = &ioc.info.logsmart;
169 
170 	printf("\tcrit_flags:\t");
171 	if (smart->crit_flags) {
172 		if (smart->crit_flags & NVME_SMART_CRF_RES80)
173 			printf(" 80");
174 		if (smart->crit_flags & NVME_SMART_CRF_RES40)
175 			printf(" 40");
176 		if (smart->crit_flags & NVME_SMART_CRF_RES20)
177 			printf(" 20");
178 		if (smart->crit_flags & NVME_SMART_CRF_VOLTL_BKUP_FAIL)
179 			printf(" MEM_BACKUP_FAILED");
180 		if (smart->crit_flags & NVME_SMART_CRF_MEDIA_RO)
181 			printf(" MEDIA_RDONLY");
182 		if (smart->crit_flags & NVME_SMART_CRF_UNRELIABLE)
183 			printf(" MEDIA_UNRELIABLE");
184 		if (smart->crit_flags & NVME_SMART_CRF_ABOVE_THRESH)
185 			printf(" TOO_HOT");
186 		if (smart->crit_flags & NVME_SMART_CRF_BELOW_THRESH)
187 			printf(" TOO_COLD");
188 	} else {
189 		printf("none\n");
190 	}
191 	printf("\tcomp_temp:\t%dC\n",
192 		(int)(smart->comp_temp1 + (smart->comp_temp2 << 8)) - 273);
193 	printf("\tLIFE_LEFT:\t%d%% (%d%% used)\n",
194 		100 - (int)smart->rated_life,
195 		(int)smart->rated_life);
196 
197 	printf("\tread_bytes:\t%s\n",
198 	       format_number(smart->read_count[0] * 512000));
199 	printf("\twrite_bytes:\t%s\n",
200 	       format_number(smart->write_count[0] * 512000));
201 	printf("\tread_cmds:\t%s\n",
202 	       format_number(smart->read_cmds[0]));
203 	printf("\twrite_cmds:\t%s\n",
204 	       format_number(smart->write_cmds[0]));
205 	printf("\tbusy_time:\t%ld min (%1.2f hrs)\n",
206 		smart->busy_time[0],
207 		(double)smart->busy_time[0] / 60.0);
208 	printf("\tpowon_hours:\t%ld\n", smart->powon_hours[0]);
209 	printf("\tpower_cyc:\t%ld\n", smart->power_cycles[0]);
210 	printf("\tunsafe_shut:\t%ld\n", smart->unsafe_shutdowns[0]);
211 
212 	printf("\tUNRECOV_ERR:\t%ld", smart->unrecoverable_errors[0]);
213 	if (smart->unrecoverable_errors[0])
214 		printf("\t*******WARNING*******");
215 	printf("\n");
216 
217 	printf("\terr_log_ent:\t%ld\n", smart->error_log_entries[0]);
218 	printf("\twarn_temp_time:\t%d min (%1.2f hrs)\n",
219 		smart->warn_comp_temp_time,
220 		(double)smart->warn_comp_temp_time / 60.0);
221 	printf("\tcrit_temp_time:\t%d min (%1.2f hrs)\n",
222 		smart->crit_comp_temp_time,
223 		(double)smart->crit_comp_temp_time / 60.0);
224 
225 	printf("\ttemp_sensors:\t");
226 	for (i = count = 0; i < 8; ++i) {
227 		if (smart->temp_sensors[i]) {
228 			if (count)
229 				printf(" ");
230 			printf("%dC", smart->temp_sensors[i] - 273);
231 			++count;
232 		}
233 	}
234 	if (count == 0)
235 		printf("none");
236 	printf("\n");
237 
238 	return 0;
239 }
240 
241 static
242 int
243 cmd_errors(int ac __unused, char **av __unused, const char *id, int fd)
244 {
245 	nvme_getlog_ioctl_t ioc;
246 	nvme_log_error_data_t *errs;
247 	int i;
248 
249 	bzero(&ioc, sizeof(ioc));
250 	ioc.lid = NVME_LID_ERROR;
251 	ioc.ret_size = sizeof(ioc.info.logsmart);
252 
253 	if (ioctl(fd, NVMEIOCGETLOG, &ioc) < 0) {
254 		fprintf(stderr, "ioctl failed: %s\n", strerror(errno));
255 		return 1;
256 	}
257 	if (NVME_COMQ_STATUS_CODE_GET(ioc.status)) {
258 		fprintf(stderr, "%s: type %d code 0x%02x\n",
259 			id,
260 			NVME_COMQ_STATUS_TYPE_GET(ioc.status),
261 			NVME_COMQ_STATUS_CODE_GET(ioc.status));
262 		return 1;
263 	}
264 	printf("%s:\n", id);
265 	errs = &ioc.info.logerr[0];
266 
267 	for (i = 0; i < 64; ++i) {
268 		if (errs->error_count == 0 && errs->subq_id == 0 &&
269 		    errs->cmd_id == 0 && errs->status == 0 &&
270 		    errs->param == 0 && errs->nsid == 0 &&
271 		    errs->vendor == 0 && errs->csi == 0 && errs->lba == 0)
272 			continue;
273 
274 		if (errs->param || errs->vendor || errs->csi) {
275 			printf("\t%2d cnt=%-3ld subq=%-2d cmdi=%-3d "
276 			       "status=%d,0x%02x parm=%04x nsid=%-3d vend=%d "
277 			       "csi=0x%lx lba=%ld",
278 			       i, errs->error_count,
279 			       (int16_t)errs->subq_id,
280 			       (int16_t)errs->cmd_id,
281 			       NVME_COMQ_STATUS_TYPE_GET(errs->status),
282 			       NVME_COMQ_STATUS_CODE_GET(errs->status),
283 			       errs->param, errs->nsid,
284 			       errs->vendor,
285 			       errs->csi, errs->lba);
286 		} else {
287 			printf("\t%2d cnt=%-3ld subq=%-2d cmdi=%-3d "
288 			       "status=%d,0x%02x nsid=%-3d lba=%ld",
289 			       i, errs->error_count,
290 			       (int16_t)errs->subq_id,
291 			       (int16_t)errs->cmd_id,
292 			       NVME_COMQ_STATUS_TYPE_GET(errs->status),
293 			       NVME_COMQ_STATUS_CODE_GET(errs->status),
294 			       errs->nsid,
295 			       errs->lba);
296 		}
297 		if (errs->status & NVME_COMQ_STATUS_DNR)
298 			printf(" DNR");
299 		printf(" %s\n", status_to_str(errs->status));
300 		++errs;
301 	}
302 
303 	return 0;
304 }
305 
306 static
307 void
308 usage(int rc)
309 {
310 	fprintf(stderr,
311 		"nvmectl [-v] cmd [nvme0,1,2...]\n"
312 		"\tinfo\n"
313 		"\terrors\n"
314 	);
315 	exit(rc);
316 }
317