]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - scsiata.cpp
import smartmontools 7.0
[mirror_smartmontools-debian.git] / scsiata.cpp
index f7b790df1537e6c1a98d177d73a2c716d189a437..94b82a6353fa435e1fdcfd0b95f76c0f9cff726e 100644 (file)
@@ -1,20 +1,12 @@
 /*
  * scsiata.cpp
  *
- * Home page of code is: http://smartmontools.sourceforge.net
+ * Home page of code is: http://www.smartmontools.org
  *
- * 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
- * the Free Software Foundation; either version 2, or (at your option)
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Copyright (C) 2006-15 Douglas Gilbert <dgilbert@interlog.com>
+ * Copyright (C) 2009-18 Christian Franke
  *
+ * SPDX-License-Identifier: GPL-2.0-or-later
  * 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
  * code is sat-r08.pdf which is not too far away from becoming a
@@ -43,7 +35,7 @@
  * Note that in the latter case, this code does not solve the
  * addressing issue (i.e. which SATA disk to address behind the logical
  * SCSI (RAID) interface).
- * 
+ *
  */
 
 #include <stdio.h>
@@ -53,7 +45,7 @@
 #include <errno.h>
 
 #include "config.h"
-#include "int64.h"
+
 #include "scsicmds.h"
 #include "atacmds.h" // ataReadHDIdentity()
 #include "knowndrives.h" // lookup_usb_device()
@@ -61,8 +53,9 @@
 #include "dev_interface.h"
 #include "dev_ata_cmd_set.h" // ata_device_with_command_set
 #include "dev_tunnelled.h" // tunnelled_device<>
+#include "sg_unaligned.h"
 
-const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 3741 2013-01-02 17:06:54Z chrfranke $";
+const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 4848 2018-12-05 18:30:46Z chrfranke $";
 
 /* This is a slightly stretched SCSI sense "descriptor" format header.
    The addition is to allow the 0x70 and 0x71 response codes. The idea
@@ -112,8 +105,14 @@ class sat_device
   virtual public /*implements*/ scsi_device
 {
 public:
+  enum sat_scsi_mode {
+    sat_always,
+    sat_auto,
+    scsi_always
+  };
+
   sat_device(smart_interface * intf, scsi_device * scsidev,
-    const char * req_type, int passthrulen = 0, bool enable_auto = false);
+    const char * req_type, sat_scsi_mode mode = sat_always, int passthrulen = 0);
 
   virtual ~sat_device() throw();
 
@@ -125,27 +124,28 @@ public:
 
 private:
   int m_passthrulen;
-  bool m_enable_auto;
+  sat_scsi_mode m_mode;
 };
 
 
 sat_device::sat_device(smart_interface * intf, scsi_device * scsidev,
-  const char * req_type, int passthrulen /* = 0 */, bool enable_auto /* = false */)
+  const char * req_type, sat_scsi_mode mode /* = sat_always */,
+  int passthrulen /* = 0 */)
 : smart_device(intf, scsidev->get_dev_name(),
-    (enable_auto ? "sat,auto" : "sat"), req_type),
+    (mode == sat_always ? "sat" : mode == sat_auto ? "sat,auto" : "scsi"), req_type),
   tunnelled_device<ata_device, scsi_device>(scsidev),
   m_passthrulen(passthrulen),
-  m_enable_auto(enable_auto)
+  m_mode(mode)
 {
-  if (enable_auto)
+  if (mode != sat_always)
     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/" : ""));
+  set_info().info_name = strprintf("%s [%s]", scsidev->get_info_name(),
+    (mode == sat_always ? "SAT" : mode == sat_auto ? "SCSI/SAT" : "SCSI"));
 }
 
 sat_device::~sat_device() throw()
@@ -203,6 +203,25 @@ sat_device::~sat_device() throw()
 // des[11]: lba_high (7:0)
 // des[12]: device
 // des[13]: status
