]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - os_linux.cpp
Remove /etc/init.d/smartd
[mirror_smartmontools-debian.git] / os_linux.cpp
index 5635eaa01454552dcd1608f1638e26e80a8d6697..b68575a12e348a995accad3021b1808b025b0a06 100644 (file)
@@ -3,12 +3,12 @@
  *
  * Home page of code is: http://smartmontools.sourceforge.net
  *
- * Copyright (C) 2003-8 Bruce Allen <smartmontools-support@lists.sourceforge.net>
- * Copyright (C) 2003-8 Doug Gilbert <dougg@torque.net>
- * Copyright (C) 2008   Hank Wu <hank@areca.com.tw>
- * Copyright (C) 2008   Oliver Bock <brevilo@users.sourceforge.net>
- * Copyright (C) 2008-9 Christian Franke <smartmontools-support@lists.sourceforge.net>
- * Copyright (C) 2008   Jordan Hargrave <jordan_hargrave@dell.com>
+ * Copyright (C) 2003-11 Bruce Allen <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2003-11 Doug Gilbert <dgilbert@interlog.com>
+ * Copyright (C) 2008-12 Hank Wu <hank@areca.com.tw>
+ * Copyright (C) 2008    Oliver Bock <brevilo@users.sourceforge.net>
+ * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2008    Jordan Hargrave <jordan_hargrave@dell.com>
  *
  *  Parts of this file are derived from code that was
  *
@@ -60,8 +60,9 @@
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
-#include <sys/file.h>
+#include <sys/utsname.h>
 #include <unistd.h>
+#include <stddef.h>  // for offsetof()
 #include <sys/uio.h>
 #include <sys/types.h>
 #ifndef makedev // old versions of types.h do not include sysmacros.h
 
 #include "int64.h"
 #include "atacmds.h"
-#include "extern.h"
 #include "os_linux.h"
 #include "scsicmds.h"
 #include "utility.h"
-#include "extern.h"
 #include "cciss.h"
 #include "megaraid.h"
 
 
 #define ARGUSED(x) ((void)(x))
 
-const char *os_XXXX_c_cvsid="$Id: os_linux.cpp 2951 2009-10-08 23:43:46Z samm2 $" \
-ATACMDS_H_CVSID CONFIG_H_CVSID INT64_H_CVSID OS_LINUX_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
-
-/* for passing global control variables */
-// (con->reportscsiioctl only)
-extern smartmonctrl *con;
+const char * os_linux_cpp_cvsid = "$Id: os_linux.cpp 3558 2012-06-05 16:42:05Z chrfranke $"
+  OS_LINUX_H_CVSID;
 
 
 namespace os_linux { // No need to publish anything, name provided for Doxygen
@@ -196,12 +191,14 @@ static const char  smartctl_examples[] =
                  "  smartctl --all --device=3ware,2 /dev/sda\n"
                  "  smartctl --all --device=3ware,2 /dev/twe0\n"
                  "  smartctl --all --device=3ware,2 /dev/twa0\n"
+                 "  smartctl --all --device=3ware,2 /dev/twl0\n"
                  "          (Prints all SMART info for 3rd ATA disk on 3ware RAID controller)\n"
                  "  smartctl --all --device=hpt,1/1/3 /dev/sda\n"
                  "          (Prints all SMART info for the SATA disk attached to the 3rd PMPort\n"
                  "           of the 1st channel on the 1st HighPoint RAID controller)\n"
-                 "  smartctl --all --device=areca,3 /dev/sg2\n"
-                 "          (Prints all SMART info for 3rd ATA disk on Areca RAID controller)\n"
+                 "  smartctl --all --device=areca,3/1 /dev/sg2\n"
+                 "          (Prints all SMART info for 3rd ATA disk of the 1st enclosure\n"
+                 "           on Areca RAID controller)\n"
   ;
 
 
@@ -493,6 +490,7 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
 #define SG_IO_RESP_SENSE_LEN 64 /* large enough see buffer */
 #define LSCSI_DRIVER_MASK  0xf /* mask out "suggestions" */
 #define LSCSI_DRIVER_SENSE  0x8 /* alternate CHECK CONDITION indication */
+#define LSCSI_DID_ERROR 0x7 /* Need to work around aacraid driver quirk */
 #define LSCSI_DRIVER_TIMEOUT  0x6
 #define LSCSI_DID_TIME_OUT  0x3
 #define LSCSI_DID_BUS_BUSY  0x2
@@ -608,7 +606,7 @@ 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 */
+    if (io_hdr.info & SG_INFO_CHECK) { /* error or warning */
         int masked_driver_status = (LSCSI_DRIVER_MASK & io_hdr.driver_status);
 
         if (0 != io_hdr.host_status) {
@@ -617,7 +615,10 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
                 (LSCSI_DID_TIME_OUT == io_hdr.host_status))
                 return -ETIMEDOUT;
             else
-                return -EIO;    /* catch all */
+               /* Check for DID_ERROR - workaround for aacraid driver quirk */
+               if (LSCSI_DID_ERROR != io_hdr.host_status) {
+                       return -EIO; /* catch all if not DID_ERR */
+               }
         }
         if (0 != masked_driver_status) {
             if (LSCSI_DRIVER_TIMEOUT == masked_driver_status)
@@ -827,24 +828,31 @@ class linux_scsi_device
   public /*extends*/ linux_smart_device
 {
 public:
-  linux_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type);
+  linux_scsi_device(smart_interface * intf, const char * dev_name,
+                    const char * req_type, bool scanning = false);
 
   virtual smart_device * autodetect_open();
 
   virtual bool scsi_pass_through(scsi_cmnd_io * iop);
+
+private:
+  bool m_scanning; ///< true if created within scan_smart_devices
 };
 
 linux_scsi_device::linux_scsi_device(smart_interface * intf,
-  const char * dev_name, const char * req_type)
+  const char * dev_name, const char * req_type, bool scanning /*= false*/)
 : smart_device(intf, dev_name, "scsi", req_type),
