]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - os_linux.cpp
New upstream version 6.6
[mirror_smartmontools-debian.git] / os_linux.cpp
index c9b58a3149ae024c3495fad91697f99ca97a604f..134d5bc9e715a4d103275059c96229da4365d969 100644 (file)
@@ -1,11 +1,11 @@
 /*
  *  os_linux.cpp
  *
- * Home page of code is: http://smartmontools.sourceforge.net
+ * Home page of code is: http://www.smartmontools.org
  *
- * Copyright (C) 2003-11 Bruce Allen <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2003-11 Bruce Allen
  * Copyright (C) 2003-11 Doug Gilbert <dgilbert@interlog.com>
- * Copyright (C) 2008-15 Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2008-16 Christian Franke
  *
  * Original AACRaid code:
  *  Copyright (C) 2014    Raghava Aditya <raghava.aditya@pmcs.com>
@@ -63,6 +63,7 @@
 #include <scsi/scsi.h>
 #include <scsi/scsi_ioctl.h>
 #include <scsi/sg.h>
+#include <linux/bsg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include "dev_ata_cmd_set.h"
 #include "dev_areca.h"
 
+// "include/uapi/linux/nvme_ioctl.h" from Linux kernel sources
+#include "linux_nvme_ioctl.h" // nvme_passthru_cmd, NVME_IOCTL_ADMIN_CMD
+
 #ifndef ENOTSUP
 #define ENOTSUP ENOSYS
 #endif
 
 #define ARGUSED(x) ((void)(x))
 
-const char * os_linux_cpp_cvsid = "$Id: os_linux.cpp 4047 2015-03-22 16:16:24Z chrfranke $"
+const char * os_linux_cpp_cvsid = "$Id: os_linux.cpp 4582 2017-11-03 20:54:56Z chrfranke $"
   OS_LINUX_H_CVSID;
 extern unsigned char failuretest_permissive;
 
@@ -356,7 +360,6 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
     unsigned char task[sizeof(ide_task_request_t)+512];
     ide_task_request_t *reqtask=(ide_task_request_t *) task;
     task_struct_t      *taskfile=(task_struct_t *) reqtask->io_ports;
-    int retval;
 
     memset(task,      0, sizeof(task));
 
@@ -377,9 +380,9 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
     // copy user data into the task request structure
     memcpy(task+sizeof(ide_task_request_t), data, 512);
 
-    if ((retval=ioctl(get_fd(), HDIO_DRIVE_TASKFILE, task))) {
-      if (errno==-EINVAL)
-        pout("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
+    if (ioctl(get_fd(), HDIO_DRIVE_TASKFILE, task)) {
+      if (errno==EINVAL)
+        pout("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASK_IOCTL set\n");
       return -1;
     }
     return 0;
@@ -388,8 +391,6 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
   // There are two different types of ioctls().  The HDIO_DRIVE_TASK
   // one is this:
   if (command==STATUS_CHECK || command==AUTOSAVE || command==AUTO_OFFLINE){
-    int retval;
-
     // NOT DOCUMENTED in /usr/src/linux/include/linux/hdreg.h. You
     // have to read the IDE driver source code.  Sigh.
     // buff[0]: ATA COMMAND CODE REGISTER
@@ -405,8 +406,8 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
     buff[4]=normal_lo;
     buff[5]=normal_hi;
 
-    if ((retval=ioctl(get_fd(), HDIO_DRIVE_TASK, buff))) {
-      if (errno==-EINVAL) {
+    if (ioctl(get_fd(), HDIO_DRIVE_TASK, buff)) {
+      if (errno==EINVAL) {
         pout("Error SMART Status command via HDIO_DRIVE_TASK failed");
         pout("Rebuild older linux 2.2 kernels with HDIO_DRIVE_TASK support added\n");
       }
@@ -510,28 +511,32 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
 #define SCSI_IOCTL_SEND_COMMAND 1
 #endif
 
-#define SG_IO_PRESENT_UNKNOWN 0
-#define SG_IO_PRESENT_YES 1
-#define SG_IO_PRESENT_NO 2
+#define SG_IO_USE_DETECT 0
+#define SG_IO_UNSUPP 1
+#define SG_IO_USE_V3 3
+#define SG_IO_USE_V4 4
 
 static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
-                         int unknown);
+                         int sgio_ver);
 static int sisc_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report);
 
-static int sg_io_state = SG_IO_PRESENT_UNKNOWN;
+static int sg_io_state = SG_IO_USE_DETECT;
 
 /* Preferred implementation for issuing SCSI commands in linux. This
  * function uses the SG_IO ioctl. Return 0 if command issued successfully
  * (various status values should still be checked). If the SCSI command
  * cannot be issued then a negative errno value is returned. */
 static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