+//
+//
+// ATA registers returned via fixed format sense (allowed >= SAT-2)
+// fxs[0]: info_valid (bit 7); response_code (6:0)
+// fxs[1]: (obsolete)
+// fxs[2]: sense_key (3:0) --> recovered error (formerly 'no sense')
+// fxs[3]: information (31:24) --> ATA Error register
+// fxs[4]: information (23:16) --> ATA Status register
+// fxs[5]: information (15:8) --> ATA Device register
+// fxs[6]: information (7:0) --> ATA Count (7:0)
+// fxs[7]: additional sense length [should be >= 10]
+// fxs[8]: command specific info (31:24) --> Extend (7), count_upper_nonzero
+//         (6), lba_upper_nonzero(5), log_index (3:0)
+// fxs[9]: command specific info (23:16) --> ATA LBA (7:0)
+// fxs[10]: command specific info (15:8) --> ATA LBA (15:8)
+// fxs[11]: command specific info (7:0) --> ATA LBA (23:16)
+// fxs[12]: additional sense code (asc) --> 0x0
+// fxs[13]: additional sense code qualifier (ascq) --> 0x1d
+//          asc,ascq = 0x0,0x1d --> 'ATA pass through information available'
 
 
 
@@ -221,21 +240,21 @@ sat_device::~sat_device() throw()
 // RETURN VALUES
 //  -1 if the command failed
 //   0 if the command succeeded,
