From ecb3c60f15f266a48ccae6fd60b027250fcedb51 Mon Sep 17 00:00:00 2001
From: chrfranke <chrfranke@4ea69e1a-61f1-4043-bf83-b5c94c648137>
Date: Wed, 5 Dec 2018 18:30:46 +0000
Subject: [PATCH] Add '-d sntjmicron[,NSID]' device type for NVMe drives behind
 JMicron USB to NVMe bridges (JMS583).

Patch provided by Harry Mallon.

git-svn-id: https://svn.code.sf.net/p/smartmontools/code/trunk@4848 4ea69e1a-61f1-4043-bf83-b5c94c648137
---
 smartmontools/ChangeLog         |   5 +
 smartmontools/Makefile.am       |   2 +
 smartmontools/atacmds.h         |  11 --
 smartmontools/dev_interface.cpp |  55 ++++++-
 smartmontools/dev_interface.h   |  16 ++
 smartmontools/dev_tunnelled.h   |   8 +
 smartmontools/drivedb.h         |   7 +
 smartmontools/os_freebsd.cpp    |   2 +-
 smartmontools/os_linux.cpp      |   2 +-
 smartmontools/os_win32.cpp      |   6 +-
 smartmontools/scsiata.cpp       |  48 +-----
 smartmontools/scsinvme.cpp      | 250 ++++++++++++++++++++++++++++++++
 smartmontools/smartctl.8.in     |   7 +
 smartmontools/smartd.conf.5.in  |   7 +
 smartmontools/utility.h         |  11 ++
 15 files changed, 379 insertions(+), 58 deletions(-)
 create mode 100644 smartmontools/scsinvme.cpp

diff --git a/smartmontools/ChangeLog b/smartmontools/ChangeLog
index 44b7f7afb..37fadd56a 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 f63a90212..e1bc6c6ce 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 005121e7e..71e37b285 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 683059474..13e0418f7 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 f06e8c42f..d561b5231 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 a89a333c0..9d2952830 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 899be6b5f..1d660a867 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 4ca03eb05..c013e5e8c 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 f30da0534..1c3d09430 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 bc2d34c99..13b1730f6 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 b4d3e52f1..6d7430276 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 000000000..f0727c825
--- /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 68175e6bc..17a59925d 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 76fb7aeb5..9dc53731f 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 85edb3044..77eb83298 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();
 
-- 
GitLab