From b69d2483bfee18b1277e1dcff27f047c99da3329 Mon Sep 17 00:00:00 2001
From: chrfranke <chrfranke@4ea69e1a-61f1-4043-bf83-b5c94c648137>
Date: Mon, 23 Mar 2009 21:59:31 +0000
Subject: [PATCH] Add option '-d usbsunplus' for drives behind SunplusIT USB
 bridges.

git-svn-id: https://smartmontools.svn.sourceforge.net/svnroot/smartmontools/trunk@2769 4ea69e1a-61f1-4043-bf83-b5c94c648137
---
 sm5/CHANGELOG     |   7 +-
 sm5/scsiata.cpp   | 232 ++++++++++++++++++++++++++++++++++++++--------
 sm5/smartctl.8.in |  11 ++-
 3 files changed, 205 insertions(+), 45 deletions(-)

diff --git a/sm5/CHANGELOG b/sm5/CHANGELOG
index 4cd89cef7..25e42f964 100644
--- a/sm5/CHANGELOG
+++ b/sm5/CHANGELOG
@@ -1,6 +1,6 @@
 CHANGELOG for smartmontools
 
-$Id: CHANGELOG,v 1.790 2009/03/22 17:17:39 chrfranke Exp $
+$Id: CHANGELOG,v 1.791 2009/03/23 21:59:31 chrfranke Exp $
 
 The most recent version of this file is:
 http://smartmontools.cvs.sourceforge.net/smartmontools/sm5/CHANGELOG?view=markup
@@ -41,6 +41,11 @@ NOTES FOR FUTURE RELEASES: see TODO file.
 
 <DEVELOPERS: ADDITIONS TO THE CHANGE LOG GO JUST BELOW HERE, PLEASE>
 
+  [CF] Add experimental option '-d usbsunplus' for drives behind
+       SunplusIT USB bridges. Tested on WinXP with SPIF215(?) in
+       TrekStor DataStation maxi m.u.. Many thanks to SunplusIT
+       tech support for providing the required information.
+
   [CF] Windows: Provide a non-console version of smartctl.exe
        as smartctl-nc.exe. This prevents that a new console is
        opened when smartctl is run from a GUI program with
diff --git a/sm5/scsiata.cpp b/sm5/scsiata.cpp
index a5f735478..394c6b7d1 100644
--- a/sm5/scsiata.cpp
+++ b/sm5/scsiata.cpp
@@ -51,7 +51,7 @@
 #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.28 2009/03/17 19:53:14 chrfranke Exp $"
+const char *scsiata_c_cvsid="$Id: scsiata.cpp,v 1.29 2009/03/23 21:59:31 chrfranke Exp $"
 CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSICMDS_H_CVSID SCSIATA_H_CVSID UTILITY_H_CVSID;
 
 /* for passing global control variables */
@@ -458,6 +458,39 @@ const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
 }
 
 
+// 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 {
@@ -925,28 +958,10 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou
   io_hdr.cmnd = cdb;
   io_hdr.cmnd_len = sizeof(cdb);
 
