]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - scsiata.cpp
Imported Upstream version 5.38+svn2879
[mirror_smartmontools-debian.git] / scsiata.cpp
index 084dcc91a485949fc9fb9f0f87efbe1141d57f07..4ae87022cd6f92c1df1c5ba0613da018a0719d66 100644 (file)
@@ -3,7 +3,8 @@
  *
  * Home page of code is: http://smartmontools.sourceforge.net
  *
- * Copyright (C) 2006 Douglas Gilbert <dougg@torque.net>
+ * Copyright (C) 2006-9 Douglas Gilbert <dougg@torque.net>
+ * Copyright (C) 2009   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
  * ATA PASS THROUGH SCSI (16) and ATA PASS THROUGH SCSI (12) defined in
  * section 12 of that document.
  *
+ * sat-r09.pdf is the most recent, easily accessible draft prior to the
+ * original SAT standard (ANSI INCITS 431-2007). By mid-2009 the second
+ * version of the SAT standard (SAT-2) is nearing standardization. In
+ * their wisdom an incompatible change has been introduced in draft
+ * sat2r08a.pdf in the area of the ATA RETURN DESCRIPTOR. A new "fixed
+ * format" ATA RETURN buffer has been defined (sat2r08b.pdf section
+ * 12.2.7) for the case when DSENSE=0 in the Control mode page.
+ * Unfortunately this is the normal case. If the change stands our
+ * code will need to be extended for this case.
+ *
  * With more transports "hiding" SATA disks (and other S-ATAPI devices)
  * behind a SCSI command set, accessing special features like SMART
  * information becomes a challenge. The SAT standard offers ATA PASS
 
 #include <stdio.h>
 #include <string.h>
+#include <ctype.h>
 
 #include "config.h"
 #include "int64.h"
 #include "extern.h"
 #include "scsicmds.h"
-#include "scsiata.h"
+#include "atacmds.h" // ataReadHDIdentity()
 #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_c_cvsid="$Id: scsiata.cpp,v 1.8 2007/12/03 02:14:20 dpgilbert Exp $"
-CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSICMDS_H_CVSID SCSIATA_H_CVSID UTILITY_H_CVSID;
+const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 2876 2009-08-20 22:53:18Z dlukes $";
 
 /* for passing global control variables */
 extern smartmonctrl *con;
 
+/* This is a slightly stretched SCSI sense "descriptor" format header.
+   The addition is to allow the 0x70 and 0x71 response codes. The idea
+   is to place the salient data of both "fixed" and "descriptor" sense
+   format into one structure to ease application processing.
+   The original sense buffer should be kept around for those cases
+   in which more information is required (e.g. the LBA of a MEDIUM ERROR). */
+/// Abridged SCSI sense data
+struct sg_scsi_sense_hdr {
+    unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */
+    unsigned char sense_key;
+    unsigned char asc;
+    unsigned char ascq;
+    unsigned char byte4;
+    unsigned char byte5;
+    unsigned char byte6;
+    unsigned char additional_length;
+};
+
+/* Maps the salient data from a sense buffer which is in either fixed or
+   descriptor format into a structure mimicking a descriptor format
+   header (i.e. the first 8 bytes of sense descriptor format).
+   If zero response code returns 0. Otherwise returns 1 and if 'sshp' is
+   non-NULL then zero all fields and then set the appropriate fields in
+   that structure. sshp::additional_length is always 0 for response
+   codes 0x70 and 0x71 (fixed format). */
+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
+
 #define DEF_SAT_ATA_PASSTHRU_SIZE 16
 #define ATA_RETURN_DESCRIPTOR 9
 
 
+namespace sat { // no need to publish anything, name provided for Doxygen
+
+/// SAT support.
+/// Implements ATA by tunnelling through SCSI.
+
+class sat_device
+: public tunnelled_device<
+    /*implements*/ ata_device
+    /*by tunnelling through a*/, scsi_device
+  >
+{
+public:
+  sat_device(smart_interface * intf, scsi_device * scsidev,
+    const char * req_type, int passthrulen = 0);
+
+  virtual ~sat_device() throw();
+
+  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+
+private:
+  int m_passthrulen;
+};
+
+
+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),
+  tunnelled_device<ata_device, scsi_device>(scsidev),
+  m_passthrulen(passthrulen)
+{
+  set_info().info_name = strprintf("%s [SAT]", scsidev->get_info_name());
+}
+
+sat_device::~sat_device() throw()
+{
+}
+
+
 // cdb[0]: ATA PASS THROUGH (16) SCSI command opcode byte (0x85)
 // cdb[1]: multiple_count, protocol + extend
 // cdb[2]: offline, ck_cond, t_dir, byte_block + t_length
@@ -112,7 +201,7 @@ extern smartmonctrl *con;
 //   This interface routine takes ATA SMART commands and packages
 //   them in the SAT-defined ATA PASS THROUGH SCSI commands. There are
 //   two available SCSI commands: a 12 byte and 16 byte variant; the
