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 #include <stdbool.h> 42 43 #include "nvmecontrol.h" 44 45 /* Tables for command line parsing */ 46 47 static cmd_fn_t wdc; 48 static cmd_fn_t wdc_cap_diag; 49 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 } 54 55 static struct cmd wdc_cmd = { 56 .name = "wdc", .fn = wdc, .descr = "wdc vendor specific commands", .ctx_size = 0, .opts = NULL, .args = NULL, 57 }; 58 59 CMD_COMMAND(wdc_cmd); 60 61 static struct options 62 { 63 const char *template; 64 const char *dev; 65 uint8_t data_area; 66 } opt = { 67 .template = NULL, 68 .dev = NULL, 69 .data_area = 0, 70 }; 71 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"), 77 OPT_END 78 }; 79 80 static const struct args args[] = { 81 { arg_string, &opt.dev, "controller-id" }, 82 { arg_none, NULL, NULL }, 83 }; 84 85 static struct cmd cap_diag_cmd = { 86 .name = "cap-diag", 87 .fn = wdc_cap_diag, 88 .descr = "Retrieve the cap-diag logs from the drive", 89 .ctx_size = sizeof(struct options), 90 .opts = opts, 91 .args = args, 92 }; 93 94 CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd); 95 96 #define WDC_NVME_VID 0x1c58 97 #define WDC_NVME_VID_2 0x1b96 98 #define WDC_NVME_VID_3 0x15b7 99 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 109 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, 115 } wdc_dui_header; 116 117 static void 118 wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) 119 { 120 struct nvme_controller_data cdata; 121 char sn[NVME_SERIAL_NUMBER_LENGTH + 1]; 122 char *walker; 123 124 len -= strlen(buf); 125 buf += strlen(buf); 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 == ' ') 130 walker--; 131 *++walker = '\0'; 132 snprintf(buf, len, "_%s_%s.bin", sn, suffix); 133 } 134 135 static void 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) 138 { 139 struct nvme_pt_command pt; 140 141 memset(&pt, 0, sizeof(pt)); 142 pt.cmd.opc = opcode; 143 pt.cmd.cdw10 = htole32(len / sizeof(uint32_t)); 144 pt.cmd.cdw12 = htole32(cmd); 145 if (e6lg_flag) 146 pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); 147 else 148 pt.cmd.cdw13 = htole32(off / sizeof(uint32_t)); 149 pt.buf = buffer; 150 pt.len = buflen; 151 pt.is_read = 1; 152 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"); 157 } 158 159 static void 160 wdc_do_dump_e6(int fd, char *tmpl, const char *suffix, uint32_t opcode, 161 uint32_t cmd, int len_off) 162 { 163 int first; 164 int fd2; 165 uint8_t *buf, *hdr; 166 uint32_t len, offset; 167 size_t resid; 168 bool e6lg_flag = false; 169 170 wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); 171 172 /* Read Log Dump header */ 173 len = WDC_NVME_LOG_SIZE_HDR_LEN; 174 offset = 0; 175 hdr = malloc(len); 176 if (hdr == NULL) 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) { 180 e6lg_flag = true; 181 } 182 183 /* XXX overwrite protection? */ 184 fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644); 185 if (fd2 < 0) 186 err(1, "open %s", tmpl); 187 buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE); 188 if (buf == NULL) 189 errx(1, "Can't get buffer to read dump"); 190 offset = 0; 191 len = NVME_MAX_XFER_SIZE; 192 first = 1; 193 194 do { 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); 197 198 if (first) { 199 len = be32dec(buf + len_off); 200 if (len == 0) 201 errx(1, "No data for %s", suffix); 202 203 printf("Dumping %d bytes of version %d.%d log to %s\n", len, 204 buf[8], buf[9], tmpl); 205 /* 206 * Adjust amount to dump if total dump < 1MB, 207 * though it likely doesn't matter to the WDC 208 * analysis tools. 209 */ 210 if (resid > len) 211 resid = len; 212 first = 0; 213 } 214 if (write(fd2, buf, resid) != (ssize_t)resid) 215 err(1, "write"); 216 offset += resid; 217 len -= resid; 218 } while (len > 0); 219 free(hdr); 220 free(buf); 221 close(fd2); 222 } 223 224 static void 225 wdc_get_data_dui(int fd, uint32_t opcode, uint32_t len, uint64_t off, 226 uint8_t *buffer, size_t buflen) 227 { 228 struct nvme_pt_command pt; 229 230 memset(&pt, 0, sizeof(pt)); 231 pt.cmd.opc = opcode; 232 pt.cmd.nsid = NONE; 233 pt.cmd.cdw10 = htole32((len / sizeof(uint32_t)) - 1) ; 234 pt.cmd.cdw12 = htole32(off & 0xFFFFFFFFu); 235 pt.cmd.cdw13 = htole32(off >> 32); 236 pt.buf = buffer; 237 pt.len = buflen; 238 pt.is_read = 1; 239 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"); 244 } 245 246 static uint8_t 247 wdc_get_dui_max_sections(uint16_t header_ver) 248 { 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; 258 } 259 return 0; 260 } 261 262 static void 263 wdc_get_dui_log_size(int fd, uint32_t opcode, uint8_t data_area, 264 uint64_t *log_size, int len_off) 265 { 266 uint8_t *hdr; 267 uint8_t max_sections; 268 int i, j; 269 uint16_t hdr_ver; 270 uint16_t len; 271 uint64_t dui_size; 272 273 dui_size = 0; 274 len = 1024; 275 hdr = (uint8_t*)malloc(len); 276 if (hdr == NULL) 277 errx(1, "Can't get buffer to read header"); 278 wdc_get_data_dui(fd, opcode, len, 0, hdr, len); 279 280 hdr += len_off; 281 hdr_ver = ((*hdr & 0xF) != 0)? *hdr : le16dec(hdr); 282 max_sections = wdc_get_dui_max_sections(hdr_ver); 283 284 if (hdr_ver == 0 || hdr_ver == 1) { 285 dui_size = (uint64_t)le32dec(hdr + 4); 286 if (dui_size == 0) { 287 hdr += 8; 288 for (i = 0, j = 0; i < (int)max_sections; i++, j+=8) 289 dui_size += (uint64_t)le32dec(hdr + j + 4); 290 } 291 } else if (hdr_ver == 2 || hdr_ver == 3) { 292 if (data_area == 0) { 293 dui_size = le64dec(hdr + 4); 294 if (dui_size == 0) { 295 hdr += 12; 296 for (i = 0, j = 0 ; i < (int)max_sections; i++, j+=12) 297 dui_size += le64dec(hdr + j + 4); 298 } 299 } else { 300 hdr += 12; 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); 304 else 305 break; 306 } 307 } 308 } 309 else 310 errx(1, "ERROR : No valid header "); 311 312 *log_size = dui_size; 313 free(hdr); 314 } 315 316 static void 317 wdc_do_dump_dui(int fd, char *tmpl, uint8_t data_area, 318 const char *suffix, uint32_t opcode, int len_off) 319 { 320 int fd2, first; 321 uint8_t *buf; 322 uint16_t hdr_ver; 323 uint64_t log_len, offset; 324 size_t resid; 325 326 wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); 327 wdc_get_dui_log_size(fd, opcode, data_area, &log_len, len_off); 328 if (log_len == 0) 329 errx(1, "No data for %s", suffix); 330 fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644); 331 if (fd2 < 0) 332 err(1, "open %s", tmpl); 333 buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE); 334 if (buf == NULL) 335 errx(1, "Can't get buffer to read dump"); 336 offset = 0; 337 first = 1; 338 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); 342 if (first) { 343 hdr_ver = ((buf[len_off] & 0xF) != 0) ? 344 (buf[len_off]) : (le16dec(buf + len_off)); 345 printf("Dumping %jd bytes of version %d log to %s\n", 346 (uintmax_t)log_len, hdr_ver, tmpl); 347 first = 0; 348 } 349 if (write(fd2, buf, resid) != (ssize_t)resid) 350 err(1, "write"); 351 offset += resid; 352 log_len -= resid; 353 } 354 355 free(buf); 356 close(fd2); 357 } 358 359 static void 360 wdc_cap_diag(const struct cmd *f, int argc, char *argv[]) 361 { 362 char tmpl[MAXPATHLEN]; 363 int fd; 364 struct nvme_controller_data cdata; 365 uint32_t vid; 366 367 if (arg_parse(argc, argv, f)) 368 return; 369 if (opt.template == NULL) { 370 fprintf(stderr, "Missing template arg.\n"); 371 arg_help(argc, argv, f); 372 } 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); 376 } 377 strlcpy(tmpl, opt.template, sizeof(tmpl)); 378 open_dev(opt.dev, &fd, 1, 1); 379 read_controller_data(fd, &cdata); 380 vid = cdata.vid; 381 382 switch (vid) { 383 case WDC_NVME_VID : 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); 387 break; 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); 391 break; 392 default: 393 errx(1, "ERROR : WDC: unsupported device (%#x) for this command", vid); 394 } 395 close(fd); 396 397 exit(1); 398 } 399 400 static void 401 wdc(const struct cmd *nf __unused, int argc, char *argv[]) 402 { 403 404 cmd_dispatch(argc, argv, &wdc_cmd); 405 } 406 407 /* 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 412 */ 413 414 typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 415 416 struct subpage_print 417 { 418 uint16_t key; 419 subprint_fn_t fn; 420 }; 421 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); 432 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 }, 444 }; 445 446 /* Print a subpage that is basically just key value pairs */ 447 static void 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) 450 { 451 uint8_t *wsp, *esp; 452 uint16_t ptype; 453 uint8_t plen; 454 uint64_t param; 455 int i; 456 457 wsp = buf; 458 esp = wsp + size; 459 while (wsp < esp) { 460 ptype = le16dec(wsp); 461 wsp += 2; 462 wsp++; /* Flags, just ignore */ 463 plen = *wsp++; 464 param = 0; 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); 468 } 469 } 470 471 static void 472 print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 473 { 474 static struct kv_name kv[] = 475 { 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" }, 485 }; 486 487 printf("Write Errors Subpage:\n"); 488 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 489 } 490 491 static void 492 print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 493 { 494 static struct kv_name kv[] = 495 { 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" }, 506 }; 507 508 printf("Read Errors Subpage:\n"); 509 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 510 } 511 512 static void 513 print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 514 { 515 static struct kv_name kv[] = 516 { 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" }, 525 }; 526 527 printf("Verify Errors Subpage:\n"); 528 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 529 } 530 531 static void 532 print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 533 { 534 size_t i; 535 uint8_t *walker = buf; 536 uint16_t code, hrs; 537 uint32_t lba; 538 539 printf("Self Test Subpage:\n"); 540 for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ 541 code = le16dec(walker); 542 walker += 2; 543 walker++; /* Ignore fixed flags */ 544 if (*walker == 0) /* Last entry is zero length */ 545 break; 546 if (*walker++ != 0x10) { 547 printf("Bad length for self test report\n"); 548 return; 549 } 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); 553 walker++; 554 printf(" %-28s: %#x\n", "Self-Test Number", *walker++); 555 hrs = le16dec(walker); 556 walker += 2; 557 lba = le32dec(walker); 558 walker += 4; 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++); 565 } 566 } 567 568 static void 569 print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 570 { 571 uint8_t *walker = buf; 572 uint8_t status; 573 uint16_t code, nscan, progress; 574 uint32_t pom, nand; 575 576 printf("Background Media Scan Subpage:\n"); 577 /* Decode the header */ 578 code = le16dec(walker); 579 walker += 2; 580 walker++; /* Ignore fixed flags */ 581 if (*walker++ != 0x10) { 582 printf("Bad length for background scan header\n"); 583 return; 584 } 585 if (code != 0) { 586 printf("Expceted code 0, found code %#x\n", code); 587 return; 588 } 589 pom = le32dec(walker); 590 walker += 4; 591 walker++; /* Reserved */ 592 status = *walker++; 593 nscan = le16dec(walker); 594 walker += 2; 595 progress = le16dec(walker); 596 walker += 2; 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"); 606 return; 607 } 608 size -= 20; 609 printf(" %-30s: %d\n", "BMS retirements", size / 0x18); 610 while (size > 0) { 611 code = le16dec(walker); 612 walker += 2; 613 walker++; 614 if (*walker++ != 0x14) { 615 printf("Bad length parameter\n"); 616 return; 617 } 618 pom = le32dec(walker); 619 walker += 4; 620 /* 621 * Spec sheet says the following are hard coded, if true, just 622 * print the NAND retirement. 623 */ 624 if (walker[0] == 0x41 && 625 walker[1] == 0x0b && 626 walker[2] == 0x01 && 627 walker[3] == 0x00 && 628 walker[4] == 0x00 && 629 walker[5] == 0x00 && 630 walker[6] == 0x00 && 631 walker[7] == 0x00) { 632 walker += 8; 633 walker += 4; /* Skip reserved */ 634 nand = le32dec(walker); 635 walker += 4; 636 printf(" %-30s: %d\n", "Retirement number", code); 637 printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); 638 } else { 639 printf("Parameter %#x entry corrupt\n", code); 640 walker += 16; 641 } 642 } 643 } 644 645 static void 646 print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 647 { 648 static struct kv_name kv[] = 649 { 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" }, 662 }; 663 664 printf("Erase Errors Subpage:\n"); 665 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 666 } 667 668 static void 669 print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 670 { 671 /* My drive doesn't export this -- so not coding up */ 672 printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); 673 } 674 675 static void 676 print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 677 { 678 uint8_t *walker = buf; 679 uint32_t min; 680 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); 687 walker += 4; 688 printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60); 689 min = le32dec(walker); 690 walker += 4; 691 printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60); 692 min = le32dec(walker); 693 walker += 4; 694 printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60); 695 } 696 697 static void 698 print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused) 699 { 700 uint8_t *walker = buf; 701 uint64_t val; 702 703 printf("SSD Performance Subpage Type %d:\n", res); 704 val = le64dec(walker); 705 walker += 8; 706 printf(" %-30s: %ju\n", "Host Read Commands", val); 707 val = le64dec(walker); 708 walker += 8; 709 printf(" %-30s: %ju\n", "Host Read Blocks", val); 710 val = le64dec(walker); 711 walker += 8; 712 printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); 713 val = le64dec(walker); 714 walker += 8; 715 printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); 716 val = le64dec(walker); 717 walker += 8; 718 printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); 719 val = le64dec(walker); 720 walker += 8; 721 printf(" %-30s: %ju\n", "Host Write Commands", val); 722 val = le64dec(walker); 723 walker += 8; 724 printf(" %-30s: %ju\n", "Host Write Blocks", val); 725 val = le64dec(walker); 726 walker += 8; 727 printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); 728 val = le64dec(walker); 729 walker += 8; 730 printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); 731 val = le64dec(walker); 732 walker += 8; 733 printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); 734 val = le64dec(walker); 735 walker += 8; 736 printf(" %-30s: %ju\n", "NAND Read Commands", val); 737 val = le64dec(walker); 738 walker += 8; 739 printf(" %-30s: %ju\n", "NAND Read Blocks", val); 740 val = le64dec(walker); 741 walker += 8; 742 printf(" %-30s: %ju\n", "NAND Write Commands", val); 743 val = le64dec(walker); 744 walker += 8; 745 printf(" %-30s: %ju\n", "NAND Write Blocks", val); 746 val = le64dec(walker); 747 walker += 8; 748 printf(" %-30s: %ju\n", "NAND Read Before Writes", val); 749 } 750 751 static void 752 print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 753 { 754 uint8_t *walker = buf; 755 756 printf("Firmware Load Subpage:\n"); 757 printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); 758 } 759 760 static void 761 kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp) 762 { 763 size_t i; 764 765 for (i = 0; i < nsp; i++, sp++) { 766 if (sp->key == subtype) { 767 sp->fn(buf, subtype, res, size); 768 return; 769 } 770 } 771 printf("No handler for page type %x\n", subtype); 772 } 773 774 static void 775 print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) 776 { 777 uint8_t *walker, *end, *subpage; 778 int pages; 779 uint16_t len; 780 uint8_t subtype, res; 781 782 printf("HGST Extra Info Log\n"); 783 printf("===================\n"); 784 785 walker = buf; 786 pages = *walker++; 787 walker++; 788 len = le16dec(walker); 789 walker += 2; 790 end = walker + len; /* Length is exclusive of this header */ 791 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 */ 798 if (walker > end) { 799 printf("Ooops! Off the end of the list\n"); 800 break; 801 } 802 kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage)); 803 } 804 } 805 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); 812