-  unsigned char sense[32] = {0, };
-  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("usbjmicron_device::ata_pass_through: scsi_pass_through() failed, "
-           "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
+  if (!scsi_pass_through_and_check(scsidev, &io_hdr,
+         "usbjmicron_device::ata_pass_through: "))
     return set_err(scsidev->get_err());
-  }
-
-  scsi_sense_disect sinfo;
-  scsi_do_sense_disect(&io_hdr, &sinfo);
-  int err = scsiSimpleSenseFilter(&sinfo);
-  if (err) {
-    if (con->reportscsiioctl > 0)
-      pout("usbjmicron_device::ata_pass_through: scsi error: %s\n",
-           scsiErrString(err));
-    return set_err(EIO, "scsi error %s", scsiErrString(err));
-  }
 
   if (in.out_needed.is_set()) {
     if (is_smart_status) {
@@ -1010,27 +1025,159 @@ bool usbjmicron_device::get_registers(unsigned short addr,
   io_hdr.cmnd = cdb;
   io_hdr.cmnd_len = sizeof(cdb);
 
-  unsigned char sense[32] = {0, };
-  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("usbjmicron_device::get_registers: scsi_pass_through failed, "
-           "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
+  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());
   }
 
-  scsi_sense_disect sinfo;
-  scsi_do_sense_disect(&io_hdr, &sinfo);
-  int err = scsiSimpleSenseFilter(&sinfo);
-  if (err) {
-    if (con->reportscsiioctl > 0)
-      pout("usbjmicron_device::get_registers: scsi error: %s\n",
-           scsiErrString(err));
-    return set_err(EIO, "scsi error %s", scsiErrString(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;
@@ -1080,6 +1227,10 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc
     return new usbjmicron_device(this, scsidev, type, 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;
@@ -1143,8 +1294,9 @@ struct usb_id_entry {
 };
 
 const char d_sat[]     = "sat";
-const char d_jmicron[] = "usbjmicron";
 const char d_cypress[] = "usbcypress";
+const char d_jmicron[] = "usbjmicron";
+const char d_sunplus[] = "usbsunplus";
 const char d_unsup[]   = "unsupported";
 
 // Map USB IDs -> '-d type' string
@@ -1155,7 +1307,7 @@ const usb_id_entry usb_ids[] = {
   { 0x059f, 0x0651,     -1, d_unsup   }, // LaCie hard disk (FA Porsche design)
   { 0x059f, 0x1018,     -1, d_sat     }, // LaCie hard disk (Neil Poulton design)
   { 0x0bc2, 0x3001,     -1, d_sat     }, // Seagate FreeAgent Desk
-  { 0x0c0b, 0xb159, 0x0103, d_unsup   }, // Dura Micro ?
+  { 0x0c0b, 0xb159, 0x0103, d_sunplus }, // Dura Micro (Sunplus USB-bridge)
   { 0x0d49, 0x7310, 0x0125, d_sat     }, // Maxtor OneTouch 4
 //{ 0x0d49,     -1,     -1, d_sat     }, // Maxtor Basics Desktop
   { 0x1058, 0x1001, 0x0104, d_sat     }, // WD Elements Desktop
diff --git a/sm5/smartctl.8.in b/sm5/smartctl.8.in
index 917ea2a06..000e5bbc6 100644
--- a/sm5/smartctl.8.in
+++ b/sm5/smartctl.8.in
@@ -1,7 +1,7 @@
 .ig
  Copyright (C) 2002-8 Bruce Allen <smartmontools-support@lists.sourceforge.net>
 
- $Id: smartctl.8.in,v 1.121 2009/03/14 16:14:10 chrfranke Exp $
+ $Id: smartctl.8.in,v 1.122 2009/03/23 21:59:31 chrfranke Exp $
  
  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
@@ -214,8 +214,8 @@ use the exit status of \fBsmartctl\fP (see RETURN VALUES below).
 .B \-d TYPE, \-\-device=TYPE
 Specifies the type of the device.  The valid arguments to this option
 are \fIata\fP, \fIscsi\fP, \fIsat\fP, \fImarvell\fP, \fI3ware,N\fP,
-\fIareca,N\fP, \fIusbcypress\fP, \fIusbjmicron\fP, \fIcciss,N\fP,
-\fIhpt,L/M\fP (or \fIhpt,L/M/N\fP), and \fItest\fP.
+\fIareca,N\fP, \fIusbcypress\fP, \fIusbjmicron\fP, \fIusbsunplus\fP,
+\fIcciss,N\fP, \fIhpt,L/M\fP (or \fIhpt,L/M/N\fP), and \fItest\fP.
 
 If this option is not used then \fBsmartctl\fP will attempt to guess
 the device type from the device name or from controller type info
@@ -244,6 +244,9 @@ specified by \'\-d usbjmicron,PORT\' where PORT is 0 (master) or 1 (slave).
 If no PORT is specified, it is auto-detected. If both ports are connected,
 0 takes precedence.
 
+[NEW EXPERIMENTAL SMARTCTL FEATURE] The \'usbsunplus\' device type is for
+SATA disks that are behind a SunplusIT USB to SATA bridge.
+
 Under Linux, to look at SATA disks behind Marvell SATA controllers
 (using Marvell's \'linuxIAL\' driver rather than libata driver) use \'\-d marvell\'. Such
 controllers show up as Marvell Technology Group Ltd. SATA I or II controllers
@@ -1666,7 +1669,7 @@ these documents may be found in the References section of the
 
 .SH
 CVS ID OF THIS PAGE:
-$Id: smartctl.8.in,v 1.121 2009/03/14 16:14:10 chrfranke Exp $
+$Id: smartctl.8.in,v 1.122 2009/03/23 21:59:31 chrfranke Exp $
 .\" Local Variables:	         
 .\" mode: nroff         
 .\" End:
-- 
GitLab