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