Module Name: src Committed By: nonaka Date: Sat Apr 29 00:06:40 UTC 2017
Modified Files: src/sbin/nvmectl: Makefile firmware.c logpage.c nvme.h nvmectl.8 nvmectl.c nvmectl.h Log Message: nvmectl(8): sync with FreeBSD HEAD r316105. - Expand the SMART / Health Information Log Page (Page 02) printout based on NVM Express 1.2.1 Standard. - Implement Intel-specific log pages. - Implement HGST-specific log pages. - Implement wdc-specific nvme control options. - Add the ability to dump log pages directly in binary to stdout. To generate a diff of this commit: cvs rdiff -u -r1.2 -r1.3 src/sbin/nvmectl/Makefile src/sbin/nvmectl/nvmectl.8 \ src/sbin/nvmectl/nvmectl.c src/sbin/nvmectl/nvmectl.h cvs rdiff -u -r1.1 -r1.2 src/sbin/nvmectl/firmware.c src/sbin/nvmectl/nvme.h cvs rdiff -u -r1.3 -r1.4 src/sbin/nvmectl/logpage.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sbin/nvmectl/Makefile diff -u src/sbin/nvmectl/Makefile:1.2 src/sbin/nvmectl/Makefile:1.3 --- src/sbin/nvmectl/Makefile:1.2 Mon Feb 13 11:16:46 2017 +++ src/sbin/nvmectl/Makefile Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.2 2017/02/13 11:16:46 nonaka Exp $ +# $NetBSD: Makefile,v 1.3 2017/04/29 00:06:40 nonaka Exp $ .include <bsd.own.mk> @@ -11,6 +11,7 @@ SRCS+= logpage.c SRCS+= perftest.c SRCS+= power.c SRCS+= reset.c +SRCS+= wdc.c SRCS+= bignum.c SRCS+= humanize_bignum.c MAN= nvmectl.8 Index: src/sbin/nvmectl/nvmectl.8 diff -u src/sbin/nvmectl/nvmectl.8:1.2 src/sbin/nvmectl/nvmectl.8:1.3 --- src/sbin/nvmectl/nvmectl.8:1.2 Sun Jun 5 09:13:08 2016 +++ src/sbin/nvmectl/nvmectl.8 Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -.\" $NetBSD: nvmectl.8,v 1.2 2016/06/05 09:13:08 wiz Exp $ +.\" $NetBSD: nvmectl.8,v 1.3 2017/04/29 00:06:40 nonaka Exp $ .\" .\" Copyright (c) 2012 Intel Corporation .\" All rights reserved. @@ -32,7 +32,7 @@ .\" .\" Author: Jim Harris <jimhar...@freebsd.org> .\" -.\" $FreeBSD: head/sbin/nvmecontrol/nvmecontrol.8 299151 2016-05-06 03:11:34Z pfg $ +.\" $FreeBSD: head/sbin/nvmecontrol/nvmecontrol.8 314230 2017-02-25 00:09:16Z imp $ .\" .Dd May 19, 2016 .Dt NVMECTL 8 @@ -62,6 +62,8 @@ .Ic logpage .Op Fl x .Op Fl p Ar page_id +.Op Fl v Ar vendor-string +.Op Fl b .Ar device_id Ns | Ns Ar namespace_id .\".Nm .\".Ic firmware @@ -75,9 +77,56 @@ .Op Fl p Ar power_state .Op Fl w Ar workload_hint .Ar device_id +.Nm +.Ic wdc cap-diag +.Op Fl o path_template +.Ar device id +.Nm +.Ic wdc drive-log +.Op Fl o path_template +.Ar device id +.Nm +.Ic wdc get-crash-dump +.Op Fl o path_template +.Ar device id +.\" .Nm +.\" .Ic wdc purge +.\" .Aq device id +.\" .Nm +.\" .Ic wdc purge-monitor +.\" .Aq device id .Sh DESCRIPTION NVM Express (NVMe) is a storage protocol standard, for SSDs and other high-speed storage devices over PCI Express. +.Pp +.Ss logpage +The logpage command knows how to print log pages of various types. +It also knows about vendor specific log pages from hgst/wdc and intel. +Page 0xc1 for hgst/wdc contains the advanced smart information about +the drive. +Page 0xc1 is read latency stats for intel. +Page 0xc2 is write latency stats for intel. +Page 0xc5 is temperature stats for intel. +Page 0xca is advanced smart information for intel. +.Pp +Specifying +.Fl p +.Ic help +will list all valid vendors and pages. +.Fl x +will print the page as hex. +.Fl b +will print the binary data for the page. +.Ss wdc +The various wdc command retrieve log data from the wdc/hgst drives. +The +.Fl o +flag specifies a path template to use to output the files. +Each file takes the path template (which defaults to nothing), appends +the drive's serial number and the type of dump it is followed +by .bin. +These logs must be sent to the vendor for analysis. +This tool only provides a way to extract them. .Sh EXAMPLES .Dl nvmectl devlist .Pp @@ -95,9 +144,9 @@ data for namespace 1. .\".Pp .\".Dl nvmectl perftest -n 32 -o read -s 512 -t 30 nvme0ns1 .\".Pp -.\"Run a performance test on nvme0ns1 using 32 kernel threads for 30 seconds. Each -.\"thread will issue a single 512 byte read command. Results are printed to -.\"stdout when 30 seconds expires. +.\"Run a performance test on nvme0ns1 using 32 kernel threads for 30 seconds. +.\"Each thread will issue a single 512 byte read command. +.\"Results are printed to stdout when 30 seconds expires. .\".Pp .\".Dl nvmectl reset nvme0 .\".Pp @@ -109,9 +158,20 @@ Display a human-readable summary of the Log pages defined by the NVMe specification include Error Information Log (ID=1), SMART/Health Information Log (ID=2), and Firmware Slot Log (ID=3). .Pp +.Dl nvmectl logpage -p 0xc1 -v wdc nvme0 +.Pp +Display a human-readable summary of the nvme0's wdc-specific advanced +SMART data. +.Pp .Dl nvmectl logpage -p 1 -x nvme0 .Pp Display a hexadecimal dump of the nvme0 controller's Error Information Log. +.Pp +.Dl nvmectl logpage -p 0xcb -b nvme0 > /tmp/page-cb.bin +.Pp +Print the contents of vendor specific page 0xcb as binary data on +standard out. +Redirect it to a temporary file. .\".Pp .\".Dl nvmectl firmware -s 2 -f /tmp/nvme_firmware nvme0 .\".Pp @@ -138,6 +198,9 @@ Set the current power mode. .Dl nvmectl power nvme0 .Pp Get the current power mode. +.Sh HISTORY +The nvmecontrol utility appeared in +.Fx 9.2 . .Sh AUTHORS .An -nosplit nvmecontrol was developed by Intel and originally written by Index: src/sbin/nvmectl/nvmectl.c diff -u src/sbin/nvmectl/nvmectl.c:1.2 src/sbin/nvmectl/nvmectl.c:1.3 --- src/sbin/nvmectl/nvmectl.c:1.2 Sat Jun 4 20:59:49 2016 +++ src/sbin/nvmectl/nvmectl.c Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: nvmectl.c,v 1.2 2016/06/04 20:59:49 joerg Exp $ */ +/* $NetBSD: nvmectl.c,v 1.3 2017/04/29 00:06:40 nonaka Exp $ */ /*- * Copyright (C) 2012-2013 Intel Corporation @@ -28,9 +28,9 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: nvmectl.c,v 1.2 2016/06/04 20:59:49 joerg Exp $"); +__RCSID("$NetBSD: nvmectl.c,v 1.3 2017/04/29 00:06:40 nonaka Exp $"); #if 0 -__FBSDID("$FreeBSD: head/sbin/nvmecontrol/nvmecontrol.c 295087 2016-01-30 22:48:06Z imp $"); +__FBSDID("$FreeBSD: head/sbin/nvmecontrol/nvmecontrol.c 314229 2017-02-25 00:09:12Z imp $"); #endif #endif @@ -52,13 +52,7 @@ __FBSDID("$FreeBSD: head/sbin/nvmecontro #include "nvmectl.h" -typedef void (*nvme_fn_t)(int argc, char *argv[]); - -static struct nvme_function { - const char *name; - nvme_fn_t fn; - const char *usage; -} funcs[] = { +static struct nvme_function funcs[] = { {"devlist", devlist, DEVLIST_USAGE}, {"identify", identify, IDENTIFY_USAGE}, #ifdef PERFTEST_USAGE @@ -72,15 +66,14 @@ static struct nvme_function { {"firmware", firmware, FIRMWARE_USAGE}, #endif {"power", power, POWER_USAGE}, + {"wdc", wdc, WDC_USAGE}, {NULL, NULL, NULL}, }; -__dead static void -usage(void) +void +gen_usage(struct nvme_function *f) { - struct nvme_function *f; - f = funcs; fprintf(stderr, "usage:\n"); while (f->name != NULL) { fprintf(stderr, "%s", f->usage); @@ -89,6 +82,26 @@ usage(void) exit(1); } +void +dispatch(int argc, char *argv[], struct nvme_function *tbl) +{ + struct nvme_function *f = tbl; + + if (argv[1] == NULL) { + gen_usage(tbl); + return; + } + + while (f->name != NULL) { + if (strcmp(argv[1], f->name) == 0) + f->fn(argc-1, &argv[1]); + f++; + } + + fprintf(stderr, "Unknown command: %s\n", argv[1]); + gen_usage(tbl); +} + static void print_bytes(void *data, uint32_t length) { @@ -269,19 +282,11 @@ nvme_strvis(u_char *dst, int dlen, const int main(int argc, char *argv[]) { - struct nvme_function *f; if (argc < 2) - usage(); - - f = funcs; - while (f->name != NULL) { - if (strcmp(argv[1], f->name) == 0) - f->fn(argc-1, &argv[1]); - f++; - } + gen_usage(funcs); - usage(); + dispatch(argc, argv, funcs); return (0); } Index: src/sbin/nvmectl/nvmectl.h diff -u src/sbin/nvmectl/nvmectl.h:1.2 src/sbin/nvmectl/nvmectl.h:1.3 --- src/sbin/nvmectl/nvmectl.h:1.2 Sat Jun 4 20:59:49 2016 +++ src/sbin/nvmectl/nvmectl.h Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: nvmectl.h,v 1.2 2016/06/04 20:59:49 joerg Exp $ */ +/* $NetBSD: nvmectl.h,v 1.3 2017/04/29 00:06:40 nonaka Exp $ */ /*- * Copyright (C) 2012-2013 Intel Corporation @@ -25,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: head/sbin/nvmecontrol/nvmecontrol.h 295087 2016-01-30 22:48:06Z imp $ + * $FreeBSD: head/sbin/nvmecontrol/nvmecontrol.h 314230 2017-02-25 00:09:16Z imp $ */ #ifndef __NVMECTL_H__ @@ -36,6 +36,14 @@ #include <dev/ic/nvmeio.h> #include "nvme.h" +typedef void (*nvme_fn_t)(int argc, char *argv[]); + +struct nvme_function { + const char *name; + nvme_fn_t fn; + const char *usage; +}; + #define NVME_CTRLR_PREFIX "nvme" #define NVME_NS_PREFIX "ns" @@ -47,9 +55,9 @@ #if 0 #define PERFTEST_USAGE \ -" nvmectl perftest <-n num_threads> <-o read|write>\n" \ +" nvmectl perftest <-n num_threads> <-o read|write>\n" \ " <-s size_in_bytes> <-t time_in_seconds>\n" \ -" <-i intr|wait> [-f refthread] [-p]\n" \ +" <-i intr|wait> [-f refthread] [-p]\n" \ " <namespace id>\n" #endif @@ -59,7 +67,8 @@ #endif #define LOGPAGE_USAGE \ -" nvmectl logpage <-p page_id> [-x] <controller id|namespace id>\n" \ +" nvmectl logpage <-p page_id> [-b] [-v vendor] [-x] " \ + "<controller id|namespace id>\n" #if 0 #define FIRMWARE_USAGE \ @@ -69,19 +78,23 @@ #define POWER_USAGE \ " nvmectl power [-l] [-p new-state [-w workload-hint]] <controller id>\n" +#define WDC_USAGE \ +" nvmecontrol wdc (cap-diag|drive-log|get-crash-dump|purge|purge-montior)\n" + void devlist(int, char *[]) __dead; void identify(int, char *[]) __dead; #ifdef PERFTEST_USAGE -void perftest(int, char *[]); +void perftest(int, char *[]) __dead; #endif #ifdef RESET_USAGE -void reset(int, char *[]); +void reset(int, char *[]) __dead; #endif void logpage(int, char *[]) __dead; #ifdef FIRMWARE_USAGE -void firmware(int, char *[]); +void firmware(int, char *[]) __dead; #endif void power(int, char *[]) __dead; +void wdc(int, char *[]) __dead; int open_dev(const char *, int *, int, int); void parse_ns_str(const char *, char *, int *); @@ -89,6 +102,8 @@ void read_controller_data(int, struct nv void read_namespace_data(int, int, struct nvm_identify_namespace *); void print_hex(void *, uint32_t); void read_logpage(int, uint8_t, int, void *, uint32_t); +void gen_usage(struct nvme_function *) __dead; +void dispatch(int argc, char *argv[], struct nvme_function *f); void nvme_strvis(uint8_t *, int, const uint8_t *, int); #endif /* __NVMECTL_H__ */ Index: src/sbin/nvmectl/firmware.c diff -u src/sbin/nvmectl/firmware.c:1.1 src/sbin/nvmectl/firmware.c:1.2 --- src/sbin/nvmectl/firmware.c:1.1 Sat Jun 4 16:29:35 2016 +++ src/sbin/nvmectl/firmware.c Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: firmware.c,v 1.1 2016/06/04 16:29:35 nonaka Exp $ */ +/* $NetBSD: firmware.c,v 1.2 2017/04/29 00:06:40 nonaka Exp $ */ /*- * Copyright (c) 2013 EMC Corp. @@ -31,9 +31,9 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: firmware.c,v 1.1 2016/06/04 16:29:35 nonaka Exp $"); +__RCSID("$NetBSD: firmware.c,v 1.2 2017/04/29 00:06:40 nonaka Exp $"); #if 0 -__FBSDID("$FreeBSD: head/sbin/nvmecontrol/firmware.c 258071 2013-11-12 21:14:19Z jimharris $"); +__FBSDID("$FreeBSD: head/sbin/nvmecontrol/firmware.c 313188 2017-02-04 05:52:50Z imp $"); #endif #endif @@ -121,7 +121,7 @@ update_firmware(int fd, uint8_t *payload off = 0; resid = payload_size; - if ((chunk = malloc(NVME_MAX_XFER_SIZE)) == NULL) + if ((chunk = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE)) == NULL) errx(1, "unable to malloc %d bytes", NVME_MAX_XFER_SIZE); while (resid > 0) { Index: src/sbin/nvmectl/nvme.h diff -u src/sbin/nvmectl/nvme.h:1.1 src/sbin/nvmectl/nvme.h:1.2 --- src/sbin/nvmectl/nvme.h:1.1 Sat Jun 4 16:29:35 2016 +++ src/sbin/nvmectl/nvme.h Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: nvme.h,v 1.1 2016/06/04 16:29:35 nonaka Exp $ */ +/* $NetBSD: nvme.h,v 1.2 2017/04/29 00:06:40 nonaka Exp $ */ /*- * Copyright (C) 2012-2013 Intel Corporation @@ -25,13 +25,14 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: head/sys/dev/nvme/nvme.h 296617 2016-03-10 17:13:10Z mav $ + * $FreeBSD: head/sys/dev/nvme/nvme.h 314888 2017-03-07 23:02:59Z imp $ */ #ifndef __NVME_H__ #define __NVME_H__ -#define NVME_MAX_XFER_SIZE MAXPHYS +/* Cap nvme to 1MB transfers driver explodes with larger sizes */ +#define NVME_MAX_XFER_SIZE (MAXPHYS < (1<<20) ? MAXPHYS : (1<<20)) /* Get/Set Features */ #define NVME_FEAT_ARBITRATION 0x01 @@ -60,6 +61,19 @@ #define NVME_LOG_CHANGED_NAMESPACE_LIST 0x04 #define NVME_LOG_COMMAND_EFFECTS_LOG 0x05 #define NVME_LOG_RESERVATION_NOTIFICATION 0x80 +/* + * The following are Intel Specific log pages, but they seem + * to be widely implemented. + */ +#define INTEL_LOG_READ_LAT_LOG 0xc1 +#define INTEL_LOG_WRITE_LAT_LOG 0xc2 +#define INTEL_LOG_TEMP_STATS 0xc5 +#define INTEL_LOG_ADD_SMART 0xca +#define INTEL_LOG_DRIVE_MKT_NAME 0xdd +/* + * HGST log page, with lots ofs sub pages. + */ +#define HGST_INFO_LOG 0xc1 /* Error Information Log (Log Identifier 01h) */ struct nvme_error_information_entry { @@ -101,9 +115,9 @@ struct nvme_health_information_page { uint64_t unsafe_shutdowns[2]; uint64_t media_errors[2]; uint64_t num_error_info_log_entries[2]; - uint32_t warning_composite_temperature_time; - uint32_t critical_composite_temperature_time; - uint16_t temperature_sensor[8]; + uint32_t warning_temp_time; + uint32_t error_temp_time; + uint16_t temp_sensor[8]; uint8_t reserved[296]; } __packed __aligned(4); @@ -126,6 +140,18 @@ struct nvme_firmware_page { uint8_t reserved[448]; } __packed __aligned(4); +struct intel_log_temp_stats { + uint64_t current; + uint64_t overtemp_flag_last; + uint64_t overtemp_flag_life; + uint64_t max_temp; + uint64_t min_temp; + uint64_t _rsvd[5]; + uint64_t max_oper_temp; + uint64_t min_oper_temp; + uint64_t est_offset; +} __packed __aligned(4); + /* Commands Supported and Effects (Log Identifier 05h) */ struct nvme_command_effeects_page { uint32_t acs[256]; Index: src/sbin/nvmectl/logpage.c diff -u src/sbin/nvmectl/logpage.c:1.3 src/sbin/nvmectl/logpage.c:1.4 --- src/sbin/nvmectl/logpage.c:1.3 Mon Feb 13 11:16:46 2017 +++ src/sbin/nvmectl/logpage.c Sat Apr 29 00:06:40 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: logpage.c,v 1.3 2017/02/13 11:16:46 nonaka Exp $ */ +/* $NetBSD: logpage.c,v 1.4 2017/04/29 00:06:40 nonaka Exp $ */ /*- * Copyright (c) 2013 EMC Corp. @@ -31,14 +31,15 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: logpage.c,v 1.3 2017/02/13 11:16:46 nonaka Exp $"); +__RCSID("$NetBSD: logpage.c,v 1.4 2017/04/29 00:06:40 nonaka Exp $"); #if 0 -__FBSDID("$FreeBSD: head/sbin/nvmecontrol/logpage.c 285796 2015-07-22 16:10:29Z jimharris $"); +__FBSDID("$FreeBSD: head/sbin/nvmecontrol/logpage.c 314230 2017-02-25 00:09:16Z imp $"); #endif #endif #include <sys/param.h> #include <sys/ioccom.h> +#include <sys/endian.h> #include <ctype.h> #include <err.h> @@ -58,6 +59,39 @@ __FBSDID("$FreeBSD: head/sbin/nvmecontro typedef void (*print_fn_t)(void *buf, uint32_t size); +struct kv_name { + uint32_t key; + const char *name; +}; + +static const char * +kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key) +{ + static char bad[32]; + size_t i; + + for (i = 0; i < kv_count; i++, kv++) + if (kv->key == key) + return kv->name; + snprintf(bad, sizeof(bad), "Attribute %#x", key); + return bad; +} + +static void +print_bin(void *data, uint32_t length) +{ + write(STDOUT_FILENO, data, length); +} + +/* "Missing" from endian.h */ +static __inline uint64_t +le48dec(const void *pp) +{ + uint8_t const *p = (uint8_t const *)pp; + + return (((uint64_t)le16dec(p + 4) << 32) | le32dec(p)); +} + static void * get_log_buffer(uint32_t size) { @@ -178,10 +212,17 @@ print_bignum(const char *title, uint64_t } static void +print_temp(uint16_t t) +{ + printf("%u K, %2.2f C, %3.2f F\n", t, (float)t - 273.15, + (float)t * 9 / 5 - 459.67); +} + +static void print_log_health(void *buf, uint32_t size __unused) { struct nvme_health_information_page *health = buf; - float composite_temperature = health->composite_temperature; + int i; printf("SMART/Health Information Log\n"); printf("============================\n"); @@ -203,10 +244,8 @@ print_log_health(void *buf, uint32_t siz printf(" Volatile memory backup: %d\n", (uint8_t)__SHIFTOUT(health->critical_warning, NVME_HEALTH_PAGE_CW_VOLATILE_MEMORY_BACKUP)); - printf("Temperature: %u K, %2.2f C, %3.2f F\n", - health->composite_temperature, - composite_temperature - (float)273.15, - (composite_temperature * (float)9/5) - (float)459.67); + printf("Temperature: "); + print_temp(health->composite_temperature); printf("Available spare: %u\n", health->available_spare); printf("Available spare threshold: %u\n", @@ -214,26 +253,28 @@ print_log_health(void *buf, uint32_t siz printf("Percentage used: %u\n", health->percentage_used); - print_bignum("Data units (512 byte) read:", - health->data_units_read, ""); - print_bignum("Data units (512 byte) written:", - health->data_units_written, ""); - print_bignum("Host read commands:", - health->host_read_commands, ""); - print_bignum("Host write commands:", - health->host_write_commands, ""); - print_bignum("Controller busy time (minutes):", - health->controller_busy_time, ""); - print_bignum("Power cycles:", - health->power_cycles, ""); - print_bignum("Power on hours:", - health->power_on_hours, ""); - print_bignum("Unsafe shutdowns:", - health->unsafe_shutdowns, ""); - print_bignum("Media errors:", - health->media_errors, ""); + print_bignum("Data units (512 byte) read:", health->data_units_read, ""); + print_bignum("Data units (512 byte) written:", health->data_units_written, + ""); + print_bignum("Host read commands:", health->host_read_commands, ""); + print_bignum("Host write commands:", health->host_write_commands, ""); + print_bignum("Controller busy time (minutes):", health->controller_busy_time, + ""); + print_bignum("Power cycles:", health->power_cycles, ""); + print_bignum("Power on hours:", health->power_on_hours, ""); + print_bignum("Unsafe shutdowns:", health->unsafe_shutdowns, ""); + print_bignum("Media errors:", health->media_errors, ""); print_bignum("No. error info log entries:", health->num_error_info_log_entries, ""); + + printf("Warning Temp Composite Time: %d\n", health->warning_temp_time); + printf("Error Temp Composite Time: %d\n", health->error_temp_time); + for (i = 0; i < 7; i++) { + if (health->temp_sensor[i] == 0) + continue; + printf("Temperature Sensor %d: ", i + 1); + print_temp(health->temp_sensor[i]); + } } static void @@ -265,14 +306,593 @@ print_log_firmware(void *buf, uint32_t s } } +/* + * Intel specific log pages from + * http://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/ssd-dc-p3700-spec.pdf + * + * Though the version as of this date has a typo for the size of log page 0xca, + * offset 147: it is only 1 byte, not 6. + */ +static void +print_intel_temp_stats(void *buf, uint32_t size __unused) +{ + struct intel_log_temp_stats *temp = buf; + + printf("Intel Temperature Log\n"); + printf("=====================\n"); + + printf("Current: "); + print_temp(temp->current); + printf("Overtemp Last Flags %#jx\n", + (uintmax_t)temp->overtemp_flag_last); + printf("Overtemp Lifetime Flags %#jx\n", + (uintmax_t)temp->overtemp_flag_life); + printf("Max Temperature "); + print_temp(temp->max_temp); + printf("Min Temperature "); + print_temp(temp->min_temp); + printf("Max Operating Temperature "); + print_temp(temp->max_oper_temp); + printf("Min Operating Temperature "); + print_temp(temp->min_oper_temp); + printf("Estimated Temperature Offset: %ju C/K\n", + (uintmax_t)temp->est_offset); +} + +/* + * Format from Table 22, section 5.7 IO Command Latency Statistics. + * Read and write stats pages have identical encoding. + */ +static void +print_intel_read_write_lat_log(void *buf, uint32_t size __unused) +{ + const char *walker = buf; + int i; + + printf("Major: %d\n", le16dec(walker + 0)); + printf("Minor: %d\n", le16dec(walker + 2)); + for (i = 0; i < 32; i++) + printf("%4dus-%4dus: %ju\n", i * 32, (i + 1) * 32, + (uintmax_t)le32dec(walker + 4 + i * 4)); + for (i = 1; i < 32; i++) + printf("%4dms-%4dms: %ju\n", i, i + 1, + (uintmax_t)le32dec(walker + 132 + i * 4)); + for (i = 1; i < 32; i++) + printf("%4dms-%4dms: %ju\n", i * 32, (i + 1) * 32, + (uintmax_t)le32dec(walker + 256 + i * 4)); +} + +static void +print_intel_read_lat_log(void *buf, uint32_t size) +{ + + printf("Intel Read Latency Log\n"); + printf("======================\n"); + print_intel_read_write_lat_log(buf, size); +} + +static void +print_intel_write_lat_log(void *buf, uint32_t size) +{ + + printf("Intel Write Latency Log\n"); + printf("=======================\n"); + print_intel_read_write_lat_log(buf, size); +} + +/* + * Table 19. 5.4 SMART Attributes. + * Samsung also implements this and some extra data not documented. + */ +static void +print_intel_add_smart(void *buf, uint32_t size __unused) +{ + uint8_t *walker = buf; + uint8_t *end = walker + 150; + const char *name; + uint64_t raw; + uint8_t normalized; + + static struct kv_name kv[] = { + { 0xab, "Program Fail Count" }, + { 0xac, "Erase Fail Count" }, + { 0xad, "Wear Leveling Count" }, + { 0xb8, "End to End Error Count" }, + { 0xc7, "CRC Error Count" }, + { 0xe2, "Timed: Media Wear" }, + { 0xe3, "Timed: Host Read %" }, + { 0xe4, "Timed: Elapsed Time" }, + { 0xea, "Thermal Throttle Status" }, + { 0xf0, "Retry Buffer Overflows" }, + { 0xf3, "PLL Lock Loss Count" }, + { 0xf4, "NAND Bytes Written" }, + { 0xf5, "Host Bytes Written" }, + }; + + printf("Additional SMART Data Log\n"); + printf("=========================\n"); + /* + * walker[0] = Key + * walker[1,2] = reserved + * walker[3] = Normalized Value + * walker[4] = reserved + * walker[5..10] = Little Endian Raw value + * (or other represenations) + * walker[11] = reserved + */ + while (walker < end) { + name = kv_lookup(kv, __arraycount(kv), *walker); + normalized = walker[3]; + raw = le48dec(walker + 5); + switch (*walker){ + case 0: + break; + case 0xad: + printf("%-32s: %3d min: %u max: %u ave: %u\n", name, + normalized, le16dec(walker + 5), le16dec(walker + 7), + le16dec(walker + 9)); + break; + case 0xe2: + printf("%-32s: %3d %.3f%%\n", name, normalized, raw / 1024.0); + break; + case 0xea: + printf("%-32s: %3d %d%% %d times\n", name, normalized, + walker[5], le32dec(walker+6)); + break; + default: + printf("%-32s: %3d %ju\n", name, normalized, (uintmax_t)raw); + break; + } + walker += 12; + } +} + +/* + * HGST's 0xc1 page. This is a grab bag of additional data. Please see + * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf + * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf + * Appendix A for details + */ + +typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); + +struct subpage_print { + uint16_t key; + subprint_fn_t fn; +}; + +static void print_hgst_info_write_errors(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_read_errors(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_verify_errors(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_self_test(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_background_scan(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_erase_errors(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_erase_counts(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_temp_history(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_ssd_perf(void *, uint16_t, uint8_t, uint32_t); +static void print_hgst_info_firmware_load(void *, uint16_t, uint8_t, uint32_t); + +static struct subpage_print hgst_subpage[] = { + { 0x02, print_hgst_info_write_errors }, + { 0x03, print_hgst_info_read_errors }, + { 0x05, print_hgst_info_verify_errors }, + { 0x10, print_hgst_info_self_test }, + { 0x15, print_hgst_info_background_scan }, + { 0x30, print_hgst_info_erase_errors }, + { 0x31, print_hgst_info_erase_counts }, + { 0x32, print_hgst_info_temp_history }, + { 0x37, print_hgst_info_ssd_perf }, + { 0x38, print_hgst_info_firmware_load }, +}; + +/* Print a subpage that is basically just key value pairs */ +static void +print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size, + const struct kv_name *kv, size_t kv_count) +{ + uint8_t *wsp, *esp; + uint16_t ptype; + uint8_t plen; + uint64_t param; + int i; + + wsp = buf; + esp = wsp + size; + while (wsp < esp) { + ptype = le16dec(wsp); + wsp += 2; + wsp++; /* Flags, just ignore */ + plen = *wsp++; + param = 0; + for (i = 0; i < plen; i++) + param |= (uint64_t)*wsp++ << (i * 8); + printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), + (uintmax_t)param); + } +} + +static void +print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, + uint32_t size) +{ + static const struct kv_name kv[] = { + { 0x0000, "Corrected Without Delay" }, + { 0x0001, "Corrected Maybe Delayed" }, + { 0x0002, "Re-Writes" }, + { 0x0003, "Errors Corrected" }, + { 0x0004, "Correct Algorithm Used" }, + { 0x0005, "Bytes Processed" }, + { 0x0006, "Uncorrected Errors" }, + { 0x8000, "Flash Write Commands" }, + { 0x8001, "HGST Special" }, + }; + + printf("Write Errors Subpage:\n"); + print_hgst_info_subpage_gen(buf, subtype, size, kv, __arraycount(kv)); +} + +static void +print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, + uint32_t size) +{ + static const struct kv_name kv[] = { + { 0x0000, "Corrected Without Delay" }, + { 0x0001, "Corrected Maybe Delayed" }, + { 0x0002, "Re-Reads" }, + { 0x0003, "Errors Corrected" }, + { 0x0004, "Correct Algorithm Used" }, + { 0x0005, "Bytes Processed" }, + { 0x0006, "Uncorrected Errors" }, + { 0x8000, "Flash Read Commands" }, + { 0x8001, "XOR Recovered" }, + { 0x8002, "Total Corrected Bits" }, + }; + + printf("Read Errors Subpage:\n"); + print_hgst_info_subpage_gen(buf, subtype, size, kv, __arraycount(kv)); +} + +static void +print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, + uint32_t size) +{ + static const struct kv_name kv[] = { + { 0x0000, "Corrected Without Delay" }, + { 0x0001, "Corrected Maybe Delayed" }, + { 0x0002, "Re-Reads" }, + { 0x0003, "Errors Corrected" }, + { 0x0004, "Correct Algorithm Used" }, + { 0x0005, "Bytes Processed" }, + { 0x0006, "Uncorrected Errors" }, + { 0x8000, "Commands Processed" }, + }; + + printf("Verify Errors Subpage:\n"); + print_hgst_info_subpage_gen(buf, subtype, size, kv, __arraycount(kv)); +} + +static void +print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, + uint32_t size) +{ + size_t i; + uint8_t *walker = buf; + uint16_t code, hrs; + uint32_t lba; + + printf("Self Test Subpage:\n"); + for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ + code = le16dec(walker); + walker += 2; + walker++; /* Ignore fixed flags */ + if (*walker == 0) /* Last entry is zero length */ + break; + if (*walker++ != 0x10) { + printf("Bad length for self test report\n"); + return; + } + printf(" %-30s: %d\n", "Recent Test", code); + printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf); + printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7); + walker++; + printf(" %-28s: %#x\n", "Self-Test Number", *walker++); + hrs = le16dec(walker); + walker += 2; + lba = le32dec(walker); + walker += 4; + printf(" %-28s: %u\n", "Total Power On Hrs", hrs); + printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, + (uintmax_t)lba); + printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf); + printf(" %-28s: %#x\n", "Additional Sense Code", *walker++); + printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++); + printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++); + } +} + +static void +print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, + uint8_t res __unused, uint32_t size) +{ + uint8_t *walker = buf; + uint8_t status; + uint16_t code, nscan, progress; + uint32_t pom, nand; + + printf("Background Media Scan Subpage:\n"); + /* Decode the header */ + code = le16dec(walker); + walker += 2; + walker++; /* Ignore fixed flags */ + if (*walker++ != 0x10) { + printf("Bad length for background scan header\n"); + return; + } + if (code != 0) { + printf("Expceted code 0, found code %#x\n", code); + return; + } + pom = le32dec(walker); + walker += 4; + walker++; /* Reserved */ + status = *walker++; + nscan = le16dec(walker); + walker += 2; + progress = le16dec(walker); + walker += 2; + walker += 6; /* Reserved */ + printf(" %-30s: %d\n", "Power On Minutes", pom); + printf(" %-30s: %x (%s)\n", "BMS Status", status, + status == 0 ? "idle" : (status == 1 ? "active" : + (status == 8 ? "suspended" : "unknown"))); + printf(" %-30s: %d\n", "Number of BMS", nscan); + printf(" %-30s: %d\n", "Progress Current BMS", progress); + /* Report retirements */ + if (walker - (uint8_t *)buf != 20) { + printf("Coding error, offset not 20\n"); + return; + } + size -= 20; + printf(" %-30s: %d\n", "BMS retirements", size / 0x18); + while (size > 0) { + code = le16dec(walker); + walker += 2; + walker++; + if (*walker++ != 0x14) { + printf("Bad length parameter\n"); + return; + } + pom = le32dec(walker); + walker += 4; + /* + * Spec sheet says the following are hard coded, if true, just + * print the NAND retirement. + */ + if (walker[0] == 0x41 && + walker[1] == 0x0b && + walker[2] == 0x01 && + walker[3] == 0x00 && + walker[4] == 0x00 && + walker[5] == 0x00 && + walker[6] == 0x00 && + walker[7] == 0x00) { + walker += 8; + walker += 4; /* Skip reserved */ + nand = le32dec(walker); + walker += 4; + printf(" %-30s: %d\n", "Retirement number", code); + printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); + } else { + printf("Parameter %#x entry corrupt\n", code); + walker += 16; + } + } +} + +static void +print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, + uint8_t res __unused, uint32_t size) +{ + static const struct kv_name kv[] = { + { 0x0000, "Corrected Without Delay" }, + { 0x0001, "Corrected Maybe Delayed" }, + { 0x0002, "Re-Erase" }, + { 0x0003, "Errors Corrected" }, + { 0x0004, "Correct Algorithm Used" }, + { 0x0005, "Bytes Processed" }, + { 0x0006, "Uncorrected Errors" }, + { 0x8000, "Flash Erase Commands" }, + { 0x8001, "Mfg Defect Count" }, + { 0x8002, "Grown Defect Count" }, + { 0x8003, "Erase Count -- User" }, + { 0x8004, "Erase Count -- System" }, + }; + + printf("Erase Errors Subpage:\n"); + print_hgst_info_subpage_gen(buf, subtype, size, kv, __arraycount(kv)); +} + +static void +print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, + uint32_t size) +{ + /* My drive doesn't export this -- so not coding up */ + printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); +} + +static void +print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, + uint8_t res __unused, uint32_t size __unused) +{ + uint8_t *walker = buf; + uint32_t min; + + printf("Temperature History:\n"); + printf(" %-30s: %d C\n", "Current Temperature", *walker++); + printf(" %-30s: %d C\n", "Reference Temperature", *walker++); + printf(" %-30s: %d C\n", "Maximum Temperature", *walker++); + printf(" %-30s: %d C\n", "Minimum Temperature", *walker++); + min = le32dec(walker); + walker += 4; + printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60); + min = le32dec(walker); + walker += 4; + printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, + min % 60); + min = le32dec(walker); + walker += 4; + printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60); +} + +static void +print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, + uint32_t size __unused) +{ + uint8_t *walker = buf; + uint64_t val; + + printf("SSD Performance Subpage Type %d:\n", res); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Read Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Read Blocks", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Write Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Write Blocks", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "NAND Read Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "NAND Read Blocks", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "NAND Write Commands", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "NAND Write Blocks", val); + val = le64dec(walker); + walker += 8; + printf(" %-30s: %ju\n", "NAND Read Before Writes", val); +} + +static void +print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, + uint8_t res __unused, uint32_t size __unused) +{ + uint8_t *walker = buf; + + printf("Firmware Load Subpage:\n"); + printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); +} + +static void +kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, + struct subpage_print *sp, size_t nsp) +{ + size_t i; + + for (i = 0; i < nsp; i++, sp++) { + if (sp->key == subtype) { + sp->fn(buf, subtype, res, size); + return; + } + } + printf("No handler for page type %x\n", subtype); +} + +static void +print_hgst_info_log(void *buf, uint32_t size __unused) +{ + uint8_t *walker, *end, *subpage; + int pages __unused; + uint16_t len; + uint8_t subtype, res; + + printf("HGST Extra Info Log\n"); + printf("===================\n"); + + walker = buf; + pages = *walker++; + walker++; + len = le16dec(walker); + walker += 2; + end = walker + len; /* Length is exclusive of this header */ + + while (walker < end) { + subpage = walker + 4; + subtype = *walker++ & 0x3f; /* subtype */ + res = *walker++; /* Reserved */ + len = le16dec(walker); + walker += len + 2; /* Length, not incl header */ + if (walker > end) { + printf("Ooops! Off the end of the list\n"); + break; + } + kv_indirect(subpage, subtype, res, len, hgst_subpage, + __arraycount(hgst_subpage)); + } +} + +/* + * Table of log page printer / sizing. + * + * This includes Intel specific pages that are widely implemented. + * Make sure you keep all the pages of one vendor together so -v help + * lists all the vendors pages. + */ static struct logpage_function { uint8_t log_page; - print_fn_t fn; + const char *vendor; + const char *name; + print_fn_t print_fn; + size_t size; } logfuncs[] = { - {NVME_LOG_ERROR, print_log_error }, - {NVME_LOG_HEALTH_INFORMATION, print_log_health }, - {NVME_LOG_FIRMWARE_SLOT, print_log_firmware }, - {0, NULL }, + {NVME_LOG_ERROR, NULL, "Drive Error Log", + print_log_error, 0}, + {NVME_LOG_HEALTH_INFORMATION, NULL, "Health/SMART Data", + print_log_health, sizeof(struct nvme_health_information_page)}, + {NVME_LOG_FIRMWARE_SLOT, NULL, "Firmware Information", + print_log_firmware, sizeof(struct nvme_firmware_page)}, + {HGST_INFO_LOG, "hgst", "Detailed Health/SMART", + print_hgst_info_log, DEFAULT_SIZE}, + {HGST_INFO_LOG, "wds", "Detailed Health/SMART", + print_hgst_info_log, DEFAULT_SIZE}, + {INTEL_LOG_TEMP_STATS, "intel", "Temperature Stats", + print_intel_temp_stats, sizeof(struct intel_log_temp_stats)}, + {INTEL_LOG_READ_LAT_LOG, "intel", "Read Latencies", + print_intel_read_lat_log, DEFAULT_SIZE}, + {INTEL_LOG_WRITE_LAT_LOG, "intel", "Write Latencies", + print_intel_write_lat_log, DEFAULT_SIZE}, + {INTEL_LOG_ADD_SMART, "intel", "Extra Health/SMART Data", + print_intel_add_smart, DEFAULT_SIZE}, + {INTEL_LOG_ADD_SMART, "samsung", "Extra Health/SMART Data", + print_intel_add_smart, DEFAULT_SIZE}, + + {0, NULL, NULL, NULL, 0}, }; __dead static void @@ -283,24 +903,48 @@ logpage_usage(void) exit(1); } +__dead static void +logpage_help(void) +{ + struct logpage_function *f; + const char *v; + + fprintf(stderr, "\n"); + fprintf(stderr, "%-8s %-10s %s\n", "Page", "Vendor","Page Name"); + fprintf(stderr, "-------- ---------- ----------\n"); + for (f = logfuncs; f->log_page > 0; f++) { + v = f->vendor == NULL ? "-" : f->vendor; + fprintf(stderr, "0x%02x %-10s %s\n", f->log_page, v, f->name); + } + + exit(1); +} + void logpage(int argc, char *argv[]) { int fd, nsid; int log_page = 0, pageflag = false; - int hexflag = false, ns_specified; + int binflag = false, hexflag = false, ns_specified; int ch; char *p; char cname[64]; uint32_t size; void *buf; + const char *vendor = NULL; struct logpage_function *f; struct nvm_identify_controller cdata; print_fn_t print_fn; - while ((ch = getopt(argc, argv, "p:x")) != -1) { + while ((ch = getopt(argc, argv, "bp:xv:")) != -1) { switch (ch) { + case 'b': + binflag = true; + break; case 'p': + if (strcmp(optarg, "help") == 0) + logpage_help(); + /* TODO: Add human-readable ASCII page IDs */ log_page = strtol(optarg, &p, 0); if (p != NULL && *p != '\0') { @@ -308,20 +952,17 @@ logpage(int argc, char *argv[]) "\"%s\" not valid log page id.\n", optarg); logpage_usage(); - /* TODO: Define valid log page id ranges in nvme.h? */ - } else if (log_page == 0 || - (log_page >= 0x04 && log_page <= 0x7F) || - (log_page >= 0x80 && log_page <= 0xBF)) { - fprintf(stderr, - "\"%s\" not valid log page id.\n", - optarg); - logpage_usage(); } pageflag = true; break; case 'x': hexflag = true; break; + case 'v': + if (strcmp(optarg, "help") == 0) + logpage_help(); + vendor = optarg; + break; } } @@ -362,39 +1003,35 @@ logpage(int argc, char *argv[]) } print_fn = print_hex; - if (!hexflag) { + size = DEFAULT_SIZE; + if (binflag) + print_fn = print_bin; + if (!binflag && !hexflag) { /* - * See if there is a pretty print function for the - * specified log page. If one isn't found, we - * just revert to the default (print_hex). + * See if there is a pretty print function for the specified log + * page. If one isn't found, we just revert to the default + * (print_hex). If there was a vendor specified bt the user, and + * the page is vendor specific, don't match the print function + * unless the vendors match. */ - f = logfuncs; - while (f->log_page > 0) { - if (log_page == f->log_page) { - print_fn = f->fn; - break; - } - f++; + for (f = logfuncs; f->log_page > 0; f++) { + if (f->vendor != NULL && vendor != NULL && + strcmp(f->vendor, vendor) != 0) + continue; + if (log_page != f->log_page) + continue; + print_fn = f->print_fn; + size = f->size; + break; } } - /* Read the log page */ - switch (log_page) { - case NVME_LOG_ERROR: + if (log_page == NVME_LOG_ERROR) { size = sizeof(struct nvme_error_information_entry); size *= (cdata.elpe + 1); - break; - case NVME_LOG_HEALTH_INFORMATION: - size = sizeof(struct nvme_health_information_page); - break; - case NVME_LOG_FIRMWARE_SLOT: - size = sizeof(struct nvme_firmware_page); - break; - default: - size = DEFAULT_SIZE; - break; } + /* Read the log page */ buf = get_log_buffer(size); read_logpage(fd, log_page, nsid, buf, size); print_fn(buf, size);