+ 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);
+
+private:
+ bool open(int pd_num, int ld_num, int tape_num, int sub_addr);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_scsi_device::win_scsi_device(smart_interface * intf,
+ const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "scsi", req_type)
+{
+}
+
+bool win_scsi_device::open()
+{
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // sd[a-z]([a-z])?,N => Physical drive 0-701, RAID port N
+ char drive[2+1] = ""; int sub_addr = -1; int n1 = -1; int n2 = -1;
+ if ( sscanf(name, "sd%2[a-z]%n,%d%n", drive, &n1, &sub_addr, &n2) >= 1
+ && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0)) ) {
+ return open(sdxy_to_phydrive(drive), -1, -1, sub_addr);
+ }
+ // pd<m>,N => Physical drive <m>, RAID port N
+ int pd_num = -1; sub_addr = -1; n1 = -1; n2 = -1;
+ if ( sscanf(name, "pd%d%n,%d%n", &pd_num, &n1, &sub_addr, &n2) >= 1
+ && pd_num >= 0 && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0))) {
+ return open(pd_num, -1, -1, sub_addr);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ return open(-1, logdrive, -1, -1);
+ }
+ // n?st<m> => tape drive <m> (same names used in Cygwin's /dev emulation)
+ int tape_num = -1; n1 = -1;
+ if (sscanf(name, "st%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+ tape_num = -1; n1 = -1;
+ if (sscanf(name, "nst%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+ // tape<m> => tape drive <m>
+ tape_num = -1; n1 = -1;
+ if (sscanf(name, "tape%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+
+ return set_err(EINVAL);
+}
+
+bool win_scsi_device::open(int pd_num, int ld_num, int tape_num, int /*sub_addr*/)
+{
+ char b[128];
+ b[sizeof(b) - 1] = '\0';
+ if (pd_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\PhysicalDrive%d", pd_num);
+ else if (ld_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\%c:", 'A' + ld_num);
+ else if (tape_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\TAPE%d", tape_num);
+ else {
+ set_err(EINVAL);
+ return false;
+ }
+
+ // Open device
+ HANDLE h = CreateFileA(b, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ set_err(ENODEV, "%s: Open failed, Error=%u", b, (unsigned)GetLastError());
+ return false;
+ }
+ set_fh(h);
+ return true;
+}
+
+
+typedef struct {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[64];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+// Issue command via IOCTL_SCSI_PASS_THROUGH instead of *_DIRECT.
+// Used if DataTransferLength not supported by *_DIRECT.
+static long scsi_pass_through_indirect(HANDLE h,
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER * sbd)
+{
+ struct SCSI_PASS_THROUGH_WITH_BUFFERS {
+ SCSI_PASS_THROUGH spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[sizeof(sbd->ucSenseBuf)];
+ UCHAR ucDataBuf[512];
+ };
+
+ SCSI_PASS_THROUGH_WITH_BUFFERS sb;
+ memset(&sb, 0, sizeof(sb));
+
+ // DATA_OUT not implemented yet
+ if (!( sbd->spt.DataIn == SCSI_IOCTL_DATA_IN
+ && sbd->spt.DataTransferLength <= sizeof(sb.ucDataBuf)))
+ return ERROR_INVALID_PARAMETER;
+
+ sb.spt.Length = sizeof(sb.spt);
+ sb.spt.CdbLength = sbd->spt.CdbLength;
+ memcpy(sb.spt.Cdb, sbd->spt.Cdb, sizeof(sb.spt.Cdb));
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ sb.spt.DataIn = sbd->spt.DataIn;
+ sb.spt.DataTransferLength = sbd->spt.DataTransferLength;
+ sb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
+ sb.spt.TimeOutValue = sbd->spt.TimeOutValue;
+
+ DWORD num_out;
+ if (!DeviceIoControl(h, IOCTL_SCSI_PASS_THROUGH,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ return GetLastError();
+
+ sbd->spt.ScsiStatus = sb.spt.ScsiStatus;
+ if (sb.spt.ScsiStatus & SCSI_STATUS_CHECK_CONDITION)
+ memcpy(sbd->ucSenseBuf, sb.ucSenseBuf, sizeof(sbd->ucSenseBuf));
+
+ sbd->spt.DataTransferLength = sb.spt.DataTransferLength;
+ if (sbd->spt.DataIn == SCSI_IOCTL_DATA_IN && sb.spt.DataTransferLength > 0)
+ memcpy(sbd->spt.DataBuffer, sb.ucDataBuf, sb.spt.DataTransferLength);
+ return 0;
+}
+
+
+// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c
+bool win_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop)
+{
+ int report = scsi_debugmode; // TODO
+
+ if (report > 0) {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+
+ np = scsi_get_opcode_name(ucp[0]);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("%s", buff);
+ }
+
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
+ if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
+ set_err(EINVAL, "cmnd_len too large");
+ return false;
+ }
+
+ memset(&sb, 0, sizeof(sb));
+ sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ sb.spt.CdbLength = iop->cmnd_len;
+ memcpy(sb.spt.Cdb, iop->cmnd, iop->cmnd_len);
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
+ sb.spt.TimeOutValue = (iop->timeout ? iop->timeout : 60);
+
+ bool direct = true;
+ switch (iop->dxfer_dir) {
+ case DXFER_NONE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case DXFER_FROM_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ // IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
+ // transfers (needed for SMART STATUS check of JMicron USB bridges)
+ if (sb.spt.DataTransferLength == 1)
+ direct = false;
+ break;
+ case DXFER_TO_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ break;
+ default:
+ set_err(EINVAL, "bad dxfer_dir");
+ return false;
+ }
+
+ long err = 0;
+ if (direct) {
+ DWORD num_out;
+ if (!DeviceIoControl(get_fh(), IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ err = GetLastError();
+ }
+ else
+ err = scsi_pass_through_indirect(get_fh(), &sb);
+
+ if (err)
+ return set_err((err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO),
+ "IOCTL_SCSI_PASS_THROUGH%s failed, Error=%ld",
+ (direct ? "_DIRECT" : ""), err);
+
+ iop->scsi_status = sb.spt.ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sb.ucSenseBuf[7] + 8;
+
+ if (slen > (int)sizeof(sb.ucSenseBuf))
+ slen = sizeof(sb.ucSenseBuf);
+ if (slen > (int)iop->max_sense_len)
+ slen = iop->max_sense_len;
+ memcpy(iop->sensep, sb.ucSenseBuf, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ } else
+ iop->resp_sense_len = 0;
+
+ if (iop->dxfer_len > sb.spt.DataTransferLength)
+ iop->resid = iop->dxfer_len - sb.spt.DataTransferLength;
+ else
+ iop->resid = 0;
+
+ if ((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+/// Areca RAID support
+
+// TODO: combine with above scsi_pass_through_direct()
+static long scsi_pass_through_direct(HANDLE fd, UCHAR targetid, struct scsi_cmnd_io * iop)
+{
+ int report = scsi_debugmode; // TODO
+
+ if (report > 0) {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+
+ np = scsi_get_opcode_name(ucp[0]);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("%s", buff);
+ }
+
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
+ if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
+ return EINVAL;
+ }
+
+ memset(&sb, 0, sizeof(sb));
+ sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ //sb.spt.PathId = 0;
+ sb.spt.TargetId = targetid;
+ //sb.spt.Lun = 0;
+ sb.spt.CdbLength = iop->cmnd_len;
+ memcpy(sb.spt.Cdb, iop->cmnd, iop->cmnd_len);
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
+ sb.spt.TimeOutValue = (iop->timeout ? iop->timeout : 60);
+
+ bool direct = true;
+ switch (iop->dxfer_dir) {
+ case DXFER_NONE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case DXFER_FROM_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ // IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
+ // transfers (needed for SMART STATUS check of JMicron USB bridges)
+ if (sb.spt.DataTransferLength == 1)
+ direct = false;
+ break;
+ case DXFER_TO_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ long err = 0;
+ if (direct) {
+ DWORD num_out;
+ if (!DeviceIoControl(fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ err = GetLastError();
+ }
+ else
+ err = scsi_pass_through_indirect(fd, &sb);
+
+ if (err)
+ {
+ return err;
+ }
+
+ iop->scsi_status = sb.spt.ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sb.ucSenseBuf[7] + 8;
+
+ if (slen > (int)sizeof(sb.ucSenseBuf))
+ slen = sizeof(sb.ucSenseBuf);
+ if (slen > (int)iop->max_sense_len)
+ slen = iop->max_sense_len;
+ memcpy(iop->sensep, sb.ucSenseBuf, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ } else
+ iop->resp_sense_len = 0;
+
+ if (iop->dxfer_len > sb.spt.DataTransferLength)
+ iop->resid = iop->dxfer_len - sb.spt.DataTransferLength;
+ else
+ iop->resid = 0;
+
+ if ((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_areca_scsi_device
+// SAS(SCSI) device behind Areca RAID Controller
+
+class win_areca_scsi_device
+: public /*implements*/ areca_scsi_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1);
+ virtual bool open();
+ virtual smart_device * autodetect_open();
+ virtual bool arcmsr_lock();
+ virtual bool arcmsr_unlock();
+ virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop);
+
+private:
+ HANDLE m_mutex;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_areca_scsi_device::win_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
+: smart_device(intf, dev_name, "areca", "areca")
+{
+ set_fh(INVALID_HANDLE_VALUE);
+ set_disknum(disknum);
+ set_encnum(encnum);
+ set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
+}
+
+bool win_areca_scsi_device::open()
+{
+ HANDLE hFh;
+
+ if( is_open() )
+ {
+ return true;
+ }
+ hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
+ if(hFh == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ set_fh(hFh);
+ return true;
+}
+
+smart_device * win_areca_scsi_device::autodetect_open()
+{
+ return this;
+}
+
+int win_areca_scsi_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop)
+{
+ int ioctlreturn = 0;
+
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 16, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 127, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ // errors found
+ return -1;
+ }
+ }
+
+ return ioctlreturn;
+}
+
+bool win_areca_scsi_device::arcmsr_lock()
+{
+#define SYNCOBJNAME "Global\\SynIoctlMutex"
+ int ctlrnum = -1;
+ char mutexstr[64];
+
+ if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1)
+ return set_err(EINVAL, "unable to parse device name");
+
+ snprintf(mutexstr, sizeof(mutexstr), "%s%d", SYNCOBJNAME, ctlrnum);
+ m_mutex = CreateMutex(NULL, FALSE, mutexstr);
+ if ( m_mutex == NULL )
+ {
+ return set_err(EIO, "CreateMutex failed");
+ }
+
+ // atomic access to driver
+ WaitForSingleObject(m_mutex, INFINITE);
+
+ return true;
+}
+
+
+bool win_areca_scsi_device::arcmsr_unlock()
+{
+ if( m_mutex != NULL)
+ {
+ ReleaseMutex(m_mutex);
+ CloseHandle(m_mutex);
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_areca_ata_device
+// SATA(ATA) device behind Areca RAID Controller
+
+class win_areca_ata_device
+: public /*implements*/ areca_ata_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1);
+ virtual bool open();
+ virtual smart_device * autodetect_open();
+ virtual bool arcmsr_lock();
+ virtual bool arcmsr_unlock();
+ virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop);
+
+private:
+ HANDLE m_mutex;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_areca_ata_device::win_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
+: smart_device(intf, dev_name, "areca", "areca")
+{
+ set_fh(INVALID_HANDLE_VALUE);
+ set_disknum(disknum);
+ set_encnum(encnum);
+ set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
+}
+
+bool win_areca_ata_device::open()
+{
+ HANDLE hFh;
+
+ if( is_open() )
+ {
+ return true;
+ }
+ hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
+ if(hFh == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ set_fh(hFh);
+ return true;
+}
+
+smart_device * win_areca_ata_device::autodetect_open()
+{
+ // autodetect device type
+ int is_ata = arcmsr_get_dev_type();
+ if(is_ata < 0)
+ {
+ set_err(EIO);
+ return this;
+ }
+
+ if(is_ata == 1)
+ {
+ // SATA device
+ return this;
+ }
+
+ // SAS device
+ smart_device_auto_ptr newdev(new win_areca_scsi_device(smi(), get_dev_name(), get_disknum(), get_encnum()));
+ close();
+ delete this;
+ newdev->open(); // TODO: Can possibly pass open fd
+
+ return newdev.release();
+}
+
+int win_areca_ata_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop)
+{
+ int ioctlreturn = 0;
+
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 16, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 127, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ // errors found
+ return -1;
+ }
+ }
+
+ return ioctlreturn;
+}
+
+bool win_areca_ata_device::arcmsr_lock()
+{
+#define SYNCOBJNAME "Global\\SynIoctlMutex"
+ int ctlrnum = -1;
+ char mutexstr[64];
+
+ if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1)
+ return set_err(EINVAL, "unable to parse device name");
+
+ snprintf(mutexstr, sizeof(mutexstr), "%s%d", SYNCOBJNAME, ctlrnum);
+ m_mutex = CreateMutex(NULL, FALSE, mutexstr);
+ if ( m_mutex == NULL )
+ {
+ return set_err(EIO, "CreateMutex failed");
+ }
+
+ // atomic access to driver
+ WaitForSingleObject(m_mutex, INFINITE);
+
+ return true;
+}
+
+
+bool win_areca_ata_device::arcmsr_unlock()
+{
+ if( m_mutex != NULL)
+ {
+ ReleaseMutex(m_mutex);
+ CloseHandle(m_mutex);
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_aacraid_device
+// PMC aacraid Support
+
+class win_aacraid_device
+:public /*implements*/ scsi_device,
+public /*extends*/ win_smart_device
+{
+public:
+ win_aacraid_device(smart_interface *intf, const char *dev_name,unsigned int ctrnum, unsigned int target, unsigned int lun);
+
+ virtual ~win_aacraid_device() throw();
+
+ virtual bool open();
+
+ virtual bool scsi_pass_through(struct scsi_cmnd_io *iop);
+
+private:
+ //Device Host number
+ int m_ctrnum;
+
+ //Channel(Lun) of the device
+ int m_lun;
+
+ //Id of the device
+ int m_target;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_aacraid_device::win_aacraid_device(smart_interface * intf,
+ const char *dev_name, unsigned ctrnum, unsigned target, unsigned lun)
+: smart_device(intf, dev_name, "aacraid", "aacraid"),
+ m_ctrnum(ctrnum), m_lun(lun), m_target(target)
+{
+ set_info().info_name = strprintf("%s [aacraid_disk_%02d_%02d_%d]", dev_name, m_ctrnum, m_lun, m_target);
+ set_info().dev_type = strprintf("aacraid,%d,%d,%d", m_ctrnum, m_lun, m_target);
+}
+
+win_aacraid_device::~win_aacraid_device() throw()
+{
+}
+
+bool win_aacraid_device::open()
+{
+ if (is_open())
+ return true;
+
+ HANDLE hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ 0);
+ if (hFh == INVALID_HANDLE_VALUE)
+ return set_err(ENODEV, "Open failed, Error=%u", (unsigned)GetLastError());
+
+ set_fh(hFh);
+ return true;
+}
+
+bool win_aacraid_device::scsi_pass_through(struct scsi_cmnd_io *iop)
+{
+ int report = scsi_debugmode;
+ if (report > 0)
+ {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+ np = scsi_get_opcode_name(ucp[0]);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : (int)iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("buff %s\n",buff);
+ }
+
+ char ioBuffer[1000];
+ SRB_IO_CONTROL * pSrbIO = (SRB_IO_CONTROL *) ioBuffer;
+ SCSI_REQUEST_BLOCK * pScsiIO = (SCSI_REQUEST_BLOCK *) (ioBuffer + sizeof(SRB_IO_CONTROL));
+ DWORD scsiRequestBlockSize = sizeof(SCSI_REQUEST_BLOCK);
+ char *pRequestSenseIO = (char *) (ioBuffer + sizeof(SRB_IO_CONTROL) + scsiRequestBlockSize);
+ DWORD dataOffset = (sizeof(SRB_IO_CONTROL) + scsiRequestBlockSize + 7) & 0xfffffff8;
+ char *pDataIO = (char *) (ioBuffer + dataOffset);
+ memset(pScsiIO, 0, scsiRequestBlockSize);
+ pScsiIO->Length = (USHORT) scsiRequestBlockSize;
+ pScsiIO->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ pScsiIO->PathId = 0;
+ pScsiIO->TargetId = m_target;
+ pScsiIO->Lun = m_lun;
+ pScsiIO->CdbLength = (int)iop->cmnd_len;
+ switch(iop->dxfer_dir){
+ case DXFER_NONE:
+ pScsiIO->SrbFlags = SRB_NoDataXfer;
+ break;
+ case DXFER_FROM_DEVICE:
+ pScsiIO->SrbFlags |= SRB_DataIn;
+ break;
+ case DXFER_TO_DEVICE:
+ pScsiIO->SrbFlags |= SRB_DataOut;
+ break;
+ default:
+ pout("aacraid: bad dxfer_dir\n");
+ return set_err(EINVAL, "aacraid: bad dxfer_dir\n");
+ }
+ pScsiIO->DataTransferLength = (ULONG)iop->dxfer_len;
+ pScsiIO->TimeOutValue = iop->timeout;
+ UCHAR *pCdb = (UCHAR *) pScsiIO->Cdb;
+ memcpy(pCdb, iop->cmnd, 16);
+ if (iop->max_sense_len){
+ memset(pRequestSenseIO, 0, iop->max_sense_len);
+ }
+ if (pScsiIO->SrbFlags & SRB_FLAGS_DATA_OUT){
+ memcpy(pDataIO, iop->dxferp, iop->dxfer_len);
+ }
+ else if (pScsiIO->SrbFlags & SRB_FLAGS_DATA_IN){
+ memset(pDataIO, 0, iop->dxfer_len);
+ }
+
+ DWORD bytesReturned = 0;
+ memset(pSrbIO, 0, sizeof(SRB_IO_CONTROL));
+ pSrbIO->HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(pSrbIO->Signature, "AACAPI", 7);
+ pSrbIO->ControlCode = ARCIOCTL_SEND_RAW_SRB;
+ pSrbIO->Length = (dataOffset + iop->dxfer_len - sizeof(SRB_IO_CONTROL) + 7) & 0xfffffff8;
+ pSrbIO->Timeout = 3*60;
+
+ if (!DeviceIoControl(
+ get_fh(),
+ IOCTL_SCSI_MINIPORT,
+ ioBuffer,
+ sizeof(SRB_IO_CONTROL) + pSrbIO->Length,
+ ioBuffer,
+ sizeof(SRB_IO_CONTROL) + pSrbIO->Length,
+ &bytesReturned,
+ NULL)
+ ) {
+ return set_err(EIO, "ARCIOCTL_SEND_RAW_SRB failed, Error=%u", (unsigned)GetLastError());
+ }
+
+ iop->scsi_status = pScsiIO->ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sizeof(pRequestSenseIO) + 8;
+ if (slen > (int)sizeof(pRequestSenseIO))
+ slen = sizeof(pRequestSenseIO);
+ if (slen > (int)iop->max_sense_len)
+ slen = (int)iop->max_sense_len;
+ memcpy(iop->sensep, pRequestSenseIO, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ }
+ else {
+ iop->resp_sense_len = 0;
+ }
+
+ if (iop->dxfer_dir == DXFER_FROM_DEVICE){
+ memcpy(iop->dxferp,pDataIO, iop->dxfer_len);
+ }
+ if((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)){
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex((const uint8_t *)pDataIO, (trunc ? 256 : (int)(iop->dxfer_len)) , 1);
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_nvme_device
+
+class win_nvme_device
+: public /*implements*/ nvme_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid);
+
+ virtual bool open();
+
+ virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out);
+
+ bool open_scsi(int n);
+
+ bool probe();
+
+private:
+ int m_scsi_no;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_nvme_device::win_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+ nvme_device(nsid),
+ m_scsi_no(-1)
+{
+}
+
+bool win_nvme_device::open_scsi(int n)
+{
+ // TODO: Use common open function for all devices using "\\.\ScsiN:"
+ char devpath[32];
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%d:", n);
+
+ 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 (nvme_debugmode > 1)
+ pout(" %s: Open failed, Error=%ld\n", devpath, err);
+ 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 (nvme_debugmode > 1)
+ pout(" %s: successfully opened\n", devpath);
+
+ set_fh(h);
+ return true;
+}
+
+// Check if NVMe DeviceIoControl(IOCTL_SCSI_MINIPORT) pass-through works.
+// On Win10 and later that returns false with an errorNumber of 1
+// ("Incorrect function"). Win10 has new pass-through:
+// DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND). However for commonly
+// requested NVMe commands like Identify and Get Features Microsoft want
+// "Protocol specific queries" sent.
+bool win_nvme_device::probe()
+{
+ smartmontools::nvme_id_ctrl id_ctrl;
+ nvme_cmd_in in;
+ in.set_data_in(smartmontools::nvme_admin_identify, &id_ctrl, sizeof(id_ctrl));
+ // in.nsid = 0;
+ in.cdw10 = 0x1;
+ nvme_cmd_out out;
+
+ bool ok = nvme_pass_through(in, out);
+ if (!ok && nvme_debugmode > 1)
+ pout(" nvme probe failed: %s\n", get_errmsg());
+ return ok;
+}
+
+bool win_nvme_device::open()
+{
+ if (m_scsi_no < 0) {
+ // First open -> search of NVMe devices
+ const char * name = skipdev(get_dev_name());
+ char s[2+1] = ""; int n1 = -1, n2 = -1, len = strlen(name);
+ unsigned no = ~0, nsid = 0xffffffff;
+ sscanf(name, "nvm%2[es]%u%nn%u%n", s, &no, &n1, &nsid, &n2);
+
+ if (!( (n1 == len || (n2 == len && nsid > 0))
+ && s[0] == 'e' && (!s[1] || s[1] == 's') ))
+ return set_err(EINVAL);
+
+ if (!s[1]) {
+ // /dev/nvmeN* -> search for nth NVMe device
+ unsigned nvme_cnt = 0;
+ for (int i = 0; i < 32; i++) {
+ if (!open_scsi(i)) {
+ if (get_errno() == EACCES)
+ return false;
+ continue;
+ }
+ // Done if pass-through works and correct number
+ if (probe()) {
+ if (nvme_cnt == no) {
+ m_scsi_no = i;
+ break;
+ }
+ nvme_cnt++;
+ }
+ close();
+ }
+
+ if (!is_open())
+ return set_err(ENOENT);
+ clear_err();
+ }
+ else {
+ // /dev/nvmesN* -> use "\\.\ScsiN:"
+ if (!open_scsi(no))
+ return false;
+ m_scsi_no = no;
+ }
+
+ if (!get_nsid())
+ set_nsid(nsid);
+ }
+ else {
+ // Reopen same "\\.\ScsiN:"
+ if (!open_scsi(m_scsi_no))
+ return false;
+ }
+
+ return true;
+}
+
+bool win_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ // Create buffer with appropriate size
+ raw_buffer pthru_raw_buf(offsetof(NVME_PASS_THROUGH_IOCTL, DataBuffer) + in.size);
+ NVME_PASS_THROUGH_IOCTL * pthru =
+ reinterpret_cast<NVME_PASS_THROUGH_IOCTL *>(pthru_raw_buf.data());
+
+ // Set NVMe command
+ pthru->SrbIoCtrl.HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(pthru->SrbIoCtrl.Signature, NVME_SIG_STR, sizeof(NVME_SIG_STR)-1);
+ pthru->SrbIoCtrl.Timeout = 60;
+ pthru->SrbIoCtrl.ControlCode = NVME_PASS_THROUGH_SRB_IO_CODE;
+ pthru->SrbIoCtrl.ReturnCode = 0;
+ pthru->SrbIoCtrl.Length = pthru_raw_buf.size() - sizeof(SRB_IO_CONTROL);
+
+ pthru->NVMeCmd[0] = in.opcode;
+ pthru->NVMeCmd[1] = in.nsid;
+ pthru->NVMeCmd[10] = in.cdw10;
+ pthru->NVMeCmd[11] = in.cdw11;
+ pthru->NVMeCmd[12] = in.cdw12;
+ pthru->NVMeCmd[13] = in.cdw13;
+ pthru->NVMeCmd[14] = in.cdw14;
+ pthru->NVMeCmd[15] = in.cdw15;
+
+ pthru->Direction = in.direction();
+ // pthru->QueueId = 0; // AdminQ
+ // pthru->DataBufferLen = 0;
+ if (in.direction() & nvme_cmd_in::data_out) {
+ pthru->DataBufferLen = in.size;
+ memcpy(pthru->DataBuffer, in.buffer, in.size);
+ }
+ // pthru->MetaDataLen = 0;
+ pthru->ReturnBufferLen = pthru_raw_buf.size();
+
+ // Call NVME_PASS_THROUGH
+ DWORD num_out = 0;
+ BOOL ok = DeviceIoControl(get_fh(), IOCTL_SCSI_MINIPORT,
+ pthru, pthru_raw_buf.size(), pthru, pthru_raw_buf.size(),
+ &num_out, (OVERLAPPED*)0);
+
+ // Check status
+ unsigned status = pthru->CplEntry[3] >> 17;
+ if (status)
+ return set_nvme_err(out, status);
+
+ if (!ok)
+ return set_err(EIO, "NVME_PASS_THROUGH failed, Error=%u", (unsigned)GetLastError());
+
+ if (in.direction() & nvme_cmd_in::data_in)
+ memcpy(in.buffer, pthru->DataBuffer, in.size);
+
+ out.result = pthru->CplEntry[0];
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win10_nvme_device
+
+class win10_nvme_device
+: public /*implements*/ nvme_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win10_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid);
+
+ virtual bool open();
+
+ virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out);
+
+private:
+ bool open(int phydrive);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win10_nvme_device::win10_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+ nvme_device(nsid)
+{
+}
+
+bool win10_nvme_device::open()
+{
+ // TODO: Use common /dev/ parsing functions
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // sd[a-z]([a-z])? => Physical drive 0-701
+ char drive[2 + 1] = ""; int n = -1;
+ if (sscanf(name, "sd%2[a-z]%n", drive, &n) == 1 && n == len)
+ return open(sdxy_to_phydrive(drive));
+
+ // pdN => Physical drive N
+ int phydrive = -1; n = -1;
+ if (sscanf(name, "pd%d%n", &phydrive, &n) == 1 && phydrive >= 0 && n == len)
+ return open(phydrive);
+
+ return set_err(EINVAL);
+}
+
+bool win10_nvme_device::open(int phydrive)
+{
+ // TODO: Use common open function for all devices using "\\.\PhysicalDriveN"
+ char devpath[64];
+ snprintf(devpath, sizeof(devpath) - 1, "\\\\.\\PhysicalDrive%d", phydrive);
+
+ // No GENERIC_READ/WRITE access required, this works without admin rights
+ HANDLE h = CreateFileA(devpath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, (HANDLE)0);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (nvme_debugmode > 1)
+ pout(" %s: Open failed, Error=%ld\n", devpath, err);
+ 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 (nvme_debugmode > 1)
+ pout(" %s: successfully opened\n", devpath);
+
+ set_fh(h);
+
+ // Use broadcast namespace if no NSID specified
+ // TODO: Get NSID of current device
+ if (!get_nsid())
+ set_nsid(0xffffffff);
+ return true;
+}
+
+struct STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER
+{
+ struct { // STORAGE_PROPERTY_QUERY without AdditionalsParameters[1]
+ STORAGE_PROPERTY_ID PropertyId;
+ STORAGE_QUERY_TYPE QueryType;
+ } PropertyQuery;
+ win10::STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecific;
+ BYTE DataBuffer[1];
+};
+
+bool win10_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ // Create buffer with appropriate size
+ raw_buffer spsq_raw_buf(offsetof(STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER, DataBuffer) + in.size);
+ STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER * spsq =
+ reinterpret_cast<STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER *>(spsq_raw_buf.data());
+
+ // Set NVMe specific STORAGE_PROPERTY_QUERY
+ spsq->PropertyQuery.QueryType = PropertyStandardQuery;
+ spsq->ProtocolSpecific.ProtocolType = win10::ProtocolTypeNvme;
+
+ switch (in.opcode) {
+ case smartmontools::nvme_admin_identify:
+ if (!in.nsid) // Identify controller
+ spsq->PropertyQuery.PropertyId = win10::StorageAdapterProtocolSpecificProperty;
+ else
+ spsq->PropertyQuery.PropertyId = win10::StorageDeviceProtocolSpecificProperty;
+ spsq->ProtocolSpecific.DataType = win10::NVMeDataTypeIdentify;
+ spsq->ProtocolSpecific.ProtocolDataRequestValue = in.cdw10;
+ break;
+ case smartmontools::nvme_admin_get_log_page:
+ spsq->PropertyQuery.PropertyId = win10::StorageDeviceProtocolSpecificProperty;
+ spsq->ProtocolSpecific.DataType = win10::NVMeDataTypeLogPage;
+ spsq->ProtocolSpecific.ProtocolDataRequestValue = in.cdw10 & 0xff; // LID only ?
+ break;
+ // TODO: nvme_admin_get_features
+ default:
+ return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
+ }
+
+ spsq->ProtocolSpecific.ProtocolDataRequestSubValue = in.nsid; // ?
+ spsq->ProtocolSpecific.ProtocolDataOffset = sizeof(spsq->ProtocolSpecific);
+ spsq->ProtocolSpecific.ProtocolDataLength = in.size;
+
+ if (in.direction() & nvme_cmd_in::data_out)
+ memcpy(spsq->DataBuffer, in.buffer, in.size);
+
+ if (nvme_debugmode > 1)
+ pout(" [STORAGE_QUERY_PROPERTY: Id=%u, Type=%u, Value=0x%08x, SubVal=0x%08x]\n",
+ (unsigned)spsq->PropertyQuery.PropertyId,
+ (unsigned)spsq->ProtocolSpecific.DataType,
+ (unsigned)spsq->ProtocolSpecific.ProtocolDataRequestValue,
+ (unsigned)spsq->ProtocolSpecific.ProtocolDataRequestSubValue);
+
+ // Call IOCTL_STORAGE_QUERY_PROPERTY
+ DWORD num_out = 0;
+ long err = 0;
+ if (!DeviceIoControl(get_fh(), IOCTL_STORAGE_QUERY_PROPERTY,
+ spsq, spsq_raw_buf.size(), spsq, spsq_raw_buf.size(),
+ &num_out, (OVERLAPPED*)0)) {
+ err = GetLastError();
+ }
+
+ if (nvme_debugmode > 1)
+ pout(" [STORAGE_QUERY_PROPERTY: ReturnData=0x%08x, Reserved[3]={0x%x, 0x%x, 0x%x}]\n",
+ (unsigned)spsq->ProtocolSpecific.FixedProtocolReturnData,
+ (unsigned)spsq->ProtocolSpecific.Reserved[0],
+ (unsigned)spsq->ProtocolSpecific.Reserved[1],
+ (unsigned)spsq->ProtocolSpecific.Reserved[2]);
+
+ // NVMe status is checked by IOCTL
+ if (err)
+ return set_err(EIO, "IOCTL_STORAGE_QUERY_PROPERTY(NVMe) failed, Error=%ld", err);
+
+ if (in.direction() & nvme_cmd_in::data_in)
+ memcpy(in.buffer, spsq->DataBuffer, in.size);
+
+ out.result = spsq->ProtocolSpecific.FixedProtocolReturnData; // Completion DW0 ?
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_smart_interface
+// Platform specific interface
+
+class win_smart_interface
+: public /*implements*/ smart_interface
+{
+public:
+ virtual std::string get_os_version_str();
+
+ virtual std::string get_app_examples(const char * appname);
+
+#ifndef __CYGWIN__
+ virtual int64_t get_timer_usec();
+#endif
+
+ virtual bool disable_system_auto_standby(bool disable);
+
+ virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
+ const char * pattern = 0);
+
+protected:
+ virtual ata_device * get_ata_device(const char * name, const char * type);
+
+ virtual scsi_device * get_scsi_device(const char * name, const char * type);
+
+ virtual nvme_device * get_nvme_device(const char * name, const char * type, unsigned nsid);
+
+ virtual smart_device * autodetect_smart_device(const char * name);
+
+ virtual smart_device * get_custom_smart_device(const char * name, const char * type);
+
+ virtual std::string get_valid_custom_dev_types_str();
+
+private:
+ smart_device * get_usb_device(const char * name, int phydrive, int logdrive = -1);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WIN64
+// Running on 64-bit Windows as 32-bit app ?
+static bool is_wow64()
+{
+ BOOL (WINAPI * IsWow64Process_p)(HANDLE, PBOOL) =
+ (BOOL (WINAPI *)(HANDLE, PBOOL))
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process");
+ if (!IsWow64Process_p)
+ return false;
+ BOOL w64 = FALSE;
+ if (!IsWow64Process_p(GetCurrentProcess(), &w64))
+ return false;
+ return !!w64;
+}
+#endif // _WIN64
+
+// Return info string about build host and OS version
+std::string win_smart_interface::get_os_version_str()
+{
+ char vstr[sizeof(SMARTMONTOOLS_BUILD_HOST)-1+sizeof("-2003r2(64)-sp2.1")+13]
+ = SMARTMONTOOLS_BUILD_HOST;
+ if (vstr[1] < '6')
+ vstr[1] = '6';
+ char * const vptr = vstr+sizeof(SMARTMONTOOLS_BUILD_HOST)-1;
+ const int vlen = sizeof(vstr)-sizeof(SMARTMONTOOLS_BUILD_HOST);
+ assert(vptr == vstr+strlen(vstr) && vptr+vlen+1 == vstr+sizeof(vstr));
+
+ // Starting with Windows 8.1, GetVersionEx() does no longer report the
+ // actual OS version. RtlGetVersion() is not affected.
+ LONG /*NTSTATUS*/ (WINAPI /*NTAPI*/ * RtlGetVersion_p)(LPOSVERSIONINFOEXW) =
+ (LONG (WINAPI *)(LPOSVERSIONINFOEXW))
+ GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion");
+
+ OSVERSIONINFOEXW vi; memset(&vi, 0, sizeof(vi));
+ vi.dwOSVersionInfoSize = sizeof(vi);
+ if (!RtlGetVersion_p || RtlGetVersion_p(&vi)) {
+ if (!GetVersionExW((OSVERSIONINFOW *)&vi))
+ return vstr;
+ }
+
+ const char * w = 0;
+ unsigned build = 0;
+ if ( vi.dwPlatformId == VER_PLATFORM_WIN32_NT
+ && vi.dwMajorVersion <= 0xf && vi.dwMinorVersion <= 0xf) {
+ switch ( (vi.dwMajorVersion << 4 | vi.dwMinorVersion) << 1
+ | (vi.wProductType > VER_NT_WORKSTATION ? 1 : 0) ) {
+ case 0x50<<1 :
+ case 0x50<<1 | 1: w = "2000"; break;
+ case 0x51<<1 : w = "xp"; break;
+ case 0x52<<1 : w = "xp64"; break;
+ case 0x52<<1 | 1: w = (!GetSystemMetrics(89/*SM_SERVERR2*/)
+ ? "2003"
+ : "2003r2"); break;
+ case 0x60<<1 : w = "vista"; break;
+ case 0x60<<1 | 1: w = "2008"; break;
+ case 0x61<<1 : w = "win7"; break;
+ case 0x61<<1 | 1: w = "2008r2"; break;
+ case 0x62<<1 : w = "win8"; break;
+ case 0x62<<1 | 1: w = "2012"; break;
+ case 0x63<<1 : w = "win8.1"; break;
+ case 0x63<<1 | 1: w = "2012r2"; break;
+ case 0xa0<<1 :
+ switch (vi.dwBuildNumber) {
+ case 10240: w = "w10-1507"; break;
+ case 10586: w = "w10-1511"; break;
+ case 14393: w = "w10-1607"; break;
+ case 15063: w = "w10-1703"; break;
+ case 16299: w = "w10-1709"; break;
+ case 17134: w = "w10-1803"; break;
+ case 17763: w = "w10-1809"; break;
+ default: w = "w10";
+ build = vi.dwBuildNumber; break;
+ } break;
+ case 0xa0<<1 | 1:
+ switch (vi.dwBuildNumber) {
+ case 14393: w = "2016"; break;
+ case 16299: w = "2016-1709"; break;
+ case 17134: w = "2016-1803"; break;
+ case 17763: w = "2019"; break;
+ default: w = (vi.dwBuildNumber < 17763
+ ? "2016"
+ : "2019");
+ build = vi.dwBuildNumber; break;
+ } break;
+ }
+ }
+
+ const char * w64 = "";
+#ifndef _WIN64
+ if (is_wow64())
+ w64 = "(64)";
+#endif
+
+ if (!w)
+ snprintf(vptr, vlen, "-%s%u.%u%s",
+ (vi.dwPlatformId==VER_PLATFORM_WIN32_NT ? "nt" : "??"),
+ (unsigned)vi.dwMajorVersion, (unsigned)vi.dwMinorVersion, w64);
+ else if (build)
+ snprintf(vptr, vlen, "-%s-b%u%s", w, build, w64);
+ else if (vi.wServicePackMinor)
+ snprintf(vptr, vlen, "-%s-sp%u.%u%s", w, vi.wServicePackMajor, vi.wServicePackMinor, w64);
+ else if (vi.wServicePackMajor)
+ snprintf(vptr, vlen, "-%s-sp%u%s", w, vi.wServicePackMajor, w64);
+ else
+ snprintf(vptr, vlen, "-%s%s", w, w64);
+ return vstr;