+
+#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;
+ 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 = (!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() throw();
+
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+};
+
+
+usbprolific_device::usbprolific_device(smart_interface * intf, scsi_device * scsidev,
+ const char * req_type)
+: smart_device(intf, scsidev->get_dev_name(), "usbprolific", req_type),
+ tunnelled_device<ata_device, scsi_device>(scsidev)
+{
+ set_info().info_name = strprintf("%s [USB Prolific]", scsidev->get_info_name());
+}
+
+usbprolific_device::~usbprolific_device() throw()
+{
+}
+
+bool usbprolific_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_48bit_hi_null |
+ ata_device::supports_output_regs |
+ ata_device::supports_smart_status,
+ "Prolific" )
+ )
+ return false;
+
+ scsi_cmnd_io io_hdr;
+ memset(&io_hdr, 0, sizeof(io_hdr));
+ unsigned char cmd_rw = 0x10; // Read
+
+ switch (in.direction) {
+ case ata_cmd_in::no_data:
+ io_hdr.dxfer_dir = DXFER_NONE;
+ break;
+ case ata_cmd_in::data_in:
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = in.size;
+ io_hdr.dxferp = (unsigned char *)in.buffer;
+ memset(in.buffer, 0, in.size);
+ break;
+ case ata_cmd_in::data_out:
+ io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+ io_hdr.dxfer_len = in.size;
+ io_hdr.dxferp = (unsigned char *)in.buffer;
+ cmd_rw = 0x0; // Write
+ break;
+ default:
+ return set_err(EINVAL);
+ }
+
+ // Based on reverse engineering of iSmart.exe with API Monitor.
+ // Seen commands:
+ // D0 0 0 0 06 7B 0 0 0 0 0 0 // Read Firmware info?, reads 16 bytes
+ // F4 0 0 0 06 7B // ??
+ // D8 15 0 D8 06 7B 0 0 0 0 1 1 4F C2 A0 B0 // SMART Enable
+ // D8 15 0 D0 06 7B 0 0 2 0 1 1 4F C2 A0 B0 // SMART Read values
+ // D8 15 0 D1 06 7B 0 0 2 0 1 1 4F C2 A0 B0 // SMART Read thresholds
+ // D8 15 0 D4 06 7B 0 0 0 0 0 1 4F C2 A0 B0 // SMART Execute self test
+ // D7 0 0 0 06 7B 0 0 0 0 0 0 0 0 0 0 // Read status registers, Reads 16 bytes of data
+ // Additional DATA OUT support based on document from Prolific
+
+ // Build pass through command
+ unsigned char cdb[16];
+ cdb[ 0] = 0xD8; // Operation Code (D8 = Prolific ATA pass through)
+ cdb[ 1] = cmd_rw|0x5; // Read(0x10)/Write(0x0) | NORMAL(0x5)/PREFIX(0x0)(?)
+ cdb[ 2] = 0x0; // Reserved
+ cdb[ 3] = in.in_regs.features; // Feature register (SMART command)
+ cdb[ 4] = 0x06; // Check Word (VendorID magic, Prolific: 0x067B)
+ cdb[ 5] = 0x7B; // Check Word (VendorID magic, Prolific: 0x067B)
+ 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() throw();
+
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+};
+
+
+usbsunplus_device::usbsunplus_device(smart_interface * intf, scsi_device * scsidev,
+ const char * req_type)
+: smart_device(intf, scsidev->get_dev_name(), "usbsunplus", req_type),
+ tunnelled_device<ata_device, scsi_device>(scsidev)
+{
+ set_info().info_name = strprintf("%s [USB Sunplus]", scsidev->get_info_name());
+}
+
+usbsunplus_device::~usbsunplus_device() throw()
+{
+}
+
+bool usbsunplus_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ if (!ata_cmd_is_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
+ 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 (!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 = 0;
+
+ if (!strncmp(type, "sat", 3)) {
+ const char * t = type + 3;
+ sat_device::sat_scsi_mode mode = sat_device::sat_always;
+ if (!strncmp(t, ",auto", 5)) {
+ 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))) {
+ set_err(EINVAL, "Option '-d sat[,auto][,N]' requires N to be 0, 12 or 16");
+ return 0;
+ }
+ 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)) {
+ unsigned signature = 0x24; int n1 = -1, n2 = -1;
+ if (!(((sscanf(type, "usbcypress%n,0x%x%n", &n1, &signature, &n2) == 1 && n2 == (int)strlen(type)) || n1 == (int)strlen(type))
+ && signature <= 0xff)) {
+ set_err(EINVAL, "Option '-d usbcypress,<n>' requires <n> to be "
+ "an hexadecimal number between 0x0 and 0xff");
+ return 0;
+ }
+ 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;
+ 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 usbjmicron[,p][,x],<n>' requires <n> to be 0 or 1");
+ return 0;
+ }
+ 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 {
+ 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.
+
+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();