-//   one used is chosen via con->satpassthrulen .
+//   one used is chosen via this->m_passthrulen .
 // DETAILED DESCRIPTION OF ARGUMENTS
 //   device: is the file descriptor provided by (a SCSI dvice type) open()
 //   command: defines the different ATA operations.
@@ -128,9 +217,15 @@ extern smartmonctrl *con;
 //   0 if the command succeeded and disk SMART status is "OK"
 //   1 if the command succeeded and disk SMART status is "FAILING"
 
-int sat_command_interface(int device, smart_command_set command, int select,
-                          char *data)
+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
+  )
+    return false;
+
     struct scsi_cmnd_io io_hdr;
     struct scsi_sense_disect sinfo;
     struct sg_scsi_sense_hdr ssh;
@@ -138,136 +233,87 @@ int sat_command_interface(int device, smart_command_set command, int select,
     unsigned char sense[32];
     const unsigned char * ardp;
     int status, ard_len, have_sense;
-    int copydata = 0;
-    int outlen = 0;
     int extend = 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 */
-    int feature = 0;
-    int ata_command = 0;
-    int sector_count = 0;
-    int lba_low = 0;
-    int lba_mid = 0;
-    int lba_high = 0;
     int passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE;
 
     memset(cdb, 0, sizeof(cdb));
     memset(sense, 0, sizeof(sense));
 
-    ata_command = ATA_SMART_CMD;
-    switch (command) {
-    case CHECK_POWER_MODE:
-        ata_command = ATA_CHECK_POWER_MODE;
-        ck_cond = 1;
-        copydata = 1;
+    // Set data direction
+    // TODO: This works only for commands where sector_count holds count!
+    switch (in.direction) {
+      case ata_cmd_in::no_data:
         break;
-    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;
-    case READ_THRESHOLDS:       /* obsolete */
-        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;
+      case ata_cmd_in::data_in:
+        protocol = 4;  // PIO data-in
+        t_length = 2;  // sector_count holds count
         break;
-    case READ_LOG:
-        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;
+      case ata_cmd_in::data_out:
+        protocol = 5;  // PIO data-out
+        t_length = 2;  // sector_count holds count
+        t_dir = 0;     // to device
         break;
-    case WRITE_LOG:
-        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;
-        break;
-    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;
-    case ENABLE:
-        feature = ATA_SMART_ENABLE;
-        lba_low = 1;
-        break;
-    case DISABLE:
-        feature = ATA_SMART_DISABLE;
-        lba_low = 1;
-        break;
-    case STATUS:
-        // this command only says if SMART is working.  It could be
-        // replaced with STATUS_CHECK below.
-        feature = ATA_SMART_STATUS;
-        ck_cond = 1;
-        break;
-    case AUTO_OFFLINE:
-        feature = ATA_SMART_AUTO_OFFLINE;
-        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
-        break;
-    case AUTOSAVE:
-        feature = ATA_SMART_AUTOSAVE;
-        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
-        break;
-    case IMMEDIATE_OFFLINE:
-        feature = ATA_SMART_IMMEDIATE_OFFLINE;
-        lba_low = select;
-        break;
-    case STATUS_CHECK:
-        // This command uses HDIO_DRIVE_TASK and has different syntax than
-        // the other commands.
-        feature = ATA_SMART_STATUS;      /* SMART RETURN STATUS */
-        ck_cond = 1;
-        break;
-    default:
-        pout("Unrecognized command %d in sat_command_interface()\n"
-             "Please contact " PACKAGE_BUGREPORT "\n", command);
-        errno=ENOSYS;
-        return -1;
+      default:
+        return set_err(EINVAL, "sat_device::ata_pass_through: invalid direction=%d",
+            (int)in.direction);
     }
-    if (ATA_SMART_CMD == ata_command) {
-        lba_mid = 0x4f;
-        lba_high = 0xc2;
+
+    // Check condition if any output register needed
+    if (in.out_needed.is_set())
+        ck_cond = 1;
+
+    if ((SAT_ATA_PASSTHROUGH_12LEN == m_passthrulen) ||
+        (SAT_ATA_PASSTHROUGH_16LEN == m_passthrulen))
+        passthru_size = m_passthrulen;
+
+    // Set extend bit on 48-bit ATA command
+    if (in.in_regs.is_48bit_cmd()) {
+      if (passthru_size != SAT_ATA_PASSTHROUGH_16LEN)
+        return set_err(ENOSYS, "48-bit ATA commands require SAT ATA PASS-THROUGH (16)");
+      extend = 1;
     }
 
-    if ((SAT_ATA_PASSTHROUGH_12LEN == con->satpassthrulen) ||
-        (SAT_ATA_PASSTHROUGH_16LEN == con->satpassthrulen))
-        passthru_size = con->satpassthrulen;
     cdb[0] = (SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ?
              SAT_ATA_PASSTHROUGH_12 : SAT_ATA_PASSTHROUGH_16;
 
     cdb[1] = (protocol << 1) | extend;
     cdb[2] = (ck_cond << 5) | (t_dir << 3) |
              (byte_block << 2) | t_length;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 3 : 4] = feature;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 4 : 6] = sector_count;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 5 : 8] = lba_low;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 6 : 10] = lba_mid;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 7 : 12] = lba_high;
