xref: /freebsd/sbin/nvmecontrol/modules/wdc/wdc.c (revision c697fb7f)
1 /*-
2  * Copyright (c) 2017 Netflix, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include <sys/param.h>
30 #include <sys/ioccom.h>
31 #include <sys/endian.h>
32 
33 #include <ctype.h>
34 #include <err.h>
35 #include <fcntl.h>
36 #include <stddef.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "nvmecontrol.h"
43 
44 /* Tables for command line parsing */
45 
46 static cmd_fn_t wdc;
47 static cmd_fn_t wdc_cap_diag;
48 
49 #define NONE 0xffffffffu
50 #define NONE64 0xffffffffffffffffull
51 #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
52 #define OPT_END	{ NULL, 0, arg_none, NULL, NULL }
53 
54 static struct cmd wdc_cmd = {
55 	.name = "wdc", .fn = wdc, .descr = "wdc vendor specific commands", .ctx_size = 0, .opts = NULL, .args = NULL,
56 };
57 
58 CMD_COMMAND(wdc_cmd);
59 
60 static struct options
61 {
62 	const char *template;
63 	const char *dev;
64 } opt = {
65 	.template = NULL,
66 	.dev = NULL,
67 };
68 
69 static const struct opts opts[] = {
70 	OPT("template", 'o', arg_string, opt, template,
71 	    "Template for paths to use for different logs"),
72 	OPT_END
73 };
74 
75 static const struct args args[] = {
76 	{ arg_string, &opt.dev, "controller-id" },
77 	{ arg_none, NULL, NULL },
78 };
79 
80 static struct cmd cap_diag_cmd = {
81 	.name = "cap-diag",
82 	.fn = wdc_cap_diag,
83 	.descr = "Retrieve the cap-diag logs from the drive",
84 	.ctx_size = sizeof(struct options),
85 	.opts = opts,
86 	.args = args,
87 };
88 
89 CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd);
90 
91 #define WDC_NVME_TOC_SIZE	8
92 
93 #define WDC_NVME_CAP_DIAG_OPCODE	0xe6
94 #define WDC_NVME_CAP_DIAG_CMD		0x0000
95 
96 static void
97 wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix)
98 {
99 	struct nvme_controller_data	cdata;
100 	char sn[NVME_SERIAL_NUMBER_LENGTH + 1];
101 	char *walker;
102 
103 	len -= strlen(buf);
104 	buf += strlen(buf);
105 	read_controller_data(fd, &cdata);
106 	memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH);
107 	walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1;
108 	while (walker > sn && *walker == ' ')
109 		walker--;
110 	*++walker = '\0';
111 	snprintf(buf, len, "%s%s.bin", sn, suffix);
112 }
113 
114 static void
115 wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd,
116     uint8_t *buffer, size_t buflen)
117 {
118 	struct nvme_pt_command	pt;
119 
120 	memset(&pt, 0, sizeof(pt));
121 	pt.cmd.opc = opcode;
122 	pt.cmd.cdw10 = htole32(len / sizeof(uint32_t));	/* - 1 like all the others ??? */
123 	pt.cmd.cdw11 = htole32(off / sizeof(uint32_t));
124 	pt.cmd.cdw12 = htole32(cmd);
125 	pt.buf = buffer;
126 	pt.len = buflen;
127 	pt.is_read = 1;
128 //	printf("opcode %#x cdw10(len) %#x cdw11(offset?) %#x cdw12(cmd/sub) %#x buflen %zd\n",
129 //	    (int)opcode, (int)cdw10, (int)cdw11, (int)cdw12, buflen);
130 
131 	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
132 		err(1, "wdc_get_data request failed");
133 	if (nvme_completion_is_error(&pt.cpl))
134 		errx(1, "wdc_get_data request returned error");
135 }
136 
137 static void
138 wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode,
139     uint32_t cmd, int len_off)
140 {
141 	int first;
142 	int fd2;
143 	uint8_t *buf;
144 	uint32_t len, offset;
145 	size_t resid;
146 
147 	wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix);
148 
149 	/* XXX overwrite protection? */
150 	fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644);
151 	if (fd2 < 0)
152 		err(1, "open %s", tmpl);
153 	buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE);
154 	if (buf == NULL)
155 		errx(1, "Can't get buffer to read dump");
156 	offset = 0;
157 	len = NVME_MAX_XFER_SIZE;
158 	first = 1;
159 
160 	do {
161 		resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len;
162 		wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid);
163 
164 		if (first) {
165 			len = be32dec(buf + len_off);
166 			if (len == 0)
167 				errx(1, "No data for %s", suffix);
168 			if (memcmp("E6LG", buf, 4) != 0)
169 				printf("Expected header of E6LG, found '%4.4s' instead\n",
170 				    buf);
171 			printf("Dumping %d bytes of version %d.%d log to %s\n", len,
172 			    buf[8], buf[9], tmpl);
173 			/*
174 			 * Adjust amount to dump if total dump < 1MB,
175 			 * though it likely doesn't matter to the WDC
176 			 * analysis tools.
177 			 */
178 			if (resid > len)
179 				resid = len;
180 			first = 0;
181 		}
182 		if (write(fd2, buf, resid) != (ssize_t)resid)
183 			err(1, "write");
184 		offset += resid;
185 		len -= resid;
186 	} while (len > 0);
187 	free(buf);
188 	close(fd2);
189 }
190 
191 static void
192 wdc_cap_diag(const struct cmd *f, int argc, char *argv[])
193 {
194 	char tmpl[MAXPATHLEN];
195  	int fd;
196 
197 	if (arg_parse(argc, argv, f))
198 		return;
199 	if (opt.template == NULL) {
200 		fprintf(stderr, "Missing template arg.\n");
201 		arg_help(argc, argv, f);
202 	}
203 	strlcpy(tmpl, opt.template, sizeof(tmpl));
204 	open_dev(opt.dev, &fd, 1, 1);
205 	wdc_do_dump(fd, tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE,
206 	    WDC_NVME_CAP_DIAG_CMD, 4);
207 
208 	close(fd);
209 
210 	exit(1);
211 }
212 
213 static void
214 wdc(const struct cmd *nf __unused, int argc, char *argv[])
215 {
216 
217 	cmd_dispatch(argc, argv, &wdc_cmd);
218 }
219 
220 /*
221  * HGST's 0xc1 page. This is a grab bag of additional data. Please see
222  * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf
223  * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf
224  * Appendix A for details
225  */
226 
227 typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
228 
229 struct subpage_print
230 {
231 	uint16_t key;
232 	subprint_fn_t fn;
233 };
234 
235 static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
236 static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
237 static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
238 static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
239 static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
240 static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
241 static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
242 static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
243 static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
244 static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
245 
246 static struct subpage_print hgst_subpage[] = {
247 	{ 0x02, print_hgst_info_write_errors },
248 	{ 0x03, print_hgst_info_read_errors },
249 	{ 0x05, print_hgst_info_verify_errors },
250 	{ 0x10, print_hgst_info_self_test },
251 	{ 0x15, print_hgst_info_background_scan },
252 	{ 0x30, print_hgst_info_erase_errors },
253 	{ 0x31, print_hgst_info_erase_counts },
254 	{ 0x32, print_hgst_info_temp_history },
255 	{ 0x37, print_hgst_info_ssd_perf },
256 	{ 0x38, print_hgst_info_firmware_load },
257 };
258 
259 /* Print a subpage that is basically just key value pairs */
260 static void
261 print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size,
262     const struct kv_name *kv, size_t kv_count)
263 {
264 	uint8_t *wsp, *esp;
265 	uint16_t ptype;
266 	uint8_t plen;
267 	uint64_t param;
268 	int i;
269 
270 	wsp = buf;
271 	esp = wsp + size;
272 	while (wsp < esp) {
273 		ptype = le16dec(wsp);
274 		wsp += 2;
275 		wsp++;			/* Flags, just ignore */
276 		plen = *wsp++;
277 		param = 0;
278 		for (i = 0; i < plen; i++)
279 			param |= (uint64_t)*wsp++ << (i * 8);
280 		printf("  %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param);
281 	}
282 }
283 
284 static void
285 print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
286 {
287 	static struct kv_name kv[] =
288 	{
289 		{ 0x0000, "Corrected Without Delay" },
290 		{ 0x0001, "Corrected Maybe Delayed" },
291 		{ 0x0002, "Re-Writes" },
292 		{ 0x0003, "Errors Corrected" },
293 		{ 0x0004, "Correct Algorithm Used" },
294 		{ 0x0005, "Bytes Processed" },
295 		{ 0x0006, "Uncorrected Errors" },
296 		{ 0x8000, "Flash Write Commands" },
297 		{ 0x8001, "HGST Special" },
298 	};
299 
300 	printf("Write Errors Subpage:\n");
301 	print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
302 }
303 
304 static void
305 print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
306 {
307 	static struct kv_name kv[] =
308 	{
309 		{ 0x0000, "Corrected Without Delay" },
310 		{ 0x0001, "Corrected Maybe Delayed" },
311 		{ 0x0002, "Re-Reads" },
312 		{ 0x0003, "Errors Corrected" },
313 		{ 0x0004, "Correct Algorithm Used" },
314 		{ 0x0005, "Bytes Processed" },
315 		{ 0x0006, "Uncorrected Errors" },
316 		{ 0x8000, "Flash Read Commands" },
317 		{ 0x8001, "XOR Recovered" },
318 		{ 0x8002, "Total Corrected Bits" },
319 	};
320 
321 	printf("Read Errors Subpage:\n");
322 	print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
323 }
324 
325 static void
326 print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
327 {
328 	static struct kv_name kv[] =
329 	{
330 		{ 0x0000, "Corrected Without Delay" },
331 		{ 0x0001, "Corrected Maybe Delayed" },
332 		{ 0x0002, "Re-Reads" },
333 		{ 0x0003, "Errors Corrected" },
334 		{ 0x0004, "Correct Algorithm Used" },
335 		{ 0x0005, "Bytes Processed" },
336 		{ 0x0006, "Uncorrected Errors" },
337 		{ 0x8000, "Commands Processed" },
338 	};
339 
340 	printf("Verify Errors Subpage:\n");
341 	print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
342 }
343 
344 static void
345 print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
346 {
347 	size_t i;
348 	uint8_t *walker = buf;
349 	uint16_t code, hrs;
350 	uint32_t lba;
351 
352 	printf("Self Test Subpage:\n");
353 	for (i = 0; i < size / 20; i++) {	/* Each entry is 20 bytes */
354 		code = le16dec(walker);
355 		walker += 2;
356 		walker++;			/* Ignore fixed flags */
357 		if (*walker == 0)		/* Last entry is zero length */
358 			break;
359 		if (*walker++ != 0x10) {
360 			printf("Bad length for self test report\n");
361 			return;
362 		}
363 		printf("  %-30s: %d\n", "Recent Test", code);
364 		printf("    %-28s: %#x\n", "Self-Test Results", *walker & 0xf);
365 		printf("    %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7);
366 		walker++;
367 		printf("    %-28s: %#x\n", "Self-Test Number", *walker++);
368 		hrs = le16dec(walker);
369 		walker += 2;
370 		lba = le32dec(walker);
371 		walker += 4;
372 		printf("    %-28s: %u\n", "Total Power On Hrs", hrs);
373 		printf("    %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba);
374 		printf("    %-28s: %#x\n", "Sense Key", *walker++ & 0xf);
375 		printf("    %-28s: %#x\n", "Additional Sense Code", *walker++);
376 		printf("    %-28s: %#x\n", "Additional Sense Qualifier", *walker++);
377 		printf("    %-28s: %#x\n", "Vendor Specific Detail", *walker++);
378 	}
379 }
380 
381 static void
382 print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
383 {
384 	uint8_t *walker = buf;
385 	uint8_t status;
386 	uint16_t code, nscan, progress;
387 	uint32_t pom, nand;
388 
389 	printf("Background Media Scan Subpage:\n");
390 	/* Decode the header */
391 	code = le16dec(walker);
392 	walker += 2;
393 	walker++;			/* Ignore fixed flags */
394 	if (*walker++ != 0x10) {
395 		printf("Bad length for background scan header\n");
396 		return;
397 	}
398 	if (code != 0) {
399 		printf("Expceted code 0, found code %#x\n", code);
400 		return;
401 	}
402 	pom = le32dec(walker);
403 	walker += 4;
404 	walker++;			/* Reserved */
405 	status = *walker++;
406 	nscan = le16dec(walker);
407 	walker += 2;
408 	progress = le16dec(walker);
409 	walker += 2;
410 	walker += 6;			/* Reserved */
411 	printf("  %-30s: %d\n", "Power On Minutes", pom);
412 	printf("  %-30s: %x (%s)\n", "BMS Status", status,
413 	    status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown")));
414 	printf("  %-30s: %d\n", "Number of BMS", nscan);
415 	printf("  %-30s: %d\n", "Progress Current BMS", progress);
416 	/* Report retirements */
417 	if (walker - (uint8_t *)buf != 20) {
418 		printf("Coding error, offset not 20\n");
419 		return;
420 	}
421 	size -= 20;
422 	printf("  %-30s: %d\n", "BMS retirements", size / 0x18);
423 	while (size > 0) {
424 		code = le16dec(walker);
425 		walker += 2;
426 		walker++;
427 		if (*walker++ != 0x14) {
428 			printf("Bad length parameter\n");
429 			return;
430 		}
431 		pom = le32dec(walker);
432 		walker += 4;
433 		/*
434 		 * Spec sheet says the following are hard coded, if true, just
435 		 * print the NAND retirement.
436 		 */
437 		if (walker[0] == 0x41 &&
438 		    walker[1] == 0x0b &&
439 		    walker[2] == 0x01 &&
440 		    walker[3] == 0x00 &&
441 		    walker[4] == 0x00 &&
442 		    walker[5] == 0x00 &&
443 		    walker[6] == 0x00 &&
444 		    walker[7] == 0x00) {
445 			walker += 8;
446 			walker += 4;	/* Skip reserved */
447 			nand = le32dec(walker);
448 			walker += 4;
449 			printf("  %-30s: %d\n", "Retirement number", code);
450 			printf("    %-28s: %#x\n", "NAND (C/T)BBBPPP", nand);
451 		} else {
452 			printf("Parameter %#x entry corrupt\n", code);
453 			walker += 16;
454 		}
455 	}
456 }
457 
458 static void
459 print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
460 {
461 	static struct kv_name kv[] =
462 	{
463 		{ 0x0000, "Corrected Without Delay" },
464 		{ 0x0001, "Corrected Maybe Delayed" },
465 		{ 0x0002, "Re-Erase" },
466 		{ 0x0003, "Errors Corrected" },
467 		{ 0x0004, "Correct Algorithm Used" },
468 		{ 0x0005, "Bytes Processed" },
469 		{ 0x0006, "Uncorrected Errors" },
470 		{ 0x8000, "Flash Erase Commands" },
471 		{ 0x8001, "Mfg Defect Count" },
472 		{ 0x8002, "Grown Defect Count" },
473 		{ 0x8003, "Erase Count -- User" },
474 		{ 0x8004, "Erase Count -- System" },
475 	};
476 
477 	printf("Erase Errors Subpage:\n");
478 	print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
479 }
480 
481 static void
482 print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
483 {
484 	/* My drive doesn't export this -- so not coding up */
485 	printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size);
486 }
487 
488 static void
489 print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
490 {
491 	uint8_t *walker = buf;
492 	uint32_t min;
493 
494 	printf("Temperature History:\n");
495 	printf("  %-30s: %d C\n", "Current Temperature", *walker++);
496 	printf("  %-30s: %d C\n", "Reference Temperature", *walker++);
497 	printf("  %-30s: %d C\n", "Maximum Temperature", *walker++);
498 	printf("  %-30s: %d C\n", "Minimum Temperature", *walker++);
499 	min = le32dec(walker);
500 	walker += 4;
501 	printf("  %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60);
502 	min = le32dec(walker);
503 	walker += 4;
504 	printf("  %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60);
505 	min = le32dec(walker);
506 	walker += 4;
507 	printf("  %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60);
508 }
509 
510 static void
511 print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused)
512 {
513 	uint8_t *walker = buf;
514 	uint64_t val;
515 
516 	printf("SSD Performance Subpage Type %d:\n", res);
517 	val = le64dec(walker);
518 	walker += 8;
519 	printf("  %-30s: %ju\n", "Host Read Commands", val);
520 	val = le64dec(walker);
521 	walker += 8;
522 	printf("  %-30s: %ju\n", "Host Read Blocks", val);
523 	val = le64dec(walker);
524 	walker += 8;
525 	printf("  %-30s: %ju\n", "Host Cache Read Hits Commands", val);
526 	val = le64dec(walker);
527 	walker += 8;
528 	printf("  %-30s: %ju\n", "Host Cache Read Hits Blocks", val);
529 	val = le64dec(walker);
530 	walker += 8;
531 	printf("  %-30s: %ju\n", "Host Read Commands Stalled", val);
532 	val = le64dec(walker);
533 	walker += 8;
534 	printf("  %-30s: %ju\n", "Host Write Commands", val);
535 	val = le64dec(walker);
536 	walker += 8;
537 	printf("  %-30s: %ju\n", "Host Write Blocks", val);
538 	val = le64dec(walker);
539 	walker += 8;
540 	printf("  %-30s: %ju\n", "Host Write Odd Start Commands", val);
541 	val = le64dec(walker);
542 	walker += 8;
543 	printf("  %-30s: %ju\n", "Host Write Odd End Commands", val);
544 	val = le64dec(walker);
545 	walker += 8;
546 	printf("  %-30s: %ju\n", "Host Write Commands Stalled", val);
547 	val = le64dec(walker);
548 	walker += 8;
549 	printf("  %-30s: %ju\n", "NAND Read Commands", val);
550 	val = le64dec(walker);
551 	walker += 8;
552 	printf("  %-30s: %ju\n", "NAND Read Blocks", val);
553 	val = le64dec(walker);
554 	walker += 8;
555 	printf("  %-30s: %ju\n", "NAND Write Commands", val);
556 	val = le64dec(walker);
557 	walker += 8;
558 	printf("  %-30s: %ju\n", "NAND Write Blocks", val);
559 	val = le64dec(walker);
560 	walker += 8;
561 	printf("  %-30s: %ju\n", "NAND Read Before Writes", val);
562 }
563 
564 static void
565 print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
566 {
567 	uint8_t *walker = buf;
568 
569 	printf("Firmware Load Subpage:\n");
570 	printf("  %-30s: %d\n", "Firmware Downloads", le32dec(walker));
571 }
572 
573 static void
574 kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp)
575 {
576 	size_t i;
577 
578 	for (i = 0; i < nsp; i++, sp++) {
579 		if (sp->key == subtype) {
580 			sp->fn(buf, subtype, res, size);
581 			return;
582 		}
583 	}
584 	printf("No handler for page type %x\n", subtype);
585 }
586 
587 static void
588 print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
589 {
590 	uint8_t	*walker, *end, *subpage;
591 	int pages;
592 	uint16_t len;
593 	uint8_t subtype, res;
594 
595 	printf("HGST Extra Info Log\n");
596 	printf("===================\n");
597 
598 	walker = buf;
599 	pages = *walker++;
600 	walker++;
601 	len = le16dec(walker);
602 	walker += 2;
603 	end = walker + len;		/* Length is exclusive of this header */
604 
605 	while (walker < end) {
606 		subpage = walker + 4;
607 		subtype = *walker++ & 0x3f;	/* subtype */
608 		res = *walker++;		/* Reserved */
609 		len = le16dec(walker);
610 		walker += len + 2;		/* Length, not incl header */
611 		if (walker > end) {
612 			printf("Ooops! Off the end of the list\n");
613 			break;
614 		}
615 		kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage));
616 	}
617 }
618 
619 NVME_LOGPAGE(hgst_info,
620     HGST_INFO_LOG,			"hgst",	"Detailed Health/SMART",
621     print_hgst_info_log,		DEFAULT_SIZE);
622 NVME_LOGPAGE(wdc_info,
623     HGST_INFO_LOG,			"wdc",	"Detailed Health/SMART",
624     print_hgst_info_log,		DEFAULT_SIZE);
625