-//   STATUS_CHECK routine: 
+//   STATUS_CHECK routine:
 //  -1 if the command failed
 //   0 if the command succeeded and disk SMART status is "OK"
 //   1 if the command succeeded and disk SMART status is "FAILING"
 
 bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
 {
-  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;
+    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;
 
     struct scsi_cmnd_io io_hdr;
     struct scsi_sense_disect sinfo;
@@ -243,7 +262,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
     unsigned char cdb[SAT_ATA_PASSTHROUGH_16LEN];
     unsigned char sense[32];
     const unsigned char * ardp;
-    int status, ard_len, have_sense;
+    int ard_len, have_sense;
     int extend = 0;
     int ck_cond = 0;    /* set to 1 to read register(s) back */
     int protocol = 3;   /* non-data */
@@ -251,6 +270,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
     int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
     int t_length = 0;   /* 0 -> no data transferred */
     int passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE;
+    bool sense_descriptor = true;
 
     memset(cdb, 0, sizeof(cdb));
     memset(sense, 0, sizeof(sense));
@@ -358,26 +378,39 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
     have_sense = sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len,
                                          &ssh);
     if (have_sense) {
-        /* look for SAT ATA Return Descriptor */
-        ardp = sg_scsi_sense_desc_find(io_hdr.sensep,
-                                       io_hdr.resp_sense_len,
-                                       ATA_RETURN_DESCRIPTOR);
-        if (ardp) {
-            ard_len = ardp[1] + 2;
-            if (ard_len < 12)
-                ard_len = 12;
-            else if (ard_len > 14)
-                ard_len = 14;
+        sense_descriptor = ssh.response_code >= 0x72;
+        if (sense_descriptor) {
+            /* look for SAT ATA Return Descriptor */
+            ardp = sg_scsi_sense_desc_find(io_hdr.sensep,
+                                           io_hdr.resp_sense_len,
+                                           ATA_RETURN_DESCRIPTOR);
+            if (ardp) {
+                ard_len = ardp[1] + 2;
+                if (ard_len < 12)
+                    ard_len = 12;
+                else if (ard_len > 14)
+                    ard_len = 14;
+            }
         }
         scsi_do_sense_disect(&io_hdr, &sinfo);
-        status = scsiSimpleSenseFilter(&sinfo);
-        if (0 != status) {
+        int status = scsiSimpleSenseFilter(&sinfo);
+
+        // Workaround for bogus sense_key in sense data with SAT ATA Return Descriptor
+        if (   status && ck_cond && ardp && ard_len > 13
+            && (ardp[13] & 0xc1) == 0x40 /* !BSY && DRDY && !ERR */) {
+            if (scsi_debugmode > 0)
+                pout("ATA status (0x%02x) indicates success, ignoring SCSI sense_key\n",
+                     ardp[13]);
+            status = 0;
+        }
+
+        if (0 != status) {  /* other than no_sense and recovered_error */
             if (scsi_debugmode > 0) {
                 pout("sat_device::ata_pass_through: scsi error: %s\n",
                      scsiErrString(status));
                 if (ardp && (scsi_debugmode > 1)) {
                     pout("Values from ATA Return Descriptor are:\n");
-                    dStrHex((const char *)ardp, ard_len, 1);
+                    dStrHex((const uint8_t *)ardp, ard_len, 1);
                 }
             }
             if (t_dir && (t_length > 0) && (in.direction == ata_cmd_in::data_in))
@@ -390,7 +423,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
             if (ardp) {
                 if (scsi_debugmode > 1) {
                     pout("Values from ATA Return Descriptor are:\n");
-                    dStrHex((const char *)ardp, ard_len, 1);
+                    dStrHex((const uint8_t *)ardp, ard_len, 1);
                 }
                 // Set output registers
                 ata_out_regs & lo = out.out_regs;
@@ -408,26 +441,57 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
                     hi.lba_mid      = ardp[ 8];
                     hi.lba_high     = ardp[10];
                 }
+            } else if ((! sense_descriptor) &&
+                       (0 == ssh.asc) &&
+                       (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq) &&
+                       (0 != io_hdr.sensep[4] /* Some ATA STATUS bit must be set */)) {
+                /* in SAT-2 and later, ATA registers may be passed back via
+                 * fixed format sense data [ref: sat3r07 section 12.2.2.7] */
+                ata_out_regs & lo = out.out_regs;
+                lo.error        = io_hdr.sensep[ 3];
+                lo.status       = io_hdr.sensep[ 4];
+                lo.device       = io_hdr.sensep[ 5];
+                lo.sector_count = io_hdr.sensep[ 6];
+                lo.lba_low      = io_hdr.sensep[ 9];
+                lo.lba_mid      = io_hdr.sensep[10];
+                lo.lba_high     = io_hdr.sensep[11];
+                if (in.in_regs.is_48bit_cmd()) {
+                    if (0 == (0x60 & io_hdr.sensep[8])) {
+                        ata_out_regs & hi = out.out_regs.prev;
+                        hi.sector_count = 0;
+                        hi.lba_low      = 0;
+                        hi.lba_mid      = 0;
+                        hi.lba_high     = 0;
+                    } else {
+                        /* getting the "hi." values when either
+                         * count_upper_nonzero or lba_upper_nonzero are set
+                         * involves fetching the SCSI ATA PASS-THROUGH
+                         * Results log page and decoding the descriptor with
+                         * the matching log_index field. Painful. */
+                    }
+                }
             }
         }
-        if (ardp == NULL)
-            ck_cond = 0;       /* not the type of sense data expected */
-    }
-    if (0 == ck_cond) {
+    } else {    /* ck_cond == 0 */
         if (have_sense) {
-            if ((ssh.response_code >= 0x72) &&
-                ((SCSI_SK_NO_SENSE == ssh.sense_key) ||
+            if (((SCSI_SK_NO_SENSE == ssh.sense_key) ||
                  (SCSI_SK_RECOVERED_ERR == ssh.sense_key)) &&
                 (0 == ssh.asc) &&
                 (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq)) {
-                if (ardp) {
-                    if (scsi_debugmode > 0) {
+                if (scsi_debugmode > 0) {
+                    if (sense_descriptor && ardp) {
                         pout("Values from ATA Return Descriptor are:\n");
-                        dStrHex((const char *)ardp, ard_len, 1);
+                        dStrHex((const uint8_t *)ardp, ard_len, 1);
+                    } else if (! sense_descriptor) {
+                        pout("Values from ATA fixed format sense are:\n");
+                        pout("  Error: 0x%x\n", io_hdr.sensep[3]);
+                        pout("  Status: 0x%x\n", io_hdr.sensep[4]);
+                        pout("  Device: 0x%x\n", io_hdr.sensep[5]);
+                        pout("  Count: 0x%x\n", io_hdr.sensep[6]);
                     }
-                    return set_err(EIO, "SAT command failed");
                 }
             }
+            return set_err(EIO, "SAT command failed");
         }
     }
     return true;
@@ -445,7 +509,7 @@ bool sat_device::scsi_pass_through(scsi_cmnd_io * iop)
 
 smart_device * sat_device::autodetect_open()
 {
-  if (!open() || !m_enable_auto)
+  if (!open() || m_mode != sat_auto)
     return this;
 
   scsi_device * scsidev = get_tunnel_dev();
@@ -483,7 +547,7 @@ smart_device * sat_device::autodetect_open()
 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
+       page.  This avoids some bugs seen on LSI controllers under
        FreeBSD */
     char *data = (char *)malloc(512);
     ata_cmd_in in;
@@ -532,40 +596,6 @@ static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
     return 1;
 }
 
-
-// Call scsi_pass_through and check sense.
-// TODO: Provide as member function of class scsi_device (?)
-static bool scsi_pass_through_and_check(scsi_device * scsidev,  scsi_cmnd_io * iop,
-                                        const char * msg = "")
-{
-  // Provide sense buffer
-  unsigned char sense[32] = {0, };
-  iop->sensep = sense;
-  iop->max_sense_len = sizeof(sense);
-  iop->timeout = SCSI_TIMEOUT_DEFAULT;
-
-  // Run cmd
-  if (!scsidev->scsi_pass_through(iop)) {
-    if (scsi_debugmode > 0)
-      pout("%sscsi_pass_through() failed, errno=%d [%s]\n",
-           msg, scsidev->get_errno(), scsidev->get_errmsg());
-    return false;
-  }
-
-  // Check sense
-  scsi_sense_disect sinfo;
-  scsi_do_sense_disect(iop, &sinfo);
-  int err = scsiSimpleSenseFilter(&sinfo);
-  if (err) {
-    if (scsi_debugmode > 0)
-      pout("%sscsi error: %s\n", msg, scsiErrString(err));
-    return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err));
-  }
-
-  return true;
-}
-
-
 /////////////////////////////////////////////////////////////////////////////
 
 namespace sat {
@@ -593,7 +623,7 @@ protected:
 
 usbcypress_device::usbcypress_device(smart_interface * intf, scsi_device * scsidev,
   const char * req_type, unsigned char signature)
-: smart_device(intf, scsidev->get_dev_name(), "sat", req_type),
+: smart_device(intf, scsidev->get_dev_name(), "usbcypress", req_type),
   tunnelled_device<ata_device_with_command_set, scsi_device>(scsidev),
   m_signature(signature)
 {
@@ -767,7 +797,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
 
     // if there is a sense the command failed or the
     // device doesn't support usbcypress
-    if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && 
+    if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION &&
             sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) {
         return -1;
     }
@@ -785,7 +815,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         cdb[2] = (1<<0); /* ask read taskfile */
         memset(sense, 0, sizeof(sense));
 
-        /* transfert 8 bytes */
+        /* transfer 8 bytes */
         memset(&io_hdr, 0, sizeof(io_hdr));
         io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
         io_hdr.dxfer_len = ard_len;
@@ -808,7 +838,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
         }
         // if there is a sense the command failed or the
         // device doesn't support usbcypress
-        if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && 
+        if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION &&
                 sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) {
             return -1;
         }
@@ -816,7 +846,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
 
         if (scsi_debugmode > 1) {
             pout("Values from ATA Return Descriptor are:\n");
-            dStrHex((const char *)ardp, ard_len, 1);
+            dStrHex((const uint8_t *)ardp, ard_len, 1);
         }
 
         if (ATA_CHECK_POWER_MODE == ata_command)
@@ -833,7 +863,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele
             pout("Retry without other disc access\n");
             pout("Please get assistance from " PACKAGE_HOMEPAGE "\n");
             pout("Values from ATA Return Descriptor are:\n");
-            dStrHex((const char *)ardp, ard_len, 1);
+            dStrHex((const uint8_t *)ardp, ard_len, 1);
             return -1;
         }
     }
