X-Git-Url: https://git.proxmox.com/?p=mirror_smartmontools-debian.git;a=blobdiff_plain;f=scsiata.cpp;h=94b82a6353fa435e1fdcfd0b95f76c0f9cff726e;hp=7ce6d5e5b058de3b7004aa3f102577a9dce01863;hb=HEAD;hpb=a3628d53b3b5aa7d9a93922ad6d3f3cd17cdfce8 diff --git a/scsiata.cpp b/scsiata.cpp index 7ce6d5e..94b82a6 100644 --- a/scsiata.cpp +++ b/scsiata.cpp @@ -1,20 +1,12 @@ /* * scsiata.cpp * - * Home page of code is: http://smartmontools.sourceforge.net + * Home page of code is: http://www.smartmontools.org * - * Copyright (C) 2006-12 Douglas Gilbert - * Copyright (C) 2009-12 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 - * 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. + * Copyright (C) 2006-15 Douglas Gilbert + * Copyright (C) 2009-18 Christian Franke * + * SPDX-License-Identifier: GPL-2.0-or-later * 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 @@ -43,7 +35,7 @@ * 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 @@ -53,7 +45,7 @@ #include #include "config.h" -#include "int64.h" + #include "scsicmds.h" #include "atacmds.h" // ataReadHDIdentity() #include "knowndrives.h" // lookup_usb_device() @@ -61,8 +53,9 @@ #include "dev_interface.h" #include "dev_ata_cmd_set.h" // ata_device_with_command_set #include "dev_tunnelled.h" // tunnelled_device<> +#include "sg_unaligned.h" -const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 3519 2012-03-06 20:01:44Z chrfranke $"; +const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 4848 2018-12-05 18:30:46Z chrfranke $"; /* This is a slightly stretched SCSI sense "descriptor" format header. The addition is to allow the 0x70 and 0x71 response codes. The idea @@ -92,12 +85,6 @@ struct sg_scsi_sense_hdr { 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 @@ -118,8 +105,14 @@ class sat_device virtual public /*implements*/ scsi_device { public: + enum sat_scsi_mode { + sat_always, + sat_auto, + scsi_always + }; + sat_device(smart_interface * intf, scsi_device * scsidev, - const char * req_type, int passthrulen = 0, bool enable_auto = false); + const char * req_type, sat_scsi_mode mode = sat_always, int passthrulen = 0); virtual ~sat_device() throw(); @@ -131,25 +124,28 @@ public: private: int m_passthrulen; - bool m_enable_auto; + sat_scsi_mode m_mode; }; sat_device::sat_device(smart_interface * intf, scsi_device * scsidev, - const char * req_type, int passthrulen /* = 0 */, bool enable_auto /* = false */) + const char * req_type, sat_scsi_mode mode /* = sat_always */, + int passthrulen /* = 0 */) : smart_device(intf, scsidev->get_dev_name(), - (enable_auto ? "sat,auto" : "sat"), req_type), + (mode == sat_always ? "sat" : mode == sat_auto ? "sat,auto" : "scsi"), req_type), tunnelled_device(scsidev), m_passthrulen(passthrulen), - m_enable_auto(enable_auto) + m_mode(mode) { - if (enable_auto) + if (mode != sat_always) hide_ata(); // Start as SCSI, switch to ATA in autodetect_open() else hide_scsi(); // ATA always + if (strcmp(scsidev->get_dev_type(), "scsi")) + set_info().dev_type += strprintf("+%s", scsidev->get_dev_type()); - set_info().info_name = strprintf("%s [%sSAT]", scsidev->get_info_name(), - (enable_auto ? "SCSI/" : "")); + set_info().info_name = strprintf("%s [%s]", scsidev->get_info_name(), + (mode == sat_always ? "SAT" : mode == sat_auto ? "SCSI/SAT" : "SCSI")); } sat_device::~sat_device() throw() @@ -207,6 +203,25 @@ sat_device::~sat_device() throw() // des[11]: lba_high (7:0) // des[12]: device // des[13]: status +// +// +// ATA registers returned via fixed format sense (allowed >= SAT-2) +// fxs[0]: info_valid (bit 7); response_code (6:0) +// fxs[1]: (obsolete) +// fxs[2]: sense_key (3:0) --> recovered error (formerly 'no sense') +// fxs[3]: information (31:24) --> ATA Error register +// fxs[4]: information (23:16) --> ATA Status register +// fxs[5]: information (15:8) --> ATA Device register +// fxs[6]: information (7:0) --> ATA Count (7:0) +// fxs[7]: additional sense length [should be >= 10] +// fxs[8]: command specific info (31:24) --> Extend (7), count_upper_nonzero +// (6), lba_upper_nonzero(5), log_index (3:0) +// fxs[9]: command specific info (23:16) --> ATA LBA (7:0) +// fxs[10]: command specific info (15:8) --> ATA LBA (15:8) +// fxs[11]: command specific info (7:0) --> ATA LBA (23:16) +// fxs[12]: additional sense code (asc) --> 0x0 +// fxs[13]: additional sense code qualifier (ascq) --> 0x1d +// asc,ascq = 0x0,0x1d --> 'ATA pass through information available' @@ -225,19 +240,21 @@ sat_device::~sat_device() throw() // RETURN VALUES // -1 if the command failed // 0 if the command succeeded, -// STATUS_CHECK routine: +// 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; + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_output_regs | + ata_device::supports_multi_sector | + ata_device::supports_48bit, + "SAT") + ) + return false; struct scsi_cmnd_io io_hdr; struct scsi_sense_disect sinfo; @@ -245,7 +262,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) unsigned char cdb[SAT_ATA_PASSTHROUGH_16LEN]; unsigned char sense[32]; const unsigned char * ardp; - int status, ard_len, have_sense; + int ard_len, have_sense; int extend = 0; int ck_cond = 0; /* set to 1 to read register(s) back */ int protocol = 3; /* non-data */ @@ -253,6 +270,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ int t_length = 0; /* 0 -> no data transferred */ int passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE; + bool sense_descriptor = true; memset(cdb, 0, sizeof(cdb)); memset(sense, 0, sizeof(sense)); @@ -360,26 +378,39 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) have_sense = sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, &ssh); if (have_sense) { - /* look for SAT ATA Return Descriptor */ - ardp = sg_scsi_sense_desc_find(io_hdr.sensep, - io_hdr.resp_sense_len, - ATA_RETURN_DESCRIPTOR); - if (ardp) { - ard_len = ardp[1] + 2; - if (ard_len < 12) - ard_len = 12; - else if (ard_len > 14) - ard_len = 14; + sense_descriptor = ssh.response_code >= 0x72; + if (sense_descriptor) { + /* look for SAT ATA Return Descriptor */ + ardp = sg_scsi_sense_desc_find(io_hdr.sensep, + io_hdr.resp_sense_len, + ATA_RETURN_DESCRIPTOR); + if (ardp) { + ard_len = ardp[1] + 2; + if (ard_len < 12) + ard_len = 12; + else if (ard_len > 14) + ard_len = 14; + } } scsi_do_sense_disect(&io_hdr, &sinfo); - status = scsiSimpleSenseFilter(&sinfo); - if (0 != status) { + int status = scsiSimpleSenseFilter(&sinfo); + + // Workaround for bogus sense_key in sense data with SAT ATA Return Descriptor + if ( status && ck_cond && ardp && ard_len > 13 + && (ardp[13] & 0xc1) == 0x40 /* !BSY && DRDY && !ERR */) { + if (scsi_debugmode > 0) + pout("ATA status (0x%02x) indicates success, ignoring SCSI sense_key\n", + ardp[13]); + status = 0; + } + + if (0 != status) { /* other than no_sense and recovered_error */ if (scsi_debugmode > 0) { pout("sat_device::ata_pass_through: scsi error: %s\n", scsiErrString(status)); if (ardp && (scsi_debugmode > 1)) { pout("Values from ATA Return Descriptor are:\n"); - dStrHex((const char *)ardp, ard_len, 1); + dStrHex((const uint8_t *)ardp, ard_len, 1); } } if (t_dir && (t_length > 0) && (in.direction == ata_cmd_in::data_in)) @@ -392,7 +423,7 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) if (ardp) { if (scsi_debugmode > 1) { pout("Values from ATA Return Descriptor are:\n"); - dStrHex((const char *)ardp, ard_len, 1); + dStrHex((const uint8_t *)ardp, ard_len, 1); } // Set output registers ata_out_regs & lo = out.out_regs; @@ -410,26 +441,57 @@ bool sat_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) hi.lba_mid = ardp[ 8]; hi.lba_high = ardp[10]; } + } else if ((! sense_descriptor) && + (0 == ssh.asc) && + (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq) && + (0 != io_hdr.sensep[4] /* Some ATA STATUS bit must be set */)) { + /* in SAT-2 and later, ATA registers may be passed back via + * fixed format sense data [ref: sat3r07 section 12.2.2.7] */ + ata_out_regs & lo = out.out_regs; + lo.error = io_hdr.sensep[ 3]; + lo.status = io_hdr.sensep[ 4]; + lo.device = io_hdr.sensep[ 5]; + lo.sector_count = io_hdr.sensep[ 6]; + lo.lba_low = io_hdr.sensep[ 9]; + lo.lba_mid = io_hdr.sensep[10]; + lo.lba_high = io_hdr.sensep[11]; + if (in.in_regs.is_48bit_cmd()) { + if (0 == (0x60 & io_hdr.sensep[8])) { + ata_out_regs & hi = out.out_regs.prev; + hi.sector_count = 0; + hi.lba_low = 0; + hi.lba_mid = 0; + hi.lba_high = 0; + } else { + /* getting the "hi." values when either + * count_upper_nonzero or lba_upper_nonzero are set + * involves fetching the SCSI ATA PASS-THROUGH + * Results log page and decoding the descriptor with + * the matching log_index field. Painful. */ + } + } } } - if (ardp == NULL) - ck_cond = 0; /* not the type of sense data expected */ - } - if (0 == ck_cond) { + } else { /* ck_cond == 0 */ if (have_sense) { - if ((ssh.response_code >= 0x72) && - ((SCSI_SK_NO_SENSE == ssh.sense_key) || + if (((SCSI_SK_NO_SENSE == ssh.sense_key) || (SCSI_SK_RECOVERED_ERR == ssh.sense_key)) && (0 == ssh.asc) && (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq)) { - if (ardp) { - if (scsi_debugmode > 0) { + if (scsi_debugmode > 0) { + if (sense_descriptor && ardp) { pout("Values from ATA Return Descriptor are:\n"); - dStrHex((const char *)ardp, ard_len, 1); + dStrHex((const uint8_t *)ardp, ard_len, 1); + } else if (! sense_descriptor) { + pout("Values from ATA fixed format sense are:\n"); + pout(" Error: 0x%x\n", io_hdr.sensep[3]); + pout(" Status: 0x%x\n", io_hdr.sensep[4]); + pout(" Device: 0x%x\n", io_hdr.sensep[5]); + pout(" Count: 0x%x\n", io_hdr.sensep[6]); } - return set_err(EIO, "SAT command failed"); } } + return set_err(EIO, "SAT command failed"); } } return true; @@ -447,7 +509,7 @@ bool sat_device::scsi_pass_through(scsi_cmnd_io * iop) smart_device * sat_device::autodetect_open() { - if (!open() || !m_enable_auto) + if (!open() || m_mode != sat_auto) return this; scsi_device * scsidev = get_tunnel_dev(); @@ -485,7 +547,7 @@ smart_device * sat_device::autodetect_open() 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 + page. This avoids some bugs seen on LSI controllers under FreeBSD */ char *data = (char *)malloc(512); ata_cmd_in in; @@ -534,66 +596,6 @@ static int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len, 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 (scsi_debugmode > 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 (scsi_debugmode > 0) - pout("%sscsi error: %s\n", msg, scsiErrString(err)); - return scsidev->set_err(EIO, "scsi error %s", scsiErrString(err)); - } - - return true; -} - - ///////////////////////////////////////////////////////////////////////////// namespace sat { @@ -621,7 +623,7 @@ protected: 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), +: smart_device(intf, scsidev->get_dev_name(), "usbcypress", req_type), tunnelled_device(scsidev), m_signature(signature) { @@ -795,7 +797,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele // if there is a sense the command failed or the // device doesn't support usbcypress - if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && + if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) { return -1; } @@ -813,7 +815,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele cdb[2] = (1<<0); /* ask read taskfile */ memset(sense, 0, sizeof(sense)); - /* transfert 8 bytes */ + /* transfer 8 bytes */ memset(&io_hdr, 0, sizeof(io_hdr)); io_hdr.dxfer_dir = DXFER_FROM_DEVICE; io_hdr.dxfer_len = ard_len; @@ -836,7 +838,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele } // if there is a sense the command failed or the // device doesn't support usbcypress - if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && + if (io_hdr.scsi_status == SCSI_STATUS_CHECK_CONDITION && sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len, NULL)) { return -1; } @@ -844,7 +846,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele if (scsi_debugmode > 1) { pout("Values from ATA Return Descriptor are:\n"); - dStrHex((const char *)ardp, ard_len, 1); + dStrHex((const uint8_t *)ardp, ard_len, 1); } if (ATA_CHECK_POWER_MODE == ata_command) @@ -861,7 +863,7 @@ int usbcypress_device::ata_command_interface(smart_command_set command, int sele 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); + dStrHex((const uint8_t *)ardp, ard_len, 1); return -1; } } @@ -925,7 +927,8 @@ class usbjmicron_device { public: usbjmicron_device(smart_interface * intf, scsi_device * scsidev, - const char * req_type, bool ata_48bit_support, int port); + const char * req_type, bool prolific, + bool ata_48bit_support, int port); virtual ~usbjmicron_device() throw(); @@ -936,16 +939,19 @@ public: private: bool get_registers(unsigned short addr, unsigned char * buf, unsigned short size); + bool m_prolific; bool m_ata_48bit_support; int m_port; }; usbjmicron_device::usbjmicron_device(smart_interface * intf, scsi_device * scsidev, - const char * req_type, bool ata_48bit_support, int port) + const char * req_type, bool prolific, + bool ata_48bit_support, int port) : smart_device(intf, scsidev->get_dev_name(), "usbjmicron", req_type), tunnelled_device(scsidev), - m_ata_48bit_support(ata_48bit_support), m_port(port) + m_prolific(prolific), m_ata_48bit_support(ata_48bit_support), + m_port(port >= 0 || !prolific ? port : 0) { set_info().info_name = strprintf("%s [USB JMicron]", scsidev->get_info_name()); } @@ -989,24 +995,14 @@ bool usbjmicron_device::open() bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) { - if (!ata_cmd_is_ok(in, - true, // data_out_support - false, // !multi_sector_support - m_ata_48bit_support) // limited, see below + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_smart_status | + (m_ata_48bit_support ? ata_device::supports_48bit_hi_null : 0), + "JMicron") ) return false; - bool is_smart_status = ( in.in_regs.command == ATA_SMART_CMD - && in.in_regs.features == ATA_SMART_STATUS); - - // Support output registers for SMART STATUS - if (in.out_needed.is_set() && !is_smart_status) - return set_err(ENOSYS, "ATA output registers not supported"); - - // Support 48-bit commands with zero high bytes - if (in.in_regs.is_real_48bit_cmd()) - return set_err(ENOSYS, "48-bit ATA commands not fully supported"); - if (m_port < 0) return set_err(EIO, "Unknown JMicron port"); @@ -1014,7 +1010,10 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou memset(&io_hdr, 0, sizeof(io_hdr)); bool rwbit = true; - unsigned char smart_status = 0; + unsigned char smart_status = 0xff; + + bool is_smart_status = ( in.in_regs.command == ATA_SMART_CMD + && in.in_regs.features == ATA_SMART_STATUS); if (is_smart_status && in.out_needed.is_set()) { io_hdr.dxfer_dir = DXFER_FROM_DEVICE; @@ -1042,12 +1041,11 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou } // Build pass through command - unsigned char cdb[12]; + unsigned char cdb[14]; 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 ); + sg_put_unaligned_be16(io_hdr.dxfer_len, cdb + 3); cdb[ 5] = in.in_regs.features; cdb[ 6] = in.in_regs.sector_count; cdb[ 7] = in.in_regs.lba_low; @@ -1055,26 +1053,36 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou cdb[ 9] = in.in_regs.lba_high; cdb[10] = in.in_regs.device | (m_port == 0 ? 0xa0 : 0xb0); cdb[11] = in.in_regs.command; + // Prolific PL3507 + cdb[12] = 0x06; + cdb[13] = 0x7b; io_hdr.cmnd = cdb; - io_hdr.cmnd_len = sizeof(cdb); + io_hdr.cmnd_len = (!m_prolific ? 12 : 14); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbjmicron_device::ata_pass_through: ")) return set_err(scsidev->get_err()); if (in.out_needed.is_set()) { if (is_smart_status) { + if (io_hdr.resid == 1) + // Some (Prolific) USB bridges do not transfer a status byte + return set_err(ENOSYS, "Incomplete response, status byte missing [JMicron]"); + switch (smart_status) { - case 0x01: case 0xc2: + case 0xc2: out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f; break; - case 0x00: case 0x2c: + case 0x2c: out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4; break; + default: + // Some (JM20336) USB bridges always return 0x01, regardless of SMART Status + return set_err(ENOSYS, "Invalid status byte (0x%02x) [JMicron]", smart_status); } } @@ -1104,19 +1112,20 @@ bool usbjmicron_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou bool usbjmicron_device::get_registers(unsigned short addr, unsigned char * buf, unsigned short size) { - unsigned char cdb[12]; + unsigned char cdb[14]; cdb[ 0] = 0xdf; cdb[ 1] = 0x10; cdb[ 2] = 0x00; - cdb[ 3] = (unsigned char)(size >> 8); - cdb[ 4] = (unsigned char)(size ); + sg_put_unaligned_be16(size, cdb + 3); cdb[ 5] = 0x00; - cdb[ 6] = (unsigned char)(addr >> 8); - cdb[ 7] = (unsigned char)(addr ); + sg_put_unaligned_be16(addr, cdb + 6); cdb[ 8] = 0x00; cdb[ 9] = 0x00; cdb[10] = 0x00; cdb[11] = 0xfd; + // Prolific PL3507 + cdb[12] = 0x06; + cdb[13] = 0x7b; scsi_cmnd_io io_hdr; memset(&io_hdr, 0, sizeof(io_hdr)); @@ -1124,10 +1133,10 @@ bool usbjmicron_device::get_registers(unsigned short addr, io_hdr.dxfer_len = size; io_hdr.dxferp = buf; io_hdr.cmnd = cdb; - io_hdr.cmnd_len = sizeof(cdb); + io_hdr.cmnd_len = (!m_prolific ? 12 : 14); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbjmicron_device::get_registers: ")) return set_err(scsidev->get_err()); @@ -1135,6 +1144,145 @@ bool usbjmicron_device::get_registers(unsigned short addr, } +///////////////////////////////////////////////////////////////////////////// + +/// Prolific USB Bridge support. (PL2773) (Probably works on PL2771 also...) + +class usbprolific_device +: public tunnelled_device< + /*implements*/ ata_device, + /*by tunnelling through a*/ scsi_device + > +{ +public: + usbprolific_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type); + + virtual ~usbprolific_device() throw(); + + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); +}; + + +usbprolific_device::usbprolific_device(smart_interface * intf, scsi_device * scsidev, + const char * req_type) +: smart_device(intf, scsidev->get_dev_name(), "usbprolific", req_type), + tunnelled_device(scsidev) +{ + set_info().info_name = strprintf("%s [USB Prolific]", scsidev->get_info_name()); +} + +usbprolific_device::~usbprolific_device() throw() +{ +} + +bool usbprolific_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +{ + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_48bit_hi_null | + ata_device::supports_output_regs | + ata_device::supports_smart_status, + "Prolific" ) + ) + return false; + + scsi_cmnd_io io_hdr; + memset(&io_hdr, 0, sizeof(io_hdr)); + unsigned char cmd_rw = 0x10; // Read + + switch (in.direction) { + case ata_cmd_in::no_data: + io_hdr.dxfer_dir = DXFER_NONE; + break; + case ata_cmd_in::data_in: + io_hdr.dxfer_dir = DXFER_FROM_DEVICE; + io_hdr.dxfer_len = in.size; + io_hdr.dxferp = (unsigned char *)in.buffer; + memset(in.buffer, 0, in.size); + break; + case ata_cmd_in::data_out: + io_hdr.dxfer_dir = DXFER_TO_DEVICE; + io_hdr.dxfer_len = in.size; + io_hdr.dxferp = (unsigned char *)in.buffer; + cmd_rw = 0x0; // Write + break; + default: + return set_err(EINVAL); + } + + // Based on reverse engineering of iSmart.exe with API Monitor. + // Seen commands: + // D0 0 0 0 06 7B 0 0 0 0 0 0 // Read Firmware info?, reads 16 bytes + // F4 0 0 0 06 7B // ?? + // D8 15 0 D8 06 7B 0 0 0 0 1 1 4F C2 A0 B0 // SMART Enable + // D8 15 0 D0 06 7B 0 0 2 0 1 1 4F C2 A0 B0 // SMART Read values + // D8 15 0 D1 06 7B 0 0 2 0 1 1 4F C2 A0 B0 // SMART Read thresholds + // D8 15 0 D4 06 7B 0 0 0 0 0 1 4F C2 A0 B0 // SMART Execute self test + // D7 0 0 0 06 7B 0 0 0 0 0 0 0 0 0 0 // Read status registers, Reads 16 bytes of data + // Additional DATA OUT support based on document from Prolific + + // Build pass through command + unsigned char cdb[16]; + cdb[ 0] = 0xD8; // Operation Code (D8 = Prolific ATA pass through) + cdb[ 1] = cmd_rw|0x5; // Read(0x10)/Write(0x0) | NORMAL(0x5)/PREFIX(0x0)(?) + cdb[ 2] = 0x0; // Reserved + cdb[ 3] = in.in_regs.features; // Feature register (SMART command) + cdb[ 4] = 0x06; // Check Word (VendorID magic, Prolific: 0x067B) + cdb[ 5] = 0x7B; // Check Word (VendorID magic, Prolific: 0x067B) + sg_put_unaligned_be32(io_hdr.dxfer_len, cdb + 6); + cdb[10] = in.in_regs.sector_count; // Sector Count + cdb[11] = in.in_regs.lba_low; // LBA Low (7:0) + cdb[12] = in.in_regs.lba_mid; // LBA Mid (15:8) + cdb[13] = in.in_regs.lba_high; // LBA High (23:16) + cdb[14] = in.in_regs.device | 0xA0; // Device/Head + cdb[15] = in.in_regs.command; // ATA Command Register (only PIO supported) + // Use '-r scsiioctl,1' to print CDB for debug purposes + + io_hdr.cmnd = cdb; + io_hdr.cmnd_len = 16; + + scsi_device * scsidev = get_tunnel_dev(); + if (!scsidev->scsi_pass_through_and_check(&io_hdr, + "usbprolific_device::ata_pass_through: ")) + return set_err(scsidev->get_err()); + + if (in.out_needed.is_set()) { + // Read ATA output registers + unsigned char regbuf[16] = {0, }; + memset(&io_hdr, 0, sizeof(io_hdr)); + io_hdr.dxfer_dir = DXFER_FROM_DEVICE; + io_hdr.dxfer_len = sizeof(regbuf); + io_hdr.dxferp = regbuf; + + memset(cdb, 0, sizeof(cdb)); + cdb[ 0] = 0xD7; // Prolific read registers + cdb[ 4] = 0x06; // Check Word (VendorID magic, Prolific: 0x067B) + cdb[ 5] = 0x7B; // Check Word (VendorID magic, Prolific: 0x067B) + io_hdr.cmnd = cdb; + io_hdr.cmnd_len = sizeof(cdb); + + if (!scsidev->scsi_pass_through_and_check(&io_hdr, + "usbprolific_device::scsi_pass_through (get registers): ")) + return set_err(scsidev->get_err()); + + // Use '-r scsiioctl,2' to print input registers for debug purposes + // Example: 50 00 00 00 00 01 4f 00 c2 00 a0 da 00 b0 00 50 + out.out_regs.status = regbuf[0]; // Status + out.out_regs.error = regbuf[1]; // Error + out.out_regs.sector_count = regbuf[2]; // Sector Count (7:0) + out.out_regs.lba_low = regbuf[4]; // LBA Low (7:0) + out.out_regs.lba_mid = regbuf[6]; // LBA Mid (7:0) + out.out_regs.lba_high = regbuf[8]; // LBA High (7:0) + out.out_regs.device = regbuf[10]; // Device/Head + // = regbuf[11]; // ATA Feature (7:0) + // = regbuf[13]; // ATA Command + } + + return true; +} + + ///////////////////////////////////////////////////////////////////////////// /// SunplusIT USB Bridge support. @@ -1169,10 +1317,11 @@ 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 + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_output_regs | + ata_device::supports_48bit, + "Sunplus") ) return false; @@ -1201,7 +1350,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd_len = sizeof(cdb); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through (presetting): ")) return set_err(scsidev->get_err()); } @@ -1248,7 +1397,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd_len = sizeof(cdb); scsi_device * scsidev = get_tunnel_dev(); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through: ")) // Returns sense key 0x03 (medium error) on ATA command error return set_err(scsidev->get_err()); @@ -1268,7 +1417,7 @@ bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & ou io_hdr.cmnd = cdb; io_hdr.cmnd_len = sizeof(cdb); - if (!scsi_pass_through_and_check(scsidev, &io_hdr, + if (!scsidev->scsi_pass_through_and_check(&io_hdr, "usbsunplus_device::scsi_pass_through (get registers): ")) return set_err(scsidev->get_err()); @@ -1296,12 +1445,19 @@ using namespace sat; ata_device * smart_interface::get_sat_device(const char * type, scsi_device * scsidev) { + if (!scsidev) + throw std::logic_error("smart_interface: get_sat_device() called with scsidev=0"); + + // Take temporary ownership of 'scsidev' to delete it on error + scsi_device_auto_ptr scsidev_holder(scsidev); + ata_device * satdev = 0; + if (!strncmp(type, "sat", 3)) { const char * t = type + 3; - bool enable_auto = false; + sat_device::sat_scsi_mode mode = sat_device::sat_always; if (!strncmp(t, ",auto", 5)) { t += 5; - enable_auto = true; + mode = sat_device::sat_auto; } int ptlen = 0, n = -1; if (*t && !(sscanf(t, ",%d%n", &ptlen, &n) == 1 && n == (int)strlen(t) @@ -1309,7 +1465,11 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc set_err(EINVAL, "Option '-d sat[,auto][,N]' requires N to be 0, 12 or 16"); return 0; } - return new sat_device(this, scsidev, type, ptlen, enable_auto); + satdev = new sat_device(this, scsidev, type, mode, ptlen); + } + + else if (!strcmp(type, "scsi")) { + satdev = new sat_device(this, scsidev, type, sat_device::scsi_always); } else if (!strncmp(type, "usbcypress", 10)) { @@ -1320,11 +1480,16 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc "an hexadecimal number between 0x0 and 0xff"); return 0; } - return new usbcypress_device(this, scsidev, type, signature); + satdev = new usbcypress_device(this, scsidev, type, signature); } else if (!strncmp(type, "usbjmicron", 10)) { const char * t = type + 10; + bool prolific = false; + if (!strncmp(t, ",p", 2)) { + t += 2; + prolific = true; + } bool ata_48bit_support = false; if (!strncmp(t, ",x", 2)) { t += 2; @@ -1333,20 +1498,28 @@ ata_device * smart_interface::get_sat_device(const char * type, scsi_device * sc 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"); + set_err(EINVAL, "Option '-d usbjmicron[,p][,x],' requires to be 0 or 1"); return 0; } - return new usbjmicron_device(this, scsidev, type, ata_48bit_support, port); + satdev = new usbjmicron_device(this, scsidev, type, prolific, ata_48bit_support, port); + } + + else if (!strcmp(type, "usbprolific")) { + satdev = new usbprolific_device(this, scsidev, type); } else if (!strcmp(type, "usbsunplus")) { - return new usbsunplus_device(this, scsidev, type); + satdev = new usbsunplus_device(this, scsidev, type); } else { set_err(EINVAL, "Unknown USB device type '%s'", type); return 0; } + + // 'scsidev' is now owned by 'satdev' + scsidev_holder.release(); + return satdev; } // Try to detect a SAT device behind a SCSI interface. @@ -1358,7 +1531,9 @@ ata_device * smart_interface::autodetect_sat_device(scsi_device * scsidev, return 0; // SAT ? - if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8)) { // TODO: Linux-specific? + if (inqdata && inqsize >= 36 && !memcmp(inqdata + 8, "ATA ", 8)) { + // TODO: Linux-specific? No, all SAT standards say the 'T10 Vendor + // Identification' field shall be 'ATA '. ata_device_auto_ptr atadev( new sat_device(this, scsidev, "") , scsidev); if (has_sat_pass_through(atadev.get())) return atadev.release(); // Detected SAT