-                         int unknown)
+                         int sg_io_ver)
 {
 #ifndef SG_IO
     ARGUSED(dev_fd); ARGUSED(iop); ARGUSED(report);
     return -ENOTTY;
 #else
-    struct sg_io_hdr io_hdr;
+
+    /* we are filling structures for both versions, but using only one requested */
+    struct sg_io_hdr io_hdr_v3;
+    struct sg_io_v4  io_hdr_v4;
 
     if (report > 0) {
         int k, j;
@@ -540,6 +545,7 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
         char buff[256];
         const int sz = (int)sizeof(buff);
 
+        pout(">>>> do_scsi_cmnd_io: sg_io_ver=%d\n", sg_io_ver);
         np = scsi_get_opcode_name(ucp[0]);
         j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
         for (k = 0; k < (int)iop->cmnd_len; ++k)
@@ -548,57 +554,121 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
             (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
             int trunc = (iop->dxfer_len > 256) ? 1 : 0;
 
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
-                          "data, len=%d%s:\n", (int)iop->dxfer_len,
-                          (trunc ? " [only first 256 bytes shown]" : ""));
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
+                     "data, len=%d%s:\n", (int)iop->dxfer_len,
+                     (trunc ? " [only first 256 bytes shown]" : ""));
             dStrHex((const char *)iop->dxferp,
                     (trunc ? 256 : iop->dxfer_len) , 1);
         }
         else
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
         pout("%s", buff);
     }
-    memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
-    io_hdr.interface_id = 'S';
-    io_hdr.cmd_len = iop->cmnd_len;
-    io_hdr.mx_sb_len = iop->max_sense_len;
-    io_hdr.dxfer_len = iop->dxfer_len;
-    io_hdr.dxferp = iop->dxferp;
-    io_hdr.cmdp = iop->cmnd;
-    io_hdr.sbp = iop->sensep;
+    memset(&io_hdr_v3, 0, sizeof(struct sg_io_hdr));
+    memset(&io_hdr_v4, 0, sizeof(struct sg_io_v4));
+
+    io_hdr_v3.interface_id =       'S';
+    io_hdr_v3.cmd_len =            iop->cmnd_len;
+    io_hdr_v3.mx_sb_len =          iop->max_sense_len;
+    io_hdr_v3.dxfer_len =          iop->dxfer_len;
+    io_hdr_v3.dxferp =             iop->dxferp;
+    io_hdr_v3.cmdp =               iop->cmnd;
+    io_hdr_v3.sbp =                iop->sensep;
     /* sg_io_hdr interface timeout has millisecond units. Timeout of 0
        defaults to 60 seconds. */
-    io_hdr.timeout = ((0 == iop->timeout) ? 60 : iop->timeout) * 1000;
+    io_hdr_v3.timeout =         ((0 == iop->timeout) ? 60 : iop->timeout) * 1000;
+
+    io_hdr_v4.guard =              'Q';
+    io_hdr_v4.request_len =        iop->cmnd_len;
+    io_hdr_v4.request =            __u64(iop->cmnd);
+    io_hdr_v4.max_response_len =   iop->max_sense_len;
+    io_hdr_v4.response =           __u64(iop->sensep);
+    io_hdr_v4.timeout =            ((0 == iop->timeout) ? 60 : iop->timeout) * 1000; // msec
+
     switch (iop->dxfer_dir) {
         case DXFER_NONE:
-            io_hdr.dxfer_direction = SG_DXFER_NONE;
+            io_hdr_v3.dxfer_direction = SG_DXFER_NONE;
             break;
         case DXFER_FROM_DEVICE:
-            io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+            io_hdr_v3.dxfer_direction = SG_DXFER_FROM_DEV;
+            io_hdr_v4.din_xfer_len =    iop->dxfer_len;
+            io_hdr_v4.din_xferp =       __u64(iop->dxferp);
             break;
         case DXFER_TO_DEVICE:
-            io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+            io_hdr_v3.dxfer_direction = SG_DXFER_TO_DEV;
+            io_hdr_v4.dout_xfer_len =   iop->dxfer_len;
+            io_hdr_v4.dout_xferp =      __u64(iop->dxferp);
             break;
         default:
             pout("do_scsi_cmnd_io: bad dxfer_dir\n");
             return -EINVAL;
     }
+
     iop->resp_sense_len = 0;
     iop->scsi_status = 0;
     iop->resid = 0;
