Index: sbin/nvmecontrol/Makefile =================================================================== --- sbin/nvmecontrol/Makefile +++ sbin/nvmecontrol/Makefile @@ -10,6 +10,7 @@ SRCS+= passthru.c SRCS+= identify_ext.c nvme_util.c nc_util.c SRCS+= selftest.c +SRCS+= feature.c MAN= nvmecontrol.8 LDFLAGS+= -rdynamic LIBADD+= util Index: sbin/nvmecontrol/feature.c =================================================================== --- /dev/null +++ sbin/nvmecontrol/feature.c @@ -0,0 +1,837 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (C) 2022 Wanpeng Qian + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvmecontrol.h" + +/* Tables for command line parsing */ + +static cmd_fn_t feature; +static cmd_fn_t feature_get; +static cmd_fn_t feature_set; +static cmd_fn_t feature_summary; + +#define NONE 0xffu +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } +#define OPT_END { NULL, 0, arg_none, NULL, NULL } + +/* Convert a zero-based value into a one-based value */ +#define ONE_BASED(zero) ((zero) + 1) +/* Convert a one-based value into a zero-based value */ +#define ZERO_BASED(one) ((one) - 1) + +static const char * +lba_range_type_name_res[] = { + [0] = "General Purpose", + [1] = "Filesystem", + [2] = "RAID", + [3] = "Cache", + [4] = "Page / swap file", +}; +static uint32_t lba_range_type_name_res_max = + nitems(lba_range_type_name_res); + +struct summary_data { + uint8_t fid; + uint8_t sel; + uint32_t cdw11; +}; + +struct nvme_lba_range_type { + uint8_t type; + uint8_t attribute; + uint8_t reserved1[14]; + uint64_t slba; + uint64_t nlb; + uint8_t guid[16]; + uint8_t reserved2[16]; +} __packed __aligned(4); + +static const struct summary_data feature_summary_list[] = { + { NVME_FEAT_ARBITRATION, 0x0, 0x0 }, + { NVME_FEAT_POWER_MANAGEMENT, 0x0, 0x0 }, + { NVME_FEAT_TEMPERATURE_THRESHOLD, 0x0, 0x0 }, + { NVME_FEAT_ERROR_RECOVERY, 0x0, 0x0 }, + { NVME_FEAT_NUMBER_OF_QUEUES, 0x0, 0x0 }, + { NVME_FEAT_INTERRUPT_COALESCING, 0x0, 0x0 }, + { NVME_FEAT_INTERRUPT_VECTOR_CONFIGURATION, 0x0, 0x0 }, + { NVME_FEAT_WRITE_ATOMICITY, 0x0, 0x0 }, + { NVME_FEAT_ASYNC_EVENT_CONFIGURATION, 0x0, 0x0 }, +}; +static uint32_t feature_summary_list_max = nitems(feature_summary_list); + +static struct cmd feature_cmd = { + .name = "feature", + .fn = feature, + .descr = "Get Feature & Set Feature for specified device", + .ctx_size = 0, + .opts = NULL, + .args = NULL, +}; + +CMD_COMMAND(feature_cmd); + +static struct get_options { + bool hex; + uint8_t fid; + uint8_t sel; + uint32_t cdw11; + const char *vendor; + const char *dev; +} get_opt = { + .hex = false, + .fid = NONE, + .sel = 0, + .cdw11 = 0, + .vendor = NULL, + .dev = NULL, +}; + +static const struct opts get_opts[] = { + OPT("hex", 'x', arg_none, get_opt, hex, + "print result hex"), + OPT("fid", 'f', arg_uint8, get_opt, fid, + "feature id (required)"), + OPT("vendor", 'v', arg_string, get_opt, vendor, + "Vendor specific formatting"), + OPT("sel", 's', arg_uint8, get_opt, sel, + "[0-3]current/default/saved/supported"), + OPT("cdw11", '1', arg_uint32, get_opt, cdw11, + "dword 11 value"), + OPT_END +}; + +static const struct args get_args[] = { + { arg_string, &get_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd get_cmd = { + .name = "get", + .fn = feature_get, + .descr = "Get Feature for specified device", + .ctx_size = sizeof(get_opt), + .opts = get_opts, + .args = get_args, +}; + +CMD_SUBCOMMAND(feature_cmd, get_cmd); + +static struct set_options { + uint8_t fid; + bool save; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; + const char *vendor; + const char *datafile; + const char *dev; +} set_opt = { + .fid = NONE, + .save = false, + .cdw11 = 0, + .cdw12 = 0, + .cdw13 = 0, + .cdw14 = 0, + .cdw15 = 0, + .vendor = NULL, + .datafile = NULL, + .dev = NULL, +}; + +static const struct opts set_opts[] = { + OPT("fid", 'f', arg_uint8, set_opt, fid, + "feature id (required)"), + OPT("vendor", 'v', arg_string, set_opt, vendor, + "Vendor specific formatting"), + OPT("save", 's', arg_none, set_opt, save, + "the controller shall save the attribute"), + OPT("cdw11", '1', arg_uint32, set_opt, cdw11, + "dword 11 value"), + OPT("cdw12", '2', arg_uint32, set_opt, cdw12, + "dword 12 value"), + OPT("cdw13", '3', arg_uint32, set_opt, cdw13, + "dword 13 value"), + OPT("cdw14", '4', arg_uint32, set_opt, cdw14, + "dword 14 value"), + OPT("cdw15", '5', arg_uint32, set_opt, cdw15, + "dword 15 value"), + OPT("datafile", 'd', arg_path, set_opt, datafile, + "data file for some special features."), + OPT_END +}; + +static const struct args set_args[] = { + { arg_string, &set_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd set_cmd = { + .name = "set", + .fn = feature_set, + .descr = "Set Feature for specified device", + .ctx_size = sizeof(set_opt), + .opts = set_opts, + .args = set_args, +}; + +CMD_SUBCOMMAND(feature_cmd, set_cmd); + +static struct summary_options { + const char *dev; +} summary_opt = { + .dev = NULL, +}; + +static const struct args summary_args[] = { + { arg_string, &summary_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd summary_cmd = { + .name = "summary", + .fn = feature_summary, + .descr = "Show Feature summary for specified device", + .ctx_size = sizeof(summary_opt), + .opts = NULL, + .args = summary_args, +}; + +CMD_SUBCOMMAND(feature_cmd, summary_cmd); + +static SLIST_HEAD(,feature_function) features; + +static int +feature_compare(struct feature_function *a, struct feature_function *b) +{ + int c; + + if ((a->vendor == NULL) != (b->vendor == NULL)) + return (a->vendor == NULL ? -1 : 1); + if (a->vendor != NULL) { + c = strcmp(a->vendor, b->vendor); + if (c != 0) + return (c); + } + return ((int)a->fid - (int)b->fid); +} + +void +feature_register(struct feature_function *p) +{ + struct feature_function *l, *a; + + a = NULL; + l = SLIST_FIRST(&features); + while (l != NULL) { + if (feature_compare(l, p) > 0) + break; + a = l; + l = SLIST_NEXT(l, link); + } + if (a == NULL) + SLIST_INSERT_HEAD(&features, p, link); + else + SLIST_INSERT_AFTER(a, p, link); +} + +static void +print_feature_arbitration(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Arbitration Burst (AB): "); + if ((NVME_FEAT_ARBITRATION_AB(cdw0)) == NVME_FEAT_ARBITRATION_AB_MASK) + printf("no limit\n"); + else + printf("%d\n", 1 << (NVME_FEAT_ARBITRATION_AB(cdw0))); + printf(" Low Priority Weight (LPW): %d\n", + ONE_BASED(NVME_FEAT_ARBITRATION_LPW(cdw0))); + printf(" Medium Priority Weight (MPW): %d\n", + ONE_BASED(NVME_FEAT_ARBITRATION_MPW(cdw0))); + printf(" High Priority Weight (HPW): %d\n", + ONE_BASED(NVME_FEAT_ARBITRATION_HPW(cdw0))); +} + +static void +print_feature_power_management(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Workload Hint (WH): %#x\n", + NVME_FEAT_POWER_MANAGEMENT_WH(cdw0)); + printf(" Power State (PS): %#x\n", + NVME_FEAT_POWER_MANAGEMENT_PS(cdw0)); +} + +static void +print_feature_lba_range_type(uint32_t cdw0 __unused, void *buf, uint32_t len) +{ + struct nvme_lba_range_type *lba = (struct nvme_lba_range_type*)buf; + size_t num; + size_t i, j; + uint8_t idx; + + num = MIN(ONE_BASED(NVME_FEAT_LBA_RANGE_TYPE_NUM(cdw0)), + len / sizeof(struct nvme_lba_range_type)); + + for (i = 0; i < num; i++) { + if (i == 0 ) + printf("IDX\tTYPE\t\tSLBA\tNLB\t\tGUID\n"); + if (idx > lba_range_type_name_res_max) + idx = 0; + printf("0x%04zx %s", i, lba_range_type_name_res[idx]); + printf("\t0x%lx\t0x%08lx\t", lba->slba, ONE_BASED(lba->nlb)); + for (j = 0; j < sizeof(lba->guid); j++) + printf("%02x", lba->guid[j]); + printf("\n"); + lba++; + } +} + +static void +print_feature_temperature_threshold(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Temperature Threshold (TMPTH): "); + print_temp(NVME_FEAT_TEMPERATURE_THRESHOLD_TMPTH(cdw0)); +} + +static void +print_feature_error_recovery(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Deallocated or Unwritten Logical Block Error Enable (DULBE): %d\n", + NVME_FEAT_ERROR_RECOVERY_DULBE(cdw0)); + printf(" Time Limited Error Recovery (TLER): %d of 100ms\n", + NVME_FEAT_ERROR_RECOVERY_TLER(cdw0)); +} + +static void +print_feature_volatile_write_cache(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Volatile Write Cache Enable (WCE): %d\n", + NVME_FEAT_VOLATILE_WRITE_CACHE_WCE(cdw0)); +} + +static void +print_feature_number_of_queues(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Number of I/O Completion Queues Allocated (NCQA): %d\n", + ONE_BASED(NVME_FEAT_NUMBER_OF_QUEUES_NCQA(cdw0))); + printf(" Number of I/O Submission Queues Allocated (NSQA): %d\n", + ONE_BASED(NVME_FEAT_NUMBER_OF_QUEUES_NSQA(cdw0))); +} + +static void +print_feature_interrupt_coalescing(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Aggregation Time (TIME): %d\n", + NVME_FEAT_INTERRUPT_COALESCING_TIME(cdw0)); + printf(" Aggregation Threshold (THR): %d\n", + NVME_FEAT_INTERRUPT_COALESCING_THR(cdw0)); +} + +static void +print_feature_interrupt_vector_configuration(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Coalescing Disable (CD): %d\n", + NVME_FEAT_INTERRUPT_VECTOR_CONFIGURATION_CD(cdw0)); + printf(" Interrupt Vector (IV): %d\n", + NVME_FEAT_INTERRUPT_VECTOR_CONFIGURATION_IV(cdw0)); +} + +static void +print_feature_write_atomicity(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Disable Normal (DN): %d\n", + NVME_FEAT_WRITE_ATOMICITY_DN(cdw0)); +} + +static void +print_feature_async_event_configuration(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Telemetry Log Notices: %d\n", + NVME_FEAT_ASYNC_EVENT_CONFIGURATION_TLN(cdw0)); + printf(" Firmware Activation Notices: %d\n", + NVME_FEAT_ASYNC_EVENT_CONFIGURATION_FAN(cdw0)); + printf(" Namespace Attribute Notices: %d\n", + NVME_FEAT_ASYNC_EVENT_CONFIGURATION_NAN(cdw0)); + printf(" SMART/Health Critical Warnings: %d\n", + NVME_FEAT_ASYNC_EVENT_CONFIGURATION_TLN(cdw0)); +} + +static void +print_feature_autonomous_power_state_transition(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Autonomous Power State Transition Enable (APSTE): %d\n", + NVME_FEAT_AUTONOMOUS_POWER_STATE_TRANSITION_APSTE(cdw0)); +} + +static void +print_feature_timestamp(uint32_t cdw0 __unused, void *buf, uint32_t len) +{ + uint8_t *data = (uint8_t*)buf; + uint64_t milliseconds, seconds; + uint8_t origin; + bool synch; + uint32_t days, hours, minutes; + + if (len < 8) + return; + + origin = (data[6] >> 1) & 0x07; + synch = data[6] & 0x01; + + memcpy((uint8_t*)&milliseconds, (uint8_t*)data, len); + milliseconds &= 0xffffffffffff; + seconds = milliseconds / 1000; + minutes = seconds / 60; + hours = minutes / 60; + days = hours / 24; + + switch (origin) { + case 0x0: + printf(" Origin: since controller reset\n"); + break; + case 0x1: + printf(" Origin: since last set feature\n"); + break; + default: + printf(" Origin: reserved\n" ); + break; + } + if (synch) + printf(" Synch: may not continuously.\n"); + else + printf(" Synch: continuously\n"); + + printf(" Time elapsed: %lu ms,", milliseconds); + printf(" (%d days %02d:%02d:%02lu.%03lu)\n", + days, hours % 24, minutes % 60, seconds % 60, milliseconds % 1000); +} + +static void +print_feature_keep_alive_timer(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Keep Alive Timeout (KATO): %d\n", + NVME_FEAT_KEEP_ALIVE_TIMER_KATO(cdw0)); +} + +static void +print_feature_host_memory_buffer(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Memory Return (MR): %d\n", + NVME_FEAT_HOST_MEMORY_BUFFER_MR(cdw0)); + printf(" Enable Host Memory (EHM): %d\n", + NVME_FEAT_HOST_MEMORY_BUFFER_EHM(cdw0)); +} + +static void +print_feature_host_controlled_thermal_mgmt(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Thermal Management Temperature 1 (TMT1): %d\n", + NVME_FEAT_HOST_CONTROLLED_THERMAL_MGMT_TMT1(cdw0)); + printf(" Thermal Management Temperature 2 (TMT2): %d\n", + NVME_FEAT_HOST_CONTROLLED_THERMAL_MGMT_TMT2(cdw0)); +} + +static void +print_feature_non_op_power_state_config(uint32_t cdw0, void *buf __unused, uint32_t len __unused) +{ + + printf(" Non-Operational Power State Permissive Mode Enable (NOPPME): %d\n", + NVME_FEAT_NON_OP_POWER_STATE_CONFIG_NOPPME(cdw0)); +} + +void +get_feature_not_support(uint32_t cdw0 __unused, void *buf __unused, uint32_t len __unused) +{ + + printf("Get for this feature is not support\n"); +} + +static void +print_feat_hex(uint32_t cdw0, void *buf, uint32_t len) +{ + + if (buf != NULL && len > 0) + print_hex(buf, len); + else + printf("0x%X\n", cdw0); +} + +static void +read_data_file(const char *path, void **buf, int32_t *size) +{ + struct stat sb; + int32_t filesize; + int fd; + + *size = 0; + *buf = NULL; + + if ((fd = open(path, O_RDONLY)) < 0) + err(EX_NOINPUT, "unable to open '%s'", path); + if (fstat(fd, &sb) < 0) + err(EX_NOINPUT, "unable to stat '%s'", path); + + if (sb.st_size > INT32_MAX) + errx(EX_USAGE, "size of file '%s' is too large (%jd bytes)", + path, (intmax_t)sb.st_size); + filesize = (int32_t)sb.st_size; + if ((*buf = malloc(filesize)) == NULL) + errx(EX_OSERR, "unable to malloc %d bytes", filesize); + if ((*size = read(fd, *buf, filesize)) < 0) + err(EX_IOERR, "error reading '%s'", path); + /* XXX assuming no short reads */ + if (*size != filesize) + errx(EX_IOERR, + "error reading '%s' (read %d bytes, requested %d bytes)", + path, *size, filesize); + close(fd); +} + +static void +feature_help(void) +{ + const struct feature_function *f; + const char *v; + + fprintf(stderr, "\n"); + fprintf(stderr, "%-10s %-10s %s\n", "Feature Id", "Vendor","Feature Name"); + fprintf(stderr, "---------- ---------- ----------\n"); + SLIST_FOREACH(f, &features, link) { + v = f->vendor == NULL ? "-" : f->vendor; + fprintf(stderr, "0x%02x %-10s %s\n", f->fid, v, f->name); + } + + exit(EX_USAGE); +} + +static void +interpret_nvme_feature_set_status(uint8_t status) +{ + + switch (status) { + case 0x0d: + printf("Feature Identifier Not Saveable\n"); + break; + case 0x0e: + printf("Feature Not Changeable\n"); + break; + case 0x0f: + printf("Feature Not Namesapce Specific\n"); + break; + case 0x14: + printf("Overlapping Range\n"); + break; + default: + printf("Unknown Status Code: %u\n", status); + break; + } +} + +static void +feature_get(const struct cmd *f, int argc, char *argv[]) +{ + struct nvme_pt_command pt; + int fd; + const struct feature_function *ff; + feat_print_fn_t print_fn; + uint8_t *buf = NULL; + uint32_t size = 0; + + if (arg_parse(argc, argv, f)) + return; + if (get_opt.vendor != NULL && strcmp(get_opt.vendor, "help") == 0) + feature_help(); + if (get_opt.fid == NONE) { + fprintf(stderr, "Missing feature id (-f).\n"); + arg_help(argc, argv, f); + } + + print_fn = print_feat_hex; + size = 0; + if (!get_opt.hex) { + /* + * See if there is a pretty print function for the specified + * feature. If one isn't found, we just revert to the default + * (print_hex). If there was a vendor specified by the user, and + * the page is vendor specific, don't match the print function + * unless the vendors match. + */ + SLIST_FOREACH(ff, &features, link) { + if (ff->vendor != NULL && get_opt.vendor != NULL && + strcmp(ff->vendor, get_opt.vendor) != 0) + continue; + if (get_opt.fid != ff->fid) + continue; + if (ff->print_fn != NULL) + print_fn = ff->print_fn; + size = ff->size; + break; + } + } + + open_dev(get_opt.dev, &fd, 1, 1); + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = NVME_OPC_GET_FEATURES; + pt.cmd.cdw10 = htole32((get_opt.sel << NVME_FEAT_GET_SEL_SHIFT) | + (get_opt.fid << NVME_FEAT_GET_FID_SHIFT)); + pt.cmd.cdw11 = htole32(get_opt.cdw11); + pt.buf = buf; + pt.len = size; + pt.is_read = 1; + + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(EX_IOERR, "passthrough request failed"); + + if (nvme_completion_is_error(&pt.cpl)) + errx(EX_IOERR, "get feature request returned error"); + + print_fn(pt.cpl.cdw0, buf, size); + + close(fd); + exit(0); +} + +static void +feature_set(const struct cmd *f, int argc, char *argv[]) +{ + struct nvme_pt_command pt; + const struct feature_function *ff; + int fd; + void *buf = NULL; + feat_interpret_fn_t pret_fn; + int32_t size = 0; + + if (arg_parse(argc, argv, f)) + return; + if (set_opt.vendor != NULL && strcmp(set_opt.vendor, "help") == 0) + feature_help(); + if (set_opt.fid == NONE) { + fprintf(stderr, + "feature id not specified\n"); + arg_help(argc, argv, f); + } + + if (set_opt.datafile) + read_data_file(set_opt.datafile, &buf, &size); + + if (set_opt.fid == NVME_FEAT_LBA_RANGE_TYPE && + ( size > 4096 || size % 64 != 0)) { + errx(EX_USAGE, "data size of file '%s' is too large (%d bytes) or not align to 64 bytes", + set_opt.datafile, size); + } + + pret_fn = NULL; + SLIST_FOREACH(ff, &features, link) { + if (ff->vendor != NULL && get_opt.vendor != NULL && + strcmp(ff->vendor, set_opt.vendor) != 0) + continue; + if (set_opt.fid != ff->fid) + continue; + if (ff->pret_fn != NULL) + pret_fn = ff->pret_fn; + size = ff->size; + break; + } + + open_dev(set_opt.dev, &fd, 1, 1); + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = NVME_OPC_SET_FEATURES; + pt.cmd.cdw10 = htole32((set_opt.save << NVME_FEAT_SET_SV_SHIFT) | + (set_opt.fid << NVME_FEAT_SET_FID_SHIFT)); + pt.cmd.cdw11 = htole32(set_opt.cdw11); + pt.cmd.cdw12 = htole32(set_opt.cdw12); + pt.cmd.cdw13 = htole32(set_opt.cdw13); + pt.cmd.cdw14 = htole32(set_opt.cdw14); + pt.cmd.cdw15 = htole32(set_opt.cdw15); + pt.buf = buf; + pt.len = size; + pt.is_read = 0; + + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(EX_IOERR, "passthrough request failed"); + + if (nvme_completion_is_error(&pt.cpl)) { + if (NVME_STATUS_GET_SCT(pt.cpl.status) == NVME_SCT_COMMAND_SPECIFIC && pret_fn) { + pret_fn(NVME_STATUS_GET_SC(pt.cpl.status)); + } + else + errx(EX_IOERR, "set feature request returned error"); + } + else + printf("set feature success\n"); + + close(fd); + exit(0); +} + +static void +feature_summary(const struct cmd *f, int argc, char *argv[]) +{ + struct nvme_pt_command pt; + const struct feature_function *ff; + uint8_t fid, sel; + uint32_t cdw11; + int fd; + feat_print_fn_t print_fn; + uint8_t *buf = NULL; + uint32_t i; + uint32_t size = 0; + + if (arg_parse(argc, argv, f)) + return; + + open_dev(summary_opt.dev, &fd, 1, 1); + + for (i = 0; i < feature_summary_list_max; i++) { + fid = feature_summary_list[i].fid; + sel = feature_summary_list[i].sel; + cdw11 = feature_summary_list[i].cdw11; + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = NVME_OPC_GET_FEATURES; + pt.cmd.cdw10 = htole32((sel << NVME_FEAT_GET_SEL_SHIFT) | + (fid << NVME_FEAT_GET_FID_SHIFT)); + pt.cmd.cdw11 = htole32(cdw11); + pt.buf = buf; + pt.len = size; + pt.is_read = 1; + + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + continue; + + if (nvme_completion_is_error(&pt.cpl)) + continue; + + print_fn = print_feat_hex; + SLIST_FOREACH(ff, &features, link) { + if (fid != ff->fid) + continue; + if (ff->print_fn != NULL) + print_fn = ff->print_fn; + size = ff->size; + break; + } + + print_fn(pt.cpl.cdw0, buf, size); + } + + close(fd); + exit(0); +} + +static void +feature(const struct cmd *nf __unused, int argc, char *argv[]) +{ + + cmd_dispatch(argc, argv, &feature_cmd); +} + +NVME_FEATURE(arbitration, + NVME_FEAT_ARBITRATION, NULL, "Command Arbitration", + print_feature_arbitration, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(power_management, + NVME_FEAT_POWER_MANAGEMENT, NULL, "Power Management", + print_feature_power_management, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(lba_range_type, + NVME_FEAT_LBA_RANGE_TYPE, NULL, "LBA Range Type", + print_feature_lba_range_type, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(temperature_threshold, + NVME_FEAT_TEMPERATURE_THRESHOLD, NULL, "Temperature Threshold", + print_feature_temperature_threshold, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(error_recovery, + NVME_FEAT_ERROR_RECOVERY, NULL, "Error Recovery", + print_feature_error_recovery, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(volatile_write_cache, + NVME_FEAT_VOLATILE_WRITE_CACHE, NULL, "Volatile Write Cache", + print_feature_volatile_write_cache, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(number_of_queues, + NVME_FEAT_NUMBER_OF_QUEUES, NULL, "Number of Queues", + print_feature_number_of_queues, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(interrupt_coalescing, + NVME_FEAT_INTERRUPT_COALESCING, NULL, "Interrupt Coalescing", + print_feature_interrupt_coalescing, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(interrupt_vector_configuration, + NVME_FEAT_INTERRUPT_VECTOR_CONFIGURATION, NULL, "Interrupt Vector Configuration", + print_feature_interrupt_vector_configuration, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(write_atomicity, + NVME_FEAT_WRITE_ATOMICITY, NULL, "Write Atomicity Normal", + print_feature_write_atomicity, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(async_event_configuration, + NVME_FEAT_ASYNC_EVENT_CONFIGURATION, NULL, "Asynchronous Event Configuration", + print_feature_async_event_configuration, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(autonomous_power_state_transition, + NVME_FEAT_AUTONOMOUS_POWER_STATE_TRANSITION, NULL, "Autonomous Power State Transition", + print_feature_autonomous_power_state_transition, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(host_memory_buffer, + NVME_FEAT_HOST_MEMORY_BUFFER, NULL, "Host Memory Buffer", + print_feature_host_memory_buffer, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(timestamp, + NVME_FEAT_TIMESTAMP, NULL, "Timestamp", + print_feature_timestamp, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(keep_alive_timer, + NVME_FEAT_KEEP_ALIVE_TIMER, NULL, "Keep Alive Timer", + print_feature_keep_alive_timer, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(host_controlled_thermal_mgmt, + NVME_FEAT_HOST_CONTROLLED_THERMAL_MGMT, NULL, "Host Controlled Thermal Management", + print_feature_host_controlled_thermal_mgmt, interpret_nvme_feature_set_status, 0); +NVME_FEATURE(non_op_power_state_config, + NVME_FEAT_NON_OP_POWER_STATE_CONFIG, NULL, "Non-Operational Power State Config", + print_feature_non_op_power_state_config, interpret_nvme_feature_set_status, 0); Index: sbin/nvmecontrol/nvmecontrol.h =================================================================== --- sbin/nvmecontrol/nvmecontrol.h +++ sbin/nvmecontrol/nvmecontrol.h @@ -35,6 +35,8 @@ #include "comnd.h" typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size); +typedef void (*feat_print_fn_t)(uint32_t cdw0, void *buf, uint32_t size); +typedef void (*feat_interpret_fn_t)(uint8_t status); struct logpage_function { SLIST_ENTRY(logpage_function) link; @@ -45,6 +47,16 @@ size_t size; }; +struct feature_function { + SLIST_ENTRY(feature_function) link; + uint8_t fid; + const char *vendor; + const char *name; + feat_print_fn_t print_fn; + feat_interpret_fn_t pret_fn; + size_t size; +}; + #define NVME_LOGPAGE(unique, lp, vend, nam, fn, sz) \ static struct logpage_function unique ## _lpf = { \ .log_page = lp, \ @@ -56,6 +68,18 @@ static void logpage_reg_##unique(void) __attribute__((constructor)); \ static void logpage_reg_##unique(void) { logpage_register(&unique##_lpf); } +#define NVME_FEATURE(unique, id, vend, na, fnp, fni, sz) \ + static struct feature_function unique ## _ff = { \ + .fid = id, \ + .vendor = vend, \ + .name = na, \ + .print_fn = fnp, \ + .pret_fn = fni, \ + .size = sz, \ + } ; \ + static void feature_reg_##unique(void) __attribute__((constructor)); \ + static void feature_reg_##unique(void) { feature_register(&unique##_ff); } + #define DEFAULT_SIZE (4096) struct kv_name { uint32_t key; @@ -65,6 +89,8 @@ const char *kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key); void logpage_register(struct logpage_function *p); +void feature_register(struct feature_function *p); +void get_feature_not_support(uint32_t cdw0, void *buf, uint32_t len); #define NVME_CTRLR_PREFIX "nvme" #define NVME_NS_PREFIX "ns" Index: sbin/nvmecontrol/nvmecontrol.8 =================================================================== --- sbin/nvmecontrol/nvmecontrol.8 +++ sbin/nvmecontrol/nvmecontrol.8 @@ -147,6 +147,29 @@ .Op Fl x .Aq Ar namespace-id .Nm +.Ic feature get +.Aq Fl f Ar feature-id +.Op Fl v Ar vendor-string +.Op Fl s Ar select +.Op Fl x +.Op Fl 1 Ar value +.Aq Ar device-id +.Nm +.Ic feature set +.Aq Fl f Ar feature-id +.Op Fl v Ar vendor-string +.Op Fl s Ar save +.Op Fl 1 Ar value +.Op Fl 2 Ar value +.Op Fl 3 Ar value +.Op Fl 4 Ar value +.Op Fl 5 Ar value +.Op Fl d Ar path_to_datafile +.Aq Ar device-id +.Nm +.Ic feature summary +.Aq Ar device-id +.Nm .Ic firmware .Op Fl s Ar slot .Op Fl f Ar path_to_firmware @@ -242,6 +265,87 @@ .Dv IDENTIFY_CONTROLLER data associated with that drive. .El +.Ss feature +The feature command includes +.Ic get set +and +.Ic summary +sub commands +to get and set a feature for specific device. +It also knows about vendor specific feature. +Specifying help as vendor string will list all valid vendors and features +.El +.Ss feature get +The feature get command can output feature settings for specific device. +.Bl -tag -width 8n +.It Fl f Ar feature-id +Feature id to get +.It Fl v +vendor-string +.It Fl s +Select +.Bl -tag -compact -width 6n +.It Dv 0 +Current +.It Dv 1 +Default +.It Dv 2 +Saved +.It Dv 3 +Supported capabilities +.El +.It Fl x +Print data as hex +.It Fl 1 Ar value +32-bit value for CDW11. +.El +.Ss feature set +The feature set command can set feature for specific device. +.Bl -tag -width 8n +.It Fl f Ar feature-id +Feature id to set +.It Fl v +vendor-string +.It Fl s +Requests that the controller save the attribute +so that the attribute persists through all power states and resets. +.It Fl 1 Ar value +32-bit value for CDW11. +.It Fl 2 Ar value +32-bit value for CDW12. +.It Fl 3 Ar value +32-bit value for CDW13. +.It Fl 4 Ar value +32-bit value for CDW14. +.It Fl 5 Ar value +32-bit value for CDW15. +.It Fl f Ar path_to_datafile +Data file for specific feature. +.El +.Ss feature summary +The feature summary command will output all mandatory features +for specific device. These features are: +.Pp +.Bl -tag -compact -width "Fid 0x00" +.It Dv Fid 0x01 +Command Arbitration +.It Dv Fid 0x02 +Power Management +.It Dv Fid 0x04 +Temperature Threshold +.It Dv Fid 0x05 +Error Recovery +.It Dv Fid 0x07 +Number of Queues +.It Dv Fid 0x08 +Interrupt Coalescing +.It Dv Fid 0x09 +Interrupt Vector Configuration +.It Dv Fid 0x0A +Write Atomicity Normal +.It Dv Fid 0x0B +Asynchronous Event Configuration +.El .Ss logpage The logpage command knows how to print log pages of various types. It also knows about vendor specific log pages from hgst/wdc and intel. @@ -670,6 +774,15 @@ standard out. Redirect it to a temporary file. .Pp +.Dl nvmecontrol feature get -f0x01 -s1 nvme0 +.Pp +Get feature default value of Command Arbitration for nvme0 controller. +.Pp +.Dl nvmecontrol feature set -f0x04 -s -10x157 nvme0 +.Pp +Set feature value of Temperature Threshold for nvme0 controller, +and tell controller to save that value. +.Pp .Dl nvmecontrol firmware -s 2 -f /tmp/nvme_firmware nvme0 .Pp Download the firmware image contained in "/tmp/nvme_firmware" to slot 2 of the