1 /* $NetBSD: wdc.c,v 1.5 2023/07/04 20:40:43 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2017 Netflix, Inc 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 #ifndef lint 31 __RCSID("$NetBSD: wdc.c,v 1.5 2023/07/04 20:40:43 riastradh Exp $"); 32 #if 0 33 __FBSDID("$FreeBSD: head/sbin/nvmecontrol/wdc.c 329824 2018-02-22 13:32:31Z wma $"); 34 #endif 35 #endif 36 37 #include <sys/param.h> 38 #include <sys/ioccom.h> 39 #include <sys/endian.h> 40 41 #include <ctype.h> 42 #include <err.h> 43 #include <fcntl.h> 44 #include <stddef.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include "nvmectl.h" 51 52 #define WDC_NVME_TOC_SIZE 8 53 54 #define WDC_NVME_CAP_DIAG_OPCODE 0xe6 55 #define WDC_NVME_CAP_DIAG_CMD 0x0000 56 57 static void wdc_cap_diag(int argc, char *argv[]); 58 59 #define WDC_CAP_DIAG_USAGE "wdc cap-diag [-o path-template]\n" 60 61 static const struct nvme_function wdc_funcs[] = { 62 {"cap-diag", wdc_cap_diag, WDC_CAP_DIAG_USAGE}, 63 {NULL, NULL, NULL}, 64 }; 65 66 static void 67 wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) 68 { 69 struct nvm_identify_controller cdata; 70 char sn[sizeof(cdata.sn) + 1]; 71 char *walker; 72 73 len -= strlen(buf); 74 buf += strlen(buf); 75 read_controller_data(fd, &cdata); 76 memcpy(sn, cdata.sn, sizeof(cdata.sn)); 77 walker = sn + sizeof(cdata.sn) - 1; 78 while (walker > sn && *walker == ' ') 79 walker--; 80 *++walker = '\0'; 81 snprintf(buf, len, "%s%s.bin", sn, suffix); 82 } 83 84 static void 85 wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd, 86 uint8_t *buffer, size_t buflen) 87 { 88 struct nvme_pt_command pt; 89 90 memset(&pt, 0, sizeof(pt)); 91 pt.cmd.opcode = opcode; 92 pt.cmd.cdw10 = len / sizeof(uint32_t); /* - 1 like all the others ??? */ 93 pt.cmd.cdw11 = off / sizeof(uint32_t); 94 pt.cmd.cdw12 = cmd; 95 pt.buf = buffer; 96 pt.len = buflen; 97 pt.is_read = 1; 98 // printf("opcode %#x cdw10(len) %#x cdw11(offset?) %#x cdw12(cmd/sub) %#x buflen %zd\n", 99 // (int)opcode, (int)cdw10, (int)cdw11, (int)cdw12, buflen); 100 101 if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 102 err(1, "wdc_get_data request failed"); 103 if (nvme_completion_is_error(&pt.cpl)) 104 errx(1, "wdc_get_data request returned error"); 105 } 106 107 static void 108 wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode, 109 uint32_t cmd, int len_off) 110 { 111 int first; 112 int fd2; 113 uint8_t *buf; 114 uint32_t len, offset; 115 size_t resid; 116 long page_size; 117 118 page_size = sysconf(_SC_PAGESIZE); 119 if (page_size <= 0) 120 page_size = 4096; 121 122 wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); 123 124 /* XXX overwrite protection? */ 125 fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644); 126 if (fd2 < 0) 127 err(1, "open %s", tmpl); 128 buf = aligned_alloc(page_size, roundup(NVME_MAX_XFER_SIZE, page_size)); 129 if (buf == NULL) 130 errx(1, "Can't get buffer to read dump"); 131 offset = 0; 132 len = NVME_MAX_XFER_SIZE; 133 first = 1; 134 135 do { 136 resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len; 137 wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid); 138 if (first) { 139 len = be32dec(buf + len_off); 140 if (len == 0) 141 errx(1, "No data for %s", suffix); 142 if (memcmp("E6LG", buf, 4) != 0) 143 printf("Expected header of E6LG, found '%4.4s' instead\n", 144 buf); 145 printf("Dumping %d bytes of version %d.%d log to %s\n", len, 146 buf[8], buf[9], tmpl); 147 /* 148 * Adjust amount to dump if total dump < 1MB, 149 * though it likely doesn't matter to the WDC 150 * analysis tools. 151 */ 152 if (resid > len) 153 resid = len; 154 first = 0; 155 } 156 if (write(fd2, buf, resid) != (ssize_t)resid) 157 err(1, "write"); 158 offset += resid; 159 len -= resid; 160 } while (len > 0); 161 free(buf); 162 close(fd2); 163 } 164 165 __dead static void 166 wdc_cap_diag_usage(void) 167 { 168 fprintf(stderr, "usage:\n"); 169 fprintf(stderr, "\t%s " WDC_CAP_DIAG_USAGE, getprogname()); 170 exit(1); 171 } 172 173 __dead static void 174 wdc_cap_diag(int argc, char *argv[]) 175 { 176 char path_tmpl[MAXPATHLEN]; 177 int ch, fd; 178 179 path_tmpl[0] = '\0'; 180 while ((ch = getopt(argc, argv, "o:")) != -1) { 181 switch ((char)ch) { 182 case 'o': 183 strlcpy(path_tmpl, optarg, MAXPATHLEN); 184 break; 185 default: 186 wdc_cap_diag_usage(); 187 } 188 } 189 /* Check that a controller was specified. */ 190 if (optind >= argc) 191 wdc_cap_diag_usage(); 192 open_dev(argv[optind], &fd, 1, 1); 193 194 wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, 195 WDC_NVME_CAP_DIAG_CMD, 4); 196 197 close(fd); 198 199 exit(1); 200 } 201 202 void 203 wdc(int argc, char *argv[]) 204 { 205 206 dispatch(argc, argv, wdc_funcs); 207 } 208