+/////////////////////////////////////////////////////////////////////////////
+// ATA PASS THROUGH (Win2003, XP SP2)
+
+// Warning:
+// IOCTL_ATA_PASS_THROUGH[_DIRECT] can only handle one interrupt/DRQ data
+// transfer per command. Therefore, multi-sector transfers are only supported
+// for the READ/WRITE MULTIPLE [EXT] commands. Other commands like READ/WRITE SECTORS
+// or READ/WRITE LOG EXT work only with single sector transfers.
+// The latter are supported on Vista (only) through new ATA_FLAGS_NO_MULTIPLE.
+// See:
+// http://social.msdn.microsoft.com/Forums/en-US/storageplatformata/thread/eb408507-f221-455b-9bbb-d1069b29c4da
+
+static int ata_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, IDEREGS * prev_regs, char * data, int datasize)
+{
+ const int max_sectors = 32; // TODO: Allocate dynamic buffer
+
+ typedef struct {
+ ATA_PASS_THROUGH_EX apt;
+ ULONG Filler;
+ UCHAR ucDataBuf[max_sectors * 512];
+ } ATA_PASS_THROUGH_EX_WITH_BUFFERS;
+
+ const unsigned char magic = 0xcf;
+
+ ATA_PASS_THROUGH_EX_WITH_BUFFERS ab; memset(&ab, 0, sizeof(ab));
+ ab.apt.Length = sizeof(ATA_PASS_THROUGH_EX);
+ //ab.apt.PathId = 0;
+ //ab.apt.TargetId = 0;
+ //ab.apt.Lun = 0;
+ ab.apt.TimeOutValue = 60; // seconds
+ unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf);
+ ab.apt.DataBufferOffset = size;
+
+ if (datasize > 0) {
+ if (datasize > (int)sizeof(ab.ucDataBuf)) {
+ errno = EINVAL;
+ return -1;
+ }
+ ab.apt.AtaFlags = ATA_FLAGS_DATA_IN;
+ ab.apt.DataTransferLength = datasize;
+ size += datasize;
+ ab.ucDataBuf[0] = magic;
+ }
+ else if (datasize < 0) {
+ if (-datasize > (int)sizeof(ab.ucDataBuf)) {
+ errno = EINVAL;
+ return -1;
+ }
+ ab.apt.AtaFlags = ATA_FLAGS_DATA_OUT;
+ ab.apt.DataTransferLength = -datasize;
+ size += -datasize;
+ memcpy(ab.ucDataBuf, data, -datasize);
+ }
+ else {
+ assert(ab.apt.AtaFlags == 0);
+ assert(ab.apt.DataTransferLength == 0);
+ }
+
+ assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS));
+ IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile;
+ IDEREGS * ptfregs = (IDEREGS *)ab.apt.PreviousTaskFile;
+ *ctfregs = *regs;
+
+ if (prev_regs) {
+ *ptfregs = *prev_regs;
+ ab.apt.AtaFlags |= ATA_FLAGS_48BIT_COMMAND;
+ }
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH,
+ &ab, size, &ab, size, &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check ATA status
+ if (ctfregs->bCommandReg/*Status*/ & (0x01/*Err*/|0x08/*DRQ*/)) {
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH command failed:\n");
+ print_ide_regs_io(regs, ctfregs);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ // Check and copy data
+ if (datasize > 0) {
+ if ( num_out != size
+ || (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) {
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH output data missing (%u)\n", (unsigned)num_out);
+ print_ide_regs_io(regs, ctfregs);
+ }
+ errno = EIO;
+ return -1;
+ }
+ memcpy(data, ab.ucDataBuf, datasize);
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_ATA_PASS_THROUGH succeeded, bytes returned: %u\n", (unsigned)num_out);
+ print_ide_regs_io(regs, ctfregs);
+ }
+ *regs = *ctfregs;
+ if (prev_regs)
+ *prev_regs = *ptfregs;
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// SMART IOCTL via SCSI MINIPORT ioctl
+
+// This function is handled by ATAPI port driver (atapi.sys) or by SCSI
+// miniport driver (via SCSI port driver scsiport.sys).
+// It can be used to skip the missing or broken handling of some SMART
+// command codes (e.g. READ_LOG) in the disk class driver (disk.sys)
+
+static int ata_via_scsi_miniport_smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize)
+{
+ // Select code
+ DWORD code = 0; const char * name = 0;
+ if (regs->bCommandReg == ATA_IDENTIFY_DEVICE) {
+ code = IOCTL_SCSI_MINIPORT_IDENTIFY; name = "IDENTIFY";
+ }
+ else if (regs->bCommandReg == ATA_SMART_CMD) switch (regs->bFeaturesReg) {
+ case ATA_SMART_READ_VALUES:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS; name = "READ_SMART_ATTRIBS"; break;
+ case ATA_SMART_READ_THRESHOLDS:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS; name = "READ_SMART_THRESHOLDS"; break;
+ case ATA_SMART_ENABLE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_SMART; name = "ENABLE_SMART"; break;
+ case ATA_SMART_DISABLE:
+ code = IOCTL_SCSI_MINIPORT_DISABLE_SMART; name = "DISABLE_SMART"; break;
+ case ATA_SMART_STATUS:
+ code = IOCTL_SCSI_MINIPORT_RETURN_STATUS; name = "RETURN_STATUS"; break;
+ case ATA_SMART_AUTOSAVE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE; name = "ENABLE_DISABLE_AUTOSAVE"; break;
+ //case ATA_SMART_SAVE: // obsolete since ATA-6, not used by smartmontools
+ // code = IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES; name = "SAVE_ATTRIBUTE_VALUES"; break;
+ case ATA_SMART_IMMEDIATE_OFFLINE:
+ code = IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS; name = "EXECUTE_OFFLINE_DIAGS"; break;
+ case ATA_SMART_AUTO_OFFLINE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE; name = "ENABLE_DISABLE_AUTO_OFFLINE"; break;
+ case ATA_SMART_READ_LOG_SECTOR:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_LOG; name = "READ_SMART_LOG"; break;
+ case ATA_SMART_WRITE_LOG_SECTOR:
+ code = IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG; name = "WRITE_SMART_LOG"; break;
+ }
+ if (!code) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ // Set SRB
+ struct {
+ SRB_IO_CONTROL srbc;
+ union {
+ SENDCMDINPARAMS in;
+ SENDCMDOUTPARAMS out;
+ } params;
+ char space[512-1];
+ } sb;
+ ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1+512);
+ memset(&sb, 0, sizeof(sb));
+
+ unsigned size;
+ if (datasize > 0) {
+ if (datasize > (int)sizeof(sb.space)+1) {
+ errno = EINVAL;
+ return -1;
+ }
+ size = datasize;
+ }
+ else if (datasize < 0) {
+ if (-datasize > (int)sizeof(sb.space)+1) {
+ errno = EINVAL;
+ return -1;
+ }
+ size = -datasize;
+ memcpy(sb.params.in.bBuffer, data, size);
+ }
+ else if (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
+ size = sizeof(IDEREGS);
+ else
+ size = 0;
+ sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(sb.srbc.Signature, "SCSIDISK", 8); // atapi.sys
+ sb.srbc.Timeout = 60; // seconds
+ sb.srbc.ControlCode = code;
+ //sb.srbc.ReturnCode = 0;
+ sb.srbc.Length = sizeof(SENDCMDINPARAMS)-1 + size;
+ sb.params.in.irDriveRegs = *regs;
+ sb.params.in.cBufferSize = size;
+
+ // Call miniport ioctl
+ size += sizeof(SRB_IO_CONTROL) + sizeof(SENDCMDINPARAMS)-1;
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &sb, size, &sb, size, &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, Error=%ld\n", name, err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check result
+ if (sb.srbc.ReturnCode) {
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, ReturnCode=0x%08x\n", name, (unsigned)sb.srbc.ReturnCode);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ if (sb.params.out.DriverStatus.bDriverError) {
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, DriverError=0x%02x, IDEError=0x%02x\n", name,
+ sb.params.out.DriverStatus.bDriverError, sb.params.out.DriverStatus.bIDEError);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (!sb.params.out.DriverStatus.bIDEError ? ENOSYS : EIO);
+ return -1;
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_SCSI_MINIPORT_%s succeeded, bytes returned: %u (buffer %u)\n", name,
+ (unsigned)num_out, (unsigned)sb.params.out.cBufferSize);
+ print_ide_regs_io(regs, (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS ?
+ (const IDEREGS *)(sb.params.out.bBuffer) : 0));
+ }
+
+ if (datasize > 0)
+ memcpy(data, sb.params.out.bBuffer, datasize);
+ else if (datasize == 0 && code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
+ memcpy(regs, sb.params.out.bBuffer, sizeof(IDEREGS));
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// ATA PASS THROUGH via 3ware specific SCSI MINIPORT ioctl
+
+static int ata_via_3ware_miniport_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize, int port)
+{
+ struct {
+ SRB_IO_CONTROL srbc;
+ IDEREGS regs;
+ UCHAR buffer[512];
+ } sb;
+ ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(IDEREGS)+512);
+
+ if (!(0 <= datasize && datasize <= (int)sizeof(sb.buffer) && port >= 0)) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&sb, 0, sizeof(sb));
+ strncpy((char *)sb.srbc.Signature, "<3ware>", sizeof(sb.srbc.Signature));
+ sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ sb.srbc.Timeout = 60; // seconds
+ sb.srbc.ControlCode = 0xA0000000;
+ sb.srbc.ReturnCode = 0;
+ sb.srbc.Length = sizeof(IDEREGS) + (datasize > 0 ? datasize : 1);
+ sb.regs = *regs;
+ sb.regs.bReserved = port;
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ return -1;
+ }
+
+ if (sb.srbc.ReturnCode) {
+ if (ata_debugmode) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)sb.srbc.ReturnCode);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ // Copy data
+ if (datasize > 0)
+ memcpy(data, sb.buffer, datasize);
+
+ if (ata_debugmode > 1) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT succeeded, bytes returned: %u\n", (unsigned)num_out);
+ print_ide_regs_io(regs, &sb.regs);
+ }
+ *regs = sb.regs;
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// 3ware specific call to update the devicemap returned by SMART_GET_VERSION.
+// 3DM/CLI "Rescan Controller" function does not to always update it.
+
+static int update_3ware_devicemap_ioctl(HANDLE hdevice)
+{
+ SRB_IO_CONTROL srbc;
+ memset(&srbc, 0, sizeof(srbc));
+ strncpy((char *)srbc.Signature, "<3ware>", sizeof(srbc.Signature));
+ srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ srbc.Timeout = 60; // seconds
+ srbc.ControlCode = 0xCC010014;
+ srbc.ReturnCode = 0;
+ srbc.Length = 0;
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &srbc, sizeof(srbc), &srbc, sizeof(srbc), &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ return -1;
+ }
+ if (srbc.ReturnCode) {
+ if (ata_debugmode)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)srbc.ReturnCode);
+ errno = EIO;
+ return -1;
+ }
+ if (ata_debugmode > 1)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT succeeded\n");
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+union STORAGE_DEVICE_DESCRIPTOR_DATA {
+ STORAGE_DEVICE_DESCRIPTOR desc;
+ char raw[256];
+};
+
+// Get STORAGE_DEVICE_DESCRIPTOR_DATA for device.
+// (This works without admin rights)
+
+static int storage_query_property_ioctl(HANDLE hdevice, STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, {0} };
+ memset(data, 0, sizeof(*data));
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), data, sizeof(*data), &num_out, NULL)) {
+ if (ata_debugmode > 1 || scsi_debugmode > 1)
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%u\n", (unsigned)GetLastError());
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ata_debugmode > 1 || scsi_debugmode > 1) {
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n"
+ " Vendor: \"%s\"\n"
+ " Product: \"%s\"\n"
+ " Revision: \"%s\"\n"
+ " Removable: %s\n"
+ " BusType: 0x%02x\n",
+ (data->desc.VendorIdOffset ? data->raw+data->desc.VendorIdOffset : "(null)"),
+ (data->desc.ProductIdOffset ? data->raw+data->desc.ProductIdOffset : "(null)"),
+ (data->desc.ProductRevisionOffset ? data->raw+data->desc.ProductRevisionOffset : "(null)"),
+ (data->desc.RemovableMedia? "Yes":"No"), data->desc.BusType
+ );
+ }
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// IOCTL_STORAGE_PREDICT_FAILURE
+
+// Call IOCTL_STORAGE_PREDICT_FAILURE, return PredictFailure value
+// or -1 on error, optionally return VendorSpecific data.
+// (This works without admin rights)
+
+static int storage_predict_failure_ioctl(HANDLE hdevice, char * data = 0)
+{
+ STORAGE_PREDICT_FAILURE pred;
+ memset(&pred, 0, sizeof(pred));
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_STORAGE_PREDICT_FAILURE,
+ 0, 0, &pred, sizeof(pred), &num_out, NULL)) {
+ if (ata_debugmode > 1)
+ pout(" IOCTL_STORAGE_PREDICT_FAILURE failed, Error=%u\n", (unsigned)GetLastError());
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_STORAGE_PREDICT_FAILURE returns:\n"
+ " PredictFailure: 0x%08x\n"
+ " VendorSpecific: 0x%02x,0x%02x,0x%02x,...,0x%02x\n",
+ (unsigned)pred.PredictFailure,
+ pred.VendorSpecific[0], pred.VendorSpecific[1], pred.VendorSpecific[2],
+ pred.VendorSpecific[sizeof(pred.VendorSpecific)-1]
+ );
+ }
+ if (data)
+ memcpy(data, pred.VendorSpecific, sizeof(pred.VendorSpecific));
+ return (!pred.PredictFailure ? 0 : 1);
+}
+
+
+// Build IDENTIFY information from STORAGE_DEVICE_DESCRIPTOR
+static int get_identify_from_device_property(HANDLE hdevice, ata_identify_device * id)
+{
+ STORAGE_DEVICE_DESCRIPTOR_DATA data;
+ if (storage_query_property_ioctl(hdevice, &data))
+ return -1;
+
+ memset(id, 0, sizeof(*id));
+
+ // Some drivers split ATA model string into VendorId and ProductId,
+ // others return it as ProductId only.
+ char model[sizeof(id->model) + 1] = "";
+
+ unsigned i = 0;
+ if (data.desc.VendorIdOffset) {
+ for ( ;i < sizeof(model)-1 && data.raw[data.desc.VendorIdOffset+i]; i++)
+ model[i] = data.raw[data.desc.VendorIdOffset+i];
+ }
+
+ if (data.desc.ProductIdOffset) {
+ while (i > 1 && model[i-2] == ' ') // Keep last blank from VendorId
+ i--;
+ // Ignore VendorId "ATA"
+ if (i <= 4 && !strncmp(model, "ATA", 3) && (i == 3 || model[3] == ' '))
+ i = 0;
+ for (unsigned j = 0; i < sizeof(model)-1 && data.raw[data.desc.ProductIdOffset+j]; i++, j++)
+ model[i] = data.raw[data.desc.ProductIdOffset+j];
+ }
+
+ while (i > 0 && model[i-1] == ' ')
+ i--;
+ model[i] = 0;
+ copy_swapped(id->model, model, sizeof(id->model));
+
+ if (data.desc.ProductRevisionOffset)
+ copy_swapped(id->fw_rev, data.raw+data.desc.ProductRevisionOffset, sizeof(id->fw_rev));
+
+ id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
+ id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
+ return 0;
+}
+
+// Get Serial Number in IDENTIFY from WMI
+static bool get_serial_from_wmi(int drive, ata_identify_device * id)
+{
+ bool debug = (ata_debugmode > 1);
+
+ wbem_services ws;
+ if (!ws.connect()) {
+ if (debug)
+ pout("WMI connect failed\n");
+ return false;
+ }
+
+ wbem_object wo;
+ if (!ws.query1(wo, "SELECT Model,SerialNumber FROM Win32_DiskDrive WHERE "
+ "DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", drive))
+ return false;
+
+ std::string serial = wo.get_str("SerialNumber");
+ if (debug)
+ pout(" WMI:PhysicalDrive%d: \"%s\", S/N:\"%s\"\n", drive, wo.get_str("Model").c_str(), serial.c_str());
+
+ copy_swapped(id->serial_no, serial.c_str(), sizeof(id->serial_no));
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// USB ID detection using WMI
+
+// Get USB ID for a physical or logical drive number
+static bool get_usb_id(int phydrive, int logdrive,
+ unsigned short & vendor_id,
+ unsigned short & product_id)
+{
+ bool debug = (scsi_debugmode > 1);
+
+ wbem_services ws;
+ if (!ws.connect()) {
+ if (debug)
+ pout("WMI connect failed\n");
+ return false;
+ }
+
+ // Get device name
+ std::string name;
+
+ wbem_object wo;
+ if (0 <= logdrive && logdrive <= 'Z'-'A') {
+ // Drive letter -> Partition info
+ if (!ws.query1(wo, "ASSOCIATORS OF {Win32_LogicalDisk.DeviceID=\"%c:\"} WHERE ResultClass = Win32_DiskPartition",
+ 'A'+logdrive))
+ return false;
+
+ std::string partid = wo.get_str("DeviceID");
+ if (debug)
+ pout("%c: --> \"%s\" -->\n", 'A'+logdrive, partid.c_str());
+
+ // Partition ID -> Physical drive info
+ if (!ws.query1(wo, "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\"%s\"} WHERE ResultClass = Win32_DiskDrive",
+ partid.c_str()))
+ return false;
+
+ name = wo.get_str("Model");
+ if (debug)
+ pout("%s --> \"%s\":\n", wo.get_str("DeviceID").c_str(), name.c_str());
+ }
+
+ else if (phydrive >= 0) {
+ // Physical drive number -> Physical drive info
+ if (!ws.query1(wo, "SELECT Model FROM Win32_DiskDrive WHERE DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", phydrive))
+ return false;
+
+ name = wo.get_str("Model");
+ if (debug)
+ pout("\\.\\\\PHYSICALDRIVE%d --> \"%s\":\n", phydrive, name.c_str());
+ }
+ else
+ return false;
+
+
+ // Get USB_CONTROLLER -> DEVICE associations
+ wbem_enumerator we;
+ if (!ws.query(we, "SELECT Antecedent,Dependent FROM Win32_USBControllerDevice"))
+ return false;
+
+ unsigned short usb_venid = 0, prev_usb_venid = 0;
+ unsigned short usb_proid = 0, prev_usb_proid = 0;
+ std::string prev_usb_ant;
+ std::string prev_ant, ant, dep;
+
+ const regular_expression regex("^.*PnPEntity\\.DeviceID=\"([^\"]*)\"");
+
+ while (we.next(wo)) {
+ prev_ant = ant;
+ // Find next 'USB_CONTROLLER, DEVICE' pair
+ ant = wo.get_str("Antecedent");
+ dep = wo.get_str("Dependent");
+
+ if (debug && ant != prev_ant)
+ pout(" %s:\n", ant.c_str());
+
+ // Extract DeviceID
+ regular_expression::match_range match[2];
+ if (!(regex.execute(dep.c_str(), 2, match) && match[1].rm_so >= 0)) {
+ if (debug)
+ pout(" | (\"%s\")\n", dep.c_str());
+ continue;
+ }
+
+ std::string devid(dep.c_str()+match[1].rm_so, match[1].rm_eo-match[1].rm_so);
+
+ if (str_starts_with(devid, "USB\\\\VID_")) {
+ // USB bridge entry, save CONTROLLER, ID
+ int nc = -1;
+ if (!(sscanf(devid.c_str(), "USB\\\\VID_%4hx&PID_%4hx%n",
+ &prev_usb_venid, &prev_usb_proid, &nc) == 2 && nc == 9+4+5+4)) {
+ prev_usb_venid = prev_usb_proid = 0;
+ }
+ prev_usb_ant = ant;
+ if (debug)
+ pout(" +-> \"%s\" [0x%04x:0x%04x]\n", devid.c_str(), prev_usb_venid, prev_usb_proid);
+ }
+ else if (str_starts_with(devid, "USBSTOR\\\\") || str_starts_with(devid, "SCSI\\\\")) {
+ // USBSTORage or SCSI device found
+ if (debug)
+ pout(" +--> \"%s\"\n", devid.c_str());
+
+ // Retrieve name
+ wbem_object wo2;
+ if (!ws.query1(wo2, "SELECT Name FROM Win32_PnPEntity WHERE DeviceID=\"%s\"", devid.c_str()))
+ continue;
+ std::string name2 = wo2.get_str("Name");
+
+ // Continue if not name of physical disk drive
+ if (name2 != name) {
+ if (debug)
+ pout(" +---> (\"%s\")\n", name2.c_str());
+ continue;
+ }
+
+ // Fail if previous USB bridge is associated to other controller or ID is unknown
+ if (!(ant == prev_usb_ant && prev_usb_venid)) {
+ if (debug)
+ pout(" +---> \"%s\" (Error: No USB bridge found)\n", name2.c_str());
+ return false;
+ }
+
+ // Handle multiple devices with same name
+ if (usb_venid) {
+ // Fail if multiple devices with same name have different USB bridge types
+ if (!(usb_venid == prev_usb_venid && usb_proid == prev_usb_proid)) {
+ if (debug)
+ pout(" +---> \"%s\" (Error: More than one USB ID found)\n", name2.c_str());
+ return false;
+ }
+ }
+
+ // Found
+ usb_venid = prev_usb_venid;
+ usb_proid = prev_usb_proid;
+ if (debug)
+ pout(" +===> \"%s\" [0x%04x:0x%04x]\n", name2.c_str(), usb_venid, usb_proid);
+
+ // Continue to check for duplicate names ...
+ }
+ else {
+ if (debug)
+ pout(" | \"%s\"\n", devid.c_str());
+ }
+ }
+
+ if (!usb_venid)
+ return false;
+
+ vendor_id = usb_venid;
+ product_id = usb_proid;
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Call GetDevicePowerState()
+// returns: 1=active, 0=standby, -1=error
+// (This would also work for SCSI drives)
+
+static int get_device_power_state(HANDLE hdevice)
+{
+ BOOL state = TRUE;
+ if (!GetDevicePowerState(hdevice, &state)) {
+ long err = GetLastError();
+ if (ata_debugmode)
+ pout(" GetDevicePowerState() failed, Error=%ld\n", err);
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ // TODO: This may not work as expected on transient errors,
+ // because smartd interprets -1 as SLEEP mode regardless of errno.
+ return -1;
+ }
+
+ if (ata_debugmode > 1)
+ pout(" GetDevicePowerState() succeeded, state=%d\n", state);
+ return state;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_ata_device
+
+class win_ata_device
+: public /*implements*/ ata_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual ~win_ata_device() throw();
+
+ virtual bool open();
+
+ virtual bool is_powered_down();
+
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+
+ virtual bool ata_identify_is_cached() const;
+
+private:
+ bool open(bool query_device);
+
+ bool open(int phydrive, int logdrive, const char * options, int port, bool query_device);
+
+ std::string m_options;
+ bool m_usr_options; // options set by user?
+ bool m_admin; // open with admin access?
+ int m_phydrive; // PhysicalDriveN or -1
+ bool m_id_is_cached; // ata_identify_is_cached() return value.
+ bool m_is_3ware; // LSI/3ware controller detected?
+ int m_port; // LSI/3ware port
+ int m_smartver_state;
+};
+
+
+win_ata_device::win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "ata", req_type),
+ m_usr_options(false),
+ m_admin(false),
+ m_phydrive(-1),
+ m_id_is_cached(false),
+ m_is_3ware(false),
+ m_port(-1),
+ m_smartver_state(0)
+{
+}
+
+win_ata_device::~win_ata_device() throw()
+{
+}
+
+// Get default ATA device options
+
+static const char * ata_get_def_options()
+{
+ return "pasifm"; // GetDevicePowerState(), ATA_, SMART_*, IDE_PASS_THROUGH,
+ // STORAGE_*, SCSI_MINIPORT_*
+}
+
+// Open ATA device
+
+bool win_ata_device::open()
+{
+ // Open device for r/w operations
+ return open(false);
+}
+
+bool win_ata_device::open(bool query_device)
+{
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // [sh]d[a-z]([a-z])?(:[saicmfp]+)? => Physical drive 0-701, with options
+ char drive[2+1] = "", options[8+1] = ""; int n1 = -1, n2 = -1;
+ if ( sscanf(name, "%*[sh]d%2[a-z]%n:%6[saimfp]%n", drive, &n1, options, &n2) >= 1
+ && ((n1 == len && !options[0]) || n2 == len) ) {
+ return open(sdxy_to_phydrive(drive), -1, options, -1, query_device);
+ }
+ // [sh]d[a-z],N(:[saicmfp3]+)? => Physical drive 0-701, RAID port N, with options
+ drive[0] = 0; options[0] = 0; n1 = -1; n2 = -1;
+ unsigned port = ~0;
+ if ( sscanf(name, "%*[sh]d%2[a-z],%u%n:%7[saimfp3]%n", drive, &port, &n1, options, &n2) >= 2
+ && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) {
+ return open(sdxy_to_phydrive(drive), -1, options, port, query_device);
+ }
+ // pd<m>,N => Physical drive <m>, RAID port N
+ int phydrive = -1; port = ~0; n1 = -1; n2 = -1;
+ if ( sscanf(name, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1
+ && phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) {
+ return open(phydrive, -1, "", (int)port, query_device);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ return open(-1, logdrive, "", -1, query_device);
+ }
+
+ return set_err(EINVAL);
+}
+
+
+bool win_ata_device::open(int phydrive, int logdrive, const char * options, int port, bool query_device)
+{
+ m_phydrive = -1;
+ char devpath[30];
+ if (0 <= phydrive && phydrive <= 255)
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", (m_phydrive = phydrive));
+ else if (0 <= logdrive && logdrive <= 'Z'-'A')
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\%c:", 'A'+logdrive);
+ else
+ return set_err(ENOENT);
+
+ // Open device
+ HANDLE h = INVALID_HANDLE_VALUE;
+ if (!(*options && !options[strspn(options, "fp")]) && !query_device) {
+ // Open with admin rights
+ m_admin = true;
+ h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, 0);
+ }
+ if (h == INVALID_HANDLE_VALUE) {
+ // Open without admin rights
+ m_admin = false;
+ h = CreateFileA(devpath, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, 0);
+ }
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+ set_fh(h);
+
+ // Warn once if admin rights are missing
+ if (!m_admin && !query_device) {
+ static bool noadmin_warning = false;
+ if (!noadmin_warning) {
+ pout("Warning: Limited functionality due to missing admin rights\n");
+ noadmin_warning = true;
+ }
+ }
+
+ if (ata_debugmode > 1)
+ pout("%s: successfully opened%s\n", devpath, (!m_admin ? " (without admin rights)" :""));
+
+ m_usr_options = false;
+ if (*options) {
+ // Save user options
+ m_options = options; m_usr_options = true;
+ }
+ else if (port >= 0)
+ // RAID: SMART_* and SCSI_MINIPORT
+ m_options = "s3";
+ else {
+ // Set default options according to Windows version
+ static const char * def_options = ata_get_def_options();
+ m_options = def_options;
+ }
+
+ // SMART_GET_VERSION may spin up disk, so delay until first real SMART_* call
+ m_port = port;
+ if (port < 0)
+ return true;
+
+ // 3ware RAID: Get port map
+ GETVERSIONINPARAMS_EX vers_ex;
+ int devmap = smart_get_version(h, &vers_ex);
+
+ // 3ware RAID if vendor id present
+ m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE);
+
+ unsigned portmap = 0;
+ if (port >= 0 && devmap >= 0) {
+ // 3ware RAID: check vendor id
+ if (!m_is_3ware) {
+ pout("SMART_GET_VERSION returns unknown Identifier = 0x%04x\n"
+ "This is no 3ware 9000 controller or driver has no SMART support.\n",
+ vers_ex.wIdentifier);
+ devmap = -1;
+ }
+ else
+ portmap = vers_ex.dwDeviceMapEx;
+ }
+ if (devmap < 0) {
+ pout("%s: ATA driver has no SMART support\n", devpath);
+ if (!is_permissive()) {
+ close();
+ return set_err(ENOSYS);
+ }
+ }
+ m_smartver_state = 1;
+
+ {
+ // 3ware RAID: update devicemap first
+ if (!update_3ware_devicemap_ioctl(h)) {
+ if ( smart_get_version(h, &vers_ex) >= 0
+ && vers_ex.wIdentifier == SMART_VENDOR_3WARE )
+ portmap = vers_ex.dwDeviceMapEx;
+ }
+ // Check port existence
+ if (!(portmap & (1U << port))) {
+ if (!is_permissive()) {
+ close();
+ return set_err(ENOENT, "%s: Port %d is empty or does not exist", devpath, port);
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Query OS if device is powered up or down.
+bool win_ata_device::is_powered_down()
+{
+ // To check power mode, we open device for query operations only.
+ // Opening for SMART r/w operations can already spin up the disk.
+ bool self_open = !is_open();
+ if (self_open)
+ if (!open(true))
+ return false;
+ int rc = get_device_power_state(get_fh());
+ if (self_open)
+ close();
+ return !rc;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Interface to ATA devices
+bool win_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ // No multi-sector support for now, see above
+ // warning about IOCTL_ATA_PASS_THROUGH
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_output_regs |
+ ata_device::supports_48bit)
+ )
+ return false;
+
+ // 3ware RAID: SMART DISABLE without port number disables SMART functions
+ if ( m_is_3ware && m_port < 0
+ && in.in_regs.command == ATA_SMART_CMD
+ && in.in_regs.features == ATA_SMART_DISABLE)
+ return set_err(ENOSYS, "SMART DISABLE requires 3ware port number");
+
+ // Determine ioctl functions valid for this ATA cmd
+ const char * valid_options = 0;
+
+ switch (in.in_regs.command) {
+ case ATA_IDENTIFY_DEVICE:
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
+ // and SCSI_MINIPORT_* if requested by user
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ case ATA_CHECK_POWER_MODE:
+ // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk
+ valid_options = "pai3";
+ break;
+
+ case ATA_SMART_CMD:
+ switch (in.in_regs.features) {
+ case ATA_SMART_READ_VALUES:
+ case ATA_SMART_READ_THRESHOLDS:
+ case ATA_SMART_AUTOSAVE:
+ case ATA_SMART_ENABLE:
+ case ATA_SMART_DISABLE:
+ case ATA_SMART_AUTO_OFFLINE:
+ // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
+ // and SCSI_MINIPORT_* if requested by user
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ case ATA_SMART_IMMEDIATE_OFFLINE:
+ // SMART_SEND_DRIVE_COMMAND does not support ABORT_SELF_TEST
+ valid_options = (m_usr_options || in.in_regs.lba_low != 127/*ABORT*/ ?
+ "saim3" : "aim3");
+ break;
+
+ case ATA_SMART_READ_LOG_SECTOR:
+ // SMART_RCV_DRIVE_DATA does not support READ_LOG
+ // Try SCSI_MINIPORT also to skip buggy class driver
+ // SMART functions do not support multi sector I/O.
+ if (in.size == 512)
+ valid_options = (m_usr_options ? "saim3" : "aim3");
+ else
+ valid_options = "a";
+ break;
+
+ case ATA_SMART_WRITE_LOG_SECTOR:
+ // ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT
+ // but SCSI_MINIPORT_* only if requested by user and single sector.
+ valid_options = (in.size == 512 && m_usr_options ? "am" : "a");
+ break;
+
+ case ATA_SMART_STATUS:
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ default:
+ // Unknown SMART command, handle below
+ break;
+ }
+ break;
+
+ default:
+ // Other ATA command, handle below
+ break;
+ }
+
+ if (!valid_options) {
+ // No special ATA command found above, select a generic pass through ioctl.
+ if (!( in.direction == ata_cmd_in::no_data
+ || (in.direction == ata_cmd_in::data_in && in.size == 512))
+ || in.in_regs.is_48bit_cmd() )
+ // DATA_OUT, more than one sector, 48-bit command: ATA_PASS_THROUGH only
+ valid_options = "a";
+ else
+ // ATA/IDE_PASS_THROUGH
+ valid_options = "ai";
+ }
+
+ if (!m_admin) {
+ // Restrict to IOCTL_STORAGE_*
+ if (strchr(valid_options, 'f'))
+ valid_options = "f";
+ else if (strchr(valid_options, 'p'))
+ valid_options = "p";
+ else
+ return set_err(ENOSYS, "Function requires admin rights");
+ }
+
+ // Set IDEREGS
+ IDEREGS regs, prev_regs;
+ {
+ const ata_in_regs & lo = in.in_regs;
+ regs.bFeaturesReg = lo.features;
+ regs.bSectorCountReg = lo.sector_count;
+ regs.bSectorNumberReg = lo.lba_low;
+ regs.bCylLowReg = lo.lba_mid;
+ regs.bCylHighReg = lo.lba_high;
+ regs.bDriveHeadReg = lo.device;
+ regs.bCommandReg = lo.command;
+ regs.bReserved = 0;
+ }
+ if (in.in_regs.is_48bit_cmd()) {
+ const ata_in_regs & hi = in.in_regs.prev;
+ prev_regs.bFeaturesReg = hi.features;
+ prev_regs.bSectorCountReg = hi.sector_count;
+ prev_regs.bSectorNumberReg = hi.lba_low;
+ prev_regs.bCylLowReg = hi.lba_mid;
+ prev_regs.bCylHighReg = hi.lba_high;
+ prev_regs.bDriveHeadReg = hi.device;
+ prev_regs.bCommandReg = hi.command;
+ prev_regs.bReserved = 0;
+ }
+
+ // Set data direction
+ int datasize = 0;
+ char * data = 0;
+ switch (in.direction) {
+ case ata_cmd_in::no_data:
+ break;
+ case ata_cmd_in::data_in:
+ datasize = (int)in.size;
+ data = (char *)in.buffer;
+ break;
+ case ata_cmd_in::data_out:
+ datasize = -(int)in.size;
+ data = (char *)in.buffer;
+ break;
+ default:
+ return set_err(EINVAL, "win_ata_device::ata_pass_through: invalid direction=%d",
+ (int)in.direction);
+ }
+
+
+ // Try all valid ioctls in the order specified in m_options
+ bool powered_up = false;
+ bool out_regs_set = false;
+ bool id_is_cached = false;
+ const char * options = m_options.c_str();
+
+ for (int i = 0; ; i++) {
+ char opt = options[i];
+
+ if (!opt) {
+ if (in.in_regs.command == ATA_CHECK_POWER_MODE && powered_up) {
+ // Power up reported by GetDevicePowerState() and no ioctl available
+ // to detect the actual mode of the drive => simulate ATA result ACTIVE/IDLE.
+ regs.bSectorCountReg = 0xff;
+ out_regs_set = true;
+ break;
+ }
+ // No IOCTL found
+ return set_err(ENOSYS);
+ }
+ if (!strchr(valid_options, opt))
+ // Invalid for this command
+ continue;
+
+ errno = 0;
+ assert( datasize == 0 || datasize == 512
+ || (datasize == -512 && strchr("am", opt))
+ || (datasize > 512 && opt == 'a'));
+ int rc;
+ switch (opt) {
+ default: assert(0);
+ case 's':
+ // call SMART_GET_VERSION once for each drive
+ if (m_smartver_state > 1) {
+ rc = -1; errno = ENOSYS;
+ break;
+ }
+ if (!m_smartver_state) {
+ assert(m_port == -1);
+ GETVERSIONINPARAMS_EX vers_ex;
+ if (smart_get_version(get_fh(), &vers_ex) < 0) {
+ if (!failuretest_permissive) {
+ m_smartver_state = 2;
+ rc = -1; errno = ENOSYS;
+ break;
+ }
+ failuretest_permissive--;
+ }
+ else {
+ // 3ware RAID if vendor id present
+ m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE);
+ }
+
+ m_smartver_state = 1;
+ }
+ rc = smart_ioctl(get_fh(), ®s, data, datasize, m_port);
+ out_regs_set = (in.in_regs.features == ATA_SMART_STATUS);
+ id_is_cached = (m_port < 0); // Not cached by 3ware driver
+ break;
+ case 'm':
+ rc = ata_via_scsi_miniport_smart_ioctl(get_fh(), ®s, data, datasize);
+ id_is_cached = (m_port < 0);
+ break;
+ case 'a':
+ rc = ata_pass_through_ioctl(get_fh(), ®s,
+ (in.in_regs.is_48bit_cmd() ? &prev_regs : 0),
+ data, datasize);
+ out_regs_set = true;
+ break;
+ case 'i':
+ rc = ide_pass_through_ioctl(get_fh(), ®s, data, datasize);
+ out_regs_set = true;
+ break;
+ case 'f':
+ if (in.in_regs.command == ATA_IDENTIFY_DEVICE) {
+ ata_identify_device * id = reinterpret_cast<ata_identify_device *>(data);
+ rc = get_identify_from_device_property(get_fh(), id);
+ if (rc == 0 && m_phydrive >= 0)
+ get_serial_from_wmi(m_phydrive, id);
+ id_is_cached = true;
+ }
+ else if (in.in_regs.command == ATA_SMART_CMD) switch (in.in_regs.features) {
+ case ATA_SMART_READ_VALUES:
+ rc = storage_predict_failure_ioctl(get_fh(), data);
+ if (rc > 0)
+ rc = 0;
+ break;
+ case ATA_SMART_ENABLE:
+ rc = 0;
+ break;
+ case ATA_SMART_STATUS:
+ rc = storage_predict_failure_ioctl(get_fh());
+ if (rc == 0) {
+ // Good SMART status
+ out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f;
+ }
+ else if (rc > 0) {
+ // Bad SMART status
+ out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4;
+ rc = 0;
+ }
+ break;
+ default:
+ errno = ENOSYS; rc = -1;
+ }
+ else {
+ errno = ENOSYS; rc = -1;
+ }
+ break;
+ case '3':
+ rc = ata_via_3ware_miniport_ioctl(get_fh(), ®s, data, datasize, m_port);
+ out_regs_set = true;
+ break;
+ case 'p':
+ assert(in.in_regs.command == ATA_CHECK_POWER_MODE && in.size == 0);
+ rc = get_device_power_state(get_fh());
+ if (rc == 0) {
+ // Power down reported by GetDevicePowerState(), using a passthrough ioctl would
+ // spin up the drive => simulate ATA result STANDBY.
+ regs.bSectorCountReg = 0x00;
+ out_regs_set = true;
+ }
+ else if (rc > 0) {
+ // Power up reported by GetDevicePowerState(), but this reflects the actual mode
+ // only if it is selected by the device driver => try a passthrough ioctl to get the
+ // actual mode, if none available simulate ACTIVE/IDLE.
+ powered_up = true;
+ rc = -1; errno = ENOSYS;
+ }
+ break;
+ }
+
+ if (!rc)
+ // Working ioctl found
+ break;
+
+ if (errno != ENOSYS)
+ // Abort on I/O error
+ return set_err(errno);
+
+ out_regs_set = false;
+ // CAUTION: *_ioctl() MUST NOT change "regs" Parameter in the ENOSYS case
+ }
+
+ // Return IDEREGS if set
+ if (out_regs_set) {
+ ata_out_regs & lo = out.out_regs;
+ lo.error = regs.bFeaturesReg;
+ lo.sector_count = regs.bSectorCountReg;
+ lo.lba_low = regs.bSectorNumberReg;
+ lo.lba_mid = regs.bCylLowReg;
+ lo.lba_high = regs.bCylHighReg;
+ lo.device = regs.bDriveHeadReg;
+ lo.status = regs.bCommandReg;
+ if (in.in_regs.is_48bit_cmd()) {
+ ata_out_regs & hi = out.out_regs.prev;
+ hi.sector_count = prev_regs.bSectorCountReg;
+ hi.lba_low = prev_regs.bSectorNumberReg;
+ hi.lba_mid = prev_regs.bCylLowReg;
+ hi.lba_high = prev_regs.bCylHighReg;
+ }
+ }
+
+ if ( in.in_regs.command == ATA_IDENTIFY_DEVICE
+ || in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE)
+ // Update ata_identify_is_cached() result according to ioctl used.
+ m_id_is_cached = id_is_cached;
+
+ return true;
+}
+
+// Return true if OS caches the ATA identify sector
+bool win_ata_device::ata_identify_is_cached() const
+{
+ return m_id_is_cached;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// csmi_device
+
+class csmi_device
+: virtual public /*extends*/ smart_device
+{
+public:
+ enum { max_number_of_ports = 32 };
+
+ /// Get bitmask of used ports
+ unsigned get_ports_used();
+
+protected:
+ csmi_device()
+ : smart_device(never_called)
+ { memset(&m_phy_ent, 0, sizeof(m_phy_ent)); }
+
+ typedef signed char port_2_index_map[max_number_of_ports];
+
+ /// Get phy info and port mapping, return #ports or -1 on error
+ int get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i);
+
+ /// Select physical drive
+ bool select_port(int port);
+
+ /// Get info for selected physical drive
+ const CSMI_SAS_PHY_ENTITY & get_phy_ent() const
+ { return m_phy_ent; }
+
+ /// Call platform-specific CSMI ioctl
+ virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz) = 0;
+
+private:
+ CSMI_SAS_PHY_ENTITY m_phy_ent; ///< CSMI info for this phy
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+int csmi_device::get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i)
+{
+ // max_number_of_ports must match CSMI_SAS_PHY_INFO.Phy[] array size
+ typedef char ASSERT_phy_info_size[
+ (int)(sizeof(phy_info.Phy) / sizeof(phy_info.Phy[0])) == max_number_of_ports ? 1 : -1]
+ ATTR_UNUSED;
+
+ // Get driver info to check CSMI support
+ CSMI_SAS_DRIVER_INFO_BUFFER driver_info_buf;
+ memset(&driver_info_buf, 0, sizeof(driver_info_buf));
+ if (!csmi_ioctl(CC_CSMI_SAS_GET_DRIVER_INFO, &driver_info_buf.IoctlHeader, sizeof(driver_info_buf)))
+ return -1;
+
+ if (scsi_debugmode > 1) {
+ const CSMI_SAS_DRIVER_INFO & driver_info = driver_info_buf.Information;
+ pout("CSMI_SAS_DRIVER_INFO:\n");
+ pout(" Name: \"%.81s\"\n", driver_info.szName);
+ pout(" Description: \"%.81s\"\n", driver_info.szDescription);
+ pout(" Revision: %d.%d\n", driver_info.usMajorRevision, driver_info.usMinorRevision);
+ }
+
+ // Get Phy info
+ CSMI_SAS_PHY_INFO_BUFFER phy_info_buf;
+ memset(&phy_info_buf, 0, sizeof(phy_info_buf));
+ if (!csmi_ioctl(CC_CSMI_SAS_GET_PHY_INFO, &phy_info_buf.IoctlHeader, sizeof(phy_info_buf)))
+ return -1;
+
+ phy_info = phy_info_buf.Information;
+
+ if (phy_info.bNumberOfPhys > max_number_of_ports) {
+ set_err(EIO, "CSMI_SAS_PHY_INFO: Bogus NumberOfPhys=%d", phy_info.bNumberOfPhys);
+ return -1;
+ }
+
+ // Create port -> index map
+ // IRST Release
+ // Phy[i].Value 9.x 10.x 14.8 15.2 16.0
+ // ----------------------------------------------------------
+ // bPortIdentifier 0xff 0xff port 0x00 port
+ // Identify.bPhyIdentifier index? index? index index port
+ // Attached.bPhyIdentifier 0x00 0x00 0x00 index 0x00
+ //
+ // Empty ports with hotplug support may appear in Phy[].
+
+ int number_of_ports;
+ for (int mode = 0; ; mode++) {
+ for (int i = 0; i < max_number_of_ports; i++)
+ p2i[i] = -1;
+
+ number_of_ports = 0;
+ bool found = false;
+ for (int i = 0; i < max_number_of_ports; i++) {
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (pe.Identify.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ continue;
+
+ // Try to detect which field contains the actual port number.
+ // Use a bPhyIdentifier or the bPortIdentifier if unique
+ // and not always identical to table index, otherwise use index.
+ int port;
+ switch (mode) {
+ case 0: port = pe.Attached.bPhyIdentifier; break;
+ case 1: port = pe.Identify.bPhyIdentifier; break;
+ case 2: port = pe.bPortIdentifier; break;
+ default: port = i; break;
+ }
+ if (!(port < max_number_of_ports && p2i[port] == -1)) {
+ found = false;
+ break;
+ }
+
+ p2i[port] = i;
+ if (number_of_ports <= port)
+ number_of_ports = port + 1;
+ if (port != i)
+ found = true;
+ }
+
+ if (found || mode > 2)
+ break;
+ }
+
+ if (scsi_debugmode > 1) {
+ pout("CSMI_SAS_PHY_INFO: NumberOfPhys=%d\n", phy_info.bNumberOfPhys);
+ for (int i = 0; i < max_number_of_ports; i++) {
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ const CSMI_SAS_IDENTIFY & id = pe.Identify, & at = pe.Attached;
+ if (id.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ continue;
+
+ int port = -1;
+ for (int p = 0; p < max_number_of_ports && port < 0; p++) {
+ if (p2i[p] == i)
+ port = p;
+ }
+
+ pout("Phy[%d] Port: %d\n", i, port);
+ pout(" Type: 0x%02x, 0x%02x\n", id.bDeviceType, at.bDeviceType);
+ pout(" InitProto: 0x%02x, 0x%02x\n", id.bInitiatorPortProtocol, at.bInitiatorPortProtocol);
+ pout(" TargetProto: 0x%02x, 0x%02x\n", id.bTargetPortProtocol, at.bTargetPortProtocol);
+ pout(" PortIdent: 0x%02x\n", pe.bPortIdentifier);
+ pout(" PhyIdent: 0x%02x, 0x%02x\n", id.bPhyIdentifier, at.bPhyIdentifier);
+ const unsigned char * b = id.bSASAddress;
+ pout(" SASAddress: %02x %02x %02x %02x %02x %02x %02x %02x, ",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
+ b = at.bSASAddress;
+ pout( "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
+ }
+ }
+
+ return number_of_ports;
+}
+
+unsigned csmi_device::get_ports_used()
+{
+ CSMI_SAS_PHY_INFO phy_info;
+ port_2_index_map p2i;
+ int number_of_ports = get_phy_info(phy_info, p2i);
+ if (number_of_ports < 0)
+ return 0;
+
+ unsigned ports_used = 0;
+ for (int p = 0; p < max_number_of_ports; p++) {
+ int i = p2i[p];
+ if (i < 0)
+ continue;
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (pe.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ continue;
+ switch (pe.Attached.bTargetPortProtocol) {
+ case CSMI_SAS_PROTOCOL_SATA:
+ case CSMI_SAS_PROTOCOL_STP:
+ break;
+ default:
+ continue;
+ }
+
+ ports_used |= (1U << p);
+ }
+
+ return ports_used;
+}
+
+bool csmi_device::select_port(int port)
+{
+ if (!(0 <= port && port < max_number_of_ports))
+ return set_err(EINVAL, "Invalid port number %d", port);
+
+ CSMI_SAS_PHY_INFO phy_info;
+ port_2_index_map p2i;
+ int number_of_ports = get_phy_info(phy_info, p2i);
+ if (number_of_ports < 0)
+ return false;
+
+ int port_index = p2i[port];
+ if (port_index < 0) {
+ if (port < number_of_ports)
+ return set_err(ENOENT, "Port %d is disabled", port);
+ else
+ return set_err(ENOENT, "Port %d does not exist (#ports: %d)", port,
+ number_of_ports);
+ }
+
+ const CSMI_SAS_PHY_ENTITY & phy_ent = phy_info.Phy[port_index];
+ if (phy_ent.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ return set_err(ENOENT, "No device on port %d", port);
+
+ switch (phy_ent.Attached.bTargetPortProtocol) {
+ case CSMI_SAS_PROTOCOL_SATA:
+ case CSMI_SAS_PROTOCOL_STP:
+ break;
+ default:
+ return set_err(ENOENT, "No SATA device on port %d (protocol: %d)",
+ port, phy_ent.Attached.bTargetPortProtocol);
+ }
+
+ m_phy_ent = phy_ent;
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// csmi_ata_device
+
+class csmi_ata_device
+: virtual public /*extends*/ csmi_device,
+ virtual public /*implements*/ ata_device
+{
+public:
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
+
+protected:
+ csmi_ata_device()
+ : smart_device(never_called) { }
+};
+
+
+//////////////////////////////////////////////////////////////////////
+
+bool csmi_ata_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,
+ "CSMI")
+ )
+ return false;
+
+ // Create buffer with appropriate size
+ raw_buffer pthru_raw_buf(sizeof(CSMI_SAS_STP_PASSTHRU_BUFFER) + in.size);
+ CSMI_SAS_STP_PASSTHRU_BUFFER * pthru_buf = (CSMI_SAS_STP_PASSTHRU_BUFFER *)pthru_raw_buf.data();
+
+ // Set addresses from Phy info
+ CSMI_SAS_STP_PASSTHRU & pthru = pthru_buf->Parameters;
+ const CSMI_SAS_PHY_ENTITY & phy_ent = get_phy_ent();
+ pthru.bPhyIdentifier = phy_ent.Identify.bPhyIdentifier;
+ pthru.bPortIdentifier = phy_ent.bPortIdentifier;
+ memcpy(pthru.bDestinationSASAddress, phy_ent.Attached.bSASAddress,
+ sizeof(pthru.bDestinationSASAddress));
+ pthru.bConnectionRate = CSMI_SAS_LINK_RATE_NEGOTIATED;
+
+ // Set transfer mode
+ switch (in.direction) {
+ case ata_cmd_in::no_data:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_UNSPECIFIED;
+ break;
+ case ata_cmd_in::data_in:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_READ;
+ pthru.uDataLength = in.size;
+ break;
+ case ata_cmd_in::data_out:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_WRITE;
+ pthru.uDataLength = in.size;
+ memcpy(pthru_buf->bDataBuffer, in.buffer, in.size);
+ break;
+ default:
+ return set_err(EINVAL, "csmi_ata_device::ata_pass_through: invalid direction=%d",
+ (int)in.direction);
+ }
+
+ // Set host-to-device FIS
+ {
+ unsigned char * fis = pthru.bCommandFIS;
+ const ata_in_regs & lo = in.in_regs;
+ const ata_in_regs & hi = in.in_regs.prev;
+ fis[ 0] = 0x27; // Type: host-to-device FIS
+ fis[ 1] = 0x80; // Bit7: Update command register
+ fis[ 2] = lo.command;
+ fis[ 3] = lo.features;
+ fis[ 4] = lo.lba_low;
+ fis[ 5] = lo.lba_mid;
+ fis[ 6] = lo.lba_high;
+ fis[ 7] = lo.device;
+ fis[ 8] = hi.lba_low;
+ fis[ 9] = hi.lba_mid;
+ fis[10] = hi.lba_high;
+ fis[11] = hi.features;
+ fis[12] = lo.sector_count;
+ fis[13] = hi.sector_count;
+ }
+
+ // Call ioctl
+ if (!csmi_ioctl(CC_CSMI_SAS_STP_PASSTHRU, &pthru_buf->IoctlHeader, pthru_raw_buf.size())) {
+ return false;
+ }
+
+ // Get device-to-host FIS
+ {
+ const unsigned char * fis = pthru_buf->Status.bStatusFIS;
+ ata_out_regs & lo = out.out_regs;
+ lo.status = fis[ 2];
+ lo.error = fis[ 3];
+ lo.lba_low = fis[ 4];
+ lo.lba_mid = fis[ 5];
+ lo.lba_high = fis[ 6];
+ lo.device = fis[ 7];
+ lo.sector_count = fis[12];
+ if (in.in_regs.is_48bit_cmd()) {
+ ata_out_regs & hi = out.out_regs.prev;
+ hi.lba_low = fis[ 8];
+ hi.lba_mid = fis[ 9];
+ hi.lba_high = fis[10];
+ hi.sector_count = fis[13];
+ }
+ }
+
+ // Get data
+ if (in.direction == ata_cmd_in::data_in)
+ // TODO: Check ptru_buf->Status.uDataBytes
+ memcpy(in.buffer, pthru_buf->bDataBuffer, in.size);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// win_csmi_device
+
+class win_csmi_device
+: public /*implements*/ csmi_ata_device
+{
+public:
+ win_csmi_device(smart_interface * intf, const char * dev_name,
+ const char * req_type);
+
+ virtual ~win_csmi_device() throw();
+
+ virtual bool open();
+
+ virtual bool close();
+
+ virtual bool is_open() const;
+
+ bool open_scsi();
+
+protected:
+ virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz);
+
+private:
+ HANDLE m_fh; ///< Controller device handle
+ int m_port; ///< Port number
+};
+
+
+//////////////////////////////////////////////////////////////////////
+
+win_csmi_device::win_csmi_device(smart_interface * intf, const char * dev_name,
+ const char * req_type)
+: smart_device(intf, dev_name, "ata", req_type),
+ m_fh(INVALID_HANDLE_VALUE), m_port(-1)
+{
+}
+
+win_csmi_device::~win_csmi_device() throw()
+{
+ if (m_fh != INVALID_HANDLE_VALUE)
+ CloseHandle(m_fh);
+}
+
+bool win_csmi_device::is_open() const
+{
+ return (m_fh != INVALID_HANDLE_VALUE);
+}
+
+bool win_csmi_device::close()
+{
+ if (m_fh == INVALID_HANDLE_VALUE)
+ return true;
+ BOOL rc = CloseHandle(m_fh);
+ m_fh = INVALID_HANDLE_VALUE;
+ return !!rc;
+}
+
+
+bool win_csmi_device::open_scsi()
+{
+ // Parse name
+ unsigned contr_no = ~0, port = ~0; int nc = -1;
+ const char * name = skipdev(get_dev_name());
+ if (!( sscanf(name, "csmi%u,%u%n", &contr_no, &port, &nc) >= 0
+ && nc == (int)strlen(name) && contr_no <= 9 && port < 32) )
+ return set_err(EINVAL);
+
+ // Open controller handle
+ char devpath[30];
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%u:", contr_no);
+
+ HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+
+ if (scsi_debugmode > 1)
+ pout(" %s: successfully opened\n", devpath);
+
+ m_fh = h;
+ m_port = port;
+ return true;
+}
+
+
+bool win_csmi_device::open()
+{
+ if (!open_scsi())
+ return false;
+
+ // Get Phy info for this drive
+ if (!select_port(m_port)) {
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+
+bool win_csmi_device::csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz)
+{
+ // Determine signature
+ const char * sig;
+ switch (code) {
+ case CC_CSMI_SAS_GET_DRIVER_INFO:
+ sig = CSMI_ALL_SIGNATURE; break;
+ case CC_CSMI_SAS_GET_PHY_INFO:
+ case CC_CSMI_SAS_STP_PASSTHRU:
+ sig = CSMI_SAS_SIGNATURE; break;
+ default:
+ return set_err(ENOSYS, "Unknown CSMI code=%u", code);
+ }
+
+ // Set header
+ csmi_buffer->HeaderLength = sizeof(IOCTL_HEADER);
+ strncpy((char *)csmi_buffer->Signature, sig, sizeof(csmi_buffer->Signature));
+ csmi_buffer->Timeout = CSMI_SAS_TIMEOUT;
+ csmi_buffer->ControlCode = code;
+ csmi_buffer->ReturnCode = 0;
+ csmi_buffer->Length = csmi_bufsiz - sizeof(IOCTL_HEADER);
+
+ // Call function
+ DWORD num_out = 0;
+ if (!DeviceIoControl(m_fh, IOCTL_SCSI_MINIPORT,
+ csmi_buffer, csmi_bufsiz, csmi_buffer, csmi_bufsiz, &num_out, (OVERLAPPED*)0)) {
+ long err = GetLastError();
+ if (scsi_debugmode)
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, Error=%ld\n", code, err);
+ if ( err == ERROR_INVALID_FUNCTION
+ || err == ERROR_NOT_SUPPORTED
+ || err == ERROR_DEV_NOT_EXIST)
+ return set_err(ENOSYS, "CSMI is not supported (Error=%ld)", err);
+ else
+ return set_err(EIO, "CSMI(%u) failed with Error=%ld", code, err);
+ }
+
+ // Check result
+ if (csmi_buffer->ReturnCode) {
+ if (scsi_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, ReturnCode=%u\n",
+ code, (unsigned)csmi_buffer->ReturnCode);
+ }
+ return set_err(EIO, "CSMI(%u) failed with ReturnCode=%u", code, (unsigned)csmi_buffer->ReturnCode);
+ }
+
+ if (scsi_debugmode > 1)
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) succeeded, bytes returned: %u\n", code, (unsigned)num_out);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// win_tw_cli_device
+
+// Routines for pseudo device /dev/tw_cli/*
+// Parses output of 3ware "tw_cli /cx/py show all" or 3DM SMART data window
+// TODO: This is OS independent
+
+class win_tw_cli_device
+: public /*implements*/ ata_device_with_command_set
+{
+public:
+ win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual bool is_open() const;
+
+ virtual bool open();
+
+ virtual bool close();
+
+protected:
+ virtual int ata_command_interface(smart_command_set command, int select, char * data);
+
+private:
+ bool m_ident_valid, m_smart_valid;
+ ata_identify_device m_ident_buf;
+ ata_smart_values m_smart_buf;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_tw_cli_device::win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "tw_cli", req_type),
+ m_ident_valid(false), m_smart_valid(false)
+{
+ memset(&m_ident_buf, 0, sizeof(m_ident_buf));
+ memset(&m_smart_buf, 0, sizeof(m_smart_buf));
+}
+
+
+bool win_tw_cli_device::is_open() const
+{
+ return (m_ident_valid || m_smart_valid);
+}
+
+
+// Get clipboard data
+
+static int get_clipboard(char * data, int datasize)
+{
+ if (!OpenClipboard(NULL))
+ return -1;
+ HANDLE h = GetClipboardData(CF_TEXT);
+ if (!h) {
+ CloseClipboard();
+ return 0;
+ }
+ const void * p = GlobalLock(h);
+ int n = GlobalSize(h);
+ if (n > datasize)
+ n = datasize;
+ memcpy(data, p, n);
+ GlobalFree(h);
+ CloseClipboard();
+ return n;
+}
+
+
+static const char * findstr(const char * str, const char * sub)
+{
+ const char * s = strstr(str, sub);
+ return (s ? s+strlen(sub) : "");
+}
+
+
+bool win_tw_cli_device::open()
+{
+ m_ident_valid = m_smart_valid = false;
+ const char * name = skipdev(get_dev_name());
+ // Read tw_cli or 3DM browser output into buffer
+ char buffer[4096];
+ int size = -1, n1 = -1, n2 = -1;
+ if (!strcmp(name, "tw_cli/clip")) { // read clipboard
+ size = get_clipboard(buffer, sizeof(buffer));
+ }
+ else if (!strcmp(name, "tw_cli/stdin")) { // read stdin
+ size = fread(buffer, 1, sizeof(buffer), stdin);
+ }
+ else if (sscanf(name, "tw_cli/%nc%*u/p%*u%n", &n1, &n2) >= 0 && n2 == (int)strlen(name)) {
+ // tw_cli/cx/py => read output from "tw_cli /cx/py show all"
+ char cmd[100];
+ snprintf(cmd, sizeof(cmd), "tw_cli /%s show all", name+n1);
+ if (ata_debugmode > 1)
+ pout("%s: Run: \"%s\"\n", name, cmd);
+ FILE * f = popen(cmd, "rb");
+ if (f) {
+ size = fread(buffer, 1, sizeof(buffer), f);
+ pclose(f);
+ }
+ }
+ else {
+ return set_err(EINVAL);
+ }
+
+ if (ata_debugmode > 1)
+ pout("%s: Read %d bytes\n", name, size);
+ if (size <= 0)
+ return set_err(ENOENT);
+ if (size >= (int)sizeof(buffer))
+ return set_err(EIO);
+
+ buffer[size] = 0;
+ if (ata_debugmode > 1)
+ pout("[\n%.100s%s\n]\n", buffer, (size>100?"...":""));
+
+ // Fake identify sector
+ ASSERT_SIZEOF(ata_identify_device, 512);
+ ata_identify_device * id = &m_ident_buf;
+ memset(id, 0, sizeof(*id));
+ copy_swapped(id->model , findstr(buffer, " Model = " ), sizeof(id->model));
+ copy_swapped(id->fw_rev , findstr(buffer, " Firmware Version = "), sizeof(id->fw_rev));
+ copy_swapped(id->serial_no, findstr(buffer, " Serial = " ), sizeof(id->serial_no));
+ unsigned long nblocks = 0; // "Capacity = N.N GB (N Blocks)"
+ sscanf(findstr(buffer, "Capacity = "), "%*[^(\r\n](%lu", &nblocks);
+ if (nblocks) {
+ id->words047_079[49-47] = 0x0200; // size valid
+ id->words047_079[60-47] = (unsigned short)(nblocks ); // secs_16
+ id->words047_079[61-47] = (unsigned short)(nblocks>>16); // secs_32
+ }
+ id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
+ id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
+
+ // Parse smart data hex dump
+ const char * s = findstr(buffer, "Drive Smart Data:");
+ if (!*s)
+ s = findstr(buffer, "Drive SMART Data:"); // tw_cli from 9.5.x
+ if (!*s) {
+ s = findstr(buffer, "S.M.A.R.T. (Controller"); // from 3DM browser window
+ if (*s) {
+ const char * s1 = findstr(s, "<td class"); // html version
+ if (*s1)
+ s = s1;
+ s += strcspn(s, "\r\n");
+ }
+ else
+ s = buffer; // try raw hex dump without header
+ }
+ unsigned char * sd = (unsigned char *)&m_smart_buf;
+ int i = 0;
+ for (;;) {
+ unsigned x = ~0; int n = -1;
+ if (!(sscanf(s, "%x %n", &x, &n) == 1 && !(x & ~0xff)))
+ break;
+ sd[i] = (unsigned char)x;
+ if (!(++i < 512 && n > 0))
+ break;
+ s += n;
+ if (*s == '<') // "<br>"
+ s += strcspn(s, "\r\n");
+ }
+ if (i < 512) {
+ if (!id->model[1]) {
+ // No useful data found
+ char * err = strstr(buffer, "Error:");
+ if (!err)
+ err = strstr(buffer, "error :");
+ if (err && (err = strchr(err, ':'))) {
+ // Show tw_cli error message
+ err++;
+ err[strcspn(err, "\r\n")] = 0;
+ return set_err(EIO, "%s", err);
+ }
+ return set_err(EIO);
+ }
+ sd = 0;
+ }
+
+ m_ident_valid = true;
+ m_smart_valid = !!sd;
+ return true;
+}
+
+
+bool win_tw_cli_device::close()
+{
+ m_ident_valid = m_smart_valid = false;
+ return true;
+}
+
+
+int win_tw_cli_device::ata_command_interface(smart_command_set command, int /*select*/, char * data)
+{
+ switch (command) {
+ case IDENTIFY:
+ if (!m_ident_valid)
+ break;
+ memcpy(data, &m_ident_buf, 512);
+ return 0;
+ case READ_VALUES:
+ if (!m_smart_valid)
+ break;
+ memcpy(data, &m_smart_buf, 512);
+ return 0;
+ case ENABLE:
+ case STATUS:
+ case STATUS_CHECK: // Fake "good" SMART status
+ return 0;
+ default:
+ break;
+ }
+ // Arrive here for all unsupported commands
+ set_err(ENOSYS);
+ return -1;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_scsi_device
+// SPT Interface (for SCSI devices and ATA devices behind SATLs)
+
+class win_scsi_device
+: public /*implements*/ scsi_device,
+ virtual public /*extends*/ win_smart_device
+{
+public:
+ win_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual bool open();
+
+ virtual bool scsi_pass_through(scsi_cmnd_io * iop);