]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/nvmecontrol/modules/wdc/wdc.c
MFV r365636: libarchive: import fix for WARNS=6 builds in testing bits
[FreeBSD/FreeBSD.git] / sbin / nvmecontrol / modules / wdc / wdc.c
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 && wsp < esp; 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);