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