+
+
+//////////////////////////////////////////////////////////////////////
+// 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);
+
+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;
+}
+
+#ifndef __CYGWIN__
+// MSVCRT only provides ftime() which uses GetSystemTime()
+// This provides only ~15ms resolution by default.
+// Use QueryPerformanceCounter instead (~300ns).
+// (Cygwin provides CLOCK_MONOTONIC which has the same effect)
+int64_t win_smart_interface::get_timer_usec()
+{
+ static int64_t freq = 0;
+
+ LARGE_INTEGER t;
+ if (freq == 0)
+ freq = (QueryPerformanceFrequency(&t) ? t.QuadPart : -1);
+ if (freq <= 0)
+ return smart_interface::get_timer_usec();
+
+ if (!QueryPerformanceCounter(&t))
+ return -1;
+ if (!(0 <= t.QuadPart && t.QuadPart <= (int64_t)(~(uint64_t)0 >> 1)/1000000))
+ return -1;
+
+ return (t.QuadPart * 1000000LL) / freq;
+}
+#endif // __CYGWIN__
+
+
+ata_device * win_smart_interface::get_ata_device(const char * name, const char * type)
+{
+ const char * testname = skipdev(name);
+ if (!strncmp(testname, "csmi", 4))
+ return new win_csmi_device(this, name, type);
+ if (!strncmp(testname, "tw_cli", 6))
+ return new win_tw_cli_device(this, name, type);
+ return new win_ata_device(this, name, type);
+}
+
+scsi_device * win_smart_interface::get_scsi_device(const char * name, const char * type)
+{
+ return new win_scsi_device(this, name, type);
+}
+
+nvme_device * win_smart_interface::get_nvme_device(const char * name, const char * type,
+ unsigned nsid)
+{
+ if (str_starts_with(skipdev(name), "nvme"))
+ return new win_nvme_device(this, name, type, nsid);
+ return new win10_nvme_device(this, name, type, nsid);
+}
+
+
+smart_device * win_smart_interface::get_custom_smart_device(const char * name, const char * type)
+{
+ // Areca?
+ int disknum = -1, n1 = -1, n2 = -1;
+ int encnum = 1;
+ char devpath[32];
+
+ if (sscanf(type, "areca,%n%d/%d%n", &n1, &disknum, &encnum, &n2) >= 1 || n1 == 6) {
+ if (!(1 <= disknum && disknum <= 128)) {
+ set_err(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum);
+ return 0;
+ }
+ if (!(1 <= encnum && encnum <= 8)) {
+ set_err(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum);
+ return 0;
+ }
+
+ name = skipdev(name);
+#define ARECA_MAX_CTLR_NUM 16
+ n1 = -1;
+ int ctlrindex = 0;
+ if (sscanf(name, "arcmsr%d%n", &ctlrindex, &n1) >= 1 && n1 == (int)strlen(name)) {
+ /*
+ 1. scan from "\\\\.\\scsi[0]:" up to "\\\\.\\scsi[ARECA_MAX_CTLR_NUM]:" and
+ 2. map arcmsrX into "\\\\.\\scsiX"
+ */
+ for (int idx = 0; idx < ARECA_MAX_CTLR_NUM; idx++) {
+ memset(devpath, 0, sizeof(devpath));
+ snprintf(devpath, sizeof(devpath), "\\\\.\\scsi%d:", idx);
+ win_areca_ata_device *arcdev = new win_areca_ata_device(this, devpath, disknum, encnum);
+ if(arcdev->arcmsr_probe()) {
+ if(ctlrindex-- == 0) {
+ return arcdev;
+ }
+ }
+ delete arcdev;
+ }
+ set_err(ENOENT, "No Areca controller found");
+ }
+ else
+ set_err(EINVAL, "Option -d areca,N/E requires device name /dev/arcmsrX");
+ return 0;
+ }
+
+ // aacraid?
+ unsigned ctrnum, lun, target;
+ n1 = -1;
+
+ if ( sscanf(type, "aacraid,%u,%u,%u%n", &ctrnum, &lun, &target, &n1) >= 3
+ && n1 == (int)strlen(type)) {
+#define aacraid_MAX_CTLR_NUM 16
+ if (ctrnum >= aacraid_MAX_CTLR_NUM) {
+ set_err(EINVAL, "aacraid: invalid host number %u", ctrnum);
+ return 0;
+ }
+
+ /*
+ 1. scan from "\\\\.\\scsi[0]:" up to "\\\\.\\scsi[AACRAID_MAX_CTLR_NUM]:" and
+ 2. map ARCX into "\\\\.\\scsiX"
+ */
+ memset(devpath, 0, sizeof(devpath));
+ unsigned ctlrindex = 0;
+ for (int portNum = 0; portNum < aacraid_MAX_CTLR_NUM; portNum++){
+ char subKey[63];
+ snprintf(subKey, sizeof(subKey), "HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port %d", portNum);
+ HKEY hScsiKey = 0;
+ long regStatus = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hScsiKey);
+ if (regStatus == ERROR_SUCCESS){
+ char driverName[20];
+ DWORD driverNameSize = sizeof(driverName);
+ DWORD regType = 0;
+ regStatus = RegQueryValueExA(hScsiKey, "Driver", NULL, ®Type, (LPBYTE) driverName, &driverNameSize);
+ if (regStatus == ERROR_SUCCESS){
+ if (regType == REG_SZ){
+ if (stricmp(driverName, "arcsas") == 0){
+ if(ctrnum == ctlrindex){
+ snprintf(devpath, sizeof(devpath), "\\\\.\\Scsi%d:", portNum);
+ return get_sat_device("sat,auto",
+ new win_aacraid_device(this, devpath, ctrnum, target, lun));
+ }
+ ctlrindex++;
+ }
+ }
+ }
+ RegCloseKey(hScsiKey);
+ }
+ }
+
+ set_err(EINVAL, "aacraid: host %u not found", ctrnum);
+ return 0;
+ }
+
+ return 0;
+}
+
+std::string win_smart_interface::get_valid_custom_dev_types_str()
+{
+ return "aacraid,H,L,ID, areca,N[/E]";
+}
+
+
+// Return value for device detection functions
+enum win_dev_type { DEV_UNKNOWN = 0, DEV_ATA, DEV_SCSI, DEV_SAT, DEV_USB, DEV_NVME };
+
+// Return true if ATA drive behind a SAT layer
+static bool is_sat(const STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ if (!data->desc.VendorIdOffset)
+ return false;
+ if (strcmp(data->raw + data->desc.VendorIdOffset, "ATA "))
+ return false;
+ return true;
+}
+
+// Return true if Intel ICHxR RAID volume
+static bool is_intel_raid_volume(const STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ if (!(data->desc.VendorIdOffset && data->desc.ProductIdOffset))
+ return false;
+ const char * vendor = data->raw + data->desc.VendorIdOffset;
+ if (!(!strnicmp(vendor, "Intel", 5) && strspn(vendor+5, " ") == strlen(vendor+5)))
+ return false;
+ if (strnicmp(data->raw + data->desc.ProductIdOffset, "Raid ", 5))
+ return false;
+ return true;
+}
+
+// get DEV_* for open handle
+static win_dev_type get_controller_type(HANDLE hdevice, bool admin, GETVERSIONINPARAMS_EX * ata_version_ex)
+{
+ // Get BusType from device descriptor
+ STORAGE_DEVICE_DESCRIPTOR_DATA data;
+ if (storage_query_property_ioctl(hdevice, &data))
+ return DEV_UNKNOWN;
+
+ // Newer BusType* values are missing in older includes
+ switch ((int)data.desc.BusType) {
+ case BusTypeAta:
+ case 0x0b: // BusTypeSata
+ // Certain Intel AHCI drivers (C600+/C220+) have broken
+ // IOCTL_ATA_PASS_THROUGH support and a working SAT layer
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ if (ata_version_ex)
+ memset(ata_version_ex, 0, sizeof(*ata_version_ex));
+ return DEV_ATA;
+
+ case BusTypeScsi:
+ case BusTypeRAID:
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ // Intel ICHxR RAID volume: reports SMART_GET_VERSION but does not support SMART_*
+ if (is_intel_raid_volume(&data))
+ return DEV_SCSI;
+ // LSI/3ware RAID volume: supports SMART_*
+ if (admin && smart_get_version(hdevice, ata_version_ex) >= 0)
+ return DEV_ATA;
+
+ return DEV_SCSI;
+
+ case 0x09: // BusTypeiScsi
+ case 0x0a: // BusTypeSas
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ return DEV_SCSI;
+
+ case BusTypeUsb:
+ return DEV_USB;
+
+ case 0x11: // BusTypeNvme
+ return DEV_NVME;
+
+ case 0x12: //BusTypeSCM
+ case 0x13: //BusTypeUfs
+ case 0x14: //BusTypeMax,
+ default:
+ return DEV_UNKNOWN;
+ }
+ /*NOTREACHED*/
+}
+
+// get DEV_* for device path
+static win_dev_type get_controller_type(const char * path, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
+{
+ bool admin = true;
+ HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ admin = false;
+ h = CreateFileA(path, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ return DEV_UNKNOWN;
+ }
+ if (ata_debugmode > 1 || scsi_debugmode > 1)
+ pout(" %s: successfully opened%s\n", path, (!admin ? " (without admin rights)" :""));
+ win_dev_type type = get_controller_type(h, admin, ata_version_ex);
+ CloseHandle(h);
+ return type;
+}
+
+// get DEV_* for physical drive number
+static win_dev_type get_phy_drive_type(int drive, GETVERSIONINPARAMS_EX * ata_version_ex)
+{
+ char path[30];
+ snprintf(path, sizeof(path)-1, "\\\\.\\PhysicalDrive%d", drive);
+ return get_controller_type(path, ata_version_ex);
+}
+
+static win_dev_type get_phy_drive_type(int drive)
+{
+ return get_phy_drive_type(drive, 0);
+}
+
+// get DEV_* for logical drive number
+static win_dev_type get_log_drive_type(int drive)
+{
+ char path[30];
+ snprintf(path, sizeof(path)-1, "\\\\.\\%c:", 'A'+drive);
+ return get_controller_type(path);
+}
+
+static win_dev_type get_dev_type(const char * name, int & phydrive, int & logdrive)
+{
+ phydrive = logdrive = -1;
+
+ name = skipdev(name);
+ if (!strncmp(name, "st", 2))
+ return DEV_SCSI;
+ if (!strncmp(name, "nst", 3))
+ return DEV_SCSI;
+ if (!strncmp(name, "tape", 4))
+ return DEV_SCSI;
+
+ logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ win_dev_type type = get_log_drive_type(logdrive);
+ return (type != DEV_UNKNOWN ? type : DEV_SCSI);
+ }
+
+ char drive[2+1] = "";
+ if (sscanf(name, "sd%2[a-z]", drive) == 1) {
+ phydrive = sdxy_to_phydrive(drive);
+ return get_phy_drive_type(phydrive);
+ }
+
+ if (sscanf(name, "pd%d", &phydrive) == 1 && phydrive >= 0)
+ return get_phy_drive_type(phydrive);
+
+ return DEV_UNKNOWN;
+}
+
+
+smart_device * win_smart_interface::get_usb_device(const char * name,
+ int phydrive, int logdrive /* = -1 */)
+{
+ // Get USB bridge ID
+ unsigned short vendor_id = 0, product_id = 0;
+ if (!get_usb_id(phydrive, logdrive, vendor_id, product_id)) {
+ set_err(EINVAL, "Unable to read USB device ID");
+ return 0;
+ }
+
+ // Get type name for this ID
+ const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id);
+ if (!usbtype)
+ return 0;
+
+ // Return SAT/USB device for this type
+ return get_scsi_passthrough_device(usbtype, new win_scsi_device(this, name, ""));
+}
+
+smart_device * win_smart_interface::autodetect_smart_device(const char * name)
+{
+ const char * testname = skipdev(name);
+ if (str_starts_with(testname, "hd"))
+ return new win_ata_device(this, name, "");
+
+ if (str_starts_with(testname, "tw_cli"))
+ return new win_tw_cli_device(this, name, "");
+
+ if (str_starts_with(testname, "csmi"))
+ return new win_csmi_device(this, name, "");
+
+ if (str_starts_with(testname, "nvme"))
+ return new win_nvme_device(this, name, "", 0 /* use default nsid */);
+
+ int phydrive = -1, logdrive = -1;
+ win_dev_type type = get_dev_type(name, phydrive, logdrive);
+
+ if (type == DEV_ATA)
+ return new win_ata_device(this, name, "");
+
+ if (type == DEV_SCSI)
+ return new win_scsi_device(this, name, "");
+
+ if (type == DEV_SAT)
+ return get_sat_device("sat", new win_scsi_device(this, name, ""));
+
+ if (type == DEV_USB)
+ return get_usb_device(name, phydrive, logdrive);
+
+ if (type == DEV_NVME)
+ return new win10_nvme_device(this, name, "", 0 /* use default nsid */);
+
+ return 0;
+}
+
+
+// Scan for devices
+bool win_smart_interface::scan_smart_devices(smart_device_list & devlist,
+ const char * type, const char * pattern /* = 0*/)
+{
+ if (pattern) {
+ set_err(EINVAL, "DEVICESCAN with pattern not implemented yet");
+ return false;
+ }
+
+ // Check for "[*,]pd" type
+ bool pd = false;
+ char type2[16+1] = "";
+ if (type) {
+ int nc = -1;
+ if (!strcmp(type, "pd")) {
+ pd = true;
+ type = 0;
+ }
+ else if (sscanf(type, "%16[^,],pd%n", type2, &nc) == 1 &&
+ nc == (int)strlen(type)) {
+ pd = true;
+ type = type2;
+ }
+ }
+
+ // Set valid types
+ bool ata, scsi, sat, usb, csmi, nvme;
+ if (!type) {
+ ata = scsi = usb = sat = csmi = true;
+#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
+ nvme = true;
+#else
+ nvme = false;
+#endif
+ }
+ else {
+ ata = scsi = usb = sat = csmi = nvme = false;
+ if (!strcmp(type, "ata"))
+ ata = true;
+ else if (!strcmp(type, "scsi"))
+ scsi = true;
+ else if (!strcmp(type, "sat"))
+ sat = true;
+ else if (!strcmp(type, "usb"))
+ usb = true;
+ else if (!strcmp(type, "csmi"))
+ csmi = true;
+ else if (!strcmp(type, "nvme"))
+ nvme = true;
+ else {
+ set_err(EINVAL,
+ "Invalid type '%s', valid arguments are: ata[,pd], scsi[,pd], "
+ "sat[,pd], usb[,pd], csmi, nvme, pd", type);
+ return false;
+ }
+ }
+
+ char name[32];
+
+ if (ata || scsi || sat || usb || nvme) {
+ // Scan up to 128 drives and 2 3ware controllers
+ const int max_raid = 2;
+ bool raid_seen[max_raid] = {false, false};
+
+ for (int i = 0; i < 128; i++) {
+ if (pd)
+ snprintf(name, sizeof(name), "/dev/pd%d", i);
+ else if (i + 'a' <= 'z')
+ snprintf(name, sizeof(name), "/dev/sd%c", i + 'a');
+ else
+ snprintf(name, sizeof(name), "/dev/sd%c%c",
+ i / ('z'-'a'+1) - 1 + 'a',
+ i % ('z'-'a'+1) + 'a');
+
+ smart_device * dev = 0;
+ GETVERSIONINPARAMS_EX vers_ex;
+
+ switch (get_phy_drive_type(i, (ata ? &vers_ex : 0))) {
+ case DEV_ATA:
+ // Driver supports SMART_GET_VERSION or STORAGE_QUERY_PROPERTY returned ATA/SATA
+ if (!ata)
+ continue;
+
+ // Interpret RAID drive map if present
+ if (vers_ex.wIdentifier == SMART_VENDOR_3WARE) {
+ // Skip if too many controllers or logical drive from this controller already seen
+ if (!(vers_ex.wControllerId < max_raid && !raid_seen[vers_ex.wControllerId]))
+ continue;
+ raid_seen[vers_ex.wControllerId] = true;
+ // Add physical drives
+ int len = strlen(name);
+ for (unsigned int pi = 0; pi < 32; pi++) {
+ if (vers_ex.dwDeviceMapEx & (1U << pi)) {
+ snprintf(name+len, sizeof(name)-1-len, ",%u", pi);
+ devlist.push_back( new win_ata_device(this, name, "ata") );
+ }
+ }
+ continue;
+ }
+
+ dev = new win_ata_device(this, name, "ata");
+ break;
+
+ case DEV_SCSI:
+ // STORAGE_QUERY_PROPERTY returned SCSI/SAS/...
+ if (!scsi)
+ continue;
+ dev = new win_scsi_device(this, name, "scsi");
+ break;
+
+ case DEV_SAT:
+ // STORAGE_QUERY_PROPERTY returned VendorId "ATA "
+ if (!sat)
+ continue;
+ dev = get_sat_device("sat", new win_scsi_device(this, name, ""));
+ break;
+
+ case DEV_USB:
+ // STORAGE_QUERY_PROPERTY returned USB
+ if (!usb)
+ continue;
+ dev = get_usb_device(name, i);
+ if (!dev)
+ // Unknown or unsupported USB ID, return as SCSI
+ dev = new win_scsi_device(this, name, "");
+ break;
+
+ case DEV_NVME:
+ // STORAGE_QUERY_PROPERTY returned NVMe
+ if (!nvme)
+ continue;
+ dev = new win10_nvme_device(this, name, "", 0 /* use default nsid */);
+ break;
+
+ default:
+ // Unknown type
+ continue;
+ }
+
+ devlist.push_back(dev);
+ }
+ }
+
+ if (csmi) {
+ // Scan CSMI devices
+ for (int i = 0; i <= 9; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/csmi%d,0", i);
+ win_csmi_device test_dev(this, name, "");
+ if (!test_dev.open_scsi())
+ continue;
+
+ unsigned ports_used = test_dev.get_ports_used();
+ if (!ports_used)
+ continue;
+
+ for (int pi = 0; pi < 32; pi++) {
+ if (!(ports_used & (1U << pi)))
+ continue;
+ snprintf(name, sizeof(name)-1, "/dev/csmi%d,%d", i, pi);
+ devlist.push_back( new win_csmi_device(this, name, "ata") );
+ }
+ }
+ }
+
+ if (nvme) {
+ // Scan \\.\Scsi[0-31] for up to 10 NVMe devices
+ int nvme_cnt = 0;
+ for (int i = 0; i < 32; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/nvme%d", i);
+ win_nvme_device test_dev(this, name, "", 0);
+ if (!test_dev.open_scsi(i)) {
+ if (test_dev.get_errno() == EACCES)
+ break;
+ continue;
+ }
+
+ if (!test_dev.probe())
+ continue;
+ if (++nvme_cnt >= 10)
+ break;
+ }
+
+ for (int i = 0; i < nvme_cnt; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/nvme%d", i);
+ devlist.push_back( new win_nvme_device(this, name, "nvme", 0) );
+ }
+ }
+ return true;
+}
+
+
+// get examples for smartctl
+std::string win_smart_interface::get_app_examples(const char * appname)
+{
+ if (strcmp(appname, "smartctl"))
+ return "";
+ return "=================================================== SMARTCTL EXAMPLES =====\n\n"
+ " smartctl -a /dev/sda (Prints all SMART information)\n\n"
+ " smartctl --smart=on --offlineauto=on --saveauto=on /dev/sda\n"
+ " (Enables SMART on first disk)\n\n"
+ " smartctl -t long /dev/sda (Executes extended disk self-test)\n\n"
+ " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/sda\n"
+ " (Prints Self-Test & Attribute errors)\n"
+ " smartctl -a /dev/sda\n"
+ " (Prints all information for disk on PhysicalDrive 0)\n"
+ " smartctl -a /dev/pd3\n"
+ " (Prints all information for disk on PhysicalDrive 3)\n"
+ " smartctl -a /dev/tape1\n"
+ " (Prints all information for SCSI tape on Tape 1)\n"
+ " smartctl -A /dev/hdb,3\n"
+ " (Prints Attributes for physical drive 3 on 3ware 9000 RAID)\n"
+ " smartctl -A /dev/tw_cli/c0/p1\n"
+ " (Prints Attributes for 3ware controller 0, port 1 using tw_cli)\n"
+ " smartctl --all --device=areca,3/1 /dev/arcmsr0\n"
+ " (Prints all SMART info for 3rd ATA disk of the 1st enclosure\n"
+ " on 1st Areca RAID controller)\n"
+ "\n"
+ " ATA SMART access methods and ordering may be specified by modifiers\n"
+ " following the device name: /dev/hdX:[saicm], where\n"
+ " 's': SMART_* IOCTLs, 'a': IOCTL_ATA_PASS_THROUGH,\n"
+ " 'i': IOCTL_IDE_PASS_THROUGH, 'f': IOCTL_STORAGE_*,\n"
+ " 'm': IOCTL_SCSI_MINIPORT_*.\n"
+ + strprintf(
+ " The default on this system is /dev/sdX:%s\n", ata_get_def_options()
+ );
+}
+
+
+bool win_smart_interface::disable_system_auto_standby(bool disable)
+{
+ if (disable) {
+ SYSTEM_POWER_STATUS ps;
+ if (!GetSystemPowerStatus(&ps))
+ return set_err(ENOSYS, "Unknown power status");
+ if (ps.ACLineStatus != 1) {
+ SetThreadExecutionState(ES_CONTINUOUS);
+ if (ps.ACLineStatus == 0)
+ set_err(EIO, "AC offline");
+ else
+ set_err(EIO, "Unknown AC line status");
+ return false;
+ }
+ }
+
+ if (!SetThreadExecutionState(ES_CONTINUOUS | (disable ? ES_SYSTEM_REQUIRED : 0)))
+ return set_err(ENOSYS);
+ return true;
+}
+
+
+} // namespace
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Initialize platform interface and register with smi()
+void smart_interface::init()
+{
+ {
+ // Remove "." from DLL search path if supported
+ // to prevent DLL preloading attacks
+ BOOL (WINAPI * SetDllDirectoryA_p)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetDllDirectoryA");
+ if (SetDllDirectoryA_p)
+ SetDllDirectoryA_p("");
+ }
+
+ static os_win32::win_smart_interface the_win_interface;
+ smart_interface::set(&the_win_interface);
+}
+
+
+#ifndef __CYGWIN__
+
+// Get exe directory
+// (prototype in utiliy.h)
+std::string get_exe_dir()
+{
+ char path[MAX_PATH];
+ // Get path of this exe
+ if (!GetModuleFileNameA(GetModuleHandleA(0), path, sizeof(path)))
+ throw std::runtime_error("GetModuleFileName() failed");
+ // Replace backslash by slash
+ int sl = -1;
+ for (int i = 0; path[i]; i++)
+ if (path[i] == '\\') {
+ path[i] = '/'; sl = i;
+ }
+ // Remove filename
+ if (sl >= 0)
+ path[sl] = 0;
+ return path;
+}
+
+#endif