-  linux_smart_device(O_RDWR | O_NONBLOCK, O_RDONLY | O_NONBLOCK)
+  // If opened with O_RDWR, a SATA disk in standby mode
+  // may spin-up after device close().
+  linux_smart_device(O_RDONLY | O_NONBLOCK),
+  m_scanning(scanning)
 {
 }
 
 
 bool linux_scsi_device::scsi_pass_through(scsi_cmnd_io * iop)
 {
-  int status = do_normal_scsi_cmnd_io(get_fd(), iop, con->reportscsiioctl);
+  int status = do_normal_scsi_cmnd_io(get_fd(), iop, scsi_debugmode);
   if (status < 0)
       return set_err(-status);
   return true;
@@ -902,6 +910,8 @@ linux_megaraid_device::~linux_megaraid_device() throw()
 
 smart_device * linux_megaraid_device::autodetect_open()
 {
+  int report = scsi_debugmode;
+
   // Open device
   if (!open())
     return this;
@@ -924,21 +934,20 @@ smart_device * linux_megaraid_device::autodetect_open()
   if (len < 36)
       return this;
 
-  printf("Got MegaRAID inquiry.. %s\n", req_buff+8);
+  if (report)
+    pout("Got MegaRAID inquiry.. %s\n", req_buff+8);
 
   // Use INQUIRY to detect type
-  smart_device * newdev = 0;
-  try {
+  {
     // SAT or USB ?
-    newdev = smi()->autodetect_sat_device(this, req_buff, len);
-    if (newdev)
+    ata_device * newdev = smi()->autodetect_sat_device(this, req_buff, len);
+    if (newdev) {
       // NOTE: 'this' is now owned by '*newdev'
+      newdev->close();
+      newdev->set_err(ENOSYS, "SATA device detected,\n"
+        "MegaRAID SAT layer is reportedly buggy, use '-d sat+megaraid,N' to try anyhow");
       return newdev;
-  }
-  catch (...) {
-    // Cleanup if exception occurs after newdev was allocated
-    delete newdev;
-    throw;
+    }
   }
 
   // Nothing special found
@@ -951,7 +960,7 @@ bool linux_megaraid_device::open()
   char line[128];
   int   mjr, n1;
   FILE *fp;
-  int report = con->reportscsiioctl; 
+  int report = scsi_debugmode;
 
   if (!linux_smart_device::open())
     return false;
@@ -974,14 +983,14 @@ bool linux_megaraid_device::open()
        if (sscanf(line, "%d megaraid_sas_ioctl%n", &mjr, &n1) == 1 && n1 == 22) {
           n1=mknod("/dev/megaraid_sas_ioctl_node", S_IFCHR, makedev(mjr, 0));
           if(report > 0)
-            printf("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno);
+            pout("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno);
           if (n1 >= 0 || errno == EEXIST)
              break;
        }
        else if (sscanf(line, "%d megadev%n", &mjr, &n1) == 1 && n1 == 11) {
           n1=mknod("/dev/megadev0", S_IFCHR, makedev(mjr, 0));
           if(report > 0)
-            printf("Creating /dev/megadev0 = %d\n", n1 >= 0 ? 0 : errno);
+            pout("Creating /dev/megadev0 = %d\n", n1 >= 0 ? 0 : errno);
           if (n1 >= 0 || errno == EEXIST)
              break;
        }
@@ -1014,7 +1023,7 @@ bool linux_megaraid_device::close()
 
 bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
 {
-  int report = con->reportscsiioctl; 
+  int report = scsi_debugmode;
 
   if (report > 0) {
         int k, j;
@@ -1042,24 +1051,21 @@ bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
         pout("%s", buff);
   }
 
-  /* Controller rejects Enable SMART and Test Unit Ready */
+  // Controller rejects Test Unit Ready
   if (iop->cmnd[0] == 0x00)
     return true;
-  if (iop->cmnd[0] == 0x85 && iop->cmnd[1] == 0x06) {
-    if(report > 0)
-      pout("Rejecting SMART/ATA command to controller\n");
-    // Emulate SMART STATUS CHECK drive reply
-    // smartctl fail to work without this
-    if(iop->cmnd[2]==0x2c) {
-      iop->resp_sense_len=22;
-      iop->sensep[0]=0x72; // response code
-      iop->sensep[7]=0x0e; // no idea what it is, copied from sat device answer
-      iop->sensep[8]=0x09; // 
-      iop->sensep[17]=0x4f; // lm
-      iop->sensep[19]=0xc2; // lh
-    }
-    return true;
+
+  if (iop->cmnd[0] == SAT_ATA_PASSTHROUGH_12 || iop->cmnd[0] == SAT_ATA_PASSTHROUGH_16) { 
+    // Controller does not return ATA output registers in SAT sense data
+    if (iop->cmnd[2] & (1 << 5)) // chk_cond
+      return set_err(ENOSYS, "ATA return descriptor not supported by controller firmware");
   }
+  // SMART WRITE LOG SECTOR causing media errors
+  if ((iop->cmnd[0] == SAT_ATA_PASSTHROUGH_16 && iop->cmnd[14] == ATA_SMART_CMD 
+       && iop->cmnd[3]==0 && iop->cmnd[4] == ATA_SMART_WRITE_LOG_SECTOR) || 
+      (iop->cmnd[0] == SAT_ATA_PASSTHROUGH_12 && iop->cmnd[9] == ATA_SMART_CMD &&
+        iop->cmnd[3] == ATA_SMART_WRITE_LOG_SECTOR)) 
+    return set_err(ENOSYS, "SMART WRITE LOG SECTOR command is not supported by controller firmware"); 
 
   if (pt_cmd == NULL)
     return false;
@@ -1078,7 +1084,7 @@ bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb,
   int rc;
 
   memset(&uio, 0, sizeof(uio));
-  pthru = (struct megasas_pthru_frame *)uio.frame.raw;
+  pthru = &uio.frame.pthru;
   pthru->cmd = MFI_CMD_PD_SCSI_IO;
   pthru->cmd_status = 0xFF;
   pthru->scsi_status = 0x0;
@@ -1087,17 +1093,21 @@ bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb,
   pthru->cdb_len = cdbLen;
   pthru->timeout = 0;
   pthru->flags = MFI_FRAME_DIR_READ;
-  pthru->sge_count = 1;
-  pthru->data_xfer_len = dataLen;
-  pthru->sgl.sge32[0].phys_addr = (intptr_t)data;
-  pthru->sgl.sge32[0].length = (uint32_t)dataLen;
+  if (dataLen > 0) {
+    pthru->sge_count = 1;
+    pthru->data_xfer_len = dataLen;
+    pthru->sgl.sge32[0].phys_addr = (intptr_t)data;
+    pthru->sgl.sge32[0].length = (uint32_t)dataLen;
+  }
   memcpy(pthru->cdb, cdb, cdbLen);
 
   uio.host_no = m_hba;
-  uio.sge_count = 1;
-  uio.sgl_off = offsetof(struct megasas_pthru_frame, sgl);
-  uio.sgl[0].iov_base = data;
-  uio.sgl[0].iov_len = dataLen;
+  if (dataLen > 0) {
+    uio.sge_count = 1;
+    uio.sgl_off = offsetof(struct megasas_pthru_frame, sgl);
+    uio.sgl[0].iov_base = data;
+    uio.sgl[0].iov_len = dataLen;
+  }
 
   rc = 0;
   errno = 0;
@@ -1116,14 +1126,11 @@ bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb,
 /* Issue passthrough scsi commands to PERC2/3/4 controllers */
 bool linux_megaraid_device::megadev_cmd(int cdbLen, void *cdb, 
   int dataLen, void *data,
-  int senseLen, void *sense, int /*report*/)
+  int /*senseLen*/, void * /*sense*/, int /*report*/)
 {
   struct uioctl_t uio;
   int rc;
 
-  sense = NULL;
-  senseLen = 0;
-
   /* Don't issue to the controller */
   if (m_disknum == 7)
     return false;
@@ -1189,7 +1196,7 @@ linux_cciss_device::linux_cciss_device(smart_interface * intf,
 
 bool linux_cciss_device::scsi_pass_through(scsi_cmnd_io * iop)
 {
-  int status = cciss_io_interface(get_fd(), m_disknum, iop, con->reportscsiioctl);
+  int status = cciss_io_interface(get_fd(), m_disknum, iop, scsi_debugmode);
   if (status < 0)
       return set_err(-status);
   return true;
@@ -1208,7 +1215,8 @@ public:
   enum escalade_type_t {
     AMCC_3WARE_678K,
     AMCC_3WARE_678K_CHAR,
-    AMCC_3WARE_9000_CHAR
+    AMCC_3WARE_9000_CHAR,
+    AMCC_3WARE_9700_CHAR
   };
 
   linux_escalade_device(smart_interface * intf, const char * dev_name,
@@ -1236,7 +1244,8 @@ linux_escalade_device::linux_escalade_device(smart_interface * intf, const char
 #define MAJOR_STRING_LENGTH 3
 #define DEVICE_STRING_LENGTH 32
 #define NODE_STRING_LENGTH 16
-int setup_3ware_nodes(const char *nodename, const char *driver_name) {
+static int setup_3ware_nodes(const char *nodename, const char *driver_name)
+{
   int              tw_major      = 0;
   int              index         = 0;
   char             majorstring[MAJOR_STRING_LENGTH+1];
@@ -1381,12 +1390,17 @@ int setup_3ware_nodes(const char *nodename, const char *driver_name) {
 
 bool linux_escalade_device::open()
 {
-  if (m_escalade_type == AMCC_3WARE_9000_CHAR || m_escalade_type == AMCC_3WARE_678K_CHAR) {
+  if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR ||
+      m_escalade_type == AMCC_3WARE_678K_CHAR) {
     // the device nodes for these controllers are dynamically assigned,
     // so we need to check that they exist with the correct major
     // numbers and if not, create them
-    const char * node   = (m_escalade_type == AMCC_3WARE_9000_CHAR ? "twa"    : "twe"    );
-    const char * driver = (m_escalade_type == AMCC_3WARE_9000_CHAR ? "3w-9xxx": "3w-xxxx");
+    const char * node   = (m_escalade_type == AMCC_3WARE_9700_CHAR ? "twl"     :
+                           m_escalade_type == AMCC_3WARE_9000_CHAR ? "twa"     :
+                                                                     "twe"      );
+    const char * driver = (m_escalade_type == AMCC_3WARE_9700_CHAR ? "3w-sas"  :
+                           m_escalade_type == AMCC_3WARE_9000_CHAR ? "3w-9xxx" :
+                                                                     "3w-xxxx"  );
     if (setup_3ware_nodes(node, driver))
       return set_err((errno ? errno : ENXIO), "setup_3ware_nodes(\"%s\", \"%s\") failed", node, driver);
   }
@@ -1453,7 +1467,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
   memset(ioctl_buffer, 0, TW_IOCTL_BUFFER_SIZE);
 
   // TODO: Handle controller differences by different classes
-  if (m_escalade_type==AMCC_3WARE_9000_CHAR) {
+  if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) {
     tw_ioctl_apache                               = (TW_Ioctl_Buf_Apache *)ioctl_buffer;
     tw_ioctl_apache->driver_command.control_code  = TW_IOCTL_FIRMWARE_PASS_THROUGH;
     tw_ioctl_apache->driver_command.buffer_length = 512; /* payload size */
@@ -1515,7 +1529,8 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
     // in dwords by 1 to account for the 64-bit single sgl 'address'
     // field. Note that this doesn't agree with the typedefs but it's
     // right (agree with kernel driver behavior/typedefs).
-    if (m_escalade_type==AMCC_3WARE_9000_CHAR && sizeof(long)==8)
+    if ((m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR)
+        && sizeof(long) == 8)
       passthru->size++;
   }
   else if (in.direction == ata_cmd_in::no_data) {
@@ -1527,7 +1542,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
     passthru->sector_count = 0x0;
   }
   else if (in.direction == ata_cmd_in::data_out) {
-    if (m_escalade_type == AMCC_3WARE_9000_CHAR)
+    if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR)
       memcpy(tw_ioctl_apache->data_buffer, in.buffer, in.size);
     else if (m_escalade_type == AMCC_3WARE_678K_CHAR)
       memcpy(tw_ioctl_char->data_buffer,   in.buffer, in.size);
@@ -1540,15 +1555,16 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
     passthru->byte0.sgloff = 0x5;
     passthru->size         = 0x7;  // TODO: Other value for multi-sector ?
     passthru->param        = 0xF;  // PIO data write
-    if (m_escalade_type==AMCC_3WARE_9000_CHAR && sizeof(long)==8)
+    if ((m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR)
+        && sizeof(long) == 8)
       passthru->size++;
   }
   else
-    set_err(EINVAL);
+    return set_err(EINVAL);
 
   // Now send the command down through an ioctl()
   int ioctlreturn;
-  if (m_escalade_type==AMCC_3WARE_9000_CHAR)
+  if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR)
     ioctlreturn=ioctl(get_fd(), TW_IOCTL_FIRMWARE_PASS_THROUGH, tw_ioctl_apache);
   else if (m_escalade_type==AMCC_3WARE_678K_CHAR)
     ioctlreturn=ioctl(get_fd(), TW_CMD_PACKET_WITH_DATA, tw_ioctl_char);
@@ -1599,7 +1615,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
 
   // If this is a read data command, copy data to output buffer
   if (readdata) {
-    if (m_escalade_type==AMCC_3WARE_9000_CHAR)
+    if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR)
       memcpy(in.buffer, tw_ioctl_apache->data_buffer, in.size);
     else if (m_escalade_type==AMCC_3WARE_678K_CHAR)
       memcpy(in.buffer, tw_ioctl_char->data_buffer, in.size);
@@ -1608,7 +1624,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
   }
 
   // Return register values
-  {
+  if (passthru) {
     ata_out_regs_48bit & r = out.out_regs;
     r.error           = passthru->features;
     r.sector_count_16 = passthru->sector_count;
@@ -1621,7 +1637,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
 
   // look for nonexistent devices/ports
   if (   in.in_regs.command == ATA_IDENTIFY_DEVICE
-      && !nonempty((unsigned char *)in.buffer, in.size)) {
+      && !nonempty(in.buffer, in.size)) {
     return set_err(ENODEV, "No drive on port %d", m_disknum);
   }
 
@@ -1633,17 +1649,18 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
 /// Areca RAID support
 
 class linux_areca_device
-: public /*implements*/ ata_device_with_command_set,
+: public /*implements*/ ata_device,
   public /*extends*/ linux_smart_device
 {
 public:
-  linux_areca_device(smart_interface * intf, const char * dev_name, int disknum);
+  linux_areca_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1);
 
 protected:
-  virtual int ata_command_interface(smart_command_set command, int select, char * data);
+  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); 
 
 private:
   int m_disknum; ///< Disk number.
+  int m_encnum;  ///< Enclosure number.
 };
 
 
@@ -1704,8 +1721,8 @@ typedef struct _SRB_BUFFER
 
 // Looks in /proc/scsi to suggest correct areca devices
 // If hint not NULL, return device path guess
-int find_areca_in_proc(char *hint) {
+static int find_areca_in_proc(char *hint)
+{
     const char* proc_format_string="host\tchan\tid\tlun\ttype\topens\tqdepth\tbusy\tonline\n";
 
     // check data formwat
@@ -1755,8 +1772,9 @@ int find_areca_in_proc(char *hint) {
 }
 
 
+#if 0 // For debugging areca code
 
-void dumpdata( unsigned char *block, int len)
+static void dumpdata(unsigned char *block, int len)
 {
        int ln = (len / 16) + 1;         // total line#
        unsigned char c;
@@ -1803,9 +1821,9 @@ void dumpdata( unsigned char *block, int len)
        printf("=====================================================================\n");
 }
 
+#endif
 
-
-int arcmsr_command_handler(int fd, unsigned long arcmsr_cmd, unsigned char *data, int data_len, void *ext_data /* reserved for further use */)
+static int arcmsr_command_handler(int fd, unsigned long arcmsr_cmd, unsigned char *data, int data_len, void *ext_data /* reserved for further use */)
 {
        ARGUSED(ext_data);
 
@@ -1927,14 +1945,14 @@ int arcmsr_command_handler(int fd, unsigned long arcmsr_cmd, unsigned char *data
        // Deal with the different error cases
        if ( ioctlreturn )
        {
-               printf("do_scsi_cmnd_io with write buffer failed code = %x\n", ioctlreturn);
+               pout("do_scsi_cmnd_io with write buffer failed code = %x\n", ioctlreturn);
                return -2;
        }
 
 
        if ( io_hdr.scsi_status )
        {
-               printf("io_hdr.scsi_status with write buffer failed code = %x\n", io_hdr.scsi_status);
+               pout("io_hdr.scsi_status with write buffer failed code = %x\n", io_hdr.scsi_status);
                return -3;
        }
 
@@ -1948,17 +1966,26 @@ int arcmsr_command_handler(int fd, unsigned long arcmsr_cmd, unsigned char *data
 }
 
 
-linux_areca_device::linux_areca_device(smart_interface * intf, const char * dev_name, int disknum)
+linux_areca_device::linux_areca_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
 : smart_device(intf, dev_name, "areca", "areca"),
   linux_smart_device(O_RDWR | O_EXCL | O_NONBLOCK),
-  m_disknum(disknum)
+  m_disknum(disknum),
+  m_encnum(encnum)
 {
-  set_info().info_name = strprintf("%s [areca_%02d]", dev_name, disknum);
+  set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
 }
 
 // Areca RAID Controller
-int linux_areca_device::ata_command_interface(smart_command_set command, int select, char * data)
+// int linux_areca_device::ata_command_interface(smart_command_set command, int select, char * data)
+bool linux_areca_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) 
 {
+if (!ata_cmd_is_ok(in, 
+    true, // data_out_support 
+    false, // TODO: multi_sector_support 
+    true) // ata_48bit_support 
+    )
+    return false; 
+
        // ATA input registers
        typedef struct _ATA_INPUT_REGISTERS
        {
@@ -2032,115 +2059,45 @@ int linux_areca_device::ata_command_interface(smart_command_set command, int sel
        areca_packet[4] = (unsigned char)(((areca_packet_len - 6) >> 8) & 0xff);
        areca_packet[5] = 0x1c; // areca defined code for ATA passthrough command
 
-
        // ----- BEGIN TO SETUP PAYLOAD DATA -----
-
        memcpy(&areca_packet[7], "SmrT", 4);    // areca defined password
-
        ata_cmd = (sATA_INPUT_REGISTERS *)&areca_packet[12];
-       ata_cmd->cylinder_low    = 0x4F;
-       ata_cmd->cylinder_high   = 0xC2;
 
-
-       if ( command == READ_VALUES     ||
-                command == READ_THRESHOLDS ||
-                command == READ_LOG ||
-                command == IDENTIFY ||
-                command == PIDENTIFY )
-       {
-               // the commands will return data
-               areca_packet[6] = 0x13;
-               ata_cmd->sector_count = 0x1;
+       // Set registers
+        {
+           const ata_in_regs_48bit & r = in.in_regs;
+           ata_cmd->features     = r.features_16;
+           ata_cmd->sector_count  = r.sector_count_16;
+           ata_cmd->sector_number = r.lba_low_16;
+           ata_cmd->cylinder_low  = r.lba_mid_16;
+           ata_cmd->cylinder_high = r.lba_high_16;
+           ata_cmd->device_head   = r.device;
+           ata_cmd->command      = r.command;
        }
-       else if ( command == WRITE_LOG )
-       {
-               // the commands will write data
-               areca_packet[6] = 0x14;
+       bool readdata = false; 
+       if (in.direction == ata_cmd_in::data_in) { 
+           readdata = true;
+           // the command will read data
+           areca_packet[6] = 0x13;
        }
-       else
+       else if ( in.direction == ata_cmd_in::no_data )
        {
                // the commands will return no data
                areca_packet[6] = 0x15;
        }
-
-
-       ata_cmd->command = ATA_SMART_CMD;
-       // Now set ATA registers depending upon command
-       switch ( command )
+       else if (in.direction == ata_cmd_in::data_out) 
        {
-       case CHECK_POWER_MODE:  
-               //printf("command = CHECK_POWER_MODE\n");
-               ata_cmd->command = ATA_CHECK_POWER_MODE;        
-               break;
-       case READ_VALUES:
-               //printf("command = READ_VALUES\n");
-               ata_cmd->features = ATA_SMART_READ_VALUES;
-               break;
-       case READ_THRESHOLDS:    
-               //printf("command = READ_THRESHOLDS\n");
-               ata_cmd->features = ATA_SMART_READ_THRESHOLDS;
-               break;
-       case READ_LOG: 
-               //printf("command = READ_LOG\n");
-               ata_cmd->features = ATA_SMART_READ_LOG_SECTOR;
-               ata_cmd->sector_number = select;        
-               break;
-       case WRITE_LOG:        
-               //printf("command = WRITE_LOG\n");    
-               ata_cmd->features = ATA_SMART_WRITE_LOG_SECTOR;
-               memcpy(ata_cmd->data, data, 512);
-               ata_cmd->sector_count = 1;
-               ata_cmd->sector_number = select;
-               break;
-       case IDENTIFY:
-               //printf("command = IDENTIFY\n");   
-               ata_cmd->command = ATA_IDENTIFY_DEVICE;         
-               break;
-       case PIDENTIFY:
-               //printf("command = PIDENTIFY\n");
-               errno=ENODEV;
-               return -1;
-       case ENABLE:
-               //printf("command = ENABLE\n");
-               ata_cmd->features = ATA_SMART_ENABLE;
-               break;
-       case DISABLE:
-               //printf("command = DISABLE\n");
-               ata_cmd->features = ATA_SMART_DISABLE;
-               break;
-       case AUTO_OFFLINE:
-               //printf("command = AUTO_OFFLINE\n");
-               ata_cmd->features = ATA_SMART_AUTO_OFFLINE;
-               // Enable or disable?
-               ata_cmd->sector_count = select;
-               break;
-       case AUTOSAVE:
-               //printf("command = AUTOSAVE\n");
-               ata_cmd->features = ATA_SMART_AUTOSAVE;
-               // Enable or disable?
-               ata_cmd->sector_count = select;
-               break;
-       case IMMEDIATE_OFFLINE:
-               //printf("command = IMMEDIATE_OFFLINE\n");
-               ata_cmd->features = ATA_SMART_IMMEDIATE_OFFLINE;
-               // What test type to run?
-               ata_cmd->sector_number = select;
-               break;
-       case STATUS_CHECK:
-               //printf("command = STATUS_CHECK\n");
-               ata_cmd->features = ATA_SMART_STATUS;           
-               break;
-       case STATUS:
-               //printf("command = STATUS\n");
-               ata_cmd->features = ATA_SMART_STATUS;       
-               break;
-       default:
-               //printf("command = UNKNOWN\n");
-               errno=ENOSYS;
-               return -1;
-       };
+               // the commands will write data
+               memcpy(ata_cmd->data, in.buffer, in.size);
+               areca_packet[6] = 0x14;
+       }
+       else {
+           // COMMAND NOT SUPPORTED VIA ARECA IOCTL INTERFACE
+           return set_err(ENOTSUP, "DATA OUT not supported for this Areca controller type");
+       }
 
-       areca_packet[11] = m_disknum - 1;                  // drive number
+       areca_packet[11] = m_disknum - 1;  // disk#
+       areca_packet[19] = m_encnum - 1;   // enc#
 
        // ----- BEGIN TO SETUP CHECKSUM -----
        for ( int loop = 3; loop < areca_packet_len - 1; loop++ )
@@ -2157,7 +2114,7 @@ int linux_areca_device::ata_command_interface(smart_command_set command, int sel
        expected = arcmsr_command_handler(get_fd(), ARCMSR_IOCTL_CLEAR_RQBUFFER, NULL, 0, NULL);
         if (expected==-3) {
            find_areca_in_proc(NULL);
-           return -1;
+           return set_err(EIO);
        }
 
        expected = arcmsr_command_handler(get_fd(), ARCMSR_IOCTL_CLEAR_WQBUFFER, NULL, 0, NULL);
@@ -2180,45 +2137,36 @@ int linux_areca_device::ata_command_interface(smart_command_set command, int sel
 
        if ( return_buff[expected - 1] != cs )
        {
-               errno = EIO;
-               return -1;
+               return set_err(EIO);
        }
 
        sATA_OUTPUT_REGISTERS *ata_out = (sATA_OUTPUT_REGISTERS *)&return_buff[5] ;
        if ( ata_out->status )
        {
-               if ( command == IDENTIFY )
-               {
-                       pout("The firmware of your Areca RAID controller appears to be outdated!\n" \
-                                "Please update your controller to firmware version 1.46 or later.\n" \
-                                "You may download it here: ftp://ftp.areca.com.tw/RaidCards/BIOS_Firmware\n\n");
-               }
-               errno = EIO;
-               return -1;
+               if ( in.in_regs.command == ATA_IDENTIFY_DEVICE
+                && !nonempty((unsigned char *)in.buffer, in.size)) 
+                {
+                   return set_err(ENODEV, "No drive on port %d", m_disknum);
+                } 
        }
 
        // returns with data
-       if ( command == READ_VALUES     ||
-                command == READ_THRESHOLDS ||
-                command == READ_LOG ||
-                command == IDENTIFY ||
-                command == PIDENTIFY )
-       {
-               memcpy(data, &return_buff[7], 512); 
-       }
-
-       if ( command == CHECK_POWER_MODE )
+       if (readdata)
        {
-               data[0] = ata_out->sector_count;
+               memcpy(in.buffer, &return_buff[7], in.size); 
        }
 
-       if ( command == STATUS_CHECK &&
-                ( ata_out->cylinder_low == 0xF4 && ata_out->cylinder_high == 0x2C ) )
+       // Return register values
        {
-               return 1;
+           ata_out_regs_48bit & r = out.out_regs;
+           r.error           = ata_out->error;
+           r.sector_count_16 = ata_out->sector_count;
+           r.lba_low_16      = ata_out->sector_number;
+           r.lba_mid_16      = ata_out->cylinder_low;
+           r.lba_high_16     = ata_out->cylinder_high;
+           r.status          = ata_out->status;
        }
-
-       return 0;
+       return true;
 }
 
 
@@ -2500,17 +2448,17 @@ int linux_highpoint_device::ata_command_interface(smart_command_set command, int
 
   if (command==WRITE_LOG) {
     unsigned char task[4*sizeof(int)+sizeof(ide_task_request_t)+512];
-    unsigned int *hpt = (unsigned int *)task;
+    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));
 
-    hpt[0] = m_hpt_data[0]; // controller id
-    hpt[1] = m_hpt_data[1]; // channel number
-    hpt[3] = m_hpt_data[2]; // pmport number
-    hpt[2] = HDIO_DRIVE_TASKFILE; // real hd ioctl
+    hpt_tf[0] = m_hpt_data[0]; // controller id
+    hpt_tf[1] = m_hpt_data[1]; // channel number
+    hpt_tf[3] = m_hpt_data[2]; // pmport number
+    hpt_tf[2] = HDIO_DRIVE_TASKFILE; // real hd ioctl
 
     taskfile->data           = 0;
     taskfile->feature        = ATA_SMART_WRITE_LOG_SECTOR;
@@ -2577,13 +2525,13 @@ int linux_highpoint_device::ata_command_interface(smart_command_set command, int
 #if 1
   if (command==IDENTIFY || command==PIDENTIFY) {
     unsigned char deviceid[4*sizeof(int)+512*sizeof(char)];
-    unsigned int *hpt = (unsigned int *)deviceid;
+    unsigned int *hpt_id = (unsigned int *)deviceid;
 
-    hpt[0] = m_hpt_data[0]; // controller id
-    hpt[1] = m_hpt_data[1]; // channel number
-    hpt[3] = m_hpt_data[2]; // pmport number
+    hpt_id[0] = m_hpt_data[0]; // controller id
+    hpt_id[1] = m_hpt_data[1]; // channel number
+    hpt_id[3] = m_hpt_data[2]; // pmport number
 
-    hpt[2] = HDIO_GET_IDENTITY;
+    hpt_id[2] = HDIO_GET_IDENTITY;
     if (!ioctl(get_fd(), HPTIO_CTL, deviceid) && (deviceid[4*sizeof(int)] & 0x8000))
       buff[0]=(command==IDENTIFY)?ATA_IDENTIFY_PACKET_DEVICE:ATA_IDENTIFY_DEVICE;
   }
@@ -2645,8 +2593,13 @@ smart_device * linux_scsi_device::autodetect_open()
     return this;
 
   // No Autodetection if device type was specified by user
-  if (*get_req_type())
-    return this;
+  bool sat_only = false;
+  if (*get_req_type()) {
+    // Detect SAT if device object was created by scan_smart_devices().
+    if (!(m_scanning && !strcmp(get_req_type(), "sat")))
+      return this;
+    sat_only = true;
+  }
 
   // The code below is based on smartd.cpp:SCSIFilterKnown()
 
@@ -2667,49 +2620,59 @@ smart_device * linux_scsi_device::autodetect_open()
 
   int avail_len = req_buff[4] + 5;
   int len = (avail_len < req_len ? avail_len : req_len);
-  if (len < 36)
-      return this;
+  if (len < 36) {
+    if (sat_only) {
+      close();
+      set_err(EIO, "INQUIRY too short for SAT");
+    }
+    return this;
+  }
 
   // Use INQUIRY to detect type
-  smart_device * newdev = 0;
-  try {
+  if (!sat_only) {
+
     // 3ware ?
     if (!memcmp(req_buff + 8, "3ware", 5) || !memcmp(req_buff + 8, "AMCC", 4)) {
       close();
       set_err(EINVAL, "AMCC/3ware controller, please try adding '-d 3ware,N',\n"
-                      "you may need to replace %s with /dev/twaN or /dev/tweN", get_dev_name());
+                      "you may need to replace %s with /dev/twlN, /dev/twaN or /dev/tweN", get_dev_name());
       return this;
     }
+
     // DELL?
     if (!memcmp(req_buff + 8, "DELL    PERC", 12) || !memcmp(req_buff + 8, "MegaRAID", 8)) {
       close();
       set_err(EINVAL, "DELL or MegaRaid controller, please try adding '-d megaraid,N'");
       return this;
     }
-    
+
     // Marvell ?
     if (len >= 42 && !memcmp(req_buff + 36, "MVSATA", 6)) {
       //pout("Device %s: using '-d marvell' for ATA disk with Marvell driver\n", get_dev_name());
       close();
-      newdev = new linux_marvell_device(smi(), get_dev_name(), get_req_type());
+      smart_device_auto_ptr newdev(
+        new linux_marvell_device(smi(), get_dev_name(), get_req_type())
+      );
       newdev->open(); // TODO: Can possibly pass open fd
       delete this;
-      return newdev;
+      return newdev.release();
     }
+  }
 
-    // SAT or USB ?
-    newdev = smi()->autodetect_sat_device(this, req_buff, len);
+  // SAT or USB ?
+  {
+    smart_device * newdev = smi()->autodetect_sat_device(this, req_buff, len);
     if (newdev)
       // NOTE: 'this' is now owned by '*newdev'
       return newdev;
   }
-  catch (...) {
-    // Cleanup if exception occurs after newdev was allocated
-    delete newdev;
-    throw;
-  }
 
   // Nothing special found
+
+  if (sat_only) {
+    close();
+    set_err(EIO, "Not a SAT device");
+  }
   return this;
 }
 
@@ -2762,7 +2725,7 @@ static bool get_usb_id(const char * name, unsigned short & vendor_id,
         && read_id(dir + "/bcdDevice", version)   ))
     return false;
 
-  if (con->reportscsiioctl > 1)
+  if (scsi_debugmode > 1)
     pout("USB ID = 0x%04x:0x%04x (0x%03x)\n", vendor_id, product_id, version);
   return true;
 }
@@ -2775,6 +2738,8 @@ class linux_smart_interface
 : public /*implements*/ smart_interface
 {
 public:
+  virtual std::string get_os_version_str();
+
   virtual std::string get_app_examples(const char * appname);
 
   virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
@@ -2798,6 +2763,15 @@ private:
   smart_device * missing_option(const char * opt);
 };
 
+std::string linux_smart_interface::get_os_version_str()
+{
+  struct utsname u;
+  if (!uname(&u))
+    return strprintf("%s-linux-%s", u.machine, u.release);
+  else
+    return SMARTMONTOOLS_BUILD_HOST;
+}
+
 std::string linux_smart_interface::get_app_examples(const char * appname)
 {
   if (!strcmp(appname, "smartctl"))
@@ -2892,11 +2866,11 @@ bool linux_smart_interface::get_dev_list(smart_device_list & devlist,
       if (autodetect)
         dev = autodetect_smart_device(name);
       else if (is_scsi)
-        dev = new linux_scsi_device(this, name, req_type);
+        dev = new linux_scsi_device(this, name, req_type, true /*scanning*/);
       else
         dev = new linux_ata_device(this, name, req_type);
       if (dev) // autodetect_smart_device() may return nullptr.
-        devlist.add(dev);
+        devlist.push_back(dev);
     }
   }
 
@@ -2918,14 +2892,19 @@ bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist,
     type = "";
 
   bool scan_ata  = (!*type || !strcmp(type, "ata" ));
-  bool scan_scsi = (!*type || !strcmp(type, "scsi"));
+  // "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;
 
   if (scan_ata)
     get_dev_list(devlist, "/dev/hd[a-t]", true, false, type, false);
-  if (scan_scsi) // Try USB autodetection if no type specifed
-    get_dev_list(devlist, "/dev/sd[a-z]", false, true, type, !*type);
+  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);
+    // Support up to 104 devices
+    get_dev_list(devlist, "/dev/sd[a-c][a-z]", false, true, type, autodetect);
+  }
 
   // if we found traditional links, we are done
   if (devlist.size() > 0)
@@ -2952,77 +2931,66 @@ smart_device * linux_smart_interface::missing_option(const char * opt)
   return 0;
 }
 
-// Return true if STR starts with PREFIX.
-static bool str_starts_with(const char * str, const char * prefix)
+// Return kernel release as integer ("2.6.31" -> 206031)
+static unsigned get_kernel_release()
 {
-  return !strncmp(str, prefix, strlen(prefix));
+  struct utsname u;
+  if (uname(&u))
+    return 0;
+  unsigned x = 0, y = 0, z = 0;
+  if (!(sscanf(u.release, "%u.%u.%u", &x, &y, &z) == 3
+        && x < 100 && y < 100 && z < 1000             ))
+    return 0;
+  return x * 100000 + y * 1000 + z;
 }
 
 // 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.
-static const char * lin_dev_prefix = "/dev/";
-static const char * lin_dev_ata_disk_plus = "h";
-static const char * lin_dev_ata_devfs_disk_plus = "ide/";
-static const char * lin_dev_scsi_devfs_disk_plus = "scsi/";
-static const char * lin_dev_scsi_disk_plus = "s";
-static const char * lin_dev_scsi_tape1 = "ns";
-static const char * lin_dev_scsi_tape2 = "os";
-static const char * lin_dev_scsi_tape3 = "nos";
-static const char * lin_dev_3ware_9000_char = "twa";
-static const char * lin_dev_3ware_678k_char = "twe";
-static const char * lin_dev_cciss_dir = "cciss/";
-static const char * lin_dev_areca = "sg";
-
 smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
 {
-  const char * dev_name = name; // TODO: Remove this hack
-  int dev_prefix_len = strlen(lin_dev_prefix);
+  const char * test_name = name;
 
-  // if dev_name null, or string length zero
-  int len;
-  if (!dev_name || !(len = strlen(dev_name)))
-    return 0;
-
-  // Dereference if /dev/disk/by-*/* symlink
-  char linkbuf[100];
-  if (   str_starts_with(dev_name, "/dev/disk/by-")
-      && readlink(dev_name, linkbuf, sizeof(linkbuf)) > 0
-      && str_starts_with(linkbuf, "../../")) {
-    dev_name = linkbuf + sizeof("../../")-1;
+  // Dereference symlinks
+  struct stat st;
+  std::string pathbuf;
+  if (!lstat(name, &st) && S_ISLNK(st.st_mode)) {
+    char * p = realpath(name, (char *)0);
+    if (p) {
+      pathbuf = p;
+      free(p);
+      test_name = pathbuf.c_str();
+    }
   }
+
   // Remove the leading /dev/... if it's there
-  else if (!strncmp(lin_dev_prefix, dev_name, dev_prefix_len)) {
-    if (len <= dev_prefix_len)
-      // if nothing else in the string, unrecognized
-      return 0;
-    // else advance pointer to following characters
-    dev_name += dev_prefix_len;
-  }
+  static const char dev_prefix[] = "/dev/";
+  if (str_starts_with(test_name, dev_prefix))
+    test_name += strlen(dev_prefix);
 
   // form /dev/h* or h*
-  if (!strncmp(lin_dev_ata_disk_plus, dev_name,
-               strlen(lin_dev_ata_disk_plus)))
+  if (str_starts_with(test_name, "h"))
     return new linux_ata_device(this, name, "");
 
   // form /dev/ide/* or ide/*
-  if (!strncmp(lin_dev_ata_devfs_disk_plus, dev_name,
-               strlen(lin_dev_ata_devfs_disk_plus)))
+  if (str_starts_with(test_name, "ide/"))
     return new linux_ata_device(this, name, "");
 
   // form /dev/s* or s*
-  if (!strncmp(lin_dev_scsi_disk_plus, dev_name,
-               strlen(lin_dev_scsi_disk_plus))) {
+  if (str_starts_with(test_name, "s")) {
 
     // Try to detect possible USB->(S)ATA bridge
     unsigned short vendor_id = 0, product_id = 0, version = 0;
-    if (get_usb_id(dev_name, vendor_id, product_id, version)) {
+    if (get_usb_id(test_name, vendor_id, product_id, version)) {
       const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version);
       if (!usbtype)
         return 0;
-      // Linux USB layer does not support 16 byte SAT pass through command
-      if (!strcmp(usbtype, "sat"))
+
+      // Kernels before 2.6.29 do not support the sense data length
+      // required for SAT ATA PASS-THROUGH(16)
+      if (!strcmp(usbtype, "sat") && get_kernel_release() < 206029)
         usbtype = "sat,12";
+
       // 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, ""));
@@ -3033,45 +3001,29 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
   }
 
   // form /dev/scsi/* or scsi/*
-  if (!strncmp(lin_dev_scsi_devfs_disk_plus, dev_name,
-               strlen(lin_dev_scsi_devfs_disk_plus)))
+  if (str_starts_with(test_name, "scsi/"))
     return new linux_scsi_device(this, name, "");
 
   // form /dev/ns* or ns*
-  if (!strncmp(lin_dev_scsi_tape1, dev_name,
-               strlen(lin_dev_scsi_tape1)))
+  if (str_starts_with(test_name, "ns"))
     return new linux_scsi_device(this, name, "");
 
   // form /dev/os* or os*
-  if (!strncmp(lin_dev_scsi_tape2, dev_name,
-               strlen(lin_dev_scsi_tape2)))
+  if (str_starts_with(test_name, "os"))
     return new linux_scsi_device(this, name, "");
 
   // form /dev/nos* or nos*
-  if (!strncmp(lin_dev_scsi_tape3, dev_name,
-               strlen(lin_dev_scsi_tape3)))
+  if (str_starts_with(test_name, "nos"))
     return new linux_scsi_device(this, name, "");
 
-  // form /dev/twa*
-  if (!strncmp(lin_dev_3ware_9000_char, dev_name,
-               strlen(lin_dev_3ware_9000_char)))
-    return missing_option("-d 3ware,N");
-
-  // form /dev/twe*
-  if (!strncmp(lin_dev_3ware_678k_char, dev_name,
-               strlen(lin_dev_3ware_678k_char)))
+  // 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");
 
-  // form /dev/cciss*
-  if (!strncmp(lin_dev_cciss_dir, dev_name,
-               strlen(lin_dev_cciss_dir)))
+  // form /dev/cciss/* or cciss/*
+  if (str_starts_with(test_name, "cciss/"))
     return missing_option("-d cciss,N");
 
-  // form /dev/sg*
-  if ( !strncmp(lin_dev_areca, dev_name,
-                strlen(lin_dev_areca)) )
-    return missing_option("-d areca,N");
-
   // we failed to recognize any of the forms
   return 0;
 }
@@ -3094,7 +3046,9 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
       return 0;
     }
 
-    if (!strncmp(name, "/dev/twa", 8))
+    if (!strncmp(name, "/dev/twl", 8))
+      return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_9700_CHAR, disknum);
+    else if (!strncmp(name, "/dev/twa", 8))
       return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_9000_CHAR, disknum);
     else if (!strncmp(name, "/dev/twe", 8))
       return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_678K_CHAR, disknum);
@@ -3104,16 +3058,17 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
 
   // Areca?
   disknum = n1 = n2 = -1;
-  if (sscanf(type, "areca,%n%d%n", &n1, &disknum, &n2) == 1 || n1 == 6) {
-    if (n2 != (int)strlen(type)) {
-      set_err(EINVAL, "Option -d areca,N requires N to be a non-negative integer");
+  int encnum = 1;
+  if (sscanf(type, "areca,%n%d/%d%n", &n1, &disknum, &encnum, &n2) >= 1 || n1 == 6) {
+    if (!(1 <= disknum && disknum <= 128)) {
+      set_err(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum);
       return 0;
     }
-    if (!(1 <= disknum && disknum <= 24)) {
-      set_err(EINVAL, "Option -d areca,N (N=%d) must have 1 <= N <= 24", disknum);
+    if (!(1 <= encnum && encnum <= 8)) {
+      set_err(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum);
       return 0;
     }
-    return new linux_areca_device(this, name, disknum);
+    return new linux_areca_device(this, name, disknum, encnum);
   }
 
   // Highpoint ?
@@ -3129,7 +3084,7 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
       set_err(EINVAL, "Option '-d hpt,L/M/N' invalid controller id L supplied");
       return 0;
     }
-    if (!(1 <= channel && channel <= 8)) {
+    if (!(1 <= channel && channel <= 16)) {
       set_err(EINVAL, "Option '-d hpt,L/M/N' invalid channel number M supplied");
       return 0;
     }
@@ -3148,8 +3103,8 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
       set_err(EINVAL, "Option -d cciss,N requires N to be a non-negative integer");
       return 0;
     }
-    if (!(0 <= disknum && disknum <= 15)) {
-      set_err(EINVAL, "Option -d cciss,N (N=%d) must have 0 <= N <= 15", disknum);
+    if (!(0 <= disknum && disknum <= 127)) {
+      set_err(EINVAL, "Option -d cciss,N (N=%d) must have 0 <= N <= 127", disknum);
       return 0;
     }
     return new linux_cciss_device(this, name, disknum);
@@ -3165,7 +3120,7 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
 
 std::string linux_smart_interface::get_valid_custom_dev_types_str()
 {
-  return "marvell, areca,N, 3ware,N, hpt,L/M/N, megaraid,N"
+  return "marvell, areca,N/E, 3ware,N, hpt,L/M/N, megaraid,N"
 #ifdef HAVE_LINUX_CCISS_IOCTL_H
                                               ", cciss,N"
 #endif