Index: sbin/nvmecontrol/Makefile =================================================================== --- sbin/nvmecontrol/Makefile +++ sbin/nvmecontrol/Makefile @@ -9,6 +9,7 @@ SRCS+= perftest.c power.c reset.c resv.c sanitize.c SRCS+= passthru.c SRCS+= identify_ext.c nvme_util.c nc_util.c +SRCS+= selftest.c MAN= nvmecontrol.8 LDFLAGS+= -rdynamic LIBADD+= util Index: sbin/nvmecontrol/logpage.c =================================================================== --- sbin/nvmecontrol/logpage.c +++ sbin/nvmecontrol/logpage.c @@ -239,6 +239,10 @@ case NVME_LOG_CHANGED_NAMESPACE: nvme_ns_list_swapbytes((struct nvme_ns_list *)payload); break; + case NVME_LOG_DEVICE_SELF_TEST: + nvme_device_self_test_swapbytes( + (struct nvme_device_self_test_page *)payload); + break; case NVME_LOG_COMMAND_EFFECT: nvme_command_effects_page_swapbytes( (struct nvme_command_effects_page *)payload); @@ -588,6 +592,103 @@ printf("Time For Crypto Erase No-Deallocate: %u sec\n", ss->etfcewnd); } +static const char * +self_test_res[] = { + [0] = "completed without error", + [1] = "aborted by a Device Self-test command", + [2] = "aborted by a Controller Level Reset", + [3] = "aborted due to namespace removal", + [4] = "aborted due to Format NVM command", + [5] = "failed due to fatal or unknown test error", + [6] = "completed with an unknown segment that failed", + [7] = "completed with one or more failed segments", + [8] = "aborted for unknown reason", + [9] = "aborted due to a sanitize operation", +}; +static uint32_t self_test_res_max = nitems(self_test_res); + +static void +print_log_self_test_status(const struct nvme_controller_data *cdata __unused, + void *buf, uint32_t size __unused) +{ + struct nvme_device_self_test_page *dst; + uint32_t r; + + dst = buf; + printf("Device Self-test Status\n"); + printf("=======================\n"); + + printf("Current Operation: "); + switch (dst->curr_operation) { + case 0x0: + printf("No device self-test operation in progress\n"); + break; + case 0x1: + printf("Short device self-test operation in progress\n"); + break; + case 0x2: + printf("Extended device self-test operation in progress\n"); + break; + case 0xe: + printf("Vendor specific\n"); + break; + default: + printf("Reserved (0x%x)\n", dst->curr_operation); + } + + if (dst->curr_operation != 0) + printf("Current Completion: %u%%\n", dst->curr_compl & 0x7f); + + printf("Results\n"); + for (r = 0; r < 20; r++) { + uint64_t failing_lba; + uint8_t code, res; + + code = (dst->result[r].status >> 4) & 0xf; + res = dst->result[r].status & 0xf; + + if (res == 0xf) + continue; + + printf("[%2u] ", r); + switch (code) { + case 0x1: + printf("Short device self-test"); + break; + case 0x2: + printf("Extended device self-test"); + break; + case 0xe: + printf("Vendor specific"); + break; + default: + printf("Reserved (0x%x)", code); + } + if (res < self_test_res_max) + printf(" %s", self_test_res[res]); + else + printf(" Reserved status 0x%x", res); + + if (res == 7) + printf(" starting in segment %u", dst->result[r].segment_num); + +#define BIT(b) (1 << (b)) + if (dst->result[r].valid_diag_info & BIT(0)) + printf(" NSID=0x%x", dst->result[r].nsid); + if (dst->result[r].valid_diag_info & BIT(1)) { + memcpy(&failing_lba, dst->result[r].failing_lba, + sizeof(failing_lba)); + printf(" FLBA=0x%lx", failing_lba); + } + if (dst->result[r].valid_diag_info & BIT(2)) + printf(" SCT=0x%x", dst->result[r].status_code_type); + if (dst->result[r].valid_diag_info & BIT(3)) + printf(" SC=0x%x", dst->result[r].status_code); +#undef BIT + printf("\n"); + } +} + /* * Table of log page printer / sizing. * @@ -611,7 +712,7 @@ print_log_command_effects, sizeof(struct nvme_command_effects_page)); NVME_LOGPAGE(dst, NVME_LOG_DEVICE_SELF_TEST, NULL, "Device Self-test", - NULL, 564); + print_log_self_test_status, sizeof(struct nvme_device_self_test_page)); NVME_LOGPAGE(thi, NVME_LOG_TELEMETRY_HOST_INITIATED, NULL, "Telemetry Host-Initiated", NULL, DEFAULT_SIZE); Index: sbin/nvmecontrol/nvmecontrol.8 =================================================================== --- sbin/nvmecontrol/nvmecontrol.8 +++ sbin/nvmecontrol/nvmecontrol.8 @@ -177,6 +177,10 @@ .Op Fl p power_state .Op Fl w workload_hint .Nm +.Ic selftest +.Aq Fl c Ar code +.Aq Ar device-id | Ar namespace-id +.Nm .Ic wdc cap-diag .Op Fl o path_template .Aq Ar device-id @@ -254,6 +258,8 @@ Changed Namespace List .It Dv Page 0x05 Commands Supported and Effects +.It Dv Page 0x06 +Device Self-test .It Dv Page 0x80 Reservation Notification .It Dv Page 0x81 @@ -464,6 +470,23 @@ mode. This will report status on a sanitize that is already running on the drive. .El +.Ss selftest +Start the specified device self-test: +.Bl -tag -width 6n +.It Fl c Ar code +Specify the device self-test command code. +Common codes are: +.Bl -tag -compact -width 6n +.It Dv 0x1 +Start a short device self-test operation +.It Dv 0x2 +Start an extended device self-test operation +.It Dv 0xe +Start a vendor specific device self-test operation +.It Dv 0xf +Abort the device self-test operation +.El +.El .Ss wdc The various wdc command retrieve log data from the wdc/hgst drives. The Index: sbin/nvmecontrol/selftest.c =================================================================== --- /dev/null +++ sbin/nvmecontrol/selftest.c @@ -0,0 +1,138 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Chuck Tuffli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvmecontrol.h" + +#define SELFTEST_CODE_NONE 0xffu +#define SELFTEST_CODE_MAX 0xfu + +static struct options { + const char *dev; + uint8_t stc; /* Self-test Code */ +} opt = { + .dev = NULL, + .stc = SELFTEST_CODE_NONE, +}; + +static void +selftest_op(int fd, uint32_t nsid, uint8_t stc) +{ + struct nvme_pt_command pt; + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = NVME_OPC_DEVICE_SELF_TEST; + pt.cmd.nsid = htole32(nsid); + pt.cmd.cdw10 = htole32(stc); + + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(EX_IOERR, "self-test request failed"); + + if (NVME_STATUS_GET_SCT(pt.cpl.status) == NVME_SCT_COMMAND_SPECIFIC && + NVME_STATUS_GET_SC(pt.cpl.status) == NVME_SC_SELF_TEST_IN_PROGRESS) + errx(EX_UNAVAILABLE, "device self-test in progress"); + else if (nvme_completion_is_error(&pt.cpl)) + errx(EX_IOERR, "self-test request returned error"); +} + +static void +selftest(const struct cmd *f, int argc, char *argv[]) +{ + struct nvme_controller_data cdata; + int fd; + char *path; + uint32_t nsid; + + if (arg_parse(argc, argv, f)) + return; + + open_dev(opt.dev, &fd, 1, 1); + get_nsid(fd, &path, &nsid); + if (nsid != 0) { + close(fd); + open_dev(path, &fd, 1, 1); + } + free(path); + + if (opt.stc == SELFTEST_CODE_NONE) + errx(EX_USAGE, "must specify a Self-test Code"); + else if (opt.stc > SELFTEST_CODE_MAX) + errx(EX_DATAERR, "illegal Self-test Code 0x%x", opt.stc); + + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); + + if (((cdata.oacs >> NVME_CTRLR_DATA_OACS_SELFTEST_SHIFT) & + NVME_CTRLR_DATA_OACS_SELFTEST_MASK) == 0) + errx(EX_UNAVAILABLE, "controller does not support self-test"); + + selftest_op(fd, nsid, opt.stc); + + close(fd); + exit(0); +} + +static const struct opts selftest_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("test-code", 'c', arg_uint8, opt, stc, + "Self-test Code to execute"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static struct args selftest_args[] = { + { arg_string, &opt.dev, "controller-id|namespace-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd selftest_cmd = { + .name = "selftest", + .fn = selftest, + .descr = "Start device self-test", + .ctx_size = sizeof(opt), + .opts = selftest_opts, + .args = selftest_args, +}; + +CMD_COMMAND(selftest_cmd); + Index: sys/dev/nvme/nvme.h =================================================================== --- sys/dev/nvme/nvme.h +++ sys/dev/nvme/nvme.h @@ -1396,6 +1396,28 @@ _Static_assert(sizeof(struct nvme_command_effects_page) == 4096, "bad size for nvme_command_effects_page"); +struct nvme_device_self_test_page { + uint8_t curr_operation; + uint8_t curr_compl; + uint8_t rsvd2[2]; + struct { + uint8_t status; + uint8_t segment_num; + uint8_t valid_diag_info; + uint8_t rsvd3; + uint64_t poh; + uint32_t nsid; + /* Define as an array to simplify alignment issues */ + uint8_t failing_lba[8]; + uint8_t status_code_type; + uint8_t status_code; + uint8_t vendor_specific[2]; + } __packed result[20]; +} __packed __aligned(4); + +_Static_assert(sizeof(struct nvme_device_self_test_page) == 564, + "bad size for nvme_device_self_test_page"); + struct nvme_res_notification_page { uint64_t log_page_count; uint8_t log_page_type; @@ -2016,4 +2038,21 @@ #endif } +static inline void +nvme_device_self_test_swapbytes(struct nvme_device_self_test_page *s __unused) +{ +#if _BYTE_ORDER != _LITTLE_ENDIAN + uint64_t failing_lba; + uint32_t r; + + for (r = 0; r < 20; r++) { + s->result[r].poh = le64toh(s->result[r].poh); + s->result[r].nsid = le32toh(s->result[r].nsid); + /* Unaligned 64-bit loads fail on some architectures */ + memcpy(&failing_lba, s->result[r].failing_lba, sizeof(failing_lba)); + failing_lba = le64toh(failing_lba); + memcpy(s->result[r].failing_lba, &failing_lba, sizeof(failing_lba)); + } +#endif +} #endif /* __NVME_H__ */