-    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 9 : 14] = ata_command;
+
+    if (passthru_size == SAT_ATA_PASSTHROUGH_12LEN) {
+        // ATA PASS-THROUGH (12)
+        const ata_in_regs & lo = in.in_regs;
+        cdb[3] = lo.features;
+        cdb[4] = lo.sector_count;
+        cdb[5] = lo.lba_low;
+        cdb[6] = lo.lba_mid;
+        cdb[7] = lo.lba_high;
+        cdb[8] = lo.device;
+        cdb[9] = lo.command;
+    }
+    else {
+        // ATA PASS-THROUGH (16)
+        const ata_in_regs & lo = in.in_regs;
+        const ata_in_regs & hi = in.in_regs.prev;
+        // Note: all 'in.in_regs.prev.*' are always zero for 28-bit commands
+        cdb[ 3] = hi.features;
+        cdb[ 4] = lo.features;
+        cdb[ 5] = hi.sector_count;
+        cdb[ 6] = lo.sector_count;
+        cdb[ 7] = hi.lba_low;
+        cdb[ 8] = lo.lba_low;
+        cdb[ 9] = hi.lba_mid;
+        cdb[10] = lo.lba_mid;
+        cdb[11] = hi.lba_high;
+        cdb[12] = lo.lba_high;
+        cdb[13] = lo.device;
+        cdb[14] = lo.command;
+    }
 
     memset(&io_hdr, 0, sizeof(io_hdr));
     if (0 == t_length) {
@@ -275,13 +321,13 @@ int sat_command_interface(int device, smart_command_set command, int select,
         io_hdr.dxfer_len = 0;
     } else if (t_dir) {         /* from device */
         io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
-        io_hdr.dxfer_len = copydata;
-        io_hdr.dxferp = (unsigned char *)data;
-        memset(data, 0, copydata); /* prefill with zeroes */
+        io_hdr.dxfer_len = in.size;
+        io_hdr.dxferp = (unsigned char *)in.buffer;
+        memset(in.buffer, 0, in.size); // prefill with zeroes
     } else {                    /* to device */
         io_hdr.dxfer_dir = DXFER_TO_DEVICE;
-        io_hdr.dxfer_len = outlen;
-        io_hdr.dxferp = (unsigned char *)data;
+        io_hdr.dxfer_len = in.size;
+        io_hdr.dxferp = (unsigned char *)in.buffer;
     }
     io_hdr.cmnd = cdb;
     io_hdr.cmnd_len = passthru_size;
@@ -289,12 +335,12 @@ int sat_command_interface(int device, smart_command_set command, int select,
     io_hdr.max_sense_len = sizeof(sense);
     io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
 
-    status = do_scsi_cmnd_io(device, &io_hdr, con->reportscsiioctl);
-    if (0 != status) {
+    scsi_device * scsidev = get_tunnel_dev();
+    if (!scsidev->scsi_pass_through(&io_hdr)) {
         if (con->reportscsiioctl > 0)
-            pout("sat_command_interface: do_scsi_cmnd_io() failed, "
-                 "status=%d\n", status);
-        return -1;
+            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());
     }
     ardp = NULL;
     ard_len = 0;
@@ -316,16 +362,16 @@ int sat_command_interface(int device, smart_command_set command, int select,
         status = scsiSimpleSenseFilter(&sinfo);
         if (0 != status) {
             if (con->reportscsiioctl > 0) {
-                pout("sat_command_interface: scsi error: %s\n",
+                pout("sat_device::ata_pass_through: scsi error: %s\n",
                      scsiErrString(status));
                 if (ardp && (con->reportscsiioctl > 1)) {
                     pout("Values from ATA Return Descriptor are:\n");
                     dStrHex((const char *)ardp, ard_len, 1);
                 }
             }
-            if (t_dir && (t_length > 0) && (copydata > 0))
-                memset(data, 0, copydata);
-            return -1;
+            if (t_dir && (t_length > 0) && (in.direction == ata_cmd_in::data_in))
+                memset(in.buffer, 0, in.size);
+            return set_err(EIO, "scsi error %s", scsiErrString(status));
         }
     }
     if (ck_cond) {     /* expecting SAT specific sense data */
@@ -335,20 +381,21 @@ int sat_command_interface(int device, smart_command_set command, int select,
                     pout("Values from ATA Return Descriptor are:\n");
                     dStrHex((const char *)ardp, ard_len, 1);
                 }
-                if (ATA_CHECK_POWER_MODE == ata_command)
-                    data[0] = ardp[5];      /* sector count (0:7) */
-                else if (STATUS_CHECK == command) {
-                    if ((ardp[9] == 0x4f) && (ardp[11] == 0xc2))
-                        return 0;    /* GOOD smart status */
-                    if ((ardp[9] == 0xf4) && (ardp[11] == 0x2c))
-                        return 1;    // smart predicting failure, "bad" status
-                    // We haven't gotten output that makes sense so
-                    // print out some debugging info
-                    syserror("Error SMART Status command failed");
-                    pout("Please get assistance from " PACKAGE_HOMEPAGE "\n");
-                    pout("Values from ATA Return Descriptor are:\n");
-                    dStrHex((const char *)ardp, ard_len, 1);
-                    return -1;
+                // Set output registers
+                ata_out_regs & lo = out.out_regs;
+                lo.error        = ardp[ 3];
+                lo.sector_count = ardp[ 5];
+                lo.lba_low      = ardp[ 7];
+                lo.lba_mid      = ardp[ 9];
+                lo.lba_high     = ardp[11];
+                lo.device       = ardp[12];
+                lo.status       = ardp[13];
+                if (in.in_regs.is_48bit_cmd()) {
+                    ata_out_regs & hi = out.out_regs.prev;
+                    hi.sector_count = ardp[ 4];
+                    hi.lba_low      = ardp[ 6];
+                    hi.lba_mid      = ardp[ 8];
+                    hi.lba_high     = ardp[10];
                 }
             }
         }
@@ -367,33 +414,37 @@ int sat_command_interface(int device, smart_command_set command, int select,
                         pout("Values from ATA Return Descriptor are:\n");
                         dStrHex((const char *)ardp, ard_len, 1);
                     }
-                    return -1;
+                    return set_err(EIO, "SAT command failed");
                 }
             }
         }
     }
