Skip to content

Commit

Permalink
Add NVMe self-test support to smartctl (#894).
Browse files Browse the repository at this point in the history
Supported options: '-l selftest', '-t short', '-t long' and '-X'.

git-svn-id: https://svn.code.sf.net/p/smartmontools/code/trunk/smartmontools@5408 4ea69e1a-61f1-4043-bf83-b5c94c648137
  • Loading branch information
chrfranke committed Sep 18, 2022
1 parent 8f40f82 commit bb905a1
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 11 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
$Id$

2022-09-18 Christian Franke <[email protected]>

Add NVMe self-test support to smartctl (#894).
Supported options: '-l selftest', '-t short', '-t long' and '-X'.

2022-08-15 Christian Franke <[email protected]>

ataprint.cpp: Print error count even if error log index is invalid.
Expand Down
27 changes: 26 additions & 1 deletion nvmecmds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Home page of code is: https://www.smartmontools.org
*
* Copyright (C) 2016-21 Christian Franke
* Copyright (C) 2016-22 Christian Franke
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
Expand Down Expand Up @@ -258,3 +258,28 @@ bool nvme_read_smart_log(nvme_device * device, nvme_smart_log & smart_log)

return true;
}

// Read NVMe Self-test Log.
bool nvme_read_self_test_log(nvme_device * device, uint32_t nsid,
smartmontools::nvme_self_test_log & self_test_log)
{
if (!nvme_read_log_page_1(device, nsid, 0x06, &self_test_log, sizeof(self_test_log)))
return false;

if (isbigendian()) {
for (int i = 0; i < 20; i++)
swapx(&self_test_log.results[i].nsid);
}

return true;
}

// Start Self-test
bool nvme_self_test(nvme_device * device, uint8_t stc, uint32_t nsid)
{
nvme_cmd_in in;
in.opcode = nvme_admin_dev_self_test;
in.nsid = nsid;
in.cdw10 = stc;
return nvme_pass_through(device, in);
}
34 changes: 33 additions & 1 deletion nvmecmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Home page of code is: https://www.smartmontools.org
*
* Copyright (C) 2016-20 Christian Franke
* Copyright (C) 2016-22 Christian Franke
*
* Original code from <linux/nvme.h>:
* Copyright (C) 2011-2014 Intel Corporation
Expand Down Expand Up @@ -213,6 +213,7 @@ enum nvme_admin_opcode {
//nvme_admin_ns_mgmt = 0x0d,
//nvme_admin_activate_fw = 0x10,
//nvme_admin_download_fw = 0x11,
nvme_admin_dev_self_test = 0x14, // NVMe 1.3
//nvme_admin_ns_attach = 0x15,
//nvme_admin_format_nvm = 0x80,
//nvme_admin_security_send = 0x81,
Expand All @@ -222,6 +223,30 @@ enum nvme_admin_opcode {
// END: From <linux/nvme.h>
////////////////////////////////////////////////////////////////////////////

// Figure 213 of NVM Express(TM) Base Specification, revision 2.0a, July 2021
struct nvme_self_test_result {
uint8_t self_test_status;
uint8_t segment;
uint8_t valid;
uint8_t rsvd3;
uint8_t power_on_hours[8]; // unaligned LE 64
uint32_t nsid;
uint8_t lba[8]; // unaligned LE 64
uint8_t status_code_type;
uint8_t status_code;
uint8_t vendor_specific[2];
};
STATIC_ASSERT(sizeof(nvme_self_test_result) == 28);

// Figure 212 of NVM Express(TM) Base Specification, revision 2.0a, July 2021
struct nvme_self_test_log {
uint8_t current_operation;
uint8_t current_completion;
uint8_t rsvd2[2];
nvme_self_test_result results[20]; // [0] = newest
};
STATIC_ASSERT(sizeof(nvme_self_test_log) == 564);

} // namespace smartmontools

class nvme_device;
Expand All @@ -246,4 +271,11 @@ unsigned nvme_read_error_log(nvme_device * device, smartmontools::nvme_error_log
// Read NVMe SMART/Health Information log.
bool nvme_read_smart_log(nvme_device * device, smartmontools::nvme_smart_log & smart_log);

// Read NVMe Self-test Log.
bool nvme_read_self_test_log(nvme_device * device, uint32_t nsid,
smartmontools::nvme_self_test_log & self_test_log);

// Start Self-test
bool nvme_self_test(nvme_device * device, uint8_t stc, uint32_t nsid);

#endif // NVMECMDS_H
128 changes: 126 additions & 2 deletions nvmeprint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Home page of code is: https://www.smartmontools.org
*
* Copyright (C) 2016-21 Christian Franke
* Copyright (C) 2016-22 Christian Franke
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
Expand Down Expand Up @@ -489,11 +489,88 @@ static void print_error_log(const nvme_error_log_page * error_log,
pout("\n");
}

static void print_self_test_log(const nvme_self_test_log & self_test_log)
{
pout("Self-test Log (NVMe Log 0x06)\n");

const char * s; char buf[32];
switch (self_test_log.current_operation & 0xf) {
case 0x0: s = "No self-test in progress"; break;
case 0x1: s = "Short self-test in progress"; break;
case 0x2: s = "Extended self-test in progress"; break;
case 0xe: s = "Vendor specific self-test in progress"; break;
default: snprintf(buf, sizeof(buf), "Unknown status (0x%x)",
self_test_log.current_operation & 0xf);
s = buf; break;
}
pout("Self-test status: %s", s);
if (self_test_log.current_operation & 0xf)
pout(" (%d%% completed)", self_test_log.current_completion & 0x7f);
pout("\n");

int cnt = 0;
for (unsigned i = 0; i < 20; i++) {
const nvme_self_test_result & r = self_test_log.results[i];
uint8_t op = r.self_test_status >> 4;
uint8_t res = r.self_test_status & 0xf;
if (!op || res == 0xf)
continue; // unused entry

const char * t; char buf2[32];
switch (op) {
case 0x1: t = "Short"; break;
case 0x2: t = "Extended"; break;
case 0xe: t = "Vendor specific"; break;
default: snprintf(buf2, sizeof(buf2), "Unknown (0x%x)", op);
t = buf2; break;
}

switch (res) {
case 0x0: s = "Completed without error"; break;
case 0x1: s = "Aborted: Self-test command"; break;
case 0x2: s = "Aborted: Controller Reset"; break;
case 0x3: s = "Aborted: Namespace removed"; break;
case 0x4: s = "Aborted: Format NVM command"; break;
case 0x5: s = "Fatal or unknown test error"; break;
case 0x6: s = "Completed: unknown failed segment"; break;
case 0x7: s = "Completed: failed segments"; break;
case 0x8: s = "Aborted: unknown reason"; break;
case 0x9: s = "Aborted: sanitize operation"; break;
default: snprintf(buf, sizeof(buf), "Unknown result (0x%x)", res);
s = buf; break;
}

char ns[16] = "-", lb[32] = "-", st[8] = "-", sc[8] = "-";
if (r.valid & 0x01) {
if (r.nsid == 0xffffffff)
ns[0] = '*', ns[1] = 0;
else
snprintf(ns, sizeof(ns), "%u", r.nsid);
}
if (r.valid & 0x02)
snprintf(lb, sizeof(lb), "%" PRIu64, sg_get_unaligned_le64(r.lba));
if (r.valid & 0x04)
snprintf(st, sizeof(st), "0x%x", r.status_code_type);
if (r.valid & 0x08)
snprintf(sc, sizeof(sc), "0x%02x", r.status_code);

if (++cnt == 1)
pout("Num Test_Description Status Power_on_Hours Failing_LBA NSID SCT Code\n");
pout("%2u %-17s %-33s %9" PRIu64 " %12s %5s %3s %4s\n",
i, t, s, sg_get_unaligned_le64(r.power_on_hours), lb, ns, st, sc);
}

if (!cnt)
pout("No Self-tests Logged\n");
pout("\n");
}

int nvmePrintMain(nvme_device * device, const nvme_print_options & options)
{
if (!( options.drive_info || options.drive_capabilities
|| options.smart_check_status || options.smart_vendor_attrib
|| options.error_log_entries || options.log_page_size )) {
|| options.smart_selftest_log || options.error_log_entries
|| options.log_page_size || options.smart_selftest_type )) {
pout("NVMe device successfully opened\n\n"
"Use 'smartctl -a' (or '-x') to print SMART (and more) information\n\n");
return 0;
Expand Down Expand Up @@ -589,6 +666,30 @@ int nvmePrintMain(nvme_device * device, const nvme_print_options & options)
print_error_log(error_log, read_entries, max_entries);
}

// Check for self-test support
bool self_test_sup = !!(id_ctrl.oacs & 0x0010);
unsigned self_test_nsid = device->get_nsid(); // TODO: Support NSID=0 to test controller

// Read and print Self-test log, check for running test
int self_test_completion = -1;
if (options.smart_selftest_log || options.smart_selftest_type) {
if (!self_test_sup)
pout("Self-tests not supported\n\n");
else {
nvme_self_test_log self_test_log;
if (!nvme_read_self_test_log(device, self_test_nsid, self_test_log)) {
jerr("Read Self-test Log failed: %s\n\n", device->get_errmsg());
return retval | FAILSMART;
}

if (options.smart_selftest_log)
print_self_test_log(self_test_log);

if (self_test_log.current_operation & 0xf)
self_test_completion = self_test_log.current_completion & 0x7f;
}
}

// Dump log page
if (options.log_page_size) {
// Align size to dword boundary
Expand Down Expand Up @@ -621,5 +722,28 @@ int nvmePrintMain(nvme_device * device, const nvme_print_options & options)
pout("\n");
}

// Start self-test
if (self_test_sup && options.smart_selftest_type) {
bool self_test_abort = (options.smart_selftest_type == 0xf);
if (!self_test_abort && self_test_completion >= 0) {
pout("Can't start self-test without aborting current test (%2d%% completed)\n"
"Use smartctl -X to abort test\n", self_test_completion);
retval |= FAILSMART;
}
else {
if (!nvme_self_test(device, options.smart_selftest_type, self_test_nsid)) {
jerr("NVMe Self-test cmd with type=0x%x, nsid=0x%x failed: %s\n\n",
options.smart_selftest_type, self_test_nsid, device->get_errmsg());
return retval | FAILSMART;
}

if (!self_test_abort)
pout("Self-test has begun\n"
"Use smartctl -X to abort test\n");
else
pout("Self-test aborted!\n");
}
}

return retval;
}
4 changes: 3 additions & 1 deletion nvmeprint.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Home page of code is: https://www.smartmontools.org
*
* Copyright (C) 2016-21 Christian Franke
* Copyright (C) 2016-22 Christian Franke
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
Expand All @@ -22,6 +22,8 @@ struct nvme_print_options
bool drive_capabilities = false;
bool smart_check_status = false;
bool smart_vendor_attrib = false;
bool smart_selftest_log = false;
unsigned char smart_selftest_type = 0; // 0 = no test, 1 = short, 2 = extended, 0xf = abort
unsigned error_log_entries = 0;
unsigned char log_page = 0;
unsigned log_page_size = 0;
Expand Down
19 changes: 17 additions & 2 deletions smartctl.8.in
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ and for SCSI, this is equivalent to
.\" %IF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
For NVMe, this is equivalent to
.br
\*(Aq\-H \-i \-c \-A \-l error\*(Aq.
\*(Aq\-H \-i \-c \-A \-l error \-l selftest\*(Aq.
.br
.\" %ENDIF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
Note that for ATA disks this does \fBnot\fP enable the non-SMART options
Expand All @@ -287,7 +287,7 @@ and for SCSI tape drivers and changers, add \-l tapedevstat
.\" %IF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
For NVMe, this is equivalent to
.br
\*(Aq\-H \-i \-c \-A \-l error\*(Aq.
\*(Aq\-H \-i \-c \-A \-l error \-l selftest\*(Aq.
.\" %ENDIF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.TP
.B \-\-scan
Expand Down Expand Up @@ -1444,6 +1444,11 @@ If provided, the SCSI Sense Key (SK), Additional Sense Code (ASC) and
Additional Sense Code Qualifier (ASCQ) are also printed. The self tests
can be run using the \*(Aq\-t\*(Aq option described below (using the ATA
test terminology).
.\" %IF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I selftest
\- [NVMe] prints the NVMe self-test log.
.\" %ENDIF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I xselftest[,NUM][,selftest]
\- [ATA only] prints the Extended SMART self-test log (General Purpose
Expand Down Expand Up @@ -2083,6 +2088,11 @@ with other disks use the \*(Aq\-c\*(Aq option to monitor progress.
.Sp
.I short
\- [SCSI] runs the "Background short" self-test.
.\" %IF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I short
\- [NVMe] runs the "Short" self-test for current namespace.
.\" %ENDIF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I long
\- [ATA] runs SMART Extended Self Test (tens of minutes to several hours).
Expand All @@ -2093,6 +2103,11 @@ below).
.Sp
.I long
\- [SCSI] runs the "Background long" self-test.
.\" %IF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I long
\- [NVMe] runs the "Extended" self-test for current namespace.
.\" %ENDIF OS Darwin FreeBSD Linux NetBSD Windows Cygwin
.Sp
.I conveyance
\- [ATA only] runs a SMART Conveyance Self Test (minutes). This
Expand Down
11 changes: 7 additions & 4 deletions smartctl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ static int parse_options(int argc, char** argv, const char * & type,
else
badarg = true;
} else if (!strcmp(optarg,"selftest")) {
ataopts.smart_selftest_log = scsiopts.smart_selftest_log = true;
ataopts.smart_selftest_log = scsiopts.smart_selftest_log = nvmeopts.smart_selftest_log = true;
} else if (!strcmp(optarg, "selective")) {
ataopts.smart_selective_selftest_log = true;
} else if (!strcmp(optarg,"directory")) {
Expand Down Expand Up @@ -719,7 +719,7 @@ static int parse_options(int argc, char** argv, const char * & type,
ataopts.smart_vendor_attrib = scsiopts.smart_vendor_attrib = nvmeopts.smart_vendor_attrib = true;
ataopts.smart_error_log = scsiopts.smart_error_log = true;
nvmeopts.error_log_entries = 16;
ataopts.smart_selftest_log = scsiopts.smart_selftest_log = true;
ataopts.smart_selftest_log = scsiopts.smart_selftest_log = nvmeopts.smart_selftest_log = true;
ataopts.smart_selective_selftest_log = true;
/* scsiopts.smart_background_log = true; */
scsiopts.smart_ss_media_log = true;
Expand All @@ -734,7 +734,7 @@ static int parse_options(int argc, char** argv, const char * & type,
nvmeopts.error_log_entries = 16;
ataopts.smart_ext_selftest_log = 25;
ataopts.retry_selftest_log = true;
scsiopts.smart_error_log = scsiopts.smart_selftest_log = true;
scsiopts.smart_error_log = scsiopts.smart_selftest_log = nvmeopts.smart_selftest_log = true;
ataopts.smart_selective_selftest_log = true;
ataopts.smart_logdir = ataopts.gp_logdir = true;
ataopts.sct_temp_sts = ataopts.sct_temp_hist = true;
Expand Down Expand Up @@ -801,10 +801,12 @@ static int parse_options(int argc, char** argv, const char * & type,
testcnt++;
ataopts.smart_selftest_type = SHORT_SELF_TEST;
scsiopts.smart_short_selftest = true;
nvmeopts.smart_selftest_type = 0x1; // TODO: enum
} else if (!strcmp(optarg,"long")) {
testcnt++;
ataopts.smart_selftest_type = EXTEND_SELF_TEST;
scsiopts.smart_extend_selftest = true;
nvmeopts.smart_selftest_type = 0x2;
} else if (!strcmp(optarg,"conveyance")) {
testcnt++;
ataopts.smart_selftest_type = CONVEYANCE_SELF_TEST;
Expand Down Expand Up @@ -878,8 +880,9 @@ static int parse_options(int argc, char** argv, const char * & type,
break;
case 'X':
testcnt++;
scsiopts.smart_selftest_abort = true;
ataopts.smart_selftest_type = ABORT_SELF_TEST;
scsiopts.smart_selftest_abort = true;
nvmeopts.smart_selftest_type = 0xf;
break;
case 'n':
// skip disk check if in low-power mode
Expand Down

0 comments on commit bb905a1

Please sign in to comment.