X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=scsiata.cpp;h=4ae87022cd6f92c1df1c5ba0613da018a0719d66;hb=2127e1931eec4a688d41baf253744fc48ed8c989;hp=084dcc91a485949fc9fb9f0f87efbe1141d57f07;hpb=34ad0c5ffe435e24270dc422f957585c31be5ec5;p=mirror_smartmontools-debian.git diff --git a/scsiata.cpp b/scsiata.cpp index 084dcc9..4ae8702 100644 --- a/scsiata.cpp +++ b/scsiata.cpp @@ -3,7 +3,8 @@ * * Home page of code is: http://smartmontools.sourceforge.net * - * Copyright (C) 2006 Douglas Gilbert + * Copyright (C) 2006-9 Douglas Gilbert + * Copyright (C) 2009 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 @@ -21,6 +22,16 @@ * 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 @@ -37,24 +48,102 @@ #include #include +#include #include "config.h" #include "int64.h" #include "extern.h" #include "scsicmds.h" -#include "scsiata.h" +#include "atacmds.h" // ataReadHDIdentity() #include "utility.h" +#include "dev_interface.h" +#include "dev_ata_cmd_set.h" // ata_device_with_command_set +#include "dev_tunnelled.h" // tunnelled_device<> -const char *scsiata_c_cvsid="$Id: scsiata.cpp,v 1.8 2007/12/03 02:14:20 dpgilbert Exp $" -CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSICMDS_H_CVSID SCSIATA_H_CVSID UTILITY_H_CVSID; +const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 2876 2009-08-20 22:53:18Z dlukes $"; /* for passing global control variables */ extern smartmonctrl *con; +/* This is a slightly stretched SCSI sense "descriptor" format header. + The addition is to allow the 0x70 and 0x71 response codes. The idea + is to place the salient data of both "fixed" and "descriptor" sense + format into one structure to ease application processing. + The original sense buffer should be kept around for those cases + in which more information is required (e.g. the LBA of a MEDIUM ERROR). */ +/// Abridged SCSI sense data +struct sg_scsi_sense_hdr { + unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */ + unsigned char sense_key; + unsigned char asc; + unsigned char ascq; + unsigned char byte4; + unsigned char byte5; + unsigned char byte6; + unsigned char additional_length; +}; + +/* Maps the salient data from a sense buffer which is in either fixed or + descriptor format into a structure mimicking a descriptor format + header (i.e. the first 8 bytes of sense descriptor format). + If zero response code returns 0. Otherwise returns 1 and if 'sshp' is + non-NULL then zero all fields and then set the appropriate fields in + that structure. sshp::additional_length is always 0 for response + codes 0x70 and 0x71 (fixed format). */ +static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, + struct sg_scsi_sense_hdr * sshp); + +/* Attempt to find the first SCSI sense data descriptor that matches the + given 'desc_type'. If found return pointer to start of sense data + descriptor; otherwise (including fixed format sense data) returns NULL. */ +static const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, + int sense_len, int desc_type); + +#define SAT_ATA_PASSTHROUGH_12LEN 12 +#define SAT_ATA_PASSTHROUGH_16LEN 16 + #define DEF_SAT_ATA_PASSTHRU_SIZE 16 #define ATA_RETURN_DESCRIPTOR 9 +namespace sat { // no need to publish anything, name provided for Doxygen + +/// SAT support. +/// Implements ATA by tunnelling through SCSI. + +class sat_device +: public tunnelled_device< + /*implements*/ ata_device + /*by tunnelling through a*/, scsi_device + > +{ +public: + sat_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, int passthrulen = 0); + + virtual ~sat_device() throw(); + + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); + +private: + int m_passthrulen; +}; + + +sat_device::sat_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, int passthrulen /*= 0*/) +: smart_device(intf, scsidev->get_dev_name(), "sat", req_type), + tunnelled_device(scsidev), + m_passthrulen(passthrulen) +{ + set_info().info_name = strprintf("%s [SAT]", scsidev->get_info_name()); +} + +sat_device::~sat_device() throw() +{ +} + + // cdb[0]: ATA PASS THROUGH (16) SCSI command opcode byte (0x85) // cdb[1]: multiple_count, protocol + extend // cdb[2]: offline, ck_cond, t_dir, byte_block + t_length @@ -112,7 +201,7 @@ extern smartmonctrl *con; // This interface routine takes ATA SMART commands and packages // them in the SAT-defined ATA PASS THROUGH SCSI commands. There are // two available SCSI commands: a 12 byte and 16 byte variant; the -// one used is chosen via con->satpassthrulen . +// one used is chosen via this->m_passthrulen . // DETAILED DESCRIPTION OF ARGUMENTS // device: is the file descriptor provided by (a SCSI dvice type) open() // command: defines the different ATA operations. @@ -128,9 +217,15 @@ extern smartmonctrl *con; // 0 if the command succeeded and disk SMART status is "OK" // 1 if the command succeeded and disk SMART status is "FAILING" -int sat_command_interface(int device, smart_command_set command, int select, - char *data) +bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) { + if (!ata_cmd_is_ok(in, + true, // data_out_support + true, // multi_sector_support + true) // ata_48bit_support + ) + return false; + struct scsi_cmnd_io io_hdr; struct scsi_sense_disect sinfo; struct sg_scsi_sense_hdr ssh; @@ -138,136 +233,87 @@ int sat_command_interface(int device, smart_command_set command, int select, unsigned char sense[32]; const unsigned char * ardp; int status, ard_len, have_sense; - int copydata = 0; - int outlen = 0; int extend = 0; int ck_cond = 0; /* set to 1 to read register(s) back */ int protocol = 3; /* non-data */ int t_dir = 1; /* 0 -> to device, 1 -> from device */ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ int t_length = 0; /* 0 -> no data transferred */ - int feature = 0; - int ata_command = 0; - int sector_count = 0; - int lba_low = 0; - int lba_mid = 0; - int lba_high = 0; int passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE; memset(cdb, 0, sizeof(cdb)); memset(sense, 0, sizeof(sense)); - ata_command = ATA_SMART_CMD; - switch (command) { - case CHECK_POWER_MODE: - ata_command = ATA_CHECK_POWER_MODE; - ck_cond = 1; - copydata = 1; + // Set data direction + // TODO: This works only for commands where sector_count holds count! + switch (in.direction) { + case ata_cmd_in::no_data: break; - case READ_VALUES: /* READ DATA */ - feature = ATA_SMART_READ_VALUES; - sector_count = 1; /* one (512 byte) block */ - protocol = 4; /* PIO data-in */ - t_length = 2; /* sector count holds count */ - copydata = 512; - break; - case READ_THRESHOLDS: /* obsolete */ - feature = ATA_SMART_READ_THRESHOLDS; - sector_count = 1; /* one (512 byte) block */ - lba_low = 1; - protocol = 4; /* PIO data-in */ - t_length = 2; /* sector count holds count */ - copydata=512; + case ata_cmd_in::data_in: + protocol = 4; // PIO data-in + t_length = 2; // sector_count holds count break; - case READ_LOG: - feature = ATA_SMART_READ_LOG_SECTOR; - sector_count = 1; /* one (512 byte) block */ - lba_low = select; - protocol = 4; /* PIO data-in */ - t_length = 2; /* sector count holds count */ - copydata = 512; + case ata_cmd_in::data_out: + protocol = 5; // PIO data-out + t_length = 2; // sector_count holds count + t_dir = 0; // to device break; - case WRITE_LOG: - feature = ATA_SMART_WRITE_LOG_SECTOR; - sector_count = 1; /* one (512 byte) block */ - lba_low = select; - protocol = 5; /* PIO data-out */ - t_length = 2; /* sector count holds count */ - t_dir = 0; /* to device */ - outlen = 512; - break; - case IDENTIFY: - ata_command = ATA_IDENTIFY_DEVICE; - sector_count = 1; /* one (512 byte) block */ - protocol = 4; /* PIO data-in */ - t_length = 2; /* sector count holds count */ - copydata = 512; - break; - case PIDENTIFY: - ata_command = ATA_IDENTIFY_PACKET_DEVICE; - sector_count = 1; /* one (512 byte) block */ - protocol = 4; /* PIO data-in */ - t_length = 2; /* sector count (7:0) holds count */ - copydata = 512; - break; - case ENABLE: - feature = ATA_SMART_ENABLE; - lba_low = 1; - break; - case DISABLE: - feature = ATA_SMART_DISABLE; - lba_low = 1; - break; - case STATUS: - // this command only says if SMART is working. It could be - // replaced with STATUS_CHECK below. - feature = ATA_SMART_STATUS; - ck_cond = 1; - break; - case AUTO_OFFLINE: - feature = ATA_SMART_AUTO_OFFLINE; - sector_count = select; // YET NOTE - THIS IS A NON-DATA COMMAND!! - break; - case AUTOSAVE: - feature = ATA_SMART_AUTOSAVE; - sector_count = select; // YET NOTE - THIS IS A NON-DATA COMMAND!! - break; - case IMMEDIATE_OFFLINE: - feature = ATA_SMART_IMMEDIATE_OFFLINE; - lba_low = select; - break; - case STATUS_CHECK: - // This command uses HDIO_DRIVE_TASK and has different syntax than - // the other commands. - feature = ATA_SMART_STATUS; /* SMART RETURN STATUS */ - ck_cond = 1; - break; - default: - pout("Unrecognized command %d in sat_command_interface()\n" - "Please contact " PACKAGE_BUGREPORT "\n", command); - errno=ENOSYS; - return -1; + default: + return set_err(EINVAL, "sat_device::ata_pass_through: invalid direction=%d", + (int)in.direction); } - if (ATA_SMART_CMD == ata_command) { - lba_mid = 0x4f; - lba_high = 0xc2; + + // Check condition if any output register needed + if (in.out_needed.is_set()) + ck_cond = 1; + + if ((SAT_ATA_PASSTHROUGH_12LEN == m_passthrulen) || + (SAT_ATA_PASSTHROUGH_16LEN == m_passthrulen)) + passthru_size = m_passthrulen; + + // Set extend bit on 48-bit ATA command + if (in.in_regs.is_48bit_cmd()) { + if (passthru_size != SAT_ATA_PASSTHROUGH_16LEN) + return set_err(ENOSYS, "48-bit ATA commands require SAT ATA PASS-THROUGH (16)"); + extend = 1; } - if ((SAT_ATA_PASSTHROUGH_12LEN == con->satpassthrulen) || - (SAT_ATA_PASSTHROUGH_16LEN == con->satpassthrulen)) - passthru_size = con->satpassthrulen; cdb[0] = (SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? SAT_ATA_PASSTHROUGH_12 : SAT_ATA_PASSTHROUGH_16; cdb[1] = (protocol << 1) | extend; cdb[2] = (ck_cond << 5) | (t_dir << 3) | (byte_block << 2) | t_length; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 3 : 4] = feature; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 4 : 6] = sector_count; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 5 : 8] = lba_low; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 6 : 10] = lba_mid; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 7 : 12] = lba_high; - cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 9 : 14] = ata_command; + + if (passthru_size == SAT_ATA_PASSTHROUGH_12LEN) { + // ATA PASS-THROUGH (12) + const ata_in_regs & lo = in.in_regs; + cdb[3] = lo.features; + cdb[4] = lo.sector_count; + cdb[5] = lo.lba_low; + cdb[6] = lo.lba_mid; + cdb[7] = lo.lba_high; + cdb[8] = lo.device; + cdb[9] = lo.command; + } + else { + // ATA PASS-THROUGH (16) + const ata_in_regs & lo = in.in_regs; + const ata_in_regs & hi = in.in_regs.prev; + // Note: all 'in.in_regs.prev.*' are always zero for 28-bit commands + cdb[ 3] = hi.features; + cdb[ 4] = lo.features; + cdb[ 5] = hi.sector_count; + cdb[ 6] = lo.sector_count; + cdb[ 7] = hi.lba_low; + cdb[ 8] = lo.lba_low; + cdb[ 9] = hi.lba_mid; + cdb[10] = lo.lba_mid; + cdb[11] = hi.lba_high; + cdb[12] = lo.lba_high; + cdb[13] = lo.device; + cdb[14] = lo.command; + } memset(&io_hdr, 0, sizeof(io_hdr)); if (0 == t_length) { @@ -275,13 +321,13 @@ int sat_command_interface(int device, smart_command_set command, int select, io_hdr.dxfer_len = 0; } else if (t_dir) { /* from device */ io_hdr.dxfer_dir = DXFER_FROM_DEVICE; - io_hdr.dxfer_len = copydata; - io_hdr.dxferp = (unsigned char *)data; - memset(data, 0, copydata); /* prefill with zeroes */ + io_hdr.dxfer_len = in.size; + io_hdr.dxferp = (unsigned char *)in.buffer; + memset(in.buffer, 0, in.size); // prefill with zeroes } else { /* to device */ io_hdr.dxfer_dir = DXFER_TO_DEVICE; - io_hdr.dxfer_len = outlen; - io_hdr.dxferp = (unsigned char *)data; + io_hdr.dxfer_len = in.size; + io_hdr.dxferp = (unsigned char *)in.buffer; } io_hdr.cmnd = cdb; io_hdr.cmnd_len = passthru_size; @@ -289,12 +335,12 @@ int sat_command_interface(int device, smart_command_set command, int select, io_hdr.max_sense_len = sizeof(sense); io_hdr.timeout = SCSI_TIMEOUT_DEFAULT; - status = do_scsi_cmnd_io(device, &io_hdr, con->reportscsiioctl); - if (0 != status) { + scsi_device * scsidev = get_tunnel_dev(); + if (!scsidev->scsi_pass_through(&io_hdr)) { if (con->reportscsiioctl > 0) - pout("sat_command_interface: do_scsi_cmnd_io() failed, " - "status=%d\n", status); - return -1; + pout("sat_device::ata_pass_through: scsi_pass_through() failed, " + "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg()); + return set_err(scsidev->get_err()); } ardp = NULL; ard_len = 0; @@ -316,16 +362,16 @@ int sat_command_interface(int device, smart_command_set command, int select, status = scsiSimpleSenseFilter(&sinfo); if (0 != status) { if (con->reportscsiioctl > 0) { - pout("sat_command_interface: scsi error: %s\n", + pout("sat_device::ata_pass_through: scsi error: %s\n", scsiErrString(status)); if (ardp && (con->reportscsiioctl > 1)) { pout("Values from ATA Return Descriptor are:\n"); dStrHex((const char *)ardp, ard_len, 1); } } - if (t_dir && (t_length > 0) && (copydata > 0)) - memset(data, 0, copydata); - return -1; + if (t_dir && (t_length > 0) && (in.direction == ata_cmd_in::data_in)) + memset(in.buffer, 0, in.size); + return set_err(EIO, "scsi error %s", scsiErrString(status)); } } if (ck_cond) { /* expecting SAT specific sense data */ @@ -335,20 +381,21 @@ int sat_command_interface(int device, smart_command_set command, int select, pout("Values from ATA Return Descriptor are:\n"); dStrHex((const char *)ardp, ard_len, 1); } - if (ATA_CHECK_POWER_MODE == ata_command) - data[0] = ardp[5]; /* sector count (0:7) */ - else if (STATUS_CHECK == command) { - if ((ardp[9] == 0x4f) && (ardp[11] == 0xc2)) - return 0; /* GOOD smart status */ - if ((ardp[9] == 0xf4) && (ardp[11] == 0x2c)) - return 1; // smart predicting failure, "bad" status - // We haven't gotten output that makes sense so - // print out some debugging info - syserror("Error SMART Status command failed"); - pout("Please get assistance from " PACKAGE_HOMEPAGE "\n"); - pout("Values from ATA Return Descriptor are:\n"); - dStrHex((const char *)ardp, ard_len, 1); - return -1; + // Set output registers + ata_out_regs & lo = out.out_regs; + lo.error = ardp[ 3]; + lo.sector_count = ardp[ 5]; + lo.lba_low = ardp[ 7]; + lo.lba_mid = ardp[ 9]; + lo.lba_high = ardp[11]; + lo.device = ardp[12]; + lo.status = ardp[13]; + if (in.in_regs.is_48bit_cmd()) { + ata_out_regs & hi = out.out_regs.prev; + hi.sector_count = ardp[ 4]; + hi.lba_low = ardp[ 6]; + hi.lba_mid = ardp[ 8]; + hi.lba_high = ardp[10]; } } } @@ -367,33 +414,37 @@ int sat_command_interface(int device, smart_command_set command, int select, pout("Values from ATA Return Descriptor are:\n"); dStrHex((const char *)ardp, ard_len, 1); } - return -1; + return set_err(EIO, "SAT command failed"); } } } } - return 0; + return true; } +} // namespace + +///////////////////////////////////////////////////////////////////////////// + /* Attempt an IDENTIFY DEVICE ATA command via SATL when packet_interface - is 0 otherwise attempt IDENTIFY PACKET DEVICE. If successful - return 1, else 0 */ -int has_sat_pass_through(int device, int packet_interface) + is false otherwise attempt IDENTIFY PACKET DEVICE. If successful + return true, else false */ + +static bool has_sat_pass_through(ata_device * dev, bool packet_interface = false) { + ata_cmd_in in; + in.in_regs.command = (packet_interface ? ATA_IDENTIFY_PACKET_DEVICE : ATA_IDENTIFY_DEVICE); char data[512]; - smart_command_set command; - - command = packet_interface ? PIDENTIFY : IDENTIFY; - if (0 == sat_command_interface(device, command, 0, data)) - return 1; - else - return 0; + in.set_data_in(data, 1); + return dev->ata_pass_through(in); } +///////////////////////////////////////////////////////////////////////////// + /* Next two functions are borrowed from sg_lib.c in the sg3_utils package. Same copyrght owner, same license as this file. */ -int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, - struct sg_scsi_sense_hdr * sshp) +static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, + struct sg_scsi_sense_hdr * sshp) { if (sshp) memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); @@ -427,8 +478,8 @@ int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, } -const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, - int sense_len, int desc_type) +static const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, + int sense_len, int desc_type) { int add_sen_len, add_len, desc_len, k; const unsigned char * descp; @@ -451,3 +502,948 @@ const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep, } return NULL; } + + +// Call scsi_pass_through and check sense. +// TODO: Provide as member function of class scsi_device (?) +static bool scsi_pass_through_and_check(scsi_device * scsidev, scsi_cmnd_io * iop, + const char * msg = "") +{ + // Provide sense buffer + unsigned char sense[32] = {0, }; + iop->sensep = sense; + iop->max_sense_len = sizeof(sense); + iop->timeout = SCSI_TIMEOUT_DEFAULT; + + // Run cmd + if (!scsidev->scsi_pass_through(iop)) { + if (con->reportscsiioctl > 0) + pout("%sscsi_pass_through() failed, errno=%d [%s]\n", + msg, scsidev->get_errno(), scsidev->get_errmsg()); + return false; + } + + // Check sense + scsi_sense_disect sinfo; + scsi_do_sense_disect(iop, &sinfo); + int err = scsiSimpleSenseFilter(&sinfo); + if (err) { + if (con->reportscsiioctl > 0) + pout("%sscsi error: %s\n", msg, scsiErrString(err)); + return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err)); + } + + return true; +} + + +///////////////////////////////////////////////////////////////////////////// + +namespace sat { + +/// Cypress USB Brigde support. + +class usbcypress_device +: public tunnelled_device< + /*implements*/ ata_device_with_command_set + /*by tunnelling through a*/, scsi_device + > +{ +public: + usbcypress_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, unsigned char signature); + + virtual ~usbcypress_device() throw(); + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); + + unsigned char m_signature; +}; + + +usbcypress_device::usbcypress_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type, unsigned char signature) +: smart_device(intf, scsidev->get_dev_name(), "sat", req_type), + tunnelled_device(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(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::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(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,' requires 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,' requires 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],' requires to be 0 or 1"); + return 0; + } + return new usbjmicron_device(this, scsidev, type, ata_48bit_support, port); + } + + else if (!strcmp(type, "usbsunplus")) { + return new usbsunplus_device(this, scsidev, type); + } + + else { + set_err(EINVAL, "Unknown USB device type '%s'", type); + return 0; + } +} + +// Try to detect a SAT device behind a SCSI interface. + +ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev, + const unsigned char * inqdata, unsigned inqsize) +{ + if (!scsidev->is_open()) + return 0; + + ata_device * atadev = 0; + try { + // SAT ? + if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8)) { // TODO: Linux-specific? + atadev = new sat_device(this, scsidev, ""); + if (has_sat_pass_through(atadev)) + return atadev; // Detected SAT + atadev->release(scsidev); + delete atadev; + } + +/* The new usbcypress_device(this, scsidev, "", 0x24) sends vendor specific comand to non-cypress devices. + * It's dangerous as other device may interpret such command as own valid vendor specific command. + * I commented it out untill problem resolved + */ +#if 0 + // USB ? + { + atadev = new usbcypress_device(this, scsidev, "", 0x24); + if (has_usbcypress_pass_through(atadev, + (inqdata && inqsize >= 36 ? (const char*)inqdata + 8 : 0), + (inqdata && inqsize >= 36 ? (const char*)inqdata + 16 : 0) )) + return atadev; // Detected USB + atadev->release(scsidev); + delete atadev; + } +#endif + } + catch (...) { + if (atadev) { + atadev->release(scsidev); + delete atadev; + } + throw; + } + + return 0; +} + + +///////////////////////////////////////////////////////////////////////////// +// USB device type detection + +struct usb_id_entry { + int vendor_id, product_id, version; + const char * type; +}; + +const char d_sat[] = "sat"; +const char d_cypress[] = "usbcypress"; +const char d_jmicron[] = "usbjmicron"; +const char d_jmicron_x[] = "usbjmicron,x"; +const char d_sunplus[] = "usbsunplus"; +const char d_unsup[] = "unsupported"; + +// Map USB IDs -> '-d type' string +const usb_id_entry usb_ids[] = { + { 0x04b4, 0x6830, 0x0001, d_unsup }, // Cypress CY7C68300A (AT2) + { 0x04b4, 0x6830, 0x0240, d_cypress }, // Cypress CY7C68300B/C (AT2LP) +//{ 0x04b4, 0x6831, -1, d_cypress }, // Cypress CY7C68310 (ISD-300LP) + { 0x04fc, 0x0c15, 0xf615, d_sunplus }, // SunPlus SPDIF215 + { 0x04fc, 0x0c25, 0x0103, d_sunplus }, // SunPlus SPDIF225 (USB+SATA->SATA) + { 0x059b, 0x0275, 0x0001, d_unsup }, // Iomega MDHD500-U + { 0x059f, 0x0651, -1, d_unsup }, // LaCie hard disk (FA Porsche design) + { 0x059f, 0x1018, -1, d_sat }, // LaCie hard disk (Neil Poulton design) + { 0x067b, 0x3507, 0x0001, d_unsup }, // Prolific PL3507 + { 0x0930, 0x0b09, -1, d_sunplus }, // Toshiba PX1396E-3T01 (similar to Dura Micro) + { 0x0bc2, 0x2000, -1, d_sat }, // Seagate FreeAgent Go + { 0x0bc2, 0x2100, -1, d_sat }, // Seagate FreeAgent Go + { 0x0bc2, 0x3001, -1, d_sat }, // Seagate FreeAgent Desk + { 0x0c0b, 0xb159, 0x0103, d_sunplus }, // Dura Micro (Sunplus USB-bridge) + { 0x0d49, 0x7310, 0x0125, d_sat }, // Maxtor OneTouch 4 + { 0x0d49, 0x7350, 0x0125, d_sat }, // Maxtor OneTouch 4 Mini + { 0x0d49, 0x7450, 0x0122, d_sat }, // Maxtor Basics Portable + { 0x1058, 0x0704, 0x0175, d_sat }, // WD My Passport Essential + { 0x1058, 0x0705, 0x0175, d_sat }, // WD My Passport Elite + { 0x1058, 0x0906, 0x0012, d_sat }, // WD My Book ES + { 0x1058, 0x1001, 0x0104, d_sat }, // WD Elements Desktop + { 0x1058, 0x1003, 0x0175, d_sat }, // WD Elements Desktop WDE1UBK... + { 0x1058, 0x1010, 0x0105, d_sat }, // WD Elements + { 0x1058, 0x1100, 0x0165, d_sat }, // WD My Book Essential + { 0x1058, 0x1102, 0x1028, d_sat }, // WD My Book + { 0x13fd, 0x1240, 0x0104, d_sat }, // Initio ? (USB->SATA) + { 0x13fd, 0x1340, 0x0208, d_sat }, // Initio ? (USB+SATA->SATA) + { 0x152d, 0x2329, 0x0100, d_jmicron }, // JMicron JM20329 (USB->SATA) + { 0x152d, 0x2336, 0x0100, d_jmicron_x},// JMicron JM20336 (USB+SATA->SATA, USB->2xSATA) + { 0x152d, 0x2338, 0x0100, d_jmicron }, // JMicron JM20337/8 (USB->SATA+PATA, USB+SATA->PATA) + { 0x152d, 0x2339, 0x0100, d_jmicron_x},// JMicron JM20339 (USB->SATA) + { 0x18a5, 0x0215, 0x0001, d_sat }, // Verbatim FW/USB160 - Oxford OXUF934SSA-LQAG (USB+IEE1394->SATA) + { 0x1bcf, 0x0c31, -1, d_sunplus } // SunplusIT +}; + +const unsigned num_usb_ids = sizeof(usb_ids)/sizeof(usb_ids[0]); + + +// Format USB ID for error messages +static std::string format_usb_id(int vendor_id, int product_id, int version) +{ + if (version >= 0) + return strprintf("[0x%04x:0x%04x (0x%03x)]", vendor_id, product_id, version); + else + return strprintf("[0x%04x:0x%04x]", vendor_id, product_id); +} + +// Get type name for USB device with known VENDOR:PRODUCT ID. +const char * smart_interface::get_usb_dev_type_by_id(int vendor_id, int product_id, + int version /*= -1*/) +{ + const usb_id_entry * entry = 0; + bool state = false; + + for (unsigned i = 0; i < num_usb_ids; i++) { + const usb_id_entry & e = usb_ids[i]; + if (!(vendor_id == e.vendor_id && product_id == e.product_id)) + continue; + + // If two entries with same vendor:product ID have different + // types, use version (if provided by OS) to select entry. + bool s = (version >= 0 && version == e.version); + if (entry) { + if (s <= state) { + if (s == state && e.type != entry->type) { + set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'", + format_usb_id(vendor_id, product_id, version).c_str(), + e.type, entry->type); + return 0; + } + continue; + } + } + state = s; + entry = &e; + } + + if (!entry) { + set_err(EINVAL, "Unknown USB bridge %s", + format_usb_id(vendor_id, product_id, version).c_str()); + return 0; + } + if (entry->type == d_unsup) { + set_err(ENOSYS, "Unsupported USB bridge %s", + format_usb_id(vendor_id, product_id, version).c_str()); + return 0; + } + return entry->type; +}