/*
 * scsiata.cpp
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2006-15 Douglas Gilbert <dgilbert@interlog.com>
 * Copyright (C) 2009-23 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
 * 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 <errno.h>

#include "config.h"

#include "scsicmds.h"
#include "atacmds.h" // ataReadHDIdentity()
#include "knowndrives.h" // lookup_usb_device()
#include "utility.h"
#include "dev_interface.h"
#include "dev_ata_cmd_set.h" // ata_device_with_command_set
#include "dev_tunnelled.h" // tunnelled_device<>
#include "sg_unaligned.h"

const char * scsiata_cpp_cvsid = "$Id: scsiata.cpp 5482 2023-06-25 16:46:49Z chrfranke $";

/* 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);

#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
  >,
  virtual public /*implements*/ scsi_device
{
public:
  enum sat_scsi_mode {
    sat_always,
    sat_auto,
    scsi_always
  };
  enum sat_variant {
    sat_standard,
    sat_asm1352r, // ASM1352R port 0 or 1
  };

  sat_device(smart_interface * intf, scsi_device * scsidev,
    const char * req_type, sat_scsi_mode mode = sat_always, int passthrulen = 0,
    sat_variant variant = sat_standard, int port = 0);

  virtual ~sat_device();

  virtual smart_device * autodetect_open() override;

  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;

  virtual bool scsi_pass_through(scsi_cmnd_io * iop) override;

private:
  int m_passthrulen;
  sat_scsi_mode m_mode;
  sat_variant m_variant;
  int m_port;
};


sat_device::sat_device(smart_interface * intf, scsi_device * scsidev,
  const char * req_type, sat_scsi_mode mode /* = sat_always */,
  int passthrulen /* = 0 */, sat_variant variant /* = sat_standard */, int port /* = 0 */)
: smart_device(intf, scsidev->get_dev_name(),
    (mode == sat_always ? "sat" : mode == sat_auto ? "sat,auto" : "scsi"), req_type),
  tunnelled_device<ata_device, scsi_device>(scsidev),
  m_passthrulen(passthrulen),
  m_mode(mode),
  m_variant(variant), m_port(port)
{
  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 [%s]", scsidev->get_info_name(),
    (variant == sat_standard ?
     (mode == sat_always ? "SAT" : mode == sat_auto ? "SCSI/SAT" : "SCSI") :
     (port == 0 ? "ASM1352R_0" : "ASM1352R_1")                              ));
}

sat_device::~sat_device()
{
}


// 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
//
//
// 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'



