/*
* scsiata.cpp
*
- * Home page of code is: http://smartmontools.sourceforge.net
+ * Home page of code is: http://www.smartmontools.org
*
- * Copyright (C) 2006-9 Douglas Gilbert <dougg@torque.net>
- * Copyright (C) 2009 Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2006-15 Douglas Gilbert <dgilbert@interlog.com>
+ * Copyright (C) 2009-17 Christian Franke
*
* 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
* any later version.
*
* You should have received a copy of the GNU General Public License
- * (for example COPYING); if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * (for example COPYING); if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* The code in this file is based on the SCSI to ATA Translation (SAT)
* draft found at http://www.t10.org . The original draft used for this
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
+#include <errno.h>
#include "config.h"
#include "int64.h"
-#include "extern.h"
#include "scsicmds.h"
#include "atacmds.h" // ataReadHDIdentity()
+#include "knowndrives.h" // lookup_usb_device()
#include "utility.h"
#include "dev_interface.h"
#include "dev_ata_cmd_set.h" // ata_device_with_command_set
#include "dev_tunnelled.h" // tunnelled_device<>
-const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 2923 2009-09-24 20:10:38Z chrfranke $";
-
-/* for passing global control variables */
-extern smartmonctrl *con;
+const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 4386 2017-01-28 16:35:06Z chrfranke $";
/* This is a slightly stretched SCSI sense "descriptor" format header.
The addition is to allow the 0x70 and 0x71 response codes. The idea
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
: public tunnelled_device<
/*implements*/ ata_device
/*by tunnelling through a*/, scsi_device
- >
+ >,
+ virtual public /*implements*/ scsi_device
{
public:
sat_device(smart_interface * intf, scsi_device * scsidev,
- const char * req_type, int passthrulen = 0);
+ const char * req_type, int passthrulen = 0, bool enable_auto = false);
virtual ~sat_device() throw();
+ virtual smart_device * autodetect_open();
+
virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+ virtual bool scsi_pass_through(scsi_cmnd_io * iop);
+
private:
int m_passthrulen;
+ bool m_enable_auto;
};
sat_device::sat_device(smart_interface * intf, scsi_device * scsidev,
- const char * req_type, int passthrulen /*= 0*/)
-: smart_device(intf, scsidev->get_dev_name(), "sat", req_type),
+ const char * req_type, int passthrulen /* = 0 */, bool enable_auto /* = false */)
+: smart_device(intf, scsidev->get_dev_name(),
+ (enable_auto ? "sat,auto" : "sat"), req_type),
tunnelled_device<ata_device, scsi_device>(scsidev),
- m_passthrulen(passthrulen)
+ m_passthrulen(passthrulen),
+ m_enable_auto(enable_auto)
{
- set_info().info_name = strprintf("%s [SAT]", scsidev->get_info_name());
+ if (enable_auto)
+ hide_ata(); // Start as SCSI, switch to ATA in autodetect_open()
+ else
+ hide_scsi(); // ATA always
+ if (strcmp(scsidev->get_dev_type(), "scsi"))
+ set_info().dev_type += strprintf("+%s", scsidev->get_dev_type());
+
+ set_info().info_name = strprintf("%s [%sSAT]", scsidev->get_info_name(),
+ (enable_auto ? "SCSI/" : ""));
}
sat_device::~sat_device() throw()
// 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'
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;
+ 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;
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 */
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));
scsi_device * scsidev = get_tunnel_dev();
if (!scsidev->scsi_pass_through(&io_hdr)) {
- if (con->reportscsiioctl > 0)
+ if (scsi_debugmode > 0)
pout("sat_device::ata_pass_through: scsi_pass_through() failed, "
"errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
return set_err(scsidev->get_err());
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) {
- if (con->reportscsiioctl > 0) {
+ 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 && (con->reportscsiioctl > 1)) {
+ if (ardp && (scsi_debugmode > 1)) {
pout("Values from ATA Return Descriptor are:\n");
dStrHex((const char *)ardp, ard_len, 1);
}
if (ck_cond) { /* expecting SAT specific sense data */
if (have_sense) {
if (ardp) {
- if (con->reportscsiioctl > 1) {
+ if (scsi_debugmode > 1) {
pout("Values from ATA Return Descriptor are:\n");
dStrHex((const char *)ardp, ard_len, 1);
}
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 (con->reportscsiioctl > 0) {
+ if (scsi_debugmode > 0) {
+ if (sense_descriptor && ardp) {
pout("Values from ATA Return Descriptor are:\n");
dStrHex((const char *)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;
}
+bool sat_device::scsi_pass_through(scsi_cmnd_io * iop)
+{
+ scsi_device * scsidev = get_tunnel_dev();
+ if (!scsidev->scsi_pass_through(iop)) {
+ set_err(scsidev->get_err());
+ return false;
+ }
+ return true;
+}
+
+smart_device * sat_device::autodetect_open()
+{
+ if (!open() || !m_enable_auto)
+ return this;
+
+ scsi_device * scsidev = get_tunnel_dev();
+
+ unsigned char inqdata[36] = {0, };
+ if (scsiStdInquiry(scsidev, inqdata, sizeof(inqdata))) {
+ smart_device::error_info err = scsidev->get_err();
+ close();
+ set_err(err.no, "INQUIRY [SAT]: %s", err.msg.c_str());
+ return this;
+ }
+
+ // Check for SAT "VENDOR"
+ int inqsize = inqdata[4] + 5;
+ bool sat = (inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8));
+
+ // Change interface
+ hide_ata(!sat);
+ hide_scsi(sat);
+
+ set_info().dev_type = (sat ? "sat" : scsidev->get_dev_type());
+ set_info().info_name = strprintf("%s [%s]", scsidev->get_info_name(),
+ (sat ? "SAT" : "SCSI"));
+ return this;
+}
+
} // namespace
/////////////////////////////////////////////////////////////////////////////
}
-static const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
- int sense_len, int desc_type)
-{
- int add_sen_len, add_len, desc_len, k;
- const unsigned char * descp;
-
- if ((sense_len < 8) || (0 == (add_sen_len = sensep[7])))
- return NULL;
- if ((sensep[0] < 0x72) || (sensep[0] > 0x73))
- return NULL;
- add_sen_len = (add_sen_len < (sense_len - 8)) ?
- add_sen_len : (sense_len - 8);
- descp = &sensep[8];
- for (desc_len = 0, k = 0; k < add_sen_len; k += desc_len) {
- descp += desc_len;
- add_len = (k < (add_sen_len - 1)) ? descp[1]: -1;
- desc_len = add_len + 2;
- if (descp[0] == desc_type)
- return descp;
- if (add_len < 0) /* short descriptor ?? */
- break;
- }
- return NULL;
-}
-
-
// Call scsi_pass_through and check sense.
// TODO: Provide as member function of class scsi_device (?)
static bool scsi_pass_through_and_check(scsi_device * scsidev, scsi_cmnd_io * iop,
// Run cmd
if (!scsidev->scsi_pass_through(iop)) {
- if (con->reportscsiioctl > 0)
+ if (scsi_debugmode > 0)
pout("%sscsi_pass_through() failed, errno=%d [%s]\n",
msg, scsidev->get_errno(), scsidev->get_errmsg());
return false;
scsi_do_sense_disect(iop, &sinfo);
int err = scsiSimpleSenseFilter(&sinfo);
if (err) {
- if (con->reportscsiioctl > 0)
+ if (scsi_debugmode > 0)
pout("%sscsi error: %s\n", msg, scsiErrString(err));
return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err));
}
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 */
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;
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;
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;
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;
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;
scsi_device * scsidev = get_tunnel_dev();
if (!scsidev->scsi_pass_through(&io_hdr)) {
- if (con->reportscsiioctl > 0)
+ if (scsi_debugmode > 0)
pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
"errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
set_err(scsidev->get_err());
if (!scsidev->scsi_pass_through(&io_hdr)) {
- if (con->reportscsiioctl > 0)
+ if (scsi_debugmode > 0)
pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
"errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
set_err(scsidev->get_err());
}
- if (con->reportscsiioctl > 1) {
+ if (scsi_debugmode > 1) {
pout("Values from ATA Return Descriptor are:\n");
dStrHex((const char *)ardp, ard_len, 1);
}
{
public:
usbjmicron_device(smart_interface * intf, scsi_device * scsidev,
- const char * req_type, bool ata_48bit_support, int port);
+ const char * req_type, bool prolific,
+ bool ata_48bit_support, int port);
virtual ~usbjmicron_device() throw();
private:
bool get_registers(unsigned short addr, unsigned char * buf, unsigned short size);
+ bool m_prolific;
bool m_ata_48bit_support;
int m_port;
};
usbjmicron_device::usbjmicron_device(smart_interface * intf, scsi_device * scsidev,
- const char * req_type, bool ata_48bit_support, int port)
+ const char * req_type, bool prolific,
+ bool ata_48bit_support, int port)
: smart_device(intf, scsidev->get_dev_name(), "usbjmicron", req_type),
tunnelled_device<ata_device, scsi_device>(scsidev),
- m_ata_48bit_support(ata_48bit_support), m_port(port)
+ m_prolific(prolific), m_ata_48bit_support(ata_48bit_support),
+ m_port(port >= 0 || !prolific ? port : 0)
{
set_info().info_name = strprintf("%s [USB JMicron]", scsidev->get_info_name());
}
bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
{
- if (!ata_cmd_is_ok(in,
- true, // data_out_support
- false, // !multi_sector_support
- m_ata_48bit_support) // limited, see below
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_smart_status |
+ (m_ata_48bit_support ? ata_device::supports_48bit_hi_null : 0),
+ "JMicron")
)
return false;
- bool is_smart_status = ( in.in_regs.command == ATA_SMART_CMD
- && in.in_regs.features == ATA_SMART_STATUS);
-
- // Support output registers for SMART STATUS
- if (in.out_needed.is_set() && !is_smart_status)
- return set_err(ENOSYS, "ATA output registers not supported");
-
- // Support 48-bit commands with zero high bytes
- if (in.in_regs.is_real_48bit_cmd())
- return set_err(ENOSYS, "48-bit ATA commands not fully supported");
-
if (m_port < 0)
return set_err(EIO, "Unknown JMicron port");
memset(&io_hdr, 0, sizeof(io_hdr));
bool rwbit = true;
- unsigned char smart_status = 0;
+ unsigned char smart_status = 0xff;
+
+ bool is_smart_status = ( in.in_regs.command == ATA_SMART_CMD
+ && in.in_regs.features == ATA_SMART_STATUS);
if (is_smart_status && in.out_needed.is_set()) {
io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
}
// Build pass through command
- unsigned char cdb[12];
+ unsigned char cdb[14];
cdb[ 0] = 0xdf;
cdb[ 1] = (rwbit ? 0x10 : 0x00);
cdb[ 2] = 0x00;
cdb[ 9] = in.in_regs.lba_high;
cdb[10] = in.in_regs.device | (m_port == 0 ? 0xa0 : 0xb0);
cdb[11] = in.in_regs.command;
+ // Prolific PL3507
+ cdb[12] = 0x06;
+ cdb[13] = 0x7b;
io_hdr.cmnd = cdb;
- io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.cmnd_len = (!m_prolific ? 12 : 14);
scsi_device * scsidev = get_tunnel_dev();
if (!scsi_pass_through_and_check(scsidev, &io_hdr,
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);
}
}
bool usbjmicron_device::get_registers(unsigned short addr,
unsigned char * buf, unsigned short size)
{
- unsigned char cdb[12];
+ unsigned char cdb[14];
cdb[ 0] = 0xdf;
cdb[ 1] = 0x10;
cdb[ 2] = 0x00;
cdb[ 9] = 0x00;
cdb[10] = 0x00;
cdb[11] = 0xfd;
+ // Prolific PL3507
+ cdb[12] = 0x06;
+ cdb[13] = 0x7b;
scsi_cmnd_io io_hdr;
memset(&io_hdr, 0, sizeof(io_hdr));
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,
}
+/////////////////////////////////////////////////////////////////////////////
+
+/// 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)
+ cdb[ 6] = (unsigned char)(io_hdr.dxfer_len >> 24); // Length MSB
+ cdb[ 7] = (unsigned char)(io_hdr.dxfer_len >> 16); // Length ...
+ cdb[ 8] = (unsigned char)(io_hdr.dxfer_len >> 8); // Length ...
+ cdb[ 9] = (unsigned char)(io_hdr.dxfer_len ); // Length LSB
+ 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 (!scsi_pass_through_and_check(scsidev, &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 (!scsi_pass_through_and_check(scsidev, &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.
bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
{
- if (!ata_cmd_is_ok(in,
- true, // data_out_support
- false, // !multi_sector_support
- true) // ata_48bit_support
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_output_regs |
+ ata_device::supports_48bit,
+ "Sunplus")
)
return false;
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)) {
- int ptlen = 0, n1 = -1, n2 = -1;
- if (!(((sscanf(type, "sat%n,%d%n", &n1, &ptlen, &n2) == 1 && n2 == (int)strlen(type)) || n1 == (int)strlen(type))
- && (ptlen == 0 || ptlen == 12 || ptlen == 16))) {
- set_err(EINVAL, "Option '-d sat,<n>' requires <n> to be 0, 12 or 16");
+ const char * t = type + 3;
+ bool enable_auto = false;
+ if (!strncmp(t, ",auto", 5)) {
+ t += 5;
+ enable_auto = true;
+ }
+ int ptlen = 0, n = -1;
+ if (*t && !(sscanf(t, ",%d%n", &ptlen, &n) == 1 && n == (int)strlen(t)
+ && (ptlen == 0 || ptlen == 12 || ptlen == 16))) {
+ set_err(EINVAL, "Option '-d sat[,auto][,N]' requires N to be 0, 12 or 16");
return 0;
}
- return new sat_device(this, scsidev, type, ptlen);
+ satdev = new sat_device(this, scsidev, type, ptlen, enable_auto);
}
else if (!strncmp(type, "usbcypress", 10)) {
"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)) {
const char * t = type + 10;
+ bool prolific = false;
+ if (!strncmp(t, ",p", 2)) {
+ t += 2;
+ prolific = true;
+ }
bool ata_48bit_support = false;
if (!strncmp(t, ",x", 2)) {
t += 2;
int port = -1, n = -1;
if (*t && !( (sscanf(t, ",%d%n", &port, &n) == 1
&& n == (int)strlen(t) && 0 <= port && port <= 1))) {
- set_err(EINVAL, "Option '-d usbmicron[,x],<n>' requires <n> to be 0 or 1");
+ set_err(EINVAL, "Option '-d usbjmicron[,p][,x],<n>' requires <n> to be 0 or 1");
return 0;
}
- return new usbjmicron_device(this, scsidev, type, ata_48bit_support, port);
+ 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.
if (!scsidev->is_open())
return 0;
- ata_device * atadev = 0;
- try {
- // SAT ?
- if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8)) { // TODO: Linux-specific?
- atadev = new sat_device(this, scsidev, "");
- if (has_sat_pass_through(atadev))
- return atadev; // Detected SAT
- atadev->release(scsidev);
- delete atadev;
- }
-
-/* The new usbcypress_device(this, scsidev, "", 0x24) sends vendor specific comand to non-cypress devices.
- * It's dangerous as other device may interpret such command as own valid vendor specific command.
- * I commented it out untill problem resolved
- */
-#if 0
- // USB ?
- {
- atadev = new usbcypress_device(this, scsidev, "", 0x24);
- if (has_usbcypress_pass_through(atadev,
- (inqdata && inqsize >= 36 ? (const char*)inqdata + 8 : 0),
- (inqdata && inqsize >= 36 ? (const char*)inqdata + 16 : 0) ))
- return atadev; // Detected USB
- atadev->release(scsidev);
- delete atadev;
- }
-#endif
- }
- catch (...) {
- if (atadev) {
- atadev->release(scsidev);
- delete atadev;
- }
- throw;
+ // SAT ?
+ if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8)) { // TODO: Linux-specific?
+ ata_device_auto_ptr atadev( new sat_device(this, scsidev, "") , scsidev);
+ if (has_sat_pass_through(atadev.get()))
+ return atadev.release(); // Detected SAT
}
return 0;
/////////////////////////////////////////////////////////////////////////////
// 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[] = {
- // Cypress
- { 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)
- // Myson Century
- { 0x04cf, 0x8818, 0xb007, d_unsup }, // Myson Century CS8818
- // Sunplus
- { 0x04fc, 0x0c15, 0xf615, d_sunplus }, // SunPlus SPDIF215
- { 0x04fc, 0x0c25, 0x0103, d_sunplus }, // SunPlus SPDIF225 (USB+SATA->SATA)
- // Iomega
- { 0x059b, 0x0275, 0x0001, d_unsup }, // Iomega MDHD500-U
- // LaCie
- { 0x059f, 0x0651, -1, d_unsup }, // LaCie hard disk (FA Porsche design)
- { 0x059f, 0x1018, -1, d_sat }, // LaCie hard disk (Neil Poulton design)
- // In-System Design
- { 0x05ab, 0x0060, 0x1101, d_cypress }, // In-System/Cypress ISD-300A1
- // Prolific
- { 0x067b, 0x3507, 0x0001, d_unsup }, // Prolific PL3507
- // Toshiba
- { 0x0930, 0x0b09, -1, d_sunplus }, // Toshiba PX1396E-3T01 (similar to Dura Micro 501)
- // Seagate
- { 0x0bc2, 0x2000, -1, d_sat }, // Seagate FreeAgent Go
- { 0x0bc2, 0x2100, -1, d_sat }, // Seagate FreeAgent Go
- { 0x0bc2, 0x3001, -1, d_sat }, // Seagate FreeAgent Desk
- // Dura Micro
- { 0x0c0b, 0xb159, 0x0103, d_sunplus }, // Dura Micro 509
- // Maxtor
- { 0x0d49, 0x7310, 0x0125, d_sat }, // Maxtor OneTouch 4
- { 0x0d49, 0x7350, 0x0125, d_sat }, // Maxtor OneTouch 4 Mini
- { 0x0d49, 0x7410, 0x0122, d_sat }, // Maxtor Basics Desktop
- { 0x0d49, 0x7450, 0x0122, d_sat }, // Maxtor Basics Portable
- // Western Digital
- { 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
- // Initio
- { 0x13fd, 0x1240, 0x0104, d_sat }, // Initio ? (USB->SATA)
- { 0x13fd, 0x1340, 0x0208, d_sat }, // Initio ? (USB+SATA->SATA)
- // JMicron
- { 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)
- // Verbatim
- { 0x18a5, 0x0215, 0x0001, d_sat }, // Verbatim FW/USB160 - Oxford OXUF934SSA-LQAG (USB+IEE1394->SATA)
- // SunplusIT
- { 0x1bcf, 0x0c31, -1, d_sunplus }, // SunplusIT
- // OnSpec
- { 0x55aa, 0x2b00, 0x0100, d_unsup } // OnSpec ? (USB->PATA)
-};
-
-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)
{
const char * smart_interface::get_usb_dev_type_by_id(int vendor_id, int product_id,
int version /*= -1*/)
{
- const usb_id_entry * entry = 0;
- bool state = false;
-
- for (unsigned i = 0; i < num_usb_ids; i++) {
- const usb_id_entry & e = usb_ids[i];
- if (!(vendor_id == e.vendor_id && product_id == e.product_id))
- continue;
-
- // If two entries with same vendor:product ID have different
- // types, use version (if provided by OS) to select entry.
- bool s = (version >= 0 && version == e.version);
- if (entry) {
- if (s <= state) {
- if (s == state && e.type != entry->type) {
- set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'",
- format_usb_id(vendor_id, product_id, version).c_str(),
- e.type, entry->type);
- return 0;
- }
- continue;
- }
- }
- state = s;
- entry = &e;
- }
+ usb_dev_info info, info2;
+ int n = lookup_usb_device(vendor_id, product_id, version, info, info2);
- if (!entry) {
+ if (n <= 0) {
set_err(EINVAL, "Unknown USB bridge %s",
format_usb_id(vendor_id, product_id, version).c_str());
return 0;
}
- if (entry->type == d_unsup) {
+
+ if (n > 1) {
+ set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'",
+ format_usb_id(vendor_id, product_id, version).c_str(),
+ (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]"),
+ (!info2.usb_type.empty() ? info2.usb_type.c_str() : "[unsupported]"));
+ return 0;
+ }
+
+ if (info.usb_type.empty()) {
set_err(ENOSYS, "Unsupported USB bridge %s",
format_usb_id(vendor_id, product_id, version).c_str());
return 0;
}
- return entry->type;
+
+ // TODO: change return type to std::string
+ static std::string type;
+ type = info.usb_type;
+ return type.c_str();
}