mirror of
https://git.proxmox.com/git/mirror_smartmontools-debian
synced 2025-05-09 15:04:26 +00:00
1477 lines
48 KiB
C++
1477 lines
48 KiB
C++
/*
|
|
* scsiata.cpp
|
|
*
|
|
* Home page of code is: http://smartmontools.sourceforge.net
|
|
*
|
|
* Copyright (C) 2006-9 Douglas Gilbert <dougg@torque.net>
|
|
* Copyright (C) 2009 Christian Franke <smartmontools-support@lists.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* (for example COPYING); if not, write to the Free
|
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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
|
|
* code is sat-r08.pdf which is not too far away from becoming a
|
|
* standard. The SAT commands of interest to smartmontools are the
|
|
* 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
|
|
* THROUGH commands for special usages. Note that the SAT layer may
|
|
* be inside a generic OS layer (e.g. libata in linux), in a host
|
|
* adapter (HA or HBA) firmware, or somewhere on the interconnect
|
|
* between the host computer and the SATA devices (e.g. a RAID made
|
|
* of SATA disks and the RAID talks "SCSI" to the host computer).
|
|
* Note that in the latter case, this code does not solve the
|
|
* addressing issue (i.e. which SATA disk to address behind the logical
|
|
* SCSI (RAID) interface).
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
#include "config.h"
|
|
#include "int64.h"
|
|
#include "extern.h"
|
|
#include "scsicmds.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_cpp_cvsid = "$Id: scsiata.cpp 2923 2009-09-24 20:10:38Z chrfranke $";
|
|
|
|
/* 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
|
|
// cdb[3]: features (15:8)
|
|
// cdb[4]: features (7:0)
|
|
// cdb[5]: sector_count (15:8)
|
|
// cdb[6]: sector_count (7:0)
|
|
// cdb[7]: lba_low (15:8)
|
|
// cdb[8]: lba_low (7:0)
|
|
// cdb[9]: lba_mid (15:8)
|
|
// cdb[10]: lba_mid (7:0)
|
|
// cdb[11]: lba_high (15:8)
|
|
// cdb[12]: lba_high (7:0)
|
|
// cdb[13]: device
|
|
// cdb[14]: (ata) command
|
|
// cdb[15]: control (SCSI, leave as zero)
|
|
//
|
|
// 24 bit lba (from MSB): cdb[12] cdb[10] cdb[8]
|
|
// 48 bit lba (from MSB): cdb[11] cdb[9] cdb[7] cdb[12] cdb[10] cdb[8]
|
|
//
|
|
//
|
|
// cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1)
|
|
// cdb[1]: multiple_count, protocol + extend
|
|
// cdb[2]: offline, ck_cond, t_dir, byte_block + t_length
|
|
// cdb[3]: features (7:0)
|
|
// cdb[4]: sector_count (7:0)
|
|
// cdb[5]: lba_low (7:0)
|
|
// cdb[6]: lba_mid (7:0)
|
|
// cdb[7]: lba_high (7:0)
|
|
// cdb[8]: device
|
|
// cdb[9]: (ata) command
|
|
// cdb[10]: reserved
|
|
// cdb[11]: control (SCSI, leave as zero)
|
|
//
|
|
//
|
|
// ATA Return Descriptor (component of descriptor sense data)
|
|
// des[0]: descriptor code (0x9)
|
|
// des[1]: additional descriptor length (0xc)
|
|
// des[2]: extend (bit 0)
|
|
// des[3]: error
|
|
// des[4]: sector_count (15:8)
|
|
// des[5]: sector_count (7:0)
|
|
// des[6]: lba_low (15:8)
|
|
// des[7]: lba_low (7:0)
|
|
// des[8]: lba_mid (15:8)
|
|
// des[9]: lba_mid (7:0)
|
|
// des[10]: lba_high (15:8)
|
|
// des[11]: lba_high (7:0)
|
|
// des[12]: device
|
|
// des[13]: status
|
|
|
|
|
|
|
|
// PURPOSE
|
|
// 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 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.
|
|
// select: additional input data if needed (which log, which type of
|
|
// self-test).
|
|
// data: location to write output data, if needed (512 bytes).
|
|
// Note: not all commands use all arguments.
|
|
// RETURN VALUES
|
|
// -1 if the command failed
|
|
// 0 if the command succeeded,
|
|
// STATUS_CHECK routine:
|
|
// -1 if the command failed
|
|
// 0 if the command succeeded and disk SMART status is "OK"
|
|
// 1 if the command succeeded and disk SMART status is "FAILING"
|
|
|
|
bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
|
|
{
|
|
if (!ata_cmd_is_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;
|
|
unsigned char cdb[SAT_ATA_PASSTHROUGH_16LEN];
|
|
unsigned char sense[32];
|
|
const unsigned char * ardp;
|
|
int status, 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 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 passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE;
|
|
|
|
memset(cdb, 0, sizeof(cdb));
|
|
memset(sense, 0, sizeof(sense));
|
|
|
|
// 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 ata_cmd_in::data_in:
|
|
protocol = 4; // PIO data-in
|
|
t_length = 2; // sector_count holds count
|
|
break;
|
|
case ata_cmd_in::data_out:
|
|
protocol = 5; // PIO data-out
|
|
t_length = 2; // sector_count holds count
|
|
t_dir = 0; // to device
|
|
break;
|
|
default:
|
|
return set_err(EINVAL, "sat_device::ata_pass_through: invalid direction=%d",
|
|
(int)in.direction);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
|
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 = 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 = in.size;
|
|
io_hdr.dxferp = (unsigned char *)in.buffer;
|
|
}
|
|
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("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;
|
|
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;
|
|
}
|
|
scsi_do_sense_disect(&io_hdr, &sinfo);
|
|
status = scsiSimpleSenseFilter(&sinfo);
|
|
if (0 != status) {
|
|
if (con->reportscsiioctl > 0) {
|
|
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) && (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 */
|
|
if (have_sense) {
|
|
if (ardp) {
|
|
if (con->reportscsiioctl > 1) {
|
|
pout("Values from ATA Return Descriptor are:\n");
|
|
dStrHex((const char *)ardp, ard_len, 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];
|
|
}
|
|
}
|
|
}
|
|
if (ardp == NULL)
|
|
ck_cond = 0; /* not the type of sense data expected */
|
|
}
|
|
if (0 == ck_cond) {
|
|
if (have_sense) {
|
|
if ((ssh.response_code >= 0x72) &&
|
|
((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) {
|
|
pout("Values from ATA Return Descriptor are:\n");
|
|
dStrHex((const char *)ardp, ard_len, 1);
|
|
}
|
|
return set_err(EIO, "SAT command failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/* Attempt an IDENTIFY DEVICE ATA command via SATL when 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)
|
|
{
|
|
/* Note: malloc() ensures the read buffer lands on a single
|
|
page. This avoids some bugs seen on LSI controlers under
|
|
FreeBSD */
|
|
char *data = (char *)malloc(512);
|
|
ata_cmd_in in;
|
|
in.in_regs.command = (packet_interface ? ATA_IDENTIFY_PACKET_DEVICE : ATA_IDENTIFY_DEVICE);
|
|
in.set_data_in(data, 1);
|
|
bool ret = dev->ata_pass_through(in);
|
|
free(data);
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/* Next two functions are borrowed from sg_lib.c in the sg3_utils
|
|
package. Same copyrght owner, same license as this file. */
|
|
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));
|
|
if ((NULL == sensep) || (0 == sb_len) || (0x70 != (0x70 & sensep[0])))
|
|
return 0;
|
|
if (sshp) {
|
|
sshp->response_code = (0x7f & sensep[0]);
|
|
if (sshp->response_code >= 0x72) { /* descriptor format */
|
|
if (sb_len > 1)
|
|
sshp->sense_key = (0xf & sensep[1]);
|
|
if (sb_len > 2)
|
|
sshp->asc = sensep[2];
|
|
if (sb_len > 3)
|
|
sshp->ascq = sensep[3];
|
|
if (sb_len > 7)
|
|
sshp->additional_length = sensep[7];
|
|
} else { /* fixed format */
|
|
if (sb_len > 2)
|
|
sshp->sense_key = (0xf & sensep[2]);
|
|
if (sb_len > 7) {
|
|
sb_len = (sb_len < (sensep[7] + 8)) ? sb_len :
|
|
(sensep[7] + 8);
|
|
if (sb_len > 12)
|
|
sshp->asc = sensep[12];
|
|
if (sb_len > 13)
|
|
sshp->ascq = sensep[13];
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
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,
|
|
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[] = {
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|