@@ -980,7 +1010,7 @@ 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);
@@ -1015,8 +1045,7 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   cdb[ 0] = 0xdf;
   cdb[ 1] = (rwbit ? 0x10 : 0x00);
   cdb[ 2] = 0x00;
-  cdb[ 3] = (unsigned char)(io_hdr.dxfer_len >> 8);
-  cdb[ 4] = (unsigned char)(io_hdr.dxfer_len     );
+  sg_put_unaligned_be16(io_hdr.dxfer_len, cdb + 3);
   cdb[ 5] = in.in_regs.features;
   cdb[ 6] = in.in_regs.sector_count;
   cdb[ 7] = in.in_regs.lba_low;
@@ -1032,21 +1061,28 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   io_hdr.cmnd_len = (!m_prolific ? 12 : 14);
 
   scsi_device * scsidev = get_tunnel_dev();
-  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+  if (!scsidev->scsi_pass_through_and_check(&io_hdr,
          "usbjmicron_device::ata_pass_through: "))
     return set_err(scsidev->get_err());
 
   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);
       }
     }
 
@@ -1080,11 +1116,9 @@ bool usbjmicron_device::get_registers(unsigned short addr,
   cdb[ 0] = 0xdf;
   cdb[ 1] = 0x10;
   cdb[ 2] = 0x00;
-  cdb[ 3] = (unsigned char)(size >> 8);
-  cdb[ 4] = (unsigned char)(size     );
+  sg_put_unaligned_be16(size, cdb + 3);
   cdb[ 5] = 0x00;
-  cdb[ 6] = (unsigned char)(addr >> 8);
-  cdb[ 7] = (unsigned char)(addr     );
+  sg_put_unaligned_be16(addr, cdb + 6);
   cdb[ 8] = 0x00;
   cdb[ 9] = 0x00;
   cdb[10] = 0x00;
@@ -1099,11 +1133,10 @@ bool usbjmicron_device::get_registers(unsigned short addr,
   io_hdr.dxfer_len = size;
   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,
+  if (!scsidev->scsi_pass_through_and_check(&io_hdr,
          "usbjmicron_device::get_registers: "))
     return set_err(scsidev->get_err());
 
@@ -1111,6 +1144,145 @@ bool usbjmicron_device::get_registers(unsigned short addr,
 }
 
 
+/////////////////////////////////////////////////////////////////////////////
+
+/// Prolific USB Bridge support. (PL2773) (Probably works on PL2771 also...)
+
+class usbprolific_device
+: public tunnelled_device<
+    /*implements*/ ata_device,
+    /*by tunnelling through a*/ scsi_device
+  >
+{
+public:
+  usbprolific_device(smart_interface * intf, scsi_device * scsidev,
+                    const char * req_type);
+
+  virtual ~usbprolific_device() throw();
+
+  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+};
+
+
+usbprolific_device::usbprolific_device(smart_interface * intf, scsi_device * scsidev,
+                                     const char * req_type)
+: smart_device(intf, scsidev->get_dev_name(), "usbprolific", req_type),
+  tunnelled_device<ata_device, scsi_device>(scsidev)
+{
+  set_info().info_name = strprintf("%s [USB Prolific]", scsidev->get_info_name());
+}
+
+usbprolific_device::~usbprolific_device() throw()
+{
+}
+
+bool usbprolific_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+  if (!ata_cmd_is_supported(in,
+    ata_device::supports_data_out |
+    ata_device::supports_48bit_hi_null |
+    ata_device::supports_output_regs |
+    ata_device::supports_smart_status,
+    "Prolific" )
+  )
+    return false;
+
+  scsi_cmnd_io io_hdr;
+  memset(&io_hdr, 0, sizeof(io_hdr));
+  unsigned char cmd_rw = 0x10;  // Read
+
+  switch (in.direction) {
+    case ata_cmd_in::no_data:
+      io_hdr.dxfer_dir = DXFER_NONE;
+      break;
+    case ata_cmd_in::data_in:
+      io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+      io_hdr.dxfer_len = in.size;
+      io_hdr.dxferp = (unsigned char *)in.buffer;
+      memset(in.buffer, 0, in.size);
+      break;
+    case ata_cmd_in::data_out:
+      io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+      io_hdr.dxfer_len = in.size;
+      io_hdr.dxferp = (unsigned char *)in.buffer;
+      cmd_rw = 0x0; // Write
+      break;
+    default:
+      return set_err(EINVAL);
+  }
+
+  // Based on reverse engineering of iSmart.exe with API Monitor.
+  // Seen commands:
+  // D0  0 0  0 06 7B 0 0 0 0 0 0                 // Read Firmware info?, reads 16 bytes
+  // F4  0 0  0 06 7B                             // ??
+  // D8 15 0 D8 06 7B 0 0 0 0 1 1 4F C2 A0 B0     // SMART Enable
+  // D8 15 0 D0 06 7B 0 0 2 0 1 1 4F C2 A0 B0     // SMART Read values
+  // D8 15 0 D1 06 7B 0 0 2 0 1 1 4F C2 A0 B0     // SMART Read thresholds
+  // D8 15 0 D4 06 7B 0 0 0 0 0 1 4F C2 A0 B0     // SMART Execute self test
+  // D7  0 0  0 06 7B 0 0 0 0 0 0 0 0 0 0         // Read status registers, Reads 16 bytes of data
+  // Additional DATA OUT support based on document from Prolific
+
+  // Build pass through command
+  unsigned char cdb[16];
+  cdb[ 0] = 0xD8;         // Operation Code (D8 = Prolific ATA pass through)
+  cdb[ 1] = cmd_rw|0x5;   // Read(0x10)/Write(0x0) | NORMAL(0x5)/PREFIX(0x0)(?)
+  cdb[ 2] = 0x0;          // Reserved
+  cdb[ 3] = in.in_regs.features;        // Feature register (SMART command)
+  cdb[ 4] = 0x06;         // Check Word (VendorID magic, Prolific: 0x067B)
+  cdb[ 5] = 0x7B;         // Check Word (VendorID magic, Prolific: 0x067B)
+  sg_put_unaligned_be32(io_hdr.dxfer_len, cdb + 6);
+  cdb[10] = in.in_regs.sector_count;    // Sector Count
+  cdb[11] = in.in_regs.lba_low;         // LBA Low (7:0)
+  cdb[12] = in.in_regs.lba_mid;         // LBA Mid (15:8)
+  cdb[13] = in.in_regs.lba_high;        // LBA High (23:16)
+  cdb[14] = in.in_regs.device | 0xA0;   // Device/Head
+  cdb[15] = in.in_regs.command;         // ATA Command Register (only PIO supported)
+  // Use '-r scsiioctl,1' to print CDB for debug purposes
+
+  io_hdr.cmnd = cdb;
+  io_hdr.cmnd_len = 16;
+
+  scsi_device * scsidev = get_tunnel_dev();
+  if (!scsidev->scsi_pass_through_and_check(&io_hdr,
+         "usbprolific_device::ata_pass_through: "))
+    return set_err(scsidev->get_err());
+
+  if (in.out_needed.is_set()) {
+    // Read ATA output registers
+    unsigned char regbuf[16] = {0, };
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+    io_hdr.dxfer_len = sizeof(regbuf);
+    io_hdr.dxferp = regbuf;
+
+    memset(cdb, 0, sizeof(cdb));
+    cdb[ 0] = 0xD7;  // Prolific read registers
+    cdb[ 4] = 0x06;  // Check Word (VendorID magic, Prolific: 0x067B)
+    cdb[ 5] = 0x7B;  // Check Word (VendorID magic, Prolific: 0x067B)
+    io_hdr.cmnd = cdb;
+    io_hdr.cmnd_len = sizeof(cdb);
+
+    if (!scsidev->scsi_pass_through_and_check(&io_hdr,
+           "usbprolific_device::scsi_pass_through (get registers): "))
+      return set_err(scsidev->get_err());
+
+    // Use '-r scsiioctl,2' to print input registers for debug purposes
+    // Example: 50 00 00 00 00 01 4f 00  c2 00 a0 da 00 b0 00 50
+    out.out_regs.status       = regbuf[0];  // Status
+    out.out_regs.error        = regbuf[1];  // Error
+    out.out_regs.sector_count = regbuf[2];  // Sector Count (7:0)
+    out.out_regs.lba_low      = regbuf[4];  // LBA Low (7:0)
+    out.out_regs.lba_mid      = regbuf[6];  // LBA Mid (7:0)
+    out.out_regs.lba_high     = regbuf[8];  // LBA High (7:0)
+    out.out_regs.device       = regbuf[10]; // Device/Head
+    //                          = regbuf[11]; // ATA Feature (7:0)
+    //                          = regbuf[13]; // ATA Command
+  }
+
+  return true;
+}
+
+
 /////////////////////////////////////////////////////////////////////////////
 
 /// SunplusIT USB Bridge support.
@@ -1178,7 +1350,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
     io_hdr.cmnd_len = sizeof(cdb);
 
     scsi_device * scsidev = get_tunnel_dev();
-    if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+    if (!scsidev->scsi_pass_through_and_check(&io_hdr,
            "usbsunplus_device::scsi_pass_through (presetting): "))
       return set_err(scsidev->get_err());
   }