-    if (ioctl(dev_fd, SG_IO, &io_hdr) < 0) {
-        if (report && (! unknown))
-            pout("  SG_IO ioctl failed, errno=%d [%s]\n", errno,
-                 strerror(errno));
+
+    void * io_hdr = NULL;
+
+    switch (sg_io_ver) {
+      case SG_IO_USE_V3:
+          io_hdr = &io_hdr_v3;
+          break;
+      case SG_IO_USE_V4:
+          io_hdr = &io_hdr_v4;
+          break;
+      default:
+          // should never be reached
+          errno = EOPNOTSUPP;
+          return -errno;
+    }
+
+    if (ioctl(dev_fd, SG_IO, io_hdr) < 0) {
+        if (report)
+            pout("  SG_IO ioctl failed, errno=%d [%s], SG_IO_V%d\n", errno,
+                 strerror(errno), sg_io_ver);
         return -errno;
     }
-    iop->resid = io_hdr.resid;
-    iop->scsi_status = io_hdr.status;
+
+    unsigned int sg_driver_status = 0,  sg_transport_status = 0, sg_info = 0,
+        sg_duration = 0;
+
+    if (sg_io_ver == SG_IO_USE_V3) {
+        iop->resid =            io_hdr_v3.resid;
+        iop->scsi_status =      io_hdr_v3.status;
+        sg_driver_status =      io_hdr_v3.driver_status;
+        sg_transport_status =   io_hdr_v3.host_status;
+        sg_info =               io_hdr_v3.info;
+        iop->resp_sense_len =   io_hdr_v3.sb_len_wr;
+        sg_duration =           io_hdr_v3.duration;
+    }
+
+    if (sg_io_ver == SG_IO_USE_V4) {
+       switch (iop->dxfer_dir) {
+           case DXFER_NONE:
+               iop->resid = 0;
+               break;
+           case DXFER_FROM_DEVICE:
+               iop->resid = io_hdr_v4.din_resid;
+               break;
+           case DXFER_TO_DEVICE:
+               iop->resid = io_hdr_v4.dout_resid;
+               break;
+       }
+       iop->scsi_status =       io_hdr_v4.device_status;
+       sg_driver_status =       io_hdr_v4.driver_status;
+       sg_transport_status =    io_hdr_v4.transport_status;
+       sg_info =                io_hdr_v4.info;
+       iop->resp_sense_len =    io_hdr_v4.response_len;
+       sg_duration =            io_hdr_v4.duration;
+    }
+
     if (report > 0) {
-        pout("  scsi_status=0x%x, host_status=0x%x, driver_status=0x%x\n"
-             "  info=0x%x  duration=%d milliseconds  resid=%d\n", io_hdr.status,
-             io_hdr.host_status, io_hdr.driver_status, io_hdr.info,
-             io_hdr.duration, io_hdr.resid);
+        pout("  scsi_status=0x%x, sg_transport_status=0x%x, sg_driver_status=0x%x\n"
+             "  sg_info=0x%x  sg_duration=%d milliseconds  resid=%d\n", iop->scsi_status,
+             sg_transport_status, sg_driver_status, sg_info,
+             sg_duration, iop->resid);
+
         if (report > 1) {
             if (DXFER_FROM_DEVICE == iop->dxfer_dir) {
                 int trunc, len;
@@ -616,17 +686,17 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
         }
     }
 
-    if (io_hdr.info & SG_INFO_CHECK) { /* error or warning */
-        int masked_driver_status = (LSCSI_DRIVER_MASK & io_hdr.driver_status);
+    if (sg_info & SG_INFO_CHECK) { /* error or warning */
+        int masked_driver_status = (LSCSI_DRIVER_MASK & sg_driver_status);
 
-        if (0 != io_hdr.host_status) {
-            if ((LSCSI_DID_NO_CONNECT == io_hdr.host_status) ||
-                (LSCSI_DID_BUS_BUSY == io_hdr.host_status) ||
-                (LSCSI_DID_TIME_OUT == io_hdr.host_status))
+        if (0 != sg_transport_status) {
+            if ((LSCSI_DID_NO_CONNECT == sg_transport_status) ||
+                (LSCSI_DID_BUS_BUSY == sg_transport_status) ||
+                (LSCSI_DID_TIME_OUT == sg_transport_status))
                 return -ETIMEDOUT;
             else
                /* Check for DID_ERROR - workaround for aacraid driver quirk */
-               if (LSCSI_DID_ERROR != io_hdr.host_status) {
+               if (LSCSI_DID_ERROR != sg_transport_status) {
                        return -EIO; /* catch all if not DID_ERR */
                }
         }
@@ -638,7 +708,6 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
         }
         if (LSCSI_DRIVER_SENSE == masked_driver_status)
             iop->scsi_status = SCSI_STATUS_CHECK_CONDITION;
-        iop->resp_sense_len = io_hdr.sb_len_wr;
         if ((SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) &&
             iop->sensep && (iop->resp_sense_len > 0)) {
             if (report > 1) {
@@ -648,7 +717,7 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
             }
         }
         if (report) {
-            if (SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) {
+            if (SCSI_STATUS_CHECK_CONDITION == iop->scsi_status && iop->sensep) {
                 if ((iop->sensep[0] & 0x7f) > 0x71)
                     pout("  status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
                          iop->scsi_status, iop->sensep[1] & 0xf,
@@ -696,18 +765,17 @@ static int sisc_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report)
         j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
         for (k = 0; k < (int)iop->cmnd_len; ++k)
             j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
-        if ((report > 1) &&
-            (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+        if ((report > 1) && (DXFER_TO_DEVICE == iop->dxfer_dir)) {
             int trunc = (iop->dxfer_len > 256) ? 1 : 0;
 
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
-                          "data, len=%d%s:\n", (int)iop->dxfer_len,
-                          (trunc ? " [only first 256 bytes shown]" : ""));
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
+                     "data, len=%d%s:\n", (int)iop->dxfer_len,
+                     (trunc ? " [only first 256 bytes shown]" : ""));
             dStrHex((const char *)iop->dxferp,
                     (trunc ? 256 : iop->dxfer_len) , 1);
         }
         else
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
         pout("%s", buff);
     }
     switch (iop->dxfer_dir) {
@@ -808,22 +876,33 @@ static int do_normal_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop,
      * other than ENODEV (no device) or permission then assume
      * SCSI_IOCTL_SEND_COMMAND is the only option. */
     switch (sg_io_state) {
-    case SG_IO_PRESENT_UNKNOWN:
+    case SG_IO_USE_DETECT:
         /* ignore report argument */
-        if (0 == (res = sg_io_cmnd_io(dev_fd, iop, report, 1))) {
-            sg_io_state = SG_IO_PRESENT_YES;
+        /* Try SG_IO V3 first */
+        if (0 == (res = sg_io_cmnd_io(dev_fd, iop, report, SG_IO_USE_V3))) {
+            sg_io_state = SG_IO_USE_V3;
+            return 0;
+        } else if ((-ENODEV == res) || (-EACCES == res) || (-EPERM == res))
+            return res;         /* wait until we see a device */
+        /* See if we can use SG_IO V4 * */
+        if (0 == (res = sg_io_cmnd_io(dev_fd, iop, report, SG_IO_USE_V4))) {
+            sg_io_state = SG_IO_USE_V4;
             return 0;
         } else if ((-ENODEV == res) || (-EACCES == res) || (-EPERM == res))
             return res;         /* wait until we see a device */
-        sg_io_state = SG_IO_PRESENT_NO;
-        /* drop through by design */
-    case SG_IO_PRESENT_NO:
+        /* fallback to the SCSI_IOCTL_SEND_COMMAND */
+        sg_io_state = SG_IO_UNSUPP;
+        /* FALLTHRU */
+    case SG_IO_UNSUPP:
+        /* depricated SCSI_IOCTL_SEND_COMMAND ioctl */
         return sisc_cmnd_io(dev_fd, iop, report);
-    case SG_IO_PRESENT_YES:
-        return sg_io_cmnd_io(dev_fd, iop, report, 0);
+    case SG_IO_USE_V3:
+    case SG_IO_USE_V4:
+        /* use SG_IO V3 or V4 ioctl, depending on availabiliy */
+        return sg_io_cmnd_io(dev_fd, iop, report, sg_io_state);
     default:
         pout(">>>> do_scsi_cmnd_io: bad sg_io_state=%d\n", sg_io_state);
-        sg_io_state = SG_IO_PRESENT_UNKNOWN;
+        sg_io_state = SG_IO_USE_DETECT;
         return -EIO;    /* report error and reset state */
     }
 }
@@ -973,18 +1052,18 @@ bool linux_aacraid_device::scsi_pass_through(scsi_cmnd_io *iop)
     j  = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
     for (k = 0; k < (int)iop->cmnd_len; ++k)
       j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
-      if ((report > 1) &&
+    if ((report > 1) &&
         (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
         int trunc = (iop->dxfer_len > 256) ? 1 : 0;
 
-        j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
-                      "data, len=%d%s:\n", (int)iop->dxfer_len,
-                      (trunc ? " [only first 256 bytes shown]" : ""));
+        snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
+                 "data, len=%d%s:\n", (int)iop->dxfer_len,
+                 (trunc ? " [only first 256 bytes shown]" : ""));
         dStrHex((const char *)iop->dxferp,
                (trunc ? 256 : iop->dxfer_len) , 1);
     }
     else
-      j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+      snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
 
     pout("%s", buff);
   }
@@ -1113,7 +1192,7 @@ class linux_megaraid_device
 {
 public:
   linux_megaraid_device(smart_interface *intf, const char *name, 
-    unsigned int bus, unsigned int tgt);
+    unsigned int tgt);
 
   virtual ~linux_megaraid_device() throw();
 
@@ -1126,7 +1205,6 @@ public:
 
 private:
   unsigned int m_disknum;
-  unsigned int m_busnum;
   unsigned int m_hba;
   int m_fd;
 
@@ -1139,10 +1217,10 @@ private:
 };
 
 linux_megaraid_device::linux_megaraid_device(smart_interface *intf,
-  const char *dev_name, unsigned int bus, unsigned int tgt)
+  const char *dev_name, unsigned int tgt)
  : smart_device(intf, dev_name, "megaraid", "megaraid"),
    linux_smart_device(O_RDWR | O_NONBLOCK),
-   m_disknum(tgt), m_busnum(bus), m_hba(0),
+   m_disknum(tgt), m_hba(0),
    m_fd(-1), pt_cmd(0)
 {
   set_info().info_name = strprintf("%s [megaraid_disk_%02d]", dev_name, m_disknum);
@@ -1202,7 +1280,7 @@ bool linux_megaraid_device::open()
   int   mjr;
   int report = scsi_debugmode;
 
-  if(sscanf(get_dev_name(),"/dev/bus/%d", &m_hba) == 0) {
+  if (sscanf(get_dev_name(), "/dev/bus/%u", &m_hba) == 0) {
     if (!linux_smart_device::open())
       return false;
     /* Get device HBA */
@@ -1282,14 +1360,14 @@ bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
             (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
             int trunc = (iop->dxfer_len > 256) ? 1 : 0;
 
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
-                          "data, len=%d%s:\n", (int)iop->dxfer_len,
-                          (trunc ? " [only first 256 bytes shown]" : ""));
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n  Outgoing "
+                     "data, len=%d%s:\n", (int)iop->dxfer_len,
+                     (trunc ? " [only first 256 bytes shown]" : ""));
             dStrHex((const char *)iop->dxferp,
                     (trunc ? 256 : iop->dxfer_len) , 1);
         }
         else
-            j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+            snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
         pout("%s", buff);
   }
 
@@ -1325,7 +1403,6 @@ bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb,
 {
   struct megasas_pthru_frame   *pthru;
   struct megasas_iocpacket     uio;
-  int rc;
 
   memset(&uio, 0, sizeof(uio));
   pthru = &uio.frame.pthru;
@@ -1367,9 +1444,8 @@ bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb,
     uio.sgl[0].iov_len = dataLen;
   }
 
-  rc = 0;
   errno = 0;
-  rc = ioctl(m_fd, MEGASAS_IOC_FIRMWARE, &uio);
+  int rc = ioctl(m_fd, MEGASAS_IOC_FIRMWARE, &uio);
   if (pthru->cmd_status || rc != 0) {
     if (pthru->cmd_status == 12) {
       return set_err(EIO, "megasas_cmd: Device %d does not exist\n", m_disknum);
@@ -1991,10 +2067,8 @@ linux_areca_ata_device::linux_areca_ata_device(smart_interface * intf, const cha
 
 smart_device * linux_areca_ata_device::autodetect_open()
 {
-  int is_ata = 1;
-
   // autodetect device type
-  is_ata = arcmsr_get_dev_type();
+  int is_ata = arcmsr_get_dev_type();
   if(is_ata < 0)
   {
     set_err(EIO);
@@ -2371,7 +2445,6 @@ int linux_highpoint_device::ata_command_interface(smart_command_set command, int
     unsigned int *hpt_tf = (unsigned int *)task;
     ide_task_request_t *reqtask = (ide_task_request_t *)(&task[4*sizeof(int)]);
     task_struct_t *taskfile = (task_struct_t *)reqtask->io_ports;
-    int retval;
 
     memset(task, 0, sizeof(task));
 
@@ -2396,16 +2469,13 @@ int linux_highpoint_device::ata_command_interface(smart_command_set command, int
 
     memcpy(task+sizeof(ide_task_request_t)+4*sizeof(int), data, 512);
 
-    if ((retval=ioctl(get_fd(), HPTIO_CTL, task))) {
-      if (retval==-EINVAL)
-        pout("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
+    if (ioctl(get_fd(), HPTIO_CTL, task))
       return -1;
-    }
+
     return 0;
   }
 
   if (command==STATUS_CHECK){
-    int retval;
     unsigned const char normal_lo=0x4f, normal_hi=0xc2;
     unsigned const char failed_lo=0xf4, failed_hi=0x2c;
     buff[4]=normal_lo;
@@ -2413,15 +2483,8 @@ int linux_highpoint_device::ata_command_interface(smart_command_set command, int
 
     hpt[2] = HDIO_DRIVE_TASK;
 
-    if ((retval=ioctl(get_fd(), HPTIO_CTL, hpt_buff))) {
-      if (retval==-EINVAL) {
-        pout("Error SMART Status command via HDIO_DRIVE_TASK failed");
-        pout("Rebuild older linux 2.2 kernels with HDIO_DRIVE_TASK support added\n");
-      }
-      else
-        syserror("Error SMART Status command failed");
+    if (ioctl(get_fd(), HPTIO_CTL, hpt_buff))
       return -1;
-    }
 
     if (buff[4]==normal_lo && buff[5]==normal_hi)
       return 0;
@@ -2596,6 +2659,76 @@ smart_device * linux_scsi_device::autodetect_open()
   return this;
 }
 
+/////////////////////////////////////////////////////////////////////////////
+/// NVMe support
+
+class linux_nvme_device
+: public /*implements*/ nvme_device,
+  public /*extends*/ linux_smart_device
+{
+public:
+  linux_nvme_device(smart_interface * intf, const char * dev_name,
+    const char * req_type, unsigned nsid);
+
+  virtual bool open();
+
+  virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out);
+};
+
+linux_nvme_device::linux_nvme_device(smart_interface * intf, const char * dev_name,
+  const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+  nvme_device(nsid),
+  linux_smart_device(O_RDONLY | O_NONBLOCK)
+{
+}
+
+bool linux_nvme_device::open()
+{
+  if (!linux_smart_device::open())
+    return false;
+
+  if (!get_nsid()) {
+    // Use actual NSID (/dev/nvmeXnN) if available,
+    // else use broadcast namespace (/dev/nvmeX)
+    int nsid = ioctl(get_fd(), NVME_IOCTL_ID, (void*)0);
+    set_nsid(nsid);
+  }
+
+  return true;
+}
+
+bool linux_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+  nvme_passthru_cmd pt;
+  memset(&pt, 0, sizeof(pt));
+
+  pt.opcode = in.opcode;
+  pt.nsid = in.nsid;
+  pt.addr = (uint64_t)in.buffer;
+  pt.data_len = in.size;
+  pt.cdw10 = in.cdw10;
+  pt.cdw11 = in.cdw11;
+  pt.cdw12 = in.cdw12;
+  pt.cdw13 = in.cdw13;
+  pt.cdw14 = in.cdw14;
+  pt.cdw15 = in.cdw15;
+  // Kernel default for NVMe admin commands is 60 seconds
+  // pt.timeout_ms = 60 * 1000;
+
+  int status = ioctl(get_fd(), NVME_IOCTL_ADMIN_CMD, &pt);
+
+  if (status < 0)
+    return set_err(errno, "NVME_IOCTL_ADMIN_CMD: %s", strerror(errno));
+
+  if (status > 0)
+    return set_nvme_err(out, status);
+
+  out.result = pt.result;
+  return true;
+}
+
+
 //////////////////////////////////////////////////////////////////////
 // USB bridge ID detection
 
@@ -2668,6 +2801,9 @@ protected:
 
   virtual scsi_device * get_scsi_device(const char * name, const char * type);
 
+  virtual nvme_device * get_nvme_device(const char * name, const char * type,
+    unsigned nsid);
+
   virtual smart_device * autodetect_smart_device(const char * name);
 
   virtual smart_device * get_custom_smart_device(const char * name, const char * type);
@@ -2676,7 +2812,9 @@ protected:
 
 private:
   bool get_dev_list(smart_device_list & devlist, const char * pattern,
-    bool scan_ata, bool scan_scsi, const char * req_type, bool autodetect);
+    bool scan_ata, bool scan_scsi, bool scan_nvme,
+    const char * req_type, bool autodetect);
+
   bool get_dev_megasas(smart_device_list & devlist);
   smart_device * missing_option(const char * opt);
   int megasas_dcmd_cmd(int bus_no, uint32_t opcode, void *buf,
@@ -2703,7 +2841,7 @@ std::string linux_smart_interface::get_app_examples(const char * appname)
 // we are going to take advantage of the fact that Linux's devfs will only
 // have device entries for devices that exist.
 bool linux_smart_interface::get_dev_list(smart_device_list & devlist,
-  const char * pattern, bool scan_ata, bool scan_scsi,
+  const char * pattern, bool scan_ata, bool scan_scsi, bool scan_nvme,
   const char * req_type, bool autodetect)
 {
   // Use glob to look for any directory entries matching the pattern
@@ -2786,6 +2924,8 @@ bool linux_smart_interface::get_dev_list(smart_device_list & devlist,
         dev = autodetect_smart_device(name);
       else if (is_scsi)
         dev = new linux_scsi_device(this, name, req_type, true /*scanning*/);
+      else if (scan_nvme)
+        dev = new linux_nvme_device(this, name, req_type, 0 /* use default nsid */);
       else
         dev = new linux_ata_device(this, name, req_type);
       if (dev) // autodetect_smart_device() may return nullptr.
@@ -2824,20 +2964,19 @@ bool linux_smart_interface::get_dev_megasas(smart_device_list & devlist)
     return false;
 
   // getting bus numbers with megasas devices
-  struct dirent *ep;
-  unsigned int host_no = 0;
-  char sysfsdir[256];
-
-  /* we are using sysfs to get list of all scsi hosts */
+  // we are using sysfs to get list of all scsi hosts
   DIR * dp = opendir ("/sys/class/scsi_host/");
   if (dp != NULL)
   {
+    struct dirent *ep;
     while ((ep = readdir (dp)) != NULL) {
-      if (!sscanf(ep->d_name, "host%d", &host_no)) 
+      unsigned int host_no = 0;
+      if (!sscanf(ep->d_name, "host%u", &host_no))
         continue;
       /* proc_name should be megaraid_sas */
+      char sysfsdir[256];
       snprintf(sysfsdir, sizeof(sysfsdir) - 1,
-        "/sys/class/scsi_host/host%d/proc_name", host_no);
+        "/sys/class/scsi_host/host%u/proc_name", host_no);
       if((fp = fopen(sysfsdir, "r")) == NULL)
         continue;
       if(fgets(line, sizeof(line), fp) != NULL && !strncmp(line,"megaraid_sas",12)) {
@@ -2867,19 +3006,32 @@ bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist,
   bool scan_ata  = (!*type || !strcmp(type, "ata" ));
   // "sat" detection will be later handled in linux_scsi_device::autodetect_open()
   bool scan_scsi = (!*type || !strcmp(type, "scsi") || !strcmp(type, "sat"));
-  if (!(scan_ata || scan_scsi))
-    return true;
+
+#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
+  bool scan_nvme = (!*type || !strcmp(type, "nvme"));
+#else
+  bool scan_nvme = (          !strcmp(type, "nvme"));
+#endif
+
+  if (!(scan_ata || scan_scsi || scan_nvme)) {
+    set_err(EINVAL, "Invalid type '%s', valid arguments are: ata, scsi, sat, nvme", type);
+    return false;
+  }
 
   if (scan_ata)
-    get_dev_list(devlist, "/dev/hd[a-t]", true, false, type, false);
+    get_dev_list(devlist, "/dev/hd[a-t]", true, false, false, type, false);
   if (scan_scsi) {
     bool autodetect = !*type; // Try USB autodetection if no type specifed
-    get_dev_list(devlist, "/dev/sd[a-z]", false, true, type, autodetect);
+    get_dev_list(devlist, "/dev/sd[a-z]", false, true, false, type, autodetect);
     // Support up to 104 devices
-    get_dev_list(devlist, "/dev/sd[a-c][a-z]", false, true, type, autodetect);
+    get_dev_list(devlist, "/dev/sd[a-c][a-z]", false, true, false, type, autodetect);
     // get device list from the megaraid device
     get_dev_megasas(devlist);
   }
+  if (scan_nvme) {
+    get_dev_list(devlist, "/dev/nvme[0-9]", false, false, true, type, false);
+    get_dev_list(devlist, "/dev/nvme[1-9][0-9]", false, false, true, type, false);
+  }
 
   // if we found traditional links, we are done
   if (devlist.size() > 0)
@@ -2887,7 +3039,7 @@ bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist,
 
   // else look for devfs entries without traditional links
   // TODO: Add udev support
-  return get_dev_list(devlist, "/dev/discs/disc*", scan_ata, scan_scsi, type, false);
+  return get_dev_list(devlist, "/dev/discs/disc*", scan_ata, scan_scsi, false, type, false);
 }
 
 ata_device * linux_smart_interface::get_ata_device(const char * name, const char * type)
@@ -2900,6 +3052,12 @@ scsi_device * linux_smart_interface::get_scsi_device(const char * name, const ch
   return new linux_scsi_device(this, name, type);
 }
 
+nvme_device * linux_smart_interface::get_nvme_device(const char * name, const char * type,
+  unsigned nsid)
+{
+  return new linux_nvme_device(this, name, type, nsid);
+}
+
 smart_device * linux_smart_interface::missing_option(const char * opt)
 {
   set_err(EINVAL, "requires option '%s'", opt);
@@ -2972,7 +3130,7 @@ linux_smart_interface::megasas_pd_add_list(int bus_no, smart_device_list & devli
   */
   megasas_pd_list * list = 0;
   for (unsigned list_size = 1024; ; ) {
-    list = (megasas_pd_list *)realloc(list, list_size);
+    list = reinterpret_cast<megasas_pd_list *>(realloc(list, list_size));
     if (!list)
       throw std::bad_alloc();
     bzero(list, list_size);
@@ -2993,7 +3151,7 @@ linux_smart_interface::megasas_pd_add_list(int bus_no, smart_device_list & devli
       continue; /* non disk device found */
     char line[128];
     snprintf(line, sizeof(line) - 1, "/dev/bus/%d", bus_no);
-    smart_device * dev = new linux_megaraid_device(this, line, 0, list->addr[i].device_id);
+    smart_device * dev = new linux_megaraid_device(this, line, list->addr[i].device_id);
     devlist.push_back(dev);
   }
   free(list);
@@ -3013,6 +3171,50 @@ static unsigned get_kernel_release()
   return x * 100000 + y * 1000 + z;
 }
 
+// Check for SCSI host proc_name "hpsa"
+static bool is_hpsa(const char * name)
+{
+  char path[128];
+  snprintf(path, sizeof(path), "/sys/block/%s/device", name);
+  char * syshostpath = canonicalize_file_name(path);
+  if (!syshostpath)
+    return false;
+
+  char * syshost = strrchr(syshostpath, '/');
+  if (!syshost) {
+    free(syshostpath);
+    return false;
+  }
+
+  char * hostsep = strchr(++syshost, ':');
+  if (hostsep)
+    *hostsep = 0;
+
+  snprintf(path, sizeof(path), "/sys/class/scsi_host/host%s/proc_name", syshost);
+  free(syshostpath);
+  int fd = open(path, O_RDONLY);
+  if (fd < 0)
+    return false;
+
+  char proc_name[32];
+  ssize_t n = read(fd, proc_name, sizeof(proc_name) - 1);
+  close(fd);
+  if (n < 4)
+    return false;
+
+  proc_name[n] = 0;
+  if (proc_name[n - 1] == '\n')
+    proc_name[n - 1] = 0;
+
+  if (scsi_debugmode > 1)
+    pout("%s -> %s: \"%s\"\n", name, path, proc_name);
+
+  if (strcmp(proc_name, "hpsa"))
+    return false;
+
+  return true;
+}
+
 // Guess device type (ata or scsi) based on device name (Linux
 // specific) SCSI device name in linux can be sd, sr, scd, st, nst,
 // osst, nosst and sg.
@@ -3065,7 +3267,11 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
       return get_sat_device(usbtype, new linux_scsi_device(this, name, ""));
     }
 
-    // No USB bridge found, assume regular SCSI device
+    // Fail if hpsa driver
+    if (is_hpsa(test_name))
+      return missing_option("-d cciss,N");
+
+    // No USB bridge or hpsa driver found, assume regular SCSI device
     return new linux_scsi_device(this, name, "");
   }
 
@@ -3073,6 +3279,10 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
   if (str_starts_with(test_name, "scsi/"))
     return new linux_scsi_device(this, name, "");
 
+  // form /dev/bsg/* or bsg/*
+  if (str_starts_with(test_name, "bsg/"))
+    return new linux_scsi_device(this, name, "");
+
   // form /dev/ns* or ns*
   if (str_starts_with(test_name, "ns"))
     return new linux_scsi_device(this, name, "");
@@ -3085,6 +3295,10 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
   if (str_starts_with(test_name, "nos"))
     return new linux_scsi_device(this, name, "");
 
+  // form /dev/nvme* or nvme*
+  if (str_starts_with(test_name, "nvme"))
+    return new linux_nvme_device(this, name, "", 0 /* use default nsid */);
+
   // form /dev/tw[ael]* or tw[ael]*
   if (str_starts_with(test_name, "tw") && strchr("ael", test_name[2]))
     return missing_option("-d 3ware,N");
@@ -3182,16 +3396,15 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
 
   // MegaRAID ?
   if (sscanf(type, "megaraid,%d", &disknum) == 1) {
-    return new linux_megaraid_device(this, name, 0, disknum);
+    return new linux_megaraid_device(this, name, disknum);
   }
 
   //aacraid?
-  unsigned int device;
-  unsigned int host;
-  if(sscanf(type, "aacraid,%d,%d,%d", &host, &channel, &device)==3) {
+  unsigned host, chan, device;
+  if (sscanf(type, "aacraid,%u,%u,%u", &host, &chan, &device) == 3) {
     //return new linux_aacraid_device(this,name,channel,device);
     return get_sat_device("sat,auto",
-      new linux_aacraid_device(this, name, host, channel, device));
+      new linux_aacraid_device(this, name, host, chan, device));
 
   }