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 "wdc (cap-diag)\n" 47 48 NVME_CMD_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(const struct nvme_function *nf, int argc, char *argv[]); 56 57 #define WDC_CAP_DIAG_USAGE "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(const struct nvme_function *nf, int argc, char *argv[]) 158 { 159 char path_tmpl[MAXPATHLEN]; 160 int ch, fd; 161 162 path_tmpl[0] = '\0'; 163 while ((ch = getopt(argc, argv, "o:")) != -1) { 164 switch ((char)ch) { 165 case 'o': 166 strlcpy(path_tmpl, optarg, MAXPATHLEN); 167 break; 168 default: 169 usage(nf); 170 } 171 } 172 /* Check that a controller was specified. */ 173 if (optind >= argc) 174 usage(nf); 175 open_dev(argv[optind], &fd, 1, 1); 176 177 wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, 178 WDC_NVME_CAP_DIAG_CMD, 4); 179 180 close(fd); 181 182 exit(1); 183 } 184 185 static void 186 wdc(const struct nvme_function *nf __unused, int argc, char *argv[]) 187 { 188 189 DISPATCH(argc, argv, wdc); 190 } 191 192 /* 193 * HGST's 0xc1 page. This is a grab bag of additional data. Please see 194 * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf 195 * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf 196 * Appendix A for details 197 */ 198 199 typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 200 201 struct subpage_print 202 { 203 uint16_t key; 204 subprint_fn_t fn; 205 }; 206 207 static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 208 static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 209 static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 210 static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 211 static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 212 static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 213 static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 214 static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 215 static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 216 static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 217 218 static struct subpage_print hgst_subpage[] = { 219 { 0x02, print_hgst_info_write_errors }, 220 { 0x03, print_hgst_info_read_errors }, 221 { 0x05, print_hgst_info_verify_errors }, 222 { 0x10, print_hgst_info_self_test }, 223 { 0x15, print_hgst_info_background_scan }, 224 { 0x30, print_hgst_info_erase_errors }, 225 { 0x31, print_hgst_info_erase_counts }, 226 { 0x32, print_hgst_info_temp_history }, 227 { 0x37, print_hgst_info_ssd_perf }, 228 { 0x38, print_hgst_info_firmware_load }, 229 }; 230 231 /* Print a subpage that is basically just key value pairs */ 232 static void 233 print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size, 234 const struct kv_name *kv, size_t kv_count) 235 { 236 uint8_t *wsp, *esp; 237 uint16_t ptype; 238 uint8_t plen; 239 uint64_t param; 240 int i; 241 242 wsp = buf; 243 esp = wsp + size; 244 while (wsp < esp) { 245 ptype = le16dec(wsp); 246 wsp += 2; 247 wsp++; /* Flags, just ignore */ 248 plen = *wsp++; 249 param = 0; 250 for (i = 0; i < plen; i++) 251 param |= (uint64_t)*wsp++ << (i * 8); 252 printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param); 253 } 254 } 255 256 static void 257 print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 258 { 259 static struct kv_name kv[] = 260 { 261 { 0x0000, "Corrected Without Delay" }, 262 { 0x0001, "Corrected Maybe Delayed" }, 263 { 0x0002, "Re-Writes" }, 264 { 0x0003, "Errors Corrected" }, 265 { 0x0004, "Correct Algorithm Used" }, 266 { 0x0005, "Bytes Processed" }, 267 { 0x0006, "Uncorrected Errors" }, 268 { 0x8000, "Flash Write Commands" }, 269 { 0x8001, "HGST Special" }, 270 }; 271 272 printf("Write Errors Subpage:\n"); 273 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 274 } 275 276 static void 277 print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 278 { 279 static struct kv_name kv[] = 280 { 281 { 0x0000, "Corrected Without Delay" }, 282 { 0x0001, "Corrected Maybe Delayed" }, 283 { 0x0002, "Re-Reads" }, 284 { 0x0003, "Errors Corrected" }, 285 { 0x0004, "Correct Algorithm Used" }, 286 { 0x0005, "Bytes Processed" }, 287 { 0x0006, "Uncorrected Errors" }, 288 { 0x8000, "Flash Read Commands" }, 289 { 0x8001, "XOR Recovered" }, 290 { 0x8002, "Total Corrected Bits" }, 291 }; 292 293 printf("Read Errors Subpage:\n"); 294 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 295 } 296 297 static void 298 print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 299 { 300 static struct kv_name kv[] = 301 { 302 { 0x0000, "Corrected Without Delay" }, 303 { 0x0001, "Corrected Maybe Delayed" }, 304 { 0x0002, "Re-Reads" }, 305 { 0x0003, "Errors Corrected" }, 306 { 0x0004, "Correct Algorithm Used" }, 307 { 0x0005, "Bytes Processed" }, 308 { 0x0006, "Uncorrected Errors" }, 309 { 0x8000, "Commands Processed" }, 310 }; 311 312 printf("Verify Errors Subpage:\n"); 313 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 314 } 315 316 static void 317 print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 318 { 319 size_t i; 320 uint8_t *walker = buf; 321 uint16_t code, hrs; 322 uint32_t lba; 323 324 printf("Self Test Subpage:\n"); 325 for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ 326 code = le16dec(walker); 327 walker += 2; 328 walker++; /* Ignore fixed flags */ 329 if (*walker == 0) /* Last entry is zero length */ 330 break; 331 if (*walker++ != 0x10) { 332 printf("Bad length for self test report\n"); 333 return; 334 } 335 printf(" %-30s: %d\n", "Recent Test", code); 336 printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf); 337 printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7); 338 walker++; 339 printf(" %-28s: %#x\n", "Self-Test Number", *walker++); 340 hrs = le16dec(walker); 341 walker += 2; 342 lba = le32dec(walker); 343 walker += 4; 344 printf(" %-28s: %u\n", "Total Power On Hrs", hrs); 345 printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba); 346 printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf); 347 printf(" %-28s: %#x\n", "Additional Sense Code", *walker++); 348 printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++); 349 printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++); 350 } 351 } 352 353 static void 354 print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 355 { 356 uint8_t *walker = buf; 357 uint8_t status; 358 uint16_t code, nscan, progress; 359 uint32_t pom, nand; 360 361 printf("Background Media Scan Subpage:\n"); 362 /* Decode the header */ 363 code = le16dec(walker); 364 walker += 2; 365 walker++; /* Ignore fixed flags */ 366 if (*walker++ != 0x10) { 367 printf("Bad length for background scan header\n"); 368 return; 369 } 370 if (code != 0) { 371 printf("Expceted code 0, found code %#x\n", code); 372 return; 373 } 374 pom = le32dec(walker); 375 walker += 4; 376 walker++; /* Reserved */ 377 status = *walker++; 378 nscan = le16dec(walker); 379 walker += 2; 380 progress = le16dec(walker); 381 walker += 2; 382 walker += 6; /* Reserved */ 383 printf(" %-30s: %d\n", "Power On Minutes", pom); 384 printf(" %-30s: %x (%s)\n", "BMS Status", status, 385 status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown"))); 386 printf(" %-30s: %d\n", "Number of BMS", nscan); 387 printf(" %-30s: %d\n", "Progress Current BMS", progress); 388 /* Report retirements */ 389 if (walker - (uint8_t *)buf != 20) { 390 printf("Coding error, offset not 20\n"); 391 return; 392 } 393 size -= 20; 394 printf(" %-30s: %d\n", "BMS retirements", size / 0x18); 395 while (size > 0) { 396 code = le16dec(walker); 397 walker += 2; 398 walker++; 399 if (*walker++ != 0x14) { 400 printf("Bad length parameter\n"); 401 return; 402 } 403 pom = le32dec(walker); 404 walker += 4; 405 /* 406 * Spec sheet says the following are hard coded, if true, just 407 * print the NAND retirement. 408 */ 409 if (walker[0] == 0x41 && 410 walker[1] == 0x0b && 411 walker[2] == 0x01 && 412 walker[3] == 0x00 && 413 walker[4] == 0x00 && 414 walker[5] == 0x00 && 415 walker[6] == 0x00 && 416 walker[7] == 0x00) { 417 walker += 8; 418 walker += 4; /* Skip reserved */ 419 nand = le32dec(walker); 420 walker += 4; 421 printf(" %-30s: %d\n", "Retirement number", code); 422 printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); 423 } else { 424 printf("Parameter %#x entry corrupt\n", code); 425 walker += 16; 426 } 427 } 428 } 429 430 static void 431 print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 432 { 433 static struct kv_name kv[] = 434 { 435 { 0x0000, "Corrected Without Delay" }, 436 { 0x0001, "Corrected Maybe Delayed" }, 437 { 0x0002, "Re-Erase" }, 438 { 0x0003, "Errors Corrected" }, 439 { 0x0004, "Correct Algorithm Used" }, 440 { 0x0005, "Bytes Processed" }, 441 { 0x0006, "Uncorrected Errors" }, 442 { 0x8000, "Flash Erase Commands" }, 443 { 0x8001, "Mfg Defect Count" }, 444 { 0x8002, "Grown Defect Count" }, 445 { 0x8003, "Erase Count -- User" }, 446 { 0x8004, "Erase Count -- System" }, 447 }; 448 449 printf("Erase Errors Subpage:\n"); 450 print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 451 } 452 453 static void 454 print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 455 { 456 /* My drive doesn't export this -- so not coding up */ 457 printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); 458 } 459 460 static void 461 print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 462 { 463 uint8_t *walker = buf; 464 uint32_t min; 465 466 printf("Temperature History:\n"); 467 printf(" %-30s: %d C\n", "Current Temperature", *walker++); 468 printf(" %-30s: %d C\n", "Reference Temperature", *walker++); 469 printf(" %-30s: %d C\n", "Maximum Temperature", *walker++); 470 printf(" %-30s: %d C\n", "Minimum Temperature", *walker++); 471 min = le32dec(walker); 472 walker += 4; 473 printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60); 474 min = le32dec(walker); 475 walker += 4; 476 printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60); 477 min = le32dec(walker); 478 walker += 4; 479 printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60); 480 } 481 482 static void 483 print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused) 484 { 485 uint8_t *walker = buf; 486 uint64_t val; 487 488 printf("SSD Performance Subpage Type %d:\n", res); 489 val = le64dec(walker); 490 walker += 8; 491 printf(" %-30s: %ju\n", "Host Read Commands", val); 492 val = le64dec(walker); 493 walker += 8; 494 printf(" %-30s: %ju\n", "Host Read Blocks", val); 495 val = le64dec(walker); 496 walker += 8; 497 printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); 498 val = le64dec(walker); 499 walker += 8; 500 printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); 501 val = le64dec(walker); 502 walker += 8; 503 printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); 504 val = le64dec(walker); 505 walker += 8; 506 printf(" %-30s: %ju\n", "Host Write Commands", val); 507 val = le64dec(walker); 508 walker += 8; 509 printf(" %-30s: %ju\n", "Host Write Blocks", val); 510 val = le64dec(walker); 511 walker += 8; 512 printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); 513 val = le64dec(walker); 514 walker += 8; 515 printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); 516 val = le64dec(walker); 517 walker += 8; 518 printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); 519 val = le64dec(walker); 520 walker += 8; 521 printf(" %-30s: %ju\n", "NAND Read Commands", val); 522 val = le64dec(walker); 523 walker += 8; 524 printf(" %-30s: %ju\n", "NAND Read Blocks", val); 525 val = le64dec(walker); 526 walker += 8; 527 printf(" %-30s: %ju\n", "NAND Write Commands", val); 528 val = le64dec(walker); 529 walker += 8; 530 printf(" %-30s: %ju\n", "NAND Write Blocks", val); 531 val = le64dec(walker); 532 walker += 8; 533 printf(" %-30s: %ju\n", "NAND Read Before Writes", val); 534 } 535 536 static void 537 print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 538 { 539 uint8_t *walker = buf; 540 541 printf("Firmware Load Subpage:\n"); 542 printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); 543 } 544 545 static void 546 kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp) 547 { 548 size_t i; 549 550 for (i = 0; i < nsp; i++, sp++) { 551 if (sp->key == subtype) { 552 sp->fn(buf, subtype, res, size); 553 return; 554 } 555 } 556 printf("No handler for page type %x\n", subtype); 557 } 558 559 static void 560 print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) 561 { 562 uint8_t *walker, *end, *subpage; 563 int pages; 564 uint16_t len; 565 uint8_t subtype, res; 566 567 printf("HGST Extra Info Log\n"); 568 printf("===================\n"); 569 570 walker = buf; 571 pages = *walker++; 572 walker++; 573 len = le16dec(walker); 574 walker += 2; 575 end = walker + len; /* Length is exclusive of this header */ 576 577 while (walker < end) { 578 subpage = walker + 4; 579 subtype = *walker++ & 0x3f; /* subtype */ 580 res = *walker++; /* Reserved */ 581 len = le16dec(walker); 582 walker += len + 2; /* Length, not incl header */ 583 if (walker > end) { 584 printf("Ooops! Off the end of the list\n"); 585 break; 586 } 587 kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage)); 588 } 589 } 590 591 NVME_LOGPAGE(hgst_info, 592 HGST_INFO_LOG, "hgst", "Detailed Health/SMART", 593 print_hgst_info_log, DEFAULT_SIZE); 594 NVME_LOGPAGE(wdc_info, 595 HGST_INFO_LOG, "wdc", "Detailed Health/SMART", 596 print_hgst_info_log, DEFAULT_SIZE); 597 NVME_COMMAND(top, wdc, wdc, WDC_USAGE); 598