# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright (c) 2018, Joyent, Inc. # Copyright 2026 Oxide Computer Company # Copyright 2022 Tintri by DDN, Inc. All rights reserved. # PROG= nvmeadm OBJS= nvme_version.o \ nvmeadm.o \ nvmeadm_extsmart.o \ nvmeadm_field.o \ nvmeadm_field_suplog.o \ nvmeadm_micron.o \ nvmeadm_nsmgmt.o \ nvmeadm_ocp.o \ nvmeadm_ofmt.o \ nvmeadm_phyeye.o \ nvmeadm_print.o \ nvmeadm_sandisk.o \ nvmeadm_solidigm.o \ nvmeadm_telemetry.o \ nvmeadm_vuc.o \ nvmeadm_wdc.o \ bitext.o \ $(HEXDUMP_OBJS) include ../Makefile.cmd include ../Makefile.cmd.64 include ../Makefile.ctf include $(SRC)/common/hexdump/Makefile.com .KEEP_STATE: CPPFLAGS += -I$(SRC)/uts/common/io/nvme -I$(SRC)/common/nvme LDLIBS += -ldevinfo -lofmt -lcmdutils -lnvme LDLIBS += -L$(ROOT)/usr/lib/fm/64 -R/usr/lib/fm/64 -ltopo CSTD= $(CSTD_GNU99) CTF_MODE= link # # nvme_print_uint128() uses VLAs which understandably confuses smatch. # It should probably be rewritten so we can smatch the file. # nvmeadm_print.o := SMATCH=off all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) %.o: %.c $(COMPILE.c) $< $(POST_PROCESS_O) %.o: $(SRC)/common/nvme/%.c $(COMPILE.c) $< $(POST_PROCESS_O) %.o: $(SRC)/common/bitext/%.c $(COMPILE.c) $< $(POST_PROCESS_O) install: all $(ROOTUSRSBINPROG) clean: $(RM) $(OBJS) $(PROG) lint: lint_SRCS include $(SRC)/common/hexdump/Makefile.targ include ../Makefile.targ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2017 Joyent, Inc. * Copyright 2026 Oxide Computer Company * Copyright 2022 Tintri by DDN, Inc. All rights reserved. */ /* * nvmeadm -- NVMe administration utility * * nvmeadm [-v] [-d] [-h] [[/][,...]] [args] * commands: list * identify * list-logpages [logpage name],... * get-logpage * get-features [,...] * format ... * secure-erase ... * detach ... * attach ... * list-firmware ... * load-firmware ... * commit-firmware ... * activate-firmware ... */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" /* * Assertions to make sure that we've properly captured various aspects of the * packed structures and haven't broken them during updates. */ CTASSERT(sizeof (nvme_identify_ctrl_t) == NVME_IDENTIFY_BUFSIZE); CTASSERT(offsetof(nvme_identify_ctrl_t, id_oacs) == 256); CTASSERT(offsetof(nvme_identify_ctrl_t, id_sqes) == 512); CTASSERT(offsetof(nvme_identify_ctrl_t, id_oncs) == 520); CTASSERT(offsetof(nvme_identify_ctrl_t, id_subnqn) == 768); CTASSERT(offsetof(nvme_identify_ctrl_t, id_nvmof) == 1792); CTASSERT(offsetof(nvme_identify_ctrl_t, id_psd) == 2048); CTASSERT(offsetof(nvme_identify_ctrl_t, id_vs) == 3072); CTASSERT(sizeof (nvme_identify_nsid_t) == NVME_IDENTIFY_BUFSIZE); CTASSERT(offsetof(nvme_identify_nsid_t, id_fpi) == 32); CTASSERT(offsetof(nvme_identify_nsid_t, id_anagrpid) == 92); CTASSERT(offsetof(nvme_identify_nsid_t, id_nguid) == 104); CTASSERT(offsetof(nvme_identify_nsid_t, id_lbaf) == 128); CTASSERT(offsetof(nvme_identify_nsid_t, id_vs) == 384); CTASSERT(sizeof (nvme_identify_nsid_list_t) == NVME_IDENTIFY_BUFSIZE); CTASSERT(sizeof (nvme_identify_ctrl_list_t) == NVME_IDENTIFY_BUFSIZE); CTASSERT(sizeof (nvme_identify_primary_caps_t) == NVME_IDENTIFY_BUFSIZE); CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vqfrt) == 32); CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vifrt) == 64); CTASSERT(sizeof (nvme_nschange_list_t) == 4096); static void usage(const nvmeadm_cmd_t *); static bool nvmeadm_ctrl_disc_cb(nvme_t *, const nvme_ctrl_disc_t *, void *); static int do_list(const nvme_process_arg_t *); static int do_identify(const nvme_process_arg_t *); static int do_identify_ctrl(const nvme_process_arg_t *); static int do_identify_ns(const nvme_process_arg_t *); static int do_list_logs(const nvme_process_arg_t *); static int do_get_logpage_fwslot(const nvme_process_arg_t *); static int do_get_logpage(const nvme_process_arg_t *); static int do_print_logpage(const nvme_process_arg_t *); static int do_list_features(const nvme_process_arg_t *); static boolean_t do_get_feat_intr_vect(const nvme_process_arg_t *, const nvme_feat_disc_t *, const nvmeadm_feature_t *); static boolean_t do_get_feat_temp_thresh(const nvme_process_arg_t *, const nvme_feat_disc_t *, const nvmeadm_feature_t *); static int do_get_features(const nvme_process_arg_t *); static int do_format(const nvme_process_arg_t *); static int do_secure_erase(const nvme_process_arg_t *); static int do_attach_bd(const nvme_process_arg_t *); static int do_detach_bd(const nvme_process_arg_t *); static int do_firmware_load(const nvme_process_arg_t *); static int do_firmware_commit(const nvme_process_arg_t *); static int do_firmware_activate(const nvme_process_arg_t *); static void optparse_list(nvme_process_arg_t *); static void optparse_identify(nvme_process_arg_t *); static void optparse_identify_ctrl(nvme_process_arg_t *); static void optparse_identify_ns(nvme_process_arg_t *); static void optparse_list_logs(nvme_process_arg_t *); static void optparse_get_logpage(nvme_process_arg_t *); static void optparse_print_logpage(nvme_process_arg_t *); static void optparse_list_features(nvme_process_arg_t *); static void optparse_secure_erase(nvme_process_arg_t *); static void usage_list(const char *); static void usage_identify(const char *); static void usage_identify_ctrl(const char *); static void usage_identify_ns(const char *); static void usage_list_logs(const char *); static void usage_get_logpage(const char *); static void usage_print_logpage(const char *); static void usage_list_features(const char *); static void usage_get_features(const char *); static void usage_format(const char *); static void usage_secure_erase(const char *); static void usage_attach_detach_bd(const char *); static void usage_firmware_list(const char *); static void usage_firmware_load(const char *); static void usage_firmware_commit(const char *); static void usage_firmware_activate(const char *); int verbose; int debug; /* * nvmeadm Secure-erase specific options */ #define NVMEADM_O_SE_CRYPTO 0x00000004 /* * nvmeadm identify specific options */ #define NVMEADM_O_ID_NSID_LIST 0x00000008 #define NVMEADM_O_ID_COMMON_NS 0x00000010 #define NVMEADM_O_ID_CTRL_LIST 0x00000020 #define NVMEADM_O_ID_DESC_LIST 0x00000040 #define NVMEADM_O_ID_ALLOC_NS 0x00000080 static int exitcode; /* * Nvmeadm subcommand definitons. * * When adding a new subcommand, please check that the commands still * line up in the usage() message, and adjust the format string in * usage() below if necessary. */ static const nvmeadm_cmd_t nvmeadm_cmds[] = { { "list", "list controllers and namespaces", " -c\t\tlist only controllers\n" " -L\t\tlist controller location\n" " -p\t\tprint parsable output\n" " -o field\tselect a field for parsable output\n", " model\t\tthe controller's model name\n" " serial\tthe controller's serial number\n" " fwrev\t\tthe controller's current firmware revision\n" " version\tthe controller's NVMe specification version\n" " capacity\tthe controller or namespace's capacity in bytes\n" " instance\tthe device driver instance (e.g. nvme3)\n" " ctrlpath\tthe controller's /devices path\n" " unallocated\tthe amount of unallocated NVM in bytes\n" " size\t\tthe namespace's logical size in bytes\n" " used\t\tthe namespace's bytes used\n" " disk\t\tthe name of the namespace's disk device\n" " namespace\tthe namespace's numerical value\n" " ns-state\tthe namespace's current state\n" " format\ta description of the namespace's format\n" " fmtid\t\tthe numerical id of the namespace's format\n" " fmtds\t\tthe data size of the namespace's format\n" " fmtms\t\tthe metadata size of the namespace's format\n" " location\tcontroller location (-L)\n" " ctlap\t\tcontrolling attachment point (-L)\n", do_list, usage_list, optparse_list, NVMEADM_C_MULTI }, { "identify", "identify controllers and/or namespaces", " -C\t\tget Common Namespace Identification\n" " -a\t\tget only allocated namespace information\n" " -c\t\tget controller identifier list\n" " -d\t\tget namespace identification descriptors list\n" " -n\t\tget namespaces identifier list", NULL, do_identify, usage_identify, optparse_identify, NVMEADM_C_MULTI }, { "identify-controller", "identify controllers", " -C\t\tget Common Namespace Identification\n" " -a\t\tget only allocated namespace information\n" " -c\t\tget controller identifier list\n" " -n\t\tget namespaces identifier list", NULL, do_identify_ctrl, usage_identify_ctrl, optparse_identify_ctrl, NVMEADM_C_MULTI }, { "identify-namespace", "identify namespaces", " -c\t\tget attached controller identifier list\n" " -d\t\tget namespace identification descriptors list", NULL, do_identify_ns, usage_identify_ns, optparse_identify_ns, NVMEADM_C_MULTI }, { "list-logpages", "list a device's supported log pages", " -a\t\tprint all log pages, including unimplemented ones\n" " -H\t\tomit column headers\n" " -o field\tselect a field for parsable output\n" " -p\t\tprint parsable output\n" " -s scope\tprint logs that match the specified scopes " "(default is based on\n\t\tdevice)\n", " device\tthe name of the controller or namespace\n" " name\t\tthe name of the log page\n" " desc\t\ta description of the loage page\n" " scope\t\tthe valid device scopes for the log page\n" " fields\tthe list of fields in the get log request that may " "be set or required\n\t\t(e.g. lsi, lsp, rae, etc.)\n" " csi\t\tthe command set interface the log page belongs to\n" " lid\t\tthe log page's numeric ID\n" " impl\t\tindicates whether the device implements the log " "page\n" " size\t\tthe size of the log page for fixed size logs\n" " minsize\tthe minimum size required to determine the full " "log page size\n\t\tfor variable-length pages\n" " sources\twhere information for this log page came from\n" " kind\t\tindicates the kind of log page e.g. standard, " "vendor-specific,\n\t\tetc.", do_list_logs, usage_list_logs, optparse_list_logs, NVMEADM_C_MULTI }, { .c_name = "get-logpage", .c_desc = "get a log page from controllers and/or namespaces", .c_flagdesc = " -H\t\tomit column headers\n" " -o field\tselect a field for parsable output\n" " -O file\toutput log raw binary data to a file\n" " -p\t\tprint parsable output\n" " -x\t\tforce hex dump output\n", .c_fielddesc = " short\t\tfield's short name\n" " desc\t\tdescription of the field\n" " value\t\tthe raw field value\n" " human\t\tthe human printable form of the value\n" " length\tthe length of the field in bytes\n" " bitlen\tthe number of additional bits long the field " "is\n" " offset\tthe offset in bytes to the start of the field\n" " bitoff\tthe additional number of bits to the start of " "the field\n", .c_func = do_get_logpage, .c_usage = usage_get_logpage, .c_optparse = optparse_get_logpage, .c_flags = NVMEADM_C_MULTI }, { .c_name = "print-logpage", .c_desc = "print and filter a log page from disk", .c_flagdesc = " -f file\tfile that contains log data\n" " -H\t\tomit column headers\n" " -o field\tselect a field for parsable output\n" " -p\t\tprint parsable output\n" " -x\t\tforce hex dump output\n", .c_fielddesc = " short\t\tfield's short name\n" " desc\t\tdescription of the field\n" " value\t\tthe raw field value\n" " human\t\tthe human printable form of the value\n" " offset\tthe length of the field in bytes\n" " bitlen\tthe number of additional bits long the field " "is\n" " offset\tthe offset in bytes to the start of the field\n" " bitoff\tthe additional number of bits to the start of " "the field\n", .c_func = do_print_logpage, .c_usage = usage_print_logpage, .c_optparse = optparse_print_logpage, .c_flags = NVMEADM_C_NOCTRL }, { "list-features", "list a device's supported features", " -a\t\tprint all features, including unsupported\n" " -H\t\tomit column headers\n" " -o field\tselect a field for parsable output\n" " -p\t\tprint parsable output", " device\tthe name of the controller or namespace\n" " short\t\tthe short name of the feature\n" " spec\t\tthe longer feature description from the NVMe spec\n" " fid\t\tthe numeric feature ID\n" " scope\t\tthe valid device scopes for the feature\n" " kind\t\tindicates the kind of feature e.g. standard, " "vendor-specific,\n\t\tetc.\n" " csi\t\tindicates the features command set interface\n" " flags\t\tindicates additional properties of the feature\n" " get-in\tindicates the fields that are required to get the " "feature\n" " set-in\tindicates the fields that are required to set the " "feature\n" " get-out\tindicates the fields the feature outputs\n" " set-out\tindicates the fields the feature outputs when " "setting the feature\n" " datalen\tindicates the length of the feature's data " "payload\n" " impl\t\tindicates whether the device implements the " "feature", do_list_features, usage_list_features, optparse_list_features, NVMEADM_C_MULTI }, { "get-features", "get features from controllers and/or namespaces", NULL, NULL, do_get_features, usage_get_features, NULL, NVMEADM_C_MULTI }, { "format", "format namespace(s) of a controller", NULL, NULL, do_format, usage_format, NULL, NVMEADM_C_EXCL }, { "secure-erase", "secure erase namespace(s) of a controller", " -c Do a cryptographic erase.", NULL, do_secure_erase, usage_secure_erase, optparse_secure_erase, NVMEADM_C_EXCL }, { "create-namespace", "create a new namespace", " -b block-size\tNamespace format chosen to match the " "requested block-size\n" " -c cap\tSpecifies the namespace capacity in bytes, defaults " "to the\n\t\tnamespace's size. When the size is greater than " "the\n\t\tcapacity, the namespace is thin provisioned.\n" " -f flbas\tformatted LBA block size index\n" " -n nmic\tmulti-path I/O and namespace sharing capabilities, " "valid values:\n" "\t\tnone\tno namespace sharing\n" "\t\tshared\tthe namespace may be attached by two or more " "controllers\n" " -t csi\tspecifies the namespace's command set interface, " "defaults to\n\t\tnvm\n", NULL, do_create_ns, usage_create_ns, optparse_create_ns, NVMEADM_C_EXCL }, { "delete-namespace", "delete a namespace", NULL, NULL, do_delete_ns, usage_delete_ns, NULL, NVMEADM_C_EXCL }, { "attach-namespace", "attach a namespace to a controller", NULL, NULL, do_attach_ns, usage_attach_ns, NULL, NVMEADM_C_EXCL }, { "detach-namespace", "detach a namespace from a controller", NULL, NULL, do_detach_ns, usage_detach_ns, NULL, NVMEADM_C_EXCL }, { "detach", "detach blkdev(4D) from namespace(s) of a controller", NULL, NULL, do_detach_bd, usage_attach_detach_bd, NULL, NVMEADM_C_EXCL }, { "attach", "attach blkdev(4D) to namespace(s) of a controller", NULL, NULL, do_attach_bd, usage_attach_detach_bd, NULL, NVMEADM_C_EXCL }, { "list-firmware", "list firmware on a controller", NULL, NULL, do_get_logpage_fwslot, usage_firmware_list, NULL, 0 }, { "load-firmware", "load firmware to a controller", NULL, NULL, do_firmware_load, usage_firmware_load, NULL, NVMEADM_C_EXCL }, { "commit-firmware", "commit downloaded firmware to a slot of a controller", NULL, NULL, do_firmware_commit, usage_firmware_commit, NULL, NVMEADM_C_EXCL }, { "activate-firmware", "activate a firmware slot of a controller", NULL, NULL, do_firmware_activate, usage_firmware_activate, NULL, NVMEADM_C_EXCL }, { .c_name = "measure-phyeye", .c_desc = "measure the physical interface eye", .c_flagdesc = " -o output\tthe data output file\n" " -Q quality\tthe quality of the eye measurement (good, " "better, or best)\n", .c_func = do_measure_phyeye_cmd, .c_usage = usage_measure_phyeye_cmd, .c_optparse = optparse_measure_phyeye_cmd, .c_flags = NVMEADM_C_EXCL }, { .c_name = "report-phyeye", .c_desc = "report information about the measured eye", .c_flagdesc = " -e eye\toptional eye index to report\n" " -l lane\tprint information only about the specific " "lane\n" " -m mode\tdata report mode, either 'print-eye' or " "'eye-data'\n", .c_func = do_report_phyeye_cmd, .c_usage = usage_report_phyeye_cmd, .c_optparse = optparse_report_phyeye_cmd, .c_flags = NVMEADM_C_NOCTRL }, { .c_name = "vendor-cmd", .c_desc = "run an arbitrary vendor-specific command", .c_flagdesc = " -O opcode\tcommand opcode\n" " -n nsid\tcommand namespace identifier\n" " --cdw12 cdw12\tcdw12 32-bit value\n" " --cdw13 cdw13\tcdw13 32-bit value\n" " --cdw14 cdw14\tcdw14 32-bit value\n" " --cdw15 cdw15\tcdw15 32-bit value\n" " -l length\tthe command data size in bytes\n" " -i input\tthe command input data file\n" " -o output\tthe command output data file\n" " -L lock\trequest a controller or namespace lock\n" " -I impact\trequest impact on the system\n" " -t timeout\trequest timeout in seconds\n", .c_func = do_vendor_cmd, .c_usage = usage_vendor_cmd, .c_optparse = optparse_vendor_cmd }, { .c_name = "sandisk/hwrev", .c_desc = "obtain device hardware revision", .c_func = do_sandisk_hwrev, .c_usage = usage_sandisk_hwrev, .c_flags = 0 }, { .c_name = "sandisk/pci-eye", .c_desc = "get PCIe receiver eye diagram", .c_flagdesc = " -l lane\t\tspecify the PCIe lane (0-3)\n" " -o output\tspecify output file destination\n", .c_func = do_sandisk_pcieye, .c_usage = usage_sandisk_pcieye, .c_optparse = optparse_sandisk_pcieye }, { "wdc/e6dump", "dump WDC e6 diagnostic log", " -o output\tspecify output file destination\n", NULL, do_wdc_e6dump, usage_wdc_e6dump, optparse_wdc_e6dump, 0 }, { "wdc/resize", "change a WDC device's capacity", " -g\t\tquery the device's current resized capacity\n" " -s size\tset the size of a device to the specified in gb", NULL, do_wdc_resize, usage_wdc_resize, optparse_wdc_resize, /* * We do not set NVMEADM_C_EXCL here as that is handled by the * vendor unique command logic and operates based on the * information we get from vuc discovery. */ 0 }, { "wdc/clear-assert", "clear internal device assertion", NULL, NULL, do_wdc_clear_assert, usage_wdc_clear_assert, NULL }, { "wdc/inject-assert", "inject internal device assertion", NULL, NULL, do_wdc_inject_assert, usage_wdc_inject_assert, NULL }, { NULL, NULL, NULL, NULL, NULL, NULL, 0 } }; static const nvmeadm_feature_t features[] = { { .f_feature = NVME_FEAT_ARBITRATION, .f_print = nvme_print_feat_arbitration }, { .f_feature = NVME_FEAT_POWER_MGMT, .f_print = nvme_print_feat_power_mgmt }, { .f_feature = NVME_FEAT_LBA_RANGE, .f_print = nvme_print_feat_lba_range }, { .f_feature = NVME_FEAT_TEMPERATURE, .f_get = do_get_feat_temp_thresh, .f_print = nvme_print_feat_temperature }, { .f_feature = NVME_FEAT_ERROR, .f_print = nvme_print_feat_error }, { .f_feature = NVME_FEAT_WRITE_CACHE, .f_print = nvme_print_feat_write_cache }, { .f_feature = NVME_FEAT_NQUEUES, .f_print = nvme_print_feat_nqueues }, { .f_feature = NVME_FEAT_INTR_COAL, .f_print = nvme_print_feat_intr_coal }, { .f_feature = NVME_FEAT_INTR_VECT, .f_get = do_get_feat_intr_vect, .f_print = nvme_print_feat_intr_vect }, { .f_feature = NVME_FEAT_WRITE_ATOM, .f_print = nvme_print_feat_write_atom }, { .f_feature = NVME_FEAT_ASYNC_EVENT, .f_print = nvme_print_feat_async_event }, { .f_feature = NVME_FEAT_AUTO_PST, .f_print = nvme_print_feat_auto_pst }, { .f_feature = NVME_FEAT_PROGRESS, .f_print = nvme_print_feat_progress }, { .f_feature = NVME_FEAT_HOST_BEHAVE, .f_print = nvme_print_feat_host_behavior } }; static void nvmeadm_ctrl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap) { nvme_ctrl_t *ctrl = npa->npa_ctrl; (void) fprintf(stderr, "nvmeadm: "); (void) vfprintf(stderr, fmt, ap); (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n", nvme_ctrl_errmsg(ctrl), nvme_ctrl_errtostr(npa->npa_ctrl, nvme_ctrl_err(ctrl)), nvme_ctrl_err(ctrl), nvme_ctrl_syserr(ctrl)); } static void nvmeadm_hdl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap) { nvme_t *nvme = npa->npa_nvme; (void) fprintf(stderr, "nvmeadm: "); (void) vfprintf(stderr, fmt, ap); (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n", nvme_errmsg(nvme), nvme_errtostr(nvme, nvme_err(nvme)), nvme_err(nvme), nvme_syserr(nvme)); } static void nvmeadm_ctrl_info_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap) { nvme_ctrl_info_t *info = npa->npa_ctrl_info; (void) fprintf(stderr, "nvmeadm: "); (void) vfprintf(stderr, fmt, ap); (void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n", nvme_ctrl_info_errmsg(info), nvme_ctrl_info_errtostr(info, nvme_ctrl_info_err(info)), nvme_ctrl_info_err(info), nvme_ctrl_info_syserr(info)); } void nvmeadm_warn(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_ctrl_vwarn(npa, fmt, ap); va_end(ap); } void __NORETURN nvmeadm_fatal(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_ctrl_vwarn(npa, fmt, ap); va_end(ap); exit(-1); } void nvmeadm_hdl_warn(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_hdl_vwarn(npa, fmt, ap); va_end(ap); } void __NORETURN nvmeadm_hdl_fatal(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_hdl_vwarn(npa, fmt, ap); va_end(ap); exit(-1); } static void nvmeadm_ctrl_info_warn(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_ctrl_info_vwarn(npa, fmt, ap); va_end(ap); } static void nvmeadm_ctrl_info_fatal(const nvme_process_arg_t *npa, const char *fmt, ...) { va_list ap; va_start(ap, fmt); nvmeadm_ctrl_info_vwarn(npa, fmt, ap); va_end(ap); exit(-1); } boolean_t nvme_version_check(const nvme_process_arg_t *npa, const nvme_version_t *vers) { return (nvme_vers_atleast(npa->npa_version, vers) ? B_TRUE : B_FALSE); } /* * Because nvmeadm operates on a series of NVMe devices for several commands, * here we need to clean up everything that we allocated for this device so we * can prepare for the next. */ static void nvmeadm_cleanup_npa(nvme_process_arg_t *npa) { npa->npa_idctl = NULL; npa->npa_version = NULL; if (npa->npa_excl) { if (npa->npa_ns != NULL) { nvme_ns_unlock(npa->npa_ns); } else if (npa->npa_ctrl != NULL) { nvme_ctrl_unlock(npa->npa_ctrl); } } if (npa->npa_ns_info != NULL) { nvme_ns_info_free(npa->npa_ns_info); npa->npa_ns_info = NULL; } if (npa->npa_ctrl_info != NULL) { nvme_ctrl_info_free(npa->npa_ctrl_info); npa->npa_ctrl_info = NULL; } if (npa->npa_ns != NULL) { nvme_ns_fini(npa->npa_ns); npa->npa_ns = NULL; } if (npa->npa_ctrl != NULL) { nvme_ctrl_fini(npa->npa_ctrl); npa->npa_ctrl = NULL; } } /* * Determine if a command requires a controller or namespace write lock. If so * we first attempt to grab it non-blocking and then if that fails, we'll warn * that we may be blocking for the lock so that way the user has a chance to do * something and can cancel it. */ void nvmeadm_excl(const nvme_process_arg_t *npa, nvme_lock_level_t level) { bool ret; nvme_lock_flags_t flags = NVME_LOCK_F_DONT_BLOCK; if (npa->npa_ns != NULL) { ret = nvme_ns_lock(npa->npa_ns, level, flags); } else { ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags); } if (ret) { return; } if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_LOCK_WOULD_BLOCK) { nvmeadm_fatal(npa, "failed to acquire lock on %s", npa->npa_name); } (void) fprintf(stderr, "Waiting on contended %s lock on %s...", npa->npa_ns != NULL ? "namespace": "controller", npa->npa_name); (void) fflush(stderr); flags &= ~NVME_LOCK_F_DONT_BLOCK; if (npa->npa_ns != NULL) { ret = nvme_ns_lock(npa->npa_ns, level, flags); } else { ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags); } if (!ret) { nvmeadm_fatal(npa, "failed to acquire lock on %s", npa->npa_name); } (void) fprintf(stderr, " acquired\n"); } /* * Most of nvmeadm was written before the existence of libnvme and always had * things like the identify controller or namespace information sitting around. * As such we try to grab all this in one place for it. Note, regardless if this * succeeds or fails, our callers will still call nvmeadm_cleanup_npa() so we * don't need to clean up the various libnvme objects. */ static boolean_t nvmeadm_open_dev(nvme_process_arg_t *npa) { if (!nvme_ctrl_ns_init(npa->npa_nvme, npa->npa_name, &npa->npa_ctrl, &npa->npa_ns)) { nvmeadm_hdl_warn(npa, "failed to open '%s'", npa->npa_name); exitcode = -1; return (B_FALSE); } /* * Several commands expect to be able to access the controller's * information snapshot. Grab that now for it and the namespace if it * exists. */ if (!nvme_ctrl_info_snap(npa->npa_ctrl, &npa->npa_ctrl_info)) { nvmeadm_warn(npa, "failed to get controller info for %s", npa->npa_ctrl_name); exitcode = -1; return (B_FALSE); } if (npa->npa_ns != NULL && !nvme_ns_info_snap(npa->npa_ns, &npa->npa_ns_info)) { nvmeadm_warn(npa, "failed to get namespace info for %s", npa->npa_name); exitcode = -1; return (B_FALSE); } /* * Snapshot data the rest of the command has fairly ingrained. */ npa->npa_version = nvme_ctrl_info_version(npa->npa_ctrl_info); npa->npa_idctl = nvme_ctrl_info_identify(npa->npa_ctrl_info); /* * If this command has requested exclusive access, proceed to grab that * before we continue. */ if (npa->npa_excl) { nvmeadm_excl(npa, NVME_LOCK_L_WRITE); } return (B_TRUE); } static bool nvmeadm_ctrl_disc_cb(nvme_t *nvme, const nvme_ctrl_disc_t *disc, void *arg) { nvme_process_arg_t *npa = arg; di_node_t di = nvme_ctrl_disc_devi(disc); char name[128]; (void) snprintf(name, sizeof (name), "%s%d", di_driver_name(di), di_instance(di)); npa->npa_name = name; npa->npa_ctrl_name = name; if (nvmeadm_open_dev(npa)) { if (npa->npa_cmd->c_func(npa) != 0) { exitcode = -1; } } nvmeadm_cleanup_npa(npa); return (true); } int main(int argc, char **argv) { int c; const nvmeadm_cmd_t *cmd; nvme_process_arg_t npa = { 0 }; int help = 0; char *ctrl = NULL; while ((c = getopt(argc, argv, "dhv")) != -1) { switch (c) { case 'd': debug++; break; case 'v': verbose++; break; case 'h': help++; break; case '?': usage(NULL); exit(-1); } } if (optind == argc) { usage(NULL); if (help) exit(0); else exit(-1); } /* Look up the specified command in the command table. */ for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) if (strcmp(cmd->c_name, argv[optind]) == 0) break; if (cmd->c_name == NULL) { usage(NULL); exit(-1); } if (help) { usage(cmd); exit(0); } npa.npa_nvme = nvme_init(); if (npa.npa_nvme == NULL) { err(-1, "failed to initialize libnvme"); } npa.npa_cmd = cmd; npa.npa_excl = ((npa.npa_cmd->c_flags & NVMEADM_C_EXCL) != 0); optind++; /* * Store the remaining arguments for use by the command. Give the * command a chance to process the options across the board before going * into each controller. */ npa.npa_argc = argc - optind; npa.npa_argv = &argv[optind]; if (npa.npa_cmd->c_optparse != NULL) { optind = 0; npa.npa_cmd->c_optparse(&npa); npa.npa_argc -= optind; npa.npa_argv += optind; } /* * If this command indicates that it does not require a controller, then * don't bother with the rest and just call it immediately. */ if ((npa.npa_cmd->c_flags & NVMEADM_C_NOCTRL) != 0) { if (npa.npa_cmd->c_func(&npa) != 0) { exitcode = -1; } exit(exitcode); } /* * All commands but "list" require a ctl/ns argument. However, this * should not be passed through to the command in its subsequent * arguments. */ if (npa.npa_argc == 0 && cmd->c_func != do_list) { warnx("missing controller/namespace name"); usage(cmd); exit(-1); } if (npa.npa_argc > 0) { ctrl = npa.npa_argv[0]; npa.npa_argv++; npa.npa_argc--; } else { if (!nvme_ctrl_discover(npa.npa_nvme, nvmeadm_ctrl_disc_cb, &npa)) { nvmeadm_hdl_fatal(&npa, "failed to walk controllers"); } exit(exitcode); } /* * Make sure we're not running commands on multiple controllers that * aren't allowed to do that. */ if (ctrl != NULL && strchr(ctrl, ',') != NULL && (cmd->c_flags & NVMEADM_C_MULTI) == 0) { warnx("%s not allowed on multiple controllers", cmd->c_name); usage(cmd); exit(-1); } /* * Get controller/namespace arguments and run command. */ while ((npa.npa_name = strsep(&ctrl, ",")) != NULL) { char *ctrl_name, *slash; /* * We may be given just a controller as an argument or a * controller and a namespace as an argument. Parts of the * commands want to know what controller they're referring to * even if the overall argument was for a namespace. So we * always dup the argument and try to make the controller out of * it. */ ctrl_name = strdup(npa.npa_name); if (ctrl_name == NULL) { err(-1, "failed to duplicate NVMe controller/namespace " "name"); } if ((slash = strchr(ctrl_name, '/')) != NULL) *slash = '\0'; npa.npa_ctrl_name = ctrl_name; if (nvmeadm_open_dev(&npa)) { if (npa.npa_cmd->c_func(&npa) != 0) { exitcode = -1; } } nvmeadm_cleanup_npa(&npa); free(ctrl_name); } exit(exitcode); } static void nvme_oferr(const char *fmt, ...) { va_list ap; va_start(ap, fmt); verrx(-1, fmt, ap); } static void usage(const nvmeadm_cmd_t *cmd) { const char *progname = getprogname(); (void) fprintf(stderr, "usage:\n"); (void) fprintf(stderr, " %s -h %s\n", progname, cmd != NULL ? cmd->c_name : "[]"); (void) fprintf(stderr, " %s [-dv] ", progname); if (cmd != NULL) { cmd->c_usage(cmd->c_name); } else { (void) fprintf(stderr, " [/][,...] []\n"); (void) fprintf(stderr, "\n Manage NVMe controllers and namespaces.\n"); (void) fprintf(stderr, "\ncommands:\n"); for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) { /* * The longest nvmeadm subcommand is 19 characters long. * The format string needs to be updated every time a * longer subcommand is added. */ (void) fprintf(stderr, " %-19s - %s\n", cmd->c_name, cmd->c_desc); } } (void) fprintf(stderr, "\n%s flags:\n" " -h\t\tprint usage information\n" " -d\t\tprint information useful for debugging %s\n" " -v\t\tprint verbose information\n", progname, progname); if (cmd != NULL && cmd->c_flagdesc != NULL) { (void) fprintf(stderr, "\n%s %s flags:\n", progname, cmd->c_name); (void) fprintf(stderr, "%s\n", cmd->c_flagdesc); } if (cmd != NULL && cmd->c_fielddesc != NULL) { (void) fprintf(stderr, "\n%s %s valid fields:\n", progname, cmd->c_name); (void) fprintf(stderr, "%s\n", cmd->c_fielddesc); } } char * nvme_dskname(di_node_t ctrl, const char *bd_addr) { di_dim_t dim; char *diskname = NULL; dim = di_dim_init(); if (dim == NULL) { err(-1, "failed to initialize devinfo minor translation"); } for (di_node_t child = di_child_node(ctrl); child != DI_NODE_NIL; child = di_sibling_node(child)) { char *disk_ctd, *path = NULL; const char *addr = di_bus_addr(child); if (addr == NULL) continue; if (strcmp(addr, bd_addr) != 0) continue; path = di_dim_path_dev(dim, di_driver_name(child), di_instance(child), "c"); /* * Error out if we didn't get a path, or if it's too short for * the following operations to be safe. */ if (path == NULL || strlen(path) < 2) { errx(-1, "failed to get a valid minor path"); } /* Chop off 's0' and get everything past the last '/' */ path[strlen(path) - 2] = '\0'; disk_ctd = strrchr(path, '/'); if (disk_ctd == NULL) { errx(-1, "encountered malformed minor path: %s", path); } diskname = strdup(++disk_ctd); if (diskname == NULL) { err(-1, "failed to duplicate disk path"); } free(path); break; } di_dim_fini(dim); return (diskname); } static void usage_list(const char *c_name) { (void) fprintf(stderr, "%s " "[-c | -L] [-p -o field[,...]] [[/][,...]\n\n" " List NVMe controllers and their namespaces. If no " "controllers and/or name-\n spaces are specified, all " "controllers and namespaces in the system will be\n " "listed.\n", c_name); } static void optparse_list(nvme_process_arg_t *npa) { int c; uint_t oflags = 0; boolean_t parse = B_FALSE, cflag = B_FALSE, Lflag = B_FALSE; const char *fields = NULL; const ofmt_field_t *ofmt = nvmeadm_list_nsid_ofmt; nvmeadm_list_t *list = NULL; if ((list = calloc(1, sizeof (nvmeadm_list_t))) == NULL) { err(-1, "failed to allocate memory for argument tracking"); } list->list_mode = NVMEADM_LIST_DEFAULT; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cLo:p")) != -1) { switch (c) { case 'c': cflag = B_TRUE; list->list_mode = NVMEADM_LIST_CTRL; ofmt = nvmeadm_list_ctrl_ofmt; break; case 'L': Lflag = B_TRUE; list->list_mode = NVMEADM_LIST_LOC; ofmt = nvmeadm_list_loc_ofmt; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; oflags |= OFMT_PARSABLE; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (cflag && Lflag) { errx(-1, "only one of -c and -L may be selected"); } if (fields != NULL && !parse) { errx(-1, "-o can only be used when in parsable mode (-p)"); } /* * We use ofmt by default with -L. */ if (fields == NULL && Lflag) { fields = "instance,model,location,ctlap"; } if (parse && fields == NULL) { errx(-1, "parsable mode (-p) requires one to specify output " "fields with -o"); } if (parse || Lflag) { ofmt_status_t oferr; oferr = ofmt_open(fields, ofmt, oflags, 0, &npa->npa_ofmt); ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx); } if (Lflag) { int e; list->list_topo = topo_open(TOPO_VERSION, NULL, &e); if (list->list_topo == NULL) { errx(-1, "location information unavailable: unable to " "obtain topo handle: %s", topo_strerror(e)); } (void) topo_snap_hold(list->list_topo, NULL, &e); if (e != 0) { errx(-1, "location information unavailable: unable to " "obtain topo snapshot: %s", topo_strerror(e)); } } npa->npa_cmd_arg = list; } static void do_list_nsid(const nvme_process_arg_t *npa, nvme_ctrl_info_t *ctrl, nvme_ns_info_t *ns) { const char *bd_addr, *disk = NULL, *state = NULL; char *disk_path = NULL; di_node_t ctrl_devi; switch (nvme_ns_info_level(ns)) { case NVME_NS_DISC_F_ALL: disk = "unallocated"; state = "unallocated"; break; case NVME_NS_DISC_F_ALLOCATED: disk = "inactive"; state = "allocated"; break; case NVME_NS_DISC_F_ACTIVE: disk = "ignored"; state = "active"; break; case NVME_NS_DISC_F_NOT_IGNORED: disk = "unattached"; state = "active-usable"; break; case NVME_NS_DISC_F_BLKDEV: disk = "unknown"; state = "blkdev"; if (nvme_ns_info_bd_addr(ns, &bd_addr) && nvme_ctrl_devi(npa->npa_ctrl, &ctrl_devi)) { disk_path = nvme_dskname(ctrl_devi, bd_addr); disk = disk_path; } break; } if (npa->npa_ofmt != NULL) { nvmeadm_list_ofmt_arg_t oarg = { 0 }; oarg.nloa_name = npa->npa_ctrl_name; oarg.nloa_ctrl = ctrl; oarg.nloa_ns = ns; oarg.nloa_disk = disk_path; oarg.nloa_state = state; if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip)) oarg.nloa_dip = DI_NODE_NIL; ofmt_print(npa->npa_ofmt, &oarg); } else { (void) printf(" %s/%u (%s)", npa->npa_ctrl_name, nvme_ns_info_nsid(ns), disk); if (nvme_ns_info_level(ns) >= NVME_NS_DISC_F_ACTIVE) { (void) printf(": "); nvme_print_nsid_summary(ns); } else { (void) printf("\n"); } } free(disk_path); } /* * Determine a device's controlling AP. As most NVMe devices are PCIe devices * (except when virtualized) we look at the parent pcieb instance and look for * its ap-names and slot-names properties. */ static void do_list_phys_map_ctlap(nvmeadm_list_ofmt_arg_t *oarg) { di_node_t pdip; int *ap_names, *slot_names; pdip = di_parent_node(oarg->nloa_dip); if (pdip == DI_NODE_NIL) { return; } /* * This probably will need to change a bit when we understand the exact * shape AP's take when ACPI-based PCI hotplug is supported for virtual * machines. In that world, it's not clear where the AP will live. */ const char *drv = di_driver_name(pdip); if (drv == NULL || strcmp(drv, "pcieb") != 0) { return; } /* * The AP names property is a bitfield that indicates which entry in the * slot-names. A given device can in theory support multiple slots, but * PCIe is always going to be a value of 1. */ if (di_prop_lookup_ints(DDI_DEV_T_ANY, pdip, "ap-names", &ap_names) != 1 || *ap_names != 1) { return; } /* * The slot-names property is structured with the first word as a * bitfield mask and then a series of names which are guaranteed to be * NULL terminated. */ int nu32 = di_prop_lookup_ints(DDI_DEV_T_ANY, pdip, "slot-names", &slot_names); if (nu32 <= 1 || slot_names[0] != 1) { return; } oarg->nloa_ap = calloc(nu32 - 1, sizeof (uint32_t)); if (oarg->nloa_ap == NULL) { err(-1, "failed to allocate memory for attachment point name"); } (void) memcpy(oarg->nloa_ap, &slot_names[1], sizeof (uint32_t) * (nu32 - 1)); } /* * NVMe devices are always enumerated with an 'nvme' node. See if we can find * that. */ static int do_list_phys_map_loc_cb(topo_hdl_t *hp, tnode_t *tn, void *arg) { nvmeadm_list_ofmt_arg_t *oarg = arg; char *label = NULL; uint32_t tinst; int err; if (strcmp(topo_node_name(tn), NVME) != 0) { return (TOPO_WALK_NEXT); } if (topo_prop_get_uint32(tn, TOPO_PGROUP_IO, TOPO_IO_INSTANCE, &tinst, &err) != 0) { return (TOPO_WALK_NEXT); } if (tinst != (uint32_t)di_instance(oarg->nloa_dip)) { return (TOPO_WALK_NEXT); } /* * Walk the node hierarchy looking for something that has a label. We * basically only consider ourselves and our immediate parent if that * parent is a bay or slot. If we go too far then we may end up finding * a label for a device that has no relationship to us. */ if (topo_prop_get_string(tn, TOPO_PGROUP_PROTOCOL, TOPO_PROP_LABEL, &label, &err) != 0) { tnode_t *pn = topo_node_parent(tn); const char *pname = topo_node_name(pn); if (strcmp(pname, BAY) == 0 || strcmp(pname, SLOT) == 0) { if (topo_prop_get_string(pn, TOPO_PGROUP_PROTOCOL, TOPO_PROP_LABEL, &label, &err) != 0) { label = NULL; } } } oarg->nloa_loc = label; return (TOPO_WALK_TERMINATE); } static void do_list_phys_map_loc(nvmeadm_list_t *list, nvmeadm_list_ofmt_arg_t *oarg) { topo_walk_t *wp; int err; wp = topo_walk_init(list->list_topo, FM_FMRI_SCHEME_HC, do_list_phys_map_loc_cb, oarg, &err); if (wp == NULL) { warnx("location information not available: failed to " "initialize topo walk: %s", topo_strerror(err)); exitcode = -1; return; } err = topo_walk_step(wp, TOPO_WALK_SIBLING); if (err == TOPO_WALK_ERR) { warnx("location information not available: topo walk failed"); exitcode = -1; } topo_walk_fini(wp); } static int do_list(const nvme_process_arg_t *npa) { nvme_ctrl_info_t *info = NULL; nvme_ns_iter_t *iter = NULL; nvme_iter_t ret; const nvme_ns_disc_t *disc; nvme_ns_disc_level_t level; int rv = -1; nvmeadm_list_t *list = npa->npa_cmd_arg; if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if (!nvme_ctrl_info_snap(npa->npa_ctrl, &info)) { nvmeadm_warn(npa, "failed to get controller information for %s", npa->npa_ctrl_name); return (-1); } if (npa->npa_ofmt == NULL) { (void) printf("%s: ", npa->npa_ctrl_name); nvme_print_ctrl_summary(info); } else if (list->list_mode != NVMEADM_LIST_DEFAULT) { nvmeadm_list_ofmt_arg_t oarg = { 0 }; oarg.nloa_name = npa->npa_ctrl_name; oarg.nloa_ctrl = info; if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip)) oarg.nloa_dip = DI_NODE_NIL; if (list->list_mode == NVMEADM_LIST_LOC) { do_list_phys_map_ctlap(&oarg); do_list_phys_map_loc(list, &oarg); } ofmt_print(npa->npa_ofmt, &oarg); free(oarg.nloa_ap); if (list->list_topo != NULL) { topo_hdl_strfree(list->list_topo, oarg.nloa_loc); } } if (list->list_mode != NVMEADM_LIST_DEFAULT) { rv = 0; goto out; } /* * Check if we were given an explicit namespace as an argument. If so, * we always list it and don't need to do discovery. */ if (npa->npa_ns != NULL) { nvme_ns_info_t *ns_info; if (!nvme_ns_info_snap(npa->npa_ns, &ns_info)) { nvmeadm_warn(npa, "failed to get namespace " "information for %s", npa->npa_name); goto out; } do_list_nsid(npa, info, ns_info); nvme_ns_info_free(ns_info); rv = 0; goto out; } if (verbose) { level = NVME_NS_DISC_F_ALL; } else { level = NVME_NS_DISC_F_NOT_IGNORED; } if (!nvme_ns_discover_init(npa->npa_ctrl, level, &iter)) { nvmeadm_warn(npa, "failed to iterate namespaces on %s", npa->npa_ctrl_name); goto out; } while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) { nvme_ns_info_t *ns_info; uint32_t nsid = nvme_ns_disc_nsid(disc); if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, nsid, &ns_info)) { nvmeadm_warn(npa, "failed to get namespace " "information for %s/%u", npa->npa_ctrl_name, nsid); exitcode = -1; continue; } do_list_nsid(npa, info, ns_info); nvme_ns_info_free(ns_info); } nvme_ns_discover_fini(iter); if (ret == NVME_ITER_ERROR) { nvmeadm_warn(npa, "failed to iterate all namespaces on %s", npa->npa_ctrl_name); } else { rv = 0; } out: nvme_ctrl_info_free(info); return (rv); } static void optparse_identify_ctrl(nvme_process_arg_t *npa) { int c; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacn")) != -1) { switch (c) { case 'C': npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS; break; case 'a': npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS; break; case 'c': npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST; break; case 'n': npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } } static void usage_identify_ctrl(const char *c_name) { (void) fprintf(stderr, "%s [-C | -c | [-a] -n] [,...]\n\n" " Print detailed information about the specified NVMe " "controllers.\n", c_name); } static int do_identify_ctrl(const nvme_process_arg_t *npa) { boolean_t alloc = B_FALSE; if (npa->npa_ns != NULL) errx(-1, "identify-controller cannot be used on namespaces"); if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) { errx(-1, "-C cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) { errx(-1, "-c cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 && npa->npa_cmdflags != (NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) { errx(-1, "-a can only be used together with -n"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) { alloc = B_TRUE; } if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) { const nvme_identify_nsid_t *idns; if (!nvme_ctrl_info_common_ns(npa->npa_ctrl_info, &idns)) { nvmeadm_ctrl_info_warn(npa, "failed to get common " "namespace information for %s", npa->npa_name); return (-1); } (void) printf("%s: ", npa->npa_name); nvme_print_identify_nsid(idns, npa->npa_version); } else if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) { const char *caption; uint32_t cns; nvme_identify_nsid_list_t *idnslist; nvme_id_req_t *req; if (alloc) { caption = "Identify Allocated Namespace List"; cns = NVME_IDENTIFY_NSID_ALLOC_LIST; } else { caption = "Identify Active Namespace List"; cns = NVME_IDENTIFY_NSID_LIST; } if ((idnslist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) { err(-1, "failed to allocate identify buffer size"); } if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, cns, &req)) { nvmeadm_fatal(npa, "failed to initialize %s request", caption); } /* * Always set the NSID for these requests to NSID 0 so that way * we can start the list at the beginning. When we encounter * devices with more than 1024 NSIDs then we'll need to issue * additional requests. */ if (!nvme_id_req_set_nsid(req, 0) || !nvme_id_req_set_output(req, idnslist, NVME_IDENTIFY_BUFSIZE)) { nvmeadm_fatal(npa, "failed to set required fields for " "identify request"); } if (!nvme_id_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute identify " "request"); } nvme_id_req_fini(req); (void) printf("%s: ", npa->npa_name); nvme_print_identify_nsid_list(caption, idnslist); free(idnslist); } else if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) { nvme_identify_ctrl_list_t *ctlist; nvme_id_req_t *req; if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) { err(-1, "failed to allocate identify buffer size"); } if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, NVME_IDENTIFY_CTRL_LIST, &req)) { nvmeadm_fatal(npa, "failed to initialize identify " "request"); } if (!nvme_id_req_set_ctrlid(req, 0) || !nvme_id_req_set_output(req, ctlist, NVME_IDENTIFY_BUFSIZE)) { nvmeadm_fatal(npa, "failed to set required fields for " "identify request"); } if (!nvme_id_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute identify " "request"); } nvme_id_req_fini(req); (void) printf("%s: ", npa->npa_name); nvme_print_identify_ctrl_list("Identify Controller List", ctlist); free(ctlist); } else { uint32_t mpsmin; if (!nvme_ctrl_info_pci_mps_min(npa->npa_ctrl_info, &mpsmin)) { nvmeadm_ctrl_info_fatal(npa, "failed to get minimum " "memory page size"); } (void) printf("%s: ", npa->npa_name); nvme_print_identify_ctrl(npa->npa_idctl, mpsmin, npa->npa_version); } return (0); } static void optparse_identify_ns(nvme_process_arg_t *npa) { int c; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cd")) != -1) { switch (c) { case 'c': npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST; break; case 'd': npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } } static void usage_identify_ns(const char *c_name) { (void) fprintf(stderr, "%s [-c | -d ] /[,...]\n\n" " Print detailed information about the specified NVMe " "namespaces.\n", c_name); } static int do_identify_ns(const nvme_process_arg_t *npa) { uint32_t nsid; if (npa->npa_ns == NULL) errx(-1, "identify-namespace cannot be used on controllers"); if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) { errx(-1, "-c cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) { errx(-1, "-d cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) { errx(-1, "-a cannot be used on namespaces"); } nsid = nvme_ns_info_nsid(npa->npa_ns_info); if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) { nvme_identify_ctrl_list_t *ctlist; nvme_id_req_t *req; if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) { err(-1, "failed to allocate identify buffer size"); } if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, NVME_IDENTIFY_NSID_CTRL_LIST, &req)) { nvmeadm_fatal(npa, "failed to initialize identify " "request"); } if (!nvme_id_req_set_nsid(req, nsid) || !nvme_id_req_set_ctrlid(req, 0) || !nvme_id_req_set_output(req, ctlist, NVME_IDENTIFY_BUFSIZE)) { nvmeadm_fatal(npa, "failed to set required fields for " "identify request"); } if (!nvme_id_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute identify " "request"); } nvme_id_req_fini(req); (void) printf("%s: ", npa->npa_name); nvme_print_identify_ctrl_list( "Identify Attached Controller List", ctlist); free(ctlist); } else if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) { nvme_identify_nsid_desc_t *nsdesc; nvme_id_req_t *req; if ((nsdesc = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) { err(-1, "failed to allocate identify buffer size"); } if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, NVME_IDENTIFY_NSID_DESC, &req)) { nvmeadm_fatal(npa, "failed to initialize identify " "request"); } if (!nvme_id_req_set_nsid(req, nsid) || !nvme_id_req_set_output(req, nsdesc, NVME_IDENTIFY_BUFSIZE)) { nvmeadm_fatal(npa, "failed to set required fields for " "identify request"); } if (!nvme_id_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute identify " "request"); } nvme_id_req_fini(req); (void) printf("%s: ", npa->npa_name); nvme_print_identify_nsid_desc(nsdesc); free(nsdesc); } else { const nvme_identify_nsid_t *idns; (void) printf("%s: ", npa->npa_name); idns = nvme_ns_info_identify(npa->npa_ns_info); nvme_print_identify_nsid(idns, npa->npa_version); } return (0); } static void optparse_identify(nvme_process_arg_t *npa) { int c; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacdn")) != -1) { switch (c) { case 'C': npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS; break; case 'a': npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS; break; case 'c': npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST; break; case 'd': npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST; break; case 'n': npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 && (npa->npa_cmdflags & ~(NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) != 0) { errx(-1, "-a can only be used alone or together with -n"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) { errx(-1, "-C cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) { errx(-1, "-c cannot be combined with other flags"); } if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 && npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) { errx(-1, "-d cannot be combined with other flags"); } } static void usage_identify(const char *c_name) { (void) fprintf(stderr, "%s [ -C | -c | -d | [-a] -n ] [/][,...]\n\n" " Print detailed information about the specified NVMe " "controllers and/or name-\n spaces.\n", c_name); } static int do_identify(const nvme_process_arg_t *npa) { if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if (npa->npa_ns != NULL) { if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) errx(-1, "-C cannot be used on namespaces"); if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) errx(-1, "-a cannot be used on namespaces"); if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) errx(-1, "-n cannot be used on namespaces"); return (do_identify_ns(npa)); } else { if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) errx(-1, "-d cannot be used on controllers"); return (do_identify_ctrl(npa)); } } static void optparse_list_logs(nvme_process_arg_t *npa) { int c; uint_t oflags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; char *scope = NULL; ofmt_status_t oferr; nvmeadm_list_logs_t *nll; if ((nll = calloc(1, sizeof (nvmeadm_list_logs_t))) == NULL) { err(-1, "failed to allocate memory to track log information"); } npa->npa_cmd_arg = nll; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:ps:")) != -1) { switch (c) { case 'a': nll->nll_unimpl = B_TRUE; break; case 'H': oflags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; oflags |= OFMT_PARSABLE; break; case 's': scope = optarg; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (!parse) { oflags |= OFMT_WRAP; } if (parse && fields == NULL) { errx(-1, "parsable mode (-p) requires fields specified with " "-o"); } if (fields == NULL) { if (nll->nll_unimpl) { fields = nvmeadm_list_logs_fields_impl; } else { fields = nvmeadm_list_logs_fields; } } if (scope != NULL) { const char *str; while ((str = strsep(&scope, ",")) != NULL) { if (strcasecmp(str, "nvm") == 0) { nll->nll_scope |= NVME_LOG_SCOPE_NVM; } else if (strcasecmp(str, "ns") == 0 || strcasecmp(str, "namespace") == 0) { nll->nll_scope |= NVME_LOG_SCOPE_NS; } else if (strcasecmp(str, "ctrl") == 0 || strcasecmp(str, "controller") == 0) { nll->nll_scope |= NVME_LOG_SCOPE_CTRL; } else { errx(-1, "unknown scope string: '%s'; valid " "values are 'nvm', 'namespace', and " "'controller'", str); } } } oferr = ofmt_open(fields, nvmeadm_list_logs_ofmt, oflags, 0, &npa->npa_ofmt); ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx); if (npa->npa_argc - optind > 1) { nll->nll_nfilts = npa->npa_argc - optind - 1; nll->nll_filts = npa->npa_argv + optind + 1; nll->nll_used = calloc(nll->nll_nfilts, sizeof (boolean_t)); if (nll->nll_used == NULL) { err(-1, "failed to allocate memory for tracking log " "page filters"); } } } static void usage_list_logs(const char *c_name) { (void) fprintf(stderr, "%s [-H] [-o field,[...] [-p]] [-s scope,[...]] " "[-a]\n\t [/][,...] [logpage...]\n\n" " List log pages supported by controllers or namespaces.\n", c_name); } static boolean_t do_list_logs_match(const nvme_log_disc_t *disc, nvmeadm_list_logs_t *nll) { if (!nll->nll_unimpl && !nvme_log_disc_impl(disc)) { return (B_FALSE); } if (nll->nll_nfilts <= 0) { return (B_TRUE); } /* * Check all filters. If we have multiple specified that would match we * want that to count. */ boolean_t match = B_FALSE; for (int i = 0; i < nll->nll_nfilts; i++) { if (strcmp(nvme_log_disc_name(disc), nll->nll_filts[i]) == 0) { nll->nll_used[i] = B_TRUE; match = B_TRUE; continue; } size_t naliases = nvme_log_disc_naliases(disc); const char *const *aliases = nvme_log_disc_aliases(disc); for (size_t a = 0; a < naliases; a++) { if (strcmp(aliases[a], nll->nll_filts[i]) == 0) { nll->nll_used[i] = B_TRUE; match = B_TRUE; break; } } } return (match); } static int do_list_logs(const nvme_process_arg_t *npa) { nvme_log_disc_scope_t scope; nvme_log_iter_t *iter; nvme_iter_t ret; const nvme_log_disc_t *disc; nvmeadm_list_logs_t *nll = npa->npa_cmd_arg; if (nll->nll_scope != 0) { scope = nll->nll_scope; } else if (npa->npa_ns != NULL) { scope = NVME_LOG_SCOPE_NS; } else { scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM; } if (!nvme_log_discover_init(npa->npa_ctrl, scope, 0, &iter)) { nvmeadm_warn(npa, "failed to iterate logs on %s", npa->npa_ctrl_name); return (-1); } while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) { if (do_list_logs_match(disc, nll)) { nvmeadm_list_logs_ofmt_arg_t print; print.nlloa_name = npa->npa_name; print.nlloa_disc = disc; ofmt_print(npa->npa_ofmt, &print); nll->nll_nprint++; } } nvme_log_discover_fini(iter); if (ret == NVME_ITER_ERROR) { nvmeadm_warn(npa, "failed to iterate logs on %s", npa->npa_ctrl_name); return (-1); } for (int i = 0; i < nll->nll_nfilts; i++) { if (!nll->nll_used[i]) { warnx("log page filter '%s' did match any log pages", nll->nll_filts[i]); exitcode = -1; } } if (nll->nll_nprint == 0) { if (nll->nll_nfilts == 0) { warnx("no log pages found for %s", npa->npa_name); } exitcode = -1; } return (exitcode); } static void usage_get_logpage(const char *c_name) { (void) fprintf(stderr, "%s [-O file | -x | -p -o field,[...] [-H]]\n" "\t [/][,...] [filter...]\n\n" " Print the specified log page of the specified NVMe " "controllers and/or name-\n spaces. Run \"nvmeadm list-logpages\" " "for supported log pages. All devices\n support error, health, " "and firmware.\n", c_name); } static void usage_firmware_list(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Print the log page that contains the list of firmware " "images installed on the specified NVMe controller.\n", c_name); } static uint64_t do_get_logpage_size(const nvme_process_arg_t *npa, nvme_log_disc_t *disc, nvme_log_req_t *req) { uint64_t len, ret; void *buf; nvme_log_size_kind_t kind; kind = nvme_log_disc_size(disc, &len); if (kind != NVME_LOG_SIZE_K_VAR) { return (len); } /* * We have a log with a variable length size. To determine the actual * size we must actually determine the full length of this. */ if ((buf = malloc(len)) == NULL) { errx(-1, "failed to allocate %zu byte buffer to get log " "page size", len); } if (!nvme_log_req_set_output(req, buf, len)) { nvmeadm_fatal(npa, "failed to set output parameters to " "determine log length"); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute log request %s to " "determine log length", npa->npa_argv[0]); } if (!nvme_log_disc_calc_size(disc, &ret, buf, len)) { errx(-1, "failed to determine full %s log length", npa->npa_argv[0]); } free(buf); return (ret); } static void do_get_logpage_dump(const void *buf, size_t len, const char *file) { size_t off = 0; int fd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644); if (fd < 0) { err(-1, "failed to create output file %s", file); } while (len > 0) { ssize_t ret = write(fd, buf + off, len - off); if (ret < 0) { err(EXIT_FAILURE, "failed to write log data to file %s " "at offset %zu", file, off); } off += (size_t)ret; len -= (size_t)ret; } (void) close(fd); } /* * Here we need to explicitly attempt to release any context that has previously * existed for the persistent event log. It is fine if none exists as the * controller is required not to error. However, if we don't do this and attempt * to establish a new context, then it will generate an error. * * We'll use our existing request, which doesn't ask for data yet and issue the * get log page request with the LSP in question. After it is completed, we'll * reset the LSP to establish a context. */ static void do_get_logpage_pev_relctx(const nvme_process_arg_t *npa, nvme_log_req_t *req) { uint32_t buf; if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_REL_CTX)) { nvmeadm_fatal(npa, "failed to set lsp to release the " "persistent event log context"); } /* * In NVMe 2.0 the spec made it explicit that the controller was * supposed to ignore the length and offset for a request to release the * context; however, that wasn't present in NVMe 1.4. The number of * dwords part of the get log page command is a zeros based value, * meaning there is no explicit way to request zero bytes. Rather than * trust that all controllers get this right (especially when it wasn't * exactly specified in NVMe 1.4), we just toss a throwaway buffer here. */ if (!nvme_log_req_set_output(req, &buf, sizeof (buf))) { nvmeadm_fatal(npa, "failed to set zero log length for " "persistent event log release context"); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute log request %s to " "release the event log context", npa->npa_argv[0]); } if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_EST_CTX_READ)) { nvmeadm_fatal(npa, "failed to set lsp to establish the " "persistent event log context"); } /* * Make sure that our stack buffer is no longer part of the log request. */ if (!nvme_log_req_clear_output(req)) { nvmeadm_fatal(npa, "failed to clear output from persistent " "event log release context"); } } /* * Fill in the baseline context for a phyeye request. In this case we always * perform a normal read command and set the quality to "good" (the * lowest quality). Configurable use of this is driven through the phyeye * commands in nvmeadm_phyeye.c. In addition, we must always set the controller * ID in this request to our own. */ static void do_get_logpage_phyeye_ctx(const nvme_process_arg_t *npa, nvme_log_req_t *req) { nvme_eom_lsp_t lsp; (void) memset(&lsp, 0, sizeof (lsp)); lsp.nel_mqual = NVME_EOM_LSP_MQUAL_GOOD; lsp.nel_act = NVME_EOM_LSP_READ; if (!nvme_log_req_set_lsp(req, lsp.r)) { nvmeadm_fatal(npa, "failed to set lsp for host phyeye"); } if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) { nvmeadm_fatal(npa, "failed to set lsi for host phyeye"); } } static int do_get_logpage_common(const nvme_process_arg_t *npa, const char *page, nvmeadm_field_filt_t *filts, size_t nfilts) { int ret = 0; nvme_log_disc_t *disc; nvme_log_req_t *req; nvme_log_disc_scope_t scope; void *buf; size_t toalloc; nvmeadm_get_logpage_t *log = npa->npa_cmd_arg; /* * If we have enough information to identify a log-page via libnvme (or * in the future take enough options to allow us to actually do this * manually), then we will fetch it. If we don't know how to print it, * then we'll just hex dump it for now. */ if (!nvme_log_req_init_by_name(npa->npa_ctrl, page, 0, &disc, &req)) { nvmeadm_fatal(npa, "could not initialize log request for %s", page); } if (npa->npa_ns != NULL) { scope = NVME_LOG_SCOPE_NS; } else { scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM; } if ((scope & nvme_log_disc_scopes(disc)) == 0) { errx(-1, "log page %s does not support operating on %s", page, npa->npa_ns != NULL ? "namespaces" : "controllers"); } /* * In the future we should add options to allow one to specify and set * the fields for the lsp, lsi, etc. and set them here. Some log pages * need a specific lsp set and special handling related to contexts. Do * that now. */ switch (nvme_log_disc_lid(disc)) { case NVME_LOGPAGE_PEV: do_get_logpage_pev_relctx(npa, req); break; case NVME_LOGPAGE_TELMHOST: return (do_get_logpage_telemetry(npa, disc, req)); case NVME_LOGPAGE_PHYEYE: do_get_logpage_phyeye_ctx(npa, req); break; default: break; } if (npa->npa_ns != NULL) { uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (!nvme_log_req_set_nsid(req, nsid)) { nvmeadm_fatal(npa, "failed to set log request " "namespace ID to 0x%x", nsid); } } /* * The output size should be the last thing that we determine as we may * need to issue a log request to figure out how much data we should * actually be reading. */ toalloc = do_get_logpage_size(npa, disc, req); buf = malloc(toalloc); if (buf == NULL) { err(-1, "failed to allocate %zu bytes for log " "request %s", toalloc, page); } if (!nvme_log_req_set_output(req, buf, toalloc)) { nvmeadm_fatal(npa, "failed to set output parameters"); } /* * Again, we need to potentially adjust specific LSP values here for the * various contexts that exist. Note that we are reusing the existing * request so if the prior values for the initial request are fine, then * we can just leave it there. */ switch (nvme_log_disc_lid(disc)) { case NVME_LOGPAGE_PEV: if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_READ)) { nvmeadm_fatal(npa, "failed to set lsp to read the " "persistent event log"); } break; default: break; } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute log request %s", npa->npa_argv[0]); } if (log != NULL && log->ngl_output != NULL) { do_get_logpage_dump(buf, toalloc, log->ngl_output); goto done; } else if (log != NULL && log->ngl_hex) { nvmeadm_dump_hex(buf, toalloc); goto done; } if (npa->npa_ofmt == NULL) { (void) printf("%s: ", npa->npa_name); } if (strcmp(page, "error") == 0) { size_t nlog = toalloc / sizeof (nvme_error_log_entry_t); if (nfilts > 0) { warnx("log filters are not currently supported with " "the %s log page", page); ret = -1; } nvme_print_error_log(nlog, buf, npa->npa_version); } else if (strcmp(page, "health") == 0) { if (nfilts > 0) { warnx("log filters are not currently supported with " "the %s log page", page); ret = -1; } nvme_print_health_log(buf, npa->npa_idctl, npa->npa_version); } else if (strcmp(page, "firmware") == 0) { if (nfilts > 0) { warnx("log filters are not currently supported with " "the %s log page", page); ret = -1; } nvme_print_fwslot_log(buf, npa->npa_idctl); } else { if (npa->npa_ofmt == NULL) { (void) printf("%s (%s)\n", nvme_log_disc_desc(disc), page); } if (!nvmeadm_log_page_fields(npa, page, buf, toalloc, filts, nfilts, 0)) { ret = -1; } } done: free(buf); nvme_log_disc_free(disc); nvme_log_req_fini(req); return (ret); } static int do_get_logpage_fwslot(const nvme_process_arg_t *npa) { if (npa->npa_argc >= 1) { warnx("no additional arguments may be specified to %s", npa->npa_cmd->c_name); usage(npa->npa_cmd); exit(-1); } return (do_get_logpage_common(npa, "firmware", NULL, 0)); } static void optparse_get_logpage(nvme_process_arg_t *npa) { int c; const char *fields = NULL; bool parse = false; uint_t oflags = 0; nvmeadm_get_logpage_t *log; uint_t count = 0; if ((log = calloc(1, sizeof (nvmeadm_get_logpage_t))) == NULL) { err(-1, "failed to allocate memory to track log page " "information"); } while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Ho:O:px")) != -1) { switch (c) { case 'H': oflags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'O': log->ngl_output = optarg; count++; break; case 'p': parse = true; oflags |= OFMT_PARSABLE; count++; break; case 'x': log->ngl_hex = true; count++; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (parse && fields == NULL) { errx(-1, "parsable mode (-p) requires fields specified with " "-o"); } if (!parse && fields != NULL) { errx(-1, "output field selection (-o) only usable in " "parsable mode (-p)"); } if (!parse && (oflags & OFMT_NOHEADER) != 0) { errx(-1, "omitting headers (-H) only usable in parsable mode " "(-p)"); } if (count > 1) { errx(-1, "only one of parsable output (-p), outputing to a " "file (-O), and hexadecimal output (-x) may be requested"); } if (parse) { ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt, oflags, 0, &npa->npa_ofmt); ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx); } npa->npa_cmd_arg = log; } static void do_logpage_filts(const nvme_process_arg_t *npa, nvmeadm_field_filt_t **filtsp, size_t *nfiltsp) { size_t nfilts; nvmeadm_field_filt_t *filts; *filtsp = NULL; *nfiltsp = 0; if (npa->npa_argc <= 1) return; nfilts = (size_t)npa->npa_argc - 1; filts = calloc(nfilts, sizeof (nvmeadm_field_filt_t)); if (filts == NULL) { err(-1, "failed to allocate memory for filter " "tracking"); } for (size_t i = 0; i < nfilts; i++) { filts[i].nff_len = strlen(npa->npa_argv[i + 1]); filts[i].nff_str = npa->npa_argv[i + 1]; } *filtsp = filts; *nfiltsp = nfilts; } static int do_get_logpage(const nvme_process_arg_t *npa) { size_t nfilts = 0; nvmeadm_field_filt_t *filts = NULL; nvmeadm_get_logpage_t *log = npa->npa_cmd_arg; if (npa->npa_argc < 1) { warnx("missing log page name"); usage(npa->npa_cmd); exit(-1); } do_logpage_filts(npa, &filts, &nfilts); if (log != NULL && log->ngl_hex && nfilts > 0) { errx(-1, "hexadecimal output (-x) cannot be used with " "log filter operands"); } return (do_get_logpage_common(npa, npa->npa_argv[0], filts, nfilts)); } static void usage_print_logpage(const char *c_name) { (void) fprintf(stderr, "%s -f file [-x | -p -o field,[...] [-H]] " "\n\t [filter...]\n\n" " Print the specified log page from a file. Optional filters " "may be used to\n restrict the log fields printed. See nvmeadm(8) " "for a list of all log\n pages. Run \"nvmeadm list-logpages\" to " "see log pages a specific controller\n supports.\n", c_name); } typedef struct { const char *pl_input; bool pl_hex; } print_logpages_t; static void optparse_print_logpage(nvme_process_arg_t *npa) { int c; const char *fields = NULL; bool parse = false; uint_t oflags = 0; print_logpages_t *pl; if ((pl = calloc(1, sizeof (print_logpages_t))) == NULL) { err(-1, "failed to allocate memory for option tracking"); } while ((c = getopt(npa->npa_argc, npa->npa_argv, ":f:Ho:px")) != -1) { switch (c) { case 'f': pl->pl_input = optarg; break; case 'H': oflags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = true; oflags |= OFMT_PARSABLE; break; case 'x': pl->pl_hex = true; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (pl->pl_input == NULL) { errx(-1, "missing required input file to process (-f)"); } if (parse && fields == NULL) { errx(-1, "parsable mode (-p) requires fields specified with " "-o"); } if (!parse && fields != NULL) { errx(-1, "output field selection (-o) only usable in " "parsable mode (-p)"); } if (!parse && (oflags & OFMT_NOHEADER) != 0) { errx(-1, "omitting headers (-H) only usable in parsable mode " "(-p)"); } if (pl->pl_hex && parse) { errx(-1, "only one of parsable output (-p) and heaxdecimal " "output (-x) may be requested"); } if (parse) { ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt, oflags, 0, &npa->npa_ofmt); ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx); } npa->npa_cmd_arg = pl; } static int do_print_logpage(const nvme_process_arg_t *npa) { int fd = -1, ret = 0; struct stat st; void *data; const char *logpage = NULL; size_t nfilts = 0; nvmeadm_field_filt_t *filts = NULL; print_logpages_t *pl = npa->npa_cmd_arg; nvmeadm_log_field_flag_t flag = 0; if (pl->pl_hex) { if (npa->npa_argc != 0) { errx(-1, "log page and filters not supported with -x"); } } else { if (npa->npa_argc == 0) { errx(-1, "missing required log page"); } logpage = npa->npa_argv[0]; do_logpage_filts(npa, &filts, &nfilts); flag |= NVMEADM_LFF_CHECK_NAME; } if ((fd = open(pl->pl_input, O_RDONLY)) < 0) { err(-1, "failed to open input file %s", pl->pl_input); } if (fstat(fd, &st) != 0) { err(-1, "failed to stat %s", pl->pl_input); } if (st.st_size > NVMEADM_MAX_MMAP) { errx(-1, "%s file size of 0x%lx exceeds maximum allowed size " "of 0x%llx", pl->pl_input, st.st_size, NVMEADM_MAX_MMAP); } data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { errx(-1, "failed to mmap %s", pl->pl_input); } if (!nvmeadm_log_page_fields(npa, logpage, data, st.st_size, filts, nfilts, flag)) { ret = -1; } VERIFY0(munmap(data, st.st_size)); VERIFY0(close(fd)); free(filts); return (ret); } static void optparse_list_features(nvme_process_arg_t *npa) { int c; uint_t oflags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; nvmeadm_features_t *feat; ofmt_status_t oferr; if ((feat = calloc(1, sizeof (nvmeadm_features_t))) == NULL) { err(-1, "failed to allocate memory to track feature " "information"); } npa->npa_cmd_arg = feat; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:p")) != -1) { switch (c) { case 'a': feat->nf_unimpl = B_TRUE; break; case 'H': oflags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; oflags |= OFMT_PARSABLE; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (!parse) { oflags |= OFMT_WRAP; } if (parse && fields == NULL) { errx(-1, "parsable mode (-p) requires fields specified with " "-o"); } if (fields == NULL) { fields = nvmeadm_list_features_fields; } oferr = ofmt_open(fields, nvmeadm_list_features_ofmt, oflags, 0, &npa->npa_ofmt); ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx); if (npa->npa_argc - optind > 1) { feat->nf_nfilts = (uint32_t)(npa->npa_argc - optind - 1); feat->nf_filts = npa->npa_argv + optind + 1; feat->nf_used = calloc(feat->nf_nfilts, sizeof (boolean_t)); if (feat->nf_used == NULL) { err(-1, "failed to allocate memory for tracking " "feature filters"); } } } static void usage_list_features(const char *c_name) { (void) fprintf(stderr, "%s [-a] [-H] [-o field,[...] [-p]] " "[/][,...]\n\t [feature...]\n\n" " List features supported by controllers or namespaces.\n", c_name); } static boolean_t do_features_match(const nvme_feat_disc_t *disc, nvmeadm_features_t *nf) { if (nf->nf_nfilts == 0) { return (B_TRUE); } for (uint32_t i = 0; i < nf->nf_nfilts; i++) { const char *match = nf->nf_filts[i]; long long fid; const char *err; if (strcmp(nvme_feat_disc_short(disc), match) == 0 || strcasecmp(nvme_feat_disc_spec(disc), match) == 0) { nf->nf_used[i] = B_TRUE; return (B_TRUE); } fid = strtonumx(match, 0, UINT32_MAX, &err, 0); if (err == NULL && fid == nvme_feat_disc_fid(disc)) { nf->nf_used[i] = B_TRUE; return (B_TRUE); } } return (B_FALSE); } /* * This is a common entry point for both list-features and get-features, which * iterate over all features and take action for each one. */ typedef void (*do_features_cb_f)(const nvme_process_arg_t *, const nvme_feat_disc_t *); static int do_features(const nvme_process_arg_t *npa, nvmeadm_features_t *nf, do_features_cb_f func) { nvme_feat_scope_t scope; nvme_feat_iter_t *iter; nvme_iter_t ret; const nvme_feat_disc_t *disc; if (npa->npa_ns != NULL) { scope = NVME_FEAT_SCOPE_NS; } else { scope = NVME_FEAT_SCOPE_CTRL; } if (!nvme_feat_discover_init(npa->npa_ctrl, scope, 0, &iter)) { nvmeadm_warn(npa, "failed to iterate features on %s", npa->npa_ctrl_name); return (-1); } while ((ret = nvme_feat_discover_step(iter, &disc)) == NVME_ITER_VALID) { if (do_features_match(disc, nf)) { if (!nf->nf_unimpl && nvme_feat_disc_impl(disc) == NVME_FEAT_IMPL_UNSUPPORTED) { continue; } func(npa, disc); nf->nf_nprint++; } } nvme_feat_discover_fini(iter); if (ret == NVME_ITER_ERROR) { nvmeadm_warn(npa, "failed to iterate features on %s", npa->npa_ctrl_name); return (-1); } for (uint32_t i = 0; i < nf->nf_nfilts; i++) { if (!nf->nf_used[i]) { warnx("feature filter '%s' did match any features", nf->nf_filts[i]); exitcode = -1; } } if (nf->nf_nprint == 0) { if (nf->nf_nfilts == 0) { warnx("no features found for %s", npa->npa_name); } exitcode = -1; } return (exitcode); } static void do_list_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc) { nvmeadm_list_features_ofmt_arg_t print; print.nlfoa_name = npa->npa_name; print.nlfoa_feat = disc; ofmt_print(npa->npa_ofmt, &print); } static int do_list_features(const nvme_process_arg_t *npa) { nvmeadm_features_t *nf = npa->npa_cmd_arg; return (do_features(npa, nf, do_list_features_cb)); } static void usage_get_features(const char *c_name) { (void) fprintf(stderr, "%s [/][,...] [[,...]]\n\n" " Print the specified features of the specified NVMe controllers " "and/or\n namespaces. Feature support varies on the controller.\n" "Run \"nvmeadm list-features \" to see supported features.\n", c_name); } /* * The nvmeadm(8) get-features output has traditionally swallowed certain errors * for features that it considers unimplemented in tandem with the kernel. With * the introduction of libnvme and ioctl interface changes, the kernel no longer * caches information about features that are unimplemented. * * There are two cases that we currently swallow errors on and the following * must all be true: * * 1) We have a controller error. * 2) The system doesn't know whether the feature is implemented or not. * 3) The controller error indicates that we have an invalid field. * * There is one additional wrinkle that we are currently papering over due to * the history of nvmeadm swallowing errors. The error recovery feature was made * explicitly namespace-specific in NVMe 1.4. However, various NVMe 1.3 devices * will error if we ask for it without specifying a namespace. Conversely, older * devices will be upset if you do ask for a namespace. This case can be removed * once we better survey devices and come up with a heuristic for how to handle * this across older generations. * * If we add a single feature endpoint that gives flexibility over how the * feature are listed, then we should not swallow errors. */ static boolean_t swallow_get_feat_err(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc) { uint32_t sct, sc; if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_CONTROLLER) { return (B_FALSE); } nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc); if (nvme_feat_disc_impl(disc) == NVME_FEAT_IMPL_UNKNOWN && sct == NVME_CQE_SCT_GENERIC && sc == NVME_CQE_SC_GEN_INV_FLD) { return (B_TRUE); } if (nvme_feat_disc_fid(disc) == NVME_FEAT_ERROR && sct == NVME_CQE_SCT_GENERIC && (sc == NVME_CQE_SC_GEN_INV_FLD || sc == NVME_CQE_SC_GEN_INV_NS)) { return (B_TRUE); } return (B_FALSE); } static boolean_t do_get_feat_common(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc, uint32_t cdw11, uint32_t *cdw0, void **datap, size_t *lenp) { nvme_get_feat_req_t *req = NULL; void *data = NULL; uint64_t datalen = 0; nvme_get_feat_fields_t fields = nvme_feat_disc_fields_get(disc); if (!nvme_get_feat_req_init_by_disc(npa->npa_ctrl, disc, &req)) { nvmeadm_warn(npa, "failed to initialize get feature request " "for feature %s", nvme_feat_disc_short(disc)); exitcode = -1; goto err; } if ((fields & NVME_GET_FEAT_F_CDW11) != 0 && !nvme_get_feat_req_set_cdw11(req, cdw11)) { nvmeadm_warn(npa, "failed to set cdw11 to 0x%x for feature %s", cdw11, nvme_feat_disc_short(disc)); exitcode = -1; goto err; } if ((fields & NVME_GET_FEAT_F_DATA) != 0) { datalen = nvme_feat_disc_data_size(disc); VERIFY3U(datalen, !=, 0); data = malloc(datalen); if (data == NULL) { err(-1, "failed to allocate %zu bytes for feature %s " "data buffer", datalen, nvme_feat_disc_short(disc)); } if (!nvme_get_feat_req_set_output(req, data, datalen)) { nvmeadm_warn(npa, "failed to set output data for " "feature %s", nvme_feat_disc_short(disc)); exitcode = -1; goto err; } } if ((fields & NVME_GET_FEAT_F_NSID) != 0) { uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (!nvme_get_feat_req_set_nsid(req, nsid)) { nvmeadm_warn(npa, "failed to set nsid to 0x%x for " "feature %s", nsid, nvme_feat_disc_spec(disc)); exitcode = -1; goto err; } } if (!nvme_get_feat_req_exec(req)) { if (!swallow_get_feat_err(npa, disc)) { nvmeadm_warn(npa, "failed to get feature %s", nvme_feat_disc_spec(disc)); exitcode = -1; } goto err; } if (!nvme_get_feat_req_get_cdw0(req, cdw0)) { nvmeadm_warn(npa, "failed to get cdw0 result data for %s", nvme_feat_disc_spec(disc)); goto err; } *datap = data; *lenp = datalen; nvme_get_feat_req_fini(req); return (B_TRUE); err: free(data); nvme_get_feat_req_fini(req); return (B_FALSE); } static void do_get_feat_temp_thresh_one(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat, const char *label, uint16_t tmpsel, uint16_t thsel) { uint32_t cdw0; void *buf = NULL; size_t buflen; nvme_temp_threshold_t tt; tt.r = 0; tt.b.tt_tmpsel = tmpsel; tt.b.tt_thsel = thsel; /* * The printing function treats the buffer argument as the label to * print for this threshold. */ if (!do_get_feat_common(npa, disc, tt.r, &cdw0, &buf, &buflen)) { return; } feat->f_print(cdw0, (void *)label, 0, npa->npa_idctl, npa->npa_version); free(buf); } /* * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the * device and changed the main device to have a composite temperature sensor. As * a result, there is a set of thresholds for each sensor. In addition, they * added both an over-temperature and under-temperature threshold. Since most * devices don't actually implement all the sensors, we get the health page and * see which sensors have a non-zero value to determine how to proceed. */ static boolean_t do_get_feat_temp_thresh(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat) { nvme_log_req_t *req = NULL; nvme_log_disc_t *log_disc = NULL; size_t toalloc; void *buf = NULL; boolean_t ret = B_FALSE; const nvme_health_log_t *hlog; nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL); do_get_feat_temp_thresh_one(npa, disc, feat, "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER); if (!nvme_version_check(npa, &nvme_vers_1v2)) { return (B_TRUE); } if (!nvme_log_req_init_by_name(npa->npa_ctrl, "health", 0, &log_disc, &req)) { nvmeadm_warn(npa, "failed to initialize health log page " "request"); return (B_FALSE); } toalloc = do_get_logpage_size(npa, log_disc, req); buf = malloc(toalloc); if (buf == NULL) { err(-1, "failed to allocate %zu bytes for health log page", toalloc); } if (!nvme_log_req_set_output(req, buf, toalloc)) { nvmeadm_warn(npa, "failed to set output parameters for health " "log page"); goto out; } if (!nvme_log_req_exec(req)) { nvmeadm_warn(npa, "failed to retrieve the health log page"); goto out; } /* cast required to prove our intentionality to smatch */ hlog = (const nvme_health_log_t *)buf; do_get_feat_temp_thresh_one(npa, disc, feat, "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER); if (hlog->hl_temp_sensor_1 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 1 Over Temp. Threshold", 1, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 1 Under Temp. Threshold", 1, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_2 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 2 Over Temp. Threshold", 2, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 2 Under Temp. Threshold", 2, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_3 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 3 Over Temp. Threshold", 3, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 3 Under Temp. Threshold", 3, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_4 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 4 Over Temp. Threshold", 4, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 4 Under Temp. Threshold", 4, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_5 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 5 Over Temp. Threshold", 5, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 5 Under Temp. Threshold", 5, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_6 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 6 Over Temp. Threshold", 6, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 6 Under Temp. Threshold", 6, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_7 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 7 Over Temp. Threshold", 7, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 7 Under Temp. Threshold", 7, NVME_TEMP_THRESH_UNDER); } if (hlog->hl_temp_sensor_8 != 0) { do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 8 Over Temp. Threshold", 8, NVME_TEMP_THRESH_OVER); do_get_feat_temp_thresh_one(npa, disc, feat, "Temp. Sensor 8 Under Temp. Threshold", 8, NVME_TEMP_THRESH_UNDER); } ret = B_TRUE; out: nvme_log_req_fini(req); free(buf); return (ret); } static boolean_t do_get_feat_intr_vect(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat) { uint32_t nintrs; boolean_t ret = B_TRUE; if (!nvme_ctrl_info_pci_nintrs(npa->npa_ctrl_info, &nintrs)) { nvmeadm_ctrl_info_warn(npa, "failed to get interrupt count " "from controller %s information snapshot", npa->npa_name); return (B_FALSE); } nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL); for (uint32_t i = 0; i < nintrs; i++) { uint32_t cdw0; void *buf; size_t buflen; nvme_intr_vect_t vect; vect.r = 0; vect.b.iv_iv = i; if (!do_get_feat_common(npa, disc, vect.r, &cdw0, &buf, &buflen)) { ret = B_FALSE; continue; } feat->f_print(cdw0, buf, buflen, npa->npa_idctl, npa->npa_version); free(buf); } return (ret); } /* * We've been asked to print the following feature that the controller probably * supports. Find our internal feature information for this to see if we know * how to deal with it. */ static void do_get_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc) { const nvmeadm_feature_t *feat = NULL; uint32_t fid = nvme_feat_disc_fid(disc); nvme_get_feat_fields_t fields; void *data = NULL; size_t datalen = 0; uint32_t cdw0; for (size_t i = 0; i < ARRAY_SIZE(features); i++) { if (features[i].f_feature == fid) { feat = &features[i]; break; } } /* * Determine if we have enough logic in here to get and print the * feature. The vast majority of NVMe features only output a single * uint32_t in cdw0 and potentially a data buffer. As long as no input * arguments are required, then we can go ahead and get this and print * the data. If there is, then we will refuse unless we have a * particular function. If we have a specific get function, we expect it * to do all the printing. */ if (feat != NULL && feat->f_get != NULL) { if (!feat->f_get(npa, disc, feat)) { exitcode = -1; } return; } fields = nvme_feat_disc_fields_get(disc); if ((fields & NVME_GET_FEAT_F_CDW11) != 0) { warnx("unable to get feature %s due to missing nvmeadm(8) " "implementation logic", nvme_feat_disc_spec(disc)); exitcode = -1; return; } /* * We do not set exitcode on failure here so that way we can swallow * errors from unimplemented features. */ if (!do_get_feat_common(npa, disc, 0, &cdw0, &data, &datalen)) { return; } nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL); if (feat != NULL && feat->f_print != NULL) { feat->f_print(cdw0, data, datalen, npa->npa_idctl, npa->npa_version); } else { nvme_feat_output_t output = nvme_feat_disc_output_get(disc); nvme_print_feat_unknown(output, cdw0, data, datalen); } free(data); } /* * This is an entry point which prints every feature that we know about. We * often go to lengths to discover all the variable inputs that can be used for * a given feature that requires an argument in cdw11. Due to the semantics of * filtering being used for features and the need to print each feature, this is * not the place to add general field filtering or a means to request a specific * cdw11 argument or similar. Instead, a new get-feature which requires someone * to specify the short name for a feature and then allows particular fields to * be grabbed and arguments should be created instead. * * This uses the same general feature logic that underpins do_list_features() * and therefore we transform filter arguments into the same style used there. */ static int do_get_features(const nvme_process_arg_t *npa) { char *fstr = NULL; char **filts = NULL; boolean_t *used = NULL; nvmeadm_features_t nf; int ret; if (npa->npa_argc > 1) errx(-1, "unexpected arguments"); if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) < NVME_NS_DISC_F_ACTIVE) { errx(-1, "cannot get feature: namespace is inactive"); } /* * We always leave nf_unimpl set to false as we don't want to bother * trying to print a feature that we know the device doesn't support. */ (void) memset(&nf, 0, sizeof (nvmeadm_features_t)); /* * If we've been given a series of features to print, treat those as * filters on the features as we're walking them to determine which to * print or not. */ if (npa->npa_argc == 1) { char *f; uint32_t i; nf.nf_nfilts = 1; fstr = strdup(npa->npa_argv[0]); if (fstr == NULL) { err(-1, "failed to allocate memory to duplicate " "feature string"); } for (const char *c = strchr(fstr, ','); c != NULL; c = strchr(c + 1, ',')) { nf.nf_nfilts++; } filts = calloc(nf.nf_nfilts, sizeof (char *)); if (filts == NULL) { err(-1, "failed to allocate memory for filter list"); } i = 0; while ((f = strsep(&fstr, ",")) != NULL) { filts[i] = f; i++; } VERIFY3U(i, ==, nf.nf_nfilts); nf.nf_filts = filts; used = calloc(nf.nf_nfilts, sizeof (boolean_t)); if (used == NULL) { err(-1, "failed to allocate memory for filter use " "tracking"); } nf.nf_used = used; } (void) printf("%s: Get Features\n", npa->npa_name); ret = do_features(npa, &nf, do_get_features_cb); free(fstr); free(filts); free(used); return (ret); } static int do_format_common(const nvme_process_arg_t *npa, uint32_t lbaf, uint32_t ses) { int ret = 0; nvme_format_req_t *req; if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) < NVME_NS_DISC_F_ACTIVE) { errx(-1, "cannot %s: namespace is inactive", npa->npa_cmd->c_name); } if (!nvme_format_req_init(npa->npa_ctrl, &req)) { nvmeadm_fatal(npa, "failed to initialize format request for " "%s", npa->npa_name); } if (npa->npa_ns != NULL) { uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (!nvme_format_req_set_nsid(req, nsid)) { nvmeadm_fatal(npa, "failed to set format request " "namespace ID to 0x%x", nsid); } } if (!nvme_format_req_set_lbaf(req, lbaf) || !nvme_format_req_set_ses(req, ses)) { nvmeadm_fatal(npa, "failed to set format request fields for %s", npa->npa_name); } if (do_detach_bd(npa) != 0) { errx(-1, "cannot %s %s due to namespace detach failure", npa->npa_cmd->c_name, npa->npa_name); } if (!nvme_format_req_exec(req)) { nvmeadm_warn(npa, "failed to %s %s", npa->npa_cmd->c_name, npa->npa_name); ret = -1; } if (do_attach_bd(npa) != 0) ret = -1; return (ret); } static void usage_format(const char *c_name) { (void) fprintf(stderr, "%s [/] []\n\n" " Format one or all namespaces of the specified NVMe " "controller. Supported LBA\n formats can be queried with " "the \"%s identify\" command on the namespace\n to be " "formatted.\n", c_name, getprogname()); } static uint32_t do_format_determine_lbaf(const nvme_process_arg_t *npa) { const nvme_nvm_lba_fmt_t *fmt; nvme_ns_info_t *ns_info = NULL; uint32_t lbaf; if (npa->npa_argc > 0) { unsigned long lba; uint32_t nlbaf = nvme_ctrl_info_nformats(npa->npa_ctrl_info); errno = 0; lba = strtoul(npa->npa_argv[0], NULL, 10); if (errno != 0 || lba >= nlbaf) errx(-1, "invalid LBA format %s", npa->npa_argv[0]); if (!nvme_ctrl_info_format(npa->npa_ctrl_info, (uint32_t)lba, &fmt)) { nvmeadm_fatal(npa, "failed to get LBA format %lu " "information", lba); } } else { /* * If we have a namespace then we use the current namespace's * LBA format. If we don't have a namespace, then we promised * we'd look at namespace 1 in the manual page. */ if (npa->npa_ns_info == NULL) { if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, 1, &ns_info)) { nvmeadm_fatal(npa, "failed to get namespace 1 " "information, please explicitly specify an " "LBA format"); } if (!nvme_ns_info_curformat(ns_info, &fmt)) { nvmeadm_fatal(npa, "failed to retrieve current " "namespace format from namespace 1"); } } else { if (!nvme_ns_info_curformat(npa->npa_ns_info, &fmt)) { nvmeadm_fatal(npa, "failed to get the current " "format information from %s", npa->npa_name); } } } if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) { errx(-1, "LBA formats with metadata are not supported"); } lbaf = nvme_nvm_lba_fmt_id(fmt); nvme_ns_info_free(ns_info); return (lbaf); } static int do_format(const nvme_process_arg_t *npa) { uint32_t lbaf; if (npa->npa_argc > 1) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[1]); } lbaf = do_format_determine_lbaf(npa); return (do_format_common(npa, lbaf, 0)); } static void usage_secure_erase(const char *c_name) { (void) fprintf(stderr, "%s [-c] [/]\n\n" " Secure-Erase one or all namespaces of the specified " "NVMe controller.\n", c_name); } static void optparse_secure_erase(nvme_process_arg_t *npa) { int c; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":c")) != -1) { switch (c) { case 'c': npa->npa_cmdflags |= NVMEADM_O_SE_CRYPTO; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } } static int do_secure_erase(const nvme_process_arg_t *npa) { unsigned long lbaf; uint8_t ses = NVME_FRMT_SES_USER; if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if ((npa->npa_cmdflags & NVMEADM_O_SE_CRYPTO) != 0) ses = NVME_FRMT_SES_CRYPTO; lbaf = do_format_determine_lbaf(npa); return (do_format_common(npa, lbaf, ses)); } static void usage_attach_detach_bd(const char *c_name) { (void) fprintf(stderr, "%s [/]\n\n" " %c%s blkdev(4D) %s one or all namespaces of the " "specified NVMe controller.\n", c_name, toupper(c_name[0]), &c_name[1], c_name[0] == 'd' ? "from" : "to"); } /* * nvmeadm does not generate an error when trying to attach blkdev to something * that already has it attached. Swallow that here. */ static boolean_t swallow_attach_bd_err(const nvme_process_arg_t *npa) { return (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_NS_BLKDEV_ATTACH); } static int do_attach_bd(const nvme_process_arg_t *npa) { int rv; nvme_ns_iter_t *iter = NULL; nvme_iter_t ret; const nvme_ns_disc_t *disc; if (npa->npa_ns != NULL) { if (!nvme_ns_bd_attach(npa->npa_ns) && !swallow_attach_bd_err(npa)) { nvmeadm_warn(npa, "faild to attach %s", npa->npa_name); return (-1); } return (0); } if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_NOT_IGNORED, &iter)) { nvmeadm_fatal(npa, "failed to initialize namespace discovery " "on %s", npa->npa_name); } rv = 0; while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) { nvme_ns_t *ns; uint32_t nsid; if (nvme_ns_disc_level(disc) == NVME_NS_DISC_F_BLKDEV) continue; nsid = nvme_ns_disc_nsid(disc); if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) { nvmeadm_warn(npa, "failed to open namespace %s/%u " "handle", npa->npa_name, nsid); rv = -1; continue; } /* * nvmeadm has historically swallowed the case where you ask to * attach an already attached namespace. */ if (!nvme_ns_bd_attach(ns) && !swallow_attach_bd_err(npa)) { nvmeadm_warn(npa, "failed to attach namespace " "%s/%u", npa->npa_name, nsid); rv = -1; } nvme_ns_fini(ns); } nvme_ns_discover_fini(iter); if (ret == NVME_ITER_ERROR) { nvmeadm_warn(npa, "failed to iterate namespaces on %s", npa->npa_name); rv = -1; } return (rv); } /* * nvmeadm does not generate an error when trying to attach blkdev to something * that already has it attached. Swallow that here. */ static boolean_t swallow_detach_bd_err(const nvme_process_arg_t *npa) { switch (nvme_ctrl_err(npa->npa_ctrl)) { case NVME_ERR_NS_UNALLOC: case NVME_ERR_NS_CTRL_NOT_ATTACHED: case NVME_ERR_NS_CTRL_ATTACHED: return (B_TRUE); default: return (B_FALSE); } } static int do_detach_bd(const nvme_process_arg_t *npa) { int rv; nvme_ns_iter_t *iter = NULL; nvme_iter_t ret; const nvme_ns_disc_t *disc; if (npa->npa_ns != NULL) { if (!nvme_ns_bd_detach(npa->npa_ns) && !swallow_detach_bd_err(npa)) { nvmeadm_warn(npa, "failed to detach %s", npa->npa_name); return (-1); } return (0); } if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_BLKDEV, &iter)) { nvmeadm_fatal(npa, "failed to initialize namespace discovery " "on %s", npa->npa_name); } rv = 0; while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) { nvme_ns_t *ns; uint32_t nsid = nvme_ns_disc_nsid(disc); if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) { nvmeadm_warn(npa, "failed to open namespace %s/%u " "handle", npa->npa_name, nsid); rv = -1; continue; } if (!nvme_ns_bd_detach(ns) && !swallow_detach_bd_err(npa)) { nvmeadm_warn(npa, "failed to detach namespace %s/%u", npa->npa_name, nsid); rv = -1; } nvme_ns_fini(ns); } nvme_ns_discover_fini(iter); if (ret == NVME_ITER_ERROR) { nvmeadm_warn(npa, "failed to iterate namespaces on %s", npa->npa_name); rv = -1; } return (rv); } static void usage_firmware_load(const char *c_name) { (void) fprintf(stderr, "%s []\n\n" " Load firmware to offset .\n" " The firmware needs to be committed to a slot using " "\"nvmeadm commit-firmware\"\n command.\n", c_name); } /* * Read exactly len bytes, or until eof. */ static size_t read_block(const nvme_process_arg_t *npa, int fd, char *buf, size_t len) { size_t remain; remain = len; while (remain > 0) { ssize_t bytes = read(fd, buf, remain); if (bytes == 0) break; if (bytes < 0) { if (errno == EINTR) continue; err(-1, "Error reading \"%s\"", npa->npa_argv[0]); } buf += (size_t)bytes; remain -= (size_t)bytes; } return (len - remain); } /* * Convert a string to a valid firmware upload offset (in bytes). */ static uint64_t get_fw_offsetb(char *str) { longlong_t offsetb; char *valend; errno = 0; offsetb = strtoll(str, &valend, 0); if (errno != 0 || *valend != '\0' || offsetb < 0 || offsetb > NVME_FW_OFFSETB_MAX) errx(-1, "Offset must be numeric and in the range of 0 to %llu", NVME_FW_OFFSETB_MAX); if ((offsetb & NVME_DWORD_MASK) != 0) errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE); return ((uint64_t)offsetb); } #define FIRMWARE_READ_BLKSIZE (64 * 1024) /* 64K */ static int do_firmware_load(const nvme_process_arg_t *npa) { int fw_fd; uint64_t offset = 0; size_t size, len; char buf[FIRMWARE_READ_BLKSIZE]; if (npa->npa_argc > 2) errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[2]); if (npa->npa_argc == 0) errx(-1, "Requires firmware file name, and an " "optional offset"); if (npa->npa_ns != NULL) errx(-1, "Firmware loading not available on a per-namespace " "basis"); if (npa->npa_argc == 2) offset = get_fw_offsetb(npa->npa_argv[1]); fw_fd = open(npa->npa_argv[0], O_RDONLY); if (fw_fd < 0) errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0], strerror(errno)); size = 0; do { len = read_block(npa, fw_fd, buf, sizeof (buf)); if (len == 0) break; if (!nvme_fw_load(npa->npa_ctrl, buf, len, offset)) { nvmeadm_fatal(npa, "failed to load firmware image " "\"%s\" at offset %" PRIu64, npa->npa_argv[0], offset); } offset += len; size += len; } while (len == sizeof (buf)); (void) close(fw_fd); if (verbose) (void) printf("%zu bytes downloaded.\n", size); return (0); } /* * Common firmware commit for nvmeadm commit-firmware and activate-firmware. */ static void nvmeadm_firmware_commit(const nvme_process_arg_t *npa, uint32_t slot, uint32_t act) { nvme_fw_commit_req_t *req; if (!nvme_fw_commit_req_init(npa->npa_ctrl, &req)) { nvmeadm_fatal(npa, "failed to initialize firmware commit " "request for %s", npa->npa_name); } if (!nvme_fw_commit_req_set_slot(req, slot) || !nvme_fw_commit_req_set_action(req, act)) { nvmeadm_fatal(npa, "failed to set firmware commit fields for " "%s", npa->npa_name); } if (!nvme_fw_commit_req_exec(req)) { /* * A number of command specific status values are informational * and indicate that the operation was successful but that * something else, such as a device reset, is still required * before the new firmware is active. * We distinguish those here and report them as a note rather * than a fatal error. */ if (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_CONTROLLER) { uint32_t sct, sc; nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc); if (sct == NVME_CQE_SCT_SPECIFIC && ( sc == NVME_CQE_SC_SPC_FW_RESET || sc == NVME_CQE_SC_SPC_FW_NSSR || sc == NVME_CQE_SC_SPC_FW_NEXT_RESET)) { fprintf(stderr, "nvmeadm: commit successful but %s\n", nvme_sctostr(npa->npa_ctrl, NVME_CSI_NVM, sct, sc)); } else { nvmeadm_fatal(npa, "failed to %s on %s", npa->npa_cmd->c_name, npa->npa_name); } } else { nvmeadm_fatal(npa, "failed to %s on %s", npa->npa_cmd->c_name, npa->npa_name); } } nvme_fw_commit_req_fini(req); } /* * Convert str to a valid firmware slot number. */ static uint32_t get_slot_number(char *str) { longlong_t slot; char *valend; errno = 0; slot = strtoll(str, &valend, 0); if (errno != 0 || *valend != '\0' || slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX) errx(-1, "Slot must be numeric and in the range of %u to %u", NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX); return ((uint32_t)slot); } static void usage_firmware_commit(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Commit previously downloaded firmware to slot .\n" " The firmware is only activated after a " "\"nvmeadm activate-firmware\" command.\n", c_name); } static int do_firmware_commit(const nvme_process_arg_t *npa) { uint32_t slot; if (npa->npa_argc > 1) errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[1]); if (npa->npa_argc == 0) errx(-1, "Firmware slot number is required"); if (npa->npa_ns != NULL) errx(-1, "Firmware committing not available on a per-namespace " "basis"); slot = get_slot_number(npa->npa_argv[0]); if (slot == 1 && npa->npa_idctl->id_frmw.fw_readonly) errx(-1, "Cannot commit firmware to slot 1: slot is read-only"); nvmeadm_firmware_commit(npa, slot, NVME_FWC_SAVE); if (verbose) (void) printf("Firmware committed to slot %u.\n", slot); return (0); } static void usage_firmware_activate(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Activate firmware in slot .\n" " The firmware will be in use after the next system reset.\n", c_name); } static int do_firmware_activate(const nvme_process_arg_t *npa) { uint32_t slot; if (npa->npa_argc > 1) errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[1]); if (npa->npa_argc == 0) errx(-1, "Firmware slot number is required"); if (npa->npa_ns != NULL) errx(-1, "Firmware activation not available on a per-namespace " "basis"); slot = get_slot_number(npa->npa_argv[0]); nvmeadm_firmware_commit(npa, slot, NVME_FWC_ACTIVATE); if (verbose) (void) printf("Slot %u successfully activated.\n", slot); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company * Copyright 2022 Tintri by DDN, Inc. All rights reserved. */ #ifndef _NVMEADM_H #define _NVMEADM_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif extern int verbose; extern int debug; /* Common structures */ typedef struct nvme_process_arg nvme_process_arg_t; typedef struct nvmeadm_feature nvmeadm_feature_t; typedef struct nvmeadm_cmd nvmeadm_cmd_t; typedef enum { /* * Indicates a command that is allowed to run on multiple controllers. */ NVMEADM_C_MULTI = 1 << 0, /* * Indicates a command that requires exclusive access to the device. */ NVMEADM_C_EXCL = 1 << 1, /* * Indicates a command that does not run on a controller and therefore * processing should not assume this. */ NVMEADM_C_NOCTRL = 1 << 2 } nvmeadm_cmd_flags_t; /* * General command structure */ struct nvmeadm_cmd { const char *c_name; const char *c_desc; const char *c_flagdesc; const char *c_fielddesc; int (*c_func)(const nvme_process_arg_t *); void (*c_usage)(const char *); void (*c_optparse)(nvme_process_arg_t *); nvmeadm_cmd_flags_t c_flags; }; /* * This is used to represent information for getting and printing specific * features. */ struct nvmeadm_feature { uint8_t f_feature; boolean_t (*f_get)(const nvme_process_arg_t *, const nvme_feat_disc_t *, const nvmeadm_feature_t *); void (*f_print)(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); }; struct nvme_process_arg { nvme_t *npa_nvme; nvme_ctrl_t *npa_ctrl; nvme_ns_t *npa_ns; nvme_ctrl_info_t *npa_ctrl_info; nvme_ns_info_t *npa_ns_info; int npa_argc; char **npa_argv; char *npa_name; const char *npa_ctrl_name; boolean_t npa_excl; uint32_t npa_cmdflags; const nvmeadm_cmd_t *npa_cmd; const nvme_identify_ctrl_t *npa_idctl; const nvme_version_t *npa_version; ofmt_handle_t npa_ofmt; void *npa_cmd_arg; }; /* * Command-specific arguments */ typedef enum { NVMEADM_LIST_DEFAULT, NVMEADM_LIST_CTRL, NVMEADM_LIST_LOC } nvmeadm_list_mode_t; typedef struct { nvmeadm_list_mode_t list_mode; topo_hdl_t *list_topo; } nvmeadm_list_t; typedef struct { boolean_t nll_unimpl; nvme_log_disc_scope_t nll_scope; uint32_t nll_nprint; int nll_nfilts; char *const *nll_filts; boolean_t *nll_used; } nvmeadm_list_logs_t; typedef struct { boolean_t nf_unimpl; uint32_t nf_nprint; uint32_t nf_nfilts; char *const *nf_filts; boolean_t *nf_used; } nvmeadm_features_t; typedef struct { boolean_t ncn_use_flbas; nvme_csi_t ncn_csi; uint64_t ncn_size; uint64_t ncn_cap; uint32_t ncn_lba; uint32_t ncn_nmic; } nvmeadm_create_ns_t; typedef struct { const char *ngl_output; bool ngl_hex; } nvmeadm_get_logpage_t; /* Version checking */ extern boolean_t nvme_version_check(const nvme_process_arg_t *, const nvme_version_t *); /* printing functions */ extern int nvme_strlen(const char *, int); extern void nvme_print(int, const char *, int, const char *, ...); extern int nvme_snprint_uint128(char *, size_t, nvme_uint128_t, int, int); extern void nvme_print_ctrl_summary(nvme_ctrl_info_t *); extern void nvme_print_nsid_summary(nvme_ns_info_t *); extern void nvme_print_identify_ctrl(const nvme_identify_ctrl_t *, uint32_t, const nvme_version_t *); extern void nvme_print_identify_nsid(const nvme_identify_nsid_t *, const nvme_version_t *); extern void nvme_print_identify_nsid_list(const char *, const nvme_identify_nsid_list_t *); extern void nvme_print_identify_nsid_desc(void *); extern void nvme_print_identify_ctrl_list(const char *, const nvme_identify_ctrl_list_t *); extern void nvme_print_error_log(int, const nvme_error_log_entry_t *, const nvme_version_t *); extern void nvme_print_health_log(const nvme_health_log_t *, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_fwslot_log(const nvme_fwslot_log_t *, const nvme_identify_ctrl_t *); extern void nvme_print_feat_unknown(nvme_feat_output_t, uint32_t, void *, size_t); extern void nvme_print_feat_arbitration(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_power_mgmt(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_lba_range(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_temperature(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_error(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_write_cache(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_nqueues(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_intr_coal(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_intr_vect(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_write_atom(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_async_event(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_auto_pst(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_progress(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvme_print_feat_host_behavior(uint32_t, void *, size_t, const nvme_identify_ctrl_t *, const nvme_version_t *); extern void nvmeadm_dump_hex(const uint8_t *, size_t); /* * ofmt related */ typedef struct { const char *nloa_name; di_node_t nloa_dip; nvme_ctrl_info_t *nloa_ctrl; nvme_ns_info_t *nloa_ns; const char *nloa_disk; const char *nloa_state; char *nloa_loc; char *nloa_ap; } nvmeadm_list_ofmt_arg_t; extern const ofmt_field_t nvmeadm_list_ctrl_ofmt[]; extern const ofmt_field_t nvmeadm_list_loc_ofmt[]; extern const ofmt_field_t nvmeadm_list_nsid_ofmt[]; typedef struct { const char *nlloa_name; const nvme_log_disc_t *nlloa_disc; } nvmeadm_list_logs_ofmt_arg_t; extern const char *nvmeadm_list_logs_fields; extern const char *nvmeadm_list_logs_fields_impl; extern const ofmt_field_t nvmeadm_list_logs_ofmt[]; typedef struct { const char *nlfoa_name; const nvme_feat_disc_t *nlfoa_feat; } nvmeadm_list_features_ofmt_arg_t; extern const char *nvmeadm_list_features_fields; extern const ofmt_field_t nvmeadm_list_features_ofmt[]; /* * Log pages that have special handling. */ extern int do_get_logpage_telemetry(const nvme_process_arg_t *, const nvme_log_disc_t *, nvme_log_req_t *); /* * Warning and error cases. The default nvmeadm ones assume a libnvme related * issue. Most errors are on the nvme_ctrl_t, which are the versions without any * args. The ones that operate on the nvme_t handle have hdl in the name. */ extern void nvmeadm_warn(const nvme_process_arg_t *, const char *, ...) __PRINTFLIKE(2); extern void nvmeadm_fatal(const nvme_process_arg_t *, const char *, ...) __PRINTFLIKE(2) __NORETURN; extern void nvmeadm_hdl_warn(const nvme_process_arg_t *, const char *, ...) __PRINTFLIKE(2); extern void nvmeadm_hdl_fatal(const nvme_process_arg_t *, const char *, ...) __PRINTFLIKE(2) __NORETURN; /* * Namespace Management Commands */ extern int do_create_ns(const nvme_process_arg_t *); extern void optparse_create_ns(nvme_process_arg_t *); extern void usage_create_ns(const char *); extern int do_delete_ns(const nvme_process_arg_t *); extern void usage_delete_ns(const char *); extern int do_attach_ns(const nvme_process_arg_t *); extern void usage_attach_ns(const char *); extern int do_detach_ns(const nvme_process_arg_t *); extern void usage_detach_ns(const char *); /* * Physical Eye Commands */ extern int do_measure_phyeye_cmd(const nvme_process_arg_t *); extern void optparse_measure_phyeye_cmd(nvme_process_arg_t *); extern void usage_measure_phyeye_cmd(const char *); extern int do_report_phyeye_cmd(const nvme_process_arg_t *); extern void optparse_report_phyeye_cmd(nvme_process_arg_t *); extern void usage_report_phyeye_cmd(const char *); /* * Locking functions */ extern void nvmeadm_excl(const nvme_process_arg_t *, nvme_lock_level_t); /* * Vendor specific commands. * * All vendor commands must first call nvmeadm_vuc_validate() which will * validate that a given vendor unique command is useable by the device and then * proceed to take any necessary locks that the command suggests. */ extern nvme_vuc_disc_t *nvmeadm_vuc_init(const nvme_process_arg_t *, const char *); extern void nvmeadm_vuc_fini(const nvme_process_arg_t *, nvme_vuc_disc_t *); extern int do_vendor_cmd(const nvme_process_arg_t *); extern void optparse_vendor_cmd(nvme_process_arg_t *); extern void usage_vendor_cmd(const char *); extern int do_wdc_e6dump(const nvme_process_arg_t *); extern void optparse_wdc_e6dump(nvme_process_arg_t *); extern void usage_wdc_e6dump(const char *); extern int do_wdc_resize(const nvme_process_arg_t *); extern void optparse_wdc_resize(nvme_process_arg_t *); extern void usage_wdc_resize(const char *); extern int do_wdc_clear_assert(const nvme_process_arg_t *); extern void usage_wdc_clear_assert(const char *); extern int do_wdc_inject_assert(const nvme_process_arg_t *); extern void usage_wdc_inject_assert(const char *); extern int do_sandisk_hwrev(const nvme_process_arg_t *); extern void usage_sandisk_hwrev(const char *); extern int do_sandisk_pcieye(const nvme_process_arg_t *); extern void optparse_sandisk_pcieye(nvme_process_arg_t *); extern void usage_sandisk_pcieye(const char *); /* * This is an arbitrary maximum that we use for what we expect the likely size * of a log page may end up being. We use 128 MiB as a rough upper bound for * what we'll mmap. This is a somewhat arbitrary value, but if we end up having * a larger file, then we'll want to be more conscious of memory and probably * read in a file in a buffer over time instead of mmap. */ #define NVMEADM_MAX_MMAP (1ULL << 27) /* * Field slicing and dicing. This is logic that is similar to pcieadm's * show-cfgspace which allows us to select specific fields based on the short * name of the log from the spec. */ typedef struct { const char *nff_str; size_t nff_len; bool nff_used; } nvmeadm_field_filt_t; typedef enum nvmeadm_field_type_t { /* * Print as a raw hexadecimal value. The optional addend may be set to * modify the value. */ NVMEADM_FT_HEX, /* * A number that should take the same shift as above, but have a * particular unscaled unit applied. */ NVMEADM_FT_UNIT, /* * The raw value maps to a string of some kind. */ NVMEADM_FT_STRMAP, /* * Treat as a power of 2 number of bytes. Raw value is the full hex * value. Otherwise this should be humanized. */ NVMEADM_FT_BYTES, /* * Indicates that this is a nested structure with a series of bitfields * that we should print. */ NVMEADM_FT_BITS, /* * Indicate that there are a series of fields inside of this. Similar in * spirit to BITS above, but generally meant to be used to help separate * stuff which can be a little weirder such as the OCP telemetry string * log or the various extended SMART items. Containers are only included * in human readable output and are not part of the machine parsable * output as they have no value. */ NVMEADM_FT_CONTAINER, /* * Indicates that this field is a normalized percentage. Note, this may * result in a value > 100%. */ NVMEADM_FT_PERCENT, /* * A 16-byte style UUID. */ NVMEADM_FT_GUID, /* * A series of characters that are supposed to be ASCII strings. The * ASCIIZ says that these are NUL padded where as ASCII * is probably space padded. Either way padding is not guaranteed. */ NVMEADM_FT_ASCII, NVMEADM_FT_ASCIIZ } nvmeadm_field_type_t; typedef struct { uint8_t nfa_shift; int64_t nfa_addend; const char *nfa_unit; } nvmeadm_field_addend_t; typedef struct nvmeadm_field_bit nvmeadm_field_bit_t; struct nvmeadm_field_bit { uint8_t nfb_lowbit; uint8_t nfb_hibit; const char *nfb_short; const char *nfb_desc; uint8_t nfb_rev; uint8_t nfb_maxrev; const nvme_version_t *nfb_vers; nvmeadm_field_type_t nfb_type; /* * Enough space for up to an 8-bit fields worth of values * (though we expect most to be sparse). */ const char *nfb_strs[128]; const nvmeadm_field_bit_t *nfb_bits; size_t nfb_nbits; nvmeadm_field_addend_t nfb_addend; }; typedef struct nvmeadm_field nvmeadm_field_t; struct nvmeadm_field { uint32_t nf_off; uint32_t nf_len; const char *nf_short; const char *nf_desc; uint32_t nf_rev; uint32_t nf_maxrev; const nvme_version_t *nf_vers; nvmeadm_field_type_t nf_type; /* * Enough space for up to an 8-bit fields worth of values * (though we expect most to be sparse). */ const char *nf_strs[128]; const nvmeadm_field_bit_t *nf_bits; size_t nf_nbits; nvmeadm_field_addend_t nf_addend; const nvmeadm_field_t *nf_fields; size_t nf_nfields; }; typedef struct nvmeadm_field_print { /* * fp_header provides a header when printing this data. In general, * 'fp_header' should only be used if we are breaking up a single log * page or similar into multiple disjoint tables. This is used when * there's a header for a log page and then a variable set of body * entries (e.g. the PHY Eye Measurement). * * This header is paired with a 'base' string that corresponds to the * short name for this region of the file. This should be set in fp_base * and is required if fp_header is set. */ const char *fp_header; /* * Optional field revision and NVMe version information. If this is * present, the field will be skipped if the object revision or the * controller version is not sufficient. If this is against a file then * the version checks are ignored by default. */ uint32_t fp_rev; const nvme_version_t *fp_vers; /* * These are the set of fields to actually print. */ const nvmeadm_field_t *fp_fields; size_t fp_nfields; /* * This represents the initial portion of the 'short' path. This will be * prepended to all the different fields in here. This may be NULL if * there is nothing here. When it is NULL, the header should be as well. */ const char *fp_base; /* * The data pointer and its corresponding length of valid data. Note, * fp_off is a logical offset to be added. There is no relationship * assumed between the data pointer and the fp_off. When a field is * processed its embedded offset is always relative to the start of * data. fp_off exists when manually driving so when a data pointer * points to the start of some region that is offset, e.g. a pointer to * the start of some variable length data after a header, then the * logical offset in the overall structure can still be applied when * telling the user about offsets. */ const void *fp_data; size_t fp_dlen; size_t fp_off; /* * Filters that are checked against. */ size_t fp_nfilts; nvmeadm_field_filt_t *fp_filts; ofmt_handle_t fp_ofmt; /* * Internal data used for indentation purposes. */ uint32_t fp_indent; } nvmeadm_field_print_t; /* * Functions and data to reach the field printing logic. */ extern const ofmt_field_t nvmeadm_field_ofmt[]; extern void nvmeadm_field_print(nvmeadm_field_print_t *); /* * This is the internal function used by log processing code to reach the * filtering / ofmt log formatting logic. */ typedef enum { /* * Indicates that this is the first time that a log page name is being * checked. If it isn't found, then we should warn about it. This'll * result in the command failing. This is mean to be used by * print-logpage. */ NVMEADM_LFF_CHECK_NAME = 1 << 0 } nvmeadm_log_field_flag_t; extern bool nvmeadm_log_page_fields(const nvme_process_arg_t *, const char *, const void *, size_t, nvmeadm_field_filt_t *, size_t, nvmeadm_log_field_flag_t); /* * Convenience macros to set a field type and consistent members. This should be * used in the implementation of field information. */ #define NVMEADM_F_BITS(bits) \ .nf_type = NVMEADM_FT_BITS, \ .nf_bits = bits, \ .nf_nbits = ARRAY_SIZE(bits) #define NVMEADM_FB_BITS(bits) \ .nfb_type = NVMEADM_FT_BITS, \ .nfb_bits = bits, \ .nfb_nbits = ARRAY_SIZE(bits) #define NVMEADM_F_FIELDS(f) \ .nf_type = NVMEADM_FT_CONTAINER, \ .nf_fields = f, \ .nf_nfields = ARRAY_SIZE(f) /* * Defined field structures. */ typedef struct { const char *nlfi_log; const nvmeadm_field_t *const nlfi_fields; const size_t nlfi_nfields; const size_t nlfi_min; /* * Return the revision of the log field. Callers are guaranteed that at * least nlfi_min byte are already present, */ uint32_t (*nlfi_getrev)(const void *, size_t len); /* * Run the process of walking through the log data and providing field * callbacks. This should be used for logs that have a fixed header and * variable contents. */ bool (*nlfi_drive)(nvmeadm_field_print_t *, const void *, size_t); } nvmeadm_log_field_info_t; extern const nvmeadm_log_field_info_t suplog_field_info; extern const nvmeadm_log_field_info_t supcmd_field_info; extern const nvmeadm_log_field_info_t supmicmd_field_info; extern const nvmeadm_log_field_info_t supfeat_field_info; extern const nvmeadm_log_field_info_t phyeye_field_info; extern const nvmeadm_log_field_info_t kioxia_vul_extsmart_field_info; extern const nvmeadm_log_field_info_t micron_vul_extsmart_field_info; extern const nvmeadm_log_field_info_t ocp_vul_smart_field_info; extern const nvmeadm_log_field_info_t ocp_vul_errrec_field_info; extern const nvmeadm_log_field_info_t ocp_vul_devcap_field_info; extern const nvmeadm_log_field_info_t ocp_vul_unsup_field_info; extern const nvmeadm_log_field_info_t ocp_vul_telstr_field_info; extern const nvmeadm_log_field_info_t solidigm_vul_power_field_info; extern const nvmeadm_log_field_info_t solidigm_vul_temp_field_info; extern const nvmeadm_log_field_info_t wdc_vul_cusmart_field_info; extern const nvmeadm_log_field_info_t wdc_vul_eol_field_info; extern const nvmeadm_log_field_info_t wdc_vul_power_field_info; #ifdef __cplusplus } #endif #endif /* _NVMEADM_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Many vendors have followed the same layout for their Extended SMART data * which usually is a 1 byte id, 1 byte normalized value, and 6ish bytes of * data. This file contains the field data for printing those and the * corresponding logs. */ #include #include #include #include #include #include "nvmeadm.h" #define EXTSMART_F(f) \ .nf_off = offsetof(solidigm_smart_ent_t, sse_##f), \ .nf_len = sizeof (((solidigm_smart_ent_t *)NULL)->sse_##f) static const nvmeadm_field_t extsmart_percent_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_PERCENT }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_HEX } }; /* * All fields are just printed in hex. */ static const nvmeadm_field_t extsmart_hex_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_t extsmart_wl_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_PERCENT }, { .nf_off = offsetof(solidigm_smart_ent_t, sse_raw[0]), .nf_len = 2, .nf_short = "min", .nf_desc = "Minimum Erase Cycles", .nf_type = NVMEADM_FT_HEX }, { .nf_off = offsetof(solidigm_smart_ent_t, sse_raw[2]), .nf_len = 2, .nf_short = "max", .nf_desc = "Maximum Erase Cycles", .nf_type = NVMEADM_FT_HEX }, { .nf_off = offsetof(solidigm_smart_ent_t, sse_raw[4]), .nf_len = 2, .nf_short = "avg", .nf_desc = "Average Erase Cycles", .nf_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_t extsmart_32mio_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_BYTES, .nf_addend = { .nfa_shift = 25 } } }; static const nvmeadm_field_t extsmart_rawpct_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_PERCENT, } }; static const nvmeadm_field_t extsmart_rawmin_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "min" } } }; static const nvmeadm_field_t extsmart_rawhour_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(raw), .nf_short = "raw", .nf_desc = "Raw", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "hours" } } }; static const nvmeadm_field_t extsmart_therm_fields[] = { { EXTSMART_F(type), .nf_short = "id", .nf_desc = "Identifier", .nf_type = NVMEADM_FT_HEX }, { EXTSMART_F(norm), .nf_short = "norm", .nf_desc = "Normalized", .nf_type = NVMEADM_FT_HEX }, { .nf_off = offsetof(solidigm_smart_ent_t, sse_raw[0]), .nf_len = 1, .nf_short = "status", .nf_desc = "Throttle Status", .nf_type = NVMEADM_FT_PERCENT }, { .nf_off = offsetof(solidigm_smart_ent_t, sse_raw[1]), .nf_len = 4, .nf_short = "count", .nf_desc = "Throttle Count", .nf_type = NVMEADM_FT_HEX } }; #define WDC_F_SMART(f) \ .nf_off = offsetof(wdc_vul_sn65x_smart_t, sm_##f), \ .nf_len = sizeof (((wdc_vul_sn65x_smart_t *)NULL)->sm_##f) static const nvmeadm_field_t wdc_vul_cusmart_fields[] = { { WDC_F_SMART(prog_fail), .nf_short = "pfc", .nf_desc = "Program Fail Count", NVMEADM_F_FIELDS(extsmart_percent_fields) }, { WDC_F_SMART(erase_fail), .nf_short = "efc", .nf_desc = "Erase Fail Count", NVMEADM_F_FIELDS(extsmart_percent_fields) }, { WDC_F_SMART(wear_level), .nf_short = "wl", .nf_desc = "Wear Leveling", NVMEADM_F_FIELDS(extsmart_wl_fields) }, { WDC_F_SMART(e2e_edet), .nf_short = "e2e", .nf_desc = "End-to-End Error Detection Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { WDC_F_SMART(crc_err), .nf_short = "crc", .nf_desc = "CRC Error Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { WDC_F_SMART(timed_wear), .nf_short = "twmw", .nf_desc = "Timed Workload Media Wear", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { WDC_F_SMART(timed_read), .nf_short = "twhr", .nf_desc = "Timed Workload Host Reads", NVMEADM_F_FIELDS(extsmart_rawpct_fields) }, { WDC_F_SMART(timed_timer), .nf_short = "twt", .nf_desc = "Timed Workload Timer", NVMEADM_F_FIELDS(extsmart_rawmin_fields) }, { WDC_F_SMART(therm_throt), .nf_short = "tthrot", .nf_desc = "Thermal Throttle", NVMEADM_F_FIELDS(extsmart_therm_fields) }, { WDC_F_SMART(retry_buf_over), .nf_short = "rboc", .nf_desc = "Retry Buffer Overflow Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { WDC_F_SMART(pll_lock_loss), .nf_short = "pllll", .nf_desc = "PLL Lock Loss Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { WDC_F_SMART(nand_write), .nf_short = "nbw", .nf_desc = "NAND Bytes Written", NVMEADM_F_FIELDS(extsmart_32mio_fields) }, { WDC_F_SMART(host_write), .nf_short = "hbw", .nf_desc = "Host Bytes Written", NVMEADM_F_FIELDS(extsmart_32mio_fields) } }; const nvmeadm_log_field_info_t wdc_vul_cusmart_field_info = { .nlfi_log = "wdc/cusmart", .nlfi_fields = wdc_vul_cusmart_fields, .nlfi_nfields = ARRAY_SIZE(wdc_vul_cusmart_fields), .nlfi_min = sizeof (wdc_vul_sn65x_smart_t) }; #define KIOXIA_F_SMART(f) \ .nf_off = offsetof(kioxia_vul_cd8_smart_t, cds_##f), \ .nf_len = sizeof (((kioxia_vul_cd8_smart_t *)NULL)->cds_##f) static const nvmeadm_field_t kioxia_vul_extsmart_fields[] = { { KIOXIA_F_SMART(prog_fail), .nf_short = "pfc", .nf_desc = "Program Fail Count", NVMEADM_F_FIELDS(extsmart_percent_fields) }, { KIOXIA_F_SMART(erase_fail), .nf_short = "efc", .nf_desc = "Erase Fail Count", NVMEADM_F_FIELDS(extsmart_percent_fields) }, { KIOXIA_F_SMART(wear_level), .nf_short = "wl", .nf_desc = "Wear Leveling", NVMEADM_F_FIELDS(extsmart_wl_fields) }, { KIOXIA_F_SMART(e2e_det), .nf_short = "e2e", .nf_desc = "End-to-End Error Detection Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { KIOXIA_F_SMART(crc_error), .nf_short = "crc", .nf_desc = "CRC Error Count", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { KIOXIA_F_SMART(nand_write), .nf_short = "nbw", .nf_desc = "NAND Bytes Written", NVMEADM_F_FIELDS(extsmart_32mio_fields) }, { KIOXIA_F_SMART(host_write), .nf_short = "hbw", .nf_desc = "Host Bytes Written", NVMEADM_F_FIELDS(extsmart_32mio_fields) }, { /* * The remaining fields (other than host bytes read) are duplicates from * the normal NVMe Health log so we use the standard's name and short * values. */ KIOXIA_F_SMART(crit_warn), .nf_short = "cw", .nf_desc = "Device Critical Warning", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { KIOXIA_F_SMART(host_read), .nf_short = "hbr", .nf_desc = "Host Bytes Read", NVMEADM_F_FIELDS(extsmart_32mio_fields) }, { KIOXIA_F_SMART(comp_temp), .nf_short = "ctemp", .nf_desc = "Composite Temperature", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { KIOXIA_F_SMART(life_used), .nf_short = "pused", .nf_desc = "Percentage Used", NVMEADM_F_FIELDS(extsmart_percent_fields) }, { KIOXIA_F_SMART(power_cycles), .nf_short = "pwrc", .nf_desc = "Power Cycles", NVMEADM_F_FIELDS(extsmart_hex_fields) }, { KIOXIA_F_SMART(power_hours), .nf_short = "poh", .nf_desc = "Power On Hours", NVMEADM_F_FIELDS(extsmart_rawhour_fields) }, { KIOXIA_F_SMART(unsafe_shut), .nf_short = "upl", .nf_desc = "Unexpected Power Losses", NVMEADM_F_FIELDS(extsmart_hex_fields) } }; const nvmeadm_log_field_info_t kioxia_vul_extsmart_field_info = { .nlfi_log = "kioxia/extsmart", .nlfi_fields = kioxia_vul_extsmart_fields, .nlfi_nfields = ARRAY_SIZE(kioxia_vul_extsmart_fields), .nlfi_min = sizeof (kioxia_vul_cd8_smart_t) }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Logic to slice, dice, and print structured data from log pages and related * NVMe structures. */ #include #include #include #include #include #include #include #include "nvmeadm.h" static const nvmeadm_log_field_info_t *field_log_map[] = { &suplog_field_info, &supcmd_field_info, &supmicmd_field_info, &supfeat_field_info, &phyeye_field_info, &kioxia_vul_extsmart_field_info, µn_vul_extsmart_field_info, &ocp_vul_smart_field_info, &ocp_vul_errrec_field_info, &ocp_vul_devcap_field_info, &ocp_vul_unsup_field_info, &ocp_vul_telstr_field_info, &solidigm_vul_power_field_info, &solidigm_vul_temp_field_info, &wdc_vul_cusmart_field_info, &wdc_vul_eol_field_info, &wdc_vul_power_field_info }; typedef struct { const char *fo_base; const char *fo_short; const char *fo_desc; char fo_val[256]; char fo_hval[256]; uint32_t fo_off; uint32_t fo_bitoff; uint32_t fo_len; uint32_t fo_bitlen; } field_ofmt_t; typedef enum { NVMEADM_FIELD_OT_SHORT, NVMEADM_FIELD_OT_DESC, NVMEADM_FIELD_OT_VALUE, NVMEADM_FIELD_OT_HUMAN, NVMEADM_FIELD_OT_BYTEOFF, NVMEADM_FIELD_OT_BITOFF, NVMEADM_FIELD_OT_BYTELEN, NVMEADM_FIELD_OT_BITLEN } phyeye_otype_t; static boolean_t nvmeadm_field_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { size_t ret; field_ofmt_t *fo = ofarg->ofmt_cbarg; switch (ofarg->ofmt_id) { case NVMEADM_FIELD_OT_SHORT: if (fo->fo_base == NULL) { ret = strlcat(buf, fo->fo_short, buflen); } else { ret = snprintf(buf, buflen, "%s.%s", fo->fo_base, fo->fo_short); } break; case NVMEADM_FIELD_OT_DESC: ret = strlcat(buf, fo->fo_desc, buflen); break; case NVMEADM_FIELD_OT_VALUE: if (fo->fo_val[0] == '\0') return (B_FALSE); ret = strlcat(buf, fo->fo_val, buflen); break; case NVMEADM_FIELD_OT_HUMAN: if (fo->fo_hval[0] != '\0') { ret = strlcat(buf, fo->fo_hval, buflen); } else { ret = strlcat(buf, fo->fo_val, buflen); } break; case NVMEADM_FIELD_OT_BYTEOFF: ret = snprintf(buf, buflen, "%u", fo->fo_off); break; case NVMEADM_FIELD_OT_BITOFF: ret = snprintf(buf, buflen, "%u", fo->fo_bitoff); break; case NVMEADM_FIELD_OT_BYTELEN: ret = snprintf(buf, buflen, "%u", fo->fo_len); break; case NVMEADM_FIELD_OT_BITLEN: ret = snprintf(buf, buflen, "%u", fo->fo_bitlen); break; default: abort(); } return (ret < buflen); } const ofmt_field_t nvmeadm_field_ofmt[] = { { "SHORT", 30, NVMEADM_FIELD_OT_SHORT, nvmeadm_field_ofmt_cb }, { "DESC", 30, NVMEADM_FIELD_OT_DESC, nvmeadm_field_ofmt_cb }, { "VALUE", 20, NVMEADM_FIELD_OT_VALUE, nvmeadm_field_ofmt_cb }, { "HUMAN", 20, NVMEADM_FIELD_OT_HUMAN, nvmeadm_field_ofmt_cb }, { "OFFSET", 8, NVMEADM_FIELD_OT_BYTEOFF, nvmeadm_field_ofmt_cb }, { "BITOFF", 8, NVMEADM_FIELD_OT_BITOFF, nvmeadm_field_ofmt_cb }, { "LENGTH", 8, NVMEADM_FIELD_OT_BYTELEN, nvmeadm_field_ofmt_cb }, { "BITLEN", 8, NVMEADM_FIELD_OT_BITLEN, nvmeadm_field_ofmt_cb }, { NULL, 0, 0, NULL } }; /* * We've been asked to apply a filter that matches on a field which may be a * top-level field or a nested one. For example, consider eom.odp.pefp. When * we're in parsable mode we only ever allow for absolute matches. This ensures * that if we add more fields to something that it doesn't end up changing the * output the user gets. However, if we're in a non-parsable mode then we'll * allow partial matches with a section. That is, 'eom' will match anything * starting with 'eom'. 'eom.odp' will match all fields with 'eom.odp'. Partial * matches within a field will not work, e.g. 'eom.o' would not match 'eom.odp'. * * However, a more specific match should match its parent. So, we want * 'eom.odp.pefp' to match 'eom' and 'eom.odp'. Even if we match those, we don't * count them as a use of the filter. Only exact matches count. This ensures * that if someone makes a typo or uses a non-existent field, say 'eom.foobar', * which does match 'eom', it still will generate an error. */ bool nvmeadm_field_filter(nvmeadm_field_print_t *print, const char *base, const char *shrt) { char buf[PATH_MAX]; const char *check; bool match = false; if (print->fp_nfilts == 0) { return (true); } if (base != NULL && shrt != NULL) { (void) snprintf(buf, sizeof (buf), "%s.%s", base, shrt); check = buf; } else if (base == NULL) { VERIFY3P(shrt, !=, NULL); check = shrt; } else if (shrt == NULL) { VERIFY3P(base, !=, NULL); check = base; } else { abort(); } /* * Always check all filters so that way a user specifying the same thing * multiple times doesn't end up in trouble. */ for (int i = 0; i < print->fp_nfilts; i++) { nvmeadm_field_filt_t *f = &print->fp_filts[i]; if (strcmp(check, f->nff_str) == 0) { f->nff_used = true; match = true; continue; } if (print->fp_ofmt != NULL) { continue; } size_t len = strlen(check); if (len >= f->nff_len) { if (strncmp(check, f->nff_str, f->nff_len) == 0 && check[f->nff_len] == '.') { match = true; continue; } } else { if (strncmp(check, f->nff_str, len) == 0 && f->nff_str[len] == '.') { match = true; continue; } } } return (match); } static void field_print_one_bit(nvmeadm_field_print_t *print, field_ofmt_t *ofarg, nvmeadm_field_type_t type, uint32_t level) { uint32_t indent; if (!nvmeadm_field_filter(print, ofarg->fo_base, ofarg->fo_short)) { return; } if (print->fp_ofmt != NULL) { ofmt_print(print->fp_ofmt, ofarg); return; } indent = 4 + print->fp_indent * 2; if (level > 1) { indent += (level - 1) * 7; } (void) printf("%*s|--> %s: ", indent, "", ofarg->fo_desc); switch (type) { case NVMEADM_FT_STRMAP: (void) printf("%s (%s)\n", ofarg->fo_hval, ofarg->fo_val); break; case NVMEADM_FT_BITS: (void) printf("%s\n", ofarg->fo_val); break; case NVMEADM_FT_HEX: case NVMEADM_FT_PERCENT: (void) printf("%s\n", ofarg->fo_hval); break; default: abort(); } } /* * Extract what should be a series of printable ASCII bytes, but don't assume * that they are. Similarly, assume we need to trim any trailing spaces in the * field. If anything in here is not ASCII, we'll escape it. */ static void field_extract_ascii(const void *data, nvmeadm_field_type_t type, size_t len, size_t off, field_ofmt_t *ofarg) { bool zpad = type == NVMEADM_FT_ASCIIZ; const uint8_t *u8p = data + off; while (len > 0) { if ((zpad && u8p[len - 1] == '\0') || (!zpad && u8p[len - 1] == ' ')) { len--; } else { break; } } if (len == 0) return; ilstr_t ilstr; ilstr_init_prealloc(&ilstr, ofarg->fo_val, sizeof (ofarg->fo_val)); for (size_t i = 0; i < len; i++) { if (isascii(u8p[i]) && isprint(u8p[i])) { ilstr_append_char(&ilstr, u8p[i]); } else { ilstr_aprintf(&ilstr, "\\x%02x", u8p[i]); } } if (ilstr_errno(&ilstr) != ILSTR_ERROR_OK) { errx(-1, "failed to construct internal string for field %s: " "0x%x", ofarg->fo_desc, ilstr_errno(&ilstr)); } (void) memcpy(ofarg->fo_hval, ofarg->fo_val, ilstr_len(&ilstr) + 1); ilstr_fini(&ilstr); } static uint64_t nvmeadm_apply_addend(uint64_t val, const nvmeadm_field_addend_t *add) { if (add->nfa_shift > 0) { val <<= add->nfa_shift; } val += add->nfa_addend; return (val); } static void nvmeadm_field_bit_extract(const nvmeadm_field_bit_t *bit, uint64_t fval, field_ofmt_t *ofarg, uint64_t *bp) { VERIFY3U(bit->nfb_hibit, <, 64); uint64_t bval = bitx64(fval, bit->nfb_hibit, bit->nfb_lowbit); if (bp != NULL) *bp = bval; (void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%" PRIx64, bval); switch (bit->nfb_type) { case NVMEADM_FT_HEX: /* * The "human" string is the version with the addend applied. */ bval = nvmeadm_apply_addend(bval, &bit->nfb_addend); (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "0x%" PRIx64, bval); break; case NVMEADM_FT_UNIT: bval = nvmeadm_apply_addend(bval, &bit->nfb_addend); (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%" PRIu64 " %s", bval, bit->nfb_addend.nfa_unit); break; case NVMEADM_FT_BITS: /* No human string for this. */ break; case NVMEADM_FT_STRMAP: if (bval < ARRAY_SIZE(bit->nfb_strs) && bit->nfb_strs[bval] != NULL) { (void) strlcpy(ofarg->fo_hval, bit->nfb_strs[bval], sizeof (ofarg->fo_hval)); } else { (void) strlcpy(ofarg->fo_hval, "reserved", sizeof (ofarg->fo_hval)); } break; case NVMEADM_FT_PERCENT: (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%u%%", bval); break; case NVMEADM_FT_BYTES: bval = nvmeadm_apply_addend(bval, &bit->nfb_addend); nicenum(bval, ofarg->fo_hval, sizeof (ofarg->fo_hval)); break; case NVMEADM_FT_GUID: /* GUIDs don't fit inside the 8 byte limit we have */ abort(); case NVMEADM_FT_ASCII: case NVMEADM_FT_ASCIIZ: /* We should handle this once it shows up here */ abort(); case NVMEADM_FT_CONTAINER: /* Containers are only used at the field level right now. */ abort(); } } static void field_print_bits(nvmeadm_field_print_t *print, const nvmeadm_field_bit_t *bits, size_t nbits, uint64_t val, const char *base, size_t off, size_t bitoff, uint32_t level) { for (size_t i = 0; i < nbits; i++) { uint8_t blen = bits[i].nfb_hibit - bits[i].nfb_lowbit + 1; field_ofmt_t ofarg = { 0 }; /* * See if this field is one that is meaningful to this revision * of the log page or controller. If the version is NULL or the * revision is 0 in the field, then there is nothing to check. * While most fields add something in a new revision, a few also * change things, so we also check for a max revision as well. */ if (bits[i].nfb_rev != 0 && bits[i].nfb_rev > print->fp_rev) { continue; } if (bits[i].nfb_maxrev != 0 && print->fp_rev > bits[i].nfb_maxrev) { continue; } if (bits[i].nfb_vers != NULL && print->fp_vers != NULL && !nvme_vers_atleast(print->fp_vers, bits[i].nfb_vers)) { continue; } ofarg.fo_base = base; ofarg.fo_short = bits[i].nfb_short; ofarg.fo_desc = bits[i].nfb_desc; ofarg.fo_off = off + (bitoff + bits[i].nfb_lowbit) / NBBY; ofarg.fo_bitoff = (bitoff + bits[i].nfb_lowbit) % NBBY; ofarg.fo_len = blen / NBBY; ofarg.fo_bitlen = blen % NBBY; uint64_t bit_val; nvmeadm_field_bit_extract(&bits[i], val, &ofarg, &bit_val); field_print_one_bit(print, &ofarg, bits[i].nfb_type, level); if (bits[i].nfb_type == NVMEADM_FT_BITS) { char buf[256]; (void) snprintf(buf, sizeof (buf), "%s.%s", base, bits[i].nfb_short); field_print_bits(print, bits[i].nfb_bits, bits[i].nfb_nbits, bit_val, buf, ofarg.fo_off, ofarg.fo_bitoff, level + 1); } } } static void field_print_one(nvmeadm_field_print_t *print, field_ofmt_t *ofarg, nvmeadm_field_type_t type) { if (!nvmeadm_field_filter(print, ofarg->fo_base, ofarg->fo_short)) { return; } if (print->fp_ofmt != NULL) { if (type == NVMEADM_FT_CONTAINER) return; ofmt_print(print->fp_ofmt, ofarg); return; } uint_t indent = 2 + print->fp_indent * 2; (void) printf("%*s%s:", indent, "", ofarg->fo_desc); switch (type) { case NVMEADM_FT_BITS: (void) printf(" %s\n", ofarg->fo_val); break; case NVMEADM_FT_STRMAP: (void) printf(" %s (%s)\n", ofarg->fo_hval, ofarg->fo_val); break; case NVMEADM_FT_HEX: case NVMEADM_FT_UNIT: case NVMEADM_FT_BYTES: case NVMEADM_FT_PERCENT: case NVMEADM_FT_GUID: case NVMEADM_FT_ASCII: case NVMEADM_FT_ASCIIZ: (void) printf(" %s\n", ofarg->fo_hval); break; case NVMEADM_FT_CONTAINER: (void) printf("\n"); break; } } /* * Extract the u128 from where we are right now. */ static void nvmeadm_field_extract_u128(const nvmeadm_field_t *field, const void *data, field_ofmt_t *ofarg) { nvme_uint128_t u128; const uint8_t *u8p; (void) memcpy(&u128, data + field->nf_off, sizeof (u128)); if (u128.hi == 0) { (void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%x", u128.lo); } else { (void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%x%016x", u128.hi, u128.lo); } switch (field->nf_type) { case NVMEADM_FT_BYTES: /* * Right now we a 64-bit byte value is 16 EiB. If we have more * than that, error so we do something more clever, but * otherwise punt for the time being. */ if (u128.hi != 0) { warnx("encountered 128-bit size with upper bits set " "for field %s, cannot accurately convert", field->nf_desc); u128.lo = UINT64_MAX; } if (field->nf_addend.nfa_shift != 0 || field->nf_addend.nfa_addend != 0) { warnx("encountered 128-bit size with addend request " "for field %s, but conversion not implemented", field->nf_desc); } nicenum(u128.lo, ofarg->fo_hval, sizeof (ofarg->fo_hval)); break; case NVMEADM_FT_GUID: u8p = data + field->nf_off; (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" "%02x%02x%02x%02x%02x%02x", u8p[15], u8p[14], u8p[13], u8p[12], u8p[11], u8p[10], u8p[9], u8p[8], u8p[7], u8p[6], u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); break; case NVMEADM_FT_HEX: if (field->nf_addend.nfa_shift != 0 || field->nf_addend.nfa_addend != 0) { warnx("encountered 128-bit field with addend, but " "cannot apply it"); } (void) memcpy(ofarg->fo_hval, ofarg->fo_val, sizeof (ofarg->fo_hval)); break; default: break; } } static void nvmeadm_field_extract(const nvmeadm_field_t *field, const void *data, field_ofmt_t *ofarg, uint64_t *bp) { uint64_t val; /* * Don't touch containers. There is nothing to extract. */ if (field->nf_type == NVMEADM_FT_CONTAINER) return; /* * If this is an ASCII field, then we just handle this immediately. */ if (field->nf_type == NVMEADM_FT_ASCII || field->nf_type == NVMEADM_FT_ASCIIZ) { field_extract_ascii(data, field->nf_type, field->nf_len, field->nf_off, ofarg); return; } /* * Next look at the type and size and see if this is something that we * can deal with simply. Items that are less than a u64 are easy. If we * had better C23 support and therefore could deal with a u128, then * this would be simpler too. Until then, we split this based on things * larger than a u64 and those not. */ if (field->nf_len > sizeof (uint64_t)) { switch (field->nf_type) { case NVMEADM_FT_HEX: case NVMEADM_FT_BYTES: case NVMEADM_FT_GUID: VERIFY3U(field->nf_len, ==, 16); nvmeadm_field_extract_u128(field, data, ofarg); break; default: abort(); } return; } /* * NVMe integers are defined as being encoded in little endian. */ val = 0; const uint8_t *u8p = data + field->nf_off; for (size_t i = 0; i < field->nf_len; i++) { uint8_t shift = i * NBBY; val |= (uint64_t)u8p[i] << shift; } if (bp != NULL) *bp = val; (void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%" PRIx64, val); switch (field->nf_type) { case NVMEADM_FT_HEX: /* * The "human" string is the version with the addend applied. */ val = nvmeadm_apply_addend(val, &field->nf_addend); (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "0x%" PRIx64, val); break; case NVMEADM_FT_UNIT: val = nvmeadm_apply_addend(val, &field->nf_addend); (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%" PRIu64 " %s", val, field->nf_addend.nfa_unit); break; case NVMEADM_FT_BITS: /* No human string for these */ break; case NVMEADM_FT_STRMAP: if (val < ARRAY_SIZE(field->nf_strs) && field->nf_strs[val] != NULL) { (void) strlcpy(ofarg->fo_hval, field->nf_strs[val], sizeof (ofarg->fo_hval)); } else { (void) strlcpy(ofarg->fo_hval, "reserved", sizeof (ofarg->fo_hval)); } break; case NVMEADM_FT_BYTES: val = nvmeadm_apply_addend(val, &field->nf_addend); nicenum(val, ofarg->fo_hval, sizeof (ofarg->fo_hval)); break; case NVMEADM_FT_PERCENT: (void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%u%%", val); break; case NVMEADM_FT_GUID: /* * GUIDs are larger than 8 bytes and so we should never hit * this. */ abort(); case NVMEADM_FT_ASCII: case NVMEADM_FT_ASCIIZ: case NVMEADM_FT_CONTAINER: /* Should already be handled above */ abort(); } } void nvmeadm_field_print(nvmeadm_field_print_t *print) { if (print->fp_ofmt == NULL && print->fp_header != NULL && nvmeadm_field_filter(print, print->fp_base, NULL)) { (void) printf("%s\n", print->fp_header); } for (size_t i = 0; i < print->fp_nfields; i++) { const nvmeadm_field_t *field = &print->fp_fields[i]; field_ofmt_t ofarg = { 0 }; /* * See if this field is one that is meaningful to this revision * of the log page or controller. If the version is NULL or the * revision is 0 in the field, then there is nothing to check. */ if (field->nf_rev != 0 && field->nf_rev > print->fp_rev) { continue; } if (field->nf_maxrev != 0 && print->fp_rev > field->nf_maxrev) { continue; } if (field->nf_vers != NULL && print->fp_vers != NULL && !nvme_vers_atleast(print->fp_vers, field->nf_vers)) { continue; } ofarg.fo_base = print->fp_base; ofarg.fo_short = field->nf_short; ofarg.fo_desc = field->nf_desc; ofarg.fo_off = print->fp_off + field->nf_off; ofarg.fo_bitoff = 0; ofarg.fo_len = field->nf_len; ofarg.fo_bitlen = 0; /* * Extract the value from the field and perform any conversions * to a human value where appropriate. */ uint64_t bit_val; nvmeadm_field_extract(field, print->fp_data, &ofarg, &bit_val); field_print_one(print, &ofarg, field->nf_type); /* * Now that we've dealt with this, handle anything that's * somewhat recursive in nature. */ if (field->nf_type == NVMEADM_FT_CONTAINER) { char buf[256]; nvmeadm_field_print_t copy = *print; if (print->fp_base == NULL) { (void) strlcpy(buf, field->nf_short, sizeof (buf)); } else { (void) snprintf(buf, sizeof (buf), "%s.%s", print->fp_base, field->nf_short); } copy.fp_header = NULL; copy.fp_base = buf; copy.fp_fields = field->nf_fields; copy.fp_nfields = field->nf_nfields; copy.fp_data += field->nf_off; copy.fp_dlen = field->nf_len; copy.fp_off += field->nf_off; copy.fp_indent++; nvmeadm_field_print(©); } else if (field->nf_type == NVMEADM_FT_BITS) { char buf[256]; if (print->fp_base == NULL) { (void) strlcpy(buf, field->nf_short, sizeof (buf)); } else { (void) snprintf(buf, sizeof (buf), "%s.%s", print->fp_base, field->nf_short); } field_print_bits(print, field->nf_bits, field->nf_nbits, bit_val, buf, ofarg.fo_off, 0, 1); } } } bool nvmeadm_log_page_fields(const nvme_process_arg_t *npa, const char *name, const void *data, size_t len, nvmeadm_field_filt_t *filts, size_t nfilts, nvmeadm_log_field_flag_t flags) { bool ret = true, found = false; VERIFY0(flags & ~NVMEADM_LFF_CHECK_NAME); /* * If we don't have a log page name, that's an indication that we're * being asked to just do our hex print. */ if (name == NULL) { nvmeadm_dump_hex(data, len); return (ret); } for (size_t i = 0; i < ARRAY_SIZE(field_log_map); i++) { if (strcmp(name, field_log_map[i]->nlfi_log) != 0) { continue; } /* * We've found a match for this log page. Ensure we don't hex * dump it at the end. */ found = true; if (len < field_log_map[i]->nlfi_min) { errx(-1, "cannot print log %s: log requires " "0x%zx bytes of data but only have 0x%zx", name, field_log_map[i]->nlfi_min, len); } nvmeadm_field_print_t print = { 0 }; if (field_log_map[i]->nlfi_getrev != NULL) { print.fp_rev = field_log_map[i]->nlfi_getrev(data, len); } /* * This may be NULL if we're not getting this fresh from a * controller. */ print.fp_vers = npa->npa_version; print.fp_filts = filts; print.fp_nfilts = nfilts; print.fp_ofmt = npa->npa_ofmt; /* * A registered log page may instead ask to drive the process as * there is variable data or similar present. */ if (field_log_map[i]->nlfi_drive != NULL) { if (!field_log_map[i]->nlfi_drive(&print, data, len)) { ret = false; } } else { print.fp_fields = field_log_map[i]->nlfi_fields; print.fp_nfields = field_log_map[i]->nlfi_nfields; /* * We set the base string to NULL for most log pages * that aren't comprised of variable components by * default. Ones that are variable instead will set this * as part of their drive function callback. */ print.fp_base = NULL; print.fp_data = data; print.fp_dlen = len; nvmeadm_field_print(&print); break; } } if (!found) { if ((flags & NVMEADM_LFF_CHECK_NAME) != 0) { warnx("unable to print log page %s: the log page is " "either unknown or printing information is missing", name); ret = false; } if (npa->npa_ofmt != NULL) { errx(-1, "parsable mode requested, but unable to print " "parsable output"); } nvmeadm_dump_hex(data, len); } for (size_t i = 0; i < nfilts; i++) { if (!filts[i].nff_used) { warnx("filter '%s' did not match any fields", filts[i].nff_str); ret = false; } } return (ret); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Field information for most the various supported classes logs: supported log * pages, supported commands, supported features, and supported management * commands. */ #include #include #include "nvmeadm.h" static const nvmeadm_field_bit_t suplog_fid_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "lsupp", .nfb_desc = "Log Page Identifier", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "ios", .nfb_desc = "Index Offset", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 16, .nfb_hibit = 31, .nfb_short = "lidsp", .nfb_desc = "LID Specific Parameter", .nfb_type = NVMEADM_FT_HEX } }; #define SUPLOG(f) { .nf_off = offsetof(nvme_suplog_log_t, nl_logs[f]), \ .nf_len = sizeof (((nvme_suplog_log_t *)NULL)->nl_logs[f]), \ .nf_short = "lids" #f, .nf_desc = "Log Page Identifier " #f, \ NVMEADM_F_BITS(suplog_fid_bits) } static const nvmeadm_field_t suplog_fields[] = { SUPLOG(0), SUPLOG(1), SUPLOG(2), SUPLOG(3), SUPLOG(4), SUPLOG(5), SUPLOG(6), SUPLOG(7), SUPLOG(8), SUPLOG(9), SUPLOG(10), SUPLOG(11), SUPLOG(12), SUPLOG(13), SUPLOG(14), SUPLOG(15), SUPLOG(16), SUPLOG(17), SUPLOG(18), SUPLOG(19), SUPLOG(20), SUPLOG(21), SUPLOG(22), SUPLOG(23), SUPLOG(24), SUPLOG(25), SUPLOG(26), SUPLOG(27), SUPLOG(28), SUPLOG(29), SUPLOG(30), SUPLOG(31), SUPLOG(32), SUPLOG(33), SUPLOG(34), SUPLOG(35), SUPLOG(36), SUPLOG(37), SUPLOG(38), SUPLOG(39), SUPLOG(40), SUPLOG(41), SUPLOG(42), SUPLOG(43), SUPLOG(44), SUPLOG(45), SUPLOG(46), SUPLOG(47), SUPLOG(48), SUPLOG(49), SUPLOG(50), SUPLOG(51), SUPLOG(52), SUPLOG(53), SUPLOG(54), SUPLOG(55), SUPLOG(56), SUPLOG(57), SUPLOG(58), SUPLOG(59), SUPLOG(60), SUPLOG(61), SUPLOG(62), SUPLOG(63), SUPLOG(64), SUPLOG(65), SUPLOG(66), SUPLOG(67), SUPLOG(68), SUPLOG(69), SUPLOG(70), SUPLOG(71), SUPLOG(72), SUPLOG(73), SUPLOG(74), SUPLOG(75), SUPLOG(76), SUPLOG(77), SUPLOG(78), SUPLOG(79), SUPLOG(80), SUPLOG(81), SUPLOG(82), SUPLOG(83), SUPLOG(84), SUPLOG(85), SUPLOG(86), SUPLOG(87), SUPLOG(88), SUPLOG(89), SUPLOG(90), SUPLOG(91), SUPLOG(92), SUPLOG(93), SUPLOG(94), SUPLOG(95), SUPLOG(96), SUPLOG(97), SUPLOG(98), SUPLOG(99), SUPLOG(100), SUPLOG(101), SUPLOG(102), SUPLOG(103), SUPLOG(104), SUPLOG(105), SUPLOG(106), SUPLOG(107), SUPLOG(108), SUPLOG(109), SUPLOG(110), SUPLOG(111), SUPLOG(112), SUPLOG(113), SUPLOG(114), SUPLOG(115), SUPLOG(116), SUPLOG(117), SUPLOG(118), SUPLOG(119), SUPLOG(120), SUPLOG(121), SUPLOG(122), SUPLOG(123), SUPLOG(124), SUPLOG(125), SUPLOG(126), SUPLOG(127), SUPLOG(128), SUPLOG(129), SUPLOG(130), SUPLOG(131), SUPLOG(132), SUPLOG(133), SUPLOG(134), SUPLOG(135), SUPLOG(136), SUPLOG(137), SUPLOG(138), SUPLOG(139), SUPLOG(140), SUPLOG(141), SUPLOG(142), SUPLOG(143), SUPLOG(144), SUPLOG(145), SUPLOG(146), SUPLOG(147), SUPLOG(148), SUPLOG(149), SUPLOG(150), SUPLOG(151), SUPLOG(152), SUPLOG(153), SUPLOG(154), SUPLOG(155), SUPLOG(156), SUPLOG(157), SUPLOG(158), SUPLOG(159), SUPLOG(160), SUPLOG(161), SUPLOG(162), SUPLOG(163), SUPLOG(164), SUPLOG(165), SUPLOG(166), SUPLOG(167), SUPLOG(168), SUPLOG(169), SUPLOG(170), SUPLOG(171), SUPLOG(172), SUPLOG(173), SUPLOG(174), SUPLOG(175), SUPLOG(176), SUPLOG(177), SUPLOG(178), SUPLOG(179), SUPLOG(180), SUPLOG(181), SUPLOG(182), SUPLOG(183), SUPLOG(184), SUPLOG(185), SUPLOG(186), SUPLOG(187), SUPLOG(188), SUPLOG(189), SUPLOG(190), SUPLOG(191), SUPLOG(192), SUPLOG(193), SUPLOG(194), SUPLOG(195), SUPLOG(196), SUPLOG(197), SUPLOG(198), SUPLOG(199), SUPLOG(200), SUPLOG(201), SUPLOG(202), SUPLOG(203), SUPLOG(204), SUPLOG(205), SUPLOG(206), SUPLOG(207), SUPLOG(208), SUPLOG(209), SUPLOG(210), SUPLOG(211), SUPLOG(212), SUPLOG(213), SUPLOG(214), SUPLOG(215), SUPLOG(216), SUPLOG(217), SUPLOG(218), SUPLOG(219), SUPLOG(220), SUPLOG(221), SUPLOG(222), SUPLOG(223), SUPLOG(224), SUPLOG(225), SUPLOG(226), SUPLOG(227), SUPLOG(228), SUPLOG(229), SUPLOG(230), SUPLOG(231), SUPLOG(232), SUPLOG(233), SUPLOG(234), SUPLOG(235), SUPLOG(236), SUPLOG(237), SUPLOG(238), SUPLOG(239), SUPLOG(240), SUPLOG(241), SUPLOG(242), SUPLOG(243), SUPLOG(244), SUPLOG(245), SUPLOG(246), SUPLOG(247), SUPLOG(248), SUPLOG(249), SUPLOG(250), SUPLOG(251), SUPLOG(252), SUPLOG(253), SUPLOG(254), SUPLOG(255) }; const nvmeadm_log_field_info_t suplog_field_info = { .nlfi_log = "suplog", .nlfi_fields = suplog_fields, .nlfi_nfields = ARRAY_SIZE(suplog_fields), .nlfi_min = sizeof (nvme_suplog_t), }; static const nvmeadm_field_bit_t supcmd_csp_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "nscpe", .nfb_desc = "Namespace Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "cscpe", .nfb_desc = "Controller Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "nsetcpe", .nfb_desc = "NVM Set Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "egscpe", .nfb_desc = "Endurance Group Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "dscpe", .nfb_desc = "Domain Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 5, .nfb_hibit = 5, .nfb_short = "nsscpe", .nfb_desc = "NVM Subsystem Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t supcmd_cs_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "csupp", .nfb_desc = "Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "lbcc", .nfb_desc = "Logical Block Content", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "ncc", .nfb_desc = "Namespace Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "nic", .nfb_desc = "Namespace Inventory", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "ccc", .nfb_desc = "Controller Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 14, .nfb_hibit = 15, .nfb_short = "cser", .nfb_desc = "Command Submission and Execution Relaxations", .nfb_vers = &nvme_vers_2v1, .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no relaxation", "any namespace" }, }, { .nfb_lowbit = 16, .nfb_hibit = 18, .nfb_short = "cse", .nfb_desc = "Command Submission and Execution", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no restriction", "same namespace", "any namespace" }, }, { .nfb_lowbit = 19, .nfb_hibit = 19, .nfb_short = "uss", .nfb_vers = &nvme_vers_1v4, .nfb_desc = "UUID Selection", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 20, .nfb_hibit = 31, .nfb_short = "csp", .nfb_desc = "Command Scope", .nfb_vers = &nvme_vers_2v0, NVMEADM_FB_BITS(supcmd_csp_bits) } }; #define SUPCMD_A(f) { .nf_off = offsetof(nvme_cmdeff_log_t, cme_admin[f]), \ .nf_len = sizeof (((nvme_cmdeff_log_t *)NULL)->cme_admin[f]), \ .nf_short = "acs" #f, .nf_desc = "Admin Command Supported " #f, \ NVMEADM_F_BITS(supcmd_cs_bits) } #define SUPCMD_I(f) { .nf_off = offsetof(nvme_cmdeff_log_t, cme_io[f]), \ .nf_len = sizeof (((nvme_cmdeff_log_t *)NULL)->cme_io[f]), \ .nf_short = "iocs" #f, .nf_desc = "I/O Command Supported " #f, \ NVMEADM_F_BITS(supcmd_cs_bits) } static const nvmeadm_field_t supcmd_fields[] = { SUPCMD_A(0), SUPCMD_A(1), SUPCMD_A(2), SUPCMD_A(3), SUPCMD_A(4), SUPCMD_A(5), SUPCMD_A(6), SUPCMD_A(7), SUPCMD_A(8), SUPCMD_A(9), SUPCMD_A(10), SUPCMD_A(11), SUPCMD_A(12), SUPCMD_A(13), SUPCMD_A(14), SUPCMD_A(15), SUPCMD_A(16), SUPCMD_A(17), SUPCMD_A(18), SUPCMD_A(19), SUPCMD_A(20), SUPCMD_A(21), SUPCMD_A(22), SUPCMD_A(23), SUPCMD_A(24), SUPCMD_A(25), SUPCMD_A(26), SUPCMD_A(27), SUPCMD_A(28), SUPCMD_A(29), SUPCMD_A(30), SUPCMD_A(31), SUPCMD_A(32), SUPCMD_A(33), SUPCMD_A(34), SUPCMD_A(35), SUPCMD_A(36), SUPCMD_A(37), SUPCMD_A(38), SUPCMD_A(39), SUPCMD_A(40), SUPCMD_A(41), SUPCMD_A(42), SUPCMD_A(43), SUPCMD_A(44), SUPCMD_A(45), SUPCMD_A(46), SUPCMD_A(47), SUPCMD_A(48), SUPCMD_A(49), SUPCMD_A(50), SUPCMD_A(51), SUPCMD_A(52), SUPCMD_A(53), SUPCMD_A(54), SUPCMD_A(55), SUPCMD_A(56), SUPCMD_A(57), SUPCMD_A(58), SUPCMD_A(59), SUPCMD_A(60), SUPCMD_A(61), SUPCMD_A(62), SUPCMD_A(63), SUPCMD_A(64), SUPCMD_A(65), SUPCMD_A(66), SUPCMD_A(67), SUPCMD_A(68), SUPCMD_A(69), SUPCMD_A(70), SUPCMD_A(71), SUPCMD_A(72), SUPCMD_A(73), SUPCMD_A(74), SUPCMD_A(75), SUPCMD_A(76), SUPCMD_A(77), SUPCMD_A(78), SUPCMD_A(79), SUPCMD_A(80), SUPCMD_A(81), SUPCMD_A(82), SUPCMD_A(83), SUPCMD_A(84), SUPCMD_A(85), SUPCMD_A(86), SUPCMD_A(87), SUPCMD_A(88), SUPCMD_A(89), SUPCMD_A(90), SUPCMD_A(91), SUPCMD_A(92), SUPCMD_A(93), SUPCMD_A(94), SUPCMD_A(95), SUPCMD_A(96), SUPCMD_A(97), SUPCMD_A(98), SUPCMD_A(99), SUPCMD_A(100), SUPCMD_A(101), SUPCMD_A(102), SUPCMD_A(103), SUPCMD_A(104), SUPCMD_A(105), SUPCMD_A(106), SUPCMD_A(107), SUPCMD_A(108), SUPCMD_A(109), SUPCMD_A(110), SUPCMD_A(111), SUPCMD_A(112), SUPCMD_A(113), SUPCMD_A(114), SUPCMD_A(115), SUPCMD_A(116), SUPCMD_A(117), SUPCMD_A(118), SUPCMD_A(119), SUPCMD_A(120), SUPCMD_A(121), SUPCMD_A(122), SUPCMD_A(123), SUPCMD_A(124), SUPCMD_A(125), SUPCMD_A(126), SUPCMD_A(127), SUPCMD_A(128), SUPCMD_A(129), SUPCMD_A(130), SUPCMD_A(131), SUPCMD_A(132), SUPCMD_A(133), SUPCMD_A(134), SUPCMD_A(135), SUPCMD_A(136), SUPCMD_A(137), SUPCMD_A(138), SUPCMD_A(139), SUPCMD_A(140), SUPCMD_A(141), SUPCMD_A(142), SUPCMD_A(143), SUPCMD_A(144), SUPCMD_A(145), SUPCMD_A(146), SUPCMD_A(147), SUPCMD_A(148), SUPCMD_A(149), SUPCMD_A(150), SUPCMD_A(151), SUPCMD_A(152), SUPCMD_A(153), SUPCMD_A(154), SUPCMD_A(155), SUPCMD_A(156), SUPCMD_A(157), SUPCMD_A(158), SUPCMD_A(159), SUPCMD_A(160), SUPCMD_A(161), SUPCMD_A(162), SUPCMD_A(163), SUPCMD_A(164), SUPCMD_A(165), SUPCMD_A(166), SUPCMD_A(167), SUPCMD_A(168), SUPCMD_A(169), SUPCMD_A(170), SUPCMD_A(171), SUPCMD_A(172), SUPCMD_A(173), SUPCMD_A(174), SUPCMD_A(175), SUPCMD_A(176), SUPCMD_A(177), SUPCMD_A(178), SUPCMD_A(179), SUPCMD_A(180), SUPCMD_A(181), SUPCMD_A(182), SUPCMD_A(183), SUPCMD_A(184), SUPCMD_A(185), SUPCMD_A(186), SUPCMD_A(187), SUPCMD_A(188), SUPCMD_A(189), SUPCMD_A(190), SUPCMD_A(191), SUPCMD_A(192), SUPCMD_A(193), SUPCMD_A(194), SUPCMD_A(195), SUPCMD_A(196), SUPCMD_A(197), SUPCMD_A(198), SUPCMD_A(199), SUPCMD_A(200), SUPCMD_A(201), SUPCMD_A(202), SUPCMD_A(203), SUPCMD_A(204), SUPCMD_A(205), SUPCMD_A(206), SUPCMD_A(207), SUPCMD_A(208), SUPCMD_A(209), SUPCMD_A(210), SUPCMD_A(211), SUPCMD_A(212), SUPCMD_A(213), SUPCMD_A(214), SUPCMD_A(215), SUPCMD_A(216), SUPCMD_A(217), SUPCMD_A(218), SUPCMD_A(219), SUPCMD_A(220), SUPCMD_A(221), SUPCMD_A(222), SUPCMD_A(223), SUPCMD_A(224), SUPCMD_A(225), SUPCMD_A(226), SUPCMD_A(227), SUPCMD_A(228), SUPCMD_A(229), SUPCMD_A(230), SUPCMD_A(231), SUPCMD_A(232), SUPCMD_A(233), SUPCMD_A(234), SUPCMD_A(235), SUPCMD_A(236), SUPCMD_A(237), SUPCMD_A(238), SUPCMD_A(239), SUPCMD_A(240), SUPCMD_A(241), SUPCMD_A(242), SUPCMD_A(243), SUPCMD_A(244), SUPCMD_A(245), SUPCMD_A(246), SUPCMD_A(247), SUPCMD_A(248), SUPCMD_A(249), SUPCMD_A(250), SUPCMD_A(251), SUPCMD_A(252), SUPCMD_A(253), SUPCMD_A(254), SUPCMD_A(255), SUPCMD_I(0), SUPCMD_I(1), SUPCMD_I(2), SUPCMD_I(3), SUPCMD_I(4), SUPCMD_I(5), SUPCMD_I(6), SUPCMD_I(7), SUPCMD_I(8), SUPCMD_I(9), SUPCMD_I(10), SUPCMD_I(11), SUPCMD_I(12), SUPCMD_I(13), SUPCMD_I(14), SUPCMD_I(15), SUPCMD_I(16), SUPCMD_I(17), SUPCMD_I(18), SUPCMD_I(19), SUPCMD_I(20), SUPCMD_I(21), SUPCMD_I(22), SUPCMD_I(23), SUPCMD_I(24), SUPCMD_I(25), SUPCMD_I(26), SUPCMD_I(27), SUPCMD_I(28), SUPCMD_I(29), SUPCMD_I(30), SUPCMD_I(31), SUPCMD_I(32), SUPCMD_I(33), SUPCMD_I(34), SUPCMD_I(35), SUPCMD_I(36), SUPCMD_I(37), SUPCMD_I(38), SUPCMD_I(39), SUPCMD_I(40), SUPCMD_I(41), SUPCMD_I(42), SUPCMD_I(43), SUPCMD_I(44), SUPCMD_I(45), SUPCMD_I(46), SUPCMD_I(47), SUPCMD_I(48), SUPCMD_I(49), SUPCMD_I(50), SUPCMD_I(51), SUPCMD_I(52), SUPCMD_I(53), SUPCMD_I(54), SUPCMD_I(55), SUPCMD_I(56), SUPCMD_I(57), SUPCMD_I(58), SUPCMD_I(59), SUPCMD_I(60), SUPCMD_I(61), SUPCMD_I(62), SUPCMD_I(63), SUPCMD_I(64), SUPCMD_I(65), SUPCMD_I(66), SUPCMD_I(67), SUPCMD_I(68), SUPCMD_I(69), SUPCMD_I(70), SUPCMD_I(71), SUPCMD_I(72), SUPCMD_I(73), SUPCMD_I(74), SUPCMD_I(75), SUPCMD_I(76), SUPCMD_I(77), SUPCMD_I(78), SUPCMD_I(79), SUPCMD_I(80), SUPCMD_I(81), SUPCMD_I(82), SUPCMD_I(83), SUPCMD_I(84), SUPCMD_I(85), SUPCMD_I(86), SUPCMD_I(87), SUPCMD_I(88), SUPCMD_I(89), SUPCMD_I(90), SUPCMD_I(91), SUPCMD_I(92), SUPCMD_I(93), SUPCMD_I(94), SUPCMD_I(95), SUPCMD_I(96), SUPCMD_I(97), SUPCMD_I(98), SUPCMD_I(99), SUPCMD_I(100), SUPCMD_I(101), SUPCMD_I(102), SUPCMD_I(103), SUPCMD_I(104), SUPCMD_I(105), SUPCMD_I(106), SUPCMD_I(107), SUPCMD_I(108), SUPCMD_I(109), SUPCMD_I(110), SUPCMD_I(111), SUPCMD_I(112), SUPCMD_I(113), SUPCMD_I(114), SUPCMD_I(115), SUPCMD_I(116), SUPCMD_I(117), SUPCMD_I(118), SUPCMD_I(119), SUPCMD_I(120), SUPCMD_I(121), SUPCMD_I(122), SUPCMD_I(123), SUPCMD_I(124), SUPCMD_I(125), SUPCMD_I(126), SUPCMD_I(127), SUPCMD_I(128), SUPCMD_I(129), SUPCMD_I(130), SUPCMD_I(131), SUPCMD_I(132), SUPCMD_I(133), SUPCMD_I(134), SUPCMD_I(135), SUPCMD_I(136), SUPCMD_I(137), SUPCMD_I(138), SUPCMD_I(139), SUPCMD_I(140), SUPCMD_I(141), SUPCMD_I(142), SUPCMD_I(143), SUPCMD_I(144), SUPCMD_I(145), SUPCMD_I(146), SUPCMD_I(147), SUPCMD_I(148), SUPCMD_I(149), SUPCMD_I(150), SUPCMD_I(151), SUPCMD_I(152), SUPCMD_I(153), SUPCMD_I(154), SUPCMD_I(155), SUPCMD_I(156), SUPCMD_I(157), SUPCMD_I(158), SUPCMD_I(159), SUPCMD_I(160), SUPCMD_I(161), SUPCMD_I(162), SUPCMD_I(163), SUPCMD_I(164), SUPCMD_I(165), SUPCMD_I(166), SUPCMD_I(167), SUPCMD_I(168), SUPCMD_I(169), SUPCMD_I(170), SUPCMD_I(171), SUPCMD_I(172), SUPCMD_I(173), SUPCMD_I(174), SUPCMD_I(175), SUPCMD_I(176), SUPCMD_I(177), SUPCMD_I(178), SUPCMD_I(179), SUPCMD_I(180), SUPCMD_I(181), SUPCMD_I(182), SUPCMD_I(183), SUPCMD_I(184), SUPCMD_I(185), SUPCMD_I(186), SUPCMD_I(187), SUPCMD_I(188), SUPCMD_I(189), SUPCMD_I(190), SUPCMD_I(191), SUPCMD_I(192), SUPCMD_I(193), SUPCMD_I(194), SUPCMD_I(195), SUPCMD_I(196), SUPCMD_I(197), SUPCMD_I(198), SUPCMD_I(199), SUPCMD_I(200), SUPCMD_I(201), SUPCMD_I(202), SUPCMD_I(203), SUPCMD_I(204), SUPCMD_I(205), SUPCMD_I(206), SUPCMD_I(207), SUPCMD_I(208), SUPCMD_I(209), SUPCMD_I(210), SUPCMD_I(211), SUPCMD_I(212), SUPCMD_I(213), SUPCMD_I(214), SUPCMD_I(215), SUPCMD_I(216), SUPCMD_I(217), SUPCMD_I(218), SUPCMD_I(219), SUPCMD_I(220), SUPCMD_I(221), SUPCMD_I(222), SUPCMD_I(223), SUPCMD_I(224), SUPCMD_I(225), SUPCMD_I(226), SUPCMD_I(227), SUPCMD_I(228), SUPCMD_I(229), SUPCMD_I(230), SUPCMD_I(231), SUPCMD_I(232), SUPCMD_I(233), SUPCMD_I(234), SUPCMD_I(235), SUPCMD_I(236), SUPCMD_I(237), SUPCMD_I(238), SUPCMD_I(239), SUPCMD_I(240), SUPCMD_I(241), SUPCMD_I(242), SUPCMD_I(243), SUPCMD_I(244), SUPCMD_I(245), SUPCMD_I(246), SUPCMD_I(247), SUPCMD_I(248), SUPCMD_I(249), SUPCMD_I(250), SUPCMD_I(251), SUPCMD_I(252), SUPCMD_I(253), SUPCMD_I(254), SUPCMD_I(255) }; const nvmeadm_log_field_info_t supcmd_field_info = { .nlfi_log = "supcmd", .nlfi_fields = supcmd_fields, .nlfi_nfields = ARRAY_SIZE(supcmd_fields), .nlfi_min = sizeof (nvme_cmdeff_log_t), }; static const nvmeadm_field_bit_t supmicmd_cs_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "csupp", .nfb_desc = "Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "lbcc", .nfb_desc = "Logical Block Content", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "ncc", .nfb_desc = "Namespace Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "nic", .nfb_desc = "Namespace Inventory", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "ccc", .nfb_desc = "Controller Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 20, .nfb_hibit = 31, .nfb_short = "csp", .nfb_desc = "Command Scope", .nfb_vers = &nvme_vers_2v0, NVMEADM_FB_BITS(supcmd_csp_bits) } }; #define SUPMICMD(f) { .nf_off = offsetof(nvme_supmicmd_log_t, \ mcl_cmds[f]), \ .nf_len = sizeof (((nvme_supmicmd_log_t *)NULL)->mcl_cmds[f]), \ .nf_short = "mics" #f, .nf_desc = \ "Management Interface Command Supported " #f, \ NVMEADM_F_BITS(supmicmd_cs_bits) } static const nvmeadm_field_t supmicmd_fields[] = { SUPMICMD(0), SUPMICMD(1), SUPMICMD(2), SUPMICMD(3), SUPMICMD(4), SUPMICMD(5), SUPMICMD(6), SUPMICMD(7), SUPMICMD(8), SUPMICMD(9), SUPMICMD(10), SUPMICMD(11), SUPMICMD(12), SUPMICMD(13), SUPMICMD(14), SUPMICMD(15), SUPMICMD(16), SUPMICMD(17), SUPMICMD(18), SUPMICMD(19), SUPMICMD(20), SUPMICMD(21), SUPMICMD(22), SUPMICMD(23), SUPMICMD(24), SUPMICMD(25), SUPMICMD(26), SUPMICMD(27), SUPMICMD(28), SUPMICMD(29), SUPMICMD(30), SUPMICMD(31), SUPMICMD(32), SUPMICMD(33), SUPMICMD(34), SUPMICMD(35), SUPMICMD(36), SUPMICMD(37), SUPMICMD(38), SUPMICMD(39), SUPMICMD(40), SUPMICMD(41), SUPMICMD(42), SUPMICMD(43), SUPMICMD(44), SUPMICMD(45), SUPMICMD(46), SUPMICMD(47), SUPMICMD(48), SUPMICMD(49), SUPMICMD(50), SUPMICMD(51), SUPMICMD(52), SUPMICMD(53), SUPMICMD(54), SUPMICMD(55), SUPMICMD(56), SUPMICMD(57), SUPMICMD(58), SUPMICMD(59), SUPMICMD(60), SUPMICMD(61), SUPMICMD(62), SUPMICMD(63), SUPMICMD(64), SUPMICMD(65), SUPMICMD(66), SUPMICMD(67), SUPMICMD(68), SUPMICMD(69), SUPMICMD(70), SUPMICMD(71), SUPMICMD(72), SUPMICMD(73), SUPMICMD(74), SUPMICMD(75), SUPMICMD(76), SUPMICMD(77), SUPMICMD(78), SUPMICMD(79), SUPMICMD(80), SUPMICMD(81), SUPMICMD(82), SUPMICMD(83), SUPMICMD(84), SUPMICMD(85), SUPMICMD(86), SUPMICMD(87), SUPMICMD(88), SUPMICMD(89), SUPMICMD(90), SUPMICMD(91), SUPMICMD(92), SUPMICMD(93), SUPMICMD(94), SUPMICMD(95), SUPMICMD(96), SUPMICMD(97), SUPMICMD(98), SUPMICMD(99), SUPMICMD(100), SUPMICMD(101), SUPMICMD(102), SUPMICMD(103), SUPMICMD(104), SUPMICMD(105), SUPMICMD(106), SUPMICMD(107), SUPMICMD(108), SUPMICMD(109), SUPMICMD(110), SUPMICMD(111), SUPMICMD(112), SUPMICMD(113), SUPMICMD(114), SUPMICMD(115), SUPMICMD(116), SUPMICMD(117), SUPMICMD(118), SUPMICMD(119), SUPMICMD(120), SUPMICMD(121), SUPMICMD(122), SUPMICMD(123), SUPMICMD(124), SUPMICMD(125), SUPMICMD(126), SUPMICMD(127), SUPMICMD(128), SUPMICMD(129), SUPMICMD(130), SUPMICMD(131), SUPMICMD(132), SUPMICMD(133), SUPMICMD(134), SUPMICMD(135), SUPMICMD(136), SUPMICMD(137), SUPMICMD(138), SUPMICMD(139), SUPMICMD(140), SUPMICMD(141), SUPMICMD(142), SUPMICMD(143), SUPMICMD(144), SUPMICMD(145), SUPMICMD(146), SUPMICMD(147), SUPMICMD(148), SUPMICMD(149), SUPMICMD(150), SUPMICMD(151), SUPMICMD(152), SUPMICMD(153), SUPMICMD(154), SUPMICMD(155), SUPMICMD(156), SUPMICMD(157), SUPMICMD(158), SUPMICMD(159), SUPMICMD(160), SUPMICMD(161), SUPMICMD(162), SUPMICMD(163), SUPMICMD(164), SUPMICMD(165), SUPMICMD(166), SUPMICMD(167), SUPMICMD(168), SUPMICMD(169), SUPMICMD(170), SUPMICMD(171), SUPMICMD(172), SUPMICMD(173), SUPMICMD(174), SUPMICMD(175), SUPMICMD(176), SUPMICMD(177), SUPMICMD(178), SUPMICMD(179), SUPMICMD(180), SUPMICMD(181), SUPMICMD(182), SUPMICMD(183), SUPMICMD(184), SUPMICMD(185), SUPMICMD(186), SUPMICMD(187), SUPMICMD(188), SUPMICMD(189), SUPMICMD(190), SUPMICMD(191), SUPMICMD(192), SUPMICMD(193), SUPMICMD(194), SUPMICMD(195), SUPMICMD(196), SUPMICMD(197), SUPMICMD(198), SUPMICMD(199), SUPMICMD(200), SUPMICMD(201), SUPMICMD(202), SUPMICMD(203), SUPMICMD(204), SUPMICMD(205), SUPMICMD(206), SUPMICMD(207), SUPMICMD(208), SUPMICMD(209), SUPMICMD(210), SUPMICMD(211), SUPMICMD(212), SUPMICMD(213), SUPMICMD(214), SUPMICMD(215), SUPMICMD(216), SUPMICMD(217), SUPMICMD(218), SUPMICMD(219), SUPMICMD(220), SUPMICMD(221), SUPMICMD(222), SUPMICMD(223), SUPMICMD(224), SUPMICMD(225), SUPMICMD(226), SUPMICMD(227), SUPMICMD(228), SUPMICMD(229), SUPMICMD(230), SUPMICMD(231), SUPMICMD(232), SUPMICMD(233), SUPMICMD(234), SUPMICMD(235), SUPMICMD(236), SUPMICMD(237), SUPMICMD(238), SUPMICMD(239), SUPMICMD(240), SUPMICMD(241), SUPMICMD(242), SUPMICMD(243), SUPMICMD(244), SUPMICMD(245), SUPMICMD(246), SUPMICMD(247), SUPMICMD(248), SUPMICMD(249), SUPMICMD(250), SUPMICMD(251), SUPMICMD(252), SUPMICMD(253), SUPMICMD(254), SUPMICMD(255), }; const nvmeadm_log_field_info_t supmicmd_field_info = { .nlfi_log = "supmicmd", .nlfi_fields = supmicmd_fields, .nlfi_nfields = ARRAY_SIZE(supmicmd_fields), .nlfi_min = sizeof (nvme_supmicmd_log_t), }; static const nvmeadm_field_bit_t supfeat_csp_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "nscpe", .nfb_desc = "Namespace Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "cscpe", .nfb_desc = "Controller Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "nsetcpe", .nfb_desc = "NVM Set Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "egscpe", .nfb_desc = "Endurance Group Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "dscpe", .nfb_desc = "Domain Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 5, .nfb_hibit = 5, .nfb_short = "nsscpe", .nfb_desc = "NVM Subsystem Scope", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } }, { .nfb_lowbit = 6, .nfb_hibit = 6, .nfb_short = "cdqscp", .nfb_desc = "Controller Data Queue", .nfb_vers = &nvme_vers_2v1, .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t supfeat_cs_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "fsupp", .nfb_desc = "Feature", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "lbcc", .nfb_desc = "Logical Block Content", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "ncc", .nfb_desc = "Namespace Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "nic", .nfb_desc = "Namespace Inventory", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "ccc", .nfb_desc = "Controller Capability", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unchanged", "changed" } }, { .nfb_lowbit = 19, .nfb_hibit = 19, .nfb_short = "uss", .nfb_vers = &nvme_vers_1v4, .nfb_desc = "UUID Selection", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 20, .nfb_hibit = 31, .nfb_short = "csp", .nfb_desc = "Command Scope", .nfb_vers = &nvme_vers_2v0, NVMEADM_FB_BITS(supfeat_csp_bits) } }; #define SUPFEAT(f) { .nf_off = offsetof(nvme_supfeat_log_t, \ nsl_feats[f]), \ .nf_len = sizeof (((nvme_supfeat_log_t *)NULL)->nsl_feats[f]), \ .nf_short = "fis" #f, .nf_desc = "Feature Identifier Supported " #f, \ NVMEADM_F_BITS(supfeat_cs_bits) } static const nvmeadm_field_t supfeat_fields[] = { SUPFEAT(0), SUPFEAT(1), SUPFEAT(2), SUPFEAT(3), SUPFEAT(4), SUPFEAT(5), SUPFEAT(6), SUPFEAT(7), SUPFEAT(8), SUPFEAT(9), SUPFEAT(10), SUPFEAT(11), SUPFEAT(12), SUPFEAT(13), SUPFEAT(14), SUPFEAT(15), SUPFEAT(16), SUPFEAT(17), SUPFEAT(18), SUPFEAT(19), SUPFEAT(20), SUPFEAT(21), SUPFEAT(22), SUPFEAT(23), SUPFEAT(24), SUPFEAT(25), SUPFEAT(26), SUPFEAT(27), SUPFEAT(28), SUPFEAT(29), SUPFEAT(30), SUPFEAT(31), SUPFEAT(32), SUPFEAT(33), SUPFEAT(34), SUPFEAT(35), SUPFEAT(36), SUPFEAT(37), SUPFEAT(38), SUPFEAT(39), SUPFEAT(40), SUPFEAT(41), SUPFEAT(42), SUPFEAT(43), SUPFEAT(44), SUPFEAT(45), SUPFEAT(46), SUPFEAT(47), SUPFEAT(48), SUPFEAT(49), SUPFEAT(50), SUPFEAT(51), SUPFEAT(52), SUPFEAT(53), SUPFEAT(54), SUPFEAT(55), SUPFEAT(56), SUPFEAT(57), SUPFEAT(58), SUPFEAT(59), SUPFEAT(60), SUPFEAT(61), SUPFEAT(62), SUPFEAT(63), SUPFEAT(64), SUPFEAT(65), SUPFEAT(66), SUPFEAT(67), SUPFEAT(68), SUPFEAT(69), SUPFEAT(70), SUPFEAT(71), SUPFEAT(72), SUPFEAT(73), SUPFEAT(74), SUPFEAT(75), SUPFEAT(76), SUPFEAT(77), SUPFEAT(78), SUPFEAT(79), SUPFEAT(80), SUPFEAT(81), SUPFEAT(82), SUPFEAT(83), SUPFEAT(84), SUPFEAT(85), SUPFEAT(86), SUPFEAT(87), SUPFEAT(88), SUPFEAT(89), SUPFEAT(90), SUPFEAT(91), SUPFEAT(92), SUPFEAT(93), SUPFEAT(94), SUPFEAT(95), SUPFEAT(96), SUPFEAT(97), SUPFEAT(98), SUPFEAT(99), SUPFEAT(100), SUPFEAT(101), SUPFEAT(102), SUPFEAT(103), SUPFEAT(104), SUPFEAT(105), SUPFEAT(106), SUPFEAT(107), SUPFEAT(108), SUPFEAT(109), SUPFEAT(110), SUPFEAT(111), SUPFEAT(112), SUPFEAT(113), SUPFEAT(114), SUPFEAT(115), SUPFEAT(116), SUPFEAT(117), SUPFEAT(118), SUPFEAT(119), SUPFEAT(120), SUPFEAT(121), SUPFEAT(122), SUPFEAT(123), SUPFEAT(124), SUPFEAT(125), SUPFEAT(126), SUPFEAT(127), SUPFEAT(128), SUPFEAT(129), SUPFEAT(130), SUPFEAT(131), SUPFEAT(132), SUPFEAT(133), SUPFEAT(134), SUPFEAT(135), SUPFEAT(136), SUPFEAT(137), SUPFEAT(138), SUPFEAT(139), SUPFEAT(140), SUPFEAT(141), SUPFEAT(142), SUPFEAT(143), SUPFEAT(144), SUPFEAT(145), SUPFEAT(146), SUPFEAT(147), SUPFEAT(148), SUPFEAT(149), SUPFEAT(150), SUPFEAT(151), SUPFEAT(152), SUPFEAT(153), SUPFEAT(154), SUPFEAT(155), SUPFEAT(156), SUPFEAT(157), SUPFEAT(158), SUPFEAT(159), SUPFEAT(160), SUPFEAT(161), SUPFEAT(162), SUPFEAT(163), SUPFEAT(164), SUPFEAT(165), SUPFEAT(166), SUPFEAT(167), SUPFEAT(168), SUPFEAT(169), SUPFEAT(170), SUPFEAT(171), SUPFEAT(172), SUPFEAT(173), SUPFEAT(174), SUPFEAT(175), SUPFEAT(176), SUPFEAT(177), SUPFEAT(178), SUPFEAT(179), SUPFEAT(180), SUPFEAT(181), SUPFEAT(182), SUPFEAT(183), SUPFEAT(184), SUPFEAT(185), SUPFEAT(186), SUPFEAT(187), SUPFEAT(188), SUPFEAT(189), SUPFEAT(190), SUPFEAT(191), SUPFEAT(192), SUPFEAT(193), SUPFEAT(194), SUPFEAT(195), SUPFEAT(196), SUPFEAT(197), SUPFEAT(198), SUPFEAT(199), SUPFEAT(200), SUPFEAT(201), SUPFEAT(202), SUPFEAT(203), SUPFEAT(204), SUPFEAT(205), SUPFEAT(206), SUPFEAT(207), SUPFEAT(208), SUPFEAT(209), SUPFEAT(210), SUPFEAT(211), SUPFEAT(212), SUPFEAT(213), SUPFEAT(214), SUPFEAT(215), SUPFEAT(216), SUPFEAT(217), SUPFEAT(218), SUPFEAT(219), SUPFEAT(220), SUPFEAT(221), SUPFEAT(222), SUPFEAT(223), SUPFEAT(224), SUPFEAT(225), SUPFEAT(226), SUPFEAT(227), SUPFEAT(228), SUPFEAT(229), SUPFEAT(230), SUPFEAT(231), SUPFEAT(232), SUPFEAT(233), SUPFEAT(234), SUPFEAT(235), SUPFEAT(236), SUPFEAT(237), SUPFEAT(238), SUPFEAT(239), SUPFEAT(240), SUPFEAT(241), SUPFEAT(242), SUPFEAT(243), SUPFEAT(244), SUPFEAT(245), SUPFEAT(246), SUPFEAT(247), SUPFEAT(248), SUPFEAT(249), SUPFEAT(250), SUPFEAT(251), SUPFEAT(252), SUPFEAT(253), SUPFEAT(254), SUPFEAT(255), }; const nvmeadm_log_field_info_t supfeat_field_info = { .nlfi_log = "supfeat", .nlfi_fields = supfeat_fields, .nlfi_nfields = ARRAY_SIZE(supfeat_fields), .nlfi_min = sizeof (nvme_supfeat_log_t), }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Deal with Micron-specific logs. */ #include #include #include #include #include #include "nvmeadm.h" /* * Synthetic identifiers for these logs to deal with the changes that we have * found in here over time. See micron_vul_ext_smart_getvers() for more. */ #define MICRON_GEN_73XX 1 #define MICRON_GEN_74XX 2 #define MICRON_F_SMART(f) \ .nf_off = offsetof(micron_vul_ext_smart_t, mes_##f), \ .nf_len = sizeof (((micron_vul_ext_smart_t *)NULL)->mes_##f) static const nvmeadm_field_bit_t micron_vul_ext_smart_wpr_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "dramue", .nfb_desc = "DRAM Double Bit Error", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "spare", .nfb_desc = "Low Remaining Spare Block Count", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "cap", .nfb_desc = "Power Holdup Capacitor Failure", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "nvram", .nfb_desc = "NVRAM Checksum Failure", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "daor", .nfb_desc = "DRAM Address Out of Range", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } }, { .nfb_lowbit = 5, .nfb_hibit = 5, .nfb_short = "temp", .nfb_desc = "Overtemp Shutdown", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "did not occur", "occurred" } } }; static const nvmeadm_field_t micron_vul_ext_smart_fields[] = { { MICRON_F_SMART(gbb), .nf_short = "gbb", .nf_desc = "Grown Bad Block Count", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(max_erase), .nf_short = "mec", .nf_desc = "Per-Block Max Erase Count", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(power_on), .nf_short = "pon", .nf_desc = "Power-on", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "min" } }, { MICRON_F_SMART(wp_reason), .nf_short = "wpr", .nf_desc = "Write Protect Reason", NVMEADM_F_BITS(micron_vul_ext_smart_wpr_bits) }, { MICRON_F_SMART(cap), .nf_short = "cap", .nf_desc = "Device Capacity", .nf_type = NVMEADM_FT_BYTES }, { MICRON_F_SMART(erase_count), .nf_short = "tec", .nf_desc = "Total Erase Count", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(use_rate), .nf_short = "use", .nf_desc = "Lifetime Use Rate", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(erase_fail), .nf_short = "efc", .nf_desc = "Erase Fail Count", .nf_rev = MICRON_GEN_74XX, .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(uecc), .nf_short = "uecc", .nf_desc = "Reported Uncorrectable ECC Errors", .nf_rev = MICRON_GEN_74XX, .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(prog_fail), .nf_short = "pfc", .nf_desc = "Program Fail Count", .nf_rev = MICRON_GEN_74XX, .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(read_bytes), .nf_short = "read", .nf_desc = "Total Bytes Read", .nf_rev = MICRON_GEN_74XX, .nf_type = NVMEADM_FT_BYTES }, { MICRON_F_SMART(write_bytes), .nf_short = "write", .nf_desc = "Total Bytes Written", .nf_rev = MICRON_GEN_74XX, .nf_type = NVMEADM_FT_BYTES }, { MICRON_F_SMART(trans_size), .nf_short = "tus", .nf_desc = "Translation Unit Size", .nf_type = NVMEADM_FT_BYTES }, { MICRON_F_SMART(bs_total), .nf_short = "tbs", .nf_desc = "Total Block Stripe Count for User Data", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(bs_free), .nf_short = "fbs", .nf_desc = "Free Block Stripe Count for User Data", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(bs_cap), .nf_short = "bss", .nf_desc = "Block Stripe Size", .nf_type = NVMEADM_FT_BYTES }, { MICRON_F_SMART(user_erase_min), .nf_short = "ubemin", .nf_desc = "Minimum User Block Erase Count", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(user_erase_avg), .nf_short = "ubeavg", .nf_desc = "Average User Block Erase Count", .nf_type = NVMEADM_FT_HEX }, { MICRON_F_SMART(user_erase_max), .nf_short = "ubemax", .nf_desc = "Maximum User Block Erase Count", .nf_type = NVMEADM_FT_HEX } }; /* * The 73xx series and 74xx series have some different entries in these log * pages. There is no good way to determine this in the log. Instead we use a * crude but reasonable heuristic. The 74xx series added a pair of counters for * total bytes read and written. If these are zero, then we know we're on the * 73xx assuming it's playing by its reserved rules. We've also seen some cases * where the 73xx parts will return all 1s for the reserved fields, so we check * that too. */ static uint32_t micron_vul_ext_smart_getvers(const void *data, size_t len) { const uint8_t zero[16] = { 0 }; uint8_t ones[16]; if (len < sizeof (micron_vul_ext_smart_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (micron_vul_ext_smart_t)); } (void) memset(ones, 0xff, sizeof (ones)); const micron_vul_ext_smart_t *log = data; if (memcmp(zero, log->mes_read_bytes, sizeof (zero)) == 0 && memcmp(zero, log->mes_write_bytes, sizeof (zero)) == 0) { return (MICRON_GEN_73XX); } if (memcmp(ones, log->mes_read_bytes, sizeof (ones)) == 0 && memcmp(ones, log->mes_write_bytes, sizeof (ones)) == 0) { return (MICRON_GEN_73XX); } return (MICRON_GEN_74XX); } const nvmeadm_log_field_info_t micron_vul_extsmart_field_info = { .nlfi_log = "micron/extsmart", .nlfi_fields = micron_vul_ext_smart_fields, .nlfi_nfields = ARRAY_SIZE(micron_vul_ext_smart_fields), .nlfi_min = sizeof (micron_vul_ext_smart_t), .nlfi_getrev = micron_vul_ext_smart_getvers }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * NVMe Namespace Management Commands */ #include #include #include #include "nvmeadm.h" /* * Attempt to parse a string with a power of 2 unit suffix into a uint64_t. We * stop allowing suffixes at PiB as we're trying to fit this into a uint64_t and * there aren't really many valid values of EiB. In the future when we have * devices with such large capacities, we should change this to return a * uint128_t style value as it's possible that with a larger block size, that * this will make more sense. When we do that, we should probably also figure * out how we want to commonize this function across the tree. */ static uint64_t nvmeadm_parse_units(const char *str, const char *desc) { unsigned long long l; char *eptr; const char units[] = { 'B', 'K', 'M', 'G', 'T', 'P' }; errno = 0; l = strtoull(str, &eptr, 0); if (errno != 0) { err(-1, "failed to parse %s: %s", desc, str); } if (*eptr == '\0') { return ((uint64_t)l); } if (eptr[1] != '\0') { errx(-1, "failed to parse %s unit suffix: %s", desc, eptr); } for (size_t i = 0; i < ARRAY_SIZE(units); i++) { if (strncasecmp(eptr, &units[i], 1) != 0) { continue; } for (; i > 0; i--) { const uint64_t max = UINT64_MAX / 1024; if (l > max) { errx(-1, "%s value %s would overflow a " "uint64_t", desc, str); } l *= 1024; } return ((uint64_t)l); } errx(-1, "invalid %s unit suffix: %s", desc, eptr); } /* * Today create-namespace takes a limited number of arguments. Here is how we * expect it to continue to change over time: * * 1) First, we have a limited number of short options that we support. If we * ever support long options, these should match the NVMe name for the option, * e.g. --nsze, --ncap, --nmic, etc. * * 2) Today we require that this operates only when the namespace is already * detached from a controller. If we want to change this behavior then we should * add something such as a [-R] flag to indicate that it should take all the * other steps necessary recursively. * * 3) Most other options have a default that indicates that they're unused or * similar. This allows us to add additional option arguments. Some of these may * end up with aliases for the default case, e.g. -t nvm for the default NVM * CSI. * * 4) We only support specifying the size of a namespace in bytes today. If we * want to change this we should add a flag like a -B that specifies that all * sizes are in units of the logical block. */ void usage_create_ns(const char *c_name) { (void) fprintf(stderr, "%s -f flbas | -b block-size [-c cap] " "[-n nmic]\n\t [-t type] \n\n" " Create a new namespace on the specified controller of the " "requested size. The\n size is specified in bytes and may use " "any suffix such as B (bytes), K\n (kibibytes, 2^10), M " "(mibibytes, 2^20), G (gibibytes, 2^30), T (tebibytes,\n 2^40), " "etc. The size must be a multiple of the selected block size. The\n" " controller may impose additional alignment constraints.\n", c_name); } void optparse_create_ns(nvme_process_arg_t *npa) { int c; nvmeadm_create_ns_t *ncn; const char *nmic = NULL, *type = NULL, *cap = NULL; const char *bs = NULL, *flbas = NULL; if ((ncn = calloc(1, sizeof (nvmeadm_create_ns_t))) == NULL) { err(-1, "failed to allocate memory to track create-namespace " "information"); } npa->npa_cmd_arg = ncn; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":b:c:f:n:t:")) != -1) { switch (c) { case 'b': bs = optarg; break; case 'c': cap = optarg; break; case 'f': flbas = optarg; break; case 'n': nmic = optarg; break; case 't': type = optarg; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (flbas != NULL && bs != NULL) { errx(-1, "only one of -b and -f may be specified"); } if (flbas == NULL && bs == NULL) { errx(-1, "at least one of -b and -f must be specified"); } if (flbas != NULL) { const char *err; ncn->ncn_use_flbas = B_TRUE; ncn->ncn_lba = strtonumx(flbas, 0, NVME_MAX_LBAF - 1, &err, 0); if (err != NULL) { errx(-1, "failed to parse formatted LBA index: %s is " "%s, valid values are between 0 and %u", flbas, err, NVME_MAX_LBAF - 1); } } if (bs != NULL) { ncn->ncn_use_flbas = B_FALSE; ncn->ncn_lba = nvmeadm_parse_units(bs, "block-size"); } if (cap != NULL) { ncn->ncn_cap = nvmeadm_parse_units(cap, "block-size"); } else { ncn->ncn_cap = UINT64_MAX; } if (type != NULL) { if (strcasecmp(type, "nvm") == 0) { ncn->ncn_csi = NVME_CSI_NVM; } else if (strcasecmp(type, "kv") == 0) { ncn->ncn_csi = NVME_CSI_KV; } else if (strcasecmp(type, "zns") == 0) { ncn->ncn_csi = NVME_CSI_ZNS; } else { errx(-1, "unknown CSI type string: '%s'; valid values " "are 'nvm', 'kv', and 'zns'", type); } } else { ncn->ncn_csi = NVME_CSI_NVM; } if (nmic != NULL) { if (strcasecmp(nmic, "none") == 0) { ncn->ncn_nmic = NVME_NS_NMIC_T_NONE; } else if (strcasecmp(nmic, "shared") == 0) { ncn->ncn_nmic = NVME_NS_NMIC_T_SHARED; } else { errx(-1, "unknown nmic string: '%s'; valid values are " "'none' and 'shared'", nmic); } } else { ncn->ncn_nmic = NVME_NS_NMIC_T_NONE; } if (npa->npa_argc - optind > 2) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[optind + 2]); } else if (npa->npa_argc - optind != 2) { errx(-1, "missing required size parameter"); } ncn->ncn_size = nvmeadm_parse_units(npa->npa_argv[optind + 1], "namespace size"); if (cap == NULL) { ncn->ncn_cap = ncn->ncn_size; } } static const nvme_nvm_lba_fmt_t * do_create_ns_find_lba(const nvme_process_arg_t *npa, const nvmeadm_create_ns_t *ncn) { const uint32_t nfmts = nvme_ctrl_info_nformats(npa->npa_ctrl_info); const nvme_nvm_lba_fmt_t *best = NULL; uint32_t best_rp = UINT32_MAX; for (size_t i = 0; i < nfmts; i++) { const nvme_nvm_lba_fmt_t *fmt; uint32_t rp; if (!nvme_ctrl_info_format(npa->npa_ctrl_info, i, &fmt)) { continue; } if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) continue; if (nvme_nvm_lba_fmt_data_size(fmt) != ncn->ncn_lba) continue; rp = nvme_nvm_lba_fmt_rel_perf(fmt); if (rp < best_rp) { best_rp = rp; best = fmt; } } if (best == NULL) { errx(-1, "failed to find an LBA format with %u byte block size", ncn->ncn_lba); } return (best); } int do_create_ns(const nvme_process_arg_t *npa) { const nvmeadm_create_ns_t *ncn = npa->npa_cmd_arg; nvme_ns_create_req_t *req; const nvme_nvm_lba_fmt_t *lba; uint32_t nsid, flbas, ds; uint64_t size; if (npa->npa_ns != NULL) { errx(-1, "%s cannot be used on namespaces", npa->npa_cmd->c_name); } /* * This should have been checked above. */ if (npa->npa_argc > 1) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[1]); } /* * If we were given a block size rather than the formatted LBA size, go * deal with converting that now. */ if (!ncn->ncn_use_flbas) { lba = do_create_ns_find_lba(npa, ncn); } else { if (!nvme_ctrl_info_format(npa->npa_ctrl_info, ncn->ncn_lba, &lba)) { nvmeadm_fatal(npa, "failed to look up LBA format index " "%u", ncn->ncn_lba); } } if (!nvme_ns_create_req_init_by_csi(npa->npa_ctrl, ncn->ncn_csi, &req)) { nvmeadm_fatal(npa, "failed to initialize namespace create " "request"); } ds = nvme_nvm_lba_fmt_data_size(lba); flbas = nvme_nvm_lba_fmt_id(lba); if (!nvme_ns_create_req_set_flbas(req, flbas)) { nvmeadm_fatal(npa, "failed to set namespace create request " "formatted LBA index to %u", flbas); } if (ncn->ncn_size % ds != 0) { nvmeadm_fatal(npa, "requested namespace size 0x%lx is not a " "multiple of the requested LBA block size (0x%x)", ncn->ncn_size, ds); } size = ncn->ncn_size / ds; if (!nvme_ns_create_req_set_nsze(req, size)) { nvmeadm_fatal(npa, "failed to set namespace create request " "namespace size to 0x%lx", size); } if (ncn->ncn_cap % ds != 0) { nvmeadm_fatal(npa, "requested namespace capacity 0x%lx is not " "a multiple of the requested LBA block size (0x%x)", ncn->ncn_cap, ds); } size = ncn->ncn_cap/ ds; if (!nvme_ns_create_req_set_ncap(req, size)) { nvmeadm_fatal(npa, "failed to set namespace create request " "namespace capacity to 0x%lx", size); } if (!nvme_ns_create_req_set_nmic(req, ncn->ncn_nmic)) { nvmeadm_fatal(npa, "failed to set namespace multipath I/O and " "sharing capabilities to 0x%x", ncn->ncn_nmic); } if (!nvme_ns_create_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute namespace create " "request"); } if (!nvme_ns_create_req_get_nsid(req, &nsid)) { nvmeadm_fatal(npa, "Failed to retrieve the new namespace ID"); } nvme_ns_create_req_fini(req); (void) printf("created namespace %s/%u\n", npa->npa_ctrl_name, nsid); return (EXIT_SUCCESS); } void usage_delete_ns(const char *c_name) { (void) fprintf(stderr, "%s /\n\n" " Delete the specified namespace. It must be first detached from " "all\n controllers. Controllers can be detached from a namespace " "with the\n detach-namespace sub-command.\n", c_name); } int do_delete_ns(const nvme_process_arg_t *npa) { nvme_ns_delete_req_t *req; if (npa->npa_ns == NULL) { errx(-1, "%s cannot be used on controllers", npa->npa_cmd->c_name); } if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if (!nvme_ns_delete_req_init(npa->npa_ctrl, &req)) { nvmeadm_fatal(npa, "failed to initialize namespace delete " "request"); } const uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (!nvme_ns_delete_req_set_nsid(req, nsid)) { nvmeadm_fatal(npa, "failed to set namespace delete request " "namespace ID to 0x%x", nsid); } if (!nvme_ns_delete_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute namespace delete " "request"); } nvme_ns_delete_req_fini(req); return (EXIT_SUCCESS); } /* * Currently both attach namespace and detach namespace only will perform an * attach or detach of the namespace from the current controller in the system. * In the future, we should probably support an argument to provide an explicit * controller list either in the form of IDs or device names, probably with -c * or -C. */ void usage_attach_ns(const char *c_name) { (void) fprintf(stderr, "%s /\n\n" " Attach the specified namespace to the current controller.\n", c_name); } void usage_detach_ns(const char *c_name) { (void) fprintf(stderr, "%s /\n\n" " Detach the specified namespace from its current controller. The " "namespace\n must have its blkdev instances detached with the " "detach sub-command.\n", c_name); } static int do_attach_ns_common(const nvme_process_arg_t *npa, uint32_t sel) { const char *desc = sel == NVME_NS_ATTACH_CTRL_ATTACH ? "attach" : "detach"; nvme_ns_attach_req_t *req; if (npa->npa_ns == NULL) { errx(-1, "%s cannot be used on controllers", npa->npa_cmd->c_name); } if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } if (!nvme_ns_attach_req_init_by_sel(npa->npa_ctrl, sel, &req)) { nvmeadm_fatal(npa, "failed to initialize controller " "%s request for %s", desc, npa->npa_name); } const uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (!nvme_ns_attach_req_set_nsid(req, nsid)) { nvmeadm_fatal(npa, "failed to set namespace to %s to %u", desc, nsid); } if (!nvme_ns_attach_req_set_ctrlid_self(req)) { nvmeadm_fatal(npa, "failed to set controller to %s for %s", desc, npa->npa_name); } if (!nvme_ns_attach_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute controller %s request", desc); } nvme_ns_attach_req_fini(req); return (EXIT_SUCCESS); } int do_attach_ns(const nvme_process_arg_t *npa) { return (do_attach_ns_common(npa, NVME_NS_ATTACH_CTRL_ATTACH)); } int do_detach_ns(const nvme_process_arg_t *npa) { return (do_attach_ns_common(npa, NVME_NS_ATTACH_CTRL_DETACH)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Field information for OCP logs. */ #include #include #include #include #include #include "nvmeadm.h" #define OCP_F_SMART(f) .nf_off = offsetof(ocp_vul_smart_t, osh_##f), \ .nf_len = sizeof (((ocp_vul_smart_t *)NULL)->osh_##f) static const nvmeadm_field_bit_t ocp_vul_smart_block_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 47, .nfb_short = "raw", .nfb_desc = "Raw Count", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 48, .nfb_hibit = 63, .nfb_short = "norm", .nfb_desc = "Normalized Value", .nfb_type = NVMEADM_FT_PERCENT, } }; static const nvmeadm_field_bit_t ocp_vul_smart_e2e_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 31, .nfb_short = "det", .nfb_desc = "Detected Errors", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 32, .nfb_hibit = 63, .nfb_short = "cor", .nfb_desc = "Corrected Errors", .nfb_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_bit_t ocp_vul_smart_udec_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 31, .nfb_short = "max", .nfb_desc = "Maximum Count", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 32, .nfb_hibit = 63, .nfb_short = "min", .nfb_desc = "Minimum Count", .nfb_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_bit_t ocp_vul_smart_therm_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 7, .nfb_short = "events", .nfb_desc = "Throttling Events", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 8, .nfb_hibit = 15, .nfb_short = "status", .nfb_desc = "Current Throttling Status", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unthrottled", "first level", "second level", "third level" } } }; static const nvmeadm_field_bit_t ocp_vul_smart_dssd_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 7, .nfb_short = "errata", .nfb_desc = "Errata Version", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 8, .nfb_hibit = 23, .nfb_short = "point", .nfb_desc = "Point Version", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 24, .nfb_hibit = 39, .nfb_short = "minor", .nfb_desc = "Minor Version", .nfb_type = NVMEADM_FT_HEX, }, { .nfb_lowbit = 40, .nfb_hibit = 47, .nfb_short = "major", .nfb_desc = "Major Version", .nfb_type = NVMEADM_FT_HEX, } }; static const nvmeadm_field_t ocp_vul_smart_fields[] = { { OCP_F_SMART(pmed_write), .nf_short = "pmuw", .nf_desc = "Physical Media Units Written", .nf_type = NVMEADM_FT_BYTES }, { OCP_F_SMART(pmed_read), .nf_short = "pmur", .nf_desc = "Physical Media Units Read", .nf_type = NVMEADM_FT_BYTES }, { OCP_F_SMART(bunb), .nf_short = "bunb", .nf_desc = "Bad User NAND Blocks", NVMEADM_F_BITS(ocp_vul_smart_block_bits) }, { OCP_F_SMART(bsnb), .nf_short = "bsnb", .nf_desc = "Bad System NAND Blocks", NVMEADM_F_BITS(ocp_vul_smart_block_bits) }, { OCP_F_SMART(xor_rec), .nf_short = "xrc", .nf_desc = "XOR Recovery Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(read_unrec), .nf_short = "urec", .nf_desc = "Uncorrectable Read Error Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(soft_ecc_err), .nf_short = "seec", .nf_desc = "Soft ECC Error Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(e2e), .nf_short = "e2e", .nf_desc = "End to End Correction Counts", NVMEADM_F_BITS(ocp_vul_smart_e2e_bits) }, { OCP_F_SMART(sys_used), .nf_short = "sdu", .nf_desc = "System Data Percent Used", .nf_type = NVMEADM_FT_PERCENT }, { OCP_F_SMART(refresh), .nf_short = "refresh", .nf_desc = "Refresh Counts", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(udec), .nf_short = "udec", .nf_desc = "User Data Erase Counts", NVMEADM_F_BITS(ocp_vul_smart_udec_bits) }, { OCP_F_SMART(therm), .nf_short = "therm", .nf_desc = "Thermal Throttling Status and Count", NVMEADM_F_BITS(ocp_vul_smart_therm_bits) }, { OCP_F_SMART(dssd), .nf_short = "dssd", .nf_desc = "DSSD Specification Version", .nf_rev = 3, NVMEADM_F_BITS(ocp_vul_smart_dssd_bits) }, { OCP_F_SMART(pcie_errcor), .nf_short = "pcicor", .nf_desc = "PCIe Correctable Error Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(inc_shut), .nf_short = "incshut", .nf_desc = "Incomplete Shutdowns", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(free), .nf_short = "freeblk", .nf_desc = "Percent Free Blocks", .nf_type = NVMEADM_FT_PERCENT }, { OCP_F_SMART(cap_health), .nf_short = "cap", .nf_desc = "Capacitor Health", .nf_type = NVMEADM_FT_PERCENT }, { OCP_F_SMART(nvme_base_errata), .nf_short = "baseev", .nf_desc = "NVMe Base Errata Version", .nf_rev = 3, .nf_type = NVMEADM_FT_ASCII }, { OCP_F_SMART(nvme_cmd_errata), .nf_short = "cmdev", .nf_desc = "NVMe Command Set Errata Version", .nf_rev = 4, .nf_type = NVMEADM_FT_ASCII }, { OCP_F_SMART(unaligned), .nf_short = "unalign", .nf_desc = "Unaligned I/O", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(sec_vers), .nf_short = "secvers", .nf_desc = "Security Version Number", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(nuse), .nf_short = "nuse", .nf_desc = "Total NUSE", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(plp_start), .nf_short = "plp", .nf_desc = "PLP Start Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(endurance), .nf_short = "endest", .nf_desc = "Endurance Estimate", .nf_type = NVMEADM_FT_BYTES }, { OCP_F_SMART(pcie_retrain), .nf_short = "retrain", .nf_desc = "PCIe Link Retraining Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(ps_change), .nf_short = "pstate", .nf_desc = "Power State Change Count", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(min_fwrev), .nf_short = "minfw", .nf_desc = "Lowest Permitted Firmware Revision", .nf_type = NVMEADM_FT_ASCII }, { OCP_F_SMART(vers), .nf_short = "lpv", .nf_desc = "Log Page Version", .nf_type = NVMEADM_FT_HEX }, { OCP_F_SMART(guid), .nf_short = "lpg", .nf_desc = "Log Page GUID", .nf_type = NVMEADM_FT_GUID } }; static uint32_t ocp_vul_smart_getvers(const void *data, size_t len) { if (len < sizeof (ocp_vul_smart_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (ocp_vul_smart_t)); } const ocp_vul_smart_t *log = data; return (log->osh_vers); } const nvmeadm_log_field_info_t ocp_vul_smart_field_info = { .nlfi_log = "ocp/smart", .nlfi_fields = ocp_vul_smart_fields, .nlfi_nfields = ARRAY_SIZE(ocp_vul_smart_fields), .nlfi_min = sizeof (ocp_vul_smart_t), .nlfi_getrev = ocp_vul_smart_getvers }; #define OCP_F_ERRREC(f) .nf_off = offsetof(ocp_vul_errrec_t, oer_##f), \ .nf_len = sizeof (((ocp_vul_errrec_t *)NULL)->oer_##f) static const nvmeadm_field_bit_t ocp_vul_errrec_pra_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "ctrl", .nfb_desc = "NVMe Controller Reset", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "subsys", .nfb_desc = "NVMe Subsystem Reset", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "flr", .nfb_desc = "PCIe Function Level Reset", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "perst", .nfb_desc = "PERST#", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "power", .nfb_desc = "Main Power Cycle", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 5, .nfb_hibit = 5, .nfb_short = "hotrst", .nfb_desc = "PCIe Conventional Hot Reset", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } } }; static const nvmeadm_field_bit_t ocp_vul_errrec_dra_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "none", .nfb_desc = "No Action", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "fmt", .nfb_desc = "Format NVM", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "vsc", .nfb_desc = "Vendor Specific Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "valys", .nfb_desc = "Vendor Analysis", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "rep", .nfb_desc = "Device Replacement", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 5, .nfb_hibit = 5, .nfb_short = "san", .nfb_desc = "Sanitize", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } }, { .nfb_lowbit = 6, .nfb_hibit = 6, .nfb_short = "udl", .nfb_desc = "User Data Loss", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not required", "required" } } }; static const nvmeadm_field_bit_t ocp_vul_errrec_devcap_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "aen", .nfb_desc = "Panic AEN", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "cfs", .nfb_desc = "Panic CFS", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } } }; static const nvmeadm_field_t ocp_vul_errrec_fields[] = { { OCP_F_ERRREC(prwt), .nf_short = "prwt", .nf_desc = "Panic Reset Wait Time", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(pra), .nf_short = "pra", .nf_desc = "Panic Reset Action", NVMEADM_F_BITS(ocp_vul_errrec_pra_bits) }, { OCP_F_ERRREC(dra), .nf_short = "dra", .nf_desc = "Device Recovery Action 1", NVMEADM_F_BITS(ocp_vul_errrec_dra_bits) }, { OCP_F_ERRREC(panic_id), .nf_short = "id", .nf_desc = "Panic ID", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(devcap), .nf_short = "devcap", .nf_desc = "Device Capabilities", NVMEADM_F_BITS(ocp_vul_errrec_devcap_bits) }, { OCP_F_ERRREC(vsr_opcode), .nf_short = "vsro", .nf_desc = "Vendor Specific Recovery Opcode", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(vsr_cdw12), .nf_short = "vcdw12", .nf_desc = "Vendor Specific Command CDW12", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(vsr_cdw13), .nf_short = "vcdw13", .nf_desc = "Vendor Specific Command CDW13", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(vsr_to), .nf_short = "vsct", .nf_desc = "Vendor Specific Command Timeout", .nf_rev = 2, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(dra2), .nf_short = "dra2", .nf_desc = "Device Recovery Action 2", .nf_rev = 3, NVMEADM_F_BITS(ocp_vul_errrec_dra_bits) }, { OCP_F_ERRREC(dra2_to), .nf_short = "dra2to", .nf_desc = "Device Recovery Action 2 Timeout", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(npanic), .nf_short = "npanic", .nf_desc = "Panic Count", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(old_panics[0]), .nf_short = "ppanic1", .nf_desc = "Previous Panic N-1", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(old_panics[1]), .nf_short = "ppanic2", .nf_desc = "Previous Panic N-2", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(old_panics[2]), .nf_short = "ppanic3", .nf_desc = "Previous Panic N-3", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(old_panics[3]), .nf_short = "ppanic4", .nf_desc = "Previous Panic N-4", .nf_rev = 3, .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(vers), .nf_short = "lpv", .nf_desc = "Log Page Version", .nf_type = NVMEADM_FT_HEX }, { OCP_F_ERRREC(guid), .nf_short = "lpg", .nf_desc = "Log Page GUID", .nf_type = NVMEADM_FT_GUID } }; static uint32_t ocp_vul_errrec_getvers(const void *data, size_t len) { if (len < sizeof (ocp_vul_errrec_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (ocp_vul_errrec_t)); } const ocp_vul_errrec_t *log = data; return (log->oer_vers); } const nvmeadm_log_field_info_t ocp_vul_errrec_field_info = { .nlfi_log = "ocp/errrec", .nlfi_fields = ocp_vul_errrec_fields, .nlfi_nfields = ARRAY_SIZE(ocp_vul_errrec_fields), .nlfi_min = sizeof (ocp_vul_errrec_t), .nlfi_getrev = ocp_vul_errrec_getvers }; #define OCP_F_DEVCAP(f) .nf_off = offsetof(ocp_vul_devcap_t, odc_##f), \ .nf_len = sizeof (((ocp_vul_devcap_t *)NULL)->odc_##f) #define OCP_F_DEVCAP_PSD(f) { .nf_off = offsetof(ocp_vul_devcap_t, \ odc_dssd[f]), \ .nf_len = sizeof (((ocp_vul_devcap_t *)NULL)->odc_dssd[f]), \ .nf_short = "psd" #f, .nf_desc = "DSSD Power State Descriptor " #f, \ NVMEADM_F_BITS(ocp_vul_devcap_psd_bits) } static const nvmeadm_field_bit_t ocp_vul_devcap_oob_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "smbus", .nfb_desc = "MCTP over SMBus", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "vdm", .nfb_desc = "MCTP over PCIe VDM", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "bmc", .nfb_desc = "NVMe Basic Management Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets OOB Management Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_wz_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "wz", .nfb_desc = "Write Zeros Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "deac", .nfb_desc = "Setting DEAC Bit", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "fua", .nfb_desc = "Setting FUA Bit", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "io5", .nfb_desc = "NVMe-IO-5 Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "io6", .nfb_desc = "NVMe-IO-6 Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets Write Zeros Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_san_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "san", .nfb_desc = "Sanitize Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "crypto", .nfb_desc = "Crypto-Erase", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "block", .nfb_desc = "Block Erase", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "ovr", .nfb_desc = "Overwrite", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 4, .nfb_hibit = 4, .nfb_short = "dea", .nfb_desc = "Deallocate LBAs", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets Sanitize Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_ds_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "dsmgmt", .nfb_desc = "Dataset Management Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "ad", .nfb_desc = "Attribute Deallocate", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets Dataset Management Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_wu_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "wu", .nfb_desc = "Write Uncorrectable Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "slba", .nfb_desc = "Single LBA", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 2, .nfb_hibit = 2, .nfb_short = "maxlba", .nfb_desc = "Maximum Number of LBAs", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 3, .nfb_hibit = 3, .nfb_short = "io14", .nfb_desc = "NVMe-IO-14", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets Write Uncorrectable Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_fuse_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "cmpwr", .nfb_desc = "Compare and Write Fused Command", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "unsupported", "supported" } }, { .nfb_lowbit = 15, .nfb_hibit = 15, .nfb_short = "pass", .nfb_desc = "Meets Fused Command Requirements", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_bit_t ocp_vul_devcap_psd_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 4, .nfb_short = "ps", .nfb_desc = "NVMe Power State", .nfb_type = NVMEADM_FT_HEX }, { .nfb_lowbit = 7, .nfb_hibit = 7, .nfb_short = "valid", .nfb_desc = "Valid DSSD Power State", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; static const nvmeadm_field_t ocp_vul_devcap_fields[] = { { OCP_F_DEVCAP(nports), .nf_short = "nports", .nf_desc = "PCI Express Ports", .nf_type = NVMEADM_FT_HEX }, { OCP_F_DEVCAP(oob_sup), .nf_short = "oob", .nf_desc = "OOB Management Support", NVMEADM_F_BITS(ocp_vul_devcap_oob_bits) }, { OCP_F_DEVCAP(wz_sup), .nf_short = "wz", .nf_desc = "Write Zeroes Command Support", NVMEADM_F_BITS(ocp_vul_devcap_wz_bits) }, { OCP_F_DEVCAP(san_sup), .nf_short = "san", .nf_desc = "Sanitize Command Support", NVMEADM_F_BITS(ocp_vul_devcap_san_bits) }, { OCP_F_DEVCAP(dsmgmt_sup), .nf_short = "ds", .nf_desc = "Dataset Management Support", NVMEADM_F_BITS(ocp_vul_devcap_ds_bits) }, { OCP_F_DEVCAP(wunc_sup), .nf_short = "wu", .nf_desc = "Write Uncorrectable Command Support", NVMEADM_F_BITS(ocp_vul_devcap_wu_bits) }, { OCP_F_DEVCAP(fuse_sup), .nf_short = "fuse", .nf_desc = "Fused Operations Support", NVMEADM_F_BITS(ocp_vul_devcap_fuse_bits) }, { OCP_F_DEVCAP(dssd_min_valid), .nf_short = "minps", .nf_desc = "Minimum Valid DSSD Power State", .nf_type = NVMEADM_FT_HEX }, OCP_F_DEVCAP_PSD(1), OCP_F_DEVCAP_PSD(2), OCP_F_DEVCAP_PSD(3), OCP_F_DEVCAP_PSD(4), OCP_F_DEVCAP_PSD(5), OCP_F_DEVCAP_PSD(6), OCP_F_DEVCAP_PSD(7), OCP_F_DEVCAP_PSD(8), OCP_F_DEVCAP_PSD(9), OCP_F_DEVCAP_PSD(10), OCP_F_DEVCAP_PSD(11), OCP_F_DEVCAP_PSD(12), OCP_F_DEVCAP_PSD(13), OCP_F_DEVCAP_PSD(14), OCP_F_DEVCAP_PSD(15), OCP_F_DEVCAP_PSD(16), OCP_F_DEVCAP_PSD(17), OCP_F_DEVCAP_PSD(18), OCP_F_DEVCAP_PSD(19), OCP_F_DEVCAP_PSD(20), OCP_F_DEVCAP_PSD(21), OCP_F_DEVCAP_PSD(22), OCP_F_DEVCAP_PSD(23), OCP_F_DEVCAP_PSD(24), OCP_F_DEVCAP_PSD(25), OCP_F_DEVCAP_PSD(26), OCP_F_DEVCAP_PSD(27), OCP_F_DEVCAP_PSD(28), OCP_F_DEVCAP_PSD(29), OCP_F_DEVCAP_PSD(30), OCP_F_DEVCAP_PSD(31), OCP_F_DEVCAP_PSD(32), OCP_F_DEVCAP_PSD(33), OCP_F_DEVCAP_PSD(34), OCP_F_DEVCAP_PSD(35), OCP_F_DEVCAP_PSD(36), OCP_F_DEVCAP_PSD(37), OCP_F_DEVCAP_PSD(38), OCP_F_DEVCAP_PSD(39), OCP_F_DEVCAP_PSD(40), OCP_F_DEVCAP_PSD(41), OCP_F_DEVCAP_PSD(42), OCP_F_DEVCAP_PSD(43), OCP_F_DEVCAP_PSD(44), OCP_F_DEVCAP_PSD(45), OCP_F_DEVCAP_PSD(46), OCP_F_DEVCAP_PSD(47), OCP_F_DEVCAP_PSD(48), OCP_F_DEVCAP_PSD(49), OCP_F_DEVCAP_PSD(50), OCP_F_DEVCAP_PSD(51), OCP_F_DEVCAP_PSD(52), OCP_F_DEVCAP_PSD(53), OCP_F_DEVCAP_PSD(54), OCP_F_DEVCAP_PSD(55), OCP_F_DEVCAP_PSD(56), OCP_F_DEVCAP_PSD(57), OCP_F_DEVCAP_PSD(58), OCP_F_DEVCAP_PSD(59), OCP_F_DEVCAP_PSD(60), OCP_F_DEVCAP_PSD(61), OCP_F_DEVCAP_PSD(62), OCP_F_DEVCAP_PSD(63), OCP_F_DEVCAP_PSD(64), OCP_F_DEVCAP_PSD(65), OCP_F_DEVCAP_PSD(66), OCP_F_DEVCAP_PSD(67), OCP_F_DEVCAP_PSD(68), OCP_F_DEVCAP_PSD(69), OCP_F_DEVCAP_PSD(70), OCP_F_DEVCAP_PSD(71), OCP_F_DEVCAP_PSD(72), OCP_F_DEVCAP_PSD(73), OCP_F_DEVCAP_PSD(74), OCP_F_DEVCAP_PSD(75), OCP_F_DEVCAP_PSD(76), OCP_F_DEVCAP_PSD(77), OCP_F_DEVCAP_PSD(78), OCP_F_DEVCAP_PSD(79), OCP_F_DEVCAP_PSD(80), OCP_F_DEVCAP_PSD(81), OCP_F_DEVCAP_PSD(82), OCP_F_DEVCAP_PSD(83), OCP_F_DEVCAP_PSD(84), OCP_F_DEVCAP_PSD(85), OCP_F_DEVCAP_PSD(86), OCP_F_DEVCAP_PSD(87), OCP_F_DEVCAP_PSD(88), OCP_F_DEVCAP_PSD(89), OCP_F_DEVCAP_PSD(90), OCP_F_DEVCAP_PSD(91), OCP_F_DEVCAP_PSD(92), OCP_F_DEVCAP_PSD(93), OCP_F_DEVCAP_PSD(94), OCP_F_DEVCAP_PSD(95), OCP_F_DEVCAP_PSD(96), OCP_F_DEVCAP_PSD(97), OCP_F_DEVCAP_PSD(98), OCP_F_DEVCAP_PSD(99), OCP_F_DEVCAP_PSD(100), OCP_F_DEVCAP_PSD(101), OCP_F_DEVCAP_PSD(102), OCP_F_DEVCAP_PSD(103), OCP_F_DEVCAP_PSD(104), OCP_F_DEVCAP_PSD(105), OCP_F_DEVCAP_PSD(106), OCP_F_DEVCAP_PSD(107), OCP_F_DEVCAP_PSD(108), OCP_F_DEVCAP_PSD(109), OCP_F_DEVCAP_PSD(110), OCP_F_DEVCAP_PSD(111), OCP_F_DEVCAP_PSD(112), OCP_F_DEVCAP_PSD(113), OCP_F_DEVCAP_PSD(114), OCP_F_DEVCAP_PSD(115), OCP_F_DEVCAP_PSD(116), OCP_F_DEVCAP_PSD(117), OCP_F_DEVCAP_PSD(118), OCP_F_DEVCAP_PSD(119), OCP_F_DEVCAP_PSD(120), OCP_F_DEVCAP_PSD(121), OCP_F_DEVCAP_PSD(122), OCP_F_DEVCAP_PSD(123), OCP_F_DEVCAP_PSD(124), OCP_F_DEVCAP_PSD(125), OCP_F_DEVCAP_PSD(126), OCP_F_DEVCAP_PSD(127), { OCP_F_DEVCAP(vers), .nf_short = "lpv", .nf_desc = "Log Page Version", .nf_type = NVMEADM_FT_HEX }, { OCP_F_DEVCAP(guid), .nf_short = "lpg", .nf_desc = "Log Page GUID", .nf_type = NVMEADM_FT_GUID } }; static uint32_t ocp_vul_devcap_getvers(const void *data, size_t len) { if (len < sizeof (ocp_vul_devcap_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (ocp_vul_devcap_t)); } const ocp_vul_devcap_t *log = data; return (log->odc_vers); } const nvmeadm_log_field_info_t ocp_vul_devcap_field_info = { .nlfi_log = "ocp/devcap", .nlfi_fields = ocp_vul_devcap_fields, .nlfi_nfields = ARRAY_SIZE(ocp_vul_devcap_fields), .nlfi_min = sizeof (ocp_vul_devcap_t), .nlfi_getrev = ocp_vul_devcap_getvers }; #define OCP_F_UNSUP(f) .nf_off = offsetof(ocp_vul_unsup_req_t, our_##f), \ .nf_len = sizeof (((ocp_vul_unsup_req_t *)NULL)->our_##f) static const nvmeadm_field_t ocp_vul_unsup_fields_head[] = { { OCP_F_UNSUP(nunsup), .nf_short = "count", .nf_desc = "Unsupported Count", .nf_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_t ocp_vul_unsup_fields_tail[] = { { OCP_F_UNSUP(vers), .nf_short = "lpv", .nf_desc = "Log Page Version", .nf_type = NVMEADM_FT_HEX }, { OCP_F_UNSUP(guid), .nf_short = "lpg", .nf_desc = "Log Page GUID", .nf_type = NVMEADM_FT_GUID } }; static uint32_t ocp_vul_unsup_getvers(const void *data, size_t len) { if (len < sizeof (ocp_vul_unsup_req_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (ocp_vul_unsup_req_t)); } const ocp_vul_unsup_req_t *log = data; return (log->our_vers); } /* * We manually drive this so we can create the appropriate number of entries for * the string table as there are a variable number of these. */ static bool ocp_vul_unsup_drive(nvmeadm_field_print_t *print, const void *data, size_t len) { print->fp_header = NULL; print->fp_fields = ocp_vul_unsup_fields_head; print->fp_nfields = ARRAY_SIZE(ocp_vul_unsup_fields_head); print->fp_base = NULL; print->fp_data = data; print->fp_dlen = len; print->fp_off = 0; nvmeadm_field_print(print); /* * Look at the data and make sure we have an appropriate number of * entries specified. While there is a uint16_t worth of entries the * specification indicates there can be a maximum of 253. */ const ocp_vul_unsup_req_t *log = data; if (log->our_nunsup > 253) { warnx("log page has questionable data: log page count of " "unsupported requirements %u exceeds spec max of 253", log->our_nunsup); } size_t nlogs = MIN(log->our_nunsup, 253); for (size_t i = 0; i < nlogs; i++) { nvmeadm_field_t field; char shrt[32]; char desc[128]; (void) snprintf(shrt, sizeof (shrt), "ureq%zu", i); (void) snprintf(desc, sizeof (desc), "Unsupported Requirement " "%zu", i); (void) memset(&field, 0, sizeof (nvmeadm_field_t)); field.nf_off = offsetof(ocp_vul_unsup_req_t, our_reqs[i]); field.nf_len = sizeof (ocp_req_str_t); field.nf_short = shrt; field.nf_desc = desc; field.nf_type = NVMEADM_FT_ASCIIZ; print->fp_fields = &field; print->fp_nfields = 1; nvmeadm_field_print(print); } print->fp_fields = ocp_vul_unsup_fields_tail; print->fp_nfields = ARRAY_SIZE(ocp_vul_unsup_fields_tail); nvmeadm_field_print(print); return (true); } const nvmeadm_log_field_info_t ocp_vul_unsup_field_info = { .nlfi_log = "ocp/unsup", .nlfi_min = sizeof (ocp_vul_unsup_req_t), .nlfi_getrev = ocp_vul_unsup_getvers, .nlfi_drive = ocp_vul_unsup_drive }; #define OCP_F_TELSTR(f) .nf_off = offsetof(ocp_vul_telstr_t, ots_##f), \ .nf_len = sizeof (((ocp_vul_telstr_t *)NULL)->ots_##f) static const nvmeadm_field_t ocp_vul_telstr_fields[] = { { OCP_F_TELSTR(vers), .nf_short = "lpv", .nf_desc = "Log Page Version", .nf_type = NVMEADM_FT_HEX }, { OCP_F_TELSTR(guid), .nf_short = "lpg", .nf_desc = "Log Page GUID", .nf_type = NVMEADM_FT_GUID }, { OCP_F_TELSTR(sls), .nf_short = "sls", .nf_desc = "Telemetry String Log Size", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(sits), .nf_short = "sits", .nf_desc = "Statistics Identifier String Table Start", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(sitz), .nf_short = "sitz", .nf_desc = "Statistics Identifier String Table Size", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(ests), .nf_short = "ests", .nf_desc = "Event String Table Start", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(estz), .nf_short = "estz", .nf_desc = "Event String Table Size", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(vuests), .nf_short = "vuests", .nf_desc = "VU Event String Table Start", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(vuestz), .nf_short = "vuestz", .nf_desc = "VU Event String Table Size", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(ascts), .nf_short = "asctss", .nf_desc = "ASCII Table Start", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } }, { OCP_F_TELSTR(asctz), .nf_short = "asctsz", .nf_desc = "ASCII Table Size", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } } }; #define OCP_F_TELSTR_SIT(f) .nf_off = offsetof(ocp_vul_telstr_sit_t, \ ocp_sit_##f), \ .nf_len = sizeof (((ocp_vul_telstr_sit_t *)NULL)->ocp_sit_##f) static const nvmeadm_field_t ocp_vul_telstr_sit_fields[] = { { OCP_F_TELSTR_SIT(id), .nf_short = "id", .nf_desc = "Vendor Unique Statistic Identifier", .nf_type = NVMEADM_FT_HEX, }, { OCP_F_TELSTR_SIT(len), .nf_short = "len", .nf_desc = "ASCII ID Length", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_addend = 1 } }, { OCP_F_TELSTR_SIT(off), .nf_short = "off", .nf_desc = "ASCII ID Offset", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } } }; #define OCP_F_TELSTR_EST(f) .nf_off = offsetof(ocp_vul_telstr_est_t, \ ocp_est_##f), \ .nf_len = sizeof (((ocp_vul_telstr_est_t *)NULL)->ocp_est_##f) /* * This is the same currently for both the vendor unique and regular events so * we use the same structure for the time being. */ static const nvmeadm_field_t ocp_vul_telstr_est_fields[] = { { OCP_F_TELSTR_EST(class), .nf_short = "class", .nf_desc = "Debug Event Class", .nf_type = NVMEADM_FT_HEX, }, { OCP_F_TELSTR_EST(eid), .nf_short = "id", .nf_desc = "Event Identifier", .nf_type = NVMEADM_FT_HEX, }, { OCP_F_TELSTR_EST(len), .nf_short = "len", .nf_desc = "ASCII ID Length", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_addend = 1 } }, { OCP_F_TELSTR_EST(off), .nf_short = "off", .nf_desc = "ASCII ID Offset", .nf_type = NVMEADM_FT_HEX, .nf_addend = { .nfa_shift = 2 } } }; static uint32_t ocp_vul_telstr_getvers(const void *data, size_t len) { if (len < sizeof (ocp_vul_telstr_t)) { errx(-1, "cannot parse revision information, found 0x%zx " "bytes, need at least 0x%zx", len, sizeof (ocp_vul_telstr_t)); } const ocp_vul_telstr_t *log = data; return (log->ots_vers); } static bool ocp_vul_telstr_sanity(const char *name, uint64_t off_dw, uint64_t len_dw, size_t flen) { const uint64_t max_dw = UINT64_MAX / sizeof (uint32_t); /* * These values are in units of uint32_t's. Make sure we can represent * them. */ if (off_dw > max_dw) { warnx("telemetry log %s offset does not fit in a 64-bit " "quantity", name); return (false); } if (len_dw > max_dw) { warnx("telemetry log %s length does not fit in a 64-bit " "quantity", name); return (false); } const uint64_t off_bytes = off_dw << 2; const uint64_t len_bytes = len_dw << 2; if (len_bytes > UINT64_MAX - off_bytes) { warnx("telemetry log %s final offset would overflow a 64-bit " "quantity", name); return (false); } const uint64_t end = off_bytes + len_bytes; if (end > flen) { warnx("telemetry log %s exceeds beyond the end of the file", name); return (false); } return (true); } /* * Set up a field to print an ASCII string and error if the embedded information * is not useful. */ static bool ocp_vul_telstr_field_str(nvmeadm_field_t *field, uint16_t len0, uint64_t off_dw, uint64_t ascii_start, uint64_t ascii_len) { const uint64_t max_dw = UINT64_MAX / sizeof (uint32_t); (void) memset(field, 0, sizeof (nvmeadm_field_t)); if (off_dw > max_dw) { warnx("telemetry log ASCII string offset 0x%" PRIx64 " is " "not representable in a 64-bit quantity", off_dw); return (false); } const uint64_t off = off_dw << 2; const uint64_t len = len0 + 1; if (len > UINT64_MAX - off) { warnx("telemetry log ASCII string would overflow a 64-bit " "quantity: offset 0x%" PRIx64 ", length: %" PRIu64, off, len); return (false); } if (off + len > ascii_start + ascii_len) { warnx("telemetry log ASCII string exceeds ASCII table"); return (false); } field->nf_len = len; field->nf_off = off + ascii_start; field->nf_short = "str"; field->nf_desc = "String"; /* * Vendors are inconsistent as to whether the string table is padded * with zeros or spaces. Use ASCIIZ here to account for both. */ field->nf_type = NVMEADM_FT_ASCIIZ; return (true); } /* * The telemetry string table is comprised of a fixed section and then a number * of variable sections that point into the ASCII table, somewhat analogous to * an ELF string table. There is no good way to see where the various strings * begin and end in the ASCII table. There is no strict separator between * entries. Entries are space padded to the next u32 aligned point generally; * however, the presence or lack of spaces doesn't tell us where something * begins or ends. * * As such, we manually drive this and relate the ASCII strings to the * corresponding other tables that we encounter. This isn't the most eloquent; * however, there's no other good way to do display this programmatically. */ static bool ocp_vul_telstr_drive(nvmeadm_field_print_t *print, const void *data, size_t len) { const ocp_vul_telstr_t *telstr = data; bool ret = true; print->fp_header = "Telemetry String Header"; print->fp_fields = ocp_vul_telstr_fields; print->fp_nfields = ARRAY_SIZE(ocp_vul_telstr_fields); print->fp_base = "tsh"; print->fp_data = data; print->fp_dlen = len; print->fp_off = 0; nvmeadm_field_print(print); /* * First take care of the 16 FIFOs. If a FIFO has a totally zero string, * then we should ignore it. This is the last data entry that we're * guaranteed we have space for. Everything else after this needs to be * checked for paranoia and consistency. */ for (size_t i = 0; i < 16; i++) { char shrt[32], desc[128]; nvmeadm_field_t field; const uint8_t empty[16] = { 0 }; (void) snprintf(shrt, sizeof (shrt), "fifo%zu", i); (void) snprintf(desc, sizeof (desc), "FIFO %zu", i); (void) memset(&field, 0, sizeof (nvmeadm_field_t)); field.nf_len = sizeof (((ocp_vul_telstr_t *)NULL)->ots_fifo0); field.nf_off = offsetof(ocp_vul_telstr_t, ots_fifo0) + i * field.nf_len; field.nf_short = shrt; field.nf_desc = desc; field.nf_type = NVMEADM_FT_ASCIIZ; if (memcmp(data + field.nf_off, empty, sizeof (empty)) == 0) { continue; } print->fp_header = NULL; print->fp_fields = &field; print->fp_nfields = 1; nvmeadm_field_print(print); } /* * Sanity check that the rest of this makes sense. In particular, this * is supposed to be ordered SITS, ESTS, VUETS, ASCTS. Make sure these * don't overlap, that the offsets don't cause an overflow when we * expand them, etc. */ if (!ocp_vul_telstr_sanity("sit", telstr->ots_sits, telstr->ots_sitz, len) || !ocp_vul_telstr_sanity("est", telstr->ots_ests, telstr->ots_estz, len) || !ocp_vul_telstr_sanity("vuest", telstr->ots_vuests, telstr->ots_vuestz, len) || !ocp_vul_telstr_sanity("asct", telstr->ots_ascts, telstr->ots_asctz, len)) { return (false); } const uint64_t sit_start = telstr->ots_sits << 2; const uint64_t sit_len = telstr->ots_sitz << 2; const uint64_t est_start = telstr->ots_ests << 2; const uint64_t est_len = telstr->ots_estz << 2; const uint64_t vu_start = telstr->ots_vuests << 2; const uint64_t vu_len = telstr->ots_vuestz << 2; const uint64_t ascii_start = telstr->ots_ascts << 2; const uint64_t ascii_len = telstr->ots_asctz << 2; if (sit_start != offsetof(ocp_vul_telstr_t, ots_data)) { warnx("invalid telemetry string table: SIT table starts at " "unexpected offset 0x%" PRIx64, sit_start); return (false); } if (est_start < sit_start + sit_len) { warnx("invalid telemetry string table: EST table starts before " "SIT table ends"); return (false); } if (vu_start < est_start + est_len) { warnx("invalid telemetry string table: VUEST table starts " "before EST table ends"); return (false); } if (ascii_start < vu_start + vu_len) { warnx("invalid telemetry string table: ASCT table starts " "before VUEST table ends"); return (false); } print->fp_header = "Statistic Identifier Table"; print->fp_base = "sit"; const uint64_t sit_nents = sit_len / sizeof (ocp_vul_telstr_sit_t); for (uint64_t i = 0; i < sit_nents; i++) { char shrt[32], desc[128]; const size_t off = sit_start + i * sizeof (ocp_vul_telstr_sit_t); const ocp_vul_telstr_sit_t *sit = data + off; nvmeadm_field_t cont; nvmeadm_field_t fields[ARRAY_SIZE(ocp_vul_telstr_sit_fields) + 1]; (void) memcpy(fields, ocp_vul_telstr_sit_fields, sizeof (ocp_vul_telstr_sit_fields)); if (!ocp_vul_telstr_field_str(&fields[ARRAY_SIZE(fields) - 1], sit->ocp_sit_len, sit->ocp_sit_off, ascii_start, ascii_len)) { ret = false; continue; } for (size_t f = 0; f < ARRAY_SIZE(fields) - 1; f++) { fields[f].nf_off += off; } (void) snprintf(shrt, sizeof (shrt), "%" PRIu64, i); (void) snprintf(desc, sizeof (desc), "SIT Entry %" PRIu64, i); (void) memset(&cont, 0, sizeof (nvmeadm_field_t)); cont.nf_off = 0; cont.nf_len = sizeof (ocp_vul_telstr_sit_t); cont.nf_short = shrt; cont.nf_desc = desc; cont.nf_type = NVMEADM_FT_CONTAINER; cont.nf_fields = fields; cont.nf_nfields = ARRAY_SIZE(fields); if (i > 0) { print->fp_header = NULL; } print->fp_fields = &cont; print->fp_nfields = 1; nvmeadm_field_print(print); } print->fp_header = "Event Identifier Table"; print->fp_base = "est"; const uint64_t est_nents = est_len / sizeof (ocp_vul_telstr_est_t); for (uint64_t i = 0; i < est_nents; i++) { char shrt[32], desc[128]; const size_t off = est_start + i * sizeof (ocp_vul_telstr_est_t); const ocp_vul_telstr_est_t *est = data + off; nvmeadm_field_t cont; nvmeadm_field_t fields[ARRAY_SIZE(ocp_vul_telstr_est_fields) + 1]; (void) memcpy(fields, ocp_vul_telstr_est_fields, sizeof (ocp_vul_telstr_est_fields)); if (!ocp_vul_telstr_field_str(&fields[ARRAY_SIZE(fields) - 1], est->ocp_est_len, est->ocp_est_off, ascii_start, ascii_len)) { ret = false; continue; } for (size_t f = 0; f < ARRAY_SIZE(fields) - 1; f++) { fields[f].nf_off += off; } (void) snprintf(shrt, sizeof (shrt), "%" PRIu64, i); (void) snprintf(desc, sizeof (desc), "EST Entry %" PRIu64, i); (void) memset(&cont, 0, sizeof (nvmeadm_field_t)); cont.nf_off = 0; cont.nf_len = sizeof (ocp_vul_telstr_est_t); cont.nf_short = shrt; cont.nf_desc = desc; cont.nf_type = NVMEADM_FT_CONTAINER; cont.nf_fields = fields; cont.nf_nfields = ARRAY_SIZE(fields); if (i > 0) { print->fp_header = NULL; } print->fp_fields = &cont; print->fp_nfields = 1; nvmeadm_field_print(print); } print->fp_header = "Vendor Unique Event Identifier Table"; print->fp_base = "vuest"; const uint64_t vuest_nents = vu_len / sizeof (ocp_vul_telstr_vuest_t); for (uint64_t i = 0; i < vuest_nents; i++) { char shrt[32], desc[128]; const size_t off = vu_start + i * sizeof (ocp_vul_telstr_vuest_t); const ocp_vul_telstr_vuest_t *vuest = data + off; nvmeadm_field_t cont; nvmeadm_field_t fields[ARRAY_SIZE(ocp_vul_telstr_est_fields) + 1]; (void) memcpy(fields, ocp_vul_telstr_est_fields, sizeof (ocp_vul_telstr_est_fields)); if (!ocp_vul_telstr_field_str(&fields[ARRAY_SIZE(fields) - 1], vuest->ocp_vuest_len, vuest->ocp_vuest_off, ascii_start, ascii_len)) { ret = false; continue; } for (size_t f = 0; f < ARRAY_SIZE(fields) - 1; f++) { fields[f].nf_off += off; } (void) snprintf(shrt, sizeof (shrt), "%" PRIu64, i); (void) snprintf(desc, sizeof (desc), "VUEST Entry %" PRIu64, i); (void) memset(&cont, 0, sizeof (nvmeadm_field_t)); cont.nf_off = 0; cont.nf_len = sizeof (ocp_vul_telstr_vuest_t); cont.nf_short = shrt; cont.nf_desc = desc; cont.nf_type = NVMEADM_FT_CONTAINER; cont.nf_fields = fields; cont.nf_nfields = ARRAY_SIZE(fields); if (i > 0) { print->fp_header = NULL; } print->fp_fields = &cont; print->fp_nfields = 1; nvmeadm_field_print(print); } return (ret); } const nvmeadm_log_field_info_t ocp_vul_telstr_field_info = { .nlfi_log = "ocp/telstr", .nlfi_min = sizeof (ocp_vul_telstr_t), .nlfi_getrev = ocp_vul_telstr_getvers, .nlfi_drive = ocp_vul_telstr_drive }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company * Copyright 2022 Tintri by DDN, Inc. All rights reserved. */ /* * nvmeadm output formatting for ofmt based rendering */ #include #include #include #include "nvmeadm.h" typedef struct { uint32_t nb_flag; const char *nb_str; } nvmeadm_bitstr_t; static boolean_t nvmeadm_bits_to_str(uint32_t val, const nvmeadm_bitstr_t *strs, size_t nstrs, char *buf, size_t buflen) { boolean_t comma = B_FALSE; buf[0] = '\0'; for (size_t i = 0; i < nstrs; i++) { if ((val & strs[i].nb_flag) != strs[i].nb_flag) continue; if (comma && strlcat(buf, ",", buflen) >= buflen) return (B_FALSE); if (strlcat(buf, strs[i].nb_str, buflen) >= buflen) return (B_FALSE); comma = true; } if (buf[0] == '\0') { if (strlcat(buf, "--", buflen) >= buflen) return (B_FALSE); } return (B_TRUE); } typedef enum nvme_list_ofmt_field { NVME_LIST_MODEL, NVME_LIST_SERIAL, NVME_LIST_FWREV, NVME_LIST_VERSION, NVME_LIST_SIZE, NVME_LIST_CAPACITY, NVME_LIST_USED, NVME_LIST_INSTANCE, NVME_LIST_NAMESPACE, NVME_LIST_DISK, NVME_LIST_UNALLOC, NVME_LIST_NS_STATE, NVME_LIST_CTRLPATH, NVME_LIST_NS_FORMAT, NVME_LIST_NS_FMTID, NVME_LIST_NS_FMTDS, NVME_LIST_NS_FMTMS, NVME_LIST_LOC_LOC, NVME_LIST_LOC_CTLAP } nvme_list_ofmt_field_t; static boolean_t nvmeadm_list_common_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen) { nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg; nvme_ctrl_info_t *ctrl = list->nloa_ctrl; const nvme_version_t *vers; char *path; size_t ret; switch (ofmt_arg->ofmt_id) { case NVME_LIST_MODEL: ret = strlcpy(buf, nvme_ctrl_info_model(ctrl), buflen); break; case NVME_LIST_SERIAL: ret = strlcpy(buf, nvme_ctrl_info_serial(ctrl), buflen); break; case NVME_LIST_FWREV: ret = strlcpy(buf, nvme_ctrl_info_fwrev(ctrl), buflen); break; case NVME_LIST_VERSION: vers = nvme_ctrl_info_version(ctrl); ret = snprintf(buf, buflen, "%u.%u", vers->v_major, vers->v_minor); break; case NVME_LIST_INSTANCE: ret = strlcpy(buf, list->nloa_name, buflen); break; case NVME_LIST_CTRLPATH: if (list->nloa_dip == DI_NODE_NIL) { return (B_FALSE); } path = di_devfs_path(list->nloa_dip); if (path == NULL) { return (B_FALSE); } ret = strlcat(buf, path, buflen); di_devfs_path_free(path); break; default: warnx("internal programmer error: encountered unknown ofmt " "argument id 0x%x", ofmt_arg->ofmt_id); abort(); } if (ret >= buflen) { return (B_FALSE); } return (B_TRUE); } static boolean_t nvmeadm_list_ctrl_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen) { nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg; nvme_ctrl_info_t *ctrl = list->nloa_ctrl; nvme_uint128_t u128; size_t ret; switch (ofmt_arg->ofmt_id) { case NVME_LIST_CAPACITY: if (nvme_ctrl_info_cap(ctrl, &u128)) { ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0); } else { return (B_FALSE); } break; case NVME_LIST_UNALLOC: if (nvme_ctrl_info_unalloc_cap(ctrl, &u128)) { ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0); } else { return (B_FALSE); } break; case NVME_LIST_LOC_LOC: if (list->nloa_loc != NULL) { ret = strlcpy(buf, list->nloa_loc, buflen); } else { ret = strlcpy(buf, "-", buflen); } break; case NVME_LIST_LOC_CTLAP: if (list->nloa_ap != NULL) { ret = strlcpy(buf, list->nloa_ap, buflen); } else { ret = strlcpy(buf, "-", buflen); } break; default: warnx("internal programmer error: encountered unknown ofmt " "argument id 0x%x", ofmt_arg->ofmt_id); abort(); } if (ret >= buflen) { return (B_FALSE); } return (B_TRUE); } static boolean_t nvmeadm_list_nsid_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen) { nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg; nvme_ns_info_t *ns = list->nloa_ns; const nvme_nvm_lba_fmt_t *fmt = NULL; const nvme_ns_disc_level_t level = nvme_ns_info_level(ns); uint64_t val; size_t ret; (void) nvme_ns_info_curformat(ns, &fmt); switch (ofmt_arg->ofmt_id) { case NVME_LIST_NAMESPACE: ret = snprintf(buf, buflen, "%u", nvme_ns_info_nsid(ns)); break; case NVME_LIST_DISK: if (list->nloa_disk != NULL) { ret = strlcpy(buf, list->nloa_disk, buflen); } else { return (B_FALSE); } break; case NVME_LIST_SIZE: if (nvme_ns_info_size(ns, &val) && fmt != NULL) { val *= nvme_nvm_lba_fmt_data_size(fmt); ret = snprintf(buf, buflen, "%" PRIu64, val); } else { return (B_FALSE); } break; case NVME_LIST_CAPACITY: if (nvme_ns_info_size(ns, &val) && fmt != NULL) { val *= nvme_nvm_lba_fmt_data_size(fmt); ret = snprintf(buf, buflen, "%" PRIu64, val); } else { return (B_FALSE); } break; case NVME_LIST_USED: if (nvme_ns_info_size(ns, &val) && fmt != NULL) { val *= nvme_nvm_lba_fmt_data_size(fmt); ret = snprintf(buf, buflen, "%" PRIu64, val); } else { return (B_FALSE); } break; case NVME_LIST_NS_STATE: ret = strlcpy(buf, list->nloa_state, buflen); break; case NVME_LIST_NS_FORMAT: if (fmt != NULL) { ret = snprintf(buf, buflen, "%u+%u", nvme_nvm_lba_fmt_data_size(fmt), nvme_nvm_lba_fmt_meta_size(fmt)); } else if (level < NVME_NS_DISC_F_ACTIVE) { ret = strlcpy(buf, "-", buflen); } else { return (B_FALSE); } break; case NVME_LIST_NS_FMTID: if (fmt != NULL) { ret = snprintf(buf, buflen, "%u", nvme_nvm_lba_fmt_id(fmt)); } else if (level < NVME_NS_DISC_F_ACTIVE) { ret = strlcpy(buf, "-", buflen); } else { return (B_FALSE); } break; case NVME_LIST_NS_FMTDS: if (fmt != NULL) { ret = snprintf(buf, buflen, "%u", nvme_nvm_lba_fmt_data_size(fmt)); } else if (level < NVME_NS_DISC_F_ACTIVE) { ret = strlcpy(buf, "-", buflen); } else { return (B_FALSE); } break; case NVME_LIST_NS_FMTMS: if (fmt != NULL) { ret = snprintf(buf, buflen, "%u", nvme_nvm_lba_fmt_meta_size(fmt)); } else if (level < NVME_NS_DISC_F_ACTIVE) { ret = strlcpy(buf, "-", buflen); } else { return (B_FALSE); } break; default: warnx("internal programmer error: encountered unknown ofmt " "argument id 0x%x", ofmt_arg->ofmt_id); abort(); } if (ret >= buflen) { return (B_FALSE); } return (B_TRUE); } const ofmt_field_t nvmeadm_list_ctrl_ofmt[] = { { "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb }, { "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb }, { "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb }, { "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb }, { "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_ctrl_ofmt_cb }, { "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb }, { "UNALLOCATED", 15, NVME_LIST_UNALLOC, nvmeadm_list_ctrl_ofmt_cb }, { "CTRLPATH", 30, NVME_LIST_CTRLPATH, nvmeadm_list_common_ofmt_cb }, { NULL, 0, 0, NULL } }; const ofmt_field_t nvmeadm_list_loc_ofmt[] = { { "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb }, { "SERIAL", 20, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb }, { "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb }, { "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb }, { "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_ctrl_ofmt_cb }, { "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb }, { "UNALLOCATED", 15, NVME_LIST_UNALLOC, nvmeadm_list_ctrl_ofmt_cb }, { "CTRLPATH", 30, NVME_LIST_CTRLPATH, nvmeadm_list_common_ofmt_cb }, { "LOCATION", 15, NVME_LIST_LOC_LOC, nvmeadm_list_ctrl_ofmt_cb }, { "CTLAP", 10, NVME_LIST_LOC_CTLAP, nvmeadm_list_ctrl_ofmt_cb }, { NULL, 0, 0, NULL } }; const ofmt_field_t nvmeadm_list_nsid_ofmt[] = { { "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb }, { "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb }, { "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb }, { "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb }, { "SIZE", 15, NVME_LIST_SIZE, nvmeadm_list_nsid_ofmt_cb }, { "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_nsid_ofmt_cb }, { "USED", 15, NVME_LIST_USED, nvmeadm_list_nsid_ofmt_cb }, { "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb }, { "NAMESPACE", 10, NVME_LIST_NAMESPACE, nvmeadm_list_nsid_ofmt_cb }, { "DISK", 15, NVME_LIST_DISK, nvmeadm_list_nsid_ofmt_cb }, { "NS-STATE", 10, NVME_LIST_NS_STATE, nvmeadm_list_nsid_ofmt_cb }, { "CTRLPATH", 30, NVME_LIST_CTRLPATH, nvmeadm_list_common_ofmt_cb }, { "FORMAT", 12, NVME_LIST_NS_FORMAT, nvmeadm_list_nsid_ofmt_cb }, { "FMTID", 8, NVME_LIST_NS_FMTID, nvmeadm_list_nsid_ofmt_cb }, { "FMTDS", 8, NVME_LIST_NS_FMTDS, nvmeadm_list_nsid_ofmt_cb }, { "FMTMS", 8, NVME_LIST_NS_FMTMS, nvmeadm_list_nsid_ofmt_cb }, { NULL, 0, 0, NULL } }; typedef enum { NVME_LIST_LOGS_DEVICE, NVME_LIST_LOGS_NAME, NVME_LIST_LOGS_DESC, NVME_LIST_LOGS_SCOPE, NVME_LIST_LOGS_FIELDS, NVME_LIST_LOGS_CSI, NVME_LIST_LOGS_LID, NVME_LIST_LOGS_SIZE, NVME_LIST_LOGS_MINSIZE, NVME_LIST_LOGS_IMPL, NVME_LIST_LOGS_SOURCES, NVME_LIST_LOGS_KIND } nvme_list_logs_ofmt_field_t; static const nvmeadm_bitstr_t nvmeadm_log_scopes[] = { { NVME_LOG_SCOPE_CTRL, "controller" }, { NVME_LOG_SCOPE_NVM, "nvm" }, { NVME_LOG_SCOPE_NS, "namespace" } }; static const nvmeadm_bitstr_t nvmeadm_log_fields[] = { { NVME_LOG_DISC_F_NEED_LSP, "lsp" }, { NVME_LOG_DISC_F_NEED_LSI, "lsi" }, { NVME_LOG_DISC_F_NEED_RAE, "rae" } }; static const nvmeadm_bitstr_t nvmeadm_log_sources[] = { { NVME_LOG_DISC_S_SPEC, "spec" }, { NVME_LOG_DISC_S_ID_CTRL, "identify-controller" }, { NVME_LOG_DISC_S_DB, "internal-db" }, { NVME_LOG_DISC_S_CMD, "command" } }; static boolean_t nvmeadm_list_logs_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen) { const nvmeadm_list_logs_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg; const nvme_log_disc_t *disc = list->nlloa_disc; uint64_t alloc; size_t ret; nvme_log_size_kind_t kind; switch (ofmt_arg->ofmt_id) { case NVME_LIST_LOGS_DEVICE: ret = strlcpy(buf, list->nlloa_name, buflen); break; case NVME_LIST_LOGS_NAME: ret = strlcpy(buf, nvme_log_disc_name(disc), buflen); break; case NVME_LIST_LOGS_DESC: ret = strlcpy(buf, nvme_log_disc_desc(disc), buflen); break; case NVME_LIST_LOGS_SCOPE: return (nvmeadm_bits_to_str(nvme_log_disc_scopes(disc), nvmeadm_log_scopes, ARRAY_SIZE(nvmeadm_log_scopes), buf, buflen)); case NVME_LIST_LOGS_FIELDS: return (nvmeadm_bits_to_str(nvme_log_disc_fields(disc), nvmeadm_log_fields, ARRAY_SIZE(nvmeadm_log_fields), buf, buflen)); break; case NVME_LIST_LOGS_CSI: switch (nvme_log_disc_csi(disc)) { case NVME_CSI_NVM: ret = strlcpy(buf, "nvm", buflen); break; case NVME_CSI_KV: ret = strlcpy(buf, "kv", buflen); break; case NVME_CSI_ZNS: ret = strlcpy(buf, "zns", buflen); break; default: ret = snprintf(buf, buflen, "unknown (0x%x)", nvme_log_disc_csi(disc)); break; } break; case NVME_LIST_LOGS_LID: ret = snprintf(buf, buflen, "0x%x", nvme_log_disc_lid(disc)); break; case NVME_LIST_LOGS_SIZE: case NVME_LIST_LOGS_MINSIZE: kind = nvme_log_disc_size(disc, &alloc); if (kind == NVME_LOG_SIZE_K_UNKNOWN) { return (B_FALSE); } if (kind == NVME_LOG_SIZE_K_VAR && ofmt_arg->ofmt_id == NVME_LIST_LOGS_SIZE) { return (B_FALSE); } ret = snprintf(buf, buflen, "%" PRIu64, alloc); break; case NVME_LIST_LOGS_IMPL: ret = strlcpy(buf, nvme_log_disc_impl(disc) ? "yes" : "no", buflen); break; case NVME_LIST_LOGS_SOURCES: return (nvmeadm_bits_to_str(nvme_log_disc_sources(disc), nvmeadm_log_sources, ARRAY_SIZE(nvmeadm_log_sources), buf, buflen)); break; case NVME_LIST_LOGS_KIND: switch (nvme_log_disc_kind(disc)) { case NVME_LOG_ID_MANDATORY: ret = strlcpy(buf, "mandatory", buflen); break; case NVME_LOG_ID_OPTIONAL: ret = strlcpy(buf, "optional", buflen); break; case NVME_LOG_ID_VENDOR_SPECIFIC: ret = strlcpy(buf, "vendor-specific", buflen); break; default: ret = snprintf(buf, buflen, "unknown (0x%x)", nvme_log_disc_kind(disc)); break; } break; default: warnx("internal programmer error: encountered unknown ofmt " "argument id 0x%x", ofmt_arg->ofmt_id); abort(); } return (ret < buflen); } const char *nvmeadm_list_logs_fields = "device,name,scope,fields,desc"; const char *nvmeadm_list_logs_fields_impl = "device,name,scope,impl,fields," "desc"; const ofmt_field_t nvmeadm_list_logs_ofmt[] = { { "DEVICE", 8, NVME_LIST_LOGS_DEVICE, nvmeadm_list_logs_ofmt_cb }, { "NAME", 18, NVME_LIST_LOGS_NAME, nvmeadm_list_logs_ofmt_cb }, { "DESC", 30, NVME_LIST_LOGS_DESC, nvmeadm_list_logs_ofmt_cb }, { "SCOPE", 14, NVME_LIST_LOGS_SCOPE, nvmeadm_list_logs_ofmt_cb }, { "FIELDS", 10, NVME_LIST_LOGS_FIELDS, nvmeadm_list_logs_ofmt_cb }, { "CSI", 6, NVME_LIST_LOGS_CSI, nvmeadm_list_logs_ofmt_cb }, { "LID", 6, NVME_LIST_LOGS_LID, nvmeadm_list_logs_ofmt_cb }, { "SIZE", 10, NVME_LIST_LOGS_SIZE, nvmeadm_list_logs_ofmt_cb }, { "MINSIZE", 10, NVME_LIST_LOGS_MINSIZE, nvmeadm_list_logs_ofmt_cb }, { "IMPL", 6, NVME_LIST_LOGS_IMPL, nvmeadm_list_logs_ofmt_cb }, { "SOURCES", 20, NVME_LIST_LOGS_SOURCES, nvmeadm_list_logs_ofmt_cb }, { "KIND", 16, NVME_LIST_LOGS_KIND, nvmeadm_list_logs_ofmt_cb }, { NULL, 0, 0, NULL } }; typedef enum { NVME_LIST_FEATS_DEVICE, NVME_LIST_FEATS_SHORT, NVME_LIST_FEATS_SPEC, NVME_LIST_FEATS_FID, NVME_LIST_FEATS_SCOPE, NVME_LIST_FEATS_KIND, NVME_LIST_FEATS_CSI, NVME_LIST_FEATS_FLAGS, NVME_LIST_FEATS_GET_IN, NVME_LIST_FEATS_SET_IN, NVME_LIST_FEATS_GET_OUT, NVME_LIST_FEATS_SET_OUT, NVME_LIST_FEATS_DATA_LEN, NVME_LIST_FEATS_IMPL } nvme_list_features_ofmt_field_t; static const nvmeadm_bitstr_t nvmeadm_feat_scopes[] = { { NVME_FEAT_SCOPE_CTRL, "controller" }, { NVME_FEAT_SCOPE_NS, "namespace" } }; static const nvmeadm_bitstr_t nvmeadm_feat_get_in[] = { { NVME_GET_FEAT_F_CDW11, "cdw11" }, { NVME_GET_FEAT_F_DATA, "data" }, { NVME_GET_FEAT_F_NSID, "nsid" } }; static const nvmeadm_bitstr_t nvmeadm_feat_set_in[] = { { NVME_SET_FEAT_F_CDW11, "cdw11" }, { NVME_SET_FEAT_F_CDW12, "cdw12" }, { NVME_SET_FEAT_F_CDW13, "cdw13" }, { NVME_SET_FEAT_F_CDW14, "cdw14" }, { NVME_SET_FEAT_F_CDW15, "cdw15" }, { NVME_SET_FEAT_F_DATA, "data" }, { NVME_SET_FEAT_F_NSID, "nsid" } }; static const nvmeadm_bitstr_t nvmeadm_feat_output[] = { { NVME_FEAT_OUTPUT_CDW0, "cdw0" }, { NVME_FEAT_OUTPUT_DATA, "data" } }; static const nvmeadm_bitstr_t nvmeadm_feat_flags[] = { { NVME_FEAT_F_GET_BCAST_NSID, "get-bcastns" }, { NVME_FEAT_F_SET_BCAST_NSID, "set-bcastns" } }; static const nvmeadm_bitstr_t nvmeadm_feat_csi[] = { { NVME_FEAT_CSI_NVM, "nvm" } }; static boolean_t nvmeadm_list_features_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen) { const nvmeadm_list_features_ofmt_arg_t *nlfo = ofmt_arg->ofmt_cbarg; const nvme_feat_disc_t *feat = nlfo->nlfoa_feat; size_t ret; switch (ofmt_arg->ofmt_id) { case NVME_LIST_FEATS_DEVICE: ret = strlcpy(buf, nlfo->nlfoa_name, buflen); break; case NVME_LIST_FEATS_SHORT: ret = strlcpy(buf, nvme_feat_disc_short(feat), buflen); break; case NVME_LIST_FEATS_SPEC: ret = strlcpy(buf, nvme_feat_disc_spec(feat), buflen); break; case NVME_LIST_FEATS_FID: ret = snprintf(buf, buflen, "0x%x", nvme_feat_disc_fid(feat)); break; case NVME_LIST_FEATS_SCOPE: return (nvmeadm_bits_to_str(nvme_feat_disc_scope(feat), nvmeadm_feat_scopes, ARRAY_SIZE(nvmeadm_feat_scopes), buf, buflen)); case NVME_LIST_FEATS_KIND: switch (nvme_feat_disc_kind(feat)) { case NVME_FEAT_MANDATORY: ret = strlcpy(buf, "mandatory", buflen); break; case NVME_FEAT_OPTIONAL: ret = strlcpy(buf, "optional", buflen); break; case NVME_FEAT_VENDOR_SPECIFIC: ret = strlcpy(buf, "vendor-specific", buflen); break; default: ret = snprintf(buf, buflen, "unknown (0x%x)", nvme_feat_disc_kind(feat)); break; } break; case NVME_LIST_FEATS_CSI: if (nvme_feat_disc_csi(feat) == NVME_FEAT_CSI_NONE) { ret = strlcpy(buf, "none", buflen); break; } return (nvmeadm_bits_to_str(nvme_feat_disc_csi(feat), nvmeadm_feat_csi, ARRAY_SIZE(nvmeadm_feat_csi), buf, buflen)); case NVME_LIST_FEATS_FLAGS: return (nvmeadm_bits_to_str(nvme_feat_disc_flags(feat), nvmeadm_feat_flags, ARRAY_SIZE(nvmeadm_feat_flags), buf, buflen)); case NVME_LIST_FEATS_GET_IN: return (nvmeadm_bits_to_str(nvme_feat_disc_fields_get(feat), nvmeadm_feat_get_in, ARRAY_SIZE(nvmeadm_feat_get_in), buf, buflen)); case NVME_LIST_FEATS_SET_IN: return (nvmeadm_bits_to_str(nvme_feat_disc_fields_set(feat), nvmeadm_feat_set_in, ARRAY_SIZE(nvmeadm_feat_set_in), buf, buflen)); case NVME_LIST_FEATS_GET_OUT: return (nvmeadm_bits_to_str(nvme_feat_disc_output_get(feat), nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf, buflen)); case NVME_LIST_FEATS_SET_OUT: return (nvmeadm_bits_to_str(nvme_feat_disc_output_set(feat), nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf, buflen)); case NVME_LIST_FEATS_DATA_LEN: if (nvme_feat_disc_data_size(feat) == 0) { ret = strlcpy(buf, "-", buflen); } else { ret = snprintf(buf, buflen, "%" PRIu64, nvme_feat_disc_data_size(feat)); } break; case NVME_LIST_FEATS_IMPL: switch (nvme_feat_disc_impl(feat)) { case NVME_FEAT_IMPL_UNKNOWN: ret = strlcpy(buf, "unknown", buflen); break; case NVME_FEAT_IMPL_UNSUPPORTED: ret = strlcpy(buf, "no", buflen); break; case NVME_FEAT_IMPL_SUPPORTED: ret = strlcpy(buf, "yes", buflen); break; default: ret = snprintf(buf, buflen, "unknown (0x%x)", nvme_feat_disc_impl(feat)); break; } break; default: warnx("internal programmer error: encountered unknown ofmt " "argument id 0x%x", ofmt_arg->ofmt_id); abort(); } return (ret < buflen); } const char *nvmeadm_list_features_fields = "device,short,scope,impl,spec"; const ofmt_field_t nvmeadm_list_features_ofmt[] = { { "DEVICE", 8, NVME_LIST_FEATS_DEVICE, nvmeadm_list_features_ofmt_cb }, { "SHORT", 14, NVME_LIST_FEATS_SHORT, nvmeadm_list_features_ofmt_cb }, { "SPEC", 30, NVME_LIST_FEATS_SPEC, nvmeadm_list_features_ofmt_cb }, { "FID", 6, NVME_LIST_FEATS_FID, nvmeadm_list_features_ofmt_cb }, { "SCOPE", 14, NVME_LIST_FEATS_SCOPE, nvmeadm_list_features_ofmt_cb }, { "KIND", 16, NVME_LIST_FEATS_KIND, nvmeadm_list_features_ofmt_cb }, { "CSI", 6, NVME_LIST_FEATS_CSI, nvmeadm_list_features_ofmt_cb }, { "FLAGS", 14, NVME_LIST_FEATS_FLAGS, nvmeadm_list_features_ofmt_cb }, { "GET-IN", 14, NVME_LIST_FEATS_GET_IN, nvmeadm_list_features_ofmt_cb }, { "SET-IN", 14, NVME_LIST_FEATS_SET_IN, nvmeadm_list_features_ofmt_cb }, { "GET-OUT", 14, NVME_LIST_FEATS_GET_OUT, nvmeadm_list_features_ofmt_cb }, { "SET-OUT", 14, NVME_LIST_FEATS_SET_OUT, nvmeadm_list_features_ofmt_cb }, { "DATALEN", 8, NVME_LIST_FEATS_DATA_LEN, nvmeadm_list_features_ofmt_cb }, { "IMPL", 8, NVME_LIST_FEATS_IMPL, nvmeadm_list_features_ofmt_cb }, { NULL, 0, 0, NULL } }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Logic to gather the physical interface receiver eye opening measurement and * slice and dice it. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" /* * Pick a reasonable buffer size that we think will fit within a device's * maximum payload. For now we use 64 KiB somewhat arbitrarily. */ #define PHYEYE_BUFSIZE (64 * 1024) /* * Maximum values for PCIe lanes and eyes. We use 16 lanes for PCIe as while * most devices are only x4, that's the largest sized slot that's typically * implemented. * * For the maximum number of eyes there is only 1 for NRZ and today 3 for PAM4. * So we pick the PAM4 default. */ #define PHYEYE_MAX_LANE 16 #define PHYEYE_MAX_EYE 3 typedef struct { const char *pm_output; nvme_eom_lsp_mqual_t pm_qual; } phyeye_measure_t; typedef enum { PHYEYE_REPORT_M_ASCII, PHYEYE_REPORT_M_OED } phyeye_report_mode_t; typedef struct { nvme_process_arg_t *pr_npa; const char *pr_input; phyeye_report_mode_t pr_mode; uint8_t pr_lane; uint8_t pr_eye; bool pr_print; } phyeye_report_t; static const nvmeadm_field_bit_t phyeye_eom_odp_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "pefp", .nfb_desc = "Printable Eye Field Present", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not present", "present" } }, { .nfb_lowbit = 1, .nfb_hibit = 1, .nfb_short = "edfp", .nfb_desc = "Eye Data Field Present", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "not present", "present" } } }; static const nvmeadm_field_bit_t phyeye_eom_lspfc_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 6, .nfb_short = "lspfv", .nfb_desc = "Log Specific Parameter Field Value", .nfb_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_bit_t phyeye_eom_linfo_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 3, .nfb_short = "mls", .nfb_desc = "Measurement Link Speed", .nfb_type = NVMEADM_FT_HEX } }; #define PHYEYE_F_EOM(f) .nf_off = offsetof(nvme_eom_hdr_t, eom_##f), \ .nf_len = sizeof (((nvme_eom_hdr_t *)NULL)->eom_##f), \ .nf_short = #f static const nvmeadm_field_t phyeye_eom_fields[] = { { PHYEYE_F_EOM(lid), .nf_desc = "Log Identifier", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(eomip), .nf_desc = "EOM In Progress", .nf_type = NVMEADM_FT_STRMAP, .nf_strs = { "no measurement", "in progress", "completed" }, }, { PHYEYE_F_EOM(hsize), .nf_desc = "Header Size", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(rsz), .nf_desc = "Result Size", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(edgn), .nf_desc = "EOM Data Generation Number", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(lrev), .nf_desc = "Log Revision", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(odp), .nf_desc = "Optional Data Present", NVMEADM_F_BITS(phyeye_eom_odp_bits) }, { PHYEYE_F_EOM(lns), .nf_desc = "Lanes", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(epl), .nf_desc = "Eyes Per Lane", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(lspfc), .nf_desc = "Log Specific Parameter Field Copy", NVMEADM_F_BITS(phyeye_eom_lspfc_bits) }, { PHYEYE_F_EOM(linfo), .nf_desc = "Link Information", NVMEADM_F_BITS(phyeye_eom_linfo_bits) }, { PHYEYE_F_EOM(lsic), .nf_desc = "Log Specific Identifier Copy", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(ds), .nf_desc = "Descriptor Size", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(nd), .nf_desc = "Number of Descriptors", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(maxtb), .nf_desc = "Maximum Top Bottom", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(maxlr), .nf_desc = "Maximum Left Right", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(etgood), .nf_desc = "Estimated Time for Good Quality", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(etbetter), .nf_desc = "Estimated Time for Better Quality", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_EOM(etbest), .nf_desc = "Estimated Time for Best Quality", .nf_type = NVMEADM_FT_HEX } }; static const nvmeadm_field_bit_t phyeye_eld_mstat_bits[] = { { .nfb_lowbit = 0, .nfb_hibit = 0, .nfb_short = "mscs", .nfb_desc = "Measurement Successful", .nfb_type = NVMEADM_FT_STRMAP, .nfb_strs = { "no", "yes" } } }; #define PHYEYE_F_ELD(f) .nf_off = offsetof(nvme_eom_lane_desc_t, eld_##f), \ .nf_len = sizeof (((nvme_eom_lane_desc_t *)NULL)->eld_##f), \ .nf_short = #f static const nvmeadm_field_t phyeye_elm_fields[] = { { PHYEYE_F_ELD(mstat), .nf_desc = "Measurement Status", NVMEADM_F_BITS(phyeye_eld_mstat_bits) }, { PHYEYE_F_ELD(ln), .nf_desc = "Lane", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(eye), .nf_desc = "Eye", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(top), .nf_desc = "Top", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(btm), .nf_desc = "Bottom", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(lft), .nf_desc = "Left", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(rgt), .nf_desc = "Right", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(nrows), .nf_desc = "Number of Rows", .nf_type = NVMEADM_FT_HEX }, { PHYEYE_F_ELD(ncols), .nf_desc = "Number of Columns", .nf_type = NVMEADM_FT_HEX }, { /* * The size of this field changed between revision 2 and 3 from 2 bytes * to 4 bytes. */ .nf_off = offsetof(nvme_eom_lane_desc_t, eld_edlen), .nf_len = sizeof (uint16_t), .nf_short = "edlen", .nf_desc = "Eye Data Length", .nf_type = NVMEADM_FT_HEX, .nf_rev = 2 }, { PHYEYE_F_ELD(edlen), .nf_desc = "Eye Data Length", .nf_type = NVMEADM_FT_HEX, .nf_rev = 3 } }; void usage_measure_phyeye_cmd(const char *c_name) { (void) fprintf(stderr, "%s -o output [-Q good | better | best] " "\n\n", c_name); (void) fprintf(stderr, " Gather physical eye opening measurements " "from the named controller and save\n them to the specified " "output file. The best quality measurement is taken by\n " "default. No other administrative operations can be executed " "during the eye\n measurement.\n"); } void optparse_measure_phyeye_cmd(nvme_process_arg_t *npa) { int c; phyeye_measure_t *phy; if ((phy = calloc(1, sizeof (phyeye_measure_t))) == NULL) { err(-1, "failed to allocate memory for option tracking"); } /* * Default to best quality if not otherwise requested. */ phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:Q:")) != -1) { switch (c) { case 'o': phy->pm_output = optarg; break; case 'Q': if (strcasecmp(optarg, "good") == 0) { phy->pm_qual = NVME_EOM_LSP_MQUAL_GOOD; } else if (strcasecmp(optarg, "better") == 0) { phy->pm_qual = NVME_EOM_LSP_MQUAL_BETTER; } else if (strcasecmp(optarg, "best") == 0) { phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST; } else { errx(-1, "invalid quality value %s: valid " "values are 'good', 'better', or 'best'", optarg); } break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (phy->pm_output == NULL) { errx(-1, "missing required output file (-o)"); } npa->npa_cmd_arg = phy; } /* * Poll the log page until it is done and ready. We always do the initial wait. * The specification says that this may take longer due to activity on the * device. We will wait up to 3x the amount of time that was indicated for this * measurement. We will begin using a 1 second delay after this point. * * This explicitly uses a volatile pointer for 'eom' due to the fact that the * log page execution will update the data that it points to. */ static void nvmeadm_phyeye_wait(const nvme_process_arg_t *npa, nvme_log_req_t *req, volatile nvme_eom_hdr_t *eom, uint16_t wait, const char *qual) { hrtime_t start = gethrtime(); hrtime_t end = start + wait * NANOSEC; hrtime_t now = start; hrtime_t max = start + wait * 3 * NANOSEC; const bool tty = isatty(STDOUT_FILENO); (void) printf("device indicates a minimum %u second wait for %s " "quality phyeye measurement\n", wait, qual); while (now < end) { if (tty) { (void) printf("\r%u/%u seconds elapsed", (now - start) / NANOSEC, wait); (void) fflush(stdout); } (void) sleep(1); now = gethrtime(); } if (tty) { (void) printf("\r%u/%u seconds elapsed\n", wait, wait); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to retrieve phyeye measurement log " "request"); } if (eom->eom_eomip == NVME_EOM_DONE) { return; } (void) printf("Measurement incomplete, proceeding to check over an " "additional %u seconds\n", max - wait); uint32_t extra = 0; now = gethrtime(); hrtime_t phase2 = now; while (now < max) { (void) sleep(1); if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to issue start phyeye " "measurement log request"); } now = gethrtime(); if (eom->eom_eomip == NVME_EOM_DONE) { return; } extra++; if (tty) { (void) printf("\rMeasurement still not available after " "%u attempts (%u seconds)", extra, (now - phase2) / NANOSEC); (void) fflush(stdout); } } errx(-1, "timed out waiting for the phyeye measurement to finish after " "%u seconds: final measurement state: %u", wait * 3, eom->eom_eomip); } static void nvmeadm_phyeye_read(const nvme_process_arg_t *npa, nvme_log_req_t *req, void *buf, size_t len, uint64_t off) { if (!nvme_log_req_set_output(req, buf, len)) { nvmeadm_fatal(npa, "failed to set output buffer"); } if (!nvme_log_req_set_offset(req, off)) { nvmeadm_fatal(npa, "failed to set offset to 0x%lx", off); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to read %zu bytes at 0x%lx", len, off); } } static void nvmeadm_phyeye_write(int fd, const void *buf, size_t len, off_t off) { size_t loff = 0; while (len > 0) { ssize_t ret = pwrite(fd, buf + loff, len, off + loff); if (ret < 0) { err(EXIT_FAILURE, "failed to write to physical eye " "measurement output file"); } loff += (size_t)ret; len -= (size_t)ret; } } /* * Perform a physical eye measurement. This consists of a few different steps to * execute it successfully: * * 1. First determine that we can actually issue this command. * 2. Open the output file early. While this may mean we truncate something, * given that this command may take some time, that's better than finding out * after you've already done all the work. * 3. We issue the first phy eye get log page command with the request to begin * a new measurement at the requested quality. We need to set the LSP, LSI, * and output buffer for this. * 4. We wait for the requested number of seconds before beginning to query for * result data. * 5. Once a second, we issue commands trying to see if it's done. * 6. Once it's finally done, then we'll go ahead and actually finish getting * the log page data and write it out to disk. * 7. When we're done with all the data, confirm that the generation is still * the same as when we started. */ int do_measure_phyeye_cmd(const nvme_process_arg_t *npa) { int fd = -1; nvme_log_req_t *req = NULL; nvme_log_disc_t *disc = NULL; nvme_eom_lsp_t lsp; const phyeye_measure_t *phy = npa->npa_cmd_arg; void *buf = NULL; uint64_t min_len; nvme_log_size_kind_t lkind; if (!nvme_log_req_init_by_name(npa->npa_ctrl, "phyeye", 0, &disc, &req)) { nvmeadm_fatal(npa, "failed to initialize phyeye log request"); } if ((fd = open(phy->pm_output, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) { err(-1, "failed to open output file %s", phy->pm_output); } if ((buf = malloc(PHYEYE_BUFSIZE)) == NULL) { err(-1, "failed to allocate internal phy data buffer"); } if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) { nvmeadm_fatal(npa, "failed to set lsi for phyeye measurement"); } (void) memset(&lsp, 0, sizeof (lsp)); lsp.nel_mqual = phy->pm_qual; lsp.nel_act = NVME_EOM_LSP_START; if (!nvme_log_req_set_lsp(req, lsp.r)) { nvmeadm_fatal(npa, "failed to set lsp for phyeye measurement"); } lkind = nvme_log_disc_size(disc, &min_len); VERIFY3U(lkind, ==, NVME_LOG_SIZE_K_VAR); VERIFY3U(min_len, >=, sizeof (nvme_eom_hdr_t)); if (!nvme_log_req_set_output(req, buf, min_len)) { nvmeadm_fatal(npa, "failed to set initial output buffer and " "length"); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to issue start phyeye measurement " "log request"); } /* * Update the request for the rest of this to always be a read request * of the existing measurement. */ lsp.nel_act = NVME_EOM_LSP_READ; if (!nvme_log_req_set_lsp(req, lsp.r)) { nvmeadm_fatal(npa, "failed to update lsp for phyeye " "measurement"); } /* * The use of volatile here is probably a little weird. But this is * aliasing memory that the log req exec will constantly be updating. */ const volatile nvme_eom_hdr_t *eom = buf; if (eom->eom_eomip != NVME_EOM_IN_PROGRESS) { warnx("EOM in progress in header is not in-progress, found %u: " "waiting the appropriate time regardless", eom->eom_eomip); } const uint8_t eom_gen = eom->eom_edgn; uint16_t wait; const char *desc; if (phy->pm_qual == NVME_EOM_LSP_MQUAL_GOOD) { wait = eom->eom_etgood; desc = "good"; } else if (phy->pm_qual == NVME_EOM_LSP_MQUAL_BETTER) { wait = eom->eom_etbetter; desc = "better"; } else { wait = eom->eom_etbest; desc = "best"; } nvmeadm_phyeye_wait(npa, req, buf, wait, desc); /* * Go ahead and calculate the final size. At this point we'll issue * requests that adjust the overall offset until we read everything and * write that out. */ uint64_t act_len, off = 0; if (!nvme_log_disc_calc_size(disc, &act_len, buf, min_len)) { errx(-1, "failed to determine full phyeye log length"); } while (off < act_len) { size_t to_read = MIN(act_len - off, PHYEYE_BUFSIZE); nvmeadm_phyeye_read(npa, req, buf, to_read, off); nvmeadm_phyeye_write(fd, buf, to_read, off); off += to_read; } /* * Now that we're done, get the initial header's worth of data again and * verify its generation to make sure nothing has changed on us. */ nvmeadm_phyeye_read(npa, req, buf, sizeof (nvme_eom_hdr_t), 0); if (eom->eom_edgn != eom_gen) { (void) unlink(phy->pm_output); errx(-1, "PHY eye measurement generation unexpectedly changed: " "was 0x%x, now is 0x%x: aborting", eom_gen, eom->eom_edgn); } /* * Note we don't actually clear the data here and basically are willing * to leave this in the controller at this point. */ (void) printf("phyeye successfully written to %s\n", phy->pm_output); free(buf); if (fd >= 0) { VERIFY0(close(fd)); } nvme_log_disc_free(disc); nvme_log_req_fini(req); return (0); } void usage_report_phyeye_cmd(const char *c_name) { (void) fprintf(stderr, "%s -f file [-l lane] [-e eye] [-m mode] " "\n\n", c_name); (void) fprintf(stderr, " Report information about a physical eye " "measurement. Eye measurements can be\n taken with \"nvmeadm " "measure-phyeye\".\n"); } void optparse_report_phyeye_cmd(nvme_process_arg_t *npa) { int c; phyeye_report_t *phy; if ((phy = calloc(1, sizeof (phyeye_report_t))) == NULL) { err(-1, "failed to allocate memory for option tracking"); } phy->pr_npa = npa; phy->pr_mode = PHYEYE_REPORT_M_ASCII; phy->pr_lane = UINT8_MAX; phy->pr_eye = UINT8_MAX; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":e:f:l:m:")) != -1) { const char *errstr; switch (c) { case 'e': phy->pr_eye = strtonumx(optarg, 0, PHYEYE_MAX_EYE, &errstr, 0); if (errstr != NULL) { errx(-1, "failed to parse eye: value %s is " "%s: valid values are in the range [%u, " "%u]", optarg, errstr, 0, PHYEYE_MAX_EYE); } break; case 'f': phy->pr_input = optarg; break; case 'l': phy->pr_lane = strtonumx(optarg, 0, PHYEYE_MAX_LANE, &errstr, 0); if (errstr != NULL) { errx(-1, "failed to parse lane: value %s is " "%s: valid values are in the range [%u, " "%u]", optarg, errstr, 0, PHYEYE_MAX_LANE); } break; case 'm': if (strcasecmp(optarg, "print-eye") == 0) { phy->pr_mode = PHYEYE_REPORT_M_ASCII; } else if (strcasecmp(optarg, "eye-data") == 0) { phy->pr_mode = PHYEYE_REPORT_M_OED; } else { errx(-1, "invalid mode value: %s: valid values " "are 'print-eye' or 'eye-data'", optarg); } break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (phy->pr_input == NULL) { errx(-1, "missing required input file to process (-f)"); } npa->npa_cmd_arg = phy; } /* * Normalize the optional eye data length. This is a uint32_t in revision 3 * logs, but a uint16_t in revision 2 logs. */ static uint32_t phyeye_eol_oed_len(const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc) { uint16_t u16; if (hdr->eom_lrev >= 3) { return (desc->eld_edlen); } (void) memcpy(&u16, &desc->eld_edlen, sizeof (uint16_t)); return (u16); } typedef void (*phyeye_lane_iter_cb_f)(uint8_t, const nvme_eom_hdr_t *, const nvme_eom_lane_desc_t *, void *); static bool phyeye_lane_iter(const nvme_eom_hdr_t *hdr, off_t max, void *arg, phyeye_lane_iter_cb_f func, uint8_t lane, uint8_t eye) { off_t cur_off = sizeof (nvme_eom_hdr_t); bool ret = true; for (uint16_t i = 0; i < hdr->eom_nd; i++) { const nvme_eom_lane_desc_t *desc = NULL; size_t dlen = sizeof (nvme_eom_lane_desc_t); if (cur_off + hdr->eom_ds > max) { errx(-1, "failed to iterate EOM Lane descriptors: " "descriptor %u starts at offset 0x%lx, but its " "size (0x%x) would exceed the maximum file length " "of 0x%lx", i, cur_off, hdr->eom_ds, max); } desc = (const nvme_eom_lane_desc_t *)((uintptr_t)hdr + cur_off); if (hdr->eom_odp.odp_pefp != 0) { if (desc->eld_nrows == 0 || desc->eld_ncols == 0) { errx(-1, "printable eye feature present but " "both NROWS (0x%x) and NCOLS (0x%x) are " "not non-zero", desc->eld_nrows, desc->eld_ncols); } dlen += desc->eld_nrows * desc->eld_ncols; } else if (desc->eld_nrows != 0 || desc->eld_ncols != 0) { errx(-1, "printable eye feature not present but both " "NROWS (0x%x) and NCOLS (0x%x) are not zero", desc->eld_nrows, desc->eld_ncols); } const uint32_t oed_len = phyeye_eol_oed_len(hdr, desc); if (hdr->eom_odp.odp_edfp != 0) { if (oed_len == 0) { errx(-1, "optional eye data feature present, " "but eye data has a zero-length"); } dlen += oed_len; } else if (oed_len != 0) { errx(-1, "optional eye data feature not present, but " "eye data has a non-zero length (0x%x)", oed_len); } if (dlen > hdr->eom_ds) { errx(-1, "failed to iterate EOM Lane descriptors: " "descriptor %u starts at offset 0x%lx, has a " "calculated size (0x%zx) that exceeds the " "header's max descriptor size (0x%x)", i, cur_off, dlen, hdr->eom_ds); } /* * Now that we've validated this we need to check a few things * before we call the command: * * 1. This matches our eye and lane filter. * 2. The data is valid. */ if (lane != UINT8_MAX && desc->eld_ln != lane) goto next; if (eye != UINT8_MAX && desc->eld_eye != eye) goto next; if (desc->eld_mstat.mstat_mcsc == 0) { warnx("lane %u, eye %u data does not have a successful " "measurement", desc->eld_ln, desc->eld_eye); ret = false; goto next; } func(i, hdr, desc, arg); next: cur_off += dlen; } return (ret); } /* * Validate the data that we have. In particular we need to confirm: * * 1. The data file covers the entire header. * 2. This is a log revision we know about. * 3. The measurement is completed. * 4. The header size reported is what we expect. * 5. The result size is covered by the file. * 6. If a specific mode requires optional data, it is present. * 7. There is a non-zero number of descriptors. * 8. The descriptor size covers at least the Lane descriptor structure. * 9. DS * NDS is within the result size. * * The specifics of each descriptor are checked when we iterate over them in * phyeye_eol_iter(). */ static void phyeye_report_sanity_check(const nvme_eom_hdr_t *hdr, off_t len, const phyeye_report_t *phy) { if (len < sizeof (nvme_eom_hdr_t)) { errx(-1, "data file is too short: file does not cover the " "0x%lx bytes required for the Eye Opening Measurement " "header", sizeof (nvme_eom_hdr_t)); } /* * This specification was first introduced in the NVMe PCIe revision 1.1 * at log revision 2. It moved to version 3 with NVMe PCIe 1.2. However, * some devices report log revision 1 which means they likely implement * a draft version of the TP. We don't know what's different between * version 1 and 2, but hope for the sake of understanding right now * it doesn't impact our ability to translate this. */ if (hdr->eom_lrev < 1 || hdr->eom_lrev > 3) { errx(-1, "encountered unknown log header revision: 0x%x", hdr->eom_lrev); } /* * Only worry about complete measurements if we're doing a report, not * if we're just printing the log. */ if (phy != NULL && hdr->eom_eomip != NVME_EOM_DONE) { errx(-1, "data file measurement in progress field does not " "indicate a finished measurement (%u): found %u", NVME_EOM_DONE, hdr->eom_eomip); } if (hdr->eom_hsize != sizeof (nvme_eom_hdr_t)) { errx(-1, "data file has unexpected header length: found 0x%x, " "expected 0x%zx", hdr->eom_hsize, sizeof (nvme_eom_hdr_t)); } if (hdr->eom_rsz > len) { errx(-1, "data file reports that the log is 0x%x bytes, but " "file is only 0x%lx bytes", hdr->eom_rsz, len); } if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_ASCII && hdr->eom_odp.odp_pefp == 0) { errx(-1, "Printable Eye requested, but field not present in " "data file"); } if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_OED && hdr->eom_odp.odp_edfp == 0) { errx(-1, "Eye Data Field requested, but field not present in " "data file"); } if (phy != NULL && hdr->eom_nd == 0) { errx(-1, "data file reports no EOM lane descriptors present"); } if (hdr->eom_nd > 0 && hdr->eom_ds < sizeof (nvme_eom_lane_desc_t)) { errx(-1, "data file reports the descriptor size is 0x%x, but " "that is less than the base descriptor size of 0x%zx", hdr->eom_ds, sizeof (nvme_eom_lane_desc_t)); } } static void phyeye_report_ascii(uint8_t descno, const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc, void *arg) { phyeye_report_t *phy = arg; phy->pr_print = true; (void) printf("Lane %u, Eye %u: Printable Eye\n", desc->eld_ln, desc->eld_eye); for (uint16_t row = 0; row < desc->eld_nrows; row++) { for (uint16_t col = 0; col < desc->eld_ncols; col++) { const uint32_t off = row * desc->eld_ncols + col; uint8_t c = desc->eld_data[off]; if (c != '0' && c != '1') c = '?'; (void) putc(c, stdout); } (void) putc('\n', stdout); } } static void phyeye_report_oed(uint8_t descno, const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc, void *arg) { phyeye_report_t *phy = arg; size_t off = desc->eld_nrows * desc->eld_ncols; phy->pr_print = true; (void) printf("Lane %u, Eye %u: Eye Data\n", desc->eld_ln, desc->eld_eye); nvmeadm_dump_hex(&desc->eld_data[off], desc->eld_edlen); } int do_report_phyeye_cmd(const nvme_process_arg_t *npa) { int fd = -1, ret = EXIT_SUCCESS; struct stat st; void *data; phyeye_report_t *phy = npa->npa_cmd_arg; phyeye_lane_iter_cb_f func = NULL; if (npa->npa_argc > 0) { errx(-1, "extraneous arguments beginning with '%s'", npa->npa_argv[0]); } if ((fd = open(phy->pr_input, O_RDONLY)) < 0) { err(-1, "failed to open input file %s", phy->pr_input); } if (fstat(fd, &st) != 0) { err(-1, "failed to stat %s", phy->pr_input); } if (st.st_size > NVMEADM_MAX_MMAP) { errx(-1, "%s file size of 0x%lx exceeds maximum allowed size " "of 0x%llx", phy->pr_input, st.st_size, NVMEADM_MAX_MMAP); } data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { errx(-1, "failed to mmap %s", phy->pr_input); } phyeye_report_sanity_check(data, st.st_size, phy); switch (phy->pr_mode) { case PHYEYE_REPORT_M_ASCII: func = phyeye_report_ascii; break; case PHYEYE_REPORT_M_OED: func = phyeye_report_oed; break; } if (!phyeye_lane_iter(data, st.st_size, phy, func, phy->pr_lane, phy->pr_eye)) { ret = -1; } /* * If nothing was printed, warn and error about this if we had an eye * constraint, a lane constraint, or the mode isn't decoding. We want to * be able to decode a log that has no data. */ if (!phy->pr_print) { warnx("failed to match and print any data"); ret = -1; } VERIFY0(munmap(data, st.st_size)); if (fd >= 0) { VERIFY0(close(fd)); } return (ret); } static uint32_t phyeye_log_getrev(const void *data, size_t len) { const nvme_eom_hdr_t *hdr = data; return (hdr->eom_lrev); } static void phyeye_log_drive_lane_cb(uint8_t descno, const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc, void *arg) { char base[32]; char header[128]; nvmeadm_field_print_t *print = arg; (void) snprintf(base, sizeof (base), "eld%u", descno); (void) snprintf(header, sizeof (header), "EOM Lane Descriptor %u", descno); print->fp_header = header; print->fp_fields = phyeye_elm_fields; print->fp_nfields = ARRAY_SIZE(phyeye_elm_fields); print->fp_base = base; print->fp_data = desc; print->fp_dlen = hdr->eom_ds; print->fp_off = (uintptr_t)desc - (uintptr_t)hdr; nvmeadm_field_print(print); } static bool phyeye_log_drive(nvmeadm_field_print_t *print, const void *data, size_t len) { print->fp_header = "EOM Header"; print->fp_fields = phyeye_eom_fields; print->fp_nfields = ARRAY_SIZE(phyeye_eom_fields); print->fp_base = "eom"; print->fp_data = data; print->fp_dlen = len; print->fp_off = 0; phyeye_report_sanity_check(data, len, NULL); nvmeadm_field_print(print); return (phyeye_lane_iter(data, len, print, phyeye_log_drive_lane_cb, UINT8_MAX, UINT8_MAX)); } const nvmeadm_log_field_info_t phyeye_field_info = { .nlfi_log = "phyeye", .nlfi_min = sizeof (nvme_eom_hdr_t), .nlfi_getrev = phyeye_log_getrev, .nlfi_drive = phyeye_log_drive }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company * Copyright 2022 OmniOS Community Edition (OmniOSce) Association. * Copyright 2022 Tintri by DDN, Inc. All rights reserved. */ /* * functions for printing of NVMe data structures and their members */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" static void nvme_print_str(int, const char *, int, const char *, int); static void nvme_print_double(int, const char *, double, int, const char *); static void nvme_print_int64(int, const char *, uint64_t, const char *, const char *); static void nvme_print_uint64(int, const char *, uint64_t, const char *, const char *); static void nvme_print_uint128(int, const char *, nvme_uint128_t, const char *, int, int); static void nvme_print_bit(int, const char *, boolean_t, uint_t, const char *, const char *); static void nvme_print_hexbuf(int, const char *, const uint8_t *, size_t); static void nvme_print_eui64(int, const char *, const uint8_t *); static void nvme_print_guid(int, const char *, const uint8_t *); static void nvme_print_uuid(int, const char *, const uint8_t *); static const char *generic_status_codes[] = { "Successful Completion", "Invalid Command Opcode", "Invalid Field in Command", "Command ID Conflict", "Data Transfer Error", "Commands Aborted due to Power Loss Notification", "Internal Error", "Command Abort Requested", "Command Aborted due to SQ Deletion", "Command Aborted due to Failed Fused Command", "Command Aborted due to Missing Fused Command", "Invalid Namespace or Format", "Command Sequence Error", /* NVMe 1.1 -- 0xd */ "Invalid SGL Segment Descriptor", "Invalid Number of SGL Descriptors", "Data SGL Length Invalid", "Metadata SGL Length Invalid", "SGL Descriptor Type Invalid", /* NVMe 1.2 -- 0x12 */ "Invalid Use of Controller Memory Buffer", "PRP Offset Invalid", "Atomic Write Unit Exceeded", /* NVMe 1.3 -- 0x15 */ "Operation Denied", "SGL Offset Invalid", "Reserved", "Host Identifier Inconsistent Format", "Keep Alive Timeout Expired", "Keep Alive Timeout Invalid", "Command Aborted due to Preempt and Abort", "Sanitize Failed", "Sanitize in Progress", "SGL Data Block Granularity Invalid", "Command Not Supported for Queue in CMB", /* NVMe 1.4 -- 0x20 */ "Namespace is Write Protected", "Command Interrupted", "Transient Transport Error" }; static const char *specific_status_codes[] = { "Completion Queue Invalid", "Invalid Queue Identifier", "Invalid Queue Size", "Abort Command Limit Exceeded", "Reserved", "Asynchronous Event Request Limit Exceeded", "Invalid Firmware Slot", "Invalid Firmware Image", "Invalid Interrupt Vector", "Invalid Log Page", "Invalid Format", "Firmware Activation Requires Conventional Reset", "Invalid Queue Deletion", /* NVMe 1.1 -- 0xd */ "Feature Identifier Not Saveable", "Feature Not Changeable", "Feature Not Namespace Specific", "Firmware Activation Requires NVM Subsystem Reset", /* NVMe 1.2 -- 0x12 */ "Firmware Activation Requires Reset", "Firmware Activation Requires Maximum Time Violation", "Firmware Activation Prohibited", "Overlapping Range", "Namespace Insufficient Capacity", "Namespace Identifier Unavailable", "Reserved", "Namespace Already Attached", "Namespace Is Private", "Namespace Not Attached", "Thin Provisioning Not Supported", "Controller List Invalid", /* NVMe 1.3 -- 0x1e */ "Boot Partition Write Prohibited", "Invalid Controller Identifier", "Invalid Secondary Controller State", "Invalid Number of Controller Resources", "Invalid Resource Identifier", /* NVMe 1.4 -- 0x23 */ "Sanitize Prohibited While Persistent Memory Region is Enabled", "ANA Group Identifier Invalid", "ANA Attach Failed" }; static const char *generic_nvm_status_codes[] = { "LBA Out Of Range", "Capacity Exceeded", "Namespace Not Ready", /* NVMe 1.1 */ "Reservation Conflict", /* NVMe 1.2 */ "Format In Progress", }; static const char *specific_nvm_status_codes[] = { "Conflicting Attributes", "Invalid Protection Information", "Attempted Write to Read Only Range" }; static const char *media_nvm_status_codes[] = { "Write Fault", "Unrecovered Read Error", "End-to-End Guard Check Error", "End-to-End Application Tag Check Error", "End-to-End Reference Tag Check Error", "Compare Failure", "Access Denied", /* NVMe 1.2 -- 0x87 (0x7) */ "Deallocated or Unwritten Logical Block" }; static const char *path_status_codes[] = { /* NVMe 1.4 -- 0x00 */ "Internal Path Error", "Asymmetric Access Persistent Loss", "Asymmetric Access Inaccessible", "Asymmetric Access Transition" }; static const char *path_controller_codes[] = { /* NVMe 1.4 -- 0x60 */ "Controller Pathing Error" }; static const char *path_host_codes[] = { /* NVMe 1.4 -- 0x70 */ "Host Pathing Error", "Command Aborted by Host" }; static const char *status_code_types[] = { "Generic Command Status", "Command Specific Status", "Media and Data Integrity Errors", "Path Related Status", "Reserved", "Reserved", "Reserved", "Vendor Specific" }; static const char *lbaf_relative_performance[] = { "Best", "Better", "Good", "Degraded" }; static const char *lba_range_types[] = { "Reserved", "Filesystem", "RAID", "Cache", "Page/Swap File" }; static const char *ns_identifier_type[] = { "Reserved", "IEEE Extended Unique Identifier", "Namespace GUID", "UUID" }; /* * nvme_print * * This function prints a string indented by the specified number of spaces, * optionally followed by the specified index if it is >= 0. If a format string * is specified, a single colon and the required number of spaces for alignment * are printed before the format string and any remaining arguments are passed * vprintf. * * NVME_PRINT_ALIGN was chosen so that all values will be lined up nicely even * for the longest name at its default indentation. */ #define NVME_PRINT_ALIGN 43 void nvme_print(int indent, const char *name, int index, const char *fmt, ...) { int align = NVME_PRINT_ALIGN - (indent + 1); va_list ap; if (name != NULL) align -= strlen(name); if (index >= 0) align -= snprintf(NULL, 0, " %d", index); if (align < 0) align = 0; va_start(ap, fmt); (void) printf("%*s%s", indent, "", name != NULL ? name : ""); if (index >= 0) (void) printf(" %d", index); if (fmt != NULL) { if (name != NULL || index >= 0) (void) printf(": "); else (void) printf(" "); (void) printf("%*s", align, ""); (void) vprintf(fmt, ap); } (void) printf("\n"); va_end(ap); } /* * nvme_strlen -- return length of string without trailing whitespace */ int nvme_strlen(const char *str, int len) { if (len <= 0) return (0); while (str[--len] == ' ') ; return (++len); } /* * nvme_print_str -- print a string up to the specified length */ static void nvme_print_str(int indent, const char *name, int index, const char *value, int len) { if (len == 0) len = strlen(value); nvme_print(indent, name, index, "%.*s", nvme_strlen(value, len), value); } /* * nvme_print_double -- print a double up to a specified number of places with * optional unit */ static void nvme_print_double(int indent, const char *name, double value, int places, const char *unit) { if (unit == NULL) unit = ""; nvme_print(indent, name, -1, "%.*g%s", places, value, unit); } /* * nvme_print_int64 -- print int64_t with optional unit in decimal or another * format specified */ static void nvme_print_int64(int indent, const char *name, uint64_t value, const char *fmt, const char *unit) { char *tmp_fmt; if (unit == NULL) unit = ""; if (fmt == NULL) fmt = "%"PRId64; if (asprintf(&tmp_fmt, "%s%%s", fmt) < 0) err(-1, "nvme_print_int64()"); nvme_print(indent, name, -1, tmp_fmt, value, unit); free(tmp_fmt); } /* * nvme_print_temp -- The NVMe specification passes most temperature values as * uint16_t values that are encoded in kelvin. This converts them in one place * to Celsius. */ static void nvme_print_temp(int indent, const char *name, uint16_t value) { int64_t temp = (int64_t)value; temp -= 273; nvme_print_int64(indent, name, temp, NULL, "C"); } /* * nvme_print_uint64 -- print uint64_t with optional unit in decimal or another * format specified */ static void nvme_print_uint64(int indent, const char *name, uint64_t value, const char *fmt, const char *unit) { char *tmp_fmt; if (unit == NULL) unit = ""; if (fmt == NULL) fmt = "%"PRIu64; if (asprintf(&tmp_fmt, "%s%%s", fmt) < 0) err(-1, "nvme_print_uint64()"); nvme_print(indent, name, -1, tmp_fmt, value, unit); free(tmp_fmt); } /* * nvme_snprint_uint128 -- format a 128bit uint with optional unit, after * applying binary and/or decimal shifting */ int nvme_snprint_uint128(char *buf, size_t buflen, nvme_uint128_t value, int scale_bits, int scale_tens) { const char hex[] = "0123456789abcdef"; uint8_t o[(128 + scale_bits) / 3]; char p[sizeof (o) * 2]; char *pp = &p[0]; int i, x; uint64_t rem = 0; /* * Don't allow binary shifting by more than 64 bits to keep the * arithmetic simple. Also limit decimal shifting based on the size * of any possible remainder from binary shifting. */ assert(scale_bits <= 64); assert(scale_tens <= (64 - scale_bits) / 3); bzero(o, sizeof (o)); bzero(p, sizeof (p)); /* * Convert the two 64-bit numbers into a series of BCD digits using * a double-dabble algorithm. By using more or less iterations than * 128 we can do a binary shift in either direction. */ for (x = 0; x != 128 - scale_bits; x++) { for (i = 0; i != sizeof (o); i++) { if ((o[i] & 0xf0) > 0x40) o[i] += 0x30; if ((o[i] & 0xf) > 4) o[i] += 3; } for (i = 0; i != sizeof (o) - 1; i++) o[i] = (o[i] << 1) + (o[i+1] >> 7); o[i] = (o[i] << 1) + (value.hi >> 63); value.hi = (value.hi << 1) + (value.lo >> 63); value.lo = (value.lo << 1); } /* * If we're supposed to do a decimal left shift (* 10^x), too, * calculate the remainder of the previous binary shift operation. */ if (scale_tens > 0) { rem = value.hi >> (64 - scale_bits); for (i = 0; i != scale_tens; i++) rem *= 10; rem >>= scale_bits; } /* * Construct the decimal number for printing. Skip leading zeros. */ for (i = 0; i < sizeof (o); i++) if (o[i] != 0) break; if (i == sizeof (o)) { /* * The converted number is 0. Just print the calculated * remainder and return. */ return (snprintf(buf, buflen, "%"PRId64, rem)); } else { if (o[i] > 0xf) *pp++ = hex[o[i] >> 4]; *pp++ = hex[o[i] & 0xf]; for (i++; i < sizeof (o); i++) { *pp++ = hex[o[i] >> 4]; *pp++ = hex[o[i] & 0xf]; } } /* * For negative decimal scaling, use the snprintf precision specifier to * truncate the results according to the requested decimal scaling. For * positive decimal scaling we print the remainder padded with 0. */ return (snprintf(buf, buflen, "%.*s%0.*"PRId64, strlen(p) + scale_tens, p, scale_tens > 0 ? scale_tens : 0, rem)); } /* * nvme_print_uint128 -- print a 128bit uint with optional unit, after applying * binary and/or decimal shifting */ static void nvme_print_uint128(int indent, const char *name, nvme_uint128_t value, const char *unit, int scale_bits, int scale_tens) { char buf[64]; if (unit == NULL) unit = ""; (void) nvme_snprint_uint128(buf, sizeof (buf), value, scale_bits, scale_tens); nvme_print(indent, name, -1, "%s%s", buf, unit); } /* * nvme_print_bit -- print a bit with optional names for both states */ static void nvme_print_bit(int indent, const char *name, boolean_t valid_vers, uint_t value, const char *s_true, const char *s_false) { if (s_true == NULL) s_true = "supported"; if (s_false == NULL) s_false = "unsupported"; if (!valid_vers) value = 0; nvme_print(indent, name, -1, "%s", value ? s_true : s_false); } /* * nvme_print_hexbuf -- print a buffer of bytes as a hex dump */ static void nvme_print_hexbuf(int indent, const char *name, const uint8_t *buf, size_t len) { /* * The format string is kept in this variable so it can be cut * short to print the remainder after the loop. */ char fmt[] = { "%02x %02x %02x %02x %02x %02x %02x %02x" }; size_t lines = len / 8; size_t rem = len % 8; size_t i; for (i = 0; i < lines; i++) { nvme_print(indent, name, -1, fmt, buf[i*8 + 0], buf[i*8 + 1], buf[i*8 + 2], buf[i*8 + 3], buf[i*8 + 4], buf[i*8 + 5], buf[i*8 + 6], buf[i*8 + 7]); name = NULL; } if (rem > 0) { fmt[rem * 5] = '\0'; nvme_print(indent, name, -1, fmt, buf[i*8 + 0], buf[i*8 + 1], buf[i*8 + 2], buf[i*8 + 3], buf[i*8 + 4], buf[i*8 + 5], buf[i*8 + 6], buf[i*8 + 7]); } } /* * nvme_print_uuid -- print a UUID in canonical form */ static void nvme_print_uuid(int indent, const char *name, const uint8_t *uuid) { nvme_print(indent, name, -1, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" "%02x%02x%02x%02x%02x%02x", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); } /* * nvme_print_guid -- print a namespace GUID */ static void nvme_print_guid(int indent, const char *name, const uint8_t *guid) { nvme_print(indent, name, -1, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", guid[0], guid[1], guid[2], guid[3], guid[4], guid[5], guid[6], guid[7], guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]); } /* * nvme_print_eui64 -- print a namespace EUI64 */ static void nvme_print_eui64(int indent, const char *name, const uint8_t *eui64) { nvme_print(indent, name, -1, "%02X%02X%02X%02X%02X%02X%02X%02X", eui64[0], eui64[1], eui64[2], eui64[3], eui64[4], eui64[5], eui64[6], eui64[7]); } /* * nvme_print_version -- print a uint32_t encoded nvme version */ static void nvme_print_version(int indent, const char *name, uint32_t value) { nvme_reg_vs_t vers; vers.r = value; nvme_print(indent, name, -1, "%u.%u", vers.b.vs_mjr, vers.b.vs_mnr); } /* * nvme_print_ctrl_summary -- print a 1-line summary of the IDENTIFY CONTROLLER * data structure */ void nvme_print_ctrl_summary(nvme_ctrl_info_t *info) { nvme_uint128_t u128; char buf[64]; const nvme_version_t *version = nvme_ctrl_info_version(info); (void) printf("model: %s, serial: %s, FW rev: %s, NVMe v%u.%u", nvme_ctrl_info_model(info), nvme_ctrl_info_serial(info), nvme_ctrl_info_fwrev(info), version->v_major, version->v_minor); /* * This can fail because a device isn't at NVMe version 1.2 or it * doesn't support namespace management. */ if (nvme_ctrl_info_cap(info, &u128)) { (void) nvme_snprint_uint128(buf, sizeof (buf), u128, 20, 0); (void) printf(", Capacity = %s MB", buf); } if (nvme_ctrl_info_unalloc_cap(info, &u128) && (u128.lo != 0 || u128.hi != 0)) { (void) nvme_snprint_uint128(buf, sizeof (buf), u128, 20, 0); (void) printf(", Unallocated = %s MB", buf); } (void) printf("\n"); } /* * nvme_print_nsid_summary -- print a 1-line summary of the IDENTIFY NAMESPACE * data structure */ void nvme_print_nsid_summary(nvme_ns_info_t *ns) { const nvme_nvm_lba_fmt_t *fmt = NULL; const char *comma = ""; uint64_t val; char numbuf[40]; (void) nvme_ns_info_curformat(ns, &fmt); if (nvme_ns_info_size(ns, &val) && fmt != NULL) { nicenum_scale(val, nvme_nvm_lba_fmt_data_size(fmt), numbuf, sizeof (numbuf), NN_UNIT_SPACE); (void) printf("Size = %sB", numbuf); comma = ", "; } if (nvme_ns_info_cap(ns, &val) && fmt != NULL) { nicenum_scale(val, nvme_nvm_lba_fmt_data_size(fmt), numbuf, sizeof (numbuf), NN_UNIT_SPACE); (void) printf("%sCapacity = %sB", comma, numbuf); comma = ", "; } if (nvme_ns_info_use(ns, &val) && fmt != NULL) { nicenum_scale(val, nvme_nvm_lba_fmt_data_size(fmt), numbuf, sizeof (numbuf), NN_UNIT_SPACE); (void) printf("%sUsed = %sB", comma, numbuf); } (void) printf("\n"); } /* * nvme_print_identify_ctrl * * This function pretty-prints the structure returned by the IDENTIFY CONTROLLER * command. */ void nvme_print_identify_ctrl(const nvme_identify_ctrl_t *idctl, uint32_t mpsmin, const nvme_version_t *version) { int i; nvme_print(0, "Identify Controller", -1, NULL); nvme_print(2, "Controller Capabilities and Features", -1, NULL); nvme_print_str(4, "Model", -1, idctl->id_model, sizeof (idctl->id_model)); nvme_print_str(4, "Serial", -1, idctl->id_serial, sizeof (idctl->id_serial)); nvme_print_str(4, "Firmware Revision", -1, idctl->id_fwrev, sizeof (idctl->id_fwrev)); if (verbose) { nvme_print_uint64(4, "PCI vendor ID", idctl->id_vid, "0x%0.4"PRIx64, NULL); nvme_print_uint64(4, "subsystem vendor ID", idctl->id_ssvid, "0x%0.4"PRIx64, NULL); nvme_print_uint64(4, "Recommended Arbitration Burst", idctl->id_rab, NULL, NULL); nvme_print(4, "Vendor IEEE OUI", -1, "%0.2X-%0.2X-%0.2X", idctl->id_oui[0], idctl->id_oui[1], idctl->id_oui[2]); } nvme_print(4, "Multi-Interface Capabilities", -1, NULL); nvme_print_bit(6, "Multiple PCI Express ports", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_mic.m_multi_pci, NULL, NULL); nvme_print_bit(6, "Multiple Controller Support", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_mic.m_multi_ctrl, NULL, NULL); nvme_print_bit(6, "Controller is an SR-IOV Virtual Function", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_mic.m_sr_iov, NULL, NULL); nvme_print_bit(6, "Asymmetric Namespace Access Reporting", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_mic.m_anar_sup, NULL, NULL); if (idctl->id_mdts > 0) nvme_print_uint64(4, "Maximum Data Transfer Size", (1 << idctl->id_mdts) * mpsmin / 1024, NULL, "kB"); else nvme_print_str(4, "Maximum Data Transfer Size", -1, "unlimited", 0); if (nvme_vers_atleast(version, &nvme_vers_1v1)) { nvme_print_uint64(4, "Unique Controller Identifier", idctl->id_cntlid, NULL, NULL); } if (nvme_vers_atleast(version, &nvme_vers_1v2)) { nvme_print_version(4, "NVMe Version", idctl->id_ver); if (idctl->id_rtd3r != 0) { nvme_print_uint64(4, "RTD3 Resume Latency", idctl->id_rtd3r, NULL, "us"); } if (idctl->id_rtd3e != 0) { nvme_print_uint64(4, "RTD3 Entry Latency", idctl->id_rtd3e, NULL, "us"); } } if (verbose) { nvme_print(4, "Optional Asynchronous Events Supported", -1, NULL); nvme_print_bit(6, "Namespace Attribute Notices", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_oaes.oaes_nsan, NULL, NULL); nvme_print_bit(6, "Firmware Activation Notices", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_oaes.oaes_fwact, NULL, NULL); nvme_print_bit(6, "Asynchronous Namespace Access Change " "Notices", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oaes.oaes_ansacn, NULL, NULL); nvme_print_bit(6, "Predictable Latency Event Aggregation", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oaes.oaes_plat, NULL, NULL); nvme_print_bit(6, "LBA Status Information Notices", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oaes.oaes_lbasi, NULL, NULL); nvme_print_bit(6, "Endurance Group Event Aggregate Log Page " "Change Notices", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oaes.oaes_egeal, NULL, NULL); nvme_print(4, "Controller Attributes", -1, NULL); nvme_print_bit(6, "128-bit Host Identifier", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_ctratt.ctrat_hid, NULL, NULL); nvme_print_bit(6, "Non-Operational Power State Permissive Mode", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_ctratt.ctrat_nops, NULL, NULL); nvme_print_bit(6, "NVM Sets", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_nvmset, NULL, NULL); nvme_print_bit(6, "Read Recovery Levels", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_rrl, NULL, NULL); nvme_print_bit(6, "Endurance Groups", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_engrp, NULL, NULL); nvme_print_bit(6, "Predictable Latency Mode", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_plm, NULL, NULL); nvme_print_bit(6, "Traffic Based Keep Alive", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_tbkas, NULL, NULL); nvme_print_bit(6, "Namespace Granularity", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_nsg, NULL, NULL); nvme_print_bit(6, "SQ Associations", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_sqass, NULL, NULL); nvme_print_bit(6, "UUID List", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_ctratt.ctrat_uuid, NULL, NULL); nvme_print(4, "Read Recovery Levels", -1, NULL); nvme_print_bit(6, "Read Recovery Level 0", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 0), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 1", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 1), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 2", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 2), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 3", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 3), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 4 - Default", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 4), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 5", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 5), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 6", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 6), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 7", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 7), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 8", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 8), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 9", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 9), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 10", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 10), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 11", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 11), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 12", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 12), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 13", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 13), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 14", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 14), NULL, NULL); nvme_print_bit(6, "Read Recovery Level 15 - Fast Fail", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_rrls & (1 << 15), NULL, NULL); } if (nvme_vers_atleast(version, &nvme_vers_1v4)) { switch (idctl->id_cntrltype) { case NVME_CNTRLTYPE_RSVD: nvme_print_str(4, "Controller Type", -1, "not reported", 0); break; case NVME_CNTRLTYPE_IO: nvme_print_str(4, "Controller Type", -1, "I/O", 0); break; case NVME_CNTRLTYPE_DISC: nvme_print_str(4, "Controller Type", -1, "discovery", 0); break; case NVME_CNTRLTYPE_ADMIN: nvme_print_str(4, "Controller Type", -1, "administrative", 0); break; default: nvme_print(4, "Controller Type", -1, "unknown reserved value: %u", idctl->id_cntrltype); break; } } else { nvme_print_str(4, "Controller Type", -1, "not reported", 0); } if (nvme_vers_atleast(version, &nvme_vers_1v3)) { uint8_t zguid[16] = { 0 }; if (memcmp(zguid, idctl->id_frguid, sizeof (zguid)) != 0) { nvme_print_guid(4, "FRU GUID", idctl->id_frguid); } else { nvme_print_str(4, "FRU GUID", -1, "unsupported", 0); } } else { nvme_print_str(4, "FRU GUID", -1, "unsupported", 0); } if (nvme_vers_atleast(version, &nvme_vers_1v4)) { nvme_print_uint64(4, "Command Retry Delay Time 1", idctl->id_crdt1 * 100, NULL, "ms"); nvme_print_uint64(4, "Command Retry Delay Time 2", idctl->id_crdt2 * 100, NULL, "ms"); nvme_print_uint64(4, "Command Retry Delay Time 3", idctl->id_crdt3 * 100, NULL, "ms"); } else { nvme_print_str(4, "Command Retry Delay Time 1", -1, "unsupported", 0); nvme_print_str(4, "Command Retry Delay Time 2", -1, "unsupported", 0); nvme_print_str(4, "Command Retry Delay Time 3", -1, "unsupported", 0); } /* * The NVMe-MI spec claimed a portion of the identify controller data; * however, there's no way to actually figure out if this data is valid * or not. We basically have to rely on the NVMe spec's initialized to * zero behavior for this region. Unfortunately, there's no way to get * the NVMe-MI version to know when fields were added here so we * basically treat the minimum version required as that of when the * NVMe-MI region was reserved in the NVMe spec, which is 1.2. Note, * these bytes go in reverse order because they're allocating them in * reverse order. */ if (verbose) { nvme_print(2, "NVMe Management Interface", -1, NULL); nvme_print(4, "Management Endpoint Capabilities", -1, NULL); nvme_print_bit(6, "SMBus/I2C Port Management Endpoint", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_mec.mec_smbusme, NULL, NULL); nvme_print_bit(6, "PCIe Port Management Endpoint", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_mec.mec_pcieme, NULL, NULL); if (idctl->id_vpdwc.vwci_valid != 0) { nvme_print_uint64(4, "VPD Write Cycles Remaining", idctl->id_vpdwc.vwci_crem, NULL, NULL); } else { nvme_print_str(4, "VPD Write Cycles Remaining", -1, "invalid or unsupported", 0); } if (idctl->id_nvmsr.nvmsr_nvmesd == 0 && idctl->id_nvmsr.nvmsr_nvmee == 0 && idctl->id_nvmsr.nvmsr_rsvd == 0) { nvme_print_str(4, "NVM Subsystem Report", -1, "unsupported", 0); } else { nvme_print(4, "NVM Subsystem Report", -1, NULL); nvme_print_bit(6, "NVMe Storage Device", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_nvmsr.nvmsr_nvmesd, NULL, NULL); nvme_print_bit(6, "NVMe Enclosure", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_nvmsr.nvmsr_nvmee, NULL, NULL); } } nvme_print(2, "Admin Command Set Attributes", -1, NULL); nvme_print(4, "Optional Admin Command Support", -1, NULL); nvme_print_bit(6, "Security Send & Receive", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oacs.oa_security, NULL, NULL); nvme_print_bit(6, "Format NVM", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oacs.oa_format, NULL, NULL); nvme_print_bit(6, "Firmware Activate & Download", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oacs.oa_firmware, NULL, NULL); nvme_print_bit(6, "Namespace Management", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_oacs.oa_nsmgmt, NULL, NULL); nvme_print_bit(6, "Device Self-test", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oacs.oa_selftest, NULL, NULL); nvme_print_bit(6, "Directives", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oacs.oa_direct, NULL, NULL); nvme_print_bit(6, "NVME-MI Send and Receive", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oacs.oa_nvmemi, NULL, NULL); nvme_print_bit(6, "Virtualization Management", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oacs.oa_virtmgmt, NULL, NULL); nvme_print_bit(6, "Doorbell Buffer Config", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oacs.oa_doorbell, NULL, NULL); nvme_print_bit(6, "Get LBA Status", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oacs.oa_lbastat, NULL, NULL); if (verbose) { nvme_print_uint64(4, "Abort Command Limit", (uint16_t)idctl->id_acl + 1, NULL, NULL); nvme_print_uint64(4, "Asynchronous Event Request Limit", (uint16_t)idctl->id_aerl + 1, NULL, NULL); } nvme_print(4, "Firmware Updates", -1, NULL); nvme_print_bit(6, "Firmware Slot 1", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_frmw.fw_readonly, "read-only", "writable"); nvme_print_uint64(6, "No. of Firmware Slots", idctl->id_frmw.fw_nslot, NULL, NULL); nvme_print_bit(6, "Activate Without Reset", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_frmw.fw_norst, NULL, NULL); nvme_print(2, "Log Page Attributes", -1, NULL); nvme_print_bit(6, "Per Namespace SMART/Health info", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_lpa.lp_smart, NULL, NULL); nvme_print_bit(6, "Commands Supported and Effects", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_lpa.lp_cmdeff, NULL, NULL); nvme_print_bit(6, "Get Log Page Extended Data", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_lpa.lp_extsup, NULL, NULL); nvme_print_bit(6, "Telemetry Log Pages", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_lpa.lp_telemetry, NULL, NULL); nvme_print_bit(6, "Persistent Event Log", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_lpa.lp_persist, NULL, NULL); nvme_print_uint64(4, "Error Log Page Entries", (uint16_t)idctl->id_elpe + 1, NULL, NULL); nvme_print_uint64(4, "Number of Power States", (uint16_t)idctl->id_npss + 1, NULL, NULL); if (verbose) { nvme_print_bit(4, "Admin Vendor-specific Command Format", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_avscc.av_spec, "standard", "vendor-specific"); } nvme_print_bit(4, "Autonomous Power State Transitions", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_apsta.ap_sup, NULL, NULL); if (nvme_vers_atleast(version, &nvme_vers_1v2)) { nvme_print_temp(4, "Warning Composite Temperature Threshold", idctl->ap_wctemp); nvme_print_temp(4, "Critical Composite Temperature Threshold", idctl->ap_cctemp); } else { nvme_print_str(4, "Warning Composite Temperature Threshold", -1, "unspecified", 0); nvme_print_str(4, "Critical Composite Temperature Threshold", -1, "unspecified", 0); } if (verbose) { if (idctl->ap_mtfa != 0) { nvme_print_uint64(4, "Maximum Firmware Activation Time", idctl->ap_mtfa * 100, NULL, "ms"); } else { nvme_print_str(4, "Maximum Firmware Activation Time", -1, "unknown", 0); } if (idctl->ap_hmpre != 0) { nvme_print_uint64(4, "Host Memory Buffer Preferred " "Size", idctl->ap_hmpre * 4, NULL, "KiB"); } else { nvme_print_str(4, "Host Memory Buffer Preferred " "Size", -1, "unsupported", 0); } if (idctl->ap_hmmin != 0) { nvme_print_uint64(4, "Host Memory Buffer Minimum Size", idctl->ap_hmmin * 4, NULL, "KiB"); } else { nvme_print_str(4, "Host Memory Buffer Minimum Size", -1, "unsupported", 0); } } if (idctl->id_oacs.oa_nsmgmt != 0) { nvme_print_uint128(4, "Total NVM Capacity", idctl->ap_tnvmcap, "B", 0, 0); nvme_print_uint128(4, "Unallocated NVM Capacity", idctl->ap_unvmcap, "B", 0, 0); } else if (verbose) { nvme_print_str(4, "Total NVM Capacity", -1, "unsupported", 0); nvme_print_str(4, "Unallocated NVM Capacity", -1, "unsupported", 0); } if (verbose) { if (idctl->ap_rpmbs.rpmbs_units != 0) { nvme_print(4, "Replay Protected Memory Block", -1, NULL); nvme_print_uint64(6, "Number of RPMB Units", idctl->ap_rpmbs.rpmbs_units, NULL, NULL); switch (idctl->ap_rpmbs.rpmbs_auth) { case NVME_RPMBS_AUTH_HMAC_SHA256: nvme_print_str(6, "Authentication Method", -1, "HMAC SHA-256", 0); break; default: nvme_print(6, "Authentication Method", -1, "unknown reserved value: %u", idctl->ap_rpmbs.rpmbs_auth); break; } nvme_print_uint64(6, "Total Size", (idctl->ap_rpmbs.rpmbs_tot + 1) * 128, NULL, "KiB"); nvme_print_uint64(6, "Access Size", (idctl->ap_rpmbs.rpmbs_acc + 1) * 512, NULL, "KiB"); } else { nvme_print_str(4, "Replay Protected Memory Block", -1, "unsupported", 0); } if (idctl->id_oacs.oa_selftest != 0) { nvme_print_uint64(4, "Extended Device Self-test Time", idctl->ap_edstt, NULL, "min"); nvme_print(4, "Device Self-test Options", -1, NULL); nvme_print_bit(6, "Self-test operation granularity", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->ap_dsto.dsto_sub, "subsystem", "controller"); } else { nvme_print_str(4, "Extended Device Self-test Time", -1, "unsupported", 0); nvme_print_str(4, "Device Self-test Options", -1, "unsupported", 0); } } switch (idctl->ap_fwug) { case 0x00: nvme_print_str(4, "Firmware Update Granularity", -1, "unknown", 0); break; case 0xff: nvme_print_str(4, "Firmware Update Granularity", -1, "unrestricted", 0); break; default: nvme_print_uint64(4, "Firmware Update Granularity", idctl->ap_fwug * 4, NULL, "KiB"); break; } if (verbose) { if (idctl->ap_kas != 0) { nvme_print_uint64(4, "Keep Alive Support", idctl->ap_kas * 100, NULL, "ms"); } else { nvme_print_str(4, "Keep Alive Support", -1, "unsupported", 0); } nvme_print(4, "Host Controlled Thermal Management Attributes", -1, NULL); nvme_print_bit(6, "Host Controlled Thermal Management", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->ap_hctma.hctma_hctm, NULL, NULL); if (idctl->ap_mntmt != 0 && nvme_vers_atleast(version, &nvme_vers_1v3)) { nvme_print_temp(6, "Minimum Thermal Management " "Temperature", idctl->ap_mntmt); } else { nvme_print_str(6, "Minimum Thermal Management " "Temperature", -1, "unsupported", -1); } if (idctl->ap_mxtmt != 0 && nvme_vers_atleast(version, &nvme_vers_1v3)) { nvme_print_temp(6, "Maximum Thermal Management " "Temperature", idctl->ap_mxtmt); } else { nvme_print_str(6, "Maximum Thermal Management " "Temperature", -1, "unsupported", -1); } nvme_print(4, "Sanitize Capabilities", -1, NULL); nvme_print_bit(6, "Crypto Erase Support", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->ap_sanitize.san_ces, NULL, NULL); nvme_print_bit(6, "Block Erase Support", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->ap_sanitize.san_bes, NULL, NULL); nvme_print_bit(6, "Overwrite Support", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->ap_sanitize.san_ows, NULL, NULL); nvme_print_bit(6, "No-Deallocate Inhibited", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_sanitize.san_ndi, NULL, NULL); if (nvme_vers_atleast(version, &nvme_vers_1v4)) { uint_t val = idctl->ap_sanitize.san_nodmmas; switch (val) { case NVME_NODMMAS_UNDEF: nvme_print_str(6, "No-Deallocate Modifies " "Media after Sanitize", -1, "undefined", 0); break; case NVME_NODMMAS_NOMOD: nvme_print_str(6, "No-Deallocate Modifies " "Media after Sanitize", -1, "no modification", 0); break; case NVME_NODMMAS_DOMOD: nvme_print_str(6, "No-Deallocate Modifies " "Media after Sanitize", -1, "modification required", 0); break; default: nvme_print(6, "No-Deallocate Modifies " "Media after Sanitize", -1, "unknown reserved value: %u", val); break; } } else { nvme_print_str(6, "No-Deallocate Modifies Media after " "Sanitize", -1, "undefined", 0); } if (idctl->ap_hmminds != 0) { nvme_print_uint64(4, "Host Memory Buffer Minimum " "Descriptor Entry Size", idctl->ap_hmminds * 4, NULL, "KiB"); } else { nvme_print_str(4, "Host Memory Buffer Minimum " "Descriptor Entry Size", -1, "unsupported", 0); } if (idctl->ap_hmmaxd != 0) { nvme_print_uint64(4, "Host Memory Buffer Maximum " "Descriptor Entries", idctl->ap_hmmaxd, NULL, NULL); } else { nvme_print_str(4, "Host Memory Buffer Maximum " "Descriptor Entries", -1, "unsupported", 0); } if (idctl->id_ctratt.ctrat_engrp != 0) { nvme_print_uint64(4, "Max Endurance Group Identifier", idctl->ap_engidmax, NULL, NULL); } else { nvme_print_str(4, "Max Endurance Group Identifier", -1, "unsupported", 0); } if (idctl->id_mic.m_anar_sup != 0) { nvme_print_uint64(4, "ANA Transition Time", idctl->ap_anatt, NULL, "secs"); } else { nvme_print_str(4, "ANA Transition Time", -1, "unsupported", 0); } nvme_print(4, "Asymmetric Namespace Access Capabilities", -1, NULL); nvme_print_bit(6, "ANA Optimized state", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_opt, NULL, NULL); nvme_print_bit(6, "ANA Non-Optimized state", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_unopt, NULL, NULL); nvme_print_bit(6, "ANA Inaccessible state", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_inacc, NULL, NULL); nvme_print_bit(6, "ANA Persistent Loss state", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_ploss, NULL, NULL); nvme_print_bit(6, "ANA Persistent Change state", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_chg, NULL, NULL); nvme_print_bit(6, "ANAGRPID doesn't change with attached NS", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_grpns, "yes", "no"); nvme_print_bit(6, "Non-zero ANAGRPID in Namespace Management", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->ap_anacap.anacap_grpid, NULL, NULL); if (idctl->id_mic.m_anar_sup != 0) { nvme_print_uint64(4, "Max ANA Group Identifier", idctl->ap_anagrpmax, NULL, NULL); nvme_print_uint64(4, "Number of ANA Group Identifiers", idctl->ap_nanagrpid, NULL, NULL); } else { nvme_print_str(4, "Max ANA Group Identifier", -1, "unsupported", 0); nvme_print_str(4, "Number of ANA Group Identifiers", -1, "unsupported", 0); } if (idctl->id_lpa.lp_persist != 0) { nvme_print_uint64(4, "Persistent Event Log Size", idctl->ap_pels * 64, NULL, "KiB"); } else { nvme_print_str(4, "Persistent Event Log Size", -1, "unsupported", 0); } } nvme_print(2, "NVM Command Set Attributes", -1, NULL); if (verbose) { nvme_print(4, "Submission Queue Entry Size", -1, "min %d, max %d", 1 << idctl->id_sqes.qes_min, 1 << idctl->id_sqes.qes_max); nvme_print(4, "Completion Queue Entry Size", -1, "min %d, max %d", 1 << idctl->id_cqes.qes_min, 1 << idctl->id_cqes.qes_max); if (nvme_vers_atleast(version, &nvme_vers_1v2)) { nvme_print_uint64(4, "Maximum Outstanding Commands", idctl->id_maxcmd, NULL, NULL); } else { nvme_print_str(4, "Maximum Outstanding Commands", -1, "unknown", 0); } } nvme_print_uint64(4, "Number of Namespaces", idctl->id_nn, NULL, NULL); nvme_print(4, "Optional NVM Command Support", -1, NULL); nvme_print_bit(6, "Compare", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oncs.on_compare, NULL, NULL); nvme_print_bit(6, "Write Uncorrectable", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oncs.on_wr_unc, NULL, NULL); nvme_print_bit(6, "Dataset Management", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_oncs.on_dset_mgmt, NULL, NULL); nvme_print_bit(6, "Write Zeros", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_oncs.on_wr_zero, NULL, NULL); nvme_print_bit(6, "Save/Select in Get/Set Features", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_oncs.on_save, NULL, NULL); nvme_print_bit(6, "Reservations", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_oncs.on_reserve, NULL, NULL); nvme_print_bit(6, "Timestamp Feature", nvme_vers_atleast(version, &nvme_vers_1v3), idctl->id_oncs.on_ts, NULL, NULL); nvme_print_bit(6, "Verify", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_oncs.on_verify, NULL, NULL); nvme_print(4, "Fused Operation Support", -1, NULL); nvme_print_bit(6, "Compare and Write", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_fuses.f_cmp_wr, NULL, NULL); nvme_print(4, "Format NVM Attributes", -1, NULL); nvme_print_bit(6, "Per Namespace Format", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_fna.fn_format == 0, NULL, NULL); nvme_print_bit(6, "Per Namespace Secure Erase", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_fna.fn_sec_erase == 0, NULL, NULL); nvme_print_bit(6, "Cryptographic Erase", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_fna.fn_crypt_erase, NULL, NULL); nvme_print(4, "Volatile Write Cache", -1, NULL); nvme_print_bit(6, "Present", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_vwc.vwc_present, "yes", "no"); if (verbose) { switch (idctl->id_vwc.vwc_nsflush) { case NVME_VWCNS_UNKNOWN: nvme_print_str(6, "Flush with NSID 0xFFFFFFFF", -1, "unknown", 0); break; case NVME_VWCNS_UNSUP: nvme_print_str(6, "Flush with NSID 0xFFFFFFFF", -1, "unsupported", 0); break; case NVME_VWCNS_SUP: nvme_print_str(6, "Flush with NSID 0xFFFFFFFF", -1, "supported", 0); break; default: nvme_print(6, "Flush with NSID 0xFFFFFFFF", -1, "unknown reserved value: %u", idctl->id_vwc.vwc_nsflush); break; } } nvme_print_uint64(4, "Atomic Write Unit Normal", (uint32_t)idctl->id_awun + 1, NULL, idctl->id_awun == 0 ? " block" : " blocks"); nvme_print_uint64(4, "Atomic Write Unit Power Fail", (uint32_t)idctl->id_awupf + 1, NULL, idctl->id_awupf == 0 ? " block" : " blocks"); if (verbose != 0) { nvme_print_bit(4, "NVM Vendor-specific Command Format", nvme_vers_atleast(version, &nvme_vers_1v0), idctl->id_nvscc.nv_spec, "standard", "vendor-specific"); nvme_print(4, "Namespace Write Protection Capabilities", -1, NULL); nvme_print_bit(6, "Core Support", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_nwpc.nwpc_base, NULL, NULL); nvme_print_bit(6, "Write Protect Until Power Cycle", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_nwpc.nwpc_wpupc, NULL, NULL); nvme_print_bit(6, "Permanent Write Protect", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_nwpc.nwpc_permwp, NULL, NULL); } if (idctl->id_fuses.f_cmp_wr && nvme_vers_atleast(version, &nvme_vers_1v1)) { nvme_print_uint64(4, "Atomic Compare & Write Size", (uint32_t)idctl->id_acwu + 1, NULL, idctl->id_acwu == 0 ? " block" : " blocks"); } else { nvme_print_str(4, "Atomic Compare & Write Size", -1, "unsupported", 0); } nvme_print(4, "SGL Support", -1, NULL); switch (idctl->id_sgls.sgl_sup) { case NVME_SGL_UNSUP: nvme_print_str(6, "Command Set", -1, "unsupported", 0); break; case NVME_SGL_SUP_UNALIGN: nvme_print_str(6, "Command Set", -1, "supported, " "no restrictions", 0); break; case NVME_SGL_SUP_ALIGN: nvme_print_str(6, "Command Set", -1, "supported, " "alignment restrictions", 0); break; default: nvme_print(6, "Command Set", -1, "unknown reserved value: %u", idctl->id_sgls.sgl_sup); break; } nvme_print_bit(6, "Keyed SGL Block Descriptor", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_sgls.sgl_keyed, NULL, NULL); nvme_print_bit(6, "SGL Bit Bucket Descriptor", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_sgls.sgl_bucket, NULL, NULL); nvme_print_bit(6, "Byte Aligned Contiguous Metadata", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_sgls.sgl_balign, NULL, NULL); nvme_print_bit(6, "SGL Longer than Data Transferred", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_sgls.sgl_sglgtd, NULL, NULL); nvme_print_bit(6, "MPTR with SGL", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_sgls.sgl_mptr, NULL, NULL); nvme_print_bit(6, "SGL Address as Offset", nvme_vers_atleast(version, &nvme_vers_1v2), idctl->id_sgls.sgl_offset, NULL, NULL); nvme_print_bit(6, "Transport SGL Data Block", nvme_vers_atleast(version, &nvme_vers_1v4), idctl->id_sgls.sgl_tport, NULL, NULL); if (verbose) { if (idctl->id_mnan != 0) { nvme_print_uint64(4, "Maximum Number of Allowed " "Namespaces", idctl->id_mnan, NULL, NULL); } else { nvme_print(4, "Maximum Number of Allowed " "Namespaces", -1, "at most %u", idctl->id_nn); } } if (nvme_vers_atleast(version, &nvme_vers_1v2) && idctl->id_subnqn[0] != '\0') { nvme_print_str(4, "NVMe Subsystem Qualified Name", -1, (char *)idctl->id_subnqn, sizeof (idctl->id_subnqn)); } else { nvme_print_str(4, "NVMe Subsystem Qualified Name", -1, "unknown", 0); } for (i = 0; i != idctl->id_npss + 1; i++) { double scale = 0.01; double power = 0; int places = 2; char *unit = "W"; if (nvme_vers_atleast(version, &nvme_vers_1v1) && idctl->id_psd[i].psd_mps == 1) { scale = 0.0001; places = 4; } power = (double)idctl->id_psd[i].psd_mp * scale; if (power < 1.0) { power *= 1000.0; unit = "mW"; } nvme_print(4, "Power State Descriptor", i, NULL); nvme_print_double(6, "Maximum Power", power, places, unit); nvme_print_bit(6, "Non-Operational State", nvme_vers_atleast(version, &nvme_vers_1v1), idctl->id_psd[i].psd_nops, "yes", "no"); nvme_print_uint64(6, "Entry Latency", idctl->id_psd[i].psd_enlat, NULL, "us"); nvme_print_uint64(6, "Exit Latency", idctl->id_psd[i].psd_exlat, NULL, "us"); nvme_print_uint64(6, "Relative Read Throughput (0 = best)", idctl->id_psd[i].psd_rrt, NULL, NULL); nvme_print_uint64(6, "Relative Read Latency (0 = best)", idctl->id_psd[i].psd_rrl, NULL, NULL); nvme_print_uint64(6, "Relative Write Throughput (0 = best)", idctl->id_psd[i].psd_rwt, NULL, NULL); nvme_print_uint64(6, "Relative Write Latency (0 = best)", idctl->id_psd[i].psd_rwl, NULL, NULL); } } /* * nvme_print_identify_nsid * * This function pretty-prints the structure returned by the IDENTIFY NAMESPACE * command. */ void nvme_print_identify_nsid(const nvme_identify_nsid_t *idns, const nvme_version_t *version) { int bsize = 1 << idns->id_lbaf[idns->id_flbas.lba_format].lbaf_lbads; int i; nvme_print(0, "Identify Namespace", -1, NULL); nvme_print(2, "Namespace Capabilities and Features", -1, NULL); nvme_print_uint64(4, "Namespace Size", idns->id_nsize * bsize / 1024 / 1024, NULL, "MB"); nvme_print_uint64(4, "Namespace Capacity", idns->id_ncap * bsize / 1024 / 1024, NULL, "MB"); nvme_print_uint64(4, "Namespace Utilization", idns->id_nuse * bsize / 1024 / 1024, NULL, "MB"); nvme_print(4, "Namespace Features", -1, NULL); nvme_print_bit(6, "Thin Provisioning", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_nsfeat.f_thin, NULL, NULL); nvme_print_bit(6, "Namespace-specific Atomic Units", nvme_vers_atleast(version, &nvme_vers_1v2), idns->id_nsfeat.f_nsabp, NULL, NULL); nvme_print_bit(6, "Deallocate errors", nvme_vers_atleast(version, &nvme_vers_1v2), idns->id_nsfeat.f_dae, NULL, NULL); nvme_print_bit(6, "Namespace GUID Reuse", nvme_vers_atleast(version, &nvme_vers_1v2), idns->id_nsfeat.f_uidreuse, "impossible", "possible"); nvme_print_bit(6, "Namespace-specific I/O Optimized Sizes", nvme_vers_atleast(version, &nvme_vers_1v4), idns->id_nsfeat.f_optperf, NULL, NULL); nvme_print_uint64(4, "Number of LBA Formats", (uint16_t)idns->id_nlbaf + 1, NULL, NULL); nvme_print(4, "Formatted LBA Size", -1, NULL); nvme_print_uint64(6, "LBA Format", (uint16_t)idns->id_flbas.lba_format, NULL, NULL); nvme_print_bit(6, "Extended Data LBA", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_flbas.lba_extlba, "yes", "no"); nvme_print(4, "Metadata Capabilities", -1, NULL); nvme_print_bit(6, "Extended Data LBA", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_mc.mc_extlba, NULL, NULL); nvme_print_bit(6, "Separate Metadata", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_mc.mc_separate, NULL, NULL); nvme_print(4, "End-to-End Data Protection Capabilities", -1, NULL); nvme_print_bit(6, "Protection Information Type 1", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dpc.dp_type1, NULL, NULL); nvme_print_bit(6, "Protection Information Type 2", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dpc.dp_type2, NULL, NULL); nvme_print_bit(6, "Protection Information Type 3", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dpc.dp_type3, NULL, NULL); nvme_print_bit(6, "Protection Information first", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dpc.dp_first, NULL, NULL); nvme_print_bit(6, "Protection Information last", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dpc.dp_last, NULL, NULL); nvme_print(4, "End-to-End Data Protection Settings", -1, NULL); if (idns->id_dps.dp_pinfo == 0) { nvme_print_str(6, "Protection Information", -1, "disabled", 0); } else { nvme_print_uint64(6, "Protection Information Type", idns->id_dps.dp_pinfo, NULL, NULL); } nvme_print_bit(6, "Protection Information in Metadata", nvme_vers_atleast(version, &nvme_vers_1v0), idns->id_dps.dp_first, "first 8 bytes", "last 8 bytes"); nvme_print(4, "Namespace Multi-Path I/O and Namespace Sharing " "Capabilities", -1, NULL); nvme_print_bit(6, "Namespace is shared", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_nmic.nm_shared, "yes", "no"); nvme_print(2, "Reservation Capabilities", -1, NULL); nvme_print_bit(6, "Persist Through Power Loss", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_persist, NULL, NULL); nvme_print_bit(6, "Write Exclusive", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_wr_excl, NULL, NULL); nvme_print_bit(6, "Exclusive Access", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_excl, NULL, NULL); nvme_print_bit(6, "Write Exclusive - Registrants Only", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_wr_excl_r, NULL, NULL); nvme_print_bit(6, "Exclusive Access - Registrants Only", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_excl_r, NULL, NULL); nvme_print_bit(6, "Write Exclusive - All Registrants", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_wr_excl_a, NULL, NULL); nvme_print_bit(6, "Exclusive Access - All Registrants", nvme_vers_atleast(version, &nvme_vers_1v1), idns->id_rescap.rc_excl_a, NULL, NULL); nvme_print_bit(6, "Ignore Existing Key Behavior", nvme_vers_atleast(version, &nvme_vers_1v3), idns->id_rescap.rc_ign_ekey, "NVMe 1.3 behavior", "pre-NVMe 1.3"); if (idns->id_fpi.fpi_sup != 0) { nvme_print_uint64(4, "NVM Format Remaining", idns->id_fpi.fpi_remp, NULL, "%"); } else { nvme_print_str(4, "NVM Format Remaining", -1, "unsupported", 0); } if (verbose) { if (idns->id_nawun != 0) { nvme_print_uint64(4, "Namespace Atomic Write Unit " "Normal", idns->id_nawun + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Atomic Write Unit " "Normal", -1, "unspecified", 0); } if (idns->id_nawupf != 0) { nvme_print_uint64(4, "Namespace Atomic Write Unit " "Power Fail", idns->id_nawupf + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Atomic Write Unit " "Power Fail", -1, "unspecified", 0); } if (idns->id_nacwu != 0) { nvme_print_uint64(4, "Namespace Atomic Compare & Write " "Unit", idns->id_nacwu + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Atomic Compare & Write " "Unit", -1, "unspecified", 0); } if (idns->id_nabsn != 0) { nvme_print_uint64(4, "Namespace Atomic Boundary Size " "Normal", idns->id_nabsn + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Atomic Boundary Size " "Normal", -1, "unspecified", 0); } if (idns->id_nbao != 0) { nvme_print(4, "Namespace Atomic Boundary Offset", -1, "LBA %u", idns->id_nbao); } else { nvme_print_str(4, "Namespace Atomic Boundary Offset", -1, "unspecified", 0); } if (idns->id_nabspf != 0) { nvme_print_uint64(4, "Namespace Atomic Boundary Size " "Power Fail", idns->id_nabspf + 1, NULL, idns->id_nabspf == 0 ? " block" : " blocks"); } else { nvme_print_str(4, "Namespace Atomic Boundary Size " "Power Fail", -1, "unspecified", 0); } if (idns->id_noiob != 0) { nvme_print_uint64(4, "Namespace Optional I/O Boundary", idns->id_noiob, NULL, idns->id_noiob == 1 ? " block" : " blocks"); } else { nvme_print_str(4, "Namespace Optimal I/O Boundary", -1, "unspecified", 0); } } if (idns->id_nvmcap.lo != 0 || idns->id_nvmcap.hi != 0) { nvme_print_uint128(4, "NVM Capacity", idns->id_nvmcap, "B", 0, 0); } else { nvme_print_str(4, "NVM Capacity", -1, "unknown", 0); } if (verbose) { if (idns->id_npwg != 0) { nvme_print_uint64(4, "Namespace Preferred Write " "Granularity", idns->id_npwg + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Preferred Write " "Granularity", -1, "unspecified", 0); } if (idns->id_npwa != 0) { nvme_print_uint64(4, "Namespace Preferred Write " "Alignment", idns->id_npwa + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Preferred Write " "Alignment", -1, "unspecified", 0); } if (idns->id_npdg != 0) { nvme_print_uint64(4, "Namespace Preferred Deallocate " "Granularity", idns->id_npdg + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Preferred Deallocate " "Granularity", -1, "unspecified", 0); } if (idns->id_npda != 0) { nvme_print_uint64(4, "Namespace Preferred Deallocate " "Alignment", idns->id_npda + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Preferred Deallocate " "Alignment", -1, "unspecified", 0); } if (idns->id_nows != 0) { nvme_print_uint64(4, "Namespace Optimal Write Size", idns->id_nows + 1, NULL, " blocks"); } else { nvme_print_str(4, "Namespace Optimal Write Size", -1, "unspecified", 0); } if (idns->id_anagrpid != 0) { nvme_print_uint64(4, "Namespace ANA Group Identifier", idns->id_anagrpid, NULL, NULL); } else { nvme_print_str(4, "Namespace ANA Group Identifier", -1, "unsupported", 0); } } nvme_print(4, "Namespace Attributes", -1, NULL); nvme_print_bit(6, "Write Protected", nvme_vers_atleast(version, &nvme_vers_1v4), idns->id_nsattr.nsa_wprot, "yes", "no"); if (verbose) { if (idns->id_nvmsetid != 0) { nvme_print_uint64(4, "Namespace Set Identifier", idns->id_nvmsetid, NULL, NULL); } else { nvme_print_str(4, "Namespace Set Identifier", -1, "unsupported", 0); } if (idns->id_endgid != 0) { nvme_print_uint64(4, "Namespace Endurance Group " "Identifier", idns->id_endgid, NULL, NULL); } else { nvme_print_str(4, "Namespace Endurance Group " "Identifier", -1, "unsupported", 0); } } if (nvme_vers_atleast(version, &nvme_vers_1v2)) { uint8_t guid[16] = { 0 }; if (memcmp(guid, idns->id_nguid, sizeof (guid) != 0)) { nvme_print_guid(4, "Namespace GUID", idns->id_nguid); } else { nvme_print_str(4, "Namespace GUID", -1, "unsupported", 0); } } else { nvme_print_str(4, "Namespace GUID", -1, "unsupported", 0); } if (nvme_vers_atleast(version, &nvme_vers_1v1)) { uint8_t oui[8] = { 0 }; if (memcmp(oui, idns->id_eui64, sizeof (oui)) != 0) { nvme_print_eui64(4, "IEEE Extended Unique Identifier", idns->id_eui64); } else { nvme_print_str(4, "IEEE Extended Unique Identifier", -1, "unsupported", 0); } } else { nvme_print_str(4, "IEEE Extended Unique Identifier", -1, "unsupported", 0); } for (i = 0; i <= idns->id_nlbaf; i++) { if (verbose == 0 && idns->id_lbaf[i].lbaf_ms != 0) continue; nvme_print(4, "LBA Format", i, NULL); nvme_print_uint64(6, "Metadata Size", idns->id_lbaf[i].lbaf_ms, NULL, " bytes"); nvme_print_uint64(6, "LBA Data Size", 1 << idns->id_lbaf[i].lbaf_lbads, NULL, " bytes"); nvme_print_str(6, "Relative Performance", -1, lbaf_relative_performance[idns->id_lbaf[i].lbaf_rp], 0); } } /* * nvme_print_identify_nsid_list * * Print a NVMe Namespace List. */ void nvme_print_identify_nsid_list(const char *header, const nvme_identify_nsid_list_t *nslist) { uint32_t i; nvme_print(0, header, -1, NULL); /* * The namespace ID list is ordered, unused entries are 0. */ for (i = 0; i < ARRAY_SIZE(nslist->nl_nsid) && nslist->nl_nsid[i] != 0; i++) { nvme_print_uint64(2, "Namespace Identifier", nslist->nl_nsid[i], NULL, NULL); } } /* * nvme_print_identify_nsid_desc * * Print a NVMe Namespace Identifier Descriptor list. */ void nvme_print_identify_nsid_desc(void *nsdesc) { const nvme_identify_nsid_desc_t *desc = nsdesc; int i = 0; uintptr_t ptr, end; nvme_print(0, "Namespace Identification Descriptors", -1, NULL); for (ptr = (uintptr_t)desc, end = ptr + NVME_IDENTIFY_BUFSIZE; desc->nd_nidl != 0 && ptr + desc->nd_nidl + 4 <= end; desc = (nvme_identify_nsid_desc_t *)(ptr += desc->nd_nidl + 4)) { const char *nidt; if (desc->nd_nidt >= ARRAY_SIZE(ns_identifier_type)) nidt = "Reserved"; else nidt = ns_identifier_type[desc->nd_nidt]; nvme_print(2, "Namespace Identifier Descriptor", i++, NULL); nvme_print_str(4, "Namespace Identifier Type", -1, nidt, 0); nvme_print_uint64(4, "Namespace Identifier Length", desc->nd_nidl, NULL, NULL); if (desc->nd_nidt == NVME_NSID_DESC_EUI64 && desc->nd_nidl == NVME_NSID_DESC_LEN_EUI64) { nvme_print_eui64(4, "IEEE Extended Unique Identifier", desc->nd_nid); } else if (desc->nd_nidt == NVME_NSID_DESC_NGUID && desc->nd_nidl == NVME_NSID_DESC_LEN_NGUID) { nvme_print_guid(4, "Namespace GUID", desc->nd_nid); } else if (desc->nd_nidt == NVME_NSID_DESC_NUUID && desc->nd_nidl == NVME_NSID_DESC_LEN_NUUID) { nvme_print_uuid(4, "Namespace UUID", desc->nd_nid); } else if (desc->nd_nidt == NVME_NSID_DESC_CSI && desc->nd_nidl == NVME_NSID_DESC_LEN_CSI) { nvme_print_uint64(4, "CSI", desc->nd_nid[0], NULL, NULL); } else if (desc->nd_nidt < NVME_NSID_DESC_MIN || desc->nd_nidt > NVME_NSID_DESC_MAX) { nvme_print_hexbuf(4, "Raw Bytes", desc->nd_nid, desc->nd_nidl); } else { nvme_print_hexbuf(4, "Raw Bytes (Invalid Descriptor Length)", desc->nd_nid, desc->nd_nidl); } } } /* * nvme_print_identify_ctrl_list * * Print a NVMe Controller List. */ void nvme_print_identify_ctrl_list(const char *header, const nvme_identify_ctrl_list_t *ctlist) { int i; nvme_print(0, header, -1, NULL); for (i = 0; i != ctlist->cl_nid; i++) { nvme_print_uint64(2, "Controller Identifier", ctlist->cl_ctlid[i], NULL, NULL); } } /* * nvme_print_error_log * * This function pretty-prints all non-zero error log entries, or all entries * if verbose is set. */ void nvme_print_error_log(int nlog, const nvme_error_log_entry_t *elog, const nvme_version_t *version) { int i; nvme_print(0, "Error Log", -1, NULL); for (i = 0; i != nlog; i++) if (elog[i].el_count == 0) break; nvme_print_uint64(2, "Number of Error Log Entries", i, NULL, NULL); for (i = 0; i != nlog; i++) { int sc = elog[i].el_sf.sf_sc; const char *sc_str = "Unknown"; if (elog[i].el_count == 0 && verbose == 0) break; switch (elog[i].el_sf.sf_sct) { case 0: /* Generic Command Status */ if (sc < ARRAY_SIZE(generic_status_codes)) { sc_str = generic_status_codes[sc]; } else if (sc >= 0x80 && sc - 0x80 < ARRAY_SIZE(generic_nvm_status_codes)) { sc_str = generic_nvm_status_codes[sc - 0x80]; } break; case 1: /* Specific Command Status */ if (sc < ARRAY_SIZE(specific_status_codes)) { sc_str = specific_status_codes[sc]; } else if (sc >= 0x80 && sc - 0x80 < ARRAY_SIZE(specific_nvm_status_codes)) { sc_str = specific_nvm_status_codes[sc - 0x80]; } break; case 2: /* Media Errors */ if (sc >= 0x80 && sc - 0x80 < ARRAY_SIZE(media_nvm_status_codes)) { sc_str = media_nvm_status_codes[sc - 0x80]; } break; case 3: /* Path Related Status */ if (sc < ARRAY_SIZE(path_status_codes)) { sc_str = path_status_codes[sc]; } else if (sc >= 0x60 && sc - 0x60 < ARRAY_SIZE(path_controller_codes)) { sc_str = path_controller_codes[sc - 0x60]; } else if (sc >= 0x70 && sc - 0x70 < ARRAY_SIZE(path_host_codes)) { sc_str = path_host_codes[sc - 0x70]; } break; case 7: /* Vendor Specific */ sc_str = "Unknown Vendor Specific"; break; default: sc_str = "Reserved"; break; } nvme_print(2, "Entry", i, NULL); nvme_print_uint64(4, "Error Count", elog[i].el_count, NULL, NULL); nvme_print_uint64(4, "Submission Queue ID", elog[i].el_sqid, NULL, NULL); nvme_print_uint64(4, "Command ID", elog[i].el_cid, NULL, NULL); nvme_print(4, "Status Field", -1, NULL); nvme_print_uint64(6, "Phase Tag", elog[i].el_sf.sf_p, NULL, NULL); nvme_print(6, "Status Code", -1, "0x%0.2x (%s)", sc, sc_str); nvme_print(6, "Status Code Type", -1, "0x%x (%s)", elog[i].el_sf.sf_sct, status_code_types[elog[i].el_sf.sf_sct]); nvme_print_bit(6, "More", nvme_vers_atleast(version, &nvme_vers_1v0), elog[i].el_sf.sf_m, "yes", "no"); nvme_print_bit(6, "Do Not Retry", nvme_vers_atleast(version, &nvme_vers_1v0), elog[i].el_sf.sf_m, "yes", "no"); nvme_print_uint64(4, "Parameter Error Location byte", elog[i].el_byte, "0x%0.2"PRIx64, NULL); nvme_print_uint64(4, "Parameter Error Location bit", elog[i].el_bit, NULL, NULL); nvme_print_uint64(4, "Logical Block Address", elog[i].el_lba, NULL, NULL); nvme_print(4, "Namespace ID", -1, "%d", elog[i].el_nsid == 0xffffffff ? 0 : elog[i].el_nsid); nvme_print_uint64(4, "Vendor Specific Information Available", elog[i].el_vendor, NULL, NULL); } } /* * nvme_print_health_log * * This function pretty-prints a summary of the SMART/Health log, or all * of the log if verbose is set. */ void nvme_print_health_log(const nvme_health_log_t *hlog, const nvme_identify_ctrl_t *idctl, const nvme_version_t *version) { nvme_print(0, "SMART/Health Information", -1, NULL); nvme_print(2, "Critical Warnings", -1, NULL); nvme_print_bit(4, "Available Space", nvme_vers_atleast(version, &nvme_vers_1v0), hlog->hl_crit_warn.cw_avail, "low", "OK"); nvme_print_bit(4, "Temperature", nvme_vers_atleast(version, &nvme_vers_1v0), hlog->hl_crit_warn.cw_temp, "too high", "OK"); nvme_print_bit(4, "Device Reliability", nvme_vers_atleast(version, &nvme_vers_1v0), hlog->hl_crit_warn.cw_reliab, "degraded", "OK"); nvme_print_bit(4, "Media", nvme_vers_atleast(version, &nvme_vers_1v0), hlog->hl_crit_warn.cw_readonly, "read-only", "OK"); if (idctl->id_vwc.vwc_present != 0) nvme_print_bit(4, "Volatile Memory Backup", nvme_vers_atleast(version, &nvme_vers_1v0), hlog->hl_crit_warn.cw_volatile, "failed", "OK"); nvme_print_temp(2, "Temperature", hlog->hl_temp); nvme_print_uint64(2, "Available Spare Capacity", hlog->hl_avail_spare, NULL, "%"); if (verbose != 0) nvme_print_uint64(2, "Available Spare Threshold", hlog->hl_avail_spare_thr, NULL, "%"); nvme_print_uint64(2, "Device Life Used", hlog->hl_used, NULL, "%"); if (verbose == 0) return; /* * The following two fields are in 1000 512 byte units. Convert that to * GB by doing binary shifts (9 left and 30 right) and multiply by 10^3. */ nvme_print_uint128(2, "Data Read", hlog->hl_data_read, "GB", 30 - 9, 3); nvme_print_uint128(2, "Data Written", hlog->hl_data_write, "GB", 30 - 9, 3); nvme_print_uint128(2, "Read Commands", hlog->hl_host_read, NULL, 0, 0); nvme_print_uint128(2, "Write Commands", hlog->hl_host_write, NULL, 0, 0); nvme_print_uint128(2, "Controller Busy", hlog->hl_ctrl_busy, "min", 0, 0); nvme_print_uint128(2, "Power Cycles", hlog->hl_power_cycles, NULL, 0, 0); nvme_print_uint128(2, "Power On", hlog->hl_power_on_hours, "h", 0, 0); nvme_print_uint128(2, "Unsafe Shutdowns", hlog->hl_unsafe_shutdn, NULL, 0, 0); nvme_print_uint128(2, "Uncorrectable Media Errors", hlog->hl_media_errors, NULL, 0, 0); nvme_print_uint128(2, "Errors Logged", hlog->hl_errors_logged, NULL, 0, 0); if (!nvme_vers_atleast(version, &nvme_vers_1v2)) { return; } if (idctl->ap_wctemp != 0) { nvme_print_uint64(2, "Warning Composite Temperature Time", hlog->hl_warn_temp_time, NULL, "min"); } if (idctl->ap_cctemp != 0) { nvme_print_uint64(2, "Critical Composite Temperature Time", hlog->hl_crit_temp_time, NULL, "min"); } if (hlog->hl_temp_sensor_1 != 0) { nvme_print_temp(2, "Temperature Sensor 1", hlog->hl_temp_sensor_1); } if (hlog->hl_temp_sensor_2 != 0) { nvme_print_temp(2, "Temperature Sensor 2", hlog->hl_temp_sensor_2); } if (hlog->hl_temp_sensor_3 != 0) { nvme_print_temp(2, "Temperature Sensor 3", hlog->hl_temp_sensor_3); } if (hlog->hl_temp_sensor_4 != 0) { nvme_print_temp(2, "Temperature Sensor 4", hlog->hl_temp_sensor_4); } if (hlog->hl_temp_sensor_5 != 0) { nvme_print_temp(2, "Temperature Sensor 5", hlog->hl_temp_sensor_5); } if (hlog->hl_temp_sensor_6 != 0) { nvme_print_temp(2, "Temperature Sensor 6", hlog->hl_temp_sensor_6); } if (hlog->hl_temp_sensor_7 != 0) { nvme_print_temp(2, "Temperature Sensor 7", hlog->hl_temp_sensor_7); } if (hlog->hl_temp_sensor_8 != 0) { nvme_print_temp(2, "Temperature Sensor 8", hlog->hl_temp_sensor_8); } if (!nvme_vers_atleast(version, &nvme_vers_1v3)) { return; } nvme_print_uint64(2, "Thermal Management Temp 1 Transition Count", hlog->hl_tmtemp_1_tc, NULL, NULL); nvme_print_uint64(2, "Thermal Management Temp 2 Transition Count", hlog->hl_tmtemp_2_tc, NULL, NULL); nvme_print_uint64(2, "Time for Thermal Management Temp 1", hlog->hl_tmtemp_1_time, NULL, "sec"); nvme_print_uint64(2, "Time for Thermal Management Temp 2", hlog->hl_tmtemp_2_time, NULL, "sec"); } /* * nvme_print_fwslot_log * * This function pretty-prints the firmware slot information. */ void nvme_print_fwslot_log(const nvme_fwslot_log_t *fwlog, const nvme_identify_ctrl_t *idctl) { int i; char str[NVME_FWVER_SZ + sizeof (" (read-only)")]; nvme_print(0, "Firmware Slot Information", -1, NULL); nvme_print_uint64(2, "Active Firmware Slot", fwlog->fw_afi, NULL, NULL); if (fwlog->fw_next != 0) nvme_print_uint64(2, "Next Firmware Slot", fwlog->fw_next, NULL, NULL); (void) snprintf(str, sizeof (str), "%.*s%s", nvme_strlen(fwlog->fw_frs[0], sizeof (fwlog->fw_frs[0])), fwlog->fw_frs[0], idctl->id_frmw.fw_readonly ? " (read-only)" : ""); nvme_print_str(2, "Firmware Revision for Slot", 1, str, sizeof (str)); for (i = 1; i < idctl->id_frmw.fw_nslot; i++) { nvme_print_str(2, "Firmware Revision for Slot", i + 1, fwlog->fw_frs[i][0] == '\0' ? "" : fwlog->fw_frs[i], sizeof (fwlog->fw_frs[i])); } } /* * nvme_print_feat_* * * These functions pretty-print the data structures returned by GET FEATURES. */ void nvme_print_feat_unknown(nvme_feat_output_t output, uint32_t cdw0, void *b, size_t s) { if ((output & NVME_FEAT_OUTPUT_CDW0) != 0) { nvme_print_uint64(4, "cdw0", cdw0, "0x%"PRIx64, NULL); } if ((output & NVME_FEAT_OUTPUT_DATA) != 0) { nvme_print_hexbuf(4, "data", b, s); } } void nvme_print_feat_arbitration(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_arbitration_t arb; arb.r = cdw0; if (arb.b.arb_ab != 7) nvme_print_uint64(4, "Arbitration Burst", 1 << arb.b.arb_ab, NULL, NULL); else nvme_print_str(4, "Arbitration Burst", 0, "no limit", 0); nvme_print_uint64(4, "Low Priority Weight", (uint16_t)arb.b.arb_lpw + 1, NULL, NULL); nvme_print_uint64(4, "Medium Priority Weight", (uint16_t)arb.b.arb_mpw + 1, NULL, NULL); nvme_print_uint64(4, "High Priority Weight", (uint16_t)arb.b.arb_hpw + 1, NULL, NULL); } void nvme_print_feat_power_mgmt(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_power_mgmt_t pm; pm.r = cdw0; nvme_print_uint64(4, "Power State", (uint8_t)pm.b.pm_ps, NULL, NULL); } void nvme_print_feat_lba_range(uint32_t cdw0, void *buf, size_t bufsize, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(id)); nvme_lba_range_type_t lrt; nvme_lba_range_t *lr; size_t n_lr; int i; if (buf == NULL) return; lrt.r = cdw0; lr = buf; n_lr = bufsize / sizeof (nvme_lba_range_t); if (n_lr > lrt.b.lr_num + 1) n_lr = lrt.b.lr_num + 1; nvme_print_uint64(4, "Number of LBA Ranges", (uint8_t)lrt.b.lr_num + 1, NULL, NULL); for (i = 0; i != n_lr; i++) { if (verbose == 0 && lr[i].lr_nlb == 0) continue; nvme_print(4, "LBA Range", i, NULL); if (lr[i].lr_type < ARRAY_SIZE(lba_range_types)) nvme_print_str(6, "Type", -1, lba_range_types[lr[i].lr_type], 0); else nvme_print_uint64(6, "Type", lr[i].lr_type, NULL, NULL); nvme_print(6, "Attributes", -1, NULL); nvme_print_bit(8, "Writable", nvme_vers_atleast(version, &nvme_vers_1v0), lr[i].lr_attr.lr_write, "yes", "no"); nvme_print_bit(8, "Hidden", nvme_vers_atleast(version, &nvme_vers_1v0), lr[i].lr_attr.lr_hidden, "yes", "no"); nvme_print_uint64(6, "Starting LBA", lr[i].lr_slba, NULL, NULL); nvme_print_uint64(6, "Number of Logical Blocks", lr[i].lr_nlb, NULL, NULL); nvme_print(6, "Unique Identifier", -1, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x" "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", lr[i].lr_guid[0], lr[i].lr_guid[1], lr[i].lr_guid[2], lr[i].lr_guid[3], lr[i].lr_guid[4], lr[i].lr_guid[5], lr[i].lr_guid[6], lr[i].lr_guid[7], lr[i].lr_guid[8], lr[i].lr_guid[9], lr[i].lr_guid[10], lr[i].lr_guid[11], lr[i].lr_guid[12], lr[i].lr_guid[13], lr[i].lr_guid[14], lr[i].lr_guid[15]); } } void nvme_print_feat_temperature(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_temp_threshold_t tt; char *label = b; tt.r = cdw0; nvme_print_temp(4, label, tt.b.tt_tmpth); } void nvme_print_feat_error(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_error_recovery_t er; er.r = cdw0; if (er.b.er_tler > 0) nvme_print_uint64(4, "Time Limited Error Recovery", (uint32_t)er.b.er_tler * 100, NULL, "ms"); else nvme_print_str(4, "Time Limited Error Recovery", -1, "no time limit", 0); } void nvme_print_feat_write_cache(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_write_cache_t wc; wc.r = cdw0; nvme_print_bit(4, "Volatile Write Cache", nvme_vers_atleast(version, &nvme_vers_1v0), wc.b.wc_wce, "enabled", "disabled"); } void nvme_print_feat_nqueues(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_nqueues_t nq; nq.r = cdw0; nvme_print_uint64(4, "Number of Submission Queues", nq.b.nq_nsq + 1, NULL, NULL); nvme_print_uint64(4, "Number of Completion Queues", nq.b.nq_ncq + 1, NULL, NULL); } void nvme_print_feat_intr_coal(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_intr_coal_t ic; ic.r = cdw0; nvme_print_uint64(4, "Aggregation Threshold", ic.b.ic_thr + 1, NULL, NULL); nvme_print_uint64(4, "Aggregation Time", (uint16_t)ic.b.ic_time * 100, NULL, "us"); } void nvme_print_feat_intr_vect(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_intr_vect_t iv; char *tmp; iv.r = cdw0; if (asprintf(&tmp, "Vector %d Coalescing Disable", iv.b.iv_iv) < 0) err(-1, "nvme_print_feat_common()"); nvme_print_bit(4, tmp, iv.b.iv_cd, nvme_vers_atleast(version, &nvme_vers_1v0), "yes", "no"); } void nvme_print_feat_write_atom(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_write_atomicity_t wa; wa.r = cdw0; nvme_print_bit(4, "Disable Normal", wa.b.wa_dn, nvme_vers_atleast(version, &nvme_vers_1v0), "yes", "no"); } void nvme_print_feat_async_event(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *idctl, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); nvme_async_event_conf_t aec; aec.r = cdw0; nvme_print_bit(4, "Available Space below threshold", nvme_vers_atleast(version, &nvme_vers_1v0), aec.b.aec_avail, "enabled", "disabled"); nvme_print_bit(4, "Temperature above threshold", nvme_vers_atleast(version, &nvme_vers_1v0), aec.b.aec_temp, "enabled", "disabled"); nvme_print_bit(4, "Device Reliability compromised", nvme_vers_atleast(version, &nvme_vers_1v0), aec.b.aec_reliab, "enabled", "disabled"); nvme_print_bit(4, "Media read-only", nvme_vers_atleast(version, &nvme_vers_1v0), aec.b.aec_readonly, "enabled", "disabled"); if (idctl->id_vwc.vwc_present != 0) { nvme_print_bit(4, "Volatile Memory Backup failed", nvme_vers_atleast(version, &nvme_vers_1v0), aec.b.aec_volatile, "enabled", "disabled"); } /* NVMe 1.2 */ nvme_print_bit(4, "Namespace attribute notices", nvme_vers_atleast(version, &nvme_vers_1v2), aec.b.aec_nsan, "enabled", "disabled"); nvme_print_bit(4, "Firmware activation notices", nvme_vers_atleast(version, &nvme_vers_1v2), aec.b.aec_fwact, "enabled", "disabled"); /* NVMe 1.3 */ nvme_print_bit(4, "Telemetry log notices", nvme_vers_atleast(version, &nvme_vers_1v3), aec.b.aec_telln, "enabled", "disabled"); /* NVMe 1.4 */ nvme_print_bit(4, "ANA change notices", nvme_vers_atleast(version, &nvme_vers_1v4), aec.b.aec_ansacn, "enabled", "disabled"); nvme_print_bit(4, "Predictable latency event aggr. LCNs", nvme_vers_atleast(version, &nvme_vers_1v4), aec.b.aec_plat, "enabled", "disabled"); nvme_print_bit(4, "LBA status information notices", nvme_vers_atleast(version, &nvme_vers_1v4), aec.b.aec_lbasi, "enabled", "disabled"); nvme_print_bit(4, "Endurance group event aggregate LCNs", nvme_vers_atleast(version, &nvme_vers_1v4), aec.b.aec_egeal, "enabled", "disabled"); } void nvme_print_feat_auto_pst(uint32_t cdw0, void *buf, size_t bufsize, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(id)); nvme_auto_power_state_trans_t apst; nvme_auto_power_state_t *aps; int i; int cnt = bufsize / sizeof (nvme_auto_power_state_t); if (buf == NULL) return; apst.r = cdw0; aps = buf; nvme_print_bit(4, "Autonomous Power State Transition", nvme_vers_atleast(version, &nvme_vers_1v0), apst.b.apst_apste, "enabled", "disabled"); for (i = 0; i != cnt; i++) { if (aps[i].apst_itps == 0 && aps[i].apst_itpt == 0) break; nvme_print(4, "Power State", i, NULL); nvme_print_uint64(6, "Idle Transition Power State", (uint16_t)aps[i].apst_itps, NULL, NULL); nvme_print_uint64(6, "Idle Time Prior to Transition", aps[i].apst_itpt, NULL, "ms"); } } void nvme_print_feat_progress(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { _NOTE(ARGUNUSED(b)); _NOTE(ARGUNUSED(s)); _NOTE(ARGUNUSED(id)); nvme_software_progress_marker_t spm; spm.r = cdw0; nvme_print_uint64(4, "Pre-Boot Software Load Count", spm.b.spm_pbslc, NULL, NULL); } void nvme_print_feat_host_behavior(uint32_t cdw0, void *b, size_t s, const nvme_identify_ctrl_t *id, const nvme_version_t *version) { const nvme_host_behavior_t *hb = b; nvme_print_bit(4, "Advanced Command Retry", nvme_vers_atleast(version, &nvme_vers_1v4), hb->nhb_acre, "enabled", "disabled"); nvme_print_bit(4, "Extended Telemetry Data Area 4", nvme_vers_atleast(version, &nvme_vers_2v0), hb->nhb_etdas, "enabled", "disabled"); nvme_print_bit(4, "LBA Format Extension", nvme_vers_atleast(version, &nvme_vers_2v0), hb->nhb_lbafee, "enabled", "disabled"); nvme_print_bit(4, "Host Dispersed Namespace Support", nvme_vers_atleast(version, &nvme_vers_2v1), hb->nhb_lbafee, "enabled", "disabled"); nvme_print(4, "Copy Descriptor Formats", -1, NULL); nvme_print_bit(6, "Copy Descriptor 2", nvme_vers_atleast(version, &nvme_vers_2v1), hb->nhb_cdfe & (1 << 2), "enabled", "disabled"); nvme_print_bit(6, "Copy Descriptor 3", nvme_vers_atleast(version, &nvme_vers_2v1), hb->nhb_cdfe & (1 << 3), "enabled", "disabled"); nvme_print_bit(6, "Copy Descriptor 4", nvme_vers_atleast(version, &nvme_vers_2v1), hb->nhb_cdfe & (1 << 4), "enabled", "disabled"); } /* * This is designed to print out a large buffer as decipherable hexadecimal. * This is intended for log pages or command output where there is unknown * printing. For an inline hex buffer, see nvme_print_hexbuf(). */ void nvmeadm_dump_hex(const uint8_t *buf, size_t len) { (void) hexdump_file(buf, len, HDF_DEFAULT, stdout); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Sandisk vendor-specific commands. These generally start with the x6x * generation of controllers. For prior generations (e.g. x4x and x5x), see * nvmeadm_wdc.c. */ #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" typedef struct { uint8_t se_lane; const char *se_output; } sandisk_eye_t; void usage_sandisk_hwrev(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Print device hardware revision\n", c_name); } int do_sandisk_hwrev(const nvme_process_arg_t *npa) { uint8_t major, minor; nvme_vuc_disc_t *vuc; if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); if (!nvme_sndk_hw_rev(npa->npa_ctrl, &major, &minor)) { nvmeadm_fatal(npa, "failed to retrieve hardware revision"); } (void) printf("%u.%u\n", major, minor); nvmeadm_vuc_fini(npa, vuc); return (0); } void usage_sandisk_pcieye(const char *c_name) { (void) fprintf(stderr, "%s -l lane -o output \n\n" " Write PCIe eye data from the specified lane to output file.\n", c_name); } void optparse_sandisk_pcieye(nvme_process_arg_t *npa) { int c; sandisk_eye_t *eye; if ((eye = calloc(1, sizeof (sandisk_eye_t))) == NULL) { err(-1, "failed to allocate memory for pci-eye options " "structure"); } eye->se_lane = UINT8_MAX; while ((c = getopt(npa->npa_argc, npa->npa_argv, ":l:o:")) != -1) { const char *errstr; switch (c) { case 'l': eye->se_lane = (uint8_t)strtonumx(optarg, 0, 3, &errstr, 0); if (errstr != NULL) { errx(-1, "invalid lane specified, valid values " "are 0-3: %s is %s", optarg, errstr); } break; case 'o': eye->se_output = optarg; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (eye->se_lane == UINT8_MAX) { errx(-1, "missing required PCIe lane (0-3), specify with -l"); } if (eye->se_output == NULL) { errx(-1, "missing required output file, specify with -o"); } npa->npa_cmd_arg = eye; } int do_sandisk_pcieye(const nvme_process_arg_t *npa) { const sandisk_eye_t *eye = npa->npa_cmd_arg; nvme_vuc_disc_t *vuc; uint8_t *buf; int ofd; if ((buf = calloc(WDC_SN861_VUC_EYE_LEN, sizeof (uint8_t))) == NULL) { err(-1, "failed to allocate eye diagram buffer"); } vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); ofd = open(eye->se_output, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (ofd < 0) { err(-1, "failed to open output file %s", eye->se_output); } if (!nvme_sndk_pci_eye(npa->npa_ctrl, eye->se_lane, buf, WDC_SN861_VUC_EYE_LEN)) { nvmeadm_fatal(npa, "failed to retrieve PCIe eye information"); } size_t off = 0, len = WDC_SN861_VUC_EYE_LEN; while (len > 0) { size_t towrite = MIN(len, 32 * 1024); ssize_t ret = write(ofd, buf + off, towrite); if (ret < 0) { err(-1, "failed to write eye data to output file"); } off += (size_t)ret; len -= (size_t)ret; } nvmeadm_vuc_fini(npa, vuc); VERIFY0(close(ofd)); free(buf); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * Deal with Solidigm-specific logs. */ #include #include #include #include "nvmeadm.h" #define SOLIDIGM_F_POWER(f) \ .nf_off = offsetof(solidigm_vul_p5x2x_power_t, pow_##f), \ .nf_len = sizeof (((solidigm_vul_p5x2x_power_t *)NULL)->pow_##f) static const nvmeadm_field_t solidigm_vul_power_fields[] = { { SOLIDIGM_F_POWER(vin1), .nf_short = "pin1", .nf_desc = "Voltage Rail 1 Power", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "uW" } }, { SOLIDIGM_F_POWER(vin2), .nf_short = "pin2", .nf_desc = "Voltage Rail 2 Power", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "uW" } } }; const nvmeadm_log_field_info_t solidigm_vul_power_field_info = { .nlfi_log = "solidigm/power", .nlfi_fields = solidigm_vul_power_fields, .nlfi_nfields = ARRAY_SIZE(solidigm_vul_power_fields), .nlfi_min = sizeof (solidigm_vul_p5x2x_power_t) }; #define SOLIDIGM_F_TEMP(f) \ .nf_off = offsetof(solidigm_vul_temp_t, temp_##f), \ .nf_len = sizeof (((solidigm_vul_temp_t *)NULL)->temp_##f) static const nvmeadm_field_t solidigm_vul_temp_fields[] = { { SOLIDIGM_F_TEMP(cur), .nf_short = "cur", .nf_desc = "Current Internal Temperature", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } }, { SOLIDIGM_F_TEMP(over_last), .nf_short = "otlpow", .nf_desc = "SSD Overtemp Shutdown flag for Last Power On", .nf_type = NVMEADM_FT_HEX }, { SOLIDIGM_F_TEMP(over_life), .nf_short = "otlife", .nf_desc = "SSD Overtemp Shutdown flag for Lifetime", .nf_type = NVMEADM_FT_HEX }, { SOLIDIGM_F_TEMP(comp_life_high), .nf_short = "lifehigh", .nf_desc = "Highest (Lifetime) Composite Temperature", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } }, { SOLIDIGM_F_TEMP(comp_life_low), .nf_short = "lifelow", .nf_desc = "Lowest (Lifetime) Composite Temperature", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } }, { SOLIDIGM_F_TEMP(norm_max_warn), .nf_short = "maxnorm", .nf_desc = "Max Warning Normalized Threshold", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } }, { SOLIDIGM_F_TEMP(spec_min_op), .nf_short = "minorm", .nf_desc = "Specified Minimum Operating Temp", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } }, { SOLIDIGM_F_TEMP(est_off), .nf_short = "estoff", .nf_desc = "Estimated Offset", .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "C" } } }; const nvmeadm_log_field_info_t solidigm_vul_temp_field_info = { .nlfi_log = "solidigm/temp", .nlfi_fields = solidigm_vul_temp_fields, .nlfi_nfields = ARRAY_SIZE(solidigm_vul_temp_fields), .nlfi_min = sizeof (solidigm_vul_temp_t) }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Logic to fetch and save an instance of the telemetry log page. The telemetry * log page consists of a 512-byte header followed by a number of data blocks. * The number of data blocks is indicated by the controller. * * Telemetry may either be host-initiated or device-initiated. When the * telemetry is host-initiated, the host specifies when to create the telemetry * using a flag in the log-specific parameter field (lsp). Whenever this is a 1, * then this data is created again. When telemetry is device-initiated, which * uses a different log page, then the data persists as long as the retain async * event flag is specified. * * In the telemetry header there are two things that we need to pay attention * to: * * 1. There are up to four indicators for the number of telemetry blocks that * could exist. These are meant to be indicators of short, medium, and long. * The 4th one requires the kernel to opt into it with a specific set * features command. We basically always try to get the largest amount that * exists in the header. * * 2. There are a series of generation numbers that exist. We need to ensure * that these generation numbers are the same across everything that we find. * So basically we read this initially at the start and then read it again at * the end. If the values differ, then we throw an error or would otherwise * have to start over. There are separate generation numbers for * host-initiated and controller-initiated telemetry. * * The telemetry file may have a large number of blocks so we split this up into * multiple requests of up to 1 MiB (our default maximum size). We do not assume * that we will get the log all in one go. As such, we also will not assume that * we can buffer the entire log page in memory and will always write it out. * This means that the user will be required to use the output file option. */ #include #include #include #include #include #include #include "nvmeadm.h" /* * We use a 64 KiB buffer here as that's usually within a device's maximum * payload. */ #define TELEM_BLKSIZE (64 * 1024) static void telemetry_read(const nvme_process_arg_t *npa, nvme_log_req_t *req, void *buf, size_t len, uint64_t off) { if (!nvme_log_req_set_output(req, buf, len)) { nvmeadm_fatal(npa, "failed to set output buffer"); } if (!nvme_log_req_set_offset(req, off)) { nvmeadm_fatal(npa, "failed to set offset to 0x%lx", off); } if (!nvme_log_req_exec(req)) { nvmeadm_fatal(npa, "failed to read %zu bytes at 0x%lx", len, off); } } static void telemetry_write(int ofd, const void *buf, size_t len) { size_t off = 0; while (len > 0) { ssize_t ret = write(ofd, buf + off, len - off); if (ret < 0) { err(EXIT_FAILURE, "failed to write to log telemetry " "output file"); } off += (size_t)ret; len -= (size_t)ret; } } int do_get_logpage_telemetry(const nvme_process_arg_t *npa, const nvme_log_disc_t *disc, nvme_log_req_t *req) { int ofd; const nvmeadm_get_logpage_t *log = npa->npa_cmd_arg; void *buf; nvme_telemetry_log_t hdr; uint64_t len; if (log->ngl_output == NULL) { errx(-1, "log page %s requires specifying an output file", nvme_log_disc_name(disc)); } ofd = open(log->ngl_output, O_WRONLY | O_TRUNC | O_CREAT, 0644); if (ofd < 0) { err(-1, "failed to create output file %s", log->ngl_output); } buf = calloc(TELEM_BLKSIZE, sizeof (uint8_t)); if (buf == NULL) { err(-1, "failed to allocate %u bytes for interim data buffer", TELEM_BLKSIZE); } /* * First create a new request and read the first 512-bytes. */ if (!nvme_log_req_set_lsp(req, NVME_TELMCTRL_LSP_CTHID)) { nvmeadm_fatal(npa, "failed to set lsp to create host " "telemetry"); } telemetry_read(npa, req, &hdr, sizeof (hdr), 0); telemetry_write(ofd, &hdr, sizeof (hdr)); /* * Clear the request to create telemetry for the rest of our operation. */ if (!nvme_log_req_set_lsp(req, 0)) { nvmeadm_fatal(npa, "failed to set lsp to create host " "telemetry"); } if (!nvme_log_disc_calc_size(disc, &len, &hdr, sizeof (hdr))) { errx(-1, "failed to determine full %s log length", npa->npa_argv[0]); } size_t off = sizeof (hdr); while (off < len) { size_t to_read = MIN(len - off, TELEM_BLKSIZE); telemetry_read(npa, req, buf, to_read, off); telemetry_write(ofd, buf, to_read); off += to_read; } telemetry_read(npa, req, buf, sizeof (hdr), 0); const nvme_telemetry_log_t *final = (const nvme_telemetry_log_t *)buf; if (hdr.ntl_thdgn != final->ntl_thdgn) { errx(-1, "log telemetry generation changed: originally was " "0x%x, ended with 0x%x", hdr.ntl_thdgn, final->ntl_thdgn); } free(buf); (void) close(ofd); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * NVMe Vendor Unique Command related functions. */ #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" /* * We choose 60 seconds as a reasonable enough default VUC timeout. This is a * fairly arbitrary selection but should be good enough for most non-formatting * related commands. */ #define NVMEADM_VUC_TO_DEFAULT 60 /* * We need some upper bound on how much data we'll read in and zero. The kernel * may change its maximum that it'll allow. It'd probably be smart of us to ask * what the max is to help reduce hardcoding. For now we use 2x its current * value 32 MiB. We make the minimum 4 bytes because we need 4 byte alignment. */ #define NVMEADM_VUC_LEN_MAX (32 * 1024 * 1024) #define NVMEADM_VUC_LEN_ALIGN 4 typedef struct nvmeadm_vuc { uint8_t vuc_opc; uint32_t vuc_nsid; uint32_t vuc_cdw12; uint32_t vuc_cdw13; uint32_t vuc_cdw14; uint32_t vuc_cdw15; uint32_t vuc_timeout; uint32_t vuc_dlen; const char *vuc_input; const char *vuc_output; nvme_lock_level_t vuc_lock; nvme_vuc_disc_impact_t vuc_impact; } nvmeadm_vuc_t; nvme_vuc_disc_t * nvmeadm_vuc_init(const nvme_process_arg_t *npa, const char *name) { nvme_vuc_disc_t *vuc; nvme_vuc_disc_lock_t lock; if (!nvme_vuc_discover_by_name(npa->npa_ctrl, name, 0, &vuc)) { nvmeadm_fatal(npa, "%s does not support operation %s: device " "does not support vendor unique command %s", npa->npa_name, npa->npa_cmd->c_name, name); } lock = nvme_vuc_disc_lock(vuc); switch (lock) { case NVME_VUC_DISC_LOCK_NONE: break; case NVME_VUC_DISC_LOCK_READ: nvmeadm_excl(npa, NVME_LOCK_L_READ); break; case NVME_VUC_DISC_LOCK_WRITE: nvmeadm_excl(npa, NVME_LOCK_L_WRITE); break; } return (vuc); } void nvmeadm_vuc_fini(const nvme_process_arg_t *npa, nvme_vuc_disc_t *vuc) { if (nvme_vuc_disc_lock(vuc) != NVME_VUC_DISC_LOCK_NONE) { if (npa->npa_ns != NULL) { nvme_ns_unlock(npa->npa_ns); } else if (npa->npa_ctrl != NULL) { nvme_ctrl_unlock(npa->npa_ctrl); } } nvme_vuc_disc_free(vuc); } void usage_vendor_cmd(const char *c_name) { (void) fprintf(stderr, "%s -O opcode [-n nsid] [--cdw12 cdw12] " "[--cdw13 cdw13]\n\t [--cdw14 cdw14] [--cdw15 cdw15] " "[-l length [-i file | -o file]]\n\t [-L lock] [-I impact] " "[-t timeout] [/]\n\n", c_name); (void) fprintf(stderr, " Run a vendor-specific command against a " "device\n"); } /* * Most folks reasonably expect short options for all long options. We do have * these here for the various --cdw arguments, but there are no good short * options here depending on what we want to do. These are different from the * Linux nvme-cli, so we would have preferred not to have them at all, but the * mappings in that tool are not very usable either, e.g. --cdw12 is 6. When * we're doing documentation: usage statements, manuals, overviews, prefer the * --cdw form. */ static const struct option vendor_cmd_lopts[] = { { "opcode", required_argument, NULL, 'O' }, { "nsid", required_argument, NULL, 'n' }, { "cdw12", required_argument, NULL, '2' }, { "cdw13", required_argument, NULL, '3' }, { "cdw14", required_argument, NULL, '4' }, { "cdw15", required_argument, NULL, '5' }, { "length", required_argument, NULL, 'l' }, { "input", required_argument, NULL, 'i' }, { "output", required_argument, NULL, 'o' }, { "lock", required_argument, NULL, 'L' }, { "impact", required_argument, NULL, 'I' }, { "timeout", required_argument, NULL, 't' }, { NULL, 0, NULL, 0 } }; static long long optparse_vendor_cmd_ui(const char *raw, const char *field, uint64_t min, uint64_t max) { const char *errstr; long long l; l = strtonumx(raw, min, max, &errstr, 0); if (errstr != NULL) { errx(-1, "failed to parse %s: value %s is %s: valid values " "are in the range [0x%" PRIx64 ", 0x%" PRIx64 "]", field, raw, errstr, min, max); } return (l); } void optparse_vendor_cmd(nvme_process_arg_t *npa) { int c; nvmeadm_vuc_t *vuc; if ((vuc = calloc(1, sizeof (nvmeadm_vuc_t))) == NULL) { err(-1, "failed to allocate memory for option tracking"); } vuc->vuc_timeout = NVMEADM_VUC_TO_DEFAULT; /* * Normally we can reset optind to 0 to make sure that we can account * for the fact that we've modified our arguments. Unfortunately * getopt_long() tries to detect this as a case where some tools have * used it as a way to ask for option processing to be reset and thus * skip our first argument. As such we cheat a bit with the arguments we * pass. */ while ((c = getopt_long(npa->npa_argc + 1, npa->npa_argv - 1, ":O:n:l:i:I:o:L:t:2:3:4:5:", vendor_cmd_lopts, NULL)) != -1) { char *last; switch (c) { case 'O': vuc->vuc_opc = (uint8_t)optparse_vendor_cmd_ui(optarg, "opcode", NVME_PASSTHRU_MIN_ADMIN_OPC, NVME_PASSTHRU_MAX_ADMIN_OPC); break; case 'n': /* * We don't use NVME_NSID_MIN here because we want to * allow the invalid nsid 0 to be specified for this * field. */ vuc->vuc_nsid = (uint8_t)optparse_vendor_cmd_ui(optarg, "opcode", 0, NVME_NSID_BCAST); break; case 'l': vuc->vuc_dlen = (uint32_t)optparse_vendor_cmd_ui(optarg, "length", 0, NVMEADM_VUC_LEN_MAX); if (vuc->vuc_dlen % NVMEADM_VUC_LEN_ALIGN != 0) { errx(-1, "invalid data length %u: must be a " "multiple of 4 bytes", vuc->vuc_dlen); } break; case 'i': vuc->vuc_input = optarg; break; case 'o': vuc->vuc_output = optarg; break; case 'L': if (strcmp(optarg, "read") == 0) { vuc->vuc_lock = NVME_LOCK_L_READ; } else if (strcmp(optarg, "write") == 0) { vuc->vuc_lock = NVME_LOCK_L_WRITE; } else { errx(-1, "invalid lock value %s: valid values " "are 'read' or 'write'", optarg); } break; case 'I': for (char *s = strtok_r(optarg, ",", &last); s != NULL; s = strtok_r(NULL, ",", &last)) { if (strcmp(s, "data") == 0) { vuc->vuc_impact |= NVME_VUC_DISC_IMPACT_DATA; } else if (strcmp(s, "namespace") == 0) { vuc->vuc_impact |= NVME_VUC_DISC_IMPACT_NS; } else { errx(-1, "invalid impact string: %s", s); } } break; case 't': /* This will be further constrained by libnvme */ vuc->vuc_timeout = (uint32_t)optparse_vendor_cmd_ui( optarg, "timeout", 1, UINT32_MAX); break; case '2': vuc->vuc_cdw12 = (uint32_t)optparse_vendor_cmd_ui( optarg, "cdw12", 0, UINT32_MAX); break; case '3': vuc->vuc_cdw13 = (uint32_t)optparse_vendor_cmd_ui( optarg, "cdw13", 0, UINT32_MAX); break; case '4': vuc->vuc_cdw14 = (uint32_t)optparse_vendor_cmd_ui( optarg, "cdw14", 0, UINT32_MAX); break; case '5': vuc->vuc_cdw15 = (uint32_t)optparse_vendor_cmd_ui( optarg, "cdw15", 0, UINT32_MAX); break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); break; } } /* * Undo our optind lies. */ optind--; if (vuc->vuc_opc == 0) { errx(-1, "missing required command opcode"); } if (vuc->vuc_input != NULL && vuc->vuc_output != NULL) { errx(-1, "cannot specify both an input file (-i) and an output " "file (-o)"); } if ((vuc->vuc_input != NULL || vuc->vuc_output != NULL) && vuc->vuc_dlen == 0) { errx(-1, "asked to transfer data (-%c) but missing required " "data length (-l)", vuc->vuc_input != NULL ? 'i' : 'o'); } if (vuc->vuc_input == NULL && vuc->vuc_output == NULL && vuc->vuc_dlen != 0) { errx(-1, "%u bytes of data transfer requested (-l), but no " "input (-i) or output (-o) specified", vuc->vuc_dlen); } /* * Only check if the namespace id matches if the user specified a * namespace. */ if (npa->npa_ns != NULL) { uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); if (vuc->vuc_nsid != 0 && vuc->vuc_nsid != nsid) { errx(-1, "Requested namespace id (-n) %u does not " "match the nsid of %s (%u): either remove the " "-n argument or specify just a controller", vuc->vuc_nsid, npa->npa_name, nsid); } vuc->vuc_nsid = nsid; } npa->npa_cmd_arg = vuc; } int do_vendor_cmd(const nvme_process_arg_t *npa) { const nvmeadm_vuc_t *vuc = npa->npa_cmd_arg; uint8_t *buf = NULL; nvme_vuc_req_t *req; int ofd = -1; /* * Verify we can get a request. This is effectively our is this * supported check. */ if (!nvme_vuc_req_init(npa->npa_ctrl, &req)) { nvmeadm_fatal(npa, "failed to initialize vendor unique " "request"); } if (vuc->vuc_dlen > 0) { if ((buf = calloc(sizeof (uint8_t), vuc->vuc_dlen)) == NULL) { nvmeadm_fatal(npa, "failed to allocate 0x%x byte " "request data buffer", vuc->vuc_dlen); } /* * If we have an input file, then we want to read data from it * until we either hit EOF or we read sufficient bytes from it * to fill our buffer. Anything we don't will be zero filled, * which was already taken care of by using calloc. */ if (vuc->vuc_input != NULL) { int ifd = open(vuc->vuc_input, O_RDONLY); if (ifd < 0) { err(EXIT_FAILURE, "failed to open input file " "%s", vuc->vuc_input); } size_t rem = vuc->vuc_dlen, off = 0; while (rem > 0) { size_t toread = MIN(16 * 1024, rem); ssize_t ret = read(ifd, buf + off, toread); if (ret < 0) { nvmeadm_fatal(npa, "failed to read %zu " "bytes at offset %zu from %s", toread, off, vuc->vuc_input); } else if (ret == 0) { break; } rem -= (size_t)ret; off += (size_t)ret; } VERIFY0(close(ifd)); } else if (vuc->vuc_output != NULL) { ofd = open(vuc->vuc_output, O_RDWR | O_TRUNC | O_CREAT, 0644); if (ofd < 0) { err(-1, "failed to open output file %s", vuc->vuc_output); } } } if (!nvme_vuc_req_set_opcode(req, vuc->vuc_opc) || !nvme_vuc_req_set_nsid(req, vuc->vuc_nsid) || !nvme_vuc_req_set_timeout(req, vuc->vuc_timeout) || !nvme_vuc_req_set_cdw12(req, vuc->vuc_cdw12) || !nvme_vuc_req_set_cdw13(req, vuc->vuc_cdw13) || !nvme_vuc_req_set_cdw14(req, vuc->vuc_cdw14) || !nvme_vuc_req_set_cdw15(req, vuc->vuc_cdw15) || !nvme_vuc_req_set_impact(req, vuc->vuc_impact)) { nvmeadm_fatal(npa, "failed to set request fields"); } if (vuc->vuc_input != NULL) { if (!nvme_vuc_req_set_input(req, buf, vuc->vuc_dlen)) { nvmeadm_fatal(npa, "failed to set input buffer"); } } else if (vuc->vuc_output != NULL) { if (!nvme_vuc_req_set_output(req, buf, vuc->vuc_dlen)) { nvmeadm_fatal(npa, "failed to set output buffer"); } } if (vuc->vuc_lock != 0) { nvmeadm_excl(npa, vuc->vuc_lock); } if (!nvme_vuc_req_exec(req)) { nvmeadm_fatal(npa, "failed to execute request"); } uint32_t cdw0; if (nvme_vuc_req_get_cdw0(req, &cdw0)) { (void) printf("Request cdw0: 0x%x\n", cdw0); } /* * Remove the lock manually. npa->npa_excl isn't set, so we need to * manually take care of this. */ if (vuc->vuc_lock != 0) { if (npa->npa_ns != NULL) { nvme_ns_unlock(npa->npa_ns); } else { nvme_ctrl_unlock(npa->npa_ctrl); } } if (vuc->vuc_output != NULL) { size_t rem = vuc->vuc_dlen, off = 0; while (rem > 0) { size_t towrite = MIN(16 * 1024, rem); ssize_t ret = write(ofd, buf + off, towrite); if (ret < 0) { nvmeadm_fatal(npa, "failed to write %zu bytes " "of output data at offset %zu to %s", towrite, off, vuc->vuc_output); } rem -= towrite; off += towrite; } } if (ofd >= 0) { VERIFY0(close(ofd)); } nvme_vuc_req_fini(req); free(buf); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * WDC vendor-specific commands and log printing. */ #include #include #include #include #include #include #include #include #include #include #include "nvmeadm.h" /* * This is the default chunk size that we'll read the e6 log in. This generally * should fit within the maximum transfer size for a device. If we wanted to * improve this, we could expose what the kernel's maximum transfer size is for * a device and then use that as a larger upper bound. Currently the value is 64 * KiB. */ #define E6_BUFSIZE 0x10000 typedef struct nvmeadm_e6_dump { const char *e6_output; } nvmeadm_e6_dump_t; typedef struct nvmeadm_wdc_resize { bool wr_query; uint32_t wr_set; } nvmeadm_wdc_resize_t; void usage_wdc_e6dump(const char *c_name) { (void) fprintf(stderr, "%s -o output \n\n" " Dump WDC e6 diagnostic log from a device.\n", c_name); } void optparse_wdc_e6dump(nvme_process_arg_t *npa) { int c; nvmeadm_e6_dump_t *e6; if ((e6 = calloc(1, sizeof (nvmeadm_e6_dump_t))) == NULL) { err(-1, "failed to allocate memory for e6 options structure"); } while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:")) != -1) { switch (c) { case 'o': e6->e6_output = optarg; break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (e6->e6_output == NULL) { errx(-1, "missing required e6dump output file, specify with " "-o"); } npa->npa_cmd_arg = e6; } static void wdc_e6_read(const nvme_process_arg_t *npa, nvme_wdc_e6_req_t *req, uint64_t off, void *buf, size_t len) { if (!nvme_wdc_e6_req_set_offset(req, off)) { nvmeadm_fatal(npa, "failed to set e6 request offset to 0x%" PRIx64, off); } if (!nvme_wdc_e6_req_set_output(req, buf, len)) { nvmeadm_fatal(npa, "failed to set e6 request output buffer"); } if (!nvme_wdc_e6_req_exec(req)) { nvmeadm_fatal(npa, "failed to issue e6 request for %zu bytes " "at offset 0x%" PRIx64, len, off); } } /* * Write out e6 data to a file. Because our read from the device has already * been constrained by size, we don't bother further chunking up the write out * to a file. */ static void wdc_e6_write(int fd, const void *buf, size_t len) { size_t off = 0; while (len > 0) { void *boff = (void *)((uintptr_t)buf + off); ssize_t ret = write(fd, boff, len); if (ret < 0) { /* * We explicitly allow a signal that interrupts us to * lead to a failure assuming someone has more likely * than not issued a SIGINT or similar. */ err(-1, "failed to write e6 data to output file"); } len -= (size_t)ret; off += (size_t)ret; } } int do_wdc_e6dump(const nvme_process_arg_t *npa) { int ofd; nvmeadm_e6_dump_t *e6 = npa->npa_cmd_arg; nvme_vuc_disc_t *vuc; void *buf; nvme_wdc_e6_req_t *req; const wdc_e6_header_t *header; uint64_t len, off; vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); ofd = open(e6->e6_output, O_RDWR | O_CREAT | O_TRUNC, 0644); if (ofd < 0) { err(-1, "failed to open file %s", e6->e6_output); } if ((buf = calloc(1, E6_BUFSIZE)) == NULL) { err(-1, "failed to allocate 0x%x bytes for E6 transfer buffer", E6_BUFSIZE); } if (!nvme_wdc_e6_req_init(npa->npa_ctrl, &req)) { nvmeadm_fatal(npa, "failed to initialize e6 request"); } /* * Begin by reading the header to determine the actual size. Note, as * far as we can tell, the size of the header is included in the size we * get. */ wdc_e6_read(npa, req, 0, buf, sizeof (wdc_e6_header_t)); header = buf; len = be32toh(header->e6_size_be); if (len == UINT32_MAX) { errx(-1, "e6 header size 0x%" PRIx64 " looks like an invalid " "PCI read, aborting", len); } if ((len % 4) != 0) { warnx("e6 header size 0x%zx is not 4 byte aligned, but " "firmware claims it always will be, rounding up", len); len = P2ROUNDUP(len, 4); } if (len < sizeof (wdc_e6_header_t)) { errx(-1, "e6 header size is too small, 0x%zx bytes does not " "even cover the header", len); } wdc_e6_write(ofd, buf, sizeof (wdc_e6_header_t)); /* * Account for the fact that we already read the header. */ off = sizeof (wdc_e6_header_t); len -= off; while (len > 0) { uint32_t toread = MIN(len, E6_BUFSIZE); wdc_e6_read(npa, req, off, buf, toread); wdc_e6_write(ofd, buf, toread); off += toread; len -= toread; } nvme_wdc_e6_req_fini(req); VERIFY0(close(ofd)); nvmeadm_vuc_fini(npa, vuc); return (0); } void usage_wdc_resize(const char *c_name) { (void) fprintf(stderr, "%s -s size | -g \n\n" " Resize a device to a new overall capacity in GB (not GiB) or " "get its\n current size. Resizing will cause all data and " "namespaces to be lost.\n", c_name); } void optparse_wdc_resize(nvme_process_arg_t *npa) { int c; nvmeadm_wdc_resize_t *resize; if ((resize = calloc(1, sizeof (nvmeadm_wdc_resize_t))) == NULL) { err(-1, "failed to allocate memory for resize options " "structure"); } while ((c = getopt(npa->npa_argc, npa->npa_argv, ":gs:")) != -1) { const char *err; switch (c) { case 'g': resize->wr_query = true; break; case 's': /* * The size to set is in GB (not GiB). While WDC * recommends specific size points depending on the * drives initial capacity, we allow the user to set * what they expect and will allow the command to * succeed or fail as per the controller's whims. It * would be better if we looked at the device and * determined its underlying capacity and figured out * what points made sense, but it's not clear on the * best way to do that across a few different * generations of WDC products. */ resize->wr_set = (uint32_t)strtonumx(optarg, 1, UINT16_MAX, &err, 0); if (err != NULL) { errx(-1, "failed to parse resize size %s:" "value is %s", optarg, err); } break; case '?': errx(-1, "unknown option: -%c", optopt); case ':': errx(-1, "option -%c requires an argument", optopt); } } if (resize->wr_query && resize->wr_set != 0) { errx(-1, "only one of -g and -s may be specified"); } if (!resize->wr_query && resize->wr_set == 0) { errx(-1, "one of -g and -s must be specified"); } npa->npa_cmd_arg = resize; } int do_wdc_resize(const nvme_process_arg_t *npa) { nvmeadm_wdc_resize_t *resize = npa->npa_cmd_arg; nvme_vuc_disc_t *vuc; vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); /* * The VUC for this generally recommends exclusive access. If this * becomes problematic for folks issuing this query, then we should * break the query into a separate VUC entry that we should discover * instead. */ if (resize->wr_query) { uint32_t val; if (!nvme_wdc_resize_get(npa->npa_ctrl, &val)) { nvmeadm_fatal(npa, "failed to query current WDC " "device capacity"); } (void) printf("%u\n", val); nvmeadm_vuc_fini(npa, vuc); return (0); } if (!nvme_wdc_resize_set(npa->npa_ctrl, resize->wr_set)) { nvmeadm_fatal(npa, "failed to resize device to %u", resize->wr_set); } (void) printf("%s resized to %u GB\n", npa->npa_name, resize->wr_set); nvmeadm_vuc_fini(npa, vuc); return (0); } int do_wdc_inject_assert(const nvme_process_arg_t *npa) { nvme_vuc_disc_t *vuc; if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); if (!nvme_wdc_assert_inject(npa->npa_ctrl)) { nvmeadm_fatal(npa, "failed to inject assertion"); } nvmeadm_vuc_fini(npa, vuc); return (0); } int do_wdc_clear_assert(const nvme_process_arg_t *npa) { nvme_vuc_disc_t *vuc; if (npa->npa_argc > 0) { errx(-1, "%s passed extraneous arguments starting with %s", npa->npa_cmd->c_name, npa->npa_argv[0]); } vuc = nvmeadm_vuc_init(npa, npa->npa_cmd->c_name); if (!nvme_wdc_assert_clear(npa->npa_ctrl)) { nvmeadm_fatal(npa, "failed to clear assertion"); } nvmeadm_vuc_fini(npa, vuc); return (0); } void usage_wdc_clear_assert(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Clear an internal device assertion.\n", c_name); } void usage_wdc_inject_assert(const char *c_name) { (void) fprintf(stderr, "%s \n\n" " Inject a device assertion. This will cause the device to " "pause\n execution of commands and create an internal fault. This " "should\n not be used unless directed as part of a " "troubleshooting exercise.\n If in doubt, do not use this!\n", c_name); } static const nvmeadm_field_t wdc_vul_power_nsamp = { .nf_off = offsetof(wdc_vul_power_t, pow_nsamples), .nf_len = sizeof (((wdc_vul_power_t *)NULL)->pow_nsamples), .nf_short = "nsamp", .nf_desc = "Number of Samples", .nf_type = NVMEADM_FT_HEX }; static const nvmeadm_field_t wdc_vul_power_samp_tmpl = { .nf_off = offsetof(wdc_vul_power_t, pow_samples[0]), .nf_len = sizeof (uint32_t), .nf_type = NVMEADM_FT_UNIT, .nf_addend = { .nfa_unit = "mW" } }; static bool wdc_vul_power_drive(nvmeadm_field_print_t *print, const void *data, size_t len) { const wdc_vul_power_t *power = data; const size_t need = power->pow_nsamples * sizeof (uint32_t) + sizeof (wdc_vul_power_t); if (need > len) { warnx("log malformed: need 0x%zx bytes, but have 0x%zx", need, len); return (false); } print->fp_header = NULL; print->fp_fields = &wdc_vul_power_nsamp; print->fp_nfields = 1; print->fp_base = NULL; print->fp_data = data; print->fp_dlen = len; print->fp_off = 0; nvmeadm_field_print(print); for (size_t i = 0; i < power->pow_nsamples; i++) { nvmeadm_field_t field; char shrt[32]; char desc[128]; (void) snprintf(shrt, sizeof (shrt), "pow%zu", i); (void) snprintf(desc, sizeof (desc), "Power Sample %zu", i); field = wdc_vul_power_samp_tmpl; field.nf_off += i * sizeof (uint32_t); field.nf_short = shrt; field.nf_desc = desc; print->fp_fields = &field; print->fp_nfields = 1; nvmeadm_field_print(print); } return (true); } const nvmeadm_log_field_info_t wdc_vul_power_field_info = { .nlfi_log = "wdc/power", .nlfi_min = sizeof (wdc_vul_power_t), .nlfi_drive = wdc_vul_power_drive }; #define WDC_F_EOL(f) .nf_off = offsetof(wdc_vul_sn840_eol_t, eol_##f), \ .nf_len = sizeof (((wdc_vul_sn840_eol_t *)NULL)->eol_##f) static const nvmeadm_field_t wdc_vul_eol_fields[] = { { WDC_F_EOL(rbc), .nf_short = "rbc", .nf_desc = "Reallocated Block Count", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(waf), .nf_short = "waf", .nf_desc = "Write Amplification Factor", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(plr), .nf_short = "plr", .nf_desc = "Percentage Life Remaining", .nf_type = NVMEADM_FT_PERCENT }, { WDC_F_EOL(pfc), .nf_short = "pfc", .nf_desc = "Program Fail Count", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(efc), .nf_short = "efc", .nf_desc = "Erase Fail Count", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(vendor), .nf_short = "vendor", .nf_desc = "Vendor Data", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(cust_sts), .nf_short = "ceolsts", .nf_desc = "Customer EOL Status", .nf_type = NVMEADM_FT_PERCENT }, { WDC_F_EOL(sys_sts), .nf_short = "seolsts", .nf_desc = "System Area EOL Status", .nf_type = NVMEADM_FT_PERCENT }, { WDC_F_EOL(cust_state), .nf_short = "ceol", .nf_desc = "Customer EOL State", .nf_type = NVMEADM_FT_HEX }, { WDC_F_EOL(sys_state), .nf_short = "seol", .nf_desc = "System Area EOL State", .nf_type = NVMEADM_FT_HEX } }; const nvmeadm_log_field_info_t wdc_vul_eol_field_info = { .nlfi_log = "wdc/eol", .nlfi_fields = wdc_vul_eol_fields, .nlfi_nfields = ARRAY_SIZE(wdc_vul_eol_fields), .nlfi_min = sizeof (wdc_vul_sn840_eol_t) };