logpage.c revision 328723
1252277Sjimharris/*- 2252277Sjimharris * Copyright (c) 2013 EMC Corp. 3252277Sjimharris * All rights reserved. 4252277Sjimharris * 5252277Sjimharris * Copyright (C) 2012-2013 Intel Corporation 6252277Sjimharris * All rights reserved. 7252277Sjimharris * 8252277Sjimharris * Redistribution and use in source and binary forms, with or without 9252277Sjimharris * modification, are permitted provided that the following conditions 10252277Sjimharris * are met: 11252277Sjimharris * 1. Redistributions of source code must retain the above copyright 12252277Sjimharris * notice, this list of conditions and the following disclaimer. 13252277Sjimharris * 2. Redistributions in binary form must reproduce the above copyright 14252277Sjimharris * notice, this list of conditions and the following disclaimer in the 15252277Sjimharris * documentation and/or other materials provided with the distribution. 16252277Sjimharris * 17252277Sjimharris * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18252277Sjimharris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19252277Sjimharris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20252277Sjimharris * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21252277Sjimharris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22252277Sjimharris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23252277Sjimharris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24252277Sjimharris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25252277Sjimharris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26252277Sjimharris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27252277Sjimharris * SUCH DAMAGE. 28252277Sjimharris */ 29252277Sjimharris 30252277Sjimharris#include <sys/cdefs.h> 31252277Sjimharris__FBSDID("$FreeBSD: stable/11/sbin/nvmecontrol/logpage.c 328723 2018-02-01 19:43:18Z mav $"); 32252277Sjimharris 33252277Sjimharris#include <sys/param.h> 34252277Sjimharris#include <sys/ioccom.h> 35252277Sjimharris 36252277Sjimharris#include <ctype.h> 37253109Sjimharris#include <err.h> 38252277Sjimharris#include <fcntl.h> 39252277Sjimharris#include <stdbool.h> 40252277Sjimharris#include <stddef.h> 41252277Sjimharris#include <stdio.h> 42252277Sjimharris#include <stdlib.h> 43252277Sjimharris#include <string.h> 44252277Sjimharris#include <unistd.h> 45328668Smav#include <sys/endian.h> 46252277Sjimharris 47328668Smav#if _BYTE_ORDER != _LITTLE_ENDIAN 48328668Smav#error "Code only works on little endian machines" 49328668Smav#endif 50328668Smav 51252277Sjimharris#include "nvmecontrol.h" 52252277Sjimharris 53252277Sjimharris#define DEFAULT_SIZE (4096) 54252277Sjimharris#define MAX_FW_SLOTS (7) 55252277Sjimharris 56252277Sjimharristypedef void (*print_fn_t)(void *buf, uint32_t size); 57252277Sjimharris 58328674Smavstruct kv_name 59328674Smav{ 60328674Smav uint32_t key; 61328674Smav const char *name; 62328674Smav}; 63328674Smav 64328674Smavstatic const char * 65328674Smavkv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key) 66328674Smav{ 67328674Smav static char bad[32]; 68328674Smav size_t i; 69328674Smav 70328674Smav for (i = 0; i < kv_count; i++, kv++) 71328674Smav if (kv->key == key) 72328674Smav return kv->name; 73328674Smav snprintf(bad, sizeof(bad), "Attribute %#x", key); 74328674Smav return bad; 75328674Smav} 76328674Smav 77328721Smavstatic void 78328721Smavprint_bin(void *data, uint32_t length) 79328721Smav{ 80328721Smav write(STDOUT_FILENO, data, length); 81328721Smav} 82328721Smav 83328668Smav/* 84328709Smav * 128-bit integer augments to standard values. On i386 this 85328709Smav * doesn't exist, so we use 64-bit values. The 128-bit counters 86328709Smav * are crazy anyway, since for this purpose, you'd need a 87328709Smav * billion IOPs for billions of seconds to overflow them. 88328709Smav * So, on 32-bit i386, you'll get truncated values. 89328668Smav */ 90328668Smav#define UINT128_DIG 39 91328709Smav#ifdef __i386__ 92328709Smavtypedef uint64_t uint128_t; 93328709Smav#else 94328668Smavtypedef __uint128_t uint128_t; 95328709Smav#endif 96328668Smav 97328668Smavstatic inline uint128_t 98328668Smavto128(void *p) 99328668Smav{ 100328668Smav return *(uint128_t *)p; 101328668Smav} 102328668Smav 103328668Smavstatic char * 104328668Smavuint128_to_str(uint128_t u, char *buf, size_t buflen) 105328668Smav{ 106328668Smav char *end = buf + buflen - 1; 107328668Smav 108328668Smav *end-- = '\0'; 109328668Smav if (u == 0) 110328668Smav *end-- = '0'; 111328668Smav while (u && end >= buf) { 112328668Smav *end-- = u % 10 + '0'; 113328668Smav u /= 10; 114328668Smav } 115328668Smav end++; 116328668Smav if (u != 0) 117328668Smav return NULL; 118328668Smav 119328668Smav return end; 120328668Smav} 121328668Smav 122328711Smav/* "Missing" from endian.h */ 123328708Smavstatic __inline uint64_t 124328708Smavle48dec(const void *pp) 125328708Smav{ 126328708Smav uint8_t const *p = (uint8_t const *)pp; 127328708Smav 128328708Smav return (((uint64_t)le16dec(p + 4) << 32) | le32dec(p)); 129328708Smav} 130328708Smav 131252277Sjimharrisstatic void * 132253109Sjimharrisget_log_buffer(uint32_t size) 133252277Sjimharris{ 134252277Sjimharris void *buf; 135252277Sjimharris 136253109Sjimharris if ((buf = malloc(size)) == NULL) 137253109Sjimharris errx(1, "unable to malloc %u bytes", size); 138253109Sjimharris 139252277Sjimharris memset(buf, 0, size); 140252277Sjimharris return (buf); 141252277Sjimharris} 142252277Sjimharris 143252277Sjimharrisvoid 144328673Smavread_logpage(int fd, uint8_t log_page, int nsid, void *payload, 145252277Sjimharris uint32_t payload_size) 146252277Sjimharris{ 147252277Sjimharris struct nvme_pt_command pt; 148252277Sjimharris 149252277Sjimharris memset(&pt, 0, sizeof(pt)); 150252277Sjimharris pt.cmd.opc = NVME_OPC_GET_LOG_PAGE; 151252277Sjimharris pt.cmd.nsid = nsid; 152252277Sjimharris pt.cmd.cdw10 = ((payload_size/sizeof(uint32_t)) - 1) << 16; 153252277Sjimharris pt.cmd.cdw10 |= log_page; 154252277Sjimharris pt.buf = payload; 155252277Sjimharris pt.len = payload_size; 156252277Sjimharris pt.is_read = 1; 157252277Sjimharris 158253109Sjimharris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 159253109Sjimharris err(1, "get log page request failed"); 160252277Sjimharris 161253109Sjimharris if (nvme_completion_is_error(&pt.cpl)) 162253109Sjimharris errx(1, "get log page request returned error"); 163252277Sjimharris} 164252277Sjimharris 165252277Sjimharrisstatic void 166252277Sjimharrisprint_log_error(void *buf, uint32_t size) 167252277Sjimharris{ 168252277Sjimharris int i, nentries; 169252277Sjimharris struct nvme_error_information_entry *entry = buf; 170252277Sjimharris struct nvme_status *status; 171252277Sjimharris 172252277Sjimharris printf("Error Information Log\n"); 173252277Sjimharris printf("=====================\n"); 174252277Sjimharris 175252277Sjimharris if (entry->error_count == 0) { 176252277Sjimharris printf("No error entries found\n"); 177252277Sjimharris return; 178252277Sjimharris } 179252277Sjimharris 180252277Sjimharris nentries = size/sizeof(struct nvme_error_information_entry); 181252277Sjimharris for (i = 0; i < nentries; i++, entry++) { 182252277Sjimharris if (entry->error_count == 0) 183252277Sjimharris break; 184252277Sjimharris 185252277Sjimharris status = &entry->status; 186252277Sjimharris printf("Entry %02d\n", i + 1); 187252277Sjimharris printf("=========\n"); 188252277Sjimharris printf(" Error count: %ju\n", entry->error_count); 189252277Sjimharris printf(" Submission queue ID: %u\n", entry->sqid); 190252277Sjimharris printf(" Command ID: %u\n", entry->cid); 191252277Sjimharris /* TODO: Export nvme_status_string structures from kernel? */ 192252277Sjimharris printf(" Status:\n"); 193252277Sjimharris printf(" Phase tag: %d\n", status->p); 194252277Sjimharris printf(" Status code: %d\n", status->sc); 195252277Sjimharris printf(" Status code type: %d\n", status->sct); 196252277Sjimharris printf(" More: %d\n", status->m); 197252277Sjimharris printf(" DNR: %d\n", status->dnr); 198252277Sjimharris printf(" Error location: %u\n", entry->error_location); 199252277Sjimharris printf(" LBA: %ju\n", entry->lba); 200252277Sjimharris printf(" Namespace ID: %u\n", entry->nsid); 201252277Sjimharris printf(" Vendor specific info: %u\n", entry->vendor_specific); 202252277Sjimharris } 203252277Sjimharris} 204252277Sjimharris 205252277Sjimharrisstatic void 206328669Smavprint_temp(uint16_t t) 207328669Smav{ 208328669Smav printf("%u K, %2.2f C, %3.2f F\n", t, (float)t - 273.15, (float)t * 9 / 5 - 459.67); 209328669Smav} 210328669Smav 211328669Smav 212328669Smavstatic void 213252277Sjimharrisprint_log_health(void *buf, uint32_t size __unused) 214252277Sjimharris{ 215252277Sjimharris struct nvme_health_information_page *health = buf; 216328668Smav char cbuf[UINT128_DIG + 1]; 217328669Smav int i; 218252277Sjimharris 219252277Sjimharris printf("SMART/Health Information Log\n"); 220252277Sjimharris printf("============================\n"); 221252277Sjimharris 222252277Sjimharris printf("Critical Warning State: 0x%02x\n", 223252277Sjimharris health->critical_warning.raw); 224252277Sjimharris printf(" Available spare: %d\n", 225252277Sjimharris health->critical_warning.bits.available_spare); 226252277Sjimharris printf(" Temperature: %d\n", 227252277Sjimharris health->critical_warning.bits.temperature); 228252277Sjimharris printf(" Device reliability: %d\n", 229252277Sjimharris health->critical_warning.bits.device_reliability); 230252277Sjimharris printf(" Read only: %d\n", 231252277Sjimharris health->critical_warning.bits.read_only); 232252277Sjimharris printf(" Volatile memory backup: %d\n", 233252277Sjimharris health->critical_warning.bits.volatile_memory_backup); 234328669Smav printf("Temperature: "); 235328669Smav print_temp(health->temperature); 236252277Sjimharris printf("Available spare: %u\n", 237252277Sjimharris health->available_spare); 238252277Sjimharris printf("Available spare threshold: %u\n", 239252277Sjimharris health->available_spare_threshold); 240252277Sjimharris printf("Percentage used: %u\n", 241252277Sjimharris health->percentage_used); 242252277Sjimharris 243328669Smav printf("Data units (512,000 byte) read: %s\n", 244328668Smav uint128_to_str(to128(health->data_units_read), cbuf, sizeof(cbuf))); 245328669Smav printf("Data units written: %s\n", 246328668Smav uint128_to_str(to128(health->data_units_written), cbuf, sizeof(cbuf))); 247328668Smav printf("Host read commands: %s\n", 248328668Smav uint128_to_str(to128(health->host_read_commands), cbuf, sizeof(cbuf))); 249328668Smav printf("Host write commands: %s\n", 250328668Smav uint128_to_str(to128(health->host_write_commands), cbuf, sizeof(cbuf))); 251328668Smav printf("Controller busy time (minutes): %s\n", 252328668Smav uint128_to_str(to128(health->controller_busy_time), cbuf, sizeof(cbuf))); 253328668Smav printf("Power cycles: %s\n", 254328668Smav uint128_to_str(to128(health->power_cycles), cbuf, sizeof(cbuf))); 255328668Smav printf("Power on hours: %s\n", 256328668Smav uint128_to_str(to128(health->power_on_hours), cbuf, sizeof(cbuf))); 257328668Smav printf("Unsafe shutdowns: %s\n", 258328668Smav uint128_to_str(to128(health->unsafe_shutdowns), cbuf, sizeof(cbuf))); 259328668Smav printf("Media errors: %s\n", 260328668Smav uint128_to_str(to128(health->media_errors), cbuf, sizeof(cbuf))); 261328668Smav printf("No. error info log entries: %s\n", 262328668Smav uint128_to_str(to128(health->num_error_info_log_entries), cbuf, sizeof(cbuf))); 263328669Smav 264328669Smav printf("Warning Temp Composite Time: %d\n", health->warning_temp_time); 265328669Smav printf("Error Temp Composite Time: %d\n", health->error_temp_time); 266328669Smav for (i = 0; i < 7; i++) { 267328669Smav if (health->temp_sensor[i] == 0) 268328669Smav continue; 269328669Smav printf("Temperature Sensor %d: ", i + 1); 270328669Smav print_temp(health->temp_sensor[i]); 271328669Smav } 272252277Sjimharris} 273252277Sjimharris 274252277Sjimharrisstatic void 275252277Sjimharrisprint_log_firmware(void *buf, uint32_t size __unused) 276252277Sjimharris{ 277252277Sjimharris int i; 278252277Sjimharris const char *status; 279252277Sjimharris struct nvme_firmware_page *fw = buf; 280252277Sjimharris 281252277Sjimharris printf("Firmware Slot Log\n"); 282252277Sjimharris printf("=================\n"); 283252277Sjimharris 284252277Sjimharris for (i = 0; i < MAX_FW_SLOTS; i++) { 285252277Sjimharris printf("Slot %d: ", i + 1); 286252277Sjimharris if (fw->afi.slot == i + 1) 287252277Sjimharris status = " Active"; 288252277Sjimharris else 289252277Sjimharris status = "Inactive"; 290252277Sjimharris 291252277Sjimharris if (fw->revision[i] == 0LLU) 292252277Sjimharris printf("Empty\n"); 293252277Sjimharris else 294252277Sjimharris if (isprint(*(char *)&fw->revision[i])) 295252277Sjimharris printf("[%s] %.8s\n", status, 296252277Sjimharris (char *)&fw->revision[i]); 297252277Sjimharris else 298252277Sjimharris printf("[%s] %016jx\n", status, 299252277Sjimharris fw->revision[i]); 300252277Sjimharris } 301252277Sjimharris} 302252277Sjimharris 303328708Smav/* 304328708Smav * Intel specific log pages from 305328708Smav * http://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/ssd-dc-p3700-spec.pdf 306328708Smav * 307328708Smav * Though the version as of this date has a typo for the size of log page 0xca, 308328708Smav * offset 147: it is only 1 byte, not 6. 309328708Smav */ 310328673Smavstatic void 311328673Smavprint_intel_temp_stats(void *buf, uint32_t size __unused) 312328673Smav{ 313328673Smav struct intel_log_temp_stats *temp = buf; 314328673Smav 315328673Smav printf("Intel Temperature Log\n"); 316328673Smav printf("=====================\n"); 317328673Smav 318328673Smav printf("Current: "); 319328673Smav print_temp(temp->current); 320328673Smav printf("Overtemp Last Flags %#jx\n", (uintmax_t)temp->overtemp_flag_last); 321328673Smav printf("Overtemp Lifetime Flags %#jx\n", (uintmax_t)temp->overtemp_flag_life); 322328673Smav printf("Max Temperature "); 323328673Smav print_temp(temp->max_temp); 324328673Smav printf("Min Temperature "); 325328673Smav print_temp(temp->min_temp); 326328673Smav printf("Max Operating Temperature "); 327328673Smav print_temp(temp->max_oper_temp); 328328673Smav printf("Min Operating Temperature "); 329328673Smav print_temp(temp->min_oper_temp); 330328673Smav printf("Estimated Temperature Offset: %ju C/K\n", (uintmax_t)temp->est_offset); 331328673Smav} 332328673Smav 333328712Smav/* 334328712Smav * Format from Table 22, section 5.7 IO Command Latency Statistics. 335328712Smav * Read and write stats pages have identical encoding. 336328712Smav */ 337328708Smavstatic void 338328712Smavprint_intel_read_write_lat_log(void *buf, uint32_t size __unused) 339328712Smav{ 340328712Smav const char *walker = buf; 341328712Smav int i; 342328712Smav 343328712Smav printf("Major: %d\n", le16dec(walker + 0)); 344328712Smav printf("Minor: %d\n", le16dec(walker + 2)); 345328712Smav for (i = 0; i < 32; i++) 346328712Smav printf("%4dus-%4dus: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 4 + i * 4)); 347328712Smav for (i = 1; i < 32; i++) 348328712Smav printf("%4dms-%4dms: %ju\n", i, i + 1, (uintmax_t)le32dec(walker + 132 + i * 4)); 349328712Smav for (i = 1; i < 32; i++) 350328712Smav printf("%4dms-%4dms: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 256 + i * 4)); 351328712Smav} 352328712Smav 353328712Smavstatic void 354328712Smavprint_intel_read_lat_log(void *buf, uint32_t size) 355328712Smav{ 356328712Smav 357328712Smav printf("Intel Read Latency Log\n"); 358328712Smav printf("======================\n"); 359328712Smav print_intel_read_write_lat_log(buf, size); 360328712Smav} 361328712Smav 362328712Smavstatic void 363328712Smavprint_intel_write_lat_log(void *buf, uint32_t size) 364328712Smav{ 365328712Smav 366328712Smav printf("Intel Write Latency Log\n"); 367328712Smav printf("=======================\n"); 368328712Smav print_intel_read_write_lat_log(buf, size); 369328712Smav} 370328712Smav 371328712Smav/* 372328712Smav * Table 19. 5.4 SMART Attributes 373328712Smav */ 374328712Smavstatic void 375328708Smavprint_intel_add_smart(void *buf, uint32_t size __unused) 376328708Smav{ 377328708Smav uint8_t *walker = buf; 378328708Smav uint8_t *end = walker + 150; 379328708Smav const char *name; 380328708Smav uint64_t raw; 381328708Smav uint8_t normalized; 382328708Smav 383328708Smav static struct kv_name kv[] = 384328708Smav { 385328708Smav { 0xab, "Program Fail Count" }, 386328708Smav { 0xac, "Erase Fail Count" }, 387328708Smav { 0xad, "Wear Leveling Count" }, 388328708Smav { 0xb8, "End to End Error Count" }, 389328708Smav { 0xc7, "CRC Error Count" }, 390328708Smav { 0xe2, "Timed: Media Wear" }, 391328708Smav { 0xe3, "Timed: Host Read %" }, 392328708Smav { 0xe4, "Timed: Elapsed Time" }, 393328708Smav { 0xea, "Thermal Throttle Status" }, 394328708Smav { 0xf0, "Retry Buffer Overflows" }, 395328708Smav { 0xf3, "PLL Lock Loss Count" }, 396328708Smav { 0xf4, "NAND Bytes Written" }, 397328708Smav { 0xf5, "Host Bytes Written" }, 398328708Smav }; 399328708Smav 400328708Smav printf("Additional SMART Data Log\n"); 401328708Smav printf("=========================\n"); 402328708Smav /* 403328708Smav * walker[0] = Key 404328708Smav * walker[1,2] = reserved 405328708Smav * walker[3] = Normalized Value 406328708Smav * walker[4] = reserved 407328708Smav * walker[5..10] = Little Endian Raw value 408328708Smav * (or other represenations) 409328708Smav * walker[11] = reserved 410328708Smav */ 411328708Smav while (walker < end) { 412328708Smav name = kv_lookup(kv, nitems(kv), *walker); 413328708Smav normalized = walker[3]; 414328708Smav raw = le48dec(walker + 5); 415328708Smav switch (*walker){ 416328708Smav case 0: 417328708Smav break; 418328708Smav case 0xad: 419328708Smav printf("%-32s: %3d min: %u max: %u ave: %u\n", name, normalized, 420328708Smav le16dec(walker + 5), le16dec(walker + 7), le16dec(walker + 9)); 421328708Smav break; 422328708Smav case 0xe2: 423328708Smav printf("%-32s: %3d %.3f%%\n", name, normalized, raw / 1024.0); 424328708Smav break; 425328708Smav case 0xea: 426328708Smav printf("%-32s: %3d %d%% %d times\n", name, normalized, walker[5], le32dec(walker+6)); 427328708Smav break; 428328708Smav default: 429328708Smav printf("%-32s: %3d %ju\n", name, normalized, (uintmax_t)raw); 430328708Smav break; 431328708Smav } 432328708Smav walker += 12; 433328708Smav } 434328708Smav} 435328708Smav 436328673Smav/* 437328674Smav * HGST's 0xc1 page. This is a grab bag of additional data. Please see 438328674Smav * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf 439328674Smav * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf 440328674Smav * Appendix A for details 441328674Smav */ 442328674Smav 443328674Smavtypedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 444328674Smav 445328674Smavstruct subpage_print 446328674Smav{ 447328674Smav uint16_t key; 448328674Smav subprint_fn_t fn; 449328674Smav}; 450328674Smav 451328674Smavstatic void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 452328674Smavstatic void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 453328674Smavstatic void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 454328674Smavstatic void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 455328674Smavstatic void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 456328674Smavstatic void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 457328674Smavstatic void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 458328674Smavstatic void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 459328674Smavstatic void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 460328674Smavstatic void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 461328674Smav 462328674Smavstatic struct subpage_print hgst_subpage[] = { 463328674Smav { 0x02, print_hgst_info_write_errors }, 464328674Smav { 0x03, print_hgst_info_read_errors }, 465328674Smav { 0x05, print_hgst_info_verify_errors }, 466328674Smav { 0x10, print_hgst_info_self_test }, 467328674Smav { 0x15, print_hgst_info_background_scan }, 468328674Smav { 0x30, print_hgst_info_erase_errors }, 469328674Smav { 0x31, print_hgst_info_erase_counts }, 470328674Smav { 0x32, print_hgst_info_temp_history }, 471328674Smav { 0x37, print_hgst_info_ssd_perf }, 472328674Smav { 0x38, print_hgst_info_firmware_load }, 473328674Smav}; 474328674Smav 475328674Smav/* Print a subpage that is basically just key value pairs */ 476328674Smavstatic void 477328674Smavprint_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size, 478328674Smav const struct kv_name *kv, size_t kv_count) 479328674Smav{ 480328674Smav uint8_t *wsp, *esp; 481328674Smav uint16_t ptype; 482328674Smav uint8_t plen; 483328674Smav uint64_t param; 484328674Smav int i; 485328674Smav 486328674Smav wsp = buf; 487328674Smav esp = wsp + size; 488328674Smav while (wsp < esp) { 489328674Smav ptype = le16dec(wsp); 490328674Smav wsp += 2; 491328674Smav wsp++; /* Flags, just ignore */ 492328674Smav plen = *wsp++; 493328674Smav param = 0; 494328674Smav for (i = 0; i < plen; i++) 495328674Smav param |= (uint64_t)*wsp++ << (i * 8); 496328674Smav printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param); 497328674Smav } 498328674Smav} 499328674Smav 500328674Smavstatic void 501328674Smavprint_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 502328674Smav{ 503328674Smav static struct kv_name kv[] = 504328674Smav { 505328674Smav { 0x0000, "Corrected Without Delay" }, 506328674Smav { 0x0001, "Corrected Maybe Delayed" }, 507328674Smav { 0x0002, "Re-Writes" }, 508328674Smav { 0x0003, "Errors Corrected" }, 509328674Smav { 0x0004, "Correct Algorithm Used" }, 510328674Smav { 0x0005, "Bytes Processed" }, 511328674Smav { 0x0006, "Uncorrected Errors" }, 512328674Smav { 0x8000, "Flash Write Commands" }, 513328674Smav { 0x8001, "HGST Special" }, 514328674Smav }; 515328674Smav 516328674Smav printf("Write Errors Subpage:\n"); 517328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 518328674Smav} 519328674Smav 520328674Smavstatic void 521328674Smavprint_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 522328674Smav{ 523328674Smav static struct kv_name kv[] = 524328674Smav { 525328674Smav { 0x0000, "Corrected Without Delay" }, 526328674Smav { 0x0001, "Corrected Maybe Delayed" }, 527328674Smav { 0x0002, "Re-Reads" }, 528328674Smav { 0x0003, "Errors Corrected" }, 529328674Smav { 0x0004, "Correct Algorithm Used" }, 530328674Smav { 0x0005, "Bytes Processed" }, 531328674Smav { 0x0006, "Uncorrected Errors" }, 532328674Smav { 0x8000, "Flash Read Commands" }, 533328674Smav { 0x8001, "XOR Recovered" }, 534328674Smav { 0x8002, "Total Corrected Bits" }, 535328674Smav }; 536328674Smav 537328674Smav printf("Read Errors Subpage:\n"); 538328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 539328674Smav} 540328674Smav 541328674Smavstatic void 542328674Smavprint_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 543328674Smav{ 544328674Smav static struct kv_name kv[] = 545328674Smav { 546328674Smav { 0x0000, "Corrected Without Delay" }, 547328674Smav { 0x0001, "Corrected Maybe Delayed" }, 548328674Smav { 0x0002, "Re-Reads" }, 549328674Smav { 0x0003, "Errors Corrected" }, 550328674Smav { 0x0004, "Correct Algorithm Used" }, 551328674Smav { 0x0005, "Bytes Processed" }, 552328674Smav { 0x0006, "Uncorrected Errors" }, 553328674Smav { 0x8000, "Commands Processed" }, 554328674Smav }; 555328674Smav 556328674Smav printf("Verify Errors Subpage:\n"); 557328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 558328674Smav} 559328674Smav 560328674Smavstatic void 561328674Smavprint_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 562328674Smav{ 563328674Smav size_t i; 564328674Smav uint8_t *walker = buf; 565328674Smav uint16_t code, hrs; 566328674Smav uint32_t lba; 567328674Smav 568328674Smav printf("Self Test Subpage:\n"); 569328674Smav for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ 570328674Smav code = le16dec(walker); 571328674Smav walker += 2; 572328674Smav walker++; /* Ignore fixed flags */ 573328674Smav if (*walker == 0) /* Last entry is zero length */ 574328674Smav break; 575328674Smav if (*walker++ != 0x10) { 576328674Smav printf("Bad length for self test report\n"); 577328674Smav return; 578328674Smav } 579328674Smav printf(" %-30s: %d\n", "Recent Test", code); 580328674Smav printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf); 581328674Smav printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7); 582328674Smav walker++; 583328674Smav printf(" %-28s: %#x\n", "Self-Test Number", *walker++); 584328674Smav hrs = le16dec(walker); 585328674Smav walker += 2; 586328674Smav lba = le32dec(walker); 587328674Smav walker += 4; 588328674Smav printf(" %-28s: %u\n", "Total Power On Hrs", hrs); 589328674Smav printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba); 590328674Smav printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf); 591328674Smav printf(" %-28s: %#x\n", "Additional Sense Code", *walker++); 592328674Smav printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++); 593328674Smav printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++); 594328674Smav } 595328674Smav} 596328674Smav 597328674Smavstatic void 598328674Smavprint_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 599328674Smav{ 600328674Smav uint8_t *walker = buf; 601328674Smav uint8_t status; 602328674Smav uint16_t code, nscan, progress; 603328674Smav uint32_t pom, nand; 604328674Smav 605328674Smav printf("Background Media Scan Subpage:\n"); 606328674Smav /* Decode the header */ 607328674Smav code = le16dec(walker); 608328674Smav walker += 2; 609328674Smav walker++; /* Ignore fixed flags */ 610328674Smav if (*walker++ != 0x10) { 611328674Smav printf("Bad length for background scan header\n"); 612328674Smav return; 613328674Smav } 614328674Smav if (code != 0) { 615328674Smav printf("Expceted code 0, found code %#x\n", code); 616328674Smav return; 617328674Smav } 618328674Smav pom = le32dec(walker); 619328674Smav walker += 4; 620328674Smav walker++; /* Reserved */ 621328674Smav status = *walker++; 622328674Smav nscan = le16dec(walker); 623328674Smav walker += 2; 624328674Smav progress = le16dec(walker); 625328674Smav walker += 2; 626328674Smav walker += 6; /* Reserved */ 627328674Smav printf(" %-30s: %d\n", "Power On Minutes", pom); 628328674Smav printf(" %-30s: %x (%s)\n", "BMS Status", status, 629328674Smav status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown"))); 630328674Smav printf(" %-30s: %d\n", "Number of BMS", nscan); 631328674Smav printf(" %-30s: %d\n", "Progress Current BMS", progress); 632328674Smav /* Report retirements */ 633328674Smav if (walker - (uint8_t *)buf != 20) { 634328674Smav printf("Coding error, offset not 20\n"); 635328674Smav return; 636328674Smav } 637328674Smav size -= 20; 638328674Smav printf(" %-30s: %d\n", "BMS retirements", size / 0x18); 639328674Smav while (size > 0) { 640328674Smav code = le16dec(walker); 641328674Smav walker += 2; 642328674Smav walker++; 643328674Smav if (*walker++ != 0x14) { 644328674Smav printf("Bad length parameter\n"); 645328674Smav return; 646328674Smav } 647328674Smav pom = le32dec(walker); 648328674Smav walker += 4; 649328674Smav /* 650328674Smav * Spec sheet says the following are hard coded, if true, just 651328674Smav * print the NAND retirement. 652328674Smav */ 653328674Smav if (walker[0] == 0x41 && 654328674Smav walker[1] == 0x0b && 655328674Smav walker[2] == 0x01 && 656328674Smav walker[3] == 0x00 && 657328674Smav walker[4] == 0x00 && 658328674Smav walker[5] == 0x00 && 659328674Smav walker[6] == 0x00 && 660328674Smav walker[7] == 0x00) { 661328674Smav walker += 8; 662328674Smav walker += 4; /* Skip reserved */ 663328674Smav nand = le32dec(walker); 664328674Smav walker += 4; 665328674Smav printf(" %-30s: %d\n", "Retirement number", code); 666328674Smav printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); 667328674Smav } else { 668328674Smav printf("Parameter %#x entry corrupt\n", code); 669328674Smav walker += 16; 670328674Smav } 671328674Smav } 672328674Smav} 673328674Smav 674328674Smavstatic void 675328674Smavprint_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 676328674Smav{ 677328674Smav static struct kv_name kv[] = 678328674Smav { 679328674Smav { 0x0000, "Corrected Without Delay" }, 680328674Smav { 0x0001, "Corrected Maybe Delayed" }, 681328674Smav { 0x0002, "Re-Erase" }, 682328674Smav { 0x0003, "Errors Corrected" }, 683328674Smav { 0x0004, "Correct Algorithm Used" }, 684328674Smav { 0x0005, "Bytes Processed" }, 685328674Smav { 0x0006, "Uncorrected Errors" }, 686328674Smav { 0x8000, "Flash Erase Commands" }, 687328674Smav { 0x8001, "Mfg Defect Count" }, 688328674Smav { 0x8002, "Grown Defect Count" }, 689328674Smav { 0x8003, "Erase Count -- User" }, 690328674Smav { 0x8004, "Erase Count -- System" }, 691328674Smav }; 692328674Smav 693328674Smav printf("Erase Errors Subpage:\n"); 694328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 695328674Smav} 696328674Smav 697328674Smavstatic void 698328674Smavprint_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 699328674Smav{ 700328674Smav /* My drive doesn't export this -- so not coding up */ 701328674Smav printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); 702328674Smav} 703328674Smav 704328674Smavstatic void 705328674Smavprint_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 706328674Smav{ 707328674Smav uint8_t *walker = buf; 708328674Smav uint32_t min; 709328674Smav 710328674Smav printf("Temperature History:\n"); 711328674Smav printf(" %-30s: %d C\n", "Current Temperature", *walker++); 712328674Smav printf(" %-30s: %d C\n", "Reference Temperature", *walker++); 713328674Smav printf(" %-30s: %d C\n", "Maximum Temperature", *walker++); 714328674Smav printf(" %-30s: %d C\n", "Minimum Temperature", *walker++); 715328674Smav min = le32dec(walker); 716328674Smav walker += 4; 717328723Smav printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60); 718328674Smav min = le32dec(walker); 719328674Smav walker += 4; 720328723Smav printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60); 721328674Smav min = le32dec(walker); 722328674Smav walker += 4; 723328723Smav printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60); 724328674Smav} 725328674Smav 726328674Smavstatic void 727328674Smavprint_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused) 728328674Smav{ 729328674Smav uint8_t *walker = buf; 730328674Smav uint64_t val; 731328674Smav 732328674Smav printf("SSD Performance Subpage Type %d:\n", res); 733328674Smav val = le64dec(walker); 734328674Smav walker += 8; 735328674Smav printf(" %-30s: %ju\n", "Host Read Commands", val); 736328674Smav val = le64dec(walker); 737328674Smav walker += 8; 738328674Smav printf(" %-30s: %ju\n", "Host Read Blocks", val); 739328674Smav val = le64dec(walker); 740328674Smav walker += 8; 741328674Smav printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); 742328674Smav val = le64dec(walker); 743328674Smav walker += 8; 744328674Smav printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); 745328674Smav val = le64dec(walker); 746328674Smav walker += 8; 747328674Smav printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); 748328674Smav val = le64dec(walker); 749328674Smav walker += 8; 750328674Smav printf(" %-30s: %ju\n", "Host Write Commands", val); 751328674Smav val = le64dec(walker); 752328674Smav walker += 8; 753328674Smav printf(" %-30s: %ju\n", "Host Write Blocks", val); 754328674Smav val = le64dec(walker); 755328674Smav walker += 8; 756328674Smav printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); 757328674Smav val = le64dec(walker); 758328674Smav walker += 8; 759328674Smav printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); 760328674Smav val = le64dec(walker); 761328674Smav walker += 8; 762328674Smav printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); 763328674Smav val = le64dec(walker); 764328674Smav walker += 8; 765328674Smav printf(" %-30s: %ju\n", "NAND Read Commands", val); 766328674Smav val = le64dec(walker); 767328674Smav walker += 8; 768328674Smav printf(" %-30s: %ju\n", "NAND Read Blocks", val); 769328674Smav val = le64dec(walker); 770328674Smav walker += 8; 771328674Smav printf(" %-30s: %ju\n", "NAND Write Commands", val); 772328674Smav val = le64dec(walker); 773328674Smav walker += 8; 774328674Smav printf(" %-30s: %ju\n", "NAND Write Blocks", val); 775328674Smav val = le64dec(walker); 776328674Smav walker += 8; 777328674Smav printf(" %-30s: %ju\n", "NAND Read Before Writes", val); 778328674Smav} 779328674Smav 780328674Smavstatic void 781328674Smavprint_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 782328674Smav{ 783328674Smav uint8_t *walker = buf; 784328674Smav 785328674Smav printf("Firmware Load Subpage:\n"); 786328674Smav printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); 787328674Smav} 788328674Smav 789328674Smavstatic void 790328674Smavkv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp) 791328674Smav{ 792328674Smav size_t i; 793328674Smav 794328674Smav for (i = 0; i < nsp; i++, sp++) { 795328674Smav if (sp->key == subtype) { 796328674Smav sp->fn(buf, subtype, res, size); 797328674Smav return; 798328674Smav } 799328674Smav } 800328674Smav printf("No handler for page type %x\n", subtype); 801328674Smav} 802328674Smav 803328674Smavstatic void 804328674Smavprint_hgst_info_log(void *buf, uint32_t size __unused) 805328674Smav{ 806328674Smav uint8_t *walker, *end, *subpage; 807328674Smav int pages; 808328674Smav uint16_t len; 809328674Smav uint8_t subtype, res; 810328674Smav 811328674Smav printf("HGST Extra Info Log\n"); 812328674Smav printf("===================\n"); 813328674Smav 814328674Smav walker = buf; 815328674Smav pages = *walker++; 816328674Smav walker++; 817328674Smav len = le16dec(walker); 818328674Smav walker += 2; 819328674Smav end = walker + len; /* Length is exclusive of this header */ 820328674Smav 821328674Smav while (walker < end) { 822328674Smav subpage = walker + 4; 823328674Smav subtype = *walker++ & 0x3f; /* subtype */ 824328674Smav res = *walker++; /* Reserved */ 825328674Smav len = le16dec(walker); 826328674Smav walker += len + 2; /* Length, not incl header */ 827328674Smav if (walker > end) { 828328674Smav printf("Ooops! Off the end of the list\n"); 829328674Smav break; 830328674Smav } 831328674Smav kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage)); 832328674Smav } 833328674Smav} 834328674Smav 835328674Smav/* 836328673Smav * Table of log page printer / sizing. 837328673Smav * 838328673Smav * This includes Intel specific pages that are widely implemented. Not 839328673Smav * sure how best to switch between different vendors. 840328673Smav */ 841252302Sglebiusstatic struct logpage_function { 842252277Sjimharris uint8_t log_page; 843328710Smav const char *vendor; 844328672Smav print_fn_t print_fn; 845328672Smav size_t size; 846252277Sjimharris} logfuncs[] = { 847328710Smav {NVME_LOG_ERROR, NULL, print_log_error, 848328672Smav 0}, 849328710Smav {NVME_LOG_HEALTH_INFORMATION, NULL, print_log_health, 850328672Smav sizeof(struct nvme_health_information_page)}, 851328710Smav {NVME_LOG_FIRMWARE_SLOT, NULL, print_log_firmware, 852328672Smav sizeof(struct nvme_firmware_page)}, 853328712Smav {HGST_INFO_LOG, "hgst", print_hgst_info_log, 854328712Smav DEFAULT_SIZE}, 855328719Smav {HGST_INFO_LOG, "wdc", print_hgst_info_log, 856328719Smav DEFAULT_SIZE}, 857328710Smav {INTEL_LOG_TEMP_STATS, "intel", print_intel_temp_stats, 858328673Smav sizeof(struct intel_log_temp_stats)}, 859328712Smav {INTEL_LOG_READ_LAT_LOG, "intel", print_intel_read_lat_log, 860328712Smav DEFAULT_SIZE}, 861328712Smav {INTEL_LOG_WRITE_LAT_LOG, "intel", print_intel_write_lat_log, 862328712Smav DEFAULT_SIZE}, 863328710Smav {INTEL_LOG_ADD_SMART, "intel", print_intel_add_smart, 864328708Smav DEFAULT_SIZE}, 865328710Smav {0, NULL, NULL, 0}, 866252277Sjimharris}; 867252277Sjimharris 868252277Sjimharrisstatic void 869252277Sjimharrislogpage_usage(void) 870252277Sjimharris{ 871252277Sjimharris fprintf(stderr, "usage:\n"); 872252277Sjimharris fprintf(stderr, LOGPAGE_USAGE); 873253109Sjimharris exit(1); 874252277Sjimharris} 875252277Sjimharris 876252277Sjimharrisvoid 877252277Sjimharrislogpage(int argc, char *argv[]) 878252277Sjimharris{ 879253114Sjimharris int fd, nsid; 880252277Sjimharris int log_page = 0, pageflag = false; 881328721Smav int binflag = false, hexflag = false, ns_specified; 882253114Sjimharris char ch, *p; 883253114Sjimharris char cname[64]; 884253109Sjimharris uint32_t size; 885252277Sjimharris void *buf; 886328710Smav const char *vendor = NULL; 887252277Sjimharris struct logpage_function *f; 888252277Sjimharris struct nvme_controller_data cdata; 889252277Sjimharris print_fn_t print_fn; 890252277Sjimharris 891328721Smav while ((ch = getopt(argc, argv, "bp:xv:")) != -1) { 892252277Sjimharris switch (ch) { 893328721Smav case 'b': 894328721Smav binflag = true; 895328721Smav break; 896252277Sjimharris case 'p': 897252277Sjimharris /* TODO: Add human-readable ASCII page IDs */ 898252277Sjimharris log_page = strtol(optarg, &p, 0); 899252277Sjimharris if (p != NULL && *p != '\0') { 900252277Sjimharris fprintf(stderr, 901252277Sjimharris "\"%s\" not valid log page id.\n", 902252277Sjimharris optarg); 903252277Sjimharris logpage_usage(); 904252277Sjimharris } 905252277Sjimharris pageflag = true; 906252277Sjimharris break; 907252277Sjimharris case 'x': 908252277Sjimharris hexflag = true; 909252277Sjimharris break; 910328710Smav case 'v': 911328710Smav vendor = optarg; 912328710Smav break; 913252277Sjimharris } 914252277Sjimharris } 915252277Sjimharris 916252277Sjimharris if (!pageflag) { 917252277Sjimharris printf("Missing page_id (-p).\n"); 918252277Sjimharris logpage_usage(); 919252277Sjimharris } 920252277Sjimharris 921252277Sjimharris /* Check that a controller and/or namespace was specified. */ 922252277Sjimharris if (optind >= argc) 923252277Sjimharris logpage_usage(); 924252277Sjimharris 925253114Sjimharris if (strstr(argv[optind], NVME_NS_PREFIX) != NULL) { 926253114Sjimharris ns_specified = true; 927253114Sjimharris parse_ns_str(argv[optind], cname, &nsid); 928253114Sjimharris open_dev(cname, &fd, 1, 1); 929253114Sjimharris } else { 930253114Sjimharris ns_specified = false; 931253114Sjimharris nsid = NVME_GLOBAL_NAMESPACE_TAG; 932253114Sjimharris open_dev(argv[optind], &fd, 1, 1); 933253114Sjimharris } 934253114Sjimharris 935285796Sjimharris read_controller_data(fd, &cdata); 936285796Sjimharris 937252277Sjimharris /* 938252277Sjimharris * The log page attribtues indicate whether or not the controller 939252277Sjimharris * supports the SMART/Health information log page on a per 940252277Sjimharris * namespace basis. 941252277Sjimharris */ 942253114Sjimharris if (ns_specified) { 943253114Sjimharris if (log_page != NVME_LOG_HEALTH_INFORMATION) 944253114Sjimharris errx(1, "log page %d valid only at controller level", 945253114Sjimharris log_page); 946253114Sjimharris if (cdata.lpa.ns_smart == 0) 947253114Sjimharris errx(1, 948253114Sjimharris "controller does not support per namespace " 949253114Sjimharris "smart/health information"); 950253114Sjimharris } 951252277Sjimharris 952252277Sjimharris print_fn = print_hex; 953328672Smav size = DEFAULT_SIZE; 954328721Smav if (binflag) 955328721Smav print_fn = print_bin; 956328721Smav if (!binflag && !hexflag) { 957252277Sjimharris /* 958328710Smav * See if there is a pretty print function for the specified log 959328710Smav * page. If one isn't found, we just revert to the default 960328710Smav * (print_hex). If there was a vendor specified bt the user, and 961328710Smav * the page is vendor specific, don't match the print function 962328710Smav * unless the vendors match. 963252277Sjimharris */ 964328710Smav for (f = logfuncs; f->log_page > 0; f++) { 965328710Smav if (f->vendor != NULL && vendor != NULL && 966328710Smav strcmp(f->vendor, vendor) != 0) 967328710Smav continue; 968328710Smav if (log_page != f->log_page) 969328710Smav continue; 970328710Smav print_fn = f->print_fn; 971328710Smav size = f->size; 972328710Smav break; 973252277Sjimharris } 974252277Sjimharris } 975252277Sjimharris 976328672Smav if (log_page == NVME_LOG_ERROR) { 977252277Sjimharris size = sizeof(struct nvme_error_information_entry); 978252277Sjimharris size *= (cdata.elpe + 1); 979252277Sjimharris } 980252277Sjimharris 981328672Smav /* Read the log page */ 982252277Sjimharris buf = get_log_buffer(size); 983252277Sjimharris read_logpage(fd, log_page, nsid, buf, size); 984252277Sjimharris print_fn(buf, size); 985252277Sjimharris 986252277Sjimharris close(fd); 987253109Sjimharris exit(0); 988252277Sjimharris} 989