]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - scsiata.cpp
Imported Upstream version 6.3+svn3990
[mirror_smartmontools-debian.git] / scsiata.cpp
index 4ae87022cd6f92c1df1c5ba0613da018a0719d66..abcb468d80161811c68f816e43b343a1884981c9 100644 (file)
@@ -3,8 +3,8 @@
  *
  * Home page of code is: http://smartmontools.sourceforge.net
  *
- * Copyright (C) 2006-9 Douglas Gilbert <dougg@torque.net>
- * Copyright (C) 2009   Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2006-12 Douglas Gilbert <dgilbert@interlog.com>
+ * Copyright (C) 2009-13 Christian Franke <smartmontools-support@lists.sourceforge.net>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,8 @@
  * any later version.
  *
  * You should have received a copy of the GNU General Public License
- * (for example COPYING); if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * (for example COPYING); if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * The code in this file is based on the SCSI to ATA Translation (SAT)
  * draft found at http://www.t10.org . The original draft used for this
 
 #include <stdio.h>
 #include <string.h>
+#include <stdlib.h>
 #include <ctype.h>
+#include <errno.h>
 
 #include "config.h"
 #include "int64.h"
-#include "extern.h"
 #include "scsicmds.h"
 #include "atacmds.h" // ataReadHDIdentity()
+#include "knowndrives.h" // lookup_usb_device()
 #include "utility.h"
 #include "dev_interface.h"
 #include "dev_ata_cmd_set.h" // ata_device_with_command_set
 #include "dev_tunnelled.h" // tunnelled_device<>
 
-const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 2876 2009-08-20 22:53:18Z dlukes $";
-
-/* for passing global control variables */
-extern smartmonctrl *con;
+const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 3922 2014-06-23 19:17:18Z chrfranke $";
 
 /* This is a slightly stretched SCSI sense "descriptor" format header.
    The addition is to allow the 0x70 and 0x71 response codes. The idea
@@ -93,12 +92,6 @@ struct sg_scsi_sense_hdr {
 static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
                                    struct sg_scsi_sense_hdr * sshp);
 
-/* Attempt to find the first SCSI sense data descriptor that matches the
-   given 'desc_type'. If found return pointer to start of sense data
-   descriptor; otherwise (including fixed format sense data) returns NULL. */
-static const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
-                                                     int sense_len, int desc_type);
-
 #define SAT_ATA_PASSTHROUGH_12LEN 12
 #define SAT_ATA_PASSTHROUGH_16LEN 16
 
@@ -115,28 +108,44 @@ class sat_device
 : public tunnelled_device<
     /*implements*/ ata_device
     /*by tunnelling through a*/, scsi_device
-  >
+  >,
+  virtual public /*implements*/ scsi_device
 {
 public:
   sat_device(smart_interface * intf, scsi_device * scsidev,
-    const char * req_type, int passthrulen = 0);
+    const char * req_type, int passthrulen = 0, bool enable_auto = false);
 
   virtual ~sat_device() throw();
 
+  virtual smart_device * autodetect_open();
+
   virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
 
+  virtual bool scsi_pass_through(scsi_cmnd_io * iop);
+
 private:
   int m_passthrulen;
+  bool m_enable_auto;
 };
 
 
 sat_device::sat_device(smart_interface * intf, scsi_device * scsidev,
-  const char * req_type, int passthrulen /*= 0*/)
-: smart_device(intf, scsidev->get_dev_name(), "sat", req_type),
+  const char * req_type, int passthrulen /* = 0 */, bool enable_auto /* = false */)
+: smart_device(intf, scsidev->get_dev_name(),
+    (enable_auto ? "sat,auto" : "sat"), req_type),
   tunnelled_device<ata_device, scsi_device>(scsidev),
-  m_passthrulen(passthrulen)
+  m_passthrulen(passthrulen),
+  m_enable_auto(enable_auto)
 {
-  set_info().info_name = strprintf("%s [SAT]", scsidev->get_info_name());
+  if (enable_auto)
+    hide_ata(); // Start as SCSI, switch to ATA in autodetect_open()
+  else
+    hide_scsi(); // ATA always
+  if (strcmp(scsidev->get_dev_type(), "scsi"))
+    set_info().dev_type += strprintf("+%s", scsidev->get_dev_type());
+
+  set_info().info_name = strprintf("%s [%sSAT]", scsidev->get_info_name(),
+                                   (enable_auto ? "SCSI/" : ""));
 }
 
 sat_device::~sat_device() throw()
@@ -219,10 +228,12 @@ sat_device::~sat_device() throw()
 
 bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 {
-  if (!ata_cmd_is_ok(in,
-    true, // data_out_support
-    true, // multi_sector_support
-    true) // ata_48bit_support
+  if (!ata_cmd_is_supported(in,
+    ata_device::supports_data_out |
+    ata_device::supports_output_regs |
+    ata_device::supports_multi_sector |
+    ata_device::supports_48bit,
+    "SAT")
   )
     return false;
 
@@ -337,7 +348,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 
     scsi_device * scsidev = get_tunnel_dev();
     if (!scsidev->scsi_pass_through(&io_hdr)) {
-        if (con->reportscsiioctl > 0)
+        if (scsi_debugmode > 0)
             pout("sat_device::ata_pass_through: scsi_pass_through() failed, "
                  "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
         return set_err(scsidev->get_err());
@@ -361,10 +372,10 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
         scsi_do_sense_disect(&io_hdr, &sinfo);
         status = scsiSimpleSenseFilter(&sinfo);
         if (0 != status) {
-            if (con->reportscsiioctl > 0) {
+            if (scsi_debugmode > 0) {
                 pout("sat_device::ata_pass_through: scsi error: %s\n",
                      scsiErrString(status));
-                if (ardp && (con->reportscsiioctl > 1)) {
+                if (ardp && (scsi_debugmode > 1)) {
                     pout("Values from ATA Return Descriptor are:\n");
                     dStrHex((const char *)ardp, ard_len, 1);
                 }
@@ -377,7 +388,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
     if (ck_cond) {     /* expecting SAT specific sense data */
         if (have_sense) {
             if (ardp) {
-                if (con->reportscsiioctl > 1) {
+                if (scsi_debugmode > 1) {
                     pout("Values from ATA Return Descriptor are:\n");
                     dStrHex((const char *)ardp, ard_len, 1);
                 }
@@ -410,7 +421,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
                 (0 == ssh.asc) &&
                 (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq)) {
                 if (ardp) {
-                    if (con->reportscsiioctl > 0) {
+                    if (scsi_debugmode > 0) {
                         pout("Values from ATA Return Descriptor are:\n");
                         dStrHex((const char *)ardp, ard_len, 1);
                     }
@@ -422,6 +433,45 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
     return true;
 }
 
+bool sat_device::scsi_pass_through(scsi_cmnd_io * iop)
+{
+  scsi_device * scsidev = get_tunnel_dev();
+  if (!scsidev->scsi_pass_through(iop)) {
+    set_err(scsidev->get_err());
+    return false;
+  }
+  return true;
+}
+
+smart_device * sat_device::autodetect_open()
+{
+  if (!open() || !m_enable_auto)
+    return this;
+
+  scsi_device * scsidev = get_tunnel_dev();
+
+  unsigned char inqdata[36] = {0, };
+  if (scsiStdInquiry(scsidev, inqdata, sizeof(inqdata))) {
+      smart_device::error_info err = scsidev->get_err();
+      close();
+      set_err(err.no, "INQUIRY [SAT]: %s", err.msg.c_str());
+      return this;
+  }
+
+  // Check for SAT "VENDOR"
+  int inqsize = inqdata[4] + 5;
+  bool sat = (inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8));
+
+  // Change interface
+  hide_ata(!sat);
+  hide_scsi(sat);
+
+  set_info().dev_type = (sat ? "sat" : scsidev->get_dev_type());
+  set_info().info_name = strprintf("%s [%s]", scsidev->get_info_name(),
+                                   (sat ? "SAT" : "SCSI"));
+  return this;
+}
+
 } // namespace
 
 /////////////////////////////////////////////////////////////////////////////
@@ -432,11 +482,16 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 
 static bool has_sat_pass_through(ata_device * dev, bool packet_interface = false)
 {
+    /* Note:  malloc() ensures the read buffer lands on a single
+       page.  This avoids some bugs seen on LSI controlers under
+       FreeBSD */
+    char *data = (char *)malloc(512);
     ata_cmd_in in;
     in.in_regs.command = (packet_interface ? ATA_IDENTIFY_PACKET_DEVICE : ATA_IDENTIFY_DEVICE);
-    char data[512];
     in.set_data_in(data, 1);
-    return dev->ata_pass_through(in);
+    bool ret = dev->ata_pass_through(in);
+    free(data);
+    return ret;
 }
 
 /////////////////////////////////////////////////////////////////////////////
@@ -478,32 +533,6 @@ static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
 }
 
 
-static const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
-                                                     int sense_len, int desc_type)
-{
-    int add_sen_len, add_len, desc_len, k;
-    const unsigned char * descp;
-
-    if ((sense_len < 8) || (0 == (add_sen_len = sensep[7])))
-        return NULL;
-    if ((sensep[0] < 0x72) || (sensep[0] > 0x73))
-        return NULL;
-    add_sen_len = (add_sen_len < (sense_len - 8)) ?
-                         add_sen_len : (sense_len - 8);
-    descp = &sensep[8];
-    for (desc_len = 0, k = 0; k < add_sen_len; k += desc_len) {
-        descp += desc_len;
-        add_len = (k < (add_sen_len - 1)) ? descp[1]: -1;
-        desc_len = add_len + 2;
-        if (descp[0] == desc_type)
-            return descp;
-        if (add_len < 0) /* short descriptor ?? */
-            break;
-    }
-    return NULL;
-}
-
-
 // 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,
@@ -517,7 +546,7 @@ static bool scsi_pass_through_and_check(scsi_device * scsidev,  scsi_cmnd_io * i
 
   // Run cmd
   if (!scsidev->scsi_pass_through(iop)) {
-    if (con->reportscsiioctl > 0)
+    if (scsi_debugmode > 0)
       pout("%sscsi_pass_through() failed, errno=%d [%s]\n",
            msg, scsidev->get_errno(), scsidev->get_errmsg());
     return false;
@@ -528,7 +557,7 @@ static bool scsi_pass_through_and_check(scsi_device * scsidev,  scsi_cmnd_io * i
   scsi_do_sense_disect(iop, &sinfo);
   int err = scsiSimpleSenseFilter(&sinfo);
   if (err) {
-    if (con->reportscsiioctl > 0)
+    if (scsi_debugmode > 0)
       pout("%sscsi error: %s\n", msg, scsiErrString(err));
     return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err));
   }
@@ -586,7 +615,6 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
     int copydata = 0;
     int outlen = 0;
     int ck_cond = 0;    /* set to 1 to read register(s) back */
-    int protocol = 3;   /* non-data */
     int t_dir = 1;      /* 0 -> to device, 1 -> from device */
     int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
     int t_length = 0;   /* 0 -> no data transferred */
@@ -611,7 +639,6 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
     case READ_VALUES:           /* READ DATA */
         feature = ATA_SMART_READ_VALUES;
         sector_count = 1;     /* one (512 byte) block */
-        protocol = 4;   /* PIO data-in */
         t_length = 2;   /* sector count holds count */
         copydata = 512;
         break;
@@ -619,7 +646,6 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         feature = ATA_SMART_READ_THRESHOLDS;
         sector_count = 1;     /* one (512 byte) block */
         lba_low = 1;
-        protocol = 4;   /* PIO data-in */
         t_length = 2;   /* sector count holds count */
         copydata=512;
         break;
@@ -627,7 +653,6 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         feature = ATA_SMART_READ_LOG_SECTOR;
         sector_count = 1;     /* one (512 byte) block */
         lba_low = select;
-        protocol = 4;   /* PIO data-in */
         t_length = 2;   /* sector count holds count */
         copydata = 512;
         break;
@@ -635,7 +660,6 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         feature = ATA_SMART_WRITE_LOG_SECTOR;
         sector_count = 1;     /* one (512 byte) block */
         lba_low = select;
-        protocol = 5;   /* PIO data-out */
         t_length = 2;   /* sector count holds count */
         t_dir = 0;      /* to device */
         outlen = 512;
@@ -643,14 +667,12 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
     case IDENTIFY:
         ata_command = ATA_IDENTIFY_DEVICE;
         sector_count = 1;     /* one (512 byte) block */
-        protocol = 4;   /* PIO data-in */
         t_length = 2;   /* sector count holds count */
         copydata = 512;
         break;
     case PIDENTIFY:
         ata_command = ATA_IDENTIFY_PACKET_DEVICE;
         sector_count = 1;     /* one (512 byte) block */
-        protocol = 4;   /* PIO data-in */
         t_length = 2;   /* sector count (7:0) holds count */
         copydata = 512;
         break;
@@ -736,7 +758,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
 
     scsi_device * scsidev = get_tunnel_dev();
     if (!scsidev->scsi_pass_through(&io_hdr)) {
-        if (con->reportscsiioctl > 0)
+        if (scsi_debugmode > 0)
             pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
                  "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
         set_err(scsidev->get_err());
@@ -778,7 +800,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
 
 
         if (!scsidev->scsi_pass_through(&io_hdr)) {
-            if (con->reportscsiioctl > 0)
+            if (scsi_debugmode > 0)
                 pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
                      "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
             set_err(scsidev->get_err());
@@ -792,7 +814,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         }
 
 
-        if (con->reportscsiioctl > 1) {
+        if (scsi_debugmode > 1) {
             pout("Values from ATA Return Descriptor are:\n");
             dStrHex((const char *)ardp, ard_len, 1);
         }
@@ -875,7 +897,8 @@ class usbjmicron_device
 {
 public:
   usbjmicron_device(smart_interface * intf, scsi_device * scsidev,
-                    const char * req_type, bool ata_48bit_support, int port);
+                    const char * req_type, bool prolific,
+                    bool ata_48bit_support, int port);
 
   virtual ~usbjmicron_device() throw();
 
@@ -886,16 +909,19 @@ public:
 private:
   bool get_registers(unsigned short addr, unsigned char * buf, unsigned short size);
 
+  bool m_prolific;
   bool m_ata_48bit_support;
   int m_port;
 };
 
 
 usbjmicron_device::usbjmicron_device(smart_interface * intf, scsi_device * scsidev,
-                                     const char * req_type, bool ata_48bit_support, int port)
+                                     const char * req_type, bool prolific,
+                                     bool ata_48bit_support, int port)
 : smart_device(intf, scsidev->get_dev_name(), "usbjmicron", req_type),
   tunnelled_device<ata_device, scsi_device>(scsidev),
-  m_ata_48bit_support(ata_48bit_support), m_port(port)
+  m_prolific(prolific), m_ata_48bit_support(ata_48bit_support),
+  m_port(port >= 0 || !prolific ? port : 0)
 {
   set_info().info_name = strprintf("%s [USB JMicron]", scsidev->get_info_name());
 }
@@ -939,24 +965,14 @@ bool usbjmicron_device::open()
 
 bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 {
-  if (!ata_cmd_is_ok(in,
-    true,  // data_out_support
-    false, // !multi_sector_support
-    m_ata_48bit_support) // limited, see below
+  if (!ata_cmd_is_supported(in,
+    ata_device::supports_data_out |
+    ata_device::supports_smart_status |
+    (m_ata_48bit_support ? ata_device::supports_48bit_hi_null : 0),
+    "JMicron")
   )
     return false;
 
-  bool is_smart_status = (   in.in_regs.command  == ATA_SMART_CMD
-                          && in.in_regs.features == ATA_SMART_STATUS);
-
-  // Support output registers for SMART STATUS
-  if (in.out_needed.is_set() && !is_smart_status)
-    return set_err(ENOSYS, "ATA output registers not supported");
-
-  // Support 48-bit commands with zero high bytes
-  if (in.in_regs.is_real_48bit_cmd())
-    return set_err(ENOSYS, "48-bit ATA commands not fully supported");
-
   if (m_port < 0)
     return set_err(EIO, "Unknown JMicron port");
 
@@ -964,7 +980,10 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   memset(&io_hdr, 0, sizeof(io_hdr));
 
   bool rwbit = true;
-  unsigned char smart_status = 0;
+  unsigned char smart_status = 0xff;
+
+  bool is_smart_status = (   in.in_regs.command  == ATA_SMART_CMD
+                          && in.in_regs.features == ATA_SMART_STATUS);
 
   if (is_smart_status && in.out_needed.is_set()) {
     io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
@@ -992,7 +1011,7 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   }
 
   // Build pass through command
-  unsigned char cdb[12];
+  unsigned char cdb[14];
   cdb[ 0] = 0xdf;
   cdb[ 1] = (rwbit ? 0x10 : 0x00);
   cdb[ 2] = 0x00;
@@ -1005,9 +1024,12 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   cdb[ 9] = in.in_regs.lba_high;
   cdb[10] = in.in_regs.device | (m_port == 0 ? 0xa0 : 0xb0);
   cdb[11] = in.in_regs.command;
+  // Prolific PL3507
+  cdb[12] = 0x06;
+  cdb[13] = 0x7b;
 
   io_hdr.cmnd = cdb;
-  io_hdr.cmnd_len = sizeof(cdb);
+  io_hdr.cmnd_len = (!m_prolific ? 12 : 14);
 
   scsi_device * scsidev = get_tunnel_dev();
   if (!scsi_pass_through_and_check(scsidev, &io_hdr,
@@ -1016,15 +1038,22 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
 
   if (in.out_needed.is_set()) {
     if (is_smart_status) {
+      if (io_hdr.resid == 1)
+        // Some (Prolific) USB bridges do not transfer a status byte
+        return set_err(ENOSYS, "Incomplete response, status byte missing [JMicron]");
+
       switch (smart_status) {
-        case 0x01: case 0xc2:
+        case 0xc2:
           out.out_regs.lba_high = 0xc2;
           out.out_regs.lba_mid = 0x4f;
           break;
-        case 0x00: case 0x2c:
+        case 0x2c:
           out.out_regs.lba_high = 0x2c;
           out.out_regs.lba_mid = 0xf4;
           break;
+        default:
+          // Some (JM20336) USB bridges always return 0x01, regardless of SMART Status
+          return set_err(ENOSYS, "Invalid status byte (0x%02x) [JMicron]", smart_status);
       }
     }
 
@@ -1054,7 +1083,7 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
 bool usbjmicron_device::get_registers(unsigned short addr,
                                       unsigned char * buf, unsigned short size)
 {
-  unsigned char cdb[12];
+  unsigned char cdb[14];
   cdb[ 0] = 0xdf;
   cdb[ 1] = 0x10;
   cdb[ 2] = 0x00;
@@ -1067,6 +1096,9 @@ bool usbjmicron_device::get_registers(unsigned short addr,
   cdb[ 9] = 0x00;
   cdb[10] = 0x00;
   cdb[11] = 0xfd;
+  // Prolific PL3507
+  cdb[12] = 0x06;
+  cdb[13] = 0x7b;
 
   scsi_cmnd_io io_hdr;
   memset(&io_hdr, 0, sizeof(io_hdr));
@@ -1075,6 +1107,7 @@ bool usbjmicron_device::get_registers(unsigned short addr,
   io_hdr.dxferp = buf;
   io_hdr.cmnd = cdb;
   io_hdr.cmnd_len = sizeof(cdb);
+  io_hdr.cmnd_len = (!m_prolific ? 12 : 14);
 
   scsi_device * scsidev = get_tunnel_dev();
   if (!scsi_pass_through_and_check(scsidev, &io_hdr,
@@ -1119,10 +1152,11 @@ usbsunplus_device::~usbsunplus_device() throw()
 
 bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 {
-  if (!ata_cmd_is_ok(in,
-    true,  // data_out_support
-    false, // !multi_sector_support
-    true)  // ata_48bit_support
+  if (!ata_cmd_is_supported(in,
+    ata_device::supports_data_out |
+    ata_device::supports_output_regs |
+    ata_device::supports_48bit,
+    "Sunplus")
   )
     return false;
 
@@ -1247,13 +1281,19 @@ using namespace sat;
 ata_device * smart_interface::get_sat_device(const char * type, scsi_device * scsidev)
 {
   if (!strncmp(type, "sat", 3)) {
-    int ptlen = 0, n1 = -1, n2 = -1;
-    if (!(((sscanf(type, "sat%n,%d%n", &n1, &ptlen, &n2) == 1 && n2 == (int)strlen(type)) || n1 == (int)strlen(type))
-        && (ptlen == 0 || ptlen == 12 || ptlen == 16))) {
-      set_err(EINVAL, "Option '-d sat,<n>' requires <n> to be 0, 12 or 16");
+    const char * t = type + 3;
+    bool enable_auto = false;
+    if (!strncmp(t, ",auto", 5)) {
+      t += 5;
+      enable_auto = true;
+    }
+    int ptlen = 0, n = -1;
+    if (*t && !(sscanf(t, ",%d%n", &ptlen, &n) == 1 && n == (int)strlen(t)
+                && (ptlen == 0 || ptlen == 12 || ptlen == 16))) {
+      set_err(EINVAL, "Option '-d sat[,auto][,N]' requires N to be 0, 12 or 16");
       return 0;
     }
-    return new sat_device(this, scsidev, type, ptlen);
+    return new sat_device(this, scsidev, type, ptlen, enable_auto);
   }
 
   else if (!strncmp(type, "usbcypress", 10)) {
@@ -1269,6 +1309,11 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
 
   else if (!strncmp(type, "usbjmicron", 10)) {
     const char * t = type + 10;
+    bool prolific = false;
+    if (!strncmp(t, ",p", 2)) {
+      t += 2;
+      prolific = true;
+    }
     bool ata_48bit_support = false;
     if (!strncmp(t, ",x", 2)) {
       t += 2;
@@ -1277,10 +1322,10 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
     int port = -1, n = -1;
     if (*t && !(  (sscanf(t, ",%d%n", &port, &n) == 1
                 && n == (int)strlen(t) && 0 <= port && port <= 1))) {
-      set_err(EINVAL, "Option '-d usbmicron[,x],<n>' requires <n> to be 0 or 1");
+      set_err(EINVAL, "Option '-d usbjmicron[,p][,x],<n>' requires <n> to be 0 or 1");
       return 0;
     }
-    return new usbjmicron_device(this, scsidev, type, ata_48bit_support, port);
+    return new usbjmicron_device(this, scsidev, type, prolific, ata_48bit_support, port);
   }
 
   else if (!strcmp(type, "usbsunplus")) {
@@ -1301,40 +1346,11 @@ ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev,
   if (!scsidev->is_open())
     return 0;
 
-  ata_device * atadev = 0;
-  try {
-    // SAT ?
-    if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8)) { // TODO: Linux-specific?
-      atadev = new sat_device(this, scsidev, "");
-      if (has_sat_pass_through(atadev))
-        return atadev; // Detected SAT
-      atadev->release(scsidev);
-      delete atadev;
-    }
-
-/* The new usbcypress_device(this, scsidev, "", 0x24) sends vendor specific comand to non-cypress devices.
- * It's dangerous as other device may interpret such command as own valid vendor specific command.
- * I commented it out untill problem resolved
- */
-#if 0
-    // USB ?
-    {
-      atadev = new usbcypress_device(this, scsidev, "", 0x24);
-      if (has_usbcypress_pass_through(atadev,
-            (inqdata && inqsize >= 36 ? (const char*)inqdata  + 8 : 0),
-            (inqdata && inqsize >= 36 ? (const char*)inqdata + 16 : 0) ))
-        return atadev; // Detected USB
-      atadev->release(scsidev);
-      delete atadev;
-    }
-#endif
-  }
-  catch (...) {
-    if (atadev) {
-      atadev->release(scsidev);
-      delete atadev;
-    }
-    throw;
+  // SAT ?
+  if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8)) { // TODO: Linux-specific?
+    ata_device_auto_ptr atadev( new sat_device(this, scsidev, "") , scsidev);
+    if (has_sat_pass_through(atadev.get()))
+      return atadev.release(); // Detected SAT
   }
 
   return 0;
@@ -1344,58 +1360,6 @@ ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev,
 /////////////////////////////////////////////////////////////////////////////
 // USB device type detection
 
-struct usb_id_entry {
-  int vendor_id, product_id, version;
-  const char * type;
-};
-
-const char d_sat[]     = "sat";
-const char d_cypress[] = "usbcypress";
-const char d_jmicron[] = "usbjmicron";
-const char d_jmicron_x[] = "usbjmicron,x";
-const char d_sunplus[] = "usbsunplus";
-const char d_unsup[]   = "unsupported";
-
-// Map USB IDs -> '-d type' string
-const usb_id_entry usb_ids[] = {
-  { 0x04b4, 0x6830, 0x0001, d_unsup   }, // Cypress CY7C68300A (AT2)
-  { 0x04b4, 0x6830, 0x0240, d_cypress }, // Cypress CY7C68300B/C (AT2LP)
-//{ 0x04b4, 0x6831,     -1, d_cypress }, // Cypress CY7C68310 (ISD-300LP)
-  { 0x04fc, 0x0c15, 0xf615, d_sunplus }, // SunPlus SPDIF215
-  { 0x04fc, 0x0c25, 0x0103, d_sunplus }, // SunPlus SPDIF225 (USB+SATA->SATA)
-  { 0x059b, 0x0275, 0x0001, d_unsup   }, // Iomega MDHD500-U
-  { 0x059f, 0x0651,     -1, d_unsup   }, // LaCie hard disk (FA Porsche design)
-  { 0x059f, 0x1018,     -1, d_sat     }, // LaCie hard disk (Neil Poulton design)
-  { 0x067b, 0x3507, 0x0001, d_unsup   }, // Prolific PL3507
-  { 0x0930, 0x0b09,     -1, d_sunplus }, // Toshiba PX1396E-3T01 (similar to Dura Micro)
-  { 0x0bc2, 0x2000,     -1, d_sat     }, // Seagate FreeAgent Go
-  { 0x0bc2, 0x2100,     -1, d_sat     }, // Seagate FreeAgent Go
-  { 0x0bc2, 0x3001,     -1, d_sat     }, // Seagate FreeAgent Desk
-  { 0x0c0b, 0xb159, 0x0103, d_sunplus }, // Dura Micro (Sunplus USB-bridge)
-  { 0x0d49, 0x7310, 0x0125, d_sat     }, // Maxtor OneTouch 4
-  { 0x0d49, 0x7350, 0x0125, d_sat     }, // Maxtor OneTouch 4 Mini
-  { 0x0d49, 0x7450, 0x0122, d_sat     }, // Maxtor Basics Portable
-  { 0x1058, 0x0704, 0x0175, d_sat     }, // WD My Passport Essential
-  { 0x1058, 0x0705, 0x0175, d_sat     }, // WD My Passport Elite
-  { 0x1058, 0x0906, 0x0012, d_sat     }, // WD My Book ES
-  { 0x1058, 0x1001, 0x0104, d_sat     }, // WD Elements Desktop
-  { 0x1058, 0x1003, 0x0175, d_sat     }, // WD Elements Desktop WDE1UBK...
-  { 0x1058, 0x1010, 0x0105, d_sat     }, // WD Elements
-  { 0x1058, 0x1100, 0x0165, d_sat     }, // WD My Book Essential
-  { 0x1058, 0x1102, 0x1028, d_sat     }, // WD My Book
-  { 0x13fd, 0x1240, 0x0104, d_sat     }, // Initio ? (USB->SATA)
-  { 0x13fd, 0x1340, 0x0208, d_sat     }, // Initio ? (USB+SATA->SATA)
-  { 0x152d, 0x2329, 0x0100, d_jmicron }, // JMicron JM20329 (USB->SATA)
-  { 0x152d, 0x2336, 0x0100, d_jmicron_x},// JMicron JM20336 (USB+SATA->SATA, USB->2xSATA)
-  { 0x152d, 0x2338, 0x0100, d_jmicron }, // JMicron JM20337/8 (USB->SATA+PATA, USB+SATA->PATA)
-  { 0x152d, 0x2339, 0x0100, d_jmicron_x},// JMicron JM20339 (USB->SATA)
-  { 0x18a5, 0x0215, 0x0001, d_sat     }, // Verbatim FW/USB160 - Oxford OXUF934SSA-LQAG (USB+IEE1394->SATA)
-  { 0x1bcf, 0x0c31,     -1, d_sunplus }  // SunplusIT
-};
-
-const unsigned num_usb_ids = sizeof(usb_ids)/sizeof(usb_ids[0]);
-
-
 // Format USB ID for error messages
 static std::string format_usb_id(int vendor_id, int product_id, int version)
 {
@@ -1409,41 +1373,31 @@ static std::string format_usb_id(int vendor_id, int product_id, int version)
 const char * smart_interface::get_usb_dev_type_by_id(int vendor_id, int product_id,
                                                      int version /*= -1*/)
 {
-  const usb_id_entry * entry = 0;
-  bool state = false;
-
-  for (unsigned i = 0; i < num_usb_ids; i++) {
-    const usb_id_entry & e = usb_ids[i];
-    if (!(vendor_id == e.vendor_id && product_id == e.product_id))
-      continue;
-
-    // If two entries with same vendor:product ID have different
-    // types, use version (if provided by OS) to select entry.
-    bool s = (version >= 0 && version == e.version);
-    if (entry) {
-      if (s <= state) {
-        if (s == state && e.type != entry->type) {
-          set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'",
-                  format_usb_id(vendor_id, product_id, version).c_str(),
-                  e.type, entry->type);
-          return 0;
-        }
-        continue;
-      }
-    }
-    state = s;
-    entry = &e;
-  }
+  usb_dev_info info, info2;
+  int n = lookup_usb_device(vendor_id, product_id, version, info, info2);
 
-  if (!entry) {
+  if (n <= 0) {
     set_err(EINVAL, "Unknown USB bridge %s",
             format_usb_id(vendor_id, product_id, version).c_str());
     return 0;
   }
-  if (entry->type == d_unsup) {
+
+  if (n > 1) {
+    set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'",
+            format_usb_id(vendor_id, product_id, version).c_str(),
+            (!info.usb_type.empty()  ? info.usb_type.c_str()  : "[unsupported]"),
+            (!info2.usb_type.empty() ? info2.usb_type.c_str() : "[unsupported]"));
+    return 0;
+  }
+
+  if (info.usb_type.empty()) {
     set_err(ENOSYS, "Unsupported USB bridge %s",
             format_usb_id(vendor_id, product_id, version).c_str());
     return 0;
   }
-  return entry->type;
+
+  // TODO: change return type to std::string
+  static std::string type;
+  type = info.usb_type;
+  return type.c_str();
 }