958 lines
34 KiB
C
958 lines
34 KiB
C
/*
|
|
* Copyright (c) 2018-2021 Douglas Gilbert
|
|
* All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the BSD_LICENSE file.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* This program issues a NVMe Identify command (controller or namespace)
|
|
* or a Device self-test command via the "SCSI" pass-through interface of
|
|
* this package's sg_utils library. That interface is primarily shown in
|
|
* the ../include/sg_pt.h header file.
|
|
*
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <getopt.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#define __STDC_FORMAT_MACROS 1
|
|
#include <inttypes.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "sg_lib.h"
|
|
#include "sg_pt.h"
|
|
#include "sg_pt_nvme.h"
|
|
#include "sg_cmds_basic.h"
|
|
#include "sg_unaligned.h"
|
|
#include "sg_pr2serr.h"
|
|
|
|
static const char * version_str = "1.07 20210225";
|
|
|
|
|
|
#define ME "sg_tst_nvme: "
|
|
|
|
#define SENSE_BUFF_LEN 32 /* Arbitrary, only need 16 bytes for NVME
|
|
* (and SCSI at least 18) currently */
|
|
#define SENSE_BUFF_NVME_LEN 16 /* 4 DWords, little endian, as byte string */
|
|
|
|
#define INQUIRY_CMD 0x12 /* SCSI command to get VPD page 0x83 */
|
|
#define INQUIRY_CMDLEN 6
|
|
#define INQUIRY_MAX_RESP_LEN 252
|
|
|
|
#define VPD_DEVICE_ID 0x83
|
|
|
|
#define NVME_NSID_ALL 0xffffffff
|
|
|
|
#define DEF_TIMEOUT_SECS 60
|
|
|
|
|
|
static struct option long_options[] = {
|
|
{"ctl", no_argument, 0, 'c'},
|
|
{"dev-id", no_argument, 0, 'd'},
|
|
{"dev_id", no_argument, 0, 'd'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{"long", no_argument, 0, 'l'},
|
|
{"maxlen", required_argument, 0, 'm'},
|
|
{"nsid", required_argument, 0, 'n'},
|
|
{"self-test", required_argument, 0, 's'},
|
|
{"self_test", required_argument, 0, 's'},
|
|
{"to-ms", required_argument, 0, 't'},
|
|
{"to_ms", required_argument, 0, 't'},
|
|
{"verbose", no_argument, 0, 'v'},
|
|
{"version", no_argument, 0, 'V'},
|
|
{0, 0, 0, 0},
|
|
};
|
|
|
|
/* Assume index is less than 16 */
|
|
static const char * sg_ansi_version_arr[16] =
|
|
{
|
|
"no conformance claimed",
|
|
"SCSI-1", /* obsolete, ANSI X3.131-1986 */
|
|
"SCSI-2", /* obsolete, ANSI X3.131-1994 */
|
|
"SPC", /* withdrawn, ANSI INCITS 301-1997 */
|
|
"SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */
|
|
"SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */
|
|
"SPC-4", /* ANSI INCITS 513-2015 */
|
|
"SPC-5",
|
|
"ecma=1, [8h]",
|
|
"ecma=1, [9h]",
|
|
"ecma=1, [Ah]",
|
|
"ecma=1, [Bh]",
|
|
"reserved [Ch]",
|
|
"reserved [Dh]",
|
|
"reserved [Eh]",
|
|
"reserved [Fh]",
|
|
};
|
|
|
|
#define MAX_DEV_NAMES 8
|
|
|
|
static const char * dev_name_arr[MAX_DEV_NAMES] = {
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
|
|
static int next_dev_name_pos = 0;
|
|
|
|
|
|
static void
|
|
usage()
|
|
{
|
|
pr2serr("Usage: sg_tst_nvme [--ctl] [--dev-id] [--help] [--long] "
|
|
"[--maxlen=LEN]\n"
|
|
" [--nsid=ID] [--self-test=ST] [--to-ms=TO] "
|
|
"[--verbose]\n"
|
|
" [--version] DEVICE [DEVICE ...]\n"
|
|
" where:\n"
|
|
" --ctl|-c only do Identify controller command\n"
|
|
" --dev-id|-d do SCSI INQUIRY for device "
|
|
" identification\n"
|
|
" VPD page (0x83) via own SNTL\n"
|
|
" --help|-h print out usage message\n"
|
|
" --long|-l add more detail to decoded output\n"
|
|
" --maxlen=LEN| -m LEN allocation length for SCSI devices\n"
|
|
" --nsid=ID| -n ID do Identify namespace with nsid set to "
|
|
"ID; if ID\n"
|
|
" is 0 then try to get nsid from "
|
|
"DEVICE.\n"
|
|
" Can also be used with self-test (def: "
|
|
"0)\n"
|
|
" --self-test=ST|-s ST do (or abort) device self-test, ST "
|
|
"can be:\n"
|
|
" 0: do nothing\n"
|
|
" 1: do short (background) "
|
|
"self-test\n"
|
|
" 2: do long self-test\n"
|
|
" 15: abort self-test in "
|
|
"progress\n"
|
|
" if nsid is 0 then test controller "
|
|
"only\n"
|
|
" if nsid is 0xffffffff (-1) then test "
|
|
"controller\n"
|
|
" and all namespaces\n"
|
|
" --to-ms=TO|-t TO command timeout in milliseconds (def: "
|
|
"60,000)\n"
|
|
" --verbose|-v increase verbosity\n"
|
|
" --version|-V print version string then exit\n\n"
|
|
"Performs a NVME Identify or Device self-test Admin command on "
|
|
"each DEVICE.\nCan also simulate a SCSI device identification VPD "
|
|
"page [0x83] via\na local SNTL. --nsid= accepts '-1' for "
|
|
"0xffffffff which means all.\n"
|
|
);
|
|
}
|
|
|
|
static void
|
|
show_nvme_id_ctl(const uint8_t *dinp, const char *dev_name, int do_long,
|
|
uint32_t * max_nsid_p)
|
|
{
|
|
bool got_fguid;
|
|
uint8_t ver_min, ver_ter, mtds;
|
|
uint16_t ver_maj, oacs, oncs;
|
|
uint32_t k, ver, max_nsid, npss, j, n, m;
|
|
uint64_t sz1, sz2;
|
|
const uint8_t * up;
|
|
|
|
max_nsid = sg_get_unaligned_le32(dinp + 516); /* NN */
|
|
if (max_nsid_p)
|
|
*max_nsid_p = max_nsid;
|
|
printf("Identify controller for %s:\n", dev_name);
|
|
printf(" Model number: %.40s\n", (const char *)(dinp + 24));
|
|
printf(" Serial number: %.20s\n", (const char *)(dinp + 4));
|
|
printf(" Firmware revision: %.8s\n", (const char *)(dinp + 64));
|
|
ver = sg_get_unaligned_le32(dinp + 80);
|
|
ver_maj = (ver >> 16);
|
|
ver_min = (ver >> 8) & 0xff;
|
|
ver_ter = (ver & 0xff);
|
|
printf(" Version: %u.%u", ver_maj, ver_min);
|
|
if ((ver_maj > 1) || ((1 == ver_maj) && (ver_min > 2)) ||
|
|
((1 == ver_maj) && (2 == ver_min) && (ver_ter > 0)))
|
|
printf(".%u\n", ver_ter);
|
|
else
|
|
printf("\n");
|
|
oacs = sg_get_unaligned_le16(dinp + 256);
|
|
if (0x1ff & oacs) {
|
|
printf(" Optional admin command support:\n");
|
|
if (0x100 & oacs)
|
|
printf(" Doorbell buffer config\n");
|
|
if (0x80 & oacs)
|
|
printf(" Virtualization management\n");
|
|
if (0x40 & oacs)
|
|
printf(" NVMe-MI send and NVMe-MI receive\n");
|
|
if (0x20 & oacs)
|
|
printf(" Directive send and directive receive\n");
|
|
if (0x10 & oacs)
|
|
printf(" Device self-test\n");
|
|
if (0x8 & oacs)
|
|
printf(" Namespace management and attachment\n");
|
|
if (0x4 & oacs)
|
|
printf(" Firmware download and commit\n");
|
|
if (0x2 & oacs)
|
|
printf(" Format NVM\n");
|
|
if (0x1 & oacs)
|
|
printf(" Security send and receive\n");
|
|
} else
|
|
printf(" No optional admin command support\n");
|
|
oncs = sg_get_unaligned_le16(dinp + 256);
|
|
if (0x7f & oncs) {
|
|
printf(" Optional NVM command support:\n");
|
|
if (0x40 & oncs)
|
|
printf(" Timestamp feature\n");
|
|
if (0x20 & oncs)
|
|
printf(" Reservations\n");
|
|
if (0x10 & oncs)
|
|
printf(" Save and Select fields non-zero\n");
|
|
if (0x8 & oncs)
|
|
printf(" Write zeroes\n");
|
|
if (0x4 & oncs)
|
|
printf(" Dataset management\n");
|
|
if (0x2 & oncs)
|
|
printf(" Write uncorrectable\n");
|
|
if (0x1 & oncs)
|
|
printf(" Compare\n");
|
|
} else
|
|
printf(" No optional NVM command support\n");
|
|
printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n",
|
|
sg_get_unaligned_le16(dinp + 0),
|
|
sg_get_unaligned_le16(dinp + 2));
|
|
printf(" IEEE OUI Identifier: 0x%x\n",
|
|
sg_get_unaligned_le24(dinp + 73));
|
|
got_fguid = ! sg_all_zeros(dinp + 112, 16);
|
|
if (got_fguid) {
|
|
printf(" FGUID: 0x%02x", dinp[112]);
|
|
for (k = 1; k < 16; ++k)
|
|
printf("%02x", dinp[112 + k]);
|
|
printf("\n");
|
|
} else if (do_long)
|
|
printf(" FGUID: 0x0\n");
|
|
printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78));
|
|
if (do_long) {
|
|
printf(" Management endpoint capabilities, over a PCIe port: %d\n",
|
|
!! (0x2 & dinp[255]));
|
|
printf(" Management endpoint capabilities, over a SMBus/I2C port: "
|
|
"%d\n", !! (0x1 & dinp[255]));
|
|
}
|
|
printf(" Number of namespaces: %u\n", max_nsid);
|
|
sz1 = sg_get_unaligned_le64(dinp + 280); /* lower 64 bits */
|
|
sz2 = sg_get_unaligned_le64(dinp + 288); /* upper 64 bits */
|
|
if (sz2)
|
|
printf(" Total NVM capacity: huge ...\n");
|
|
else if (sz1)
|
|
printf(" Total NVM capacity: %" PRIu64 " bytes\n", sz1);
|
|
mtds = dinp[77];
|
|
printf(" Maximum data transfer size: ");
|
|
if (mtds)
|
|
printf("%u pages\n", 1U << mtds);
|
|
else
|
|
printf("<unlimited>\n");
|
|
|
|
if (do_long) {
|
|
const char * const non_op = "does not process I/O";
|
|
const char * const operat = "processes I/O";
|
|
const char * cp;
|
|
|
|
printf(" Total NVM capacity: 0 bytes\n");
|
|
npss = dinp[263] + 1;
|
|
up = dinp + 2048;
|
|
for (k = 0; k < npss; ++k, up += 32) {
|
|
n = sg_get_unaligned_le16(up + 0);
|
|
n *= (0x1 & up[3]) ? 1 : 100; /* unit: 100 microWatts */
|
|
j = n / 10; /* unit: 1 milliWatts */
|
|
m = j % 1000;
|
|
j /= 1000;
|
|
cp = (0x2 & up[3]) ? non_op : operat;
|
|
printf(" Power state %u: Max power: ", k);
|
|
if (0 == j) {
|
|
m = n % 10;
|
|
n /= 10;
|
|
printf("%u.%u milliWatts, %s\n", n, m, cp);
|
|
} else
|
|
printf("%u.%03u Watts, %s\n", j, m, cp);
|
|
n = sg_get_unaligned_le32(up + 4);
|
|
if (0 == n)
|
|
printf(" [ENLAT], ");
|
|
else
|
|
printf(" ENLAT=%u, ", n);
|
|
n = sg_get_unaligned_le32(up + 8);
|
|
if (0 == n)
|
|
printf("[EXLAT], ");
|
|
else
|
|
printf("EXLAT=%u, ", n);
|
|
n = 0x1f & up[12];
|
|
printf("RRT=%u, ", n);
|
|
n = 0x1f & up[13];
|
|
printf("RRL=%u, ", n);
|
|
n = 0x1f & up[14];
|
|
printf("RWT=%u, ", n);
|
|
n = 0x1f & up[15];
|
|
printf("RWL=%u\n", n);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char * rperf[] = {"Best", "Better", "Good", "Degraded"};
|
|
|
|
static void
|
|
show_nvme_id_ns(const uint8_t * dinp, uint32_t nsid, const char *dev_name,
|
|
int do_long)
|
|
{
|
|
bool got_eui_128 = false;
|
|
uint32_t u, k, off, num_lbaf, flbas, flba_info, md_size, lb_size;
|
|
uint64_t ns_sz, eui_64;
|
|
|
|
printf("Identify namespace %u for %s:\n", nsid, dev_name);
|
|
num_lbaf = dinp[25] + 1; /* spec says this is "0's based value" */
|
|
flbas = dinp[26] & 0xf; /* index of active LBA format (for this ns) */
|
|
ns_sz = sg_get_unaligned_le64(dinp + 0);
|
|
eui_64 = sg_get_unaligned_be64(dinp + 120); /* N.B. EUI is big endian */
|
|
if (! sg_all_zeros(dinp + 104, 16))
|
|
got_eui_128 = true;
|
|
printf(" Namespace size/capacity: %" PRIu64 "/%" PRIu64
|
|
" blocks\n", ns_sz, sg_get_unaligned_le64(dinp + 8));
|
|
printf(" Namespace utilization: %" PRIu64 " blocks\n",
|
|
sg_get_unaligned_le64(dinp + 16));
|
|
if (got_eui_128) { /* N.B. big endian */
|
|
printf(" NGUID: 0x%02x", dinp[104]);
|
|
for (k = 1; k < 16; ++k)
|
|
printf("%02x", dinp[104 + k]);
|
|
printf("\n");
|
|
} else if (do_long)
|
|
printf(" NGUID: 0x0\n");
|
|
if (eui_64)
|
|
printf(" EUI-64: 0x%" PRIx64 "\n", eui_64); /* N.B. big endian */
|
|
printf(" Number of LBA formats: %u\n", num_lbaf);
|
|
printf(" Index LBA size: %u\n", flbas);
|
|
for (k = 0, off = 128; k < num_lbaf; ++k, off += 4) {
|
|
printf(" LBA format %u support:", k);
|
|
if (k == flbas)
|
|
printf(" <-- active\n");
|
|
else
|
|
printf("\n");
|
|
flba_info = sg_get_unaligned_le32(dinp + off);
|
|
md_size = flba_info & 0xffff;
|
|
lb_size = flba_info >> 16 & 0xff;
|
|
if (lb_size > 31) {
|
|
pr2serr("%s: logical block size exponent of %u implies a LB "
|
|
"size larger than 4 billion bytes, ignore\n", __func__,
|
|
lb_size);
|
|
continue;
|
|
}
|
|
lb_size = 1U << lb_size;
|
|
ns_sz *= lb_size;
|
|
ns_sz /= 500*1000*1000;
|
|
if (ns_sz & 0x1)
|
|
ns_sz = (ns_sz / 2) + 1;
|
|
else
|
|
ns_sz = ns_sz / 2;
|
|
u = (flba_info >> 24) & 0x3;
|
|
printf(" Logical block size: %u bytes\n", lb_size);
|
|
printf(" Approximate namespace size: %" PRIu64 " GB\n", ns_sz);
|
|
printf(" Metadata size: %u bytes\n", md_size);
|
|
printf(" Relative performance: %s [0x%x]\n", rperf[u], u);
|
|
}
|
|
}
|
|
|
|
/* Invokes a NVMe Admin command via sg_utils library pass-through that will
|
|
* potentially fetch data from the device (din). Returns 0 -> success,
|
|
* various SG_LIB_* positive values or negated errno values.
|
|
* SG_LIB_NVME_STATUS is returned if the NVMe status is non-zero. */
|
|
static int
|
|
nvme_din_admin_cmd(struct sg_pt_base * ptvp, const uint8_t *cmdp,
|
|
uint32_t cmd_len, const char *cmd_str, uint8_t *dip,
|
|
int di_len, int timeout_ms, uint16_t *sct_scp, int vb)
|
|
{
|
|
int res, k;
|
|
uint16_t sct_sc = 0;
|
|
uint32_t result, clen;
|
|
uint8_t sense_b[SENSE_BUFF_NVME_LEN] SG_C_CPP_ZERO_INIT;
|
|
uint8_t ucmd[128];
|
|
char b[32];
|
|
|
|
snprintf(b, sizeof(b), "%s", cmd_str);
|
|
clen = (cmd_len > sizeof(ucmd)) ? sizeof(ucmd) : cmd_len;
|
|
memcpy(ucmd, cmdp, clen);
|
|
if (vb > 1) {
|
|
pr2serr(" %s cdb:\n", b);
|
|
hex2stderr(ucmd, clen, -1);
|
|
}
|
|
set_scsi_pt_cdb(ptvp, ucmd, clen);
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
if (dip && (di_len > 0))
|
|
set_scsi_pt_data_in(ptvp, dip, di_len);
|
|
res = do_scsi_pt(ptvp, -1, -timeout_ms, vb);
|
|
if (res) {
|
|
if (res < 0) {
|
|
res = sg_convert_errno(-res);
|
|
goto err_out;
|
|
} else {
|
|
if (SCSI_PT_DO_BAD_PARAMS == res)
|
|
pr2serr("%s: bad parameters to do_scsi_pt()\n", __func__);
|
|
else if (SCSI_PT_DO_TIMEOUT == res)
|
|
pr2serr("%s: timeout in do_scsi_pt()\n", __func__);
|
|
else if (SCSI_PT_DO_NVME_STATUS == res) {
|
|
sct_sc = get_scsi_pt_status_response(ptvp);
|
|
res = SG_LIB_NVME_STATUS;
|
|
goto nvme_status_err;
|
|
} else
|
|
pr2serr("%s: unknown error (%d) from do_scsi_pt()\n",
|
|
__func__, res);
|
|
}
|
|
res = SG_LIB_FILE_ERROR;
|
|
goto err_out;
|
|
}
|
|
|
|
if ((vb > 2) && dip && di_len) {
|
|
k = get_scsi_pt_resid(ptvp);
|
|
pr2serr(" Data in buffer [%d bytes]:\n", di_len - k);
|
|
if (di_len > k)
|
|
hex2stderr(dip, di_len - k, -1);
|
|
if (vb > 3)
|
|
pr2serr(" do_scsi_pt(nvme): res=%d resid=%d\n", res, k);
|
|
}
|
|
sct_sc = get_scsi_pt_status_response(ptvp);
|
|
result = get_pt_result(ptvp);
|
|
k = get_scsi_pt_sense_len(ptvp);
|
|
if (vb) {
|
|
pr2serr("Status: 0x%x [SCT<<8 + SC], Result: 0x%x, Completion Q:\n",
|
|
sct_sc, result);
|
|
if (k > 0)
|
|
hex2stderr(sense_b, k, -1);
|
|
}
|
|
nvme_status_err:
|
|
if (sct_scp)
|
|
*sct_scp = sct_sc;
|
|
err_out:
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
std_inq_decode(const char * prefix, uint8_t * b, int len, int vb)
|
|
{
|
|
int pqual, n;
|
|
|
|
if (len < 4)
|
|
return;
|
|
pqual = (b[0] & 0xe0) >> 5;
|
|
if (0 == pqual)
|
|
printf("%s:\n", prefix);
|
|
else if (1 == pqual)
|
|
printf("%s: [qualifier indicates no connected LU]\n", prefix);
|
|
else if (3 == pqual)
|
|
printf("%s: [qualifier indicates not capable of supporting LU]\n",
|
|
prefix);
|
|
else
|
|
printf("%s: [reserved or vendor specific qualifier [%d]]\n",
|
|
prefix, pqual);
|
|
printf(" PQual=%d Device_type=%d RMB=%d LU_CONG=%d "
|
|
"version=0x%02x ", pqual, b[0] & 0x1f, !!(b[1] & 0x80),
|
|
!!(b[1] & 0x40), (unsigned int)b[2]);
|
|
printf(" [%s]\n", sg_ansi_version_arr[b[2] & 0xf]);
|
|
printf(" [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d "
|
|
" Resp_data_format=%d\n",
|
|
!!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20),
|
|
!!(b[3] & 0x10), b[3] & 0x0f);
|
|
if (len < 5)
|
|
return;
|
|
n = b[4] + 5;
|
|
if (vb)
|
|
pr2serr(">> requested %d bytes, %d bytes available\n", len, n);
|
|
printf(" SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d ",
|
|
!!(b[5] & 0x80), !!(b[5] & 0x40), ((b[5] & 0x30) >> 4),
|
|
!!(b[5] & 0x08), !!(b[5] & 0x01));
|
|
printf(" [BQue=%d]\n EncServ=%d ", !!(b[6] & 0x80),
|
|
!!(b[6] & 0x40));
|
|
if (b[6] & 0x10)
|
|
printf("MultiP=1 (VS=%d) ", !!(b[6] & 0x20));
|
|
else
|
|
printf("MultiP=0 ");
|
|
printf("[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n [RelAdr=%d] ",
|
|
!!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01),
|
|
!!(b[7] & 0x80));
|
|
printf("WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ",
|
|
!!(b[7] & 0x20), !!(b[7] & 0x10), !!(b[7] & 0x08),
|
|
!!(b[7] & 0x04));
|
|
printf("CmdQue=%d\n", !!(b[7] & 0x02));
|
|
if (len < 36)
|
|
return;
|
|
printf(" Vendor_identification: %.8s\n", b + 8);
|
|
printf(" Product_identification: %.16s\n", b + 16);
|
|
printf(" Product_revision_level: %.4s\n", b + 32);
|
|
}
|
|
|
|
/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
|
|
* successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
|
|
* The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
|
|
* an argument to set it has been removed (use the REPORT SUPPORTED OPERATION
|
|
* CODES command instead). Adds the ability to set the command abort timeout
|
|
* and the ability to report the residual count. If timeout_secs is zero
|
|
* the default command abort timeout (60 seconds) is used.
|
|
* If residp is non-NULL then the residual value is written where residp
|
|
* points. A residual value of 0 implies mx_resp_len bytes have be written
|
|
* where resp points. If the residual value equals mx_resp_len then no
|
|
* bytes have been written. */
|
|
static int
|
|
sg_scsi_inquiry(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp,
|
|
int mx_resp_len, int timeout_secs, int * residp,
|
|
bool noisy, int vb)
|
|
{
|
|
int res, ret, k, sense_cat, resid;
|
|
uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
|
|
uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
|
|
uint8_t * up;
|
|
|
|
if (evpd)
|
|
inq_cdb[1] |= 1;
|
|
inq_cdb[2] = (uint8_t)pg_op;
|
|
sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
|
|
if (vb > 1) {
|
|
pr2serr(" INQUIRY cdb: ");
|
|
for (k = 0; k < INQUIRY_CMDLEN; ++k)
|
|
pr2serr("%02x ", inq_cdb[k]);
|
|
pr2serr("\n");
|
|
}
|
|
if (resp && (mx_resp_len > 0)) {
|
|
up = (uint8_t *)resp;
|
|
up[0] = 0x7f; /* defensive prefill */
|
|
if (mx_resp_len > 4)
|
|
up[4] = 0;
|
|
}
|
|
if (timeout_secs == 0)
|
|
timeout_secs = DEF_TIMEOUT_SECS;
|
|
set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, -1, timeout_secs, vb);
|
|
ret = sg_cmds_process_resp(ptvp, "inquiry", res, noisy, vb, &sense_cat);
|
|
resid = get_scsi_pt_resid(ptvp);
|
|
if (residp)
|
|
*residp = resid;
|
|
if (-1 == ret)
|
|
;
|
|
else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else if (ret < 4) {
|
|
if (vb)
|
|
pr2serr("%s: got too few bytes (%d)\n", __func__, ret);
|
|
ret = SG_LIB_CAT_MALFORMED;
|
|
} else
|
|
ret = 0;
|
|
|
|
if (resid > 0) {
|
|
if (resid > mx_resp_len) {
|
|
pr2serr("INQUIRY resid (%d) should never exceed requested "
|
|
"len=%d\n", resid, mx_resp_len);
|
|
return ret ? ret : SG_LIB_CAT_MALFORMED;
|
|
}
|
|
/* zero unfilled section of response buffer */
|
|
memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
main(int argc, char * argv[])
|
|
{
|
|
bool do_all = false;
|
|
bool do_dev_id_vpd = false;
|
|
bool do_id_ctl = false;
|
|
bool do_id_ns = false;
|
|
bool do_self_test = false;
|
|
bool flagged = false;
|
|
bool is_nvme = false;
|
|
int res, c, n, resid, off, len, ln, k, q, num;
|
|
int curr_dev_name_pos = 0;
|
|
int do_long = 0;
|
|
int maxlen = INQUIRY_MAX_RESP_LEN;
|
|
int self_test = 0;
|
|
int sg_fd = -1;
|
|
int ret = 0;
|
|
int timeout_ms = DEF_TIMEOUT_SECS * 1000;
|
|
int vb = 0;
|
|
uint32_t nsid = 0;
|
|
uint32_t dn_nsid, al_size;
|
|
uint32_t pg_sz = sg_get_page_size();
|
|
int64_t ll;
|
|
uint8_t * al_buff = NULL;
|
|
uint8_t * free_al_buff = NULL;
|
|
uint8_t * bp;
|
|
const char * device_name = NULL;
|
|
const char * cp;
|
|
struct sg_pt_base * ptvp = NULL;
|
|
char cmd_name[32];
|
|
char b[2048];
|
|
|
|
while (1) {
|
|
int option_index = 0;
|
|
|
|
c = getopt_long(argc, argv, "cdhlm:n:s:t:vV", long_options,
|
|
&option_index);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c) {
|
|
case 'c':
|
|
strcpy(cmd_name, "Identify(ctl)");
|
|
do_id_ctl = true;
|
|
break;
|
|
case 'd':
|
|
strcpy(cmd_name, "INQUIRY(vpd=0x83)");
|
|
do_dev_id_vpd = true;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
usage();
|
|
return 0;
|
|
case 'l':
|
|
++do_long;
|
|
break;
|
|
case 'm':
|
|
maxlen = sg_get_num(optarg);
|
|
if (maxlen < 0) {
|
|
pr2serr("bad argument to '--maxlen='\n");
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
break;
|
|
case 'n':
|
|
if ((2 == strlen(optarg)) && (0 == memcmp("-1", optarg, 2))) {
|
|
nsid = NVME_NSID_ALL; /* treat '-1' as (2**32 - 1) */
|
|
break;
|
|
}
|
|
ll = sg_get_llnum(optarg);
|
|
if ((ll < 0) || (ll > UINT32_MAX)) {
|
|
pr2serr("bad argument to '--nsid', accept 0 to 0xffffffff\n");
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
strcpy(cmd_name, "Identify(ns)");
|
|
nsid = (uint32_t)ll;
|
|
do_id_ns = true;
|
|
break;
|
|
case 's':
|
|
self_test = sg_get_num(optarg);
|
|
if (self_test < 0) {
|
|
pr2serr("bad argument to '--self-test=', expect 0 or "
|
|
"higher\n");
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
strcpy(cmd_name, "Device self-test");
|
|
do_self_test = true;
|
|
break;
|
|
case 't':
|
|
timeout_ms = sg_get_num(optarg);
|
|
if (timeout_ms < 0) {
|
|
pr2serr("bad argument to '--to-ms=', expect 0 or higher\n");
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
break;
|
|
case 'v':
|
|
++vb;
|
|
break;
|
|
case 'V':
|
|
pr2serr(ME "version: %s\n", version_str);
|
|
return 0;
|
|
default:
|
|
pr2serr("unrecognised option code 0x%x ??\n", c);
|
|
usage();
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
}
|
|
if (optind < argc) {
|
|
for (; optind < argc; ++optind) {
|
|
if (next_dev_name_pos >= MAX_DEV_NAMES) {
|
|
pr2serr("Only accepts %d DEVICE names\n", MAX_DEV_NAMES);
|
|
usage();
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
dev_name_arr[next_dev_name_pos++] = argv[optind];
|
|
}
|
|
}
|
|
|
|
if (next_dev_name_pos < 1) {
|
|
pr2serr("Need at least one DEVICE, can have up to %d\n\n",
|
|
MAX_DEV_NAMES);
|
|
usage();
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
}
|
|
|
|
if (do_self_test && do_id_ns)
|
|
do_id_ns = false; /* self-test with DW10 set to nsid */
|
|
n = (int)do_id_ctl + (int)do_id_ns + (int)do_dev_id_vpd +
|
|
(int)do_self_test;
|
|
if (n > 1) {
|
|
pr2serr("can only have one of --ctl, --dev-id, --nsid= and "
|
|
"--self-test=\n\n");
|
|
usage();
|
|
return SG_LIB_SYNTAX_ERROR;
|
|
} else if (0 == n) {
|
|
do_id_ns = true;
|
|
strcpy(cmd_name, "Identify(ns)");
|
|
}
|
|
|
|
al_size = ((uint32_t)maxlen > pg_sz) ? (uint32_t)maxlen : pg_sz;
|
|
al_buff = sg_memalign(al_size, pg_sz, &free_al_buff, vb > 3);
|
|
if (NULL == al_buff) {
|
|
pr2serr("out of memory allocating page sized buffer (of %u bytes)\n",
|
|
al_size);
|
|
return SG_LIB_OS_BASE_ERR + ENOMEM;
|
|
}
|
|
device_name = dev_name_arr[curr_dev_name_pos++];
|
|
sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
|
|
if (sg_fd < 0) {
|
|
pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
|
|
ret = SG_LIB_FILE_ERROR;
|
|
flagged = true;
|
|
goto fini;
|
|
}
|
|
n = check_pt_file_handle(sg_fd, device_name, vb);
|
|
if (n < 0) {
|
|
pr2serr("check_pt_file_handle error: %s: %s\n", device_name,
|
|
safe_strerror(-n));
|
|
flagged = true;
|
|
goto fini;
|
|
}
|
|
cp = NULL;
|
|
switch (n) {
|
|
case 0:
|
|
cp = "Unidentified device (SATA disk ?)";
|
|
break;
|
|
case 1:
|
|
cp = "SCSI char device (e.g. in Linux: sg or bsg device)";
|
|
break;
|
|
case 2:
|
|
cp = "SCSI block device (e.g. in FreeBSD: /dev/da0)";
|
|
break;
|
|
case 3:
|
|
cp = "NVMe char device (e.g. in Linux: /dev/nvme0)";
|
|
break;
|
|
case 4:
|
|
cp = "NVMe block device (e.g. in FreeBSD: /dev/nvme0ns1)";
|
|
break;
|
|
default:
|
|
pr2serr("Strange value from check_pt_file_handle() --> %d\n", n);
|
|
break;
|
|
}
|
|
if (cp && (vb || (do_long > 0)))
|
|
pr2serr("%s\n", cp);
|
|
|
|
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
|
|
if (NULL == ptvp) {
|
|
pr2serr("%s: out of memory\n", b);
|
|
ret = sg_convert_errno(ENOMEM);
|
|
goto fini;
|
|
}
|
|
k = get_scsi_pt_os_err(ptvp);
|
|
if (k) {
|
|
pr2serr("OS error from construct_scsi_pt_obj_with_fd(): %s\n",
|
|
safe_strerror(k));
|
|
ret = sg_convert_errno(k);
|
|
goto fini;
|
|
}
|
|
|
|
/* Loop over all given DEVICEs */
|
|
for (q = 0; q < MAX_DEV_NAMES; ++q) {
|
|
is_nvme = pt_device_is_nvme(ptvp);
|
|
if ((curr_dev_name_pos > 1) && vb)
|
|
pr2serr("Device %d [%s] seems to be %s\n", q + 1, device_name,
|
|
is_nvme ? "NVMe" : "SCSI or ATA");
|
|
resid = 0;
|
|
if (do_dev_id_vpd || (! is_nvme)) {
|
|
if (do_dev_id_vpd)
|
|
ret = sg_scsi_inquiry(ptvp, true /* evpd */, VPD_DEVICE_ID,
|
|
al_buff, maxlen, timeout_ms / 1000,
|
|
&resid, true, vb);
|
|
else /* do a standard INQUIRY */
|
|
ret = sg_scsi_inquiry(ptvp, false /* evpd */, 0, al_buff,
|
|
maxlen, timeout_ms / 1000, &resid, true,
|
|
vb);
|
|
if (ret) {
|
|
pr2serr("SCSI INQUIRY(%s) failed\n",
|
|
do_dev_id_vpd ? "dev_id" : "standard");
|
|
goto fini;
|
|
}
|
|
len = maxlen - resid;
|
|
if (len < 4) {
|
|
pr2serr("Something wrong with data-in, len=%d (resid=%d)\n",
|
|
len, resid);
|
|
goto fini;
|
|
}
|
|
if (do_dev_id_vpd) {
|
|
printf(" Device %d [%s] identification VPD:\n", q + 1,
|
|
device_name);
|
|
for (off = -1, bp = al_buff + 4, ln = len - 4;
|
|
0 == sg_vpd_dev_id_iter(bp, ln, &off, -1, -1, -1); ) {
|
|
n = sg_get_designation_descriptor_str(" ", bp + off,
|
|
bp[off + 3] + 4, do_long,
|
|
do_long > 1, sizeof(b), b);
|
|
if (n > 0)
|
|
printf("%s", b);
|
|
}
|
|
} else {
|
|
snprintf(b, sizeof(b), " Device %d [%s] Standard INQUIRY:",
|
|
q + 1, device_name);
|
|
std_inq_decode(b, al_buff, len, vb);
|
|
}
|
|
clear_scsi_pt_obj(ptvp);
|
|
} else { /* NVME Identify or Device self-test */
|
|
bool this_ctl = false;
|
|
uint16_t sct_sc = 0;
|
|
uint32_t max_nsid;
|
|
struct sg_nvme_passthru_cmd n_cmd;
|
|
|
|
if ((! do_self_test) && (NVME_NSID_ALL == nsid))
|
|
do_all = true;
|
|
num = 1; /* preliminary, may alter */
|
|
for (k = 0; k < num; ++k) {
|
|
bp = (uint8_t *)&n_cmd;
|
|
memset(bp, 0, sizeof(n_cmd));
|
|
if (do_self_test) {
|
|
n_cmd.opcode = 0x14; /* Device self-test */
|
|
n_cmd.nsid = nsid;
|
|
n_cmd.cdw10 = self_test;
|
|
if (0 == k) {
|
|
if (0 == nsid)
|
|
printf("Starting Device self-test for controller "
|
|
"only\n");
|
|
else if (do_all)
|
|
printf("Starting Device self-test for controller "
|
|
"and all namespaces\n");
|
|
else
|
|
printf("Starting Device self-test for controller "
|
|
"and namespace %u\n", nsid);
|
|
}
|
|
} else { /* one or more variants of Identify */
|
|
n_cmd.opcode = 0x6; /* Identify */
|
|
dn_nsid = get_pt_nvme_nsid(ptvp);
|
|
if ((0 == k) && (do_id_ctl || (0 == nsid) || do_all)) {
|
|
n_cmd.cdw10 = 0x1; /* Controller */
|
|
this_ctl = true;
|
|
} else {
|
|
n_cmd.cdw10 = 0x0; /* Namespace */
|
|
if (do_all)
|
|
n_cmd.nsid = k;
|
|
else if (nsid > 0)
|
|
n_cmd.nsid = nsid;
|
|
else if (dn_nsid > 0)
|
|
n_cmd.nsid = dn_nsid;
|
|
else
|
|
break;
|
|
this_ctl = false;
|
|
}
|
|
sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)al_buff,
|
|
bp + SG_NVME_PT_ADDR);
|
|
sg_put_unaligned_le32(pg_sz, bp + SG_NVME_PT_DATA_LEN);
|
|
}
|
|
ret = nvme_din_admin_cmd(ptvp, (const uint8_t *)&n_cmd,
|
|
sizeof(n_cmd), cmd_name, al_buff,
|
|
pg_sz, timeout_ms, &sct_sc, vb);
|
|
if (sct_sc || (SG_LIB_NVME_STATUS == ret)) {
|
|
sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b);
|
|
pr2serr("%s: %s\n", cmd_name, b);
|
|
flagged = true;
|
|
goto fini;
|
|
}
|
|
if (ret)
|
|
goto fini;
|
|
if (0x6 == n_cmd.opcode) {
|
|
if (this_ctl) {
|
|
show_nvme_id_ctl(al_buff, device_name, do_long,
|
|
&max_nsid);
|
|
num = max_nsid + 1;
|
|
} else
|
|
show_nvme_id_ns(al_buff, n_cmd.nsid, device_name,
|
|
do_long);
|
|
}
|
|
|
|
clear_scsi_pt_obj(ptvp);
|
|
if (do_self_test)
|
|
break;
|
|
if (do_id_ctl)
|
|
break;
|
|
} /* end of for loop */
|
|
}
|
|
ret = 0;
|
|
|
|
if (sg_fd >= 0) {
|
|
res = sg_cmds_close_device(sg_fd);
|
|
if (res < 0) {
|
|
pr2serr("close error: %s\n", safe_strerror(-res));
|
|
ret = sg_convert_errno(-res);
|
|
break;
|
|
}
|
|
sg_fd = -1;
|
|
}
|
|
if (ret)
|
|
break;
|
|
if (curr_dev_name_pos < next_dev_name_pos)
|
|
device_name = dev_name_arr[curr_dev_name_pos++];
|
|
else
|
|
break;
|
|
if (NULL == device_name) {
|
|
pr2serr("Unexpected NULL device name at pos=%d\n",
|
|
curr_dev_name_pos - 1);
|
|
ret = sg_convert_errno(EINVAL);
|
|
flagged = true;
|
|
break;
|
|
}
|
|
sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
|
|
if (sg_fd < 0) {
|
|
pr2serr(ME "open error: %s: %s\n", device_name,
|
|
safe_strerror(-sg_fd));
|
|
ret = sg_convert_errno(-sg_fd);
|
|
flagged = true;
|
|
break;
|
|
}
|
|
k = set_pt_file_handle(ptvp, sg_fd, vb);
|
|
if (k) {
|
|
ret = sg_convert_errno(k);
|
|
pr2serr("set_pt_file_handle() failed: %s\n", safe_strerror(k));
|
|
flagged = true;
|
|
break;
|
|
}
|
|
printf("\n");
|
|
} /* end of "q" outer for loop */
|
|
fini:
|
|
if (ptvp) {
|
|
destruct_scsi_pt_obj(ptvp);
|
|
ptvp = NULL;
|
|
}
|
|
if (free_al_buff)
|
|
free(free_al_buff);
|
|
if (sg_fd >= 0) {
|
|
res = sg_cmds_close_device(sg_fd);
|
|
if (res < 0) {
|
|
pr2serr("close error: %s\n", safe_strerror(-res));
|
|
if (0 == ret)
|
|
return SG_LIB_FILE_ERROR;
|
|
}
|
|
}
|
|
if (ret && (0 == vb) && (! flagged)) {
|
|
if (! sg_if_can2stderr("", ret))
|
|
pr2serr("Some error occurred [%d]\n", ret);
|
|
}
|
|
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
|
|
}
|