-    return 0;
+    return true;
 }
 
+} // namespace
+
+/////////////////////////////////////////////////////////////////////////////
+
 /* Attempt an IDENTIFY DEVICE ATA command via SATL when packet_interface
-   is 0 otherwise attempt IDENTIFY PACKET DEVICE. If successful
-   return 1, else 0 */
-int has_sat_pass_through(int device, int packet_interface)
+   is false otherwise attempt IDENTIFY PACKET DEVICE. If successful
+   return true, else false */
+
+static bool has_sat_pass_through(ata_device * dev, bool packet_interface = false)
 {
+    ata_cmd_in in;
+    in.in_regs.command = (packet_interface ? ATA_IDENTIFY_PACKET_DEVICE : ATA_IDENTIFY_DEVICE);
     char data[512];
-    smart_command_set command;
-
-    command = packet_interface ? PIDENTIFY : IDENTIFY;
-    if (0 == sat_command_interface(device, command, 0, data))
-        return 1;
-    else
-        return 0;
+    in.set_data_in(data, 1);
+    return dev->ata_pass_through(in);
 }
 
+/////////////////////////////////////////////////////////////////////////////
+
 /* Next two functions are borrowed from sg_lib.c in the sg3_utils
    package. Same copyrght owner, same license as this file. */
-int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
-                            struct sg_scsi_sense_hdr * sshp)
+static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
+                                   struct sg_scsi_sense_hdr * sshp)
 {
     if (sshp)
         memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
@@ -427,8 +478,8 @@ int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
 }
 
 
-const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
-                                              int sense_len, int desc_type)
+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;
@@ -451,3 +502,948 @@ const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
     }
     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,
