2 * Copyright (c) 2017 Netflix, Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
29 #include <sys/param.h>
30 #include <sys/ioccom.h>
31 #include <sys/endian.h>
43 #include "nvmecontrol.h"
45 /* Tables for command line parsing */
48 static cmd_fn_t wdc_cap_diag;
50 #define NONE 0xffffffffu
51 #define NONE64 0xffffffffffffffffull
52 #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
53 #define OPT_END { NULL, 0, arg_none, NULL, NULL }
55 static struct cmd wdc_cmd = {
56 .name = "wdc", .fn = wdc, .descr = "wdc vendor specific commands", .ctx_size = 0, .opts = NULL, .args = NULL,
72 static const struct opts opts[] = {
73 OPT("template", 'o', arg_string, opt, template,
74 "Template for paths to use for different logs"),
75 OPT("data-area", 'd', arg_uint8, opt, data_area,
76 "Data-area to retrieve up to"),
80 static const struct args args[] = {
81 { arg_string, &opt.dev, "controller-id" },
82 { arg_none, NULL, NULL },
85 static struct cmd cap_diag_cmd = {
88 .descr = "Retrieve the cap-diag logs from the drive",
89 .ctx_size = sizeof(struct options),
94 CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd);
96 #define WDC_NVME_VID 0x1c58
97 #define WDC_NVME_VID_2 0x1b96
98 #define WDC_NVME_VID_3 0x15b7
100 #define WDC_NVME_TOC_SIZE 0x8
101 #define WDC_NVME_LOG_SIZE_HDR_LEN 0x8
102 #define WDC_NVME_CAP_DIAG_OPCODE_E6 0xe6
103 #define WDC_NVME_CAP_DIAG_CMD 0x0000
104 #define WDC_NVME_CAP_DIAG_OPCODE_FA 0xfa
105 #define WDC_NVME_DUI_MAX_SECTIONS_V0 0x3c
106 #define WDC_NVME_DUI_MAX_SECTIONS_V1 0x3a
107 #define WDC_NVME_DUI_MAX_SECTIONS_V2 0x26
108 #define WDC_NVME_DUI_MAX_SECTIONS_V3 0x23
110 typedef enum wdc_dui_header {
111 WDC_DUI_HEADER_VER_0 = 0,
112 WDC_DUI_HEADER_VER_1,
113 WDC_DUI_HEADER_VER_2,
114 WDC_DUI_HEADER_VER_3,
118 wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix)
120 struct nvme_controller_data cdata;
121 char sn[NVME_SERIAL_NUMBER_LENGTH + 1];
126 read_controller_data(fd, &cdata);
127 memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH);
128 walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1;
129 while (walker > sn && *walker == ' ')
132 snprintf(buf, len, "_%s_%s.bin", sn, suffix);
136 wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd,
137 uint8_t *buffer, size_t buflen, bool e6lg_flag)
139 struct nvme_pt_command pt;
141 memset(&pt, 0, sizeof(pt));
143 pt.cmd.cdw10 = htole32(len / sizeof(uint32_t));
144 pt.cmd.cdw12 = htole32(cmd);
146 pt.cmd.cdw11 = htole32(off / sizeof(uint32_t));
148 pt.cmd.cdw13 = htole32(off / sizeof(uint32_t));
153 if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
154 err(1, "wdc_get_data request failed");
155 if (nvme_completion_is_error(&pt.cpl))
156 errx(1, "wdc_get_data request returned error");
160 wdc_do_dump_e6(int fd, char *tmpl, const char *suffix, uint32_t opcode,
161 uint32_t cmd, int len_off)
166 uint32_t len, offset;
168 bool e6lg_flag = false;
170 wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix);
172 /* Read Log Dump header */
173 len = WDC_NVME_LOG_SIZE_HDR_LEN;
177 errx(1, "Can't get buffer to read dump");
178 wdc_get_data(fd, opcode, len, offset, cmd, hdr, len, false);
179 if (memcmp("E6LG", hdr, 4) == 0) {
183 /* XXX overwrite protection? */
184 fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644);
186 err(1, "open %s", tmpl);
187 buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE);
189 errx(1, "Can't get buffer to read dump");
191 len = NVME_MAX_XFER_SIZE;
195 resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len;
196 wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid, e6lg_flag);
199 len = be32dec(buf + len_off);
201 errx(1, "No data for %s", suffix);
203 printf("Dumping %d bytes of version %d.%d log to %s\n", len,
204 buf[8], buf[9], tmpl);
206 * Adjust amount to dump if total dump < 1MB,
207 * though it likely doesn't matter to the WDC
214 if (write(fd2, buf, resid) != (ssize_t)resid)
225 wdc_get_data_dui(int fd, uint32_t opcode, uint32_t len, uint64_t off,
226 uint8_t *buffer, size_t buflen)
228 struct nvme_pt_command pt;
230 memset(&pt, 0, sizeof(pt));
233 pt.cmd.cdw10 = htole32((len / sizeof(uint32_t)) - 1) ;
234 pt.cmd.cdw12 = htole32(off & 0xFFFFFFFFu);
235 pt.cmd.cdw13 = htole32(off >> 32);
240 if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
241 err(1, "wdc_get_data_dui request failed");
242 if (nvme_completion_is_error(&pt.cpl))
243 errx(1, "wdc_get_data_dui request returned error");
247 wdc_get_dui_max_sections(uint16_t header_ver)
249 switch (header_ver) {
250 case WDC_DUI_HEADER_VER_0:
251 return WDC_NVME_DUI_MAX_SECTIONS_V0;
252 case WDC_DUI_HEADER_VER_1:
253 return WDC_NVME_DUI_MAX_SECTIONS_V1;
254 case WDC_DUI_HEADER_VER_2:
255 return WDC_NVME_DUI_MAX_SECTIONS_V2;
256 case WDC_DUI_HEADER_VER_3:
257 return WDC_NVME_DUI_MAX_SECTIONS_V3;
263 wdc_get_dui_log_size(int fd, uint32_t opcode, uint8_t data_area,
264 uint64_t *log_size, int len_off)
267 uint8_t max_sections;
275 hdr = (uint8_t*)malloc(len);
277 errx(1, "Can't get buffer to read header");
278 wdc_get_data_dui(fd, opcode, len, 0, hdr, len);
281 hdr_ver = ((*hdr & 0xF) != 0)? *hdr : le16dec(hdr);
282 max_sections = wdc_get_dui_max_sections(hdr_ver);
284 if (hdr_ver == 0 || hdr_ver == 1) {
285 dui_size = (uint64_t)le32dec(hdr + 4);
288 for (i = 0, j = 0; i < (int)max_sections; i++, j+=8)
289 dui_size += (uint64_t)le32dec(hdr + j + 4);
291 } else if (hdr_ver == 2 || hdr_ver == 3) {
292 if (data_area == 0) {
293 dui_size = le64dec(hdr + 4);
296 for (i = 0, j = 0 ; i < (int)max_sections; i++, j+=12)
297 dui_size += le64dec(hdr + j + 4);
301 for (i = 0, j = 0; i < (int)max_sections; i++, j+=12) {
302 if (le16dec(hdr + j + 2) <= data_area)
303 dui_size += le64dec(hdr + j + 4);
310 errx(1, "ERROR : No valid header ");
312 *log_size = dui_size;
317 wdc_do_dump_dui(int fd, char *tmpl, uint8_t data_area,
318 const char *suffix, uint32_t opcode, int len_off)
323 uint64_t log_len, offset;
326 wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix);
327 wdc_get_dui_log_size(fd, opcode, data_area, &log_len, len_off);
329 errx(1, "No data for %s", suffix);
330 fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644);
332 err(1, "open %s", tmpl);
333 buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE);
335 errx(1, "Can't get buffer to read dump");
339 while (log_len > 0) {
340 resid = log_len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : log_len;
341 wdc_get_data_dui(fd, opcode, resid, offset, buf, resid);
343 hdr_ver = ((buf[len_off] & 0xF) != 0) ?
344 (buf[len_off]) : (le16dec(buf + len_off));
345 printf("Dumping %ld bytes of version %d log to %s\n", log_len,
349 if (write(fd2, buf, resid) != (ssize_t)resid)
360 wdc_cap_diag(const struct cmd *f, int argc, char *argv[])
362 char tmpl[MAXPATHLEN];
364 struct nvme_controller_data cdata;
367 if (arg_parse(argc, argv, f))
369 if (opt.template == NULL) {
370 fprintf(stderr, "Missing template arg.\n");
371 arg_help(argc, argv, f);
373 if (opt.data_area > 4) {
374 fprintf(stderr, "Data area range 1-4, supplied %d.\n", opt.data_area);
375 arg_help(argc, argv, f);
377 strlcpy(tmpl, opt.template, sizeof(tmpl));
378 open_dev(opt.dev, &fd, 1, 1);
379 read_controller_data(fd, &cdata);
384 case WDC_NVME_VID_2 :
385 wdc_do_dump_e6(fd, tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE_E6,
386 WDC_NVME_CAP_DIAG_CMD, 4);
388 case WDC_NVME_VID_3 :
389 wdc_do_dump_dui(fd, tmpl, opt.data_area, "cap_diag",
390 WDC_NVME_CAP_DIAG_OPCODE_FA, 512);
393 errx(1, "ERROR : WDC: unsupported device (%#x) for this command", vid);
401 wdc(const struct cmd *nf __unused, int argc, char *argv[])
404 cmd_dispatch(argc, argv, &wdc_cmd);
408 * HGST's 0xc1 page. This is a grab bag of additional data. Please see
409 * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf
410 * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf
411 * Appendix A for details
414 typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
422 static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
423 static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
424 static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
425 static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
426 static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
427 static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
428 static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
429 static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
430 static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
431 static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
433 static struct subpage_print hgst_subpage[] = {
434 { 0x02, print_hgst_info_write_errors },
435 { 0x03, print_hgst_info_read_errors },
436 { 0x05, print_hgst_info_verify_errors },
437 { 0x10, print_hgst_info_self_test },
438 { 0x15, print_hgst_info_background_scan },
439 { 0x30, print_hgst_info_erase_errors },
440 { 0x31, print_hgst_info_erase_counts },
441 { 0x32, print_hgst_info_temp_history },
442 { 0x37, print_hgst_info_ssd_perf },
443 { 0x38, print_hgst_info_firmware_load },
446 /* Print a subpage that is basically just key value pairs */
448 print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size,
449 const struct kv_name *kv, size_t kv_count)
460 ptype = le16dec(wsp);
462 wsp++; /* Flags, just ignore */
465 for (i = 0; i < plen && wsp < esp; i++)
466 param |= (uint64_t)*wsp++ << (i * 8);
467 printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param);
472 print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
474 static struct kv_name kv[] =
476 { 0x0000, "Corrected Without Delay" },
477 { 0x0001, "Corrected Maybe Delayed" },
478 { 0x0002, "Re-Writes" },
479 { 0x0003, "Errors Corrected" },
480 { 0x0004, "Correct Algorithm Used" },
481 { 0x0005, "Bytes Processed" },
482 { 0x0006, "Uncorrected Errors" },
483 { 0x8000, "Flash Write Commands" },
484 { 0x8001, "HGST Special" },
487 printf("Write Errors Subpage:\n");
488 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
492 print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
494 static struct kv_name kv[] =
496 { 0x0000, "Corrected Without Delay" },
497 { 0x0001, "Corrected Maybe Delayed" },
498 { 0x0002, "Re-Reads" },
499 { 0x0003, "Errors Corrected" },
500 { 0x0004, "Correct Algorithm Used" },
501 { 0x0005, "Bytes Processed" },
502 { 0x0006, "Uncorrected Errors" },
503 { 0x8000, "Flash Read Commands" },
504 { 0x8001, "XOR Recovered" },
505 { 0x8002, "Total Corrected Bits" },
508 printf("Read Errors Subpage:\n");
509 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
513 print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
515 static struct kv_name kv[] =
517 { 0x0000, "Corrected Without Delay" },
518 { 0x0001, "Corrected Maybe Delayed" },
519 { 0x0002, "Re-Reads" },
520 { 0x0003, "Errors Corrected" },
521 { 0x0004, "Correct Algorithm Used" },
522 { 0x0005, "Bytes Processed" },
523 { 0x0006, "Uncorrected Errors" },
524 { 0x8000, "Commands Processed" },
527 printf("Verify Errors Subpage:\n");
528 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
532 print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
535 uint8_t *walker = buf;
539 printf("Self Test Subpage:\n");
540 for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */
541 code = le16dec(walker);
543 walker++; /* Ignore fixed flags */
544 if (*walker == 0) /* Last entry is zero length */
546 if (*walker++ != 0x10) {
547 printf("Bad length for self test report\n");
550 printf(" %-30s: %d\n", "Recent Test", code);
551 printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf);
552 printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7);
554 printf(" %-28s: %#x\n", "Self-Test Number", *walker++);
555 hrs = le16dec(walker);
557 lba = le32dec(walker);
559 printf(" %-28s: %u\n", "Total Power On Hrs", hrs);
560 printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba);
561 printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf);
562 printf(" %-28s: %#x\n", "Additional Sense Code", *walker++);
563 printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++);
564 printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++);
569 print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
571 uint8_t *walker = buf;
573 uint16_t code, nscan, progress;
576 printf("Background Media Scan Subpage:\n");
577 /* Decode the header */
578 code = le16dec(walker);
580 walker++; /* Ignore fixed flags */
581 if (*walker++ != 0x10) {
582 printf("Bad length for background scan header\n");
586 printf("Expceted code 0, found code %#x\n", code);
589 pom = le32dec(walker);
591 walker++; /* Reserved */
593 nscan = le16dec(walker);
595 progress = le16dec(walker);
597 walker += 6; /* Reserved */
598 printf(" %-30s: %d\n", "Power On Minutes", pom);
599 printf(" %-30s: %x (%s)\n", "BMS Status", status,
600 status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown")));
601 printf(" %-30s: %d\n", "Number of BMS", nscan);
602 printf(" %-30s: %d\n", "Progress Current BMS", progress);
603 /* Report retirements */
604 if (walker - (uint8_t *)buf != 20) {
605 printf("Coding error, offset not 20\n");
609 printf(" %-30s: %d\n", "BMS retirements", size / 0x18);
611 code = le16dec(walker);
614 if (*walker++ != 0x14) {
615 printf("Bad length parameter\n");
618 pom = le32dec(walker);
621 * Spec sheet says the following are hard coded, if true, just
622 * print the NAND retirement.
624 if (walker[0] == 0x41 &&
633 walker += 4; /* Skip reserved */
634 nand = le32dec(walker);
636 printf(" %-30s: %d\n", "Retirement number", code);
637 printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand);
639 printf("Parameter %#x entry corrupt\n", code);
646 print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
648 static struct kv_name kv[] =
650 { 0x0000, "Corrected Without Delay" },
651 { 0x0001, "Corrected Maybe Delayed" },
652 { 0x0002, "Re-Erase" },
653 { 0x0003, "Errors Corrected" },
654 { 0x0004, "Correct Algorithm Used" },
655 { 0x0005, "Bytes Processed" },
656 { 0x0006, "Uncorrected Errors" },
657 { 0x8000, "Flash Erase Commands" },
658 { 0x8001, "Mfg Defect Count" },
659 { 0x8002, "Grown Defect Count" },
660 { 0x8003, "Erase Count -- User" },
661 { 0x8004, "Erase Count -- System" },
664 printf("Erase Errors Subpage:\n");
665 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
669 print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
671 /* My drive doesn't export this -- so not coding up */
672 printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size);
676 print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
678 uint8_t *walker = buf;
681 printf("Temperature History:\n");
682 printf(" %-30s: %d C\n", "Current Temperature", *walker++);
683 printf(" %-30s: %d C\n", "Reference Temperature", *walker++);
684 printf(" %-30s: %d C\n", "Maximum Temperature", *walker++);
685 printf(" %-30s: %d C\n", "Minimum Temperature", *walker++);
686 min = le32dec(walker);
688 printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60);
689 min = le32dec(walker);
691 printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60);
692 min = le32dec(walker);
694 printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60);
698 print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused)
700 uint8_t *walker = buf;
703 printf("SSD Performance Subpage Type %d:\n", res);
704 val = le64dec(walker);
706 printf(" %-30s: %ju\n", "Host Read Commands", val);
707 val = le64dec(walker);
709 printf(" %-30s: %ju\n", "Host Read Blocks", val);
710 val = le64dec(walker);
712 printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val);
713 val = le64dec(walker);
715 printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val);
716 val = le64dec(walker);
718 printf(" %-30s: %ju\n", "Host Read Commands Stalled", val);
719 val = le64dec(walker);
721 printf(" %-30s: %ju\n", "Host Write Commands", val);
722 val = le64dec(walker);
724 printf(" %-30s: %ju\n", "Host Write Blocks", val);
725 val = le64dec(walker);
727 printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val);
728 val = le64dec(walker);
730 printf(" %-30s: %ju\n", "Host Write Odd End Commands", val);
731 val = le64dec(walker);
733 printf(" %-30s: %ju\n", "Host Write Commands Stalled", val);
734 val = le64dec(walker);
736 printf(" %-30s: %ju\n", "NAND Read Commands", val);
737 val = le64dec(walker);
739 printf(" %-30s: %ju\n", "NAND Read Blocks", val);
740 val = le64dec(walker);
742 printf(" %-30s: %ju\n", "NAND Write Commands", val);
743 val = le64dec(walker);
745 printf(" %-30s: %ju\n", "NAND Write Blocks", val);
746 val = le64dec(walker);
748 printf(" %-30s: %ju\n", "NAND Read Before Writes", val);
752 print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
754 uint8_t *walker = buf;
756 printf("Firmware Load Subpage:\n");
757 printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker));
761 kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp)
765 for (i = 0; i < nsp; i++, sp++) {
766 if (sp->key == subtype) {
767 sp->fn(buf, subtype, res, size);
771 printf("No handler for page type %x\n", subtype);
775 print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
777 uint8_t *walker, *end, *subpage;
780 uint8_t subtype, res;
782 printf("HGST Extra Info Log\n");
783 printf("===================\n");
788 len = le16dec(walker);
790 end = walker + len; /* Length is exclusive of this header */
792 while (walker < end) {
793 subpage = walker + 4;
794 subtype = *walker++ & 0x3f; /* subtype */
795 res = *walker++; /* Reserved */
796 len = le16dec(walker);
797 walker += len + 2; /* Length, not incl header */
799 printf("Ooops! Off the end of the list\n");
802 kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage));
806 NVME_LOGPAGE(hgst_info,
807 HGST_INFO_LOG, "hgst", "Detailed Health/SMART",
808 print_hgst_info_log, DEFAULT_SIZE);
809 NVME_LOGPAGE(wdc_info,
810 HGST_INFO_LOG, "wdc", "Detailed Health/SMART",
811 print_hgst_info_log, DEFAULT_SIZE);