// 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_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;
    struct sg_scsi_sense_hdr ssh;
    unsigned char cdb[SAT_ATA_PASSTHROUGH_16LEN] = {};
    unsigned char sense[32] = {};
    const unsigned char * ardp;
    int ard_len, have_sense;
    int extend = 0;
    int ck_cond = 0;    /* set to 1 to read register(s) back */
    int protocol = 3;   /* non-data */
    int 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;
    bool sense_descriptor = true;

    // 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);
    }

    // The ASM1352R uses reserved values for 'protocol' field to select drive
    if (m_variant == sat_asm1352r) {
      if (in.direction == ata_cmd_in::no_data)
        return set_err(ENOSYS, "NO DATA ATA commands not implemented [ASM1352R]");
      protocol = (m_port == 0 ? 0xd : 0xe);
    }

    // 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;
    }

    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 (scsi_debugmode > 0)
            pout("sat_device::ata_pass_through: scsi_pass_through() failed, "
                 "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
        return set_err(scsidev->get_err());
    }
    ardp = NULL;
    ard_len = 0;
    have_sense = sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len,
                                         &ssh);
    if (have_sense) {
        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);
        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 uint8_t *)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 (scsi_debugmode > 1) {
                    pout("Values from ATA Return Descriptor are:\n");
                    dStrHex((const uint8_t *)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];
                }
            } 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. */
                    }
                }
            }
        }
    } else {    /* ck_cond == 0 */
        if (have_sense) {
            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 (scsi_debugmode > 0) {
                    if (sense_descriptor && ardp) {
                        pout("Values from ATA Return Descriptor are:\n");
                        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 true;
}

bool sat_device::scsi_pass_through(scsi_cmnd_io * iop)
{
  scsi_device * scsidev = get_tunnel_dev();
  if (!scsidev->scsi_pass_through(iop))
    return set_err(scsidev->get_err());
  return true;
}

smart_device * sat_device::autodetect_open()
{
  if (!open() || m_mode != sat_auto)
    return this;

  scsi_device * scsidev = get_tunnel_dev();

  unsigned char inqdata[36] = {0, };
  if (scsiStdInquiry(scsidev, inqdata, sizeof(inqdata))) {
      smart_device::error_info err = scsidev->get_err();
      close();
      set_err(err.no, "INQUIRY [SAT]: %s", err.msg.c_str());
      return this;
  }

  // Check for SAT "VENDOR"
  int inqsize = inqdata[4] + 5;
  bool sat = (inqsize >= 36 && !memcmp(inqdata + 8, "ATA     ", 8));

  // Change interface
  hide_ata(!sat);
  hide_scsi(sat);

  set_info().dev_type = (sat ? "sat" : scsidev->get_dev_type());
  set_info().info_name = strprintf("%s [%s]", scsidev->get_info_name(),
                                   (sat ? "SAT" : "SCSI"));
  return this;
}

} // namespace

/////////////////////////////////////////////////////////////////////////////

/* 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 controllers 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;
}

/////////////////////////////////////////////////////////////////////////////

namespace sat {

/// Cypress USB Bridge 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();

protected:
  virtual int ata_command_interface(smart_command_set command, int select, char * data) override;

  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(), "usbcypress", 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()
{
}


/* 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 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;

    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 */
        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;
        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;
        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;
        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 */
        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 */
        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;

    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 (scsi_debugmode > 0)
            pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
                 "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
        set_err(scsidev->get_err());
        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));

        /* transfer 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 (scsi_debugmode > 0)
                pout("usbcypress_device::ata_command_interface: scsi_pass_through() failed, "
                     "errno=%d [%s]\n", scsidev->get_errno(), scsidev->get_errmsg());
            set_err(scsidev->get_err());
            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 (scsi_debugmode > 1) {
            pout("Values from ATA Return Descriptor are:\n");
            dStrHex((const uint8_t *)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_URL "\n");
            pout("Values from ATA Return Descriptor are:\n");
            dStrHex((const uint8_t *)ardp, ard_len, 1);
            return -1;
        }
    }
    return 0;
}

/////////////////////////////////////////////////////////////////////////////

/// 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 prolific,
                    bool ata_48bit_support, int port);

  virtual ~usbjmicron_device();

  virtual bool open() override;

  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;

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 prolific,
                                     bool ata_48bit_support, int port)
: smart_device(intf, scsidev->get_dev_name(), "usbjmicron", req_type),
  tunnelled_device<ata_device, scsi_device>(scsidev),
  m_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());
}

usbjmicron_device::~usbjmicron_device()
{
}


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_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;

  if (m_port < 0)
    return set_err(EIO, "Unknown JMicron port");

  scsi_cmnd_io io_hdr = {};

  bool rwbit = true;
  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;
    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[14];
  cdb[ 0] = 0xdf;
  cdb[ 1] = (rwbit ? 0x10 : 0x00);
  cdb[ 2] = 0x00;
  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;
  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;
  // Prolific PL3507
  cdb[12] = 0x06;
  cdb[13] = 0x7b;

  io_hdr.cmnd = cdb;
  io_hdr.cmnd_len = (!m_prolific ? 12 : 14);

  scsi_device * scsidev = get_tunnel_dev();
  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 0xc2:
          out.out_regs.lba_high = 0xc2;
          out.out_regs.lba_mid = 0x4f;
          break;
        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);
      }
    }

#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[14];
  cdb[ 0] = 0xdf;
  cdb[ 1] = 0x10;
  cdb[ 2] = 0x00;
  sg_put_unaligned_be16(size, cdb + 3);
  cdb[ 5] = 0x00;
  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 = {};
  io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
  io_hdr.dxfer_len = size;
  io_hdr.dxferp = buf;
  io_hdr.cmnd = cdb;
  io_hdr.cmnd_len = (!m_prolific ? 12 : 14);

  scsi_device * scsidev = get_tunnel_dev();
  if (!scsidev->scsi_pass_through_and_check(&io_hdr,
         "usbjmicron_device::get_registers: "))
    return set_err(scsidev->get_err());

  return true;
}


/////////////////////////////////////////////////////////////////////////////

/// 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();

  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;
};


usbprolific_device::usbprolific_device(smart_interface * intf, scsi_device * scsidev,
                                     const char * req_type)
: smart_device(intf, scsidev->get_dev_name(), "usbprolific", req_type),
  tunnelled_device<ata_device, scsi_device>(scsidev)
{
  set_info().info_name = strprintf("%s [USB Prolific]", scsidev->get_info_name());
}

usbprolific_device::~usbprolific_device()
{
}

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 = {};
  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.

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();

  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;
};


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()
{
}

bool usbsunplus_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_output_regs |
    ata_device::supports_48bit,
    "Sunplus")
  )
    return false;

  scsi_cmnd_io io_hdr = {};
  unsigned char cdb[12];

  if (in.in_regs.is_48bit_cmd()) {
    // Set "previous" registers
    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 (!scsidev->scsi_pass_through_and_check(&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 (!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());

  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 (!scsidev->scsi_pass_through_and_check(&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 (!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 = nullptr;

  if (str_starts_with(type, "sat")) {
    const char * t = type + 3;
    sat_device::sat_scsi_mode mode = sat_device::sat_always;
    if (str_starts_with(t, ",auto")) {
      t += 5;
      mode = sat_device::sat_auto;
    }
    int ptlen = 0, n = -1;
    if (*t && !(sscanf(t, ",%d%n", &ptlen, &n) == 1 && n == (int)strlen(t)
                && (ptlen == 0 || ptlen == 12 || ptlen == 16)))
      return set_err_np(EINVAL, "Option '-d sat[,auto][,N]' requires N to be 0, 12 or 16");
    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 (str_starts_with(type, "usbcypress")) {
    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))
      return set_err_np(EINVAL, "Option '-d usbcypress,<n>' requires <n> to be "
                                "an hexadecimal number between 0x0 and 0xff"    );
    satdev = new usbcypress_device(this, scsidev, type, signature);
  }

  else if (str_starts_with(type, "usbjmicron")) {
    const char * t = type + 10;
    bool prolific = false;
    if (str_starts_with(t, ",p")) {
      t += 2;
      prolific = true;
    }
    bool ata_48bit_support = false;
    if (str_starts_with(t, ",x")) {
      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)))
      return set_err_np(EINVAL, "Option '-d usbjmicron[,p][,x],<n>' requires <n> to be 0 or 1");
    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")) {
    satdev = new usbsunplus_device(this, scsidev, type);
  }

  else if (str_starts_with(type, "usbasm1352r")) {
    unsigned port = ~0; int n = -1;
    if (!(sscanf(type, "usbasm1352r,%u%n", &port, &n) == 1 && n == (int)strlen(type) && port <= 1))
      return set_err_np(EINVAL, "Option '-d usbasm1352r,<n>' requires <n> to be 0 or 1");
    satdev = new sat_device(this, scsidev, type, sat_device::sat_always, 0, sat_device::sat_asm1352r, port);
  }

  else {
    return set_err_np(EINVAL, "Unknown USB device type '%s'", type);
  }

  // 'scsidev' is now owned by 'satdev'
  scsidev_holder.release();
  return satdev;
}

// 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;

  // SAT ?
  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
  }

  return 0;
}


/////////////////////////////////////////////////////////////////////////////
// USB device type detection

// 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*/)
{
  usb_dev_info info, info2;
  int n = lookup_usb_device(vendor_id, product_id, version, info, info2);

  if (n <= 0) {
    set_err(EINVAL, "Unknown USB bridge %s",
            format_usb_id(vendor_id, product_id, version).c_str());
    return 0;
  }

  if (n > 1) {
    set_err(EINVAL, "USB bridge %s type is ambiguous: '%s' or '%s'",
            format_usb_id(vendor_id, product_id, version).c_str(),
            (!info.usb_type.empty()  ? info.usb_type.c_str()  : "[unsupported]"),
            (!info2.usb_type.empty() ? info2.usb_type.c_str() : "[unsupported]"));
    return 0;
  }

  if (info.usb_type.empty()) {
    set_err(ENOSYS, "Unsupported USB bridge %s",
            format_usb_id(vendor_id, product_id, version).c_str());
    return 0;
  }

  // TODO: change return type to std::string
  static std::string type;
  type = info.usb_type;
  return type.c_str();
}