+                                        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 (con->reportscsiioctl > 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 (con->reportscsiioctl > 0)
+      pout("%sscsi error: %s\n", msg, scsiErrString(err));
+    return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err));
+  }
+
+  return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+namespace sat {
+
+/// Cypress USB Brigde support.
+
+class usbcypress_device
+: public tunnelled_device<
+    /*implements*/ ata_device_with_command_set
+    /*by tunnelling through a*/, scsi_device
+  >
+{
+public:
+  usbcypress_device(smart_interface * intf, scsi_device * scsidev,
+    const char * req_type, unsigned char signature);
+
+  virtual ~usbcypress_device() throw();
+
+protected:
+  virtual int ata_command_interface(smart_command_set command, int select, char * data);
+
+  unsigned char m_signature;
+};
+
+
+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),
+  tunnelled_device<ata_device_with_command_set, scsi_device>(scsidev),
+  m_signature(signature)
+{
+  set_info().info_name = strprintf("%s [USB Cypress]", scsidev->get_info_name());
+}
+
+usbcypress_device::~usbcypress_device() throw()
+{
+}
+
+
+/* see cy7c68300c_8.pdf for more information */
+#define USBCYPRESS_PASSTHROUGH_LEN 16
+int usbcypress_device::ata_command_interface(smart_command_set command, int select, char *data)
+{
+    struct scsi_cmnd_io io_hdr;
+    unsigned char cdb[USBCYPRESS_PASSTHROUGH_LEN];
+    unsigned char sense[32];
+    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 */
+    int feature = 0;
+    int ata_command = 0;
+    int sector_count = 0;
+    int lba_low = 0;
+    int lba_mid = 0;
+    int lba_high = 0;
+    int passthru_size = USBCYPRESS_PASSTHROUGH_LEN;
+
+    memset(cdb, 0, sizeof(cdb));
+    memset(sense, 0, sizeof(sense));
+
+    ata_command = ATA_SMART_CMD;
+    switch (command) {
+    case CHECK_POWER_MODE:
+        ata_command = ATA_CHECK_POWER_MODE;
+        ck_cond = 1;
+        copydata = 1;
+        break;
+    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;
+    case READ_THRESHOLDS:       /* obsolete */
+        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;
+    case READ_LOG:
+        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;
+    case WRITE_LOG:
+        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;
+        break;
+    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;
+    case ENABLE:
+        feature = ATA_SMART_ENABLE;
+        lba_low = 1;
+        break;
+    case DISABLE:
+        feature = ATA_SMART_DISABLE;
+        lba_low = 1;
+        break;
+    case STATUS:
+        // this command only says if SMART is working.  It could be
+        // replaced with STATUS_CHECK below.
+        feature = ATA_SMART_STATUS;
+        ck_cond = 1;
+        break;
+    case AUTO_OFFLINE:
+        feature = ATA_SMART_AUTO_OFFLINE;
+        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
+        break;
+    case AUTOSAVE:
+        feature = ATA_SMART_AUTOSAVE;
+        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
+        break;
+    case IMMEDIATE_OFFLINE:
+        feature = ATA_SMART_IMMEDIATE_OFFLINE;
+        lba_low = select;
+        break;
+    case STATUS_CHECK:
+        // This command uses HDIO_DRIVE_TASK and has different syntax than
+        // the other commands.
+        feature = ATA_SMART_STATUS;      /* SMART RETURN STATUS */
+        ck_cond = 1;
+        break;
+    default:
+        pout("Unrecognized command %d in usbcypress_device::ata_command_interface()\n"
+             "Please contact " PACKAGE_BUGREPORT "\n", command);
+        errno=ENOSYS;
+        return -1;
+    }
+    if (ATA_SMART_CMD == ata_command) {
+        lba_mid = 0x4f;
+        lba_high = 0xc2;
+    }
+
+    cdb[0] = m_signature; // bVSCBSignature : vendor-specific command
+    cdb[1] = 0x24; // bVSCBSubCommand : 0x24 for ATACB
+    cdb[2] = 0x0;
+    if (ata_command == ATA_IDENTIFY_DEVICE || ata_command == ATA_IDENTIFY_PACKET_DEVICE)
+        cdb[2] |= (1<<7); //set  IdentifyPacketDevice for these cmds
+    cdb[3] = 0xff - (1<<0) - (1<<6); //features, sector count, lba low, lba med
+                                     // lba high, command are valid
+    cdb[4] = byte_block; //TransferBlockCount : 512
+
+
+    cdb[6] = feature;
+    cdb[7] = sector_count;
+    cdb[8] = lba_low;
+    cdb[9] = lba_mid;
+    cdb[10] = lba_high;
+    cdb[12] = ata_command;
+
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    if (0 == t_length) {
+        io_hdr.dxfer_dir = DXFER_NONE;
+        io_hdr.dxfer_len = 0;
+    } else if (t_dir) {         /* from device */
+        io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+        io_hdr.dxfer_len = copydata;
+        io_hdr.dxferp = (unsigned char *)data;
+        memset(data, 0, copydata); /* prefill with zeroes */
+    } else {                    /* to device */
+        io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+        io_hdr.dxfer_len = outlen;
+        io_hdr.dxferp = (unsigned char *)data;
+    }
+    io_hdr.cmnd = cdb;
+    io_hdr.cmnd_len = passthru_size;
+    io_hdr.sensep = sense;
+    io_hdr.max_sense_len = sizeof(sense);
+    io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+    scsi_device * scsidev = get_tunnel_dev();
+    if (!scsidev->scsi_pass_through(&io_hdr)) {
+        if (con->reportscsiioctl > 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());
+        return -1;
+    }
+
+    // if there is a sense the command failed or the
+    // device doesn't support usbcypress
+    if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && 
+            sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) {
+        return -1;
+    }
+    if (ck_cond) {
+        unsigned char ardp[8];
+        int ard_len = 8;
+        /* XXX this is racy if there other scsi command between
+         * the first usbcypress command and this one
+         */
+        //pout("If you got strange result, please retry without traffic on the disc\n");
+        /* we use the same command as before, but we set
+         * * the read taskfile bit, for not executing usbcypress command,
+         * * but reading register selected in srb->cmnd[4]
+         */
+        cdb[2] = (1<<0); /* ask read taskfile */
+        memset(sense, 0, sizeof(sense));
+
+        /* transfert 8 bytes */
+        memset(&io_hdr, 0, sizeof(io_hdr));
+        io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+        io_hdr.dxfer_len = ard_len;
+        io_hdr.dxferp = (unsigned char *)ardp;
+        memset(ardp, 0, ard_len); /* prefill with zeroes */
+
+        io_hdr.cmnd = cdb;
+        io_hdr.cmnd_len = passthru_size;
+        io_hdr.sensep = sense;
+        io_hdr.max_sense_len = sizeof(sense);
+        io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+
+        if (!scsidev->scsi_pass_through(&io_hdr)) {
+            if (con->reportscsiioctl > 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());
+            return -1;
+        }
+        // if there is a sense the command failed or the
+        // device doesn't support usbcypress
+        if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && 
+                sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) {
+            return -1;
+        }
+
+
+        if (con->reportscsiioctl > 1) {
+            pout("Values from ATA Return Descriptor are:\n");
+            dStrHex((const char *)ardp, ard_len, 1);
+        }
+
+        if (ATA_CHECK_POWER_MODE == ata_command)
+            data[0] = ardp[2];      /* sector count (0:7) */
+        else if (STATUS_CHECK == command) {
+            if ((ardp[4] == 0x4f) && (ardp[5] == 0xc2))
+                return 0;    /* GOOD smart status */
+            if ((ardp[4] == 0xf4) && (ardp[5] == 0x2c))
+                return 1;    // smart predicting failure, "bad" status
+            // We haven't gotten output that makes sense so
+            // print out some debugging info
+            syserror("Error SMART Status command failed");
+            pout("This may be due to a race in usbcypress\n");
+            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);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+#if 0 // Not used, see autodetect_sat_device() below.
+static int isprint_string(const char *s)
+{
+    while (*s) {
+        if (isprint(*s) == 0)
+            return 0;
+        s++;
+    }
+    return 1;
+}
+
+/* Attempt an IDENTIFY DEVICE ATA or IDENTIFY PACKET DEVICE command
+   If successful return 1, else 0 */
+// TODO: Combine with has_sat_pass_through above
+static int has_usbcypress_pass_through(ata_device * atadev, const char *manufacturer, const char *product)
+{
+    struct ata_identify_device drive;
+    char model[40], serial[20], firm[8];
+
+    /* issue the command and do a checksum if possible */
+    if (ataReadHDIdentity(atadev, &drive) < 0)
+        return 0;
+
+    /* check if model string match, revision doesn't work for me */
+    format_ata_string(model, drive.model, 40);
+    if (*model == 0 || isprint_string(model) == 0)
+        return 0;
+
+    if (manufacturer && strncmp(manufacturer, model, 8))
+        pout("manufacturer doesn't match in pass_through test\n");
+    if (product &&
+            strlen(model) > 8 && strncmp(product, model+8, strlen(model)-8))
+        pout("product doesn't match in pass_through test\n");
+
+    /* check serial */
+    format_ata_string(serial, drive.serial_no, 20);
+    if (isprint_string(serial) == 0)
+        return 0;
+    format_ata_string(firm, drive.fw_rev, 8);
+    if (isprint_string(firm) == 0)
+        return 0;
+    return 1;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+
+/// JMicron USB Bridge support.
+
+class usbjmicron_device
+: public tunnelled_device<
+    /*implements*/ ata_device,
+    /*by tunnelling through a*/ scsi_device
+  >
+{
+public:
+  usbjmicron_device(smart_interface * intf, scsi_device * scsidev,
+                    const char * req_type, bool ata_48bit_support, int port);
+
+  virtual ~usbjmicron_device() throw();
+
+  virtual bool open();
+
+  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+
+private:
+  bool get_registers(unsigned short addr, unsigned char * buf, unsigned short size);
+
+  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)
+: 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)
+{
+  set_info().info_name = strprintf("%s [USB JMicron]", scsidev->get_info_name());
+}
+
+usbjmicron_device::~usbjmicron_device() throw()
+{
+}
+
+
+bool usbjmicron_device::open()
+{
+  // Open USB first
+  if (!tunnelled_device<ata_device, scsi_device>::open())
+    return false;
+
+  // Detect port if not specified
+  if (m_port < 0) {
+    unsigned char regbuf[1] = {0};
+    if (!get_registers(0x720f, regbuf, sizeof(regbuf))) {
+      close();
+      return false;
+    }
+
+    switch (regbuf[0] & 0x44) {
+      case 0x04:
+        m_port = 0; break;
+      case 0x40:
+        m_port = 1; break;
+      case 0x44:
+        close();
+        return set_err(EINVAL, "Two devices connected, try '-d usbjmicron,[01]'");
+      default:
+        close();
+        return set_err(ENODEV, "No device connected");
+    }
+  }
+
+  return true;
+}
+
+
+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
+  )
+    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");
+
+  scsi_cmnd_io io_hdr;
+  memset(&io_hdr, 0, sizeof(io_hdr));
+
+  bool rwbit = true;
+  unsigned char smart_status = 0;
+
+  if (is_smart_status && in.out_needed.is_set()) {
+    io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+    io_hdr.dxfer_len = 1;
+    io_hdr.dxferp = &smart_status;
+  }
+  else 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;
+      rwbit = false;
+      break;
+    default:
+      return set_err(EINVAL);
+  }
+
+  // Build pass through command
+  unsigned char cdb[12];
+  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     );
+  cdb[ 5] = in.in_regs.features;
+  cdb[ 6] = in.in_regs.sector_count;
+  cdb[ 7] = in.in_regs.lba_low;
+  cdb[ 8] = in.in_regs.lba_mid;
+  cdb[ 9] = in.in_regs.lba_high;
+  cdb[10] = in.in_regs.device | (m_port == 0 ? 0xa0 : 0xb0);
+  cdb[11] = in.in_regs.command;
+
+  io_hdr.cmnd = cdb;
+  io_hdr.cmnd_len = sizeof(cdb);
+
+  scsi_device * scsidev = get_tunnel_dev();
+  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+         "usbjmicron_device::ata_pass_through: "))
+    return set_err(scsidev->get_err());
+
+  if (in.out_needed.is_set()) {
+    if (is_smart_status) {
+      switch (smart_status) {
+        case 0x01: case 0xc2:
+          out.out_regs.lba_high = 0xc2;
+          out.out_regs.lba_mid = 0x4f;
+          break;
+        case 0x00: case 0x2c:
+          out.out_regs.lba_high = 0x2c;
+          out.out_regs.lba_mid = 0xf4;
+          break;
+      }
+    }
+
+#if 0 // Not needed for SMART STATUS, see also notes below
+    else {
+      // Read ATA output registers
+      // NOTE: The register addresses are not valid for some older chip revisions
+      // NOTE: There is a small race condition here!
+      unsigned char regbuf[16] = {0, };
+      if (!get_registers((m_port == 0 ? 0x8000 : 0x9000), regbuf, sizeof(regbuf)))
+        return false;
+
+      out.out_regs.sector_count = regbuf[ 0];
+      out.out_regs.lba_mid      = regbuf[ 4];
+      out.out_regs.lba_low      = regbuf[ 6];
+      out.out_regs.device       = regbuf[ 9];
+      out.out_regs.lba_high     = regbuf[10];
+      out.out_regs.error        = regbuf[13];
+      out.out_regs.status       = regbuf[14];
+    }
+#endif
+  }
+
+  return true;
+}
+
+bool usbjmicron_device::get_registers(unsigned short addr,
+                                      unsigned char * buf, unsigned short size)
+{
+  unsigned char cdb[12];
+  cdb[ 0] = 0xdf;
+  cdb[ 1] = 0x10;
+  cdb[ 2] = 0x00;
+  cdb[ 3] = (unsigned char)(size >> 8);
+  cdb[ 4] = (unsigned char)(size     );
+  cdb[ 5] = 0x00;
+  cdb[ 6] = (unsigned char)(addr >> 8);
+  cdb[ 7] = (unsigned char)(addr     );
+  cdb[ 8] = 0x00;
+  cdb[ 9] = 0x00;
+  cdb[10] = 0x00;
+  cdb[11] = 0xfd;
+
+  scsi_cmnd_io io_hdr;
+  memset(&io_hdr, 0, sizeof(io_hdr));
+  io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+  io_hdr.dxfer_len = size;
+  io_hdr.dxferp = buf;
+  io_hdr.cmnd = cdb;
+  io_hdr.cmnd_len = sizeof(cdb);
+
+  scsi_device * scsidev = get_tunnel_dev();
+  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+         "usbjmicron_device::get_registers: "))
+    return set_err(scsidev->get_err());
+
+  return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+/// SunplusIT USB Bridge support.
+
+class usbsunplus_device
+: public tunnelled_device<
+    /*implements*/ ata_device,
+    /*by tunnelling through a*/ scsi_device
+  >
+{
+public:
+  usbsunplus_device(smart_interface * intf, scsi_device * scsidev,
+                    const char * req_type);
+
+  virtual ~usbsunplus_device() throw();
+
+  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+};
+
+
+usbsunplus_device::usbsunplus_device(smart_interface * intf, scsi_device * scsidev,
+                                     const char * req_type)
+: smart_device(intf, scsidev->get_dev_name(), "usbsunplus", req_type),
+  tunnelled_device<ata_device, scsi_device>(scsidev)
+{
+  set_info().info_name = strprintf("%s [USB Sunplus]", scsidev->get_info_name());
+}
+
+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
+  )
+    return false;
+
+  scsi_cmnd_io io_hdr;
+  unsigned char cdb[12];
+
+  if (in.in_regs.is_48bit_cmd()) {
+    // Set "previous" registers
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    io_hdr.dxfer_dir = DXFER_NONE;
+
+    cdb[ 0] = 0xf8;
+    cdb[ 1] = 0x00;
+    cdb[ 2] = 0x23; // Subcommand: Pass through presetting
+    cdb[ 3] = 0x00;
+    cdb[ 4] = 0x00;
+    cdb[ 5] = in.in_regs.prev.features;
+    cdb[ 6] = in.in_regs.prev.sector_count;
+    cdb[ 7] = in.in_regs.prev.lba_low;
+    cdb[ 8] = in.in_regs.prev.lba_mid;
+    cdb[ 9] = in.in_regs.prev.lba_high;
+    cdb[10] = 0x00;
+    cdb[11] = 0x00;
+
+    io_hdr.cmnd = cdb;
+    io_hdr.cmnd_len = sizeof(cdb);
+
+    scsi_device * scsidev = get_tunnel_dev();
+    if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+           "usbsunplus_device::scsi_pass_through (presetting): "))
+      return set_err(scsidev->get_err());
+  }
+
+  // Run Pass through command
+  memset(&io_hdr, 0, sizeof(io_hdr));
+  unsigned char protocol;
+  switch (in.direction) {
+    case ata_cmd_in::no_data:
+      io_hdr.dxfer_dir = DXFER_NONE;
+      protocol = 0x00;
+      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);
+      protocol = 0x10;
+      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;
+      protocol = 0x11;
+      break;
+    default:
+      return set_err(EINVAL);
+  }
+
+  cdb[ 0] = 0xf8;
+  cdb[ 1] = 0x00;
+  cdb[ 2] = 0x22; // Subcommand: Pass through
+  cdb[ 3] = protocol;
+  cdb[ 4] = (unsigned char)(io_hdr.dxfer_len >> 9);
+  cdb[ 5] = in.in_regs.features;
+  cdb[ 6] = in.in_regs.sector_count;
+  cdb[ 7] = in.in_regs.lba_low;
+  cdb[ 8] = in.in_regs.lba_mid;
+  cdb[ 9] = in.in_regs.lba_high;
+  cdb[10] = in.in_regs.device | 0xa0;
+  cdb[11] = in.in_regs.command;
+
+  io_hdr.cmnd = cdb;
+  io_hdr.cmnd_len = sizeof(cdb);
+
+  scsi_device * scsidev = get_tunnel_dev();
+  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+         "usbsunplus_device::scsi_pass_through: "))
+    // Returns sense key 0x03 (medium error) on ATA command error
+    return set_err(scsidev->get_err());
+
+  if (in.out_needed.is_set()) {
+    // Read ATA output registers
+    unsigned char regbuf[8] = {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;
+
+    cdb[ 0] = 0xf8;
+    cdb[ 1] = 0x00;
+    cdb[ 2] = 0x21; // Subcommand: Get status
+    memset(cdb+3, 0, sizeof(cdb)-3);
+    io_hdr.cmnd = cdb;
+    io_hdr.cmnd_len = sizeof(cdb);
+
+    if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+           "usbsunplus_device::scsi_pass_through (get registers): "))
+      return set_err(scsidev->get_err());
+
+    out.out_regs.error        = regbuf[1];
+    out.out_regs.sector_count = regbuf[2];
+    out.out_regs.lba_low      = regbuf[3];
+    out.out_regs.lba_mid      = regbuf[4];
+    out.out_regs.lba_high     = regbuf[5];
+    out.out_regs.device       = regbuf[6];
+    out.out_regs.status       = regbuf[7];
+  }
+
+  return true;
+}
+
+
+} // namespace
+
+using namespace sat;
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Return ATA->SCSI filter for SAT or USB.
+
+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");
+      return 0;
+    }
+    return new sat_device(this, scsidev, type, ptlen);
+  }
+
+  else if (!strncmp(type, "usbcypress", 10)) {
+    unsigned signature = 0x24; int n1 = -1, n2 = -1;
+    if (!(((sscanf(type, "usbcypress%n,0x%x%n", &n1, &signature, &n2) == 1 && n2 == (int)strlen(type)) || n1 == (int)strlen(type))
+          && signature <= 0xff)) {
+      set_err(EINVAL, "Option '-d usbcypress,<n>' requires <n> to be "
+                      "an hexadecimal number between 0x0 and 0xff");
+      return 0;
+    }
+    return new usbcypress_device(this, scsidev, type, signature);
+  }
+
+  else if (!strncmp(type, "usbjmicron", 10)) {
+    const char * t = type + 10;
+    bool ata_48bit_support = false;
+    if (!strncmp(t, ",x", 2)) {
+      t += 2;
+      ata_48bit_support = true;
+    }
+    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");
+      return 0;
+    }
+    return new usbjmicron_device(this, scsidev, type, ata_48bit_support, port);
+  }
+
+  else if (!strcmp(type, "usbsunplus")) {
+    return new usbsunplus_device(this, scsidev, type);
+  }
+
+  else {
+    set_err(EINVAL, "Unknown USB device type '%s'", type);
+    return 0;
+  }
+}
+
+// Try to detect a SAT device behind a SCSI interface.
+
+ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev,
+  const unsigned char * inqdata, unsigned inqsize)
+{
+  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;
+  }
+
+  return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// 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)
+{
+  if (version >= 0)
+    return strprintf("[0x%04x:0x%04x (0x%03x)]", vendor_id, product_id, version);
+  else
+    return strprintf("[0x%04x:0x%04x]", vendor_id, product_id);
+}
+
+// Get type name for USB device with known VENDOR:PRODUCT ID.
+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;
+  }
+
+  if (!entry) {
+    set_err(EINVAL, "Unknown USB bridge %s",
+            format_usb_id(vendor_id, product_id, version).c_str());
+    return 0;
+  }
+  if (entry->type == d_unsup) {
+    set_err(ENOSYS, "Unsupported USB bridge %s",
+            format_usb_id(vendor_id, product_id, version).c_str());
+    return 0;
+  }
+  return entry->type;
+}