Index: sbin/nvmecontrol/Makefile =================================================================== --- sbin/nvmecontrol/Makefile +++ sbin/nvmecontrol/Makefile @@ -3,7 +3,8 @@ PACKAGE=runtime PROG= nvmecontrol SRCS= nvmecontrol.c devlist.c firmware.c format.c identify.c identify_ext.c logpage.c \ - perftest.c reset.c ns.c nvme_util.c power.c nc_util.c wdc.c + perftest.c reset.c ns.c nvme_util.c power.c nc_util.c +SRCS+= wdc.c intel.c MAN= nvmecontrol.8 .PATH: ${SRCTOP}/sys/dev/nvme Index: sbin/nvmecontrol/devlist.c =================================================================== --- sbin/nvmecontrol/devlist.c +++ sbin/nvmecontrol/devlist.c @@ -43,13 +43,8 @@ #include "nvmecontrol.h" -static void -devlist_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, DEVLIST_USAGE); - exit(1); -} +#define DEVLIST_USAGE \ + "devlist\n" static inline uint32_t ns_get_sector_size(struct nvme_namespace_data *nsdata) @@ -64,8 +59,8 @@ return (1 << lbads); } -void -devlist(int argc, char *argv[]) +static void +devlist(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_controller_data cdata; struct nvme_namespace_data nsdata; @@ -77,7 +72,7 @@ while ((ch = getopt(argc, argv, "")) != -1) { switch ((char)ch) { default: - devlist_usage(); + usage(nf); } } @@ -124,3 +119,5 @@ exit(1); } + +NVME_COMMAND(top, devlist, devlist, DEVLIST_USAGE); Index: sbin/nvmecontrol/firmware.c =================================================================== --- sbin/nvmecontrol/firmware.c +++ sbin/nvmecontrol/firmware.c @@ -50,6 +50,9 @@ #include "nvmecontrol.h" +#define FIRMWARE_USAGE \ + "firmware [-s slot] [-f path_to_firmware] [-a] \n" + static int slot_has_valid_firmware(int fd, int slot) { @@ -171,15 +174,7 @@ } static void -firmware_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, FIRMWARE_USAGE); - exit(1); -} - -void -firmware(int argc, char *argv[]) +firmware(struct nvme_function *nf, int argc, char *argv[]) { int fd = -1, slot = 0; int a_flag, s_flag, f_flag; @@ -206,18 +201,18 @@ fprintf(stderr, "\"%s\" not valid slot.\n", optarg); - firmware_usage(); + usage(nf); } else if (slot == 0) { fprintf(stderr, "0 is not a valid slot number. " "Slot numbers start at 1.\n"); - firmware_usage(); + usage(nf); } else if (slot > 7) { fprintf(stderr, "Slot number %s specified which is " "greater than max allowed slot number of " "7.\n", optarg); - firmware_usage(); + usage(nf); } s_flag = true; break; @@ -230,20 +225,20 @@ /* Check that a controller (and not a namespace) was specified. */ if (optind >= argc || strstr(argv[optind], NVME_NS_PREFIX) != NULL) - firmware_usage(); + usage(nf); if (!f_flag && !a_flag) { fprintf(stderr, "Neither a replace ([-f path_to_firmware]) nor " "activate ([-a]) firmware image action\n" "was specified.\n"); - firmware_usage(); + usage(nf); } if (!f_flag && a_flag && slot == 0) { fprintf(stderr, "Slot number to activate not specified.\n"); - firmware_usage(); + usage(nf); } controller = argv[optind]; @@ -338,3 +333,5 @@ close(fd); exit(0); } + +NVME_COMMAND(top, firmware, firmware, FIRMWARE_USAGE); Index: sbin/nvmecontrol/format.c =================================================================== --- sbin/nvmecontrol/format.c +++ sbin/nvmecontrol/format.c @@ -43,16 +43,11 @@ #include "nvmecontrol.h" -static void -format_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, FORMAT_USAGE); - exit(1); -} +#define FORMAT_USAGE \ + "format [-f fmt] [-m mset] [-p pi] [-l pil] [-E] [-C] \n" -void -format(int argc, char *argv[]) +static void +format(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_controller_data cd; struct nvme_namespace_data nsd; @@ -64,7 +59,7 @@ int lbaf = -1, mset = -1, pi = -1, pil = -1, ses = 0; if (argc < 2) - format_usage(); + usage(nf); while ((ch = getopt(argc, argv, "f:m:p:l:EC")) != -1) { switch ((char)ch) { @@ -91,13 +86,13 @@ ses = 2; break; default: - format_usage(); + usage(nf); } } /* Check that a controller or namespace was specified. */ if (optind >= argc) - format_usage(); + usage(nf); target = argv[optind]; /* @@ -185,3 +180,5 @@ close(fd); exit(0); } + +NVME_COMMAND(top, format, format, FORMAT_USAGE); Index: sbin/nvmecontrol/identify.c =================================================================== --- sbin/nvmecontrol/identify.c +++ sbin/nvmecontrol/identify.c @@ -43,6 +43,9 @@ #include "nvmecontrol.h" #include "nvmecontrol_ext.h" +#define IDENTIFY_USAGE \ + "identify [-x [-v]] \n" + static void print_namespace(struct nvme_namespace_data *nsdata) { @@ -147,15 +150,7 @@ } static void -identify_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, IDENTIFY_USAGE); - exit(1); -} - -static void -identify_ctrlr(int argc, char *argv[]) +identify_ctrlr(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_controller_data cdata; int ch, fd, hexflag = 0, hexlength; @@ -170,13 +165,13 @@ hexflag = 1; break; default: - identify_usage(); + usage(nf); } } /* Check that a controller was specified. */ if (optind >= argc) - identify_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); read_controller_data(fd, &cdata); @@ -194,7 +189,7 @@ if (verboseflag == 1) { fprintf(stderr, "-v not currently supported without -x\n"); - identify_usage(); + usage(nf); } nvme_print_controller(&cdata); @@ -202,7 +197,7 @@ } static void -identify_ns(int argc, char *argv[]) +identify_ns(struct nvme_function *nf,int argc, char *argv[]) { struct nvme_namespace_data nsdata; char path[64]; @@ -219,13 +214,13 @@ hexflag = 1; break; default: - identify_usage(); + usage(nf); } } /* Check that a namespace was specified. */ if (optind >= argc) - identify_usage(); + usage(nf); /* * Check if the specified device node exists before continuing. @@ -258,26 +253,26 @@ if (verboseflag == 1) { fprintf(stderr, "-v not currently supported without -x\n"); - identify_usage(); + usage(nf); } print_namespace(&nsdata); exit(0); } -void -identify(int argc, char *argv[]) +static void +identify(struct nvme_function *nf, int argc, char *argv[]) { char *target; if (argc < 2) - identify_usage(); + usage(nf); while (getopt(argc, argv, "vx") != -1) ; /* Check that a controller or namespace was specified. */ if (optind >= argc) - identify_usage(); + usage(nf); target = argv[optind]; @@ -289,7 +284,9 @@ * otherwise, consider it a controller. */ if (strstr(target, NVME_NS_PREFIX) == NULL) - identify_ctrlr(argc, argv); + identify_ctrlr(nf, argc, argv); else - identify_ns(argc, argv); + identify_ns(nf, argc, argv); } + +NVME_COMMAND(top, identify, identify, IDENTIFY_USAGE); Index: sbin/nvmecontrol/intel.c =================================================================== --- /dev/null +++ sbin/nvmecontrol/intel.c @@ -0,0 +1,198 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2013 EMC Corp. + * All rights reserved. + * + * Copyright (C) 2012-2013 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvmecontrol.h" + +/* + * 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(const struct nvme_controller_data *cdata __unused, 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(const struct nvme_controller_data *cdata __unused, 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(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size) +{ + + printf("Intel Read Latency Log\n"); + printf("======================\n"); + print_intel_read_write_lat_log(cdata, buf, size); +} + +static void +print_intel_write_lat_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size) +{ + + printf("Intel Write Latency Log\n"); + printf("=======================\n"); + print_intel_read_write_lat_log(cdata, buf, size); +} + +/* + * Table 19. 5.4 SMART Attributes. Samsung also implements this and some extra data not documented. + */ +static void +print_intel_add_smart(const struct nvme_controller_data *cdata __unused, 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, nitems(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; + } +} + +NVME_LOGPAGE(intel_temp, + INTEL_LOG_TEMP_STATS, "intel", "Temperature Stats", + print_intel_temp_stats, sizeof(struct intel_log_temp_stats)); +NVME_LOGPAGE(intel_rlat, + INTEL_LOG_READ_LAT_LOG, "intel", "Read Latencies", + print_intel_read_lat_log, DEFAULT_SIZE); +NVME_LOGPAGE(intel_wlat, + INTEL_LOG_WRITE_LAT_LOG, "intel", "Write Latencies", + print_intel_write_lat_log, DEFAULT_SIZE); +NVME_LOGPAGE(intel_smart, + INTEL_LOG_ADD_SMART, "intel", "Extra Health/SMART Data", + print_intel_add_smart, DEFAULT_SIZE); +NVME_LOGPAGE(samsung_smart, /* NOTE, this will be deleted before 13.0 */ + INTEL_LOG_ADD_SMART, "samsung", "Extra Health/SMART Data", + print_intel_add_smart, DEFAULT_SIZE); Index: sbin/nvmecontrol/logpage.c =================================================================== --- sbin/nvmecontrol/logpage.c +++ sbin/nvmecontrol/logpage.c @@ -48,18 +48,14 @@ #include "nvmecontrol.h" -#define DEFAULT_SIZE (4096) -#define MAX_FW_SLOTS (7) +SET_DECLARE(logpage, struct logpage_function); -typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size); +#define LOGPAGE_USAGE \ + "logpage <-p page_id> [-b] [-v vendor] [-x] \n" \ -struct kv_name -{ - uint32_t key; - const char *name; -}; +#define MAX_FW_SLOTS (7) -static const char * +const char * kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key) { static char bad[32]; @@ -195,7 +191,7 @@ } } -static void +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); @@ -311,605 +307,41 @@ } } -/* - * 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(const struct nvme_controller_data *cdata __unused, 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(const struct nvme_controller_data *cdata __unused, 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(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size) -{ - - printf("Intel Read Latency Log\n"); - printf("======================\n"); - print_intel_read_write_lat_log(cdata, buf, size); -} - -static void -print_intel_write_lat_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size) -{ - - printf("Intel Write Latency Log\n"); - printf("=======================\n"); - print_intel_read_write_lat_log(cdata, buf, size); -} - -/* - * Table 19. 5.4 SMART Attributes. Samsung also implements this and some extra data not documented. - */ -static void -print_intel_add_smart(const struct nvme_controller_data *cdata __unused, 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, nitems(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 *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); -static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); - -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 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, nitems(kv)); -} - -static void -print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) -{ - static 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, nitems(kv)); -} - -static void -print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) -{ - static 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, nitems(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 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, nitems(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(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) -{ - uint8_t *walker, *end, *subpage; - int pages; - 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, nitems(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; - const char *vendor; - const char *name; - print_fn_t print_fn; - size_t size; -} logfuncs[] = { - {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, "wdc", "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}, -}; - -static void -logpage_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, LOGPAGE_USAGE); - exit(1); -} +NVME_LOGPAGE(error, + NVME_LOG_ERROR, NULL, "Drive Error Log", + print_log_error, 0); +NVME_LOGPAGE(healt, + NVME_LOG_HEALTH_INFORMATION, NULL, "Health/SMART Data", + print_log_health, sizeof(struct nvme_health_information_page)); +NVME_LOGPAGE(fw, + NVME_LOG_FIRMWARE_SLOT, NULL, "Firmware Information", + print_log_firmware, sizeof(struct nvme_firmware_page)); static void logpage_help(void) { - struct logpage_function *f; + 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); + for (f = SET_BEGIN(logpage); f < SET_LIMIT(logpage); 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[]) +static void +logpage(struct nvme_function *nf, int argc, char *argv[]) { int fd; int log_page = 0, pageflag = false; @@ -920,7 +352,7 @@ uint32_t nsid, size; void *buf; const char *vendor = NULL; - struct logpage_function *f; + struct logpage_function **f; struct nvme_controller_data cdata; print_fn_t print_fn; uint8_t ns_smart; @@ -940,7 +372,7 @@ fprintf(stderr, "\"%s\" not valid log page id.\n", optarg); - logpage_usage(); + usage(nf); } pageflag = true; break; @@ -957,12 +389,12 @@ if (!pageflag) { printf("Missing page_id (-p).\n"); - logpage_usage(); + usage(nf); } /* Check that a controller and/or namespace was specified. */ if (optind >= argc) - logpage_usage(); + usage(nf); if (strstr(argv[optind], NVME_NS_PREFIX) != NULL) { ns_specified = true; @@ -1006,14 +438,14 @@ * the page is vendor specific, don't match the print function * unless the vendors match. */ - for (f = logfuncs; f->log_page > 0; f++) { - if (f->vendor != NULL && vendor != NULL && - strcmp(f->vendor, vendor) != 0) + for (f = SET_BEGIN(logpage); f < SET_LIMIT(logpage); f++) { + if ((*f)->vendor != NULL && vendor != NULL && + strcmp((*f)->vendor, vendor) != 0) continue; - if (log_page != f->log_page) + if (log_page != (*f)->log_page) continue; - print_fn = f->print_fn; - size = f->size; + print_fn = (*f)->print_fn; + size = (*f)->size; break; } } @@ -1031,3 +463,5 @@ close(fd); exit(0); } + +NVME_COMMAND(top, logpage, logpage, LOGPAGE_USAGE); Index: sbin/nvmecontrol/ns.c =================================================================== --- sbin/nvmecontrol/ns.c +++ sbin/nvmecontrol/ns.c @@ -41,64 +41,34 @@ #include "nvmecontrol.h" +SET_DECLARE(ns, struct nvme_function); + +#define NS_USAGE \ + "ns (create|delete|attach|detach)\n" + /* handles NVME_OPC_NAMESPACE_MANAGEMENT and ATTACHMENT admin cmds */ #define NSCREATE_USAGE \ -" nvmecontrol ns create -s size [-c cap] [-f fmt] [-m mset] [-n nmic] [-p pi] [-l pil] nvmeN\n" + "ns create -s size [-c cap] [-f fmt] [-m mset] [-n nmic] [-p pi] [-l pil] nvmeN\n" #define NSDELETE_USAGE \ -" nvmecontrol ns delete -n nsid nvmeN\n" + "ns delete -n nsid nvmeN\n" #define NSATTACH_USAGE \ -" nvmecontrol ns attach -n nsid [-c ctrlrid] nvmeN \n" + "ns attach -n nsid [-c ctrlrid] nvmeN \n" #define NSDETACH_USAGE \ -" nvmecontrol ns detach -n nsid [-c ctrlrid] nvmeN\n" - -void nscreate(int argc, char *argv[]); -void nsdelete(int argc, char *argv[]); -void nsattach(int argc, char *argv[]); -void nsdetach(int argc, char *argv[]); - -static struct nvme_function ns_funcs[] = { - {"create", nscreate, NSCREATE_USAGE}, - {"delete", nsdelete, NSDELETE_USAGE}, - {"attach", nsattach, NSATTACH_USAGE}, - {"detach", nsdetach, NSDETACH_USAGE}, - {NULL, NULL, NULL}, -}; - -static void -nscreate_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, NSCREATE_USAGE); - exit(1); -} - -static void -nsdelete_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, NSDELETE_USAGE); - exit(1); -} + "ns detach -n nsid [-c ctrlrid] nvmeN\n" -static void -nsattach_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, NSATTACH_USAGE); - exit(1); -} +void nscreate(struct nvme_function *nf, int argc, char *argv[]); +void nsdelete(struct nvme_function *nf, int argc, char *argv[]); +void nsattach(struct nvme_function *nf, int argc, char *argv[]); +void nsdetach(struct nvme_function *nf, int argc, char *argv[]); -static void -nsdetach_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, NSDETACH_USAGE); - exit(1); -} +NVME_COMMAND(ns, create, nscreate, NSCREATE_USAGE); +NVME_COMMAND(ns, delete, nsdelete, NSDELETE_USAGE); +NVME_COMMAND(ns, attach, nsattach, NSATTACH_USAGE); +NVME_COMMAND(ns, detach, nsdetach, NSDETACH_USAGE); struct ns_result_str { uint16_t res; @@ -140,7 +110,7 @@ * 0xb = Thin Provisioning Not supported */ void -nscreate(int argc, char *argv[]) +nscreate(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; @@ -149,7 +119,7 @@ int ch, fd, result, lbaf = 0, mset = 0, nmic = -1, pi = 0, pil = 0; if (optind >= argc) - nscreate_usage(); + usage(nf); while ((ch = getopt(argc, argv, "s:c:f:m:n:p:l:")) != -1) { switch (ch) { @@ -175,17 +145,17 @@ pil = strtol(optarg, NULL, 0); break; default: - nscreate_usage(); + usage(nf); } } if (optind >= argc) - nscreate_usage(); + usage(nf); if (cap == -1) cap = nsze; if (nsze == -1 || cap == -1) - nscreate_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); read_controller_data(fd, &cd); @@ -235,7 +205,7 @@ } void -nsdelete(int argc, char *argv[]) +nsdelete(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; @@ -243,7 +213,7 @@ char buf[2]; if (optind >= argc) - nsdelete_usage(); + usage(nf); while ((ch = getopt(argc, argv, "n:")) != -1) { switch ((char)ch) { @@ -251,12 +221,12 @@ nsid = strtol(optarg, (char **)NULL, 0); break; default: - nsdelete_usage(); + usage(nf); } } if (optind >= argc || nsid == -2) - nsdelete_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); read_controller_data(fd, &cd); @@ -302,7 +272,7 @@ * 0x2 Invalid Field can occur if ctrlrid d.n.e in system. */ void -nsattach(int argc, char *argv[]) +nsattach(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; @@ -311,7 +281,7 @@ uint16_t clist[2048]; if (optind >= argc) - nsattach_usage(); + usage(nf); while ((ch = getopt(argc, argv, "n:c:")) != -1) { switch (ch) { @@ -322,15 +292,15 @@ ctrlrid = strtol(optarg, (char **)NULL, 0); break; default: - nsattach_usage(); + usage(nf); } } if (optind >= argc) - nsattach_usage(); + usage(nf); if (nsid == -1 ) - nsattach_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); read_controller_data(fd, &cd); @@ -381,7 +351,7 @@ } void -nsdetach(int argc, char *argv[]) +nsdetach(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; @@ -390,7 +360,7 @@ uint16_t clist[2048]; if (optind >= argc) - nsdetach_usage(); + usage(nf); while ((ch = getopt(argc, argv, "n:c:")) != -1) { switch (ch) { @@ -401,15 +371,15 @@ ctrlrid = strtol(optarg, (char **)NULL, 0); break; default: - nsdetach_usage(); + usage(nf); } } if (optind >= argc) - nsdetach_usage(); + usage(nf); if (nsid == -1) - nsdetach_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); read_controller_data(fd, &cd); @@ -466,9 +436,11 @@ exit(0); } -void -ns(int argc, char *argv[]) +static void +ns(struct nvme_function *nf __unused, int argc, char *argv[]) { - dispatch(argc, argv, ns_funcs); + DISPATCH(argc, argv, ns); } + +NVME_COMMAND(top, ns, ns, NS_USAGE); Index: sbin/nvmecontrol/nvmecontrol.h =================================================================== --- sbin/nvmecontrol/nvmecontrol.h +++ sbin/nvmecontrol/nvmecontrol.h @@ -31,9 +31,11 @@ #ifndef __NVMECONTROL_H__ #define __NVMECONTROL_H__ +#include #include -typedef void (*nvme_fn_t)(int argc, char *argv[]); +struct nvme_function; +typedef void (*nvme_fn_t)(struct nvme_function *nf, int argc, char *argv[]); struct nvme_function { const char *name; @@ -41,52 +43,44 @@ const char *usage; }; -#define NVME_CTRLR_PREFIX "nvme" -#define NVME_NS_PREFIX "ns" - -#define DEVLIST_USAGE \ -" nvmecontrol devlist\n" - -#define IDENTIFY_USAGE \ -" nvmecontrol identify [-x [-v]] \n" - -#define PERFTEST_USAGE \ -" nvmecontrol perftest <-n num_threads> <-o read|write>\n" \ -" <-s size_in_bytes> <-t time_in_seconds>\n" \ -" <-i intr|wait> [-f refthread] [-p]\n" \ -" \n" - -#define RESET_USAGE \ -" nvmecontrol reset \n" - -#define LOGPAGE_USAGE \ -" nvmecontrol logpage <-p page_id> [-b] [-v vendor] [-x] \n" \ - -#define FIRMWARE_USAGE \ -" nvmecontrol firmware [-s slot] [-f path_to_firmware] [-a] \n" +#define NVME_CMDSET(set, sym) DATA_SET(set, sym) +#define NVME_COMMAND(set, nam, function, usage_str) \ + static struct nvme_function function ## _nvme_cmd = \ + { .name = #nam, .fn = function, .usage = usage_str }; \ + NVME_CMDSET(set, function ## _nvme_cmd); -#define FORMAT_USAGE \ -" nvmecontrol format [-f fmt] [-m mset] [-p pi] [-l pil] [-E] [-C] \n" +typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size); -#define POWER_USAGE \ -" nvmecontrol power [-l] [-p new-state [-w workload-hint]] \n" +struct logpage_function { + uint8_t log_page; + const char *vendor; + const char *name; + print_fn_t print_fn; + size_t size; +}; -#define WDC_USAGE \ -" nvmecontrol wdc (cap-diag|drive-log|get-crash-dump|purge|purge-montior)\n" +#define NVME_LOGPAGESET(sym) DATA_SET(logpage, sym) +#define NVME_LOGPAGE(unique, lp, vend, nam, fn, sz) \ + static struct logpage_function unique ## _lpf = { \ + .log_page = lp, \ + .vendor = vend, \ + .name = nam, \ + .print_fn = fn, \ + .size = sz, \ + } ; \ + NVME_LOGPAGESET(unique ## _lpf); + +#define DEFAULT_SIZE (4096) +struct kv_name +{ + uint32_t key; + const char *name; +}; -#define NS_USAGE \ -" nvmecontrol ns (create|delete|attach|detach)\n" +const char *kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key); -void devlist(int argc, char *argv[]); -void identify(int argc, char *argv[]); -void perftest(int argc, char *argv[]); -void reset(int argc, char *argv[]); -void logpage(int argc, char *argv[]); -void firmware(int argc, char *argv[]); -void format(int argc, char *argv[]); -void power(int argc, char *argv[]); -void wdc(int argc, char *argv[]); -void ns(int argc, char *argv[]); +#define NVME_CTRLR_PREFIX "nvme" +#define NVME_NS_PREFIX "ns" int open_dev(const char *str, int *fd, int show_error, int exit_on_error); void parse_ns_str(const char *ns_str, char *ctrlr_str, uint32_t *nsid); @@ -95,8 +89,14 @@ void print_hex(void *data, uint32_t length); void read_logpage(int fd, uint8_t log_page, uint32_t nsid, void *payload, uint32_t payload_size); -void gen_usage(struct nvme_function *); -void dispatch(int argc, char *argv[], struct nvme_function *f); +void print_temp(uint16_t t); + +void usage(struct nvme_function *f); +void dispatch_set(int argc, char *argv[], struct nvme_function **tbl, + struct nvme_function **tbl_limit); + +#define DISPATCH(argc, argv, set) \ + dispatch_set(argc, argv, SET_BEGIN(set), SET_LIMIT(set)) /* Utility Routines */ /* Index: sbin/nvmecontrol/nvmecontrol.c =================================================================== --- sbin/nvmecontrol/nvmecontrol.c +++ sbin/nvmecontrol/nvmecontrol.c @@ -47,51 +47,73 @@ #include "nvmecontrol.h" +SET_DECLARE(top, struct nvme_function); -static struct nvme_function funcs[] = { - {"devlist", devlist, DEVLIST_USAGE}, - {"identify", identify, IDENTIFY_USAGE}, - {"perftest", perftest, PERFTEST_USAGE}, - {"reset", reset, RESET_USAGE}, - {"logpage", logpage, LOGPAGE_USAGE}, - {"firmware", firmware, FIRMWARE_USAGE}, - {"format", format, FORMAT_USAGE}, - {"power", power, POWER_USAGE}, - {"wdc", wdc, WDC_USAGE}, - {"ns", ns, NS_USAGE}, - {NULL, NULL, NULL}, -}; +static void +print_usage(struct nvme_function *f) +{ + const char *cp; + char ch; + bool need_prefix = true; + + cp = f->usage; + while (*cp) { + ch = *cp++; + if (need_prefix) { + if (ch != ' ') + fputs(" nvmecontrol ", stderr); + else + fputs(" ", stderr); + } + fputc(ch, stderr); + need_prefix = (ch == '\n'); + } + if (!need_prefix) + fputc('\n', stderr); +} -void -gen_usage(struct nvme_function *f) +static void +gen_usage_set(struct nvme_function **f, struct nvme_function **flimit) { fprintf(stderr, "usage:\n"); - while (f->name != NULL) { - fprintf(stderr, "%s", f->usage); + while (f < flimit) { + print_usage(*f); f++; } exit(1); } void -dispatch(int argc, char *argv[], struct nvme_function *tbl) +usage(struct nvme_function *f) +{ + + fprintf(stderr, "usage:\n"); + print_usage(f); + exit(1); +} + +void +dispatch_set(int argc, char *argv[], struct nvme_function **tbl, + struct nvme_function **tbl_limit) { - struct nvme_function *f = tbl; + struct nvme_function **f = tbl; if (argv[1] == NULL) { - gen_usage(tbl); + gen_usage_set(tbl, tbl_limit); return; } - while (f->name != NULL) { - if (strcmp(argv[1], f->name) == 0) - f->fn(argc-1, &argv[1]); + while (f < tbl_limit) { + if (strcmp(argv[1], (*f)->name) == 0) { + (*f)->fn(*f, argc-1, &argv[1]); + return; + } f++; } fprintf(stderr, "Unknown command: %s\n", argv[1]); - gen_usage(tbl); + gen_usage_set(tbl, tbl_limit); } static void @@ -243,9 +265,9 @@ { if (argc < 2) - gen_usage(funcs); + gen_usage_set(SET_BEGIN(top), SET_LIMIT(top)); - dispatch(argc, argv, funcs); + DISPATCH(argc, argv, top); return (0); } Index: sbin/nvmecontrol/perftest.c =================================================================== --- sbin/nvmecontrol/perftest.c +++ sbin/nvmecontrol/perftest.c @@ -45,6 +45,12 @@ #include "nvmecontrol.h" +#define PERFTEST_USAGE \ + "perftest <-n num_threads> <-o read|write>\n" \ + " <-s size_in_bytes> <-t time_in_seconds>\n" \ + " <-i intr|wait> [-f refthread] [-p]\n" \ + " \n" + static void print_perftest(struct nvme_io_test *io_test, bool perthread) { @@ -69,15 +75,7 @@ } static void -perftest_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, PERFTEST_USAGE); - exit(1); -} - -void -perftest(int argc, char *argv[]) +perftest(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_io_test io_test; int fd; @@ -112,13 +110,13 @@ fprintf(stderr, "\"%s\" not valid number of threads.\n", optarg); - perftest_usage(); + usage(nf); } else if (io_test.num_threads == 0 || io_test.num_threads > 128) { fprintf(stderr, "\"%s\" not valid number of threads.\n", optarg); - perftest_usage(); + usage(nf); } break; case 'o': @@ -131,7 +129,7 @@ else { fprintf(stderr, "\"%s\" not valid opcode.\n", optarg); - perftest_usage(); + usage(nf); } break; case 'p': @@ -149,7 +147,7 @@ } else { fprintf(stderr, "\"%s\" not valid size.\n", optarg); - perftest_usage(); + usage(nf); } break; case 't': @@ -159,14 +157,15 @@ fprintf(stderr, "\"%s\" not valid time duration.\n", optarg); - perftest_usage(); + usage(nf); } break; } } if (!nflag || !oflag || !sflag || !tflag || optind >= argc) - perftest_usage(); + usage(nf); + open_dev(argv[optind], &fd, 1, 1); if (ioctl(fd, ioctl_cmd, &io_test) < 0) @@ -176,3 +175,5 @@ print_perftest(&io_test, perthread); exit(0); } + +NVME_COMMAND(top, perftest, perftest, PERFTEST_USAGE); Index: sbin/nvmecontrol/power.c =================================================================== --- sbin/nvmecontrol/power.c +++ sbin/nvmecontrol/power.c @@ -44,13 +44,8 @@ _Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY, "nvme_power_state size wrong"); -static void -power_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, POWER_USAGE); - exit(1); -} +#define POWER_USAGE \ + "power [-l] [-p new-state [-w workload-hint]] \n" static void power_list_one(int i, struct nvme_power_state *nps) @@ -133,8 +128,8 @@ printf("Current Power Mode is %d\n", pt.cpl.cdw0); } -void -power(int argc, char *argv[]) +static void +power(struct nvme_function *nf, int argc, char *argv[]) { struct nvme_controller_data cdata; int ch, listflag = 0, powerflag = 0, power_val = 0, fd; @@ -151,28 +146,28 @@ power_val = strtol(optarg, &end, 0); if (*end != '\0') { fprintf(stderr, "Invalid power state number: %s\n", optarg); - power_usage(); + usage(nf); } break; case 'w': workload = strtol(optarg, &end, 0); if (*end != '\0') { fprintf(stderr, "Invalid workload hint: %s\n", optarg); - power_usage(); + usage(nf); } break; default: - power_usage(); + usage(nf); } } /* Check that a controller was specified. */ if (optind >= argc) - power_usage(); + usage(nf); if (listflag && powerflag) { fprintf(stderr, "Can't set power and list power states\n"); - power_usage(); + usage(nf); } open_dev(argv[optind], &fd, 1, 1); @@ -193,3 +188,5 @@ close(fd); exit(0); } + +NVME_COMMAND(top, power, power, POWER_USAGE); Index: sbin/nvmecontrol/reset.c =================================================================== --- sbin/nvmecontrol/reset.c +++ sbin/nvmecontrol/reset.c @@ -41,29 +41,24 @@ #include "nvmecontrol.h" -static void -reset_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, RESET_USAGE); - exit(1); -} +#define RESET_USAGE \ + "reset \n" -void -reset(int argc, char *argv[]) +static void +reset(struct nvme_function *nf, int argc, char *argv[]) { int ch, fd; while ((ch = getopt(argc, argv, "")) != -1) { switch ((char)ch) { default: - reset_usage(); + usage(nf); } } /* Check that a controller was specified. */ if (optind >= argc) - reset_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); if (ioctl(fd, NVME_RESET_CONTROLLER) < 0) @@ -71,3 +66,5 @@ exit(0); } + +NVME_COMMAND(top, reset, reset, RESET_USAGE); Index: sbin/nvmecontrol/wdc.c =================================================================== --- sbin/nvmecontrol/wdc.c +++ sbin/nvmecontrol/wdc.c @@ -42,19 +42,21 @@ #include "nvmecontrol.h" +#define WDC_USAGE \ + "wdc (cap-diag)\n" + +SET_DECLARE(wdc, struct nvme_function); + #define WDC_NVME_TOC_SIZE 8 #define WDC_NVME_CAP_DIAG_OPCODE 0xe6 #define WDC_NVME_CAP_DIAG_CMD 0x0000 -static void wdc_cap_diag(int argc, char *argv[]); +static void wdc_cap_diag(struct nvme_function *nf, int argc, char *argv[]); -#define WDC_CAP_DIAG_USAGE "\tnvmecontrol wdc cap-diag [-o path-template]\n" +#define WDC_CAP_DIAG_USAGE "wdc cap-diag [-o path-template]\n" -static struct nvme_function wdc_funcs[] = { - {"cap-diag", wdc_cap_diag, WDC_CAP_DIAG_USAGE}, - {NULL, NULL, NULL}, -}; +NVME_COMMAND(wdc, cap-diag, wdc_cap_diag, WDC_CAP_DIAG_USAGE); static void wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) @@ -152,15 +154,7 @@ } static void -wdc_cap_diag_usage(void) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, WDC_CAP_DIAG_USAGE); - exit(1); -} - -static void -wdc_cap_diag(int argc, char *argv[]) +wdc_cap_diag(struct nvme_function *nf, int argc, char *argv[]) { char path_tmpl[MAXPATHLEN]; int ch, fd; @@ -172,12 +166,12 @@ strlcpy(path_tmpl, optarg, MAXPATHLEN); break; default: - wdc_cap_diag_usage(); + usage(nf); } } /* Check that a controller was specified. */ if (optind >= argc) - wdc_cap_diag_usage(); + usage(nf); open_dev(argv[optind], &fd, 1, 1); wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, @@ -188,9 +182,416 @@ exit(1); } -void -wdc(int argc, char *argv[]) +static void +wdc(struct nvme_function *nf __unused, int argc, char *argv[]) +{ + + DISPATCH(argc, argv, wdc); +} + +/* + * 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 *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); +static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); + +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 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, nitems(kv)); +} + +static void +print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) +{ + static 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, nitems(kv)); +} + +static void +print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) +{ + static 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, nitems(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 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" }, + }; - dispatch(argc, argv, wdc_funcs); + printf("Erase Errors Subpage:\n"); + print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(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(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) +{ + uint8_t *walker, *end, *subpage; + int pages; + 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, nitems(hgst_subpage)); + } +} + +NVME_LOGPAGE(hgst_info, + HGST_INFO_LOG, "hgst", "Detailed Health/SMART", + print_hgst_info_log, DEFAULT_SIZE); +NVME_LOGPAGE(wdc_info, + HGST_INFO_LOG, "wdc", "Detailed Health/SMART", + print_hgst_info_log, DEFAULT_SIZE); +NVME_COMMAND(top, wdc, wdc, WDC_USAGE);