@@ -1225,7 +1397,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   io_hdr.cmnd_len = sizeof(cdb);
 
   scsi_device * scsidev = get_tunnel_dev();
-  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+  if (!scsidev->scsi_pass_through_and_check(&io_hdr,
          "usbsunplus_device::scsi_pass_through: "))
     // Returns sense key 0x03 (medium error) on ATA command error
     return set_err(scsidev->get_err());
@@ -1245,7 +1417,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
     io_hdr.cmnd = cdb;
     io_hdr.cmnd_len = sizeof(cdb);
 
-    if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+    if (!scsidev->scsi_pass_through_and_check(&io_hdr,
            "usbsunplus_device::scsi_pass_through (get registers): "))
       return set_err(scsidev->get_err());
 
@@ -1273,12 +1445,19 @@ using namespace sat;
 
 ata_device * smart_interface::get_sat_device(const char * type, scsi_device * scsidev)
 {
+  if (!scsidev)
+    throw std::logic_error("smart_interface: get_sat_device() called with scsidev=0");
+
+  // Take temporary ownership of 'scsidev' to delete it on error
+  scsi_device_auto_ptr scsidev_holder(scsidev);
+  ata_device * satdev = 0;
+
   if (!strncmp(type, "sat", 3)) {
     const char * t = type + 3;
-    bool enable_auto = false;
+    sat_device::sat_scsi_mode mode = sat_device::sat_always;
     if (!strncmp(t, ",auto", 5)) {
       t += 5;
-      enable_auto = true;
+      mode = sat_device::sat_auto;
     }
     int ptlen = 0, n = -1;
     if (*t && !(sscanf(t, ",%d%n", &ptlen, &n) == 1 && n == (int)strlen(t)
@@ -1286,7 +1465,11 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
       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, enable_auto);
+    satdev = new sat_device(this, scsidev, type, mode, ptlen);
+  }
+
+  else if (!strcmp(type, "scsi")) {
+    satdev = new sat_device(this, scsidev, type, sat_device::scsi_always);
   }
 
   else if (!strncmp(type, "usbcypress", 10)) {
@@ -1297,7 +1480,7 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
                       "an hexadecimal number between 0x0 and 0xff");
       return 0;
     }
-    return new usbcypress_device(this, scsidev, type, signature);
+    satdev = new usbcypress_device(this, scsidev, type, signature);
   }
 
   else if (!strncmp(type, "usbjmicron", 10)) {
@@ -1318,17 +1501,25 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
       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, prolific, ata_48bit_support, port);
+    satdev = new usbjmicron_device(this, scsidev, type, prolific, ata_48bit_support, port);
+  }
+
+  else if (!strcmp(type, "usbprolific")) {
+    satdev = new usbprolific_device(this, scsidev, type);
   }
 
   else if (!strcmp(type, "usbsunplus")) {
-    return new usbsunplus_device(this, scsidev, type);
+    satdev = new usbsunplus_device(this, scsidev, type);
   }
 
   else {
     set_err(EINVAL, "Unknown USB device type '%s'", type);
     return 0;
   }
+
+  // 'scsidev' is now owned by 'satdev'
+  scsidev_holder.release();
+  return satdev;
 }
 
 // Try to detect a SAT device behind a SCSI interface.
@@ -1340,7 +1531,9 @@ ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev,
     return 0;
 
   // SAT ?
-  if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8)) { // TODO: Linux-specific?
+  if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8)) {
+    // TODO: Linux-specific? No, all SAT standards say the 'T10 Vendor
+    // Identification' field shall be 'ATA     '.
     ata_device_auto_ptr atadev( new sat_device(this, scsidev, "") , scsidev);
     if (has_sat_pass_through(atadev.get()))
       return atadev.release(); // Detected SAT