diff --git a/smartmontools/ChangeLog b/smartmontools/ChangeLog index 44b7f7afbd2a2aaf55b284c8064e80f696f6d42f..37fadd56a388b36249b32b44196d9d4f595c4fc2 100644 --- a/smartmontools/ChangeLog +++ b/smartmontools/ChangeLog @@ -1,5 +1,10 @@ $Id$ +2018-12-05 Harry Mallon <hjmallon@gmail.com> + + Add '-d sntjmicron[,NSID]' device type for NVMe drives behind + JMicron USB to NVMe bridges (JMS583). + 2018-12-04 Christian Franke <franke@computer.org> os_linux.cpp: Add '-d by-id' option to device scanning. diff --git a/smartmontools/Makefile.am b/smartmontools/Makefile.am index f63a90212ea372ed5fc57e3f56d84c8866f0dc6f..e1bc6c6cecfe222ac93820b0beda7254f1a55388 100644 --- a/smartmontools/Makefile.am +++ b/smartmontools/Makefile.am @@ -80,6 +80,7 @@ smartctl_SOURCES = \ scsicmds.cpp \ scsicmds.h \ scsiata.cpp \ + scsinvme.cpp \ scsiprint.cpp \ scsiprint.h \ utility.cpp \ @@ -152,6 +153,7 @@ smartd_SOURCES = \ scsicmds.cpp \ scsicmds.h \ scsiata.cpp \ + scsinvme.cpp \ utility.cpp \ utility.h \ sg_unaligned.h diff --git a/smartmontools/atacmds.h b/smartmontools/atacmds.h index 005121e7e63e3f884429245f6984af34158e766c..71e37b285450a89d1c0c81ca72ce451a197314e6 100644 --- a/smartmontools/atacmds.h +++ b/smartmontools/atacmds.h @@ -1004,17 +1004,6 @@ void ata_format_id_string(char * out, const unsigned char * in, int n); // Utility routines. unsigned char checksum(const void * data); -void swap2(char *location); -void swap4(char *location); -void swap8(char *location); -// Typesafe variants using overloading -inline void swapx(unsigned short * p) - { swap2((char*)p); } -inline void swapx(unsigned int * p) - { swap4((char*)p); } -inline void swapx(uint64_t * p) - { swap8((char*)p); } - // Return pseudo-device to parse "smartctl -r ataioctl,2 ..." output // and simulate an ATA device with same behaviour ata_device * get_parsed_ata_device(smart_interface * intf, const char * dev_name); diff --git a/smartmontools/dev_interface.cpp b/smartmontools/dev_interface.cpp index 683059474cd37ab5861df35f7ddb3d7bbc44eec5..13e0418f78e30355a7328feef83dd6b87aa89da8 100644 --- a/smartmontools/dev_interface.cpp +++ b/smartmontools/dev_interface.cpp @@ -14,6 +14,7 @@ #include "dev_intelliprop.h" #include "dev_tunnelled.h" #include "atacmds.h" // ATA_SMART_CMD/STATUS +#include "scsicmds.h" // scsi_cmnd_io #include "utility.h" #include <errno.h> @@ -196,6 +197,38 @@ bool ata_device::ata_identify_is_cached() const return false; } +///////////////////////////////////////////////////////////////////////////// +// scsi_device + +bool scsi_device::scsi_pass_through_and_check(scsi_cmnd_io * iop, + const char * msg) +{ + // Provide sense buffer + unsigned char sense[32] = {0, }; + iop->sensep = sense; + iop->max_sense_len = sizeof(sense); + iop->timeout = SCSI_TIMEOUT_DEFAULT; + + // Run cmd + if (!scsi_pass_through(iop)) { + if (scsi_debugmode > 0) + pout("%sscsi_pass_through() failed, errno=%d [%s]\n", + msg, get_errno(), get_errmsg()); + return false; + } + + // Check sense + scsi_sense_disect sinfo; + scsi_do_sense_disect(iop, &sinfo); + int err = scsiSimpleSenseFilter(&sinfo); + if (err) { + if (scsi_debugmode > 0) + pout("%sscsi error: %s\n", msg, scsiErrString(err)); + return set_err(EIO, "scsi error %s", scsiErrString(err)); + } + + return true; +} ///////////////////////////////////////////////////////////////////////////// // nvme_device @@ -276,7 +309,8 @@ std::string smart_interface::get_valid_dev_types_str() // default std::string s = "ata, scsi[+TYPE], nvme[,NSID], sat[,auto][,N][+TYPE], usbcypress[,X], " - "usbjmicron[,p][,x][,N], usbprolific, usbsunplus, intelliprop,N[+TYPE]"; + "usbjmicron[,p][,x][,N], usbprolific, usbsunplus, sntjmicron[,NSID], " + "intelliprop,N[+TYPE]"; // append custom std::string s2 = get_valid_custom_dev_types_str(); if (!s2.empty()) { @@ -417,6 +451,16 @@ smart_device * smart_interface::get_smart_device(const char * name, const char * return get_sat_device(sattype.c_str(), basedev.release()->to_scsi()); } + else if (str_starts_with(type, "snt")) { + smart_device_auto_ptr basedev( get_smart_device(name, "scsi") ); + if (!basedev) { + set_err(EINVAL, "Type '%s': %s", type, get_errmsg()); + return 0; + } + + return get_snt_device(type, basedev.release()->to_scsi()); + } + else if (str_starts_with(type, "intelliprop")) { // Parse "intelliprop,N[+base...]" unsigned phydrive = ~0; int n = -1; char c = 0; @@ -491,3 +535,12 @@ std::string smart_interface::get_valid_custom_dev_types_str() { return ""; } + +smart_device * smart_interface::get_scsi_passthrough_device(const char * type, scsi_device * scsidev) +{ + if (!strncmp(type, "snt", 3)) { + return get_snt_device(type, scsidev); + } + + return get_sat_device(type, scsidev); +} diff --git a/smartmontools/dev_interface.h b/smartmontools/dev_interface.h index f06e8c42f05e092611b5a308157867b0b74447b0..d561b5231ce3a3efb5bfdb11e88abe5353890d4a 100644 --- a/smartmontools/dev_interface.h +++ b/smartmontools/dev_interface.h @@ -584,6 +584,10 @@ public: /// Returns false on error. virtual bool scsi_pass_through(scsi_cmnd_io * iop) = 0; + // Call scsi_pass_through and check sense. + bool scsi_pass_through_and_check(scsi_cmnd_io * iop, + const char * msg = ""); + /// Always try READ CAPACITY(10) (rcap10) first but once we know /// rcap16 is needed, use it instead. void set_rcap16_first() @@ -998,6 +1002,11 @@ protected: /// Default implementation returns empty string. virtual std::string get_valid_custom_dev_types_str(); + /// Return ATA->SCSI of NVMe->SCSI filter for a SAT, SNT or USB 'type'. + /// Uses get_sat_device and get_snt_device. + /// Return 0 and delete 'scsidev' on error. + virtual smart_device * get_scsi_passthrough_device(const char * type, scsi_device * scsidev); + /// Return ATA->SCSI filter for a SAT or USB 'type'. /// Device 'scsidev' is used for SCSI access. /// Return 0 and delete 'scsidev' on error. @@ -1005,6 +1014,13 @@ protected: virtual ata_device * get_sat_device(const char * type, scsi_device * scsidev); //{ implemented in scsiata.cpp } + /// Return NVMe->SCSI filter for a SNT or USB 'type'. + /// Device 'scsidev' is used for SCSI access. + /// Return 0 and delete 'scsidev' on error. + /// Override only if platform needs special handling. + virtual nvme_device * get_snt_device(const char * type, scsi_device * scsidev); + //{ implemented in scsinvme.cpp } + public: /// Try to detect a SAT device behind a SCSI interface. /// Inquiry data can be passed if available. diff --git a/smartmontools/dev_tunnelled.h b/smartmontools/dev_tunnelled.h index a89a333c07d3d66e64ab047a55491dc644768156..9d29528301ab87cb0fd4cf3beffbd04a916e438b 100644 --- a/smartmontools/dev_tunnelled.h +++ b/smartmontools/dev_tunnelled.h @@ -64,6 +64,14 @@ protected: m_tunnel_dev(tunnel_dev) { } + // For nvme_device + explicit tunnelled_device(tunnel_device_type * tunnel_dev, unsigned nsid) + : smart_device(smart_device::never_called), + BaseDev(nsid), + tunnelled_device_base(tunnel_dev), + m_tunnel_dev(tunnel_dev) + { } + public: virtual void release(const smart_device * dev) { diff --git a/smartmontools/drivedb.h b/smartmontools/drivedb.h index 899be6b5f7fc07c57c39abad54729050b4918ce2..1d660a86736e8661d1d1e6dc35fae58075711f56 100644 --- a/smartmontools/drivedb.h +++ b/smartmontools/drivedb.h @@ -4931,6 +4931,13 @@ const drive_settings builtin_knowndrives[] = { "", "-d sat" }, +// sntjmicron is currently in beta +// { "USB: ; JMicron JMS583", // USB->PCIe (NVMe) +// "0x152d:0x0583", +// "", +// "", +// "-d sntjmicron" +// }, { "USB: OCZ THROTTLE OCZESATATHR8G; JMicron JMF601", "0x152d:0x0602", "", diff --git a/smartmontools/os_freebsd.cpp b/smartmontools/os_freebsd.cpp index 4ca03eb05615c379c31d0986bd981a591c7ce5e2..c013e5e8c9d307c5407c27a369bc4458bb5a62a2 100644 --- a/smartmontools/os_freebsd.cpp +++ b/smartmontools/os_freebsd.cpp @@ -2013,7 +2013,7 @@ smart_device * freebsd_smart_interface::autodetect_smart_device(const char * nam if(usbdevlist(bus,vendor_id, product_id, version)){ const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version); if (usbtype) - return get_sat_device(usbtype, new freebsd_scsi_device(this, test_name, "")); + return get_scsi_passthrough_device(usbtype, new freebsd_scsi_device(this, test_name, "")); } return 0; } diff --git a/smartmontools/os_linux.cpp b/smartmontools/os_linux.cpp index f30da053491bcb4a52b424b25396d949662bf571..1c3d094301e36a915cf009d168e7269a1dbe8e7f 100644 --- a/smartmontools/os_linux.cpp +++ b/smartmontools/os_linux.cpp @@ -3297,7 +3297,7 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name) // Return SAT/USB device for this type // (Note: linux_scsi_device::autodetect_open() will not be called in this case) - return get_sat_device(usbtype, new linux_scsi_device(this, name, "")); + return get_scsi_passthrough_device(usbtype, new linux_scsi_device(this, name, "")); } // Fail if hpsa driver diff --git a/smartmontools/os_win32.cpp b/smartmontools/os_win32.cpp index bc2d34c99f2fc24e01f009e02e9e3e7ed21c5c09..13b1730f6dc7dda3b945b7e9fe4cb65d4d931687 100644 --- a/smartmontools/os_win32.cpp +++ b/smartmontools/os_win32.cpp @@ -3946,7 +3946,7 @@ protected: virtual std::string get_valid_custom_dev_types_str(); private: - ata_device * get_usb_device(const char * name, int phydrive, int logdrive = -1); + smart_device * get_usb_device(const char * name, int phydrive, int logdrive = -1); }; @@ -4365,7 +4365,7 @@ static win_dev_type get_dev_type(const char * name, int & phydrive, int & logdri } -ata_device * win_smart_interface::get_usb_device(const char * name, +smart_device * win_smart_interface::get_usb_device(const char * name, int phydrive, int logdrive /* = -1 */) { // Get USB bridge ID @@ -4381,7 +4381,7 @@ ata_device * win_smart_interface::get_usb_device(const char * name, return 0; // Return SAT/USB device for this type - return get_sat_device(usbtype, new win_scsi_device(this, name, "")); + return get_scsi_passthrough_device(usbtype, new win_scsi_device(this, name, "")); } smart_device * win_smart_interface::autodetect_smart_device(const char * name) diff --git a/smartmontools/scsiata.cpp b/smartmontools/scsiata.cpp index b4d3e52f15f36e47f5afa0a63f7b23abd314f958..6d74302764032bae94e172c680d67f527a2706ae 100644 --- a/smartmontools/scsiata.cpp +++ b/smartmontools/scsiata.cpp @@ -596,40 +596,6 @@ static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, return 1; } - -// Call scsi_pass_through and check sense. -// TODO: Provide as member function of class scsi_device (?) -static bool scsi_pass_through_and_check(scsi_device * scsidev, scsi_cmnd_io * iop, - const char * msg = "") -{ - // Provide sense buffer - unsigned char sense[32] = {0, }; - iop->sensep = sense; - iop->max_sense_len = sizeof(sense); - iop->timeout = SCSI_TIMEOUT_DEFAULT; - - // Run cmd - if (!scsidev->scsi_pass_through(iop)) { - if (scsi_debugmode > 0) - pout("%sscsi_pass_through() failed, errno=%d [%s]\n", - msg, scsidev->get_errno(), scsidev->get_errmsg()); - return false; - } - - // Check sense - scsi_sense_disect sinfo; - scsi_do_sense_disect(iop, &sinfo); - int err = scsiSimpleSenseFilter(&sinfo); - if (err) { - if (scsi_debugmode > 0) - pout("%sscsi error: %s\n", msg, scsiErrString(err)); - return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err)); - } - - return true; -} - - ///////////////////////////////////////////////////////////////////////////// namespace sat { @@ -1095,7 +1061,7 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd_len = (!m_prolific ? 12 : 14); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbjmicron_device::ata_pass_through: ")) return set_err(scsidev->get_err()); @@ -1170,7 +1136,7 @@ bool usbjmicron_device::get_registers(unsigned short addr, io_hdr.cmnd_len = (!m_prolific ? 12 : 14); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbjmicron_device::get_registers: ")) return set_err(scsidev->get_err()); @@ -1277,7 +1243,7 @@ bool usbprolific_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & o io_hdr.cmnd_len = 16; scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbprolific_device::ata_pass_through: ")) return set_err(scsidev->get_err()); @@ -1296,7 +1262,7 @@ bool usbprolific_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & o io_hdr.cmnd = cdb; io_hdr.cmnd_len = sizeof(cdb); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbprolific_device::scsi_pass_through (get registers): ")) return set_err(scsidev->get_err()); @@ -1384,7 +1350,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd_len = sizeof(cdb); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through (presetting): ")) return set_err(scsidev->get_err()); } @@ -1431,7 +1397,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd_len = sizeof(cdb); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through: ")) // Returns sense key 0x03 (medium error) on ATA command error return set_err(scsidev->get_err()); @@ -1451,7 +1417,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd = cdb; io_hdr.cmnd_len = sizeof(cdb); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through (get registers): ")) return set_err(scsidev->get_err()); diff --git a/smartmontools/scsinvme.cpp b/smartmontools/scsinvme.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f0727c82541b88c0a587a6641c653a2add2d25ac --- /dev/null +++ b/smartmontools/scsinvme.cpp @@ -0,0 +1,250 @@ +/* + * scsinvme.cpp + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2018 Harry Mallon <hjmallon@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include <errno.h> + +#include "dev_interface.h" +#include "dev_tunnelled.h" +#include "scsicmds.h" +#include "sg_unaligned.h" +#include "utility.h" + +// SNT (SCSI NVMe Translation) namespace and prefix +namespace snt { + +#define SNT_JMICRON_NVME_SIGNATURE 0x454d564eu // 'NVME' reversed (little endian) +#define SNT_JMICRON_CDB_LEN 12 +#define SNT_JMICRON_NVM_CMD_LEN 512 + +class sntjmicron_device +: public tunnelled_device< + /*implements*/ nvme_device, + /*by tunnelling through a*/ scsi_device + > +{ +public: + sntjmicron_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, unsigned nsid); + + virtual ~sntjmicron_device() throw(); + + virtual bool open(); + + virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); + +private: + enum { + proto_nvm_cmd = 0x0, proto_non_data = 0x1, proto_dma_in = 0x2, + proto_dma_out = 0x3, proto_response = 0xF + }; +}; + +sntjmicron_device::sntjmicron_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, unsigned nsid) +: smart_device(intf, scsidev->get_dev_name(), "sntjmicron", req_type), + tunnelled_device<nvme_device, scsi_device>(scsidev, nsid) +{ + set_info().info_name = strprintf("%s [USB NVMe JMicron]", scsidev->get_info_name()); +} + +sntjmicron_device::~sntjmicron_device() throw() +{ +} + +bool sntjmicron_device::open() +{ + // Open USB first + if (!tunnelled_device<nvme_device, scsi_device>::open()) + return false; + + // No sure how multiple namespaces come up on device so we + // cannot detect e.g. /dev/sdX is NSID 2. + // Set to broadcast if not available + if (!get_nsid()) { + set_nsid(0xFFFFFFFF); + } + + return true; +} + +// cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1) +// cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ] +// cdb[2]: reserved +// cdb[3]: parameter list length (23:16) +// cdb[4]: parameter list length (15:08) +// cdb[5]: parameter list length (07:00) +// cdb[6]: reserved +// cdb[7]: reserved +// cdb[8]: reserved +// cdb[9]: reserved +// cdb[10]: reserved +// cdb[11]: CONTROL (?) +bool sntjmicron_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) +{ + /* Only admin commands used */ + bool admin = true; + + // 1: "NVM Command Set Payload" + { + unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; + cdb[0] = SAT_ATA_PASSTHROUGH_12; + cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd; + sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); + + unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; + nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE; + // nvm_cmd[1]: reserved + nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future + nvm_cmd[3] = in.nsid; + // nvm_cmd[4-5]: reserved + // nvm_cmd[6-7]: metadata pointer + // nvm_cmd[8-11]: data ptr (?) + nvm_cmd[12] = in.cdw10; + nvm_cmd[13] = in.cdw11; + nvm_cmd[14] = in.cdw12; + nvm_cmd[15] = in.cdw13; + nvm_cmd[16] = in.cdw14; + nvm_cmd[17] = in.cdw15; + // nvm_cmd[18-127]: reserved + + if (isbigendian()) + for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) + swapx(&nvm_cmd[i]); + + scsi_cmnd_io io_nvm; + memset(&io_nvm, 0, sizeof(io_nvm)); + + io_nvm.cmnd = cdb; + io_nvm.cmnd_len = SNT_JMICRON_CDB_LEN; + io_nvm.dxfer_dir = DXFER_TO_DEVICE; + io_nvm.dxferp = (uint8_t *)nvm_cmd; + io_nvm.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; + + scsi_device * scsidev = get_tunnel_dev(); + if (!scsidev->scsi_pass_through_and_check(&io_nvm, + "sntjmicron_device::nvme_pass_through:NVM: ")) + return set_err(scsidev->get_err()); + } + + // 2: DMA or Non-Data + { + unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; + cdb[0] = SAT_ATA_PASSTHROUGH_12; + + scsi_cmnd_io io_data; + memset(&io_data, 0, sizeof(io_data)); + io_data.cmnd = cdb; + io_data.cmnd_len = SNT_JMICRON_CDB_LEN; + + switch (in.direction()) { + case nvme_cmd_in::no_data: + cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data; + io_data.dxfer_dir = DXFER_NONE; + break; + case nvme_cmd_in::data_out: + cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out; + sg_put_unaligned_be24(in.size, &cdb[3]); + io_data.dxfer_dir = DXFER_TO_DEVICE; + io_data.dxferp = (uint8_t *)in.buffer; + io_data.dxfer_len = in.size; + break; + case nvme_cmd_in::data_in: + cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in; + sg_put_unaligned_be24(in.size, &cdb[3]); + io_data.dxfer_dir = DXFER_FROM_DEVICE; + io_data.dxferp = (uint8_t *)in.buffer; + io_data.dxfer_len = in.size; + memset(in.buffer, 0, in.size); + break; + case nvme_cmd_in::data_io: + default: + return set_err(EINVAL); + } + + scsi_device * scsidev = get_tunnel_dev(); + if (!scsidev->scsi_pass_through_and_check(&io_data, + "sntjmicron_device::nvme_pass_through:Data: ")) + return set_err(scsidev->get_err()); + } + + // 3: "Return Response Information" + { + unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; + cdb[0] = SAT_ATA_PASSTHROUGH_12; + cdb[1] = (admin ? 0x80 : 0x00) | proto_response; + sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); + + unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; + + scsi_cmnd_io io_reply; + memset(&io_reply, 0, sizeof(io_reply)); + + io_reply.cmnd = cdb; + io_reply.cmnd_len = SNT_JMICRON_CDB_LEN; + io_reply.dxfer_dir = DXFER_FROM_DEVICE; + io_reply.dxferp = (uint8_t *)nvm_reply; + io_reply.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; + + scsi_device * scsidev = get_tunnel_dev(); + if (!scsidev->scsi_pass_through_and_check(&io_reply, + "sntjmicron_device::nvme_pass_through:Data: ")) + return set_err(scsidev->get_err()); + + if (isbigendian()) + for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) + swapx(&nvm_reply[i]); + + if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE) + return set_err(EIO, "Out of spec JMicron NVMe reply"); + + int status = nvm_reply[5] >> 17; + + if (status > 0) + return set_nvme_err(out, status); + + out.result = nvm_reply[2]; + } + + return true; +} + +} // namespace snt + +using namespace snt; + +nvme_device * smart_interface::get_snt_device(const char * type, scsi_device * scsidev) +{ + if (!scsidev) + throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0"); + + // Take temporary ownership of 'scsidev' to delete it on error + scsi_device_auto_ptr scsidev_holder(scsidev); + nvme_device * sntdev = 0; + + if (!strncmp(type, "sntjmicron", 10)) { + int n1 = -1, n2 = -1, len = strlen(type); + unsigned nsid = 0; // invalid namespace id -> use default + sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2); + if (!(n1 == len || n2 == len)) { + set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type); + return 0; + } + sntdev = new sntjmicron_device(this, scsidev, type, nsid); + } + else { + set_err(EINVAL, "Unknown SNT device type '%s'", type); + return 0; + } + + // 'scsidev' is now owned by 'sntdev' + scsidev_holder.release(); + return sntdev; +} diff --git a/smartmontools/smartctl.8.in b/smartmontools/smartctl.8.in index 68175e6bcfcba6424ee90aef5384314ca4b4c3c8..17a59925dab2f07755187a85a96ed27fb3a5871c 100644 --- a/smartmontools/smartctl.8.in +++ b/smartmontools/smartctl.8.in @@ -453,6 +453,13 @@ PL2571/2771/2773/2775 USB to SATA bridge. \- this device type is for SATA disks that are behind a SunplusIT USB to SATA bridge. .Sp +.I sntjmicron[,NSID] +\- this device type is for NVMe disks that are behind a JMicron USB to NVMe +bridge. +The optional parameter NSID specifies the namespace id (in hex) passed +to the driver. +The default namespace id is the broadcast namespace id (0xffffffff). +.Sp .\" %ENDIF NOT OS Darwin .\" %IF OS Linux .I marvell diff --git a/smartmontools/smartd.conf.5.in b/smartmontools/smartd.conf.5.in index 76fb7aeb58e5b21a3cb7f6acaaa9af0c756cb39e..9dc53731fa548c80e498e51e6ba588033cc71525 100644 --- a/smartmontools/smartd.conf.5.in +++ b/smartmontools/smartd.conf.5.in @@ -393,6 +393,13 @@ PL2571/2771/2773/2775 USB to SATA bridge. \- this device type is for SATA disks that are behind a SunplusIT USB to SATA bridge. .Sp +.I sntjmicron[,NSID] +\- this device type is for NVMe disks that are behind a JMicron USB to NVMe +bridge. +The optional parameter NSID specifies the namespace id (in hex) passed +to the driver. +The default namespace id is the broadcast namespace id (0xffffffff). +.Sp .\" %ENDIF NOT OS Darwin .\" %IF OS Linux .I marvell diff --git a/smartmontools/utility.h b/smartmontools/utility.h index 85edb30440a5f1b913d1fd7d2753e2a54c2c6948..77eb8329881f6d22432b569ad2f18229fe883bed 100644 --- a/smartmontools/utility.h +++ b/smartmontools/utility.h @@ -94,6 +94,17 @@ inline bool isbigendian() #endif } +void swap2(char *location); +void swap4(char *location); +void swap8(char *location); +// Typesafe variants using overloading +inline void swapx(unsigned short * p) + { swap2((char*)p); } +inline void swapx(unsigned int * p) + { swap4((char*)p); } +inline void swapx(uint64_t * p) + { swap8((char*)p); } + // Runtime check of ./configure result, throws on error. void check_config();