X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=os_win32.cpp;h=bce23f11cecd46df182605230fc062065a4a5093;hb=f9e10201baef7a8dcb11ab6d11a2046d724af9d0;hp=ace6d32efeb08ff0cfa43b7b50969e8a18ab5c30;hpb=f4e463df436b0b3c97efe7e53c81b663e4241180;p=mirror_smartmontools-debian.git diff --git a/os_win32.cpp b/os_win32.cpp index ace6d32..bce23f1 100644 --- a/os_win32.cpp +++ b/os_win32.cpp @@ -1,10 +1,15 @@ /* * os_win32.cpp * - * Home page of code is: http://smartmontools.sourceforge.net + * Home page of code is: http://www.smartmontools.org * - * Copyright (C) 2004-12 Christian Franke - * Copyright (C) 2012 Hank Wu + * Copyright (C) 2004-17 Christian Franke + * + * Original AACRaid code: + * Copyright (C) 2015 Nidhi Malhotra + * + * Original Areca code: + * Copyright (C) 2012 Hank Wu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,11 +28,13 @@ #include "int64.h" #include "atacmds.h" #include "scsicmds.h" +#include "nvmecmds.h" #include "utility.h" #include "smartctl.h" // TODO: Do not use smartctl only variables here #include "dev_interface.h" #include "dev_ata_cmd_set.h" +#include "dev_areca.h" #include "os_win32/wmiquery.h" @@ -48,14 +55,14 @@ #include #if HAVE_NTDDDISK_H -// i686-w64-mingw32, x86_64-w64-mingw32 +// i686-pc-cygwin, i686-w64-mingw32, x86_64-w64-mingw32 // (Missing: FILE_DEVICE_SCSI) #include #include #include #include #elif HAVE_DDK_NTDDDISK_H -// i686-pc-cygwin, i686-pc-mingw32, i586-mingw32msvc +// older i686-pc-cygwin, i686-pc-mingw32, i586-mingw32msvc // (Missing: IOCTL_IDE_PASS_THROUGH, IOCTL_ATA_PASS_THROUGH, FILE_DEVICE_SCSI) #include #include @@ -67,18 +74,30 @@ #include #endif +#ifndef _WIN32 +// csmisas.h and aacraid.h require _WIN32 but w32api-headers no longer define it on Cygwin +// (aacraid.h also checks for _WIN64 which is also set on Cygwin x64) +#define _WIN32 +#endif + // CSMI support #include "csmisas.h" -#ifdef __CYGWIN__ -#include // CYGWIN_VERSION_DLL_MAJOR +// aacraid support +#include "aacraid.h" + +// Silence -Wunused-local-typedefs warning from g++ >= 4.8 +#if __GNUC__ >= 4 +#define ATTR_UNUSED __attribute__((unused)) +#else +#define ATTR_UNUSED /**/ #endif // Macro to check constants at compile time using a dummy typedef #define ASSERT_CONST(c, n) \ - typedef char assert_const_##c[((c) == (n)) ? 1 : -1] + typedef char assert_const_##c[((c) == (n)) ? 1 : -1] ATTR_UNUSED #define ASSERT_SIZEOF(t, n) \ - typedef char assert_sizeof_##t[(sizeof(t) == (n)) ? 1 : -1] + typedef char assert_sizeof_##t[(sizeof(t) == (n)) ? 1 : -1] ATTR_UNUSED #ifndef _WIN64 #define SELECT_WIN_32_64(x32, x64) (x32) @@ -86,21 +105,15 @@ #define SELECT_WIN_32_64(x32, x64) (x64) #endif -const char * os_win32_cpp_cvsid = "$Id: os_win32.cpp 3558 2012-06-05 16:42:05Z chrfranke $"; - -// Disable Win9x/ME specific code if no longer supported by compiler. -#ifdef _WIN64 - #undef WIN9X_SUPPORT -#elif !defined(WIN9X_SUPPORT) - #if defined(CYGWIN_VERSION_DLL_MAJOR) && (CYGWIN_VERSION_DLL_MAJOR >= 1007) - // Win9x/ME support was dropped in Cygwin 1.7 - #elif defined(_MSC_VER) && (_MSC_VER >= 1500) - // Win9x/ME support was dropped in MSVC9 (cl.exe 15.0) - #else - #define WIN9X_SUPPORT 1 - #endif +// Cygwin does no longer provide strn?icmp() compatibility macros +// MSVCRT does not provide strn?casecmp() +#if defined(__CYGWIN__) && !defined(stricmp) +#define stricmp strcasecmp +#define strnicmp strncasecmp #endif +const char * os_win32_cpp_cvsid = "$Id: os_win32.cpp 4559 2017-10-22 16:17:50Z chrfranke $"; + ///////////////////////////////////////////////////////////////////////////// // Windows I/O-controls, some declarations are missing in the include files @@ -263,6 +276,45 @@ ASSERT_SIZEOF(STORAGE_DEVICE_DESCRIPTOR, 36+1+3); ASSERT_SIZEOF(STORAGE_PROPERTY_QUERY, 8+1+3); +// IOCTL_STORAGE_QUERY_PROPERTY: Windows 10 enhancements + +namespace win10 { + + // enum STORAGE_PROPERTY_ID: new values + const STORAGE_PROPERTY_ID StorageAdapterProtocolSpecificProperty = (STORAGE_PROPERTY_ID)49; + const STORAGE_PROPERTY_ID StorageDeviceProtocolSpecificProperty = (STORAGE_PROPERTY_ID)50; + + typedef enum _STORAGE_PROTOCOL_TYPE { + ProtocolTypeUnknown = 0, + ProtocolTypeScsi, + ProtocolTypeAta, + ProtocolTypeNvme, + ProtocolTypeSd + } STORAGE_PROTOCOL_TYPE; + + typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE { + NVMeDataTypeUnknown = 0, + NVMeDataTypeIdentify, + NVMeDataTypeLogPage, + NVMeDataTypeFeature + } STORAGE_PROTOCOL_NVME_DATA_TYPE; + + typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA { + STORAGE_PROTOCOL_TYPE ProtocolType; + ULONG DataType; + ULONG ProtocolDataRequestValue; + ULONG ProtocolDataRequestSubValue; + ULONG ProtocolDataOffset; + ULONG ProtocolDataLength; + ULONG FixedProtocolReturnData; + ULONG Reserved[3]; + } STORAGE_PROTOCOL_SPECIFIC_DATA; + + ASSERT_SIZEOF(STORAGE_PROTOCOL_SPECIFIC_DATA, 40); + +} // namespace win10 + + // IOCTL_STORAGE_PREDICT_FAILURE ASSERT_CONST(IOCTL_STORAGE_PREDICT_FAILURE, 0x002d1100); @@ -303,6 +355,39 @@ ASSERT_SIZEOF(GETVERSIONINPARAMS_EX, sizeof(GETVERSIONINPARAMS)); ASSERT_SIZEOF(SENDCMDINPARAMS_EX, sizeof(SENDCMDINPARAMS)); +// NVME_PASS_THROUGH + +#ifndef NVME_PASS_THROUGH_SRB_IO_CODE + +#define NVME_SIG_STR "NvmeMini" +#define NVME_STORPORT_DRIVER 0xe000 + +#define NVME_PASS_THROUGH_SRB_IO_CODE \ + CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#pragma pack(1) +typedef struct _NVME_PASS_THROUGH_IOCTL +{ + SRB_IO_CONTROL SrbIoCtrl; + ULONG VendorSpecific[6]; + ULONG NVMeCmd[16]; // Command DW[0...15] + ULONG CplEntry[4]; // Completion DW[0...3] + ULONG Direction; // 0=No, 1=Out, 2=In, 3=I/O + ULONG QueueId; // 0=AdminQ + ULONG DataBufferLen; // sizeof(DataBuffer) if Data In + ULONG MetaDataLen; + ULONG ReturnBufferLen; // offsetof(DataBuffer), plus sizeof(DataBuffer) if Data Out + UCHAR DataBuffer[1]; +} NVME_PASS_THROUGH_IOCTL; +#pragma pack() + +#endif // NVME_PASS_THROUGH_SRB_IO_CODE + +ASSERT_CONST(NVME_PASS_THROUGH_SRB_IO_CODE, (int)0xe0002000); +ASSERT_SIZEOF(NVME_PASS_THROUGH_IOCTL, 152+1); +ASSERT_SIZEOF(NVME_PASS_THROUGH_IOCTL, offsetof(NVME_PASS_THROUGH_IOCTL, DataBuffer)+1); + + // CSMI structs ASSERT_SIZEOF(IOCTL_HEADER, sizeof(SRB_IO_CONTROL)); @@ -310,6 +395,10 @@ ASSERT_SIZEOF(CSMI_SAS_DRIVER_INFO_BUFFER, 204); ASSERT_SIZEOF(CSMI_SAS_PHY_INFO_BUFFER, 2080); ASSERT_SIZEOF(CSMI_SAS_STP_PASSTHRU_BUFFER, 168); +// aacraid struct + +ASSERT_SIZEOF(SCSI_REQUEST_BLOCK, SELECT_WIN_32_64(64, 88)); + } // extern "C" ///////////////////////////////////////////////////////////////////////////// @@ -320,15 +409,57 @@ namespace os_win32 { // no need to publish anything, name provided for Doxygen #pragma warning(disable:4250) #endif -// Running on Win9x/ME ? -#if WIN9X_SUPPORT -// Set true in win9x_smart_interface ctor. -static bool win9x = false; -#else -// Never true (const allows compiler to remove dead code). -const bool win9x = false; -#endif +static int is_permissive() +{ + if (!failuretest_permissive) { + pout("To continue, add one or more '-T permissive' options.\n"); + return 0; + } + failuretest_permissive--; + return 1; +} + +// return number for drive letter, -1 on error +// "[A-Za-z]:([/\\][.]?)?" => 0-25 +// Accepts trailing '"' to fix broken "X:\" parameter passing from .bat files +static int drive_letter(const char * s) +{ + return ( (('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z')) + && s[1] == ':' + && (!s[2] || ( strchr("/\\\"", s[2]) + && (!s[3] || (s[3] == '.' && !s[4]))) ) ? + (s[0] & 0x1f) - 1 : -1); +} + +// Skip trailing "/dev/", do not allow "/dev/X:" +static const char * skipdev(const char * s) +{ + return (!strncmp(s, "/dev/", 5) && drive_letter(s+5) < 0 ? s+5 : s); +} + +// "sd[a-z]" -> 0-25, "sd[a-z][a-z]" -> 26-701 +static int sdxy_to_phydrive(const char (& xy)[2+1]) +{ + int phydrive = xy[0] - 'a'; + if (xy[1]) + phydrive = (phydrive + 1) * ('z' - 'a' + 1) + (xy[1] - 'a'); + return phydrive; +} + +static void copy_swapped(unsigned char * dest, const char * src, int destsize) +{ + int srclen = strcspn(src, "\r\n"); + int i; + for (i = 0; i < destsize-1 && i < srclen-1; i+=2) { + dest[i] = src[i+1]; dest[i+1] = src[i]; + } + if (i < destsize-1 && i < srclen) + dest[i+1] = src[i]; +} + +///////////////////////////////////////////////////////////////////////////// +// win_smart_device class win_smart_device : virtual public /*implements*/ smart_device @@ -359,4561 +490,4229 @@ private: }; -///////////////////////////////////////////////////////////////////////////// +// Common routines for devices with HANDLEs -class win_ata_device -: public /*implements*/ ata_device, - public /*extends*/ win_smart_device +win_smart_device::~win_smart_device() throw() { -public: - win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); + if (m_fh != INVALID_HANDLE_VALUE) + ::CloseHandle(m_fh); +} - virtual ~win_ata_device() throw(); +bool win_smart_device::is_open() const +{ + return (m_fh != INVALID_HANDLE_VALUE); +} - virtual bool open(); +bool win_smart_device::close() +{ + if (m_fh == INVALID_HANDLE_VALUE) + return true; + BOOL rc = ::CloseHandle(m_fh); + m_fh = INVALID_HANDLE_VALUE; + return !!rc; +} - virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); - virtual bool ata_identify_is_cached() const; +///////////////////////////////////////////////////////////////////////////// -private: - bool open(int phydrive, int logdrive, const char * options, int port); +#define SMART_CYL_LOW 0x4F +#define SMART_CYL_HI 0xC2 - std::string m_options; - bool m_usr_options; // options set by user? - bool m_admin; // open with admin access? - bool m_id_is_cached; // ata_identify_is_cached() return value. - bool m_is_3ware; // AMCC/3ware controller detected? - int m_drive, m_port; - int m_smartver_state; -}; +static void print_ide_regs(const IDEREGS * r, int out) +{ + pout("%s=0x%02x,%s=0x%02x, SC=0x%02x, SN=0x%02x, CL=0x%02x, CH=0x%02x, SEL=0x%02x\n", + (out?"STS":"CMD"), r->bCommandReg, (out?"ERR":" FR"), r->bFeaturesReg, + r->bSectorCountReg, r->bSectorNumberReg, r->bCylLowReg, r->bCylHighReg, r->bDriveHeadReg); +} +static void print_ide_regs_io(const IDEREGS * ri, const IDEREGS * ro) +{ + pout(" Input : "); print_ide_regs(ri, 0); + if (ro) { + pout(" Output: "); print_ide_regs(ro, 1); + } +} ///////////////////////////////////////////////////////////////////////////// -class win_scsi_device -: public /*implements*/ scsi_device, - virtual public /*extends*/ win_smart_device +// call SMART_GET_VERSION, return device map or -1 on error + +static int smart_get_version(HANDLE hdevice, GETVERSIONINPARAMS_EX * ata_version_ex = 0) { -public: - win_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type); + GETVERSIONINPARAMS vers; memset(&vers, 0, sizeof(vers)); + const GETVERSIONINPARAMS_EX & vers_ex = (const GETVERSIONINPARAMS_EX &)vers; + DWORD num_out; - virtual bool open(); + if (!DeviceIoControl(hdevice, SMART_GET_VERSION, + NULL, 0, &vers, sizeof(vers), &num_out, NULL)) { + if (ata_debugmode) + pout(" SMART_GET_VERSION failed, Error=%u\n", (unsigned)GetLastError()); + errno = ENOSYS; + return -1; + } + assert(num_out == sizeof(GETVERSIONINPARAMS)); - virtual bool scsi_pass_through(scsi_cmnd_io * iop); + if (ata_debugmode > 1) { + pout(" SMART_GET_VERSION suceeded, bytes returned: %u\n" + " Vers = %d.%d, Caps = 0x%x, DeviceMap = 0x%02x\n", + (unsigned)num_out, vers.bVersion, vers.bRevision, + (unsigned)vers.fCapabilities, vers.bIDEDeviceMap); + if (vers_ex.wIdentifier == SMART_VENDOR_3WARE) + pout(" Identifier = %04x(3WARE), ControllerId=%u, DeviceMapEx = 0x%08x\n", + vers_ex.wIdentifier, vers_ex.wControllerId, (unsigned)vers_ex.dwDeviceMapEx); + } -private: - bool open(int pd_num, int ld_num, int tape_num, int sub_addr); -}; + if (ata_version_ex) + *ata_version_ex = vers_ex; + // TODO: Check vers.fCapabilities here? + return vers.bIDEDeviceMap; +} -///////////////////////////////////////////////////////////////////////////// -#if WIN9X_SUPPORT +// call SMART_* ioctl -class win_aspi_device -: public /*implements*/ scsi_device +static int smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize, int port) { -public: - win_aspi_device(smart_interface * intf, const char * dev_name, const char * req_type); - - virtual bool is_open() const; - - virtual bool open(); + SENDCMDINPARAMS inpar; + SENDCMDINPARAMS_EX & inpar_ex = (SENDCMDINPARAMS_EX &)inpar; - virtual bool close(); + unsigned char outbuf[sizeof(SENDCMDOUTPARAMS)-1 + 512]; + const SENDCMDOUTPARAMS * outpar; + DWORD code, num_out; + unsigned int size_out; + const char * name; - virtual bool scsi_pass_through(scsi_cmnd_io * iop); + memset(&inpar, 0, sizeof(inpar)); + inpar.irDriveRegs = *regs; -private: - int m_adapter; - unsigned char m_id; -}; + // Older drivers may require bits 5 and 7 set + // ATA-3: bits shall be set, ATA-4 and later: bits are obsolete + inpar.irDriveRegs.bDriveHeadReg |= 0xa0; -#endif // WIN9X_SUPPORT + // Drive number 0-3 was required on Win9x/ME only + //inpar.irDriveRegs.bDriveHeadReg |= (drive & 1) << 4; + //inpar.bDriveNumber = drive; + if (port >= 0) { + // Set RAID port + inpar_ex.wIdentifier = SMART_VENDOR_3WARE; + inpar_ex.bPortNumber = port; + } -////////////////////////////////////////////////////////////////////// + if (datasize == 512) { + code = SMART_RCV_DRIVE_DATA; name = "SMART_RCV_DRIVE_DATA"; + inpar.cBufferSize = size_out = 512; + } + else if (datasize == 0) { + code = SMART_SEND_DRIVE_COMMAND; name = "SMART_SEND_DRIVE_COMMAND"; + if (regs->bFeaturesReg == ATA_SMART_STATUS) + size_out = sizeof(IDEREGS); // ioctl returns new IDEREGS as data + // Note: cBufferSize must be 0 on Win9x + else + size_out = 0; + } + else { + errno = EINVAL; + return -1; + } -class csmi_device -: virtual public /*extends*/ smart_device -{ -public: - /// Get phy info - bool get_phy_info(CSMI_SAS_PHY_INFO & phy_info); + memset(&outbuf, 0, sizeof(outbuf)); - /// Check physical drive existence - bool check_phy(const CSMI_SAS_PHY_INFO & phy_info, unsigned phy_no); + if (!DeviceIoControl(hdevice, code, &inpar, sizeof(SENDCMDINPARAMS)-1, + outbuf, sizeof(SENDCMDOUTPARAMS)-1 + size_out, &num_out, NULL)) { + // CAUTION: DO NOT change "regs" Parameter in this case, see win_ata_device::ata_pass_through() + long err = GetLastError(); + if (ata_debugmode && (err != ERROR_INVALID_PARAMETER || ata_debugmode > 1)) { + pout(" %s failed, Error=%ld\n", name, err); + print_ide_regs_io(regs, NULL); + } + errno = ( err == ERROR_INVALID_FUNCTION/*9x*/ + || err == ERROR_INVALID_PARAMETER/*NT/2K/XP*/ + || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); + return -1; + } + // NOTE: On Win9x, inpar.irDriveRegs now contains the returned regs -protected: - csmi_device() - : smart_device(never_called) - { memset(&m_phy_ent, 0, sizeof(m_phy_ent)); } + outpar = (const SENDCMDOUTPARAMS *)outbuf; - /// Select physical drive - bool select_phy(unsigned phy_no); + if (outpar->DriverStatus.bDriverError) { + if (ata_debugmode) { + pout(" %s failed, DriverError=0x%02x, IDEError=0x%02x\n", name, + outpar->DriverStatus.bDriverError, outpar->DriverStatus.bIDEError); + print_ide_regs_io(regs, NULL); + } + errno = (!outpar->DriverStatus.bIDEError ? ENOSYS : EIO); + return -1; + } - /// Get info for selected physical drive - const CSMI_SAS_PHY_ENTITY & get_phy_ent() const - { return m_phy_ent; } + if (ata_debugmode > 1) { + pout(" %s suceeded, bytes returned: %u (buffer %u)\n", name, + (unsigned)num_out, (unsigned)outpar->cBufferSize); + print_ide_regs_io(regs, (regs->bFeaturesReg == ATA_SMART_STATUS ? + (const IDEREGS *)(outpar->bBuffer) : NULL)); + } - /// Call platform-specific CSMI ioctl - virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer, - unsigned csmi_bufsiz) = 0; + if (datasize) + memcpy(data, outpar->bBuffer, 512); + else if (regs->bFeaturesReg == ATA_SMART_STATUS) { + if (nonempty(outpar->bBuffer, sizeof(IDEREGS))) + memcpy(regs, outpar->bBuffer, sizeof(IDEREGS)); + else { // Workaround for driver not returning regs + if (ata_debugmode) + pout(" WARNING: driver does not return ATA registers in output buffer!\n"); + *regs = inpar.irDriveRegs; + } + } -private: - CSMI_SAS_PHY_ENTITY m_phy_ent; ///< CSMI info for this phy -}; + return 0; +} -class csmi_ata_device -: virtual public /*extends*/ csmi_device, - virtual public /*implements*/ ata_device -{ -public: - virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); - -protected: - csmi_ata_device() - : smart_device(never_called) { } -}; - - -////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +// IDE PASS THROUGH (2000, XP, undocumented) +// +// Based on WinATA.cpp, 2002 c't/Matthias Withopf +// ftp://ftp.heise.de/pub/ct/listings/0207-218.zip -class win_csmi_device -: public /*implements*/ csmi_ata_device +static int ide_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize) { -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 - unsigned m_phy_no; ///< Physical drive number -}; - + if (datasize > 512) { + errno = EINVAL; + return -1; + } + unsigned int size = sizeof(ATA_PASS_THROUGH)-1 + datasize; + ATA_PASS_THROUGH * buf = (ATA_PASS_THROUGH *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); + DWORD num_out; + const unsigned char magic = 0xcf; -////////////////////////////////////////////////////////////////////// + if (!buf) { + errno = ENOMEM; + return -1; + } -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); + buf->IdeReg = *regs; + buf->DataBufferSize = datasize; + if (datasize) + buf->DataBuffer[0] = magic; - virtual bool is_open() const; + if (!DeviceIoControl(hdevice, IOCTL_IDE_PASS_THROUGH, + buf, size, buf, size, &num_out, NULL)) { + long err = GetLastError(); + if (ata_debugmode) { + pout(" IOCTL_IDE_PASS_THROUGH failed, Error=%ld\n", err); + print_ide_regs_io(regs, NULL); + } + VirtualFree(buf, 0, MEM_RELEASE); + errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); + return -1; + } - virtual bool open(); + // Check ATA status + if (buf->IdeReg.bCommandReg/*Status*/ & 0x01) { + if (ata_debugmode) { + pout(" IOCTL_IDE_PASS_THROUGH command failed:\n"); + print_ide_regs_io(regs, &buf->IdeReg); + } + VirtualFree(buf, 0, MEM_RELEASE); + errno = EIO; + return -1; + } - virtual bool close(); + // Check and copy data + if (datasize) { + if ( num_out != size + || (buf->DataBuffer[0] == magic && !nonempty(buf->DataBuffer+1, datasize-1))) { + if (ata_debugmode) { + pout(" IOCTL_IDE_PASS_THROUGH output data missing (%u, %u)\n", + (unsigned)num_out, (unsigned)buf->DataBufferSize); + print_ide_regs_io(regs, &buf->IdeReg); + } + VirtualFree(buf, 0, MEM_RELEASE); + errno = EIO; + return -1; + } + memcpy(data, buf->DataBuffer, datasize); + } -protected: - virtual int ata_command_interface(smart_command_set command, int select, char * data); + if (ata_debugmode > 1) { + pout(" IOCTL_IDE_PASS_THROUGH suceeded, bytes returned: %u (buffer %u)\n", + (unsigned)num_out, (unsigned)buf->DataBufferSize); + print_ide_regs_io(regs, &buf->IdeReg); + } + *regs = buf->IdeReg; -private: - bool m_ident_valid, m_smart_valid; - ata_identify_device m_ident_buf; - ata_smart_values m_smart_buf; -}; + // Caution: VirtualFree() fails if parameter "dwSize" is nonzero + VirtualFree(buf, 0, MEM_RELEASE); + return 0; +} ///////////////////////////////////////////////////////////////////////////// -/// Areca RAID support - -/* ARECA IO CONTROL CODE*/ -#define ARCMSR_IOCTL_READ_RQBUFFER 0x90002004 -#define ARCMSR_IOCTL_WRITE_WQBUFFER 0x90002008 -#define ARCMSR_IOCTL_CLEAR_RQBUFFER 0x9000200C -#define ARCMSR_IOCTL_CLEAR_WQBUFFER 0x90002010 -#define ARCMSR_IOCTL_RETURN_CODE_3F 0x90002018 -#define ARECA_SIG_STR "ARCMSR" - +// ATA PASS THROUGH (Win2003, XP SP2) -// The SRB_IO_CONTROL & SRB_BUFFER structures are used to communicate(to/from) to areca driver -typedef struct _SRB_IO_CONTROL -{ - unsigned int HeaderLength; - unsigned char Signature[8]; - unsigned int Timeout; - unsigned int ControlCode; - unsigned int ReturnCode; - unsigned int Length; -} sSRB_IO_CONTROL; - -typedef struct _SRB_BUFFER -{ - sSRB_IO_CONTROL srbioctl; - unsigned char ioctldatabuffer[1032]; // the buffer to put the command data to/from firmware -} sSRB_BUFFER; +// Warning: +// IOCTL_ATA_PASS_THROUGH[_DIRECT] can only handle one interrupt/DRQ data +// transfer per command. Therefore, multi-sector transfers are only supported +// for the READ/WRITE MULTIPLE [EXT] commands. Other commands like READ/WRITE SECTORS +// or READ/WRITE LOG EXT work only with single sector transfers. +// The latter are supported on Vista (only) through new ATA_FLAGS_NO_MULTIPLE. +// See: +// http://social.msdn.microsoft.com/Forums/en-US/storageplatformata/thread/eb408507-f221-455b-9bbb-d1069b29c4da -class win_areca_device -: public /*implements*/ ata_device, - public /*extends*/ win_smart_device +static int ata_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, IDEREGS * prev_regs, char * data, int datasize) { -public: - win_areca_device(smart_interface * intf, const char * dev_name, HANDLE fh, int disknum, int encnum = 1); - - static int arcmsr_command_handler(HANDLE fh, unsigned long arcmsr_cmd, unsigned char *data, int data_len); + const int max_sectors = 32; // TODO: Allocate dynamic buffer -protected: - virtual bool open(); + typedef struct { + ATA_PASS_THROUGH_EX apt; + ULONG Filler; + UCHAR ucDataBuf[max_sectors * 512]; + } ATA_PASS_THROUGH_EX_WITH_BUFFERS; - virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); + const unsigned char magic = 0xcf; - bool arcmsr_ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); + ATA_PASS_THROUGH_EX_WITH_BUFFERS ab; memset(&ab, 0, sizeof(ab)); + ab.apt.Length = sizeof(ATA_PASS_THROUGH_EX); + //ab.apt.PathId = 0; + //ab.apt.TargetId = 0; + //ab.apt.Lun = 0; + ab.apt.TimeOutValue = 10; + unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf); + ab.apt.DataBufferOffset = size; -private: - int m_disknum; ///< Disk number. - int m_encnum; ///< Enclosure number. -}; + if (datasize > 0) { + if (datasize > (int)sizeof(ab.ucDataBuf)) { + errno = EINVAL; + return -1; + } + ab.apt.AtaFlags = ATA_FLAGS_DATA_IN; + ab.apt.DataTransferLength = datasize; + size += datasize; + ab.ucDataBuf[0] = magic; + } + else if (datasize < 0) { + if (-datasize > (int)sizeof(ab.ucDataBuf)) { + errno = EINVAL; + return -1; + } + ab.apt.AtaFlags = ATA_FLAGS_DATA_OUT; + ab.apt.DataTransferLength = -datasize; + size += -datasize; + memcpy(ab.ucDataBuf, data, -datasize); + } + else { + assert(ab.apt.AtaFlags == 0); + assert(ab.apt.DataTransferLength == 0); + } + assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS)); + IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile; + IDEREGS * ptfregs = (IDEREGS *)ab.apt.PreviousTaskFile; + *ctfregs = *regs; -////////////////////////////////////////////////////////////////////// -// Platform specific interfaces + if (prev_regs) { + *ptfregs = *prev_regs; + ab.apt.AtaFlags |= ATA_FLAGS_48BIT_COMMAND; + } -// Common to all windows flavors -class win_smart_interface -: public /*implements part of*/ smart_interface -{ -public: - virtual std::string get_os_version_str(); + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH, + &ab, size, &ab, size, &num_out, NULL)) { + long err = GetLastError(); + if (ata_debugmode) { + pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err); + print_ide_regs_io(regs, NULL); + } + errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); + return -1; + } - virtual std::string get_app_examples(const char * appname); + // Check ATA status + if (ctfregs->bCommandReg/*Status*/ & (0x01/*Err*/|0x08/*DRQ*/)) { + if (ata_debugmode) { + pout(" IOCTL_ATA_PASS_THROUGH command failed:\n"); + print_ide_regs_io(regs, ctfregs); + } + errno = EIO; + return -1; + } -#ifndef __CYGWIN__ - virtual int64_t get_timer_usec(); -#endif + // Check and copy data + if (datasize > 0) { + if ( num_out != size + || (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) { + if (ata_debugmode) { + pout(" IOCTL_ATA_PASS_THROUGH output data missing (%u)\n", (unsigned)num_out); + print_ide_regs_io(regs, ctfregs); + } + errno = EIO; + return -1; + } + memcpy(data, ab.ucDataBuf, datasize); + } -//virtual bool scan_smart_devices(smart_device_list & devlist, const char * type, -// const char * pattern = 0); + if (ata_debugmode > 1) { + pout(" IOCTL_ATA_PASS_THROUGH suceeded, bytes returned: %u\n", (unsigned)num_out); + print_ide_regs_io(regs, ctfregs); + } + *regs = *ctfregs; + if (prev_regs) + *prev_regs = *ptfregs; -protected: - virtual ata_device * get_ata_device(const char * name, const char * type); + return 0; +} -//virtual scsi_device * get_scsi_device(const char * name, const char * type); - virtual smart_device * autodetect_smart_device(const char * name); -}; +///////////////////////////////////////////////////////////////////////////// +// SMART IOCTL via SCSI MINIPORT ioctl -#if WIN9X_SUPPORT +// This function is handled by ATAPI port driver (atapi.sys) or by SCSI +// miniport driver (via SCSI port driver scsiport.sys). +// It can be used to skip the missing or broken handling of some SMART +// command codes (e.g. READ_LOG) in the disk class driver (disk.sys) -// Win9x/ME reduced functionality -class win9x_smart_interface -: public /*extends*/ win_smart_interface +static int ata_via_scsi_miniport_smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize) { -public: - win9x_smart_interface() - { win9x = true; } - - virtual bool scan_smart_devices(smart_device_list & devlist, const char * type, - const char * pattern = 0); - -protected: - virtual scsi_device * get_scsi_device(const char * name, const char * type); - -private: - bool ata_scan(smart_device_list & devlist); - - bool scsi_scan(smart_device_list & devlist); -}; + // Select code + DWORD code = 0; const char * name = 0; + if (regs->bCommandReg == ATA_IDENTIFY_DEVICE) { + code = IOCTL_SCSI_MINIPORT_IDENTIFY; name = "IDENTIFY"; + } + else if (regs->bCommandReg == ATA_SMART_CMD) switch (regs->bFeaturesReg) { + case ATA_SMART_READ_VALUES: + code = IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS; name = "READ_SMART_ATTRIBS"; break; + case ATA_SMART_READ_THRESHOLDS: + code = IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS; name = "READ_SMART_THRESHOLDS"; break; + case ATA_SMART_ENABLE: + code = IOCTL_SCSI_MINIPORT_ENABLE_SMART; name = "ENABLE_SMART"; break; + case ATA_SMART_DISABLE: + code = IOCTL_SCSI_MINIPORT_DISABLE_SMART; name = "DISABLE_SMART"; break; + case ATA_SMART_STATUS: + code = IOCTL_SCSI_MINIPORT_RETURN_STATUS; name = "RETURN_STATUS"; break; + case ATA_SMART_AUTOSAVE: + code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE; name = "ENABLE_DISABLE_AUTOSAVE"; break; + //case ATA_SMART_SAVE: // obsolete since ATA-6, not used by smartmontools + // code = IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES; name = "SAVE_ATTRIBUTE_VALUES"; break; + case ATA_SMART_IMMEDIATE_OFFLINE: + code = IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS; name = "EXECUTE_OFFLINE_DIAGS"; break; + case ATA_SMART_AUTO_OFFLINE: + code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE; name = "ENABLE_DISABLE_AUTO_OFFLINE"; break; + case ATA_SMART_READ_LOG_SECTOR: + code = IOCTL_SCSI_MINIPORT_READ_SMART_LOG; name = "READ_SMART_LOG"; break; + case ATA_SMART_WRITE_LOG_SECTOR: + code = IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG; name = "WRITE_SMART_LOG"; break; + } + if (!code) { + errno = ENOSYS; + return -1; + } -#endif // WIN9X_SUPPORT + // Set SRB + struct { + SRB_IO_CONTROL srbc; + union { + SENDCMDINPARAMS in; + SENDCMDOUTPARAMS out; + } params; + char space[512-1]; + } sb; + ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1+512); + memset(&sb, 0, sizeof(sb)); -// WinNT,2000,XP,... -class winnt_smart_interface -: public /*extends*/ win_smart_interface -{ -public: - virtual bool disable_system_auto_standby(bool disable); + unsigned size; + if (datasize > 0) { + if (datasize > (int)sizeof(sb.space)+1) { + errno = EINVAL; + return -1; + } + size = datasize; + } + else if (datasize < 0) { + if (-datasize > (int)sizeof(sb.space)+1) { + errno = EINVAL; + return -1; + } + size = -datasize; + memcpy(sb.params.in.bBuffer, data, size); + } + else if (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS) + size = sizeof(IDEREGS); + else + size = 0; + sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL); + memcpy(sb.srbc.Signature, "SCSIDISK", 8); // atapi.sys + sb.srbc.Timeout = 60; // seconds + sb.srbc.ControlCode = code; + //sb.srbc.ReturnCode = 0; + sb.srbc.Length = sizeof(SENDCMDINPARAMS)-1 + size; + sb.params.in.irDriveRegs = *regs; + sb.params.in.cBufferSize = size; - virtual bool scan_smart_devices(smart_device_list & devlist, const char * type, - const char * pattern = 0); + // Call miniport ioctl + size += sizeof(SRB_IO_CONTROL) + sizeof(SENDCMDINPARAMS)-1; + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, + &sb, size, &sb, size, &num_out, NULL)) { + long err = GetLastError(); + if (ata_debugmode) { + pout(" IOCTL_SCSI_MINIPORT_%s failed, Error=%ld\n", name, err); + print_ide_regs_io(regs, NULL); + } + errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); + return -1; + } -protected: - virtual scsi_device * get_scsi_device(const char * name, const char * type); + // Check result + if (sb.srbc.ReturnCode) { + if (ata_debugmode) { + pout(" IOCTL_SCSI_MINIPORT_%s failed, ReturnCode=0x%08x\n", name, (unsigned)sb.srbc.ReturnCode); + print_ide_regs_io(regs, NULL); + } + errno = EIO; + return -1; + } - virtual smart_device * autodetect_smart_device(const char * name); + if (sb.params.out.DriverStatus.bDriverError) { + if (ata_debugmode) { + pout(" IOCTL_SCSI_MINIPORT_%s failed, DriverError=0x%02x, IDEError=0x%02x\n", name, + sb.params.out.DriverStatus.bDriverError, sb.params.out.DriverStatus.bIDEError); + print_ide_regs_io(regs, NULL); + } + errno = (!sb.params.out.DriverStatus.bIDEError ? ENOSYS : EIO); + return -1; + } - virtual smart_device * get_custom_smart_device(const char * name, const char * type); + if (ata_debugmode > 1) { + pout(" IOCTL_SCSI_MINIPORT_%s suceeded, bytes returned: %u (buffer %u)\n", name, + (unsigned)num_out, (unsigned)sb.params.out.cBufferSize); + print_ide_regs_io(regs, (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS ? + (const IDEREGS *)(sb.params.out.bBuffer) : 0)); + } - virtual std::string get_valid_custom_dev_types_str(); -}; + if (datasize > 0) + memcpy(data, sb.params.out.bBuffer, datasize); + else if (datasize == 0 && code == IOCTL_SCSI_MINIPORT_RETURN_STATUS) + memcpy(regs, sb.params.out.bBuffer, sizeof(IDEREGS)); + return 0; +} -////////////////////////////////////////////////////////////////////// -#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 +///////////////////////////////////////////////////////////////////////////// +// ATA PASS THROUGH via 3ware specific SCSI MINIPORT ioctl -// Return info string about build host and OS version -std::string win_smart_interface::get_os_version_str() +static int ata_via_3ware_miniport_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize, int port) { - 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)); + struct { + SRB_IO_CONTROL srbc; + IDEREGS regs; + UCHAR buffer[512]; + } sb; + ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(IDEREGS)+512); - OSVERSIONINFOEXA vi; memset(&vi, 0, sizeof(vi)); - vi.dwOSVersionInfoSize = sizeof(vi); - if (!GetVersionExA((OSVERSIONINFOA *)&vi)) { - memset(&vi, 0, sizeof(vi)); - vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); - if (!GetVersionExA((OSVERSIONINFOA *)&vi)) - return vstr; + if (!(0 <= datasize && datasize <= (int)sizeof(sb.buffer) && port >= 0)) { + errno = EINVAL; + return -1; } + memset(&sb, 0, sizeof(sb)); + strncpy((char *)sb.srbc.Signature, "<3ware>", sizeof(sb.srbc.Signature)); + sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL); + sb.srbc.Timeout = 60; // seconds + sb.srbc.ControlCode = 0xA0000000; + sb.srbc.ReturnCode = 0; + sb.srbc.Length = sizeof(IDEREGS) + (datasize > 0 ? datasize : 1); + sb.regs = *regs; + sb.regs.bReserved = port; - if (vi.dwPlatformId > 0xff || vi.dwMajorVersion > 0xff || vi.dwMinorVersion > 0xff) - return vstr; - - const char * w; - switch (vi.dwPlatformId << 16 | vi.dwMajorVersion << 8 | vi.dwMinorVersion) { - case VER_PLATFORM_WIN32_WINDOWS<<16|0x0400| 0: - w = (vi.szCSDVersion[1] == 'B' || - vi.szCSDVersion[1] == 'C' ? "95-osr2" : "95"); break; - case VER_PLATFORM_WIN32_WINDOWS<<16|0x0400|10: - w = (vi.szCSDVersion[1] == 'A' ? "98se" : "98"); break; - case VER_PLATFORM_WIN32_WINDOWS<<16|0x0400|90: w = "me"; break; - //case VER_PLATFORM_WIN32_NT <<16|0x0300|51: w = "nt3.51"; break; - case VER_PLATFORM_WIN32_NT <<16|0x0400| 0: w = "nt4"; break; - case VER_PLATFORM_WIN32_NT <<16|0x0500| 0: w = "2000"; break; - case VER_PLATFORM_WIN32_NT <<16|0x0500| 1: - w = (!GetSystemMetrics(87/*SM_MEDIACENTER*/) ? "xp" - : "xp-mc"); break; - case VER_PLATFORM_WIN32_NT <<16|0x0500| 2: - w = (!GetSystemMetrics(89/*SM_SERVERR2*/) ? "2003" - : "2003r2"); break; - case VER_PLATFORM_WIN32_NT <<16|0x0600| 0: - w = (vi.wProductType == VER_NT_WORKSTATION ? "vista" - : "2008" ); break; - case VER_PLATFORM_WIN32_NT <<16|0x0600| 1: - w = (vi.wProductType == VER_NT_WORKSTATION ? "win7" - : "2008r2"); break; - case VER_PLATFORM_WIN32_NT <<16|0x0600| 2: - w = (vi.wProductType == VER_NT_WORKSTATION ? "win8" - : "win8s"); break; - default: w = 0; break; + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, + &sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) { + long err = GetLastError(); + if (ata_debugmode) { + pout(" ATA via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err); + print_ide_regs_io(regs, NULL); + } + errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); + return -1; } - const char * w64 = ""; -#ifndef _WIN64 - if (is_wow64()) - w64 = "(64)"; -#endif - - if (!w) - snprintf(vptr, vlen, "-%s%lu.%lu%s", - (vi.dwPlatformId==VER_PLATFORM_WIN32_NT ? "nt" : "9x"), - vi.dwMajorVersion, vi.dwMinorVersion, w64); - else if (vi.wServicePackMinor) - snprintf(vptr, vlen, "-%s%s-sp%u.%u", w, w64, vi.wServicePackMajor, vi.wServicePackMinor); - else if (vi.wServicePackMajor) - snprintf(vptr, vlen, "-%s%s-sp%u", w, w64, vi.wServicePackMajor); - 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)) + if (sb.srbc.ReturnCode) { + if (ata_debugmode) { + pout(" ATA via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)sb.srbc.ReturnCode); + print_ide_regs_io(regs, NULL); + } + errno = EIO; return -1; + } - return (t.QuadPart * 1000000LL) / freq; -} -#endif // __CYGWIN__ + // Copy data + if (datasize > 0) + memcpy(data, sb.buffer, datasize); + if (ata_debugmode > 1) { + pout(" ATA via IOCTL_SCSI_MINIPORT suceeded, bytes returned: %u\n", (unsigned)num_out); + print_ide_regs_io(regs, &sb.regs); + } + *regs = sb.regs; -// Return value for device detection functions -enum win_dev_type { DEV_UNKNOWN = 0, DEV_ATA, DEV_SCSI, DEV_USB }; + return 0; +} -static win_dev_type get_phy_drive_type(int drive); -static win_dev_type get_phy_drive_type(int drive, GETVERSIONINPARAMS_EX * ata_version_ex); -static win_dev_type get_log_drive_type(int drive); -static bool get_usb_id(int drive, unsigned short & vendor_id, - unsigned short & product_id); -static const char * ata_get_def_options(void); +///////////////////////////////////////////////////////////////////////////// +// 3ware specific call to update the devicemap returned by SMART_GET_VERSION. +// 3DM/CLI "Rescan Controller" function does not to always update it. -static int is_permissive() +static int update_3ware_devicemap_ioctl(HANDLE hdevice) { - if (!failuretest_permissive) { - pout("To continue, add one or more '-T permissive' options.\n"); - return 0; - } - failuretest_permissive--; - return 1; -} + SRB_IO_CONTROL srbc; + memset(&srbc, 0, sizeof(srbc)); + strncpy((char *)srbc.Signature, "<3ware>", sizeof(srbc.Signature)); + srbc.HeaderLength = sizeof(SRB_IO_CONTROL); + srbc.Timeout = 60; // seconds + srbc.ControlCode = 0xCC010014; + srbc.ReturnCode = 0; + srbc.Length = 0; -// return number for drive letter, -1 on error -// "[A-Za-z]:([/\\][.]?)?" => 0-25 -// Accepts trailing '"' to fix broken "X:\" parameter passing from .bat files -static int drive_letter(const char * s) -{ - return ( (('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z')) - && s[1] == ':' - && (!s[2] || ( strchr("/\\\"", s[2]) - && (!s[3] || (s[3] == '.' && !s[4]))) ) ? - (s[0] & 0x1f) - 1 : -1); + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, + &srbc, sizeof(srbc), &srbc, sizeof(srbc), &num_out, NULL)) { + long err = GetLastError(); + if (ata_debugmode) + pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err); + errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); + return -1; + } + if (srbc.ReturnCode) { + if (ata_debugmode) + pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)srbc.ReturnCode); + errno = EIO; + return -1; + } + if (ata_debugmode > 1) + pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT suceeded\n"); + return 0; } -// Skip trailing "/dev/", do not allow "/dev/X:" -static const char * skipdev(const char * s) -{ - return (!strncmp(s, "/dev/", 5) && drive_letter(s+5) < 0 ? s+5 : s); -} -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); -} +///////////////////////////////////////////////////////////////////////////// +// IOCTL_STORAGE_QUERY_PROPERTY + +union STORAGE_DEVICE_DESCRIPTOR_DATA { + STORAGE_DEVICE_DESCRIPTOR desc; + char raw[256]; +}; -#ifdef WIN9X_SUPPORT +// Get STORAGE_DEVICE_DESCRIPTOR_DATA for device. +// (This works without admin rights) -scsi_device * win9x_smart_interface::get_scsi_device(const char * name, const char * type) +static int storage_query_property_ioctl(HANDLE hdevice, STORAGE_DEVICE_DESCRIPTOR_DATA * data) { - return new win_aspi_device(this, name, type); -} + STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, {0} }; + memset(data, 0, sizeof(*data)); -#endif + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY, + &query, sizeof(query), data, sizeof(*data), &num_out, NULL)) { + if (ata_debugmode > 1 || scsi_debugmode > 1) + pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%u\n", (unsigned)GetLastError()); + errno = ENOSYS; + return -1; + } -scsi_device * winnt_smart_interface::get_scsi_device(const char * name, const char * type) -{ - const char * testname = skipdev(name); - if (!strncmp(testname, "scsi", 4)) -#if WIN9X_SUPPORT - return new win_aspi_device(this, name, type); -#else - return (set_err(EINVAL, "ASPI interface not supported"), (scsi_device *)0); -#endif - return new win_scsi_device(this, name, type); + if (ata_debugmode > 1 || scsi_debugmode > 1) { + pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n" + " Vendor: \"%s\"\n" + " Product: \"%s\"\n" + " Revision: \"%s\"\n" + " Removable: %s\n" + " BusType: 0x%02x\n", + (data->desc.VendorIdOffset ? data->raw+data->desc.VendorIdOffset : "(null)"), + (data->desc.ProductIdOffset ? data->raw+data->desc.ProductIdOffset : "(null)"), + (data->desc.ProductRevisionOffset ? data->raw+data->desc.ProductRevisionOffset : "(null)"), + (data->desc.RemovableMedia? "Yes":"No"), data->desc.BusType + ); + } + return 0; } -static win_dev_type get_dev_type(const char * name, int & phydrive) + +///////////////////////////////////////////////////////////////////////////// +// IOCTL_STORAGE_PREDICT_FAILURE + +// Call IOCTL_STORAGE_PREDICT_FAILURE, return PredictFailure value +// or -1 on error, opionally return VendorSpecific data. +// (This works without admin rights) + +static int storage_predict_failure_ioctl(HANDLE hdevice, char * data = 0) { - phydrive = -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; + STORAGE_PREDICT_FAILURE pred; + memset(&pred, 0, sizeof(pred)); - int logdrive = drive_letter(name); - if (logdrive >= 0) { - win_dev_type type = get_log_drive_type(logdrive); - return (type != DEV_UNKNOWN ? type : DEV_SCSI); + DWORD num_out; + if (!DeviceIoControl(hdevice, IOCTL_STORAGE_PREDICT_FAILURE, + 0, 0, &pred, sizeof(pred), &num_out, NULL)) { + if (ata_debugmode > 1) + pout(" IOCTL_STORAGE_PREDICT_FAILURE failed, Error=%u\n", (unsigned)GetLastError()); + errno = ENOSYS; + return -1; } - char drive[1+1] = ""; - if (sscanf(name, "sd%1[a-z]", drive) == 1) { - phydrive = drive[0] - 'a'; - return get_phy_drive_type(phydrive); + if (ata_debugmode > 1) { + pout(" IOCTL_STORAGE_PREDICT_FAILURE returns:\n" + " PredictFailure: 0x%08x\n" + " VendorSpecific: 0x%02x,0x%02x,0x%02x,...,0x%02x\n", + (unsigned)pred.PredictFailure, + pred.VendorSpecific[0], pred.VendorSpecific[1], pred.VendorSpecific[2], + pred.VendorSpecific[sizeof(pred.VendorSpecific)-1] + ); } - - phydrive = -1; - if (sscanf(name, "pd%d", &phydrive) == 1 && phydrive >= 0) - return get_phy_drive_type(phydrive); - return DEV_UNKNOWN; + if (data) + memcpy(data, pred.VendorSpecific, sizeof(pred.VendorSpecific)); + return (!pred.PredictFailure ? 0 : 1); } -smart_device * win_smart_interface::autodetect_smart_device(const char * name) + +// Build IDENTIFY information from STORAGE_DEVICE_DESCRIPTOR +static int get_identify_from_device_property(HANDLE hdevice, ata_identify_device * id) { - const char * testname = skipdev(name); - if (!strncmp(testname, "hd", 2)) - return new win_ata_device(this, name, ""); -#if WIN9X_SUPPORT - if (!strncmp(testname, "scsi", 4)) - return new win_aspi_device(this, name, ""); -#endif - if (!strncmp(testname, "tw_cli", 6)) - return new win_tw_cli_device(this, name, ""); - return 0; -} + STORAGE_DEVICE_DESCRIPTOR_DATA data; + if (storage_query_property_ioctl(hdevice, &data)) + return -1; + memset(id, 0, sizeof(*id)); -smart_device * winnt_smart_interface::get_custom_smart_device(const char * name, const char * type) -{ - // Areca? - int disknum = -1, n1 = -1, n2 = -1; - int encnum = 1; - HANDLE fh = INVALID_HANDLE_VALUE; - char devpath[32]; + // Some drivers split ATA model string into VendorId and ProductId, + // others return it as ProductId only. + char model[sizeof(id->model) + 1] = ""; - 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; - } + unsigned i = 0; + if (data.desc.VendorIdOffset) { + for ( ;i < sizeof(model)-1 && data.raw[data.desc.VendorIdOffset+i]; i++) + model[i] = data.raw[data.desc.VendorIdOffset+i]; + } - 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)); - sprintf(devpath, "\\\\.\\scsi%d:", idx); - if ( (fh = CreateFile( devpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL )) != INVALID_HANDLE_VALUE ) { - if (win_areca_device::arcmsr_command_handler(fh, ARCMSR_IOCTL_RETURN_CODE_3F, NULL, 0) == 0) { - if (ctlrindex-- == 0) { - return new win_areca_device(this, devpath, fh, disknum, encnum); - } - } - CloseHandle(fh); - } - } - set_err(ENOENT, "No Areca controller found"); - } - else - set_err(EINVAL, "Option -d areca,N/E requires device name /dev/arcmsrX"); + if (data.desc.ProductIdOffset) { + while (i > 1 && model[i-2] == ' ') // Keep last blank from VendorId + i--; + // Ignore VendorId "ATA" + if (i <= 4 && !strncmp(model, "ATA", 3) && (i == 3 || model[3] == ' ')) + i = 0; + for (unsigned j = 0; i < sizeof(model)-1 && data.raw[data.desc.ProductIdOffset+j]; i++, j++) + model[i] = data.raw[data.desc.ProductIdOffset+j]; } - return 0; -} + while (i > 0 && model[i-1] == ' ') + i--; + model[i] = 0; + copy_swapped(id->model, model, sizeof(id->model)); -std::string winnt_smart_interface::get_valid_custom_dev_types_str() -{ - return "areca,N[/E]"; -} + if (data.desc.ProductRevisionOffset) + copy_swapped(id->fw_rev, data.raw+data.desc.ProductRevisionOffset, sizeof(id->fw_rev)); + id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid + id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid + return 0; +} -smart_device * winnt_smart_interface::autodetect_smart_device(const char * name) +// Get Serial Number in IDENTIFY from WMI +static bool get_serial_from_wmi(int drive, ata_identify_device * id) { - smart_device * dev = win_smart_interface::autodetect_smart_device(name); - if (dev) - return dev; - - if (!strncmp(skipdev(name), "csmi", 4)) - return new win_csmi_device(this, name, ""); + bool debug = (ata_debugmode > 1); - int phydrive = -1; - win_dev_type type = get_dev_type(name, phydrive); + wbem_services ws; + if (!ws.connect()) { + if (debug) + pout("WMI connect failed\n"); + return false; + } - if (type == DEV_ATA) - return new win_ata_device(this, name, ""); - if (type == DEV_SCSI) - return new win_scsi_device(this, name, ""); + wbem_object wo; + if (!ws.query1(wo, "SELECT Model,SerialNumber FROM Win32_DiskDrive WHERE " + "DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", drive)) + return false; - if (type == DEV_USB) { - // Get USB bridge ID - unsigned short vendor_id = 0, product_id = 0; - if (!(phydrive >= 0 && get_usb_id(phydrive, 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_sat_device(usbtype, new win_scsi_device(this, name, "")); - } + std::string serial = wo.get_str("SerialNumber"); + if (debug) + pout(" WMI:PhysicalDrive%d: \"%s\", S/N:\"%s\"\n", drive, wo.get_str("Model").c_str(), serial.c_str()); - return 0; + copy_swapped(id->serial_no, serial.c_str(), sizeof(id->serial_no)); + return true; } -#if WIN9X_SUPPORT - -// Scan for devices on Win9x/ME +///////////////////////////////////////////////////////////////////////////// +// USB ID detection using WMI -bool win9x_smart_interface::scan_smart_devices(smart_device_list & devlist, - const char * type, const char * pattern /* = 0*/) +// Get USB ID for a physical or logical drive number +static bool get_usb_id(int phydrive, int logdrive, + unsigned short & vendor_id, + unsigned short & product_id) { - if (pattern) { - set_err(EINVAL, "DEVICESCAN with pattern not implemented yet"); + bool debug = (scsi_debugmode > 1); + + wbem_services ws; + if (!ws.connect()) { + if (debug) + pout("WMI connect failed\n"); return false; } - if (!type || !strcmp(type, "ata")) { - if (!ata_scan(devlist)) + // Get device name + std::string name; + + wbem_object wo; + if (0 <= logdrive && logdrive <= 'Z'-'A') { + // Drive letter -> Partition info + if (!ws.query1(wo, "ASSOCIATORS OF {Win32_LogicalDisk.DeviceID=\"%c:\"} WHERE ResultClass = Win32_DiskPartition", + 'A'+logdrive)) return false; - } - if (!type || !strcmp(type, "scsi")) { - if (!scsi_scan(devlist)) + std::string partid = wo.get_str("DeviceID"); + if (debug) + pout("%c: --> \"%s\" -->\n", 'A'+logdrive, partid.c_str()); + + // Partition ID -> Physical drive info + if (!ws.query1(wo, "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\"%s\"} WHERE ResultClass = Win32_DiskDrive", + partid.c_str())) return false; + + name = wo.get_str("Model"); + if (debug) + pout("%s --> \"%s\":\n", wo.get_str("DeviceID").c_str(), name.c_str()); } - return true; -} -#endif // WIN9X_SUPPORT + else if (phydrive >= 0) { + // Physical drive number -> Physical drive info + if (!ws.query1(wo, "SELECT Model FROM Win32_DiskDrive WHERE DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", phydrive)) + return false; + name = wo.get_str("Model"); + if (debug) + pout("\\.\\\\PHYSICALDRIVE%d --> \"%s\":\n", phydrive, name.c_str()); + } + else + return false; -// Scan for devices -bool winnt_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"); + // Get USB_CONTROLLER -> DEVICE associations + wbem_enumerator we; + if (!ws.query(we, "SELECT Antecedent,Dependent FROM Win32_USBControllerDevice")) return false; - } - // Set valid types - bool ata, scsi, usb, csmi; - if (!type) { - ata = scsi = usb = csmi = true; - } - else { - ata = scsi = usb = csmi = false; - if (!strcmp(type, "ata")) - ata = true; - else if (!strcmp(type, "scsi")) - scsi = true; - else if (!strcmp(type, "usb")) - usb = true; - else if (!strcmp(type, "csmi")) - csmi = true; - else { - set_err(EINVAL, "Invalid type '%s', valid arguments are: ata, scsi, usb, csmi", type); - return false; - } - } + unsigned short usb_venid = 0, prev_usb_venid = 0; + unsigned short usb_proid = 0, prev_usb_proid = 0; + std::string prev_usb_ant; + std::string prev_ant, ant, dep; - // Scan up to 10 drives and 2 3ware controllers - const int max_raid = 2; - bool raid_seen[max_raid] = {false, false}; + const regular_expression regex("^.*PnPEntity\\.DeviceID=\"([^\"]*)\"", REG_EXTENDED); - char name[20]; - for (int i = 0; i <= 9; i++) { - sprintf(name, "/dev/sd%c", 'a'+i); - 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; + while (we.next(wo)) { + prev_ant = ant; + // Find next 'USB_CONTROLLER, DEVICE' pair + ant = wo.get_str("Antecedent"); + dep = wo.get_str("Dependent"); - // 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 (int pi = 0; pi < 32; pi++) { - if (vers_ex.dwDeviceMapEx & (1L << pi)) { - sprintf(name+len, ",%u", pi); - devlist.push_back( new win_ata_device(this, name, "ata") ); - } - } - } - else { - devlist.push_back( new win_ata_device(this, name, "ata") ); - } - break; + if (debug && ant != prev_ant) + pout(" %s:\n", ant.c_str()); - case DEV_SCSI: - // STORAGE_QUERY_PROPERTY returned SCSI/SAS/... - if (!scsi) - continue; - devlist.push_back( new win_scsi_device(this, name, "scsi") ); - break; + // Extract DeviceID + regmatch_t match[2]; + if (!(regex.execute(dep.c_str(), 2, match) && match[1].rm_so >= 0)) { + if (debug) + pout(" | (\"%s\")\n", dep.c_str()); + continue; + } - case DEV_USB: - // STORAGE_QUERY_PROPERTY returned USB - if (!usb) - continue; - { - // TODO: Use common function for this and autodetect_smart_device() - // Get USB bridge ID - unsigned short vendor_id = 0, product_id = 0; - if (!get_usb_id(i, vendor_id, product_id)) - continue; - // Get type name for this ID - const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id); - if (!usbtype) - continue; - // Return SAT/USB device for this type - ata_device * dev = get_sat_device(usbtype, new win_scsi_device(this, name, "")); - if (!dev) - continue; - devlist.push_back(dev); - } - break; + std::string devid(dep.c_str()+match[1].rm_so, match[1].rm_eo-match[1].rm_so); - default: - // Unknown type - break; + if (str_starts_with(devid, "USB\\\\VID_")) { + // USB bridge entry, save CONTROLLER, ID + int nc = -1; + if (!(sscanf(devid.c_str(), "USB\\\\VID_%4hx&PID_%4hx%n", + &prev_usb_venid, &prev_usb_proid, &nc) == 2 && nc == 9+4+5+4)) { + prev_usb_venid = prev_usb_proid = 0; + } + prev_usb_ant = ant; + if (debug) + pout(" +-> \"%s\" [0x%04x:0x%04x]\n", devid.c_str(), prev_usb_venid, prev_usb_proid); } - } + else if (str_starts_with(devid, "USBSTOR\\\\") || str_starts_with(devid, "SCSI\\\\")) { + // USBSTORage or SCSI device found + if (debug) + pout(" +--> \"%s\"\n", devid.c_str()); - 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()) + // Retrieve name + wbem_object wo2; + if (!ws.query1(wo2, "SELECT Name FROM Win32_PnPEntity WHERE DeviceID=\"%s\"", devid.c_str())) continue; - CSMI_SAS_PHY_INFO phy_info; - if (!test_dev.get_phy_info(phy_info)) + std::string name2 = wo2.get_str("Name"); + + // Continue if not name of physical disk drive + if (name2 != name) { + if (debug) + pout(" +---> (\"%s\")\n", name2.c_str()); continue; + } - for (int pi = 0; pi < phy_info.bNumberOfPhys; pi++) { - if (!test_dev.check_phy(phy_info, pi)) - continue; - snprintf(name, sizeof(name)-1, "/dev/csmi%d,%d", i, pi); - devlist.push_back( new win_csmi_device(this, name, "ata") ); + // Fail if previous USB bridge is associated to other controller or ID is unknown + if (!(ant == prev_usb_ant && prev_usb_venid)) { + if (debug) + pout(" +---> \"%s\" (Error: No USB bridge found)\n", name2.c_str()); + return false; + } + + // Handle multiple devices with same name + if (usb_venid) { + // Fail if multiple devices with same name have different USB bridge types + if (!(usb_venid == prev_usb_venid && usb_proid == prev_usb_proid)) { + if (debug) + pout(" +---> \"%s\" (Error: More than one USB ID found)\n", name2.c_str()); + return false; + } } + + // Found + usb_venid = prev_usb_venid; + usb_proid = prev_usb_proid; + if (debug) + pout(" +===> \"%s\" [0x%04x:0x%04x]\n", name2.c_str(), usb_venid, usb_proid); + + // Continue to check for duplicate names ... + } + else { + if (debug) + pout(" | \"%s\"\n", devid.c_str()); } } + + if (!usb_venid) + return false; + + vendor_id = usb_venid; + product_id = usb_proid; + return true; } -// 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/hda (Prints all SMART information)\n\n" - " smartctl --smart=on --offlineauto=on --saveauto=on /dev/hda\n" - " (Enables SMART on first disk)\n\n" - " smartctl -t long /dev/hda (Executes extended disk self-test)\n\n" - " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/hda\n" - " (Prints Self-Test & Attribute errors)\n" -#if WIN9X_SUPPORT - " smartctl -a /dev/scsi21\n" - " (Prints all information for SCSI disk on ASPI adapter 2, ID 1)\n" -#endif - " smartctl -a /dev/sda\n" - " (Prints all information for SCSI disk on PhysicalDrive 0)\n" - " smartctl -a /dev/pd3\n" - " (Prints all information for SCSI 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, 'c': ATA via IOCTL_SCSI_PASS_THROUGH,\n" - " 'f': IOCTL_STORAGE_*, 'm': IOCTL_SCSI_MINIPORT_*.\n" - + strprintf( - " The default on this system is /dev/sdX:%s\n", ata_get_def_options() - ); -} +///////////////////////////////////////////////////////////////////////////// +// Call GetDevicePowerState() +// returns: 1=active, 0=standby, -1=error +// (This would also work for SCSI drives) -bool winnt_smart_interface::disable_system_auto_standby(bool disable) +static int get_device_power_state(HANDLE hdevice) { - 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; - } + BOOL state = TRUE; + if (!GetDevicePowerState(hdevice, &state)) { + long err = GetLastError(); + if (ata_debugmode) + pout(" GetDevicePowerState() failed, Error=%ld\n", err); + errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); + // TODO: This may not work as expected on transient errors, + // because smartd interprets -1 as SLEEP mode regardless of errno. + return -1; } - if (!SetThreadExecutionState(ES_CONTINUOUS | (disable ? ES_SYSTEM_REQUIRED : 0))) - return set_err(ENOSYS); - return true; + if (ata_debugmode > 1) + pout(" GetDevicePowerState() succeeded, state=%d\n", state); + return state; } ///////////////////////////////////////////////////////////////////////////// -// ATA Interface -///////////////////////////////////////////////////////////////////////////// +// win_ata_device -#define SMART_CYL_LOW 0x4F -#define SMART_CYL_HI 0xC2 - -static void print_ide_regs(const IDEREGS * r, int out) +class win_ata_device +: public /*implements*/ ata_device, + public /*extends*/ win_smart_device { - pout("%s=0x%02x,%s=0x%02x, SC=0x%02x, SN=0x%02x, CL=0x%02x, CH=0x%02x, SEL=0x%02x\n", - (out?"STS":"CMD"), r->bCommandReg, (out?"ERR":" FR"), r->bFeaturesReg, - r->bSectorCountReg, r->bSectorNumberReg, r->bCylLowReg, r->bCylHighReg, r->bDriveHeadReg); -} +public: + win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); -static void print_ide_regs_io(const IDEREGS * ri, const IDEREGS * ro) -{ - pout(" Input : "); print_ide_regs(ri, 0); - if (ro) { - pout(" Output: "); print_ide_regs(ro, 1); - } -} + virtual ~win_ata_device() throw(); -///////////////////////////////////////////////////////////////////////////// + virtual bool open(); -// call SMART_GET_VERSION, return device map or -1 on error + virtual bool is_powered_down(); -static int smart_get_version(HANDLE hdevice, GETVERSIONINPARAMS_EX * ata_version_ex = 0) -{ - GETVERSIONINPARAMS vers; memset(&vers, 0, sizeof(vers)); - const GETVERSIONINPARAMS_EX & vers_ex = (const GETVERSIONINPARAMS_EX &)vers; - DWORD num_out; + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); - if (!DeviceIoControl(hdevice, SMART_GET_VERSION, - NULL, 0, &vers, sizeof(vers), &num_out, NULL)) { - if (ata_debugmode) - pout(" SMART_GET_VERSION failed, Error=%ld\n", GetLastError()); - errno = ENOSYS; - return -1; - } - assert(num_out == sizeof(GETVERSIONINPARAMS)); + virtual bool ata_identify_is_cached() const; - if (ata_debugmode > 1) { - pout(" SMART_GET_VERSION suceeded, bytes returned: %lu\n" - " Vers = %d.%d, Caps = 0x%lx, DeviceMap = 0x%02x\n", - num_out, vers.bVersion, vers.bRevision, - vers.fCapabilities, vers.bIDEDeviceMap); - if (vers_ex.wIdentifier == SMART_VENDOR_3WARE) - pout(" Identifier = %04x(3WARE), ControllerId=%u, DeviceMapEx = 0x%08lx\n", - vers_ex.wIdentifier, vers_ex.wControllerId, vers_ex.dwDeviceMapEx); - } +private: + bool open(bool query_device); - if (ata_version_ex) - *ata_version_ex = vers_ex; + bool open(int phydrive, int logdrive, const char * options, int port, bool query_device); - // TODO: Check vers.fCapabilities here? - return vers.bIDEDeviceMap; + std::string m_options; + bool m_usr_options; // options set by user? + bool m_admin; // open with admin access? + int m_phydrive; // PhysicalDriveN or -1 + bool m_id_is_cached; // ata_identify_is_cached() return value. + bool m_is_3ware; // LSI/3ware controller detected? + int m_port; // LSI/3ware port + int m_smartver_state; +}; + + +win_ata_device::win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "ata", req_type), + m_usr_options(false), + m_admin(false), + m_phydrive(-1), + m_id_is_cached(false), + m_is_3ware(false), + m_port(-1), + m_smartver_state(0) +{ } +win_ata_device::~win_ata_device() throw() +{ +} -// call SMART_* ioctl +// Get default ATA device options -static int smart_ioctl(HANDLE hdevice, int drive, IDEREGS * regs, char * data, unsigned datasize, int port) +static const char * ata_get_def_options() { - SENDCMDINPARAMS inpar; - SENDCMDINPARAMS_EX & inpar_ex = (SENDCMDINPARAMS_EX &)inpar; + return "pasifm"; // GetDevicePowerState(), ATA_, SMART_*, IDE_PASS_THROUGH, + // STORAGE_*, SCSI_MINIPORT_* +} - unsigned char outbuf[sizeof(SENDCMDOUTPARAMS)-1 + 512]; - const SENDCMDOUTPARAMS * outpar; - DWORD code, num_out; - unsigned int size_out; - const char * name; +// Open ATA device - memset(&inpar, 0, sizeof(inpar)); - inpar.irDriveRegs = *regs; - // drive is set to 0-3 on Win9x only - inpar.irDriveRegs.bDriveHeadReg = 0xA0 | ((drive & 1) << 4); - inpar.bDriveNumber = drive; +bool win_ata_device::open() +{ + // Open device for r/w operations + return open(false); +} - if (port >= 0) { - // Set RAID port - inpar_ex.wIdentifier = SMART_VENDOR_3WARE; - inpar_ex.bPortNumber = port; +bool win_ata_device::open(bool query_device) +{ + const char * name = skipdev(get_dev_name()); int len = strlen(name); + // [sh]d[a-z]([a-z])?(:[saicmfp]+)? => Physical drive 0-701, with options + char drive[2+1] = "", options[8+1] = ""; int n1 = -1, n2 = -1; + if ( sscanf(name, "%*[sh]d%2[a-z]%n:%6[saimfp]%n", drive, &n1, options, &n2) >= 1 + && ((n1 == len && !options[0]) || n2 == len) ) { + return open(sdxy_to_phydrive(drive), -1, options, -1, query_device); } - - if (datasize == 512) { - code = SMART_RCV_DRIVE_DATA; name = "SMART_RCV_DRIVE_DATA"; - inpar.cBufferSize = size_out = 512; + // [sh]d[a-z],N(:[saicmfp3]+)? => Physical drive 0-701, RAID port N, with options + drive[0] = 0; options[0] = 0; n1 = -1; n2 = -1; + unsigned port = ~0; + if ( sscanf(name, "%*[sh]d%2[a-z],%u%n:%7[saimfp3]%n", drive, &port, &n1, options, &n2) >= 2 + && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) { + return open(sdxy_to_phydrive(drive), -1, options, port, query_device); } - else if (datasize == 0) { - code = SMART_SEND_DRIVE_COMMAND; name = "SMART_SEND_DRIVE_COMMAND"; - if (regs->bFeaturesReg == ATA_SMART_STATUS) - size_out = sizeof(IDEREGS); // ioctl returns new IDEREGS as data - // Note: cBufferSize must be 0 on Win9x - else - size_out = 0; + // pd,N => Physical drive , RAID port N + int phydrive = -1; port = ~0; n1 = -1; n2 = -1; + if ( sscanf(name, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1 + && phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) { + return open(phydrive, -1, "", (int)port, query_device); } - else { - errno = EINVAL; - return -1; + // [a-zA-Z]: => Physical drive behind logical drive 0-25 + int logdrive = drive_letter(name); + if (logdrive >= 0) { + return open(-1, logdrive, "", -1, query_device); } - memset(&outbuf, 0, sizeof(outbuf)); + return set_err(EINVAL); +} - if (!DeviceIoControl(hdevice, code, &inpar, sizeof(SENDCMDINPARAMS)-1, - outbuf, sizeof(SENDCMDOUTPARAMS)-1 + size_out, &num_out, NULL)) { - // CAUTION: DO NOT change "regs" Parameter in this case, see ata_command_interface() - long err = GetLastError(); - if (ata_debugmode && (err != ERROR_INVALID_PARAMETER || ata_debugmode > 1)) { - pout(" %s failed, Error=%ld\n", name, err); - print_ide_regs_io(regs, NULL); - } - errno = ( err == ERROR_INVALID_FUNCTION/*9x*/ - || err == ERROR_INVALID_PARAMETER/*NT/2K/XP*/ - || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); - return -1; - } - // NOTE: On Win9x, inpar.irDriveRegs now contains the returned regs - outpar = (const SENDCMDOUTPARAMS *)outbuf; +bool win_ata_device::open(int phydrive, int logdrive, const char * options, int port, bool query_device) +{ + m_phydrive = -1; + char devpath[30]; + if (0 <= phydrive && phydrive <= 255) + snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", (m_phydrive = phydrive)); + else if (0 <= logdrive && logdrive <= 'Z'-'A') + snprintf(devpath, sizeof(devpath)-1, "\\\\.\\%c:", 'A'+logdrive); + else + return set_err(ENOENT); - if (outpar->DriverStatus.bDriverError) { - if (ata_debugmode) { - pout(" %s failed, DriverError=0x%02x, IDEError=0x%02x\n", name, - outpar->DriverStatus.bDriverError, outpar->DriverStatus.bIDEError); - print_ide_regs_io(regs, NULL); - } - errno = (!outpar->DriverStatus.bIDEError ? ENOSYS : EIO); - return -1; + // Open device + HANDLE h = INVALID_HANDLE_VALUE; + if (!(*options && !options[strspn(options, "fp")]) && !query_device) { + // Open with admin rights + m_admin = true; + h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0); } - - if (ata_debugmode > 1) { - pout(" %s suceeded, bytes returned: %lu (buffer %lu)\n", name, - num_out, outpar->cBufferSize); - print_ide_regs_io(regs, (regs->bFeaturesReg == ATA_SMART_STATUS ? - (const IDEREGS *)(outpar->bBuffer) : NULL)); + if (h == INVALID_HANDLE_VALUE) { + // Open without admin rights + m_admin = false; + h = CreateFileA(devpath, 0, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0); + } + if (h == INVALID_HANDLE_VALUE) { + long err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) + set_err(ENOENT, "%s: not found", devpath); + else if (err == ERROR_ACCESS_DENIED) + set_err(EACCES, "%s: access denied", devpath); + else + set_err(EIO, "%s: Error=%ld", devpath, err); + return false; } + set_fh(h); - if (datasize) - memcpy(data, outpar->bBuffer, 512); - else if (regs->bFeaturesReg == ATA_SMART_STATUS) { - if (nonempty(outpar->bBuffer, sizeof(IDEREGS))) - memcpy(regs, outpar->bBuffer, sizeof(IDEREGS)); - else { // Workaround for driver not returning regs - if (ata_debugmode) - pout(" WARNING: driver does not return ATA registers in output buffer!\n"); - *regs = inpar.irDriveRegs; + // Warn once if admin rights are missing + if (!m_admin && !query_device) { + static bool noadmin_warning = false; + if (!noadmin_warning) { + pout("Warning: Limited functionality due to missing admin rights\n"); + noadmin_warning = true; } } - return 0; -} - - -///////////////////////////////////////////////////////////////////////////// -// IDE PASS THROUGH (2000, XP, undocumented) -// -// Based on WinATA.cpp, 2002 c't/Matthias Withopf -// ftp://ftp.heise.de/pub/ct/listings/0207-218.zip + if (ata_debugmode > 1) + pout("%s: successfully opened%s\n", devpath, (!m_admin ? " (without admin rights)" :"")); -static int ide_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize) -{ - if (datasize > 512) { - errno = EINVAL; - return -1; + m_usr_options = false; + if (*options) { + // Save user options + m_options = options; m_usr_options = true; } - unsigned int size = sizeof(ATA_PASS_THROUGH)-1 + datasize; - ATA_PASS_THROUGH * buf = (ATA_PASS_THROUGH *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); - DWORD num_out; - const unsigned char magic = 0xcf; - - if (!buf) { - errno = ENOMEM; - return -1; + else if (port >= 0) + // RAID: SMART_* and SCSI_MINIPORT + m_options = "s3"; + else { + // Set default options according to Windows version + static const char * def_options = ata_get_def_options(); + m_options = def_options; } - buf->IdeReg = *regs; - buf->DataBufferSize = datasize; - if (datasize) - buf->DataBuffer[0] = magic; + // SMART_GET_VERSION may spin up disk, so delay until first real SMART_* call + m_port = port; + if (port < 0) + return true; - if (!DeviceIoControl(hdevice, IOCTL_IDE_PASS_THROUGH, - buf, size, buf, size, &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) { - pout(" IOCTL_IDE_PASS_THROUGH failed, Error=%ld\n", err); - print_ide_regs_io(regs, NULL); + // 3ware RAID: Get port map + GETVERSIONINPARAMS_EX vers_ex; + int devmap = smart_get_version(h, &vers_ex); + + // 3ware RAID if vendor id present + m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE); + + unsigned long portmap = 0; + if (port >= 0 && devmap >= 0) { + // 3ware RAID: check vendor id + if (!m_is_3ware) { + pout("SMART_GET_VERSION returns unknown Identifier = 0x%04x\n" + "This is no 3ware 9000 controller or driver has no SMART support.\n", + vers_ex.wIdentifier); + devmap = -1; } - VirtualFree(buf, 0, MEM_RELEASE); - errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); - return -1; + else + portmap = vers_ex.dwDeviceMapEx; } - - // Check ATA status - if (buf->IdeReg.bCommandReg/*Status*/ & 0x01) { - if (ata_debugmode) { - pout(" IOCTL_IDE_PASS_THROUGH command failed:\n"); - print_ide_regs_io(regs, &buf->IdeReg); + if (devmap < 0) { + pout("%s: ATA driver has no SMART support\n", devpath); + if (!is_permissive()) { + close(); + return set_err(ENOSYS); } - VirtualFree(buf, 0, MEM_RELEASE); - errno = EIO; - return -1; } + m_smartver_state = 1; - // Check and copy data - if (datasize) { - if ( num_out != size - || (buf->DataBuffer[0] == magic && !nonempty(buf->DataBuffer+1, datasize-1))) { - if (ata_debugmode) { - pout(" IOCTL_IDE_PASS_THROUGH output data missing (%lu, %lu)\n", - num_out, buf->DataBufferSize); - print_ide_regs_io(regs, &buf->IdeReg); + { + // 3ware RAID: update devicemap first + + if (!update_3ware_devicemap_ioctl(h)) { + if ( smart_get_version(h, &vers_ex) >= 0 + && vers_ex.wIdentifier == SMART_VENDOR_3WARE ) + portmap = vers_ex.dwDeviceMapEx; + } + // Check port existence + if (!(portmap & (1L << port))) { + if (!is_permissive()) { + close(); + return set_err(ENOENT, "%s: Port %d is empty or does not exist", devpath, port); } - VirtualFree(buf, 0, MEM_RELEASE); - errno = EIO; - return -1; } - memcpy(data, buf->DataBuffer, datasize); - } - - if (ata_debugmode > 1) { - pout(" IOCTL_IDE_PASS_THROUGH suceeded, bytes returned: %lu (buffer %lu)\n", - num_out, buf->DataBufferSize); - print_ide_regs_io(regs, &buf->IdeReg); } - *regs = buf->IdeReg; - // Caution: VirtualFree() fails if parameter "dwSize" is nonzero - VirtualFree(buf, 0, MEM_RELEASE); - return 0; + return true; } ///////////////////////////////////////////////////////////////////////////// -// ATA PASS THROUGH (Win2003, XP SP2) -// Warning: -// IOCTL_ATA_PASS_THROUGH[_DIRECT] can only handle one interrupt/DRQ data -// transfer per command. Therefore, multi-sector transfers are only supported -// for the READ/WRITE MULTIPLE [EXT] commands. Other commands like READ/WRITE SECTORS -// or READ/WRITE LOG EXT work only with single sector transfers. -// The latter are supported on Vista (only) through new ATA_FLAGS_NO_MULTIPLE. -// See: -// http://social.msdn.microsoft.com/Forums/en-US/storageplatformata/thread/eb408507-f221-455b-9bbb-d1069b29c4da +// Query OS if device is powered up or down. +bool win_ata_device::is_powered_down() +{ + // To check power mode, we open device for query operations only. + // Opening for SMART r/w operations can already spin up the disk. + bool self_open = !is_open(); + if (self_open) + if (!open(true)) + return false; + int rc = get_device_power_state(get_fh()); + if (self_open) + close(); + return !rc; +} -static int ata_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, IDEREGS * prev_regs, char * data, int datasize) +///////////////////////////////////////////////////////////////////////////// + +// Interface to ATA devices +bool win_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) { - const int max_sectors = 32; // TODO: Allocate dynamic buffer + // No multi-sector support for now, see above + // warning about IOCTL_ATA_PASS_THROUGH + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_output_regs | + ata_device::supports_48bit) + ) + return false; - typedef struct { - ATA_PASS_THROUGH_EX apt; - ULONG Filler; - UCHAR ucDataBuf[max_sectors * 512]; - } ATA_PASS_THROUGH_EX_WITH_BUFFERS; + // 3ware RAID: SMART DISABLE without port number disables SMART functions + if ( m_is_3ware && m_port < 0 + && in.in_regs.command == ATA_SMART_CMD + && in.in_regs.features == ATA_SMART_DISABLE) + return set_err(ENOSYS, "SMART DISABLE requires 3ware port number"); - const unsigned char magic = 0xcf; + // Determine ioctl functions valid for this ATA cmd + const char * valid_options = 0; - ATA_PASS_THROUGH_EX_WITH_BUFFERS ab; memset(&ab, 0, sizeof(ab)); - ab.apt.Length = sizeof(ATA_PASS_THROUGH_EX); - //ab.apt.PathId = 0; - //ab.apt.TargetId = 0; - //ab.apt.Lun = 0; - ab.apt.TimeOutValue = 10; - unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf); - ab.apt.DataBufferOffset = size; + switch (in.in_regs.command) { + case ATA_IDENTIFY_DEVICE: + case ATA_IDENTIFY_PACKET_DEVICE: + // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE + // and SCSI_MINIPORT_* if requested by user + valid_options = (m_usr_options ? "saimf" : "saif"); + break; - if (datasize > 0) { - if (datasize > (int)sizeof(ab.ucDataBuf)) { - errno = EINVAL; - return -1; - } - ab.apt.AtaFlags = ATA_FLAGS_DATA_IN; - ab.apt.DataTransferLength = datasize; - size += datasize; - ab.ucDataBuf[0] = magic; - } - else if (datasize < 0) { - if (-datasize > (int)sizeof(ab.ucDataBuf)) { - errno = EINVAL; - return -1; - } - ab.apt.AtaFlags = ATA_FLAGS_DATA_OUT; - ab.apt.DataTransferLength = -datasize; - size += -datasize; - memcpy(ab.ucDataBuf, data, -datasize); - } - else { - assert(ab.apt.AtaFlags == 0); - assert(ab.apt.DataTransferLength == 0); - } + case ATA_CHECK_POWER_MODE: + // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk + valid_options = "pai3"; + break; - assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS)); - IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile; - IDEREGS * ptfregs = (IDEREGS *)ab.apt.PreviousTaskFile; - *ctfregs = *regs; + case ATA_SMART_CMD: + switch (in.in_regs.features) { + case ATA_SMART_READ_VALUES: + case ATA_SMART_READ_THRESHOLDS: + case ATA_SMART_AUTOSAVE: + case ATA_SMART_ENABLE: + case ATA_SMART_DISABLE: + case ATA_SMART_AUTO_OFFLINE: + // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE + // and SCSI_MINIPORT_* if requested by user + valid_options = (m_usr_options ? "saimf" : "saif"); + break; - if (prev_regs) { - *ptfregs = *prev_regs; - ab.apt.AtaFlags |= ATA_FLAGS_48BIT_COMMAND; - } + case ATA_SMART_IMMEDIATE_OFFLINE: + // SMART_SEND_DRIVE_COMMAND does not support ABORT_SELF_TEST + valid_options = (m_usr_options || in.in_regs.lba_low != 127/*ABORT*/ ? + "saim3" : "aim3"); + break; - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH, - &ab, size, &ab, size, &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) { - pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err); - print_ide_regs_io(regs, NULL); - } - errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); - return -1; - } + case ATA_SMART_READ_LOG_SECTOR: + // SMART_RCV_DRIVE_DATA does not support READ_LOG + // Try SCSI_MINIPORT also to skip buggy class driver + // SMART functions do not support multi sector I/O. + if (in.size == 512) + valid_options = (m_usr_options ? "saim3" : "aim3"); + else + valid_options = "a"; + break; - // Check ATA status - if (ctfregs->bCommandReg/*Status*/ & (0x01/*Err*/|0x08/*DRQ*/)) { - if (ata_debugmode) { - pout(" IOCTL_ATA_PASS_THROUGH command failed:\n"); - print_ide_regs_io(regs, ctfregs); - } - errno = EIO; - return -1; - } + case ATA_SMART_WRITE_LOG_SECTOR: + // ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT + // but SCSI_MINIPORT_* only if requested by user and single sector. + valid_options = (in.size == 512 && m_usr_options ? "am" : "a"); + break; - // Check and copy data - if (datasize > 0) { - if ( num_out != size - || (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) { - if (ata_debugmode) { - pout(" IOCTL_ATA_PASS_THROUGH output data missing (%lu)\n", num_out); - print_ide_regs_io(regs, ctfregs); + case ATA_SMART_STATUS: + valid_options = (m_usr_options ? "saimf" : "saif"); + break; + + default: + // Unknown SMART command, handle below + break; } - errno = EIO; - return -1; - } - memcpy(data, ab.ucDataBuf, datasize); - } + break; - if (ata_debugmode > 1) { - pout(" IOCTL_ATA_PASS_THROUGH suceeded, bytes returned: %lu\n", num_out); - print_ide_regs_io(regs, ctfregs); + default: + // Other ATA command, handle below + break; } - *regs = *ctfregs; - if (prev_regs) - *prev_regs = *ptfregs; - - return 0; -} - - -///////////////////////////////////////////////////////////////////////////// -// ATA PASS THROUGH via SCSI PASS THROUGH (WinNT4 only) - -// undocumented SCSI opcode to for ATA passthrough -#define SCSIOP_ATA_PASSTHROUGH 0xCC - -static int ata_via_scsi_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize) -{ - typedef struct { - SCSI_PASS_THROUGH spt; - ULONG Filler; - UCHAR ucSenseBuf[32]; - UCHAR ucDataBuf[512]; - } SCSI_PASS_THROUGH_WITH_BUFFERS; - SCSI_PASS_THROUGH_WITH_BUFFERS sb; - IDEREGS * cdbregs; - unsigned int size; - DWORD num_out; - const unsigned char magic = 0xcf; - - memset(&sb, 0, sizeof(sb)); - sb.spt.Length = sizeof(SCSI_PASS_THROUGH); - //sb.spt.PathId = 0; - sb.spt.TargetId = 1; - //sb.spt.Lun = 0; - sb.spt.CdbLength = 10; sb.spt.SenseInfoLength = 24; - sb.spt.TimeOutValue = 10; - sb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf); - size = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf); - sb.spt.DataBufferOffset = size; - - if (datasize) { - if (datasize > sizeof(sb.ucDataBuf)) { - errno = EINVAL; - return -1; - } - sb.spt.DataIn = SCSI_IOCTL_DATA_IN; - sb.spt.DataTransferLength = datasize; - size += datasize; - sb.ucDataBuf[0] = magic; - } - else { - sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; - //sb.spt.DataTransferLength = 0; + if (!valid_options) { + // No special ATA command found above, select a generic pass through ioctl. + if (!( in.direction == ata_cmd_in::no_data + || (in.direction == ata_cmd_in::data_in && in.size == 512)) + || in.in_regs.is_48bit_cmd() ) + // DATA_OUT, more than one sector, 48-bit command: ATA_PASS_THROUGH only + valid_options = "a"; + else + // ATA/IDE_PASS_THROUGH + valid_options = "ai"; } - // Use pseudo SCSI command followed by registers - sb.spt.Cdb[0] = SCSIOP_ATA_PASSTHROUGH; - cdbregs = (IDEREGS *)(sb.spt.Cdb+2); - *cdbregs = *regs; - - if (!DeviceIoControl(hdevice, IOCTL_SCSI_PASS_THROUGH, - &sb, size, &sb, size, &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) - pout(" ATA via IOCTL_SCSI_PASS_THROUGH failed, Error=%ld\n", err); - errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); - return -1; + if (!m_admin) { + // Restrict to IOCTL_STORAGE_* + if (strchr(valid_options, 'f')) + valid_options = "f"; + else if (strchr(valid_options, 'p')) + valid_options = "p"; + else + return set_err(ENOSYS, "Function requires admin rights"); } - // Cannot check ATA status, because command does not return IDEREGS - - // Check and copy data - if (datasize) { - if ( num_out != size - || (sb.ucDataBuf[0] == magic && !nonempty(sb.ucDataBuf+1, datasize-1))) { - if (ata_debugmode) { - pout(" ATA via IOCTL_SCSI_PASS_THROUGH output data missing (%lu)\n", num_out); - print_ide_regs_io(regs, NULL); - } - errno = EIO; - return -1; - } - memcpy(data, sb.ucDataBuf, datasize); + // Set IDEREGS + IDEREGS regs, prev_regs; + { + const ata_in_regs & lo = in.in_regs; + regs.bFeaturesReg = lo.features; + regs.bSectorCountReg = lo.sector_count; + regs.bSectorNumberReg = lo.lba_low; + regs.bCylLowReg = lo.lba_mid; + regs.bCylHighReg = lo.lba_high; + regs.bDriveHeadReg = lo.device; + regs.bCommandReg = lo.command; + regs.bReserved = 0; + } + if (in.in_regs.is_48bit_cmd()) { + const ata_in_regs & hi = in.in_regs.prev; + prev_regs.bFeaturesReg = hi.features; + prev_regs.bSectorCountReg = hi.sector_count; + prev_regs.bSectorNumberReg = hi.lba_low; + prev_regs.bCylLowReg = hi.lba_mid; + prev_regs.bCylHighReg = hi.lba_high; + prev_regs.bDriveHeadReg = hi.device; + prev_regs.bCommandReg = hi.command; + prev_regs.bReserved = 0; } - if (ata_debugmode > 1) { - pout(" ATA via IOCTL_SCSI_PASS_THROUGH suceeded, bytes returned: %lu\n", num_out); - print_ide_regs_io(regs, NULL); + // Set data direction + int datasize = 0; + char * data = 0; + switch (in.direction) { + case ata_cmd_in::no_data: + break; + case ata_cmd_in::data_in: + datasize = (int)in.size; + data = (char *)in.buffer; + break; + case ata_cmd_in::data_out: + datasize = -(int)in.size; + data = (char *)in.buffer; + break; + default: + return set_err(EINVAL, "win_ata_device::ata_pass_through: invalid direction=%d", + (int)in.direction); } - return 0; -} -///////////////////////////////////////////////////////////////////////////// -// SMART IOCTL via SCSI MINIPORT ioctl + // Try all valid ioctls in the order specified in m_options + bool powered_up = false; + bool out_regs_set = false; + bool id_is_cached = false; + const char * options = m_options.c_str(); -// This function is handled by ATAPI port driver (atapi.sys) or by SCSI -// miniport driver (via SCSI port driver scsiport.sys). -// It can be used to skip the missing or broken handling of some SMART -// command codes (e.g. READ_LOG) in the disk class driver (disk.sys) + for (int i = 0; ; i++) { + char opt = options[i]; -static int ata_via_scsi_miniport_smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize) -{ - // Select code - DWORD code = 0; const char * name = 0; - if (regs->bCommandReg == ATA_IDENTIFY_DEVICE) { - code = IOCTL_SCSI_MINIPORT_IDENTIFY; name = "IDENTIFY"; - } - else if (regs->bCommandReg == ATA_SMART_CMD) switch (regs->bFeaturesReg) { - case ATA_SMART_READ_VALUES: - code = IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS; name = "READ_SMART_ATTRIBS"; break; - case ATA_SMART_READ_THRESHOLDS: - code = IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS; name = "READ_SMART_THRESHOLDS"; break; - case ATA_SMART_ENABLE: - code = IOCTL_SCSI_MINIPORT_ENABLE_SMART; name = "ENABLE_SMART"; break; - case ATA_SMART_DISABLE: - code = IOCTL_SCSI_MINIPORT_DISABLE_SMART; name = "DISABLE_SMART"; break; - case ATA_SMART_STATUS: - code = IOCTL_SCSI_MINIPORT_RETURN_STATUS; name = "RETURN_STATUS"; break; - case ATA_SMART_AUTOSAVE: - code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE; name = "ENABLE_DISABLE_AUTOSAVE"; break; - //case ATA_SMART_SAVE: // obsolete since ATA-6, not used by smartmontools - // code = IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES; name = "SAVE_ATTRIBUTE_VALUES"; break; - case ATA_SMART_IMMEDIATE_OFFLINE: - code = IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS; name = "EXECUTE_OFFLINE_DIAGS"; break; - case ATA_SMART_AUTO_OFFLINE: - code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE; name = "ENABLE_DISABLE_AUTO_OFFLINE"; break; - case ATA_SMART_READ_LOG_SECTOR: - code = IOCTL_SCSI_MINIPORT_READ_SMART_LOG; name = "READ_SMART_LOG"; break; - case ATA_SMART_WRITE_LOG_SECTOR: - code = IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG; name = "WRITE_SMART_LOG"; break; - } - if (!code) { - errno = ENOSYS; - return -1; - } + if (!opt) { + if (in.in_regs.command == ATA_CHECK_POWER_MODE && powered_up) { + // Power up reported by GetDevicePowerState() and no ioctl available + // to detect the actual mode of the drive => simulate ATA result ACTIVE/IDLE. + regs.bSectorCountReg = 0xff; + out_regs_set = true; + break; + } + // No IOCTL found + return set_err(ENOSYS); + } + if (!strchr(valid_options, opt)) + // Invalid for this command + continue; - // Set SRB - struct { - SRB_IO_CONTROL srbc; - union { - SENDCMDINPARAMS in; - SENDCMDOUTPARAMS out; - } params; - char space[512-1]; - } sb; - ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1+512); - memset(&sb, 0, sizeof(sb)); + errno = 0; + assert( datasize == 0 || datasize == 512 + || (datasize == -512 && strchr("am", opt)) + || (datasize > 512 && opt == 'a')); + int rc; + switch (opt) { + default: assert(0); + case 's': + // call SMART_GET_VERSION once for each drive + if (m_smartver_state > 1) { + rc = -1; errno = ENOSYS; + break; + } + if (!m_smartver_state) { + assert(m_port == -1); + GETVERSIONINPARAMS_EX vers_ex; + if (smart_get_version(get_fh(), &vers_ex) < 0) { + if (!failuretest_permissive) { + m_smartver_state = 2; + rc = -1; errno = ENOSYS; + break; + } + failuretest_permissive--; + } + else { + // 3ware RAID if vendor id present + m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE); + } - unsigned size; - if (datasize > 0) { - if (datasize > (int)sizeof(sb.space)+1) { - errno = EINVAL; - return -1; - } - size = datasize; - } - else if (datasize < 0) { - if (-datasize > (int)sizeof(sb.space)+1) { - errno = EINVAL; - return -1; + m_smartver_state = 1; + } + rc = smart_ioctl(get_fh(), ®s, data, datasize, m_port); + out_regs_set = (in.in_regs.features == ATA_SMART_STATUS); + id_is_cached = (m_port < 0); // Not cached by 3ware driver + break; + case 'm': + rc = ata_via_scsi_miniport_smart_ioctl(get_fh(), ®s, data, datasize); + id_is_cached = (m_port < 0); + break; + case 'a': + rc = ata_pass_through_ioctl(get_fh(), ®s, + (in.in_regs.is_48bit_cmd() ? &prev_regs : 0), + data, datasize); + out_regs_set = true; + break; + case 'i': + rc = ide_pass_through_ioctl(get_fh(), ®s, data, datasize); + out_regs_set = true; + break; + case 'f': + if (in.in_regs.command == ATA_IDENTIFY_DEVICE) { + ata_identify_device * id = reinterpret_cast(data); + rc = get_identify_from_device_property(get_fh(), id); + if (rc == 0 && m_phydrive >= 0) + get_serial_from_wmi(m_phydrive, id); + id_is_cached = true; + } + else if (in.in_regs.command == ATA_SMART_CMD) switch (in.in_regs.features) { + case ATA_SMART_READ_VALUES: + rc = storage_predict_failure_ioctl(get_fh(), data); + if (rc > 0) + rc = 0; + break; + case ATA_SMART_ENABLE: + rc = 0; + break; + case ATA_SMART_STATUS: + rc = storage_predict_failure_ioctl(get_fh()); + if (rc == 0) { + // Good SMART status + out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f; + } + else if (rc > 0) { + // Bad SMART status + out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4; + rc = 0; + } + break; + default: + errno = ENOSYS; rc = -1; + } + else { + errno = ENOSYS; rc = -1; + } + break; + case '3': + rc = ata_via_3ware_miniport_ioctl(get_fh(), ®s, data, datasize, m_port); + out_regs_set = true; + break; + case 'p': + assert(in.in_regs.command == ATA_CHECK_POWER_MODE && in.size == 0); + rc = get_device_power_state(get_fh()); + if (rc == 0) { + // Power down reported by GetDevicePowerState(), using a passthrough ioctl would + // spin up the drive => simulate ATA result STANDBY. + regs.bSectorCountReg = 0x00; + out_regs_set = true; + } + else if (rc > 0) { + // Power up reported by GetDevicePowerState(), but this reflects the actual mode + // only if it is selected by the device driver => try a passthrough ioctl to get the + // actual mode, if none available simulate ACTIVE/IDLE. + powered_up = true; + rc = -1; errno = ENOSYS; + } + break; } - size = -datasize; - memcpy(sb.params.in.bBuffer, data, size); - } - else if (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS) - size = sizeof(IDEREGS); - else - size = 0; - sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL); - memcpy(sb.srbc.Signature, "SCSIDISK", 8); // atapi.sys - sb.srbc.Timeout = 60; // seconds - sb.srbc.ControlCode = code; - //sb.srbc.ReturnCode = 0; - sb.srbc.Length = sizeof(SENDCMDINPARAMS)-1 + size; - sb.params.in.irDriveRegs = *regs; - sb.params.in.cBufferSize = size; - // Call miniport ioctl - size += sizeof(SRB_IO_CONTROL) + sizeof(SENDCMDINPARAMS)-1; - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, - &sb, size, &sb, size, &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) { - pout(" IOCTL_SCSI_MINIPORT_%s failed, Error=%ld\n", name, err); - print_ide_regs_io(regs, NULL); - } - errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO); - return -1; - } + if (!rc) + // Working ioctl found + break; - // Check result - if (sb.srbc.ReturnCode) { - if (ata_debugmode) { - pout(" IOCTL_SCSI_MINIPORT_%s failed, ReturnCode=0x%08lx\n", name, sb.srbc.ReturnCode); - print_ide_regs_io(regs, NULL); - } - errno = EIO; - return -1; - } + if (errno != ENOSYS) + // Abort on I/O error + return set_err(errno); - if (sb.params.out.DriverStatus.bDriverError) { - if (ata_debugmode) { - pout(" IOCTL_SCSI_MINIPORT_%s failed, DriverError=0x%02x, IDEError=0x%02x\n", name, - sb.params.out.DriverStatus.bDriverError, sb.params.out.DriverStatus.bIDEError); - print_ide_regs_io(regs, NULL); - } - errno = (!sb.params.out.DriverStatus.bIDEError ? ENOSYS : EIO); - return -1; + out_regs_set = false; + // CAUTION: *_ioctl() MUST NOT change "regs" Parameter in the ENOSYS case } - if (ata_debugmode > 1) { - pout(" IOCTL_SCSI_MINIPORT_%s suceeded, bytes returned: %lu (buffer %lu)\n", name, - num_out, sb.params.out.cBufferSize); - print_ide_regs_io(regs, (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS ? - (const IDEREGS *)(sb.params.out.bBuffer) : 0)); + // Return IDEREGS if set + if (out_regs_set) { + ata_out_regs & lo = out.out_regs; + lo.error = regs.bFeaturesReg; + lo.sector_count = regs.bSectorCountReg; + lo.lba_low = regs.bSectorNumberReg; + lo.lba_mid = regs.bCylLowReg; + lo.lba_high = regs.bCylHighReg; + lo.device = regs.bDriveHeadReg; + lo.status = regs.bCommandReg; + if (in.in_regs.is_48bit_cmd()) { + ata_out_regs & hi = out.out_regs.prev; + hi.sector_count = prev_regs.bSectorCountReg; + hi.lba_low = prev_regs.bSectorNumberReg; + hi.lba_mid = prev_regs.bCylLowReg; + hi.lba_high = prev_regs.bCylHighReg; + } } - if (datasize > 0) - memcpy(data, sb.params.out.bBuffer, datasize); - else if (datasize == 0 && code == IOCTL_SCSI_MINIPORT_RETURN_STATUS) - memcpy(regs, sb.params.out.bBuffer, sizeof(IDEREGS)); + if ( in.in_regs.command == ATA_IDENTIFY_DEVICE + || in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE) + // Update ata_identify_is_cached() result according to ioctl used. + m_id_is_cached = id_is_cached; - return 0; + return true; } +// Return true if OS caches the ATA identify sector +bool win_ata_device::ata_identify_is_cached() const +{ + return m_id_is_cached; +} -///////////////////////////////////////////////////////////////////////////// -// ATA PASS THROUGH via 3ware specific SCSI MINIPORT ioctl +////////////////////////////////////////////////////////////////////// +// csmi_device -static int ata_via_3ware_miniport_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize, int port) +class csmi_device +: virtual public /*extends*/ smart_device { - struct { - SRB_IO_CONTROL srbc; - IDEREGS regs; - UCHAR buffer[512]; - } sb; - ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(IDEREGS)+512); +public: + enum { max_number_of_ports = 32 }; - if (!(0 <= datasize && datasize <= (int)sizeof(sb.buffer) && port >= 0)) { - errno = EINVAL; - return -1; - } - memset(&sb, 0, sizeof(sb)); - strcpy((char *)sb.srbc.Signature, "<3ware>"); - sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL); - sb.srbc.Timeout = 60; // seconds - sb.srbc.ControlCode = 0xA0000000; - sb.srbc.ReturnCode = 0; - sb.srbc.Length = sizeof(IDEREGS) + (datasize > 0 ? datasize : 1); - sb.regs = *regs; - sb.regs.bReserved = port; + /// Get bitmask of used ports + unsigned get_ports_used(); - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, - &sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) { - pout(" ATA via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err); - print_ide_regs_io(regs, NULL); - } - errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); - return -1; - } +protected: + csmi_device() + : smart_device(never_called) + { memset(&m_phy_ent, 0, sizeof(m_phy_ent)); } - if (sb.srbc.ReturnCode) { - if (ata_debugmode) { - pout(" ATA via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08lx\n", sb.srbc.ReturnCode); - print_ide_regs_io(regs, NULL); - } - errno = EIO; - return -1; - } + typedef signed char port_2_index_map[max_number_of_ports]; - // Copy data - if (datasize > 0) - memcpy(data, sb.buffer, datasize); + /// Get phy info and port mapping, return #ports or -1 on error + int get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i); - if (ata_debugmode > 1) { - pout(" ATA via IOCTL_SCSI_MINIPORT suceeded, bytes returned: %lu\n", num_out); - print_ide_regs_io(regs, &sb.regs); - } - *regs = sb.regs; + /// Select physical drive + bool select_port(int port); - return 0; -} + /// Get info for selected physical drive + const CSMI_SAS_PHY_ENTITY & get_phy_ent() const + { return m_phy_ent; } + /// Call platform-specific CSMI ioctl + virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer, + unsigned csmi_bufsiz) = 0; -///////////////////////////////////////////////////////////////////////////// +private: + CSMI_SAS_PHY_ENTITY m_phy_ent; ///< CSMI info for this phy +}; -// 3ware specific call to update the devicemap returned by SMART_GET_VERSION. -// 3DM/CLI "Rescan Controller" function does not to always update it. -static int update_3ware_devicemap_ioctl(HANDLE hdevice) +///////////////////////////////////////////////////////////////////////////// + +int csmi_device::get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i) { - SRB_IO_CONTROL srbc; - memset(&srbc, 0, sizeof(srbc)); - strcpy((char *)srbc.Signature, "<3ware>"); - srbc.HeaderLength = sizeof(SRB_IO_CONTROL); - srbc.Timeout = 60; // seconds - srbc.ControlCode = 0xCC010014; - srbc.ReturnCode = 0; - srbc.Length = 0; + // max_number_of_ports must match CSMI_SAS_PHY_INFO.Phy[] array size + typedef char ASSERT_phy_info_size[ + (int)(sizeof(phy_info.Phy) / sizeof(phy_info.Phy[0])) == max_number_of_ports ? 1 : -1] + ATTR_UNUSED; - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT, - &srbc, sizeof(srbc), &srbc, sizeof(srbc), &num_out, NULL)) { - long err = GetLastError(); - if (ata_debugmode) - pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err); - errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); + // Get driver info to check CSMI support + CSMI_SAS_DRIVER_INFO_BUFFER driver_info_buf; + memset(&driver_info_buf, 0, sizeof(driver_info_buf)); + if (!csmi_ioctl(CC_CSMI_SAS_GET_DRIVER_INFO, &driver_info_buf.IoctlHeader, sizeof(driver_info_buf))) return -1; + + if (scsi_debugmode > 1) { + const CSMI_SAS_DRIVER_INFO & driver_info = driver_info_buf.Information; + pout("CSMI_SAS_DRIVER_INFO:\n"); + pout(" Name: \"%.81s\"\n", driver_info.szName); + pout(" Description: \"%.81s\"\n", driver_info.szDescription); + pout(" Revision: %d.%d\n", driver_info.usMajorRevision, driver_info.usMinorRevision); } - if (srbc.ReturnCode) { - if (ata_debugmode) - pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08lx\n", srbc.ReturnCode); - errno = EIO; + + // Get Phy info + CSMI_SAS_PHY_INFO_BUFFER phy_info_buf; + memset(&phy_info_buf, 0, sizeof(phy_info_buf)); + if (!csmi_ioctl(CC_CSMI_SAS_GET_PHY_INFO, &phy_info_buf.IoctlHeader, sizeof(phy_info_buf))) + return -1; + + phy_info = phy_info_buf.Information; + + if (phy_info.bNumberOfPhys > max_number_of_ports) { + set_err(EIO, "CSMI_SAS_PHY_INFO: Bogus NumberOfPhys=%d", phy_info.bNumberOfPhys); return -1; } - if (ata_debugmode > 1) - pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT suceeded\n"); - return 0; -} + // Create port -> index map + // IRST Release + // Phy[i].Value 9.x 10.4 14.8 15.2 + // --------------------------------------------------- + // bPortIdentifier 0xff 0xff port 0x00 + // Identify.bPhyIdentifier port port port port + // Attached.bPhyIdentifier 0x00 0x00 0x00 port + // Empty ports in Phy[]? no ? yes yes + + int number_of_ports; + for (int mode = 0; ; mode++) { + for (int i = 0; i < max_number_of_ports; i++) + p2i[i] = -1; + + number_of_ports = 0; + bool unique = true; + for (int i = 0; i < max_number_of_ports; i++) { + const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i]; + if (pe.Identify.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED) + continue; + // Use a bPhyIdentifier or the bPortIdentifier if unique, + // otherwise use table index + int port; + switch (mode) { + case 0: port = pe.Attached.bPhyIdentifier; break; + case 1: port = pe.Identify.bPhyIdentifier; break; + case 2: port = pe.bPortIdentifier; break; + default: port = i; break; + } + if (!(port < max_number_of_ports && p2i[port] == -1)) { + unique = false; + break; + } -///////////////////////////////////////////////////////////////////////////// + p2i[port] = i; + if (number_of_ports <= port) + number_of_ports = port + 1; + } -// Routines for pseudo device /dev/tw_cli/* -// Parses output of 3ware "tw_cli /cx/py show all" or 3DM SMART data window + if (unique || mode > 2) + break; + } + if (scsi_debugmode > 1) { + pout("CSMI_SAS_PHY_INFO: NumberOfPhys=%d\n", phy_info.bNumberOfPhys); + for (int i = 0; i < max_number_of_ports; i++) { + const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i]; + const CSMI_SAS_IDENTIFY & id = pe.Identify, & at = pe.Attached; + if (id.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED) + continue; -// Get clipboard data + int port = -1; + for (int p = 0; p < max_number_of_ports && port < 0; p++) { + if (p2i[p] == i) + port = p; + } + + pout("Phy[%d] Port: %d\n", i, port); + pout(" Type: 0x%02x, 0x%02x\n", id.bDeviceType, at.bDeviceType); + pout(" InitProto: 0x%02x, 0x%02x\n", id.bInitiatorPortProtocol, at.bInitiatorPortProtocol); + pout(" TargetProto: 0x%02x, 0x%02x\n", id.bTargetPortProtocol, at.bTargetPortProtocol); + pout(" PortIdent: 0x%02x\n", pe.bPortIdentifier); + pout(" PhyIdent: 0x%02x, 0x%02x\n", id.bPhyIdentifier, at.bPhyIdentifier); + const unsigned char * b = id.bSASAddress; + pout(" SASAddress: %02x %02x %02x %02x %02x %02x %02x %02x, ", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + b = at.bSASAddress; + pout( "%02x %02x %02x %02x %02x %02x %02x %02x\n", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + } + } + + return number_of_ports; +} -static int get_clipboard(char * data, int datasize) +unsigned csmi_device::get_ports_used() { - if (!OpenClipboard(NULL)) - return -1; - HANDLE h = GetClipboardData(CF_TEXT); - if (!h) { - CloseClipboard(); + CSMI_SAS_PHY_INFO phy_info; + port_2_index_map p2i; + int number_of_ports = get_phy_info(phy_info, p2i); + if (number_of_ports < 0) return 0; + + unsigned ports_used = 0; + for (int p = 0; p < max_number_of_ports; p++) { + int i = p2i[p]; + if (i < 0) + continue; + const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i]; + if (pe.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED) + continue; + switch (pe.Attached.bTargetPortProtocol) { + case CSMI_SAS_PROTOCOL_SATA: + case CSMI_SAS_PROTOCOL_STP: + break; + default: + continue; + } + + ports_used |= (1 << p); } - const void * p = GlobalLock(h); - int n = GlobalSize(h); - if (n > datasize) - n = datasize; - memcpy(data, p, n); - GlobalFree(h); - CloseClipboard(); - return n; + + return ports_used; } +bool csmi_device::select_port(int port) +{ + if (!(0 <= port && port < max_number_of_ports)) + return set_err(EINVAL, "Invalid port number %d", port); -// Run a command, write stdout to dataout -// TODO: Combine with daemon_win32.cpp:daemon_spawn() + CSMI_SAS_PHY_INFO phy_info; + port_2_index_map p2i; + int number_of_ports = get_phy_info(phy_info, p2i); + if (number_of_ports < 0) + return false; -static int run_cmd(const char * cmd, char * dataout, int outsize) -{ - // Create stdout pipe - SECURITY_ATTRIBUTES sa = {sizeof(sa), 0, TRUE}; - HANDLE pipe_out_w, h; - if (!CreatePipe(&h, &pipe_out_w, &sa/*inherit*/, outsize)) - return -1; - HANDLE self = GetCurrentProcess(); - HANDLE pipe_out_r; - if (!DuplicateHandle(self, h, self, &pipe_out_r, - GENERIC_READ, FALSE/*!inherit*/, DUPLICATE_CLOSE_SOURCE)) { - CloseHandle(pipe_out_w); - return -1; - } - HANDLE pipe_err_w; - if (!DuplicateHandle(self, pipe_out_w, self, &pipe_err_w, - 0, TRUE/*inherit*/, DUPLICATE_SAME_ACCESS)) { - CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); - return -1; + int port_index = p2i[port]; + if (port_index < 0) { + if (port < number_of_ports) + return set_err(ENOENT, "Port %d is disabled", port); + else + return set_err(ENOENT, "Port %d does not exist (#ports: %d)", port, + number_of_ports); } - // Create process - STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); - si.hStdInput = INVALID_HANDLE_VALUE; - si.hStdOutput = pipe_out_w; si.hStdError = pipe_err_w; - si.dwFlags = STARTF_USESTDHANDLES; - PROCESS_INFORMATION pi; - if (!CreateProcess( - NULL, const_cast(cmd), - NULL, NULL, TRUE/*inherit*/, - CREATE_NO_WINDOW/*do not create a new console window*/, - NULL, NULL, &si, &pi)) { - CloseHandle(pipe_err_w); CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); - return -1; - } - CloseHandle(pi.hThread); - CloseHandle(pipe_err_w); CloseHandle(pipe_out_w); + const CSMI_SAS_PHY_ENTITY & phy_ent = phy_info.Phy[port_index]; + if (phy_ent.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED) + return set_err(ENOENT, "No device on port %d", port); - // Copy stdout to output buffer - int i = 0; - while (i < outsize) { - DWORD num_read; - if (!ReadFile(pipe_out_r, dataout+i, outsize-i, &num_read, NULL) || num_read == 0) + switch (phy_ent.Attached.bTargetPortProtocol) { + case CSMI_SAS_PROTOCOL_SATA: + case CSMI_SAS_PROTOCOL_STP: break; - i += num_read; + default: + return set_err(ENOENT, "No SATA device on port %d (protocol: %d)", + port, phy_ent.Attached.bTargetPortProtocol); } - CloseHandle(pipe_out_r); - // Wait for process - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - return i; -} - - -static const char * findstr(const char * str, const char * sub) -{ - const char * s = strstr(str, sub); - return (s ? s+strlen(sub) : ""); -} - -static void copy_swapped(unsigned char * dest, const char * src, int destsize) -{ - int srclen = strcspn(src, "\r\n"); - int i; - for (i = 0; i < destsize-1 && i < srclen-1; i+=2) { - dest[i] = src[i+1]; dest[i+1] = src[i]; - } - if (i < destsize-1 && i < srclen) - dest[i+1] = src[i]; + m_phy_ent = phy_ent; + return true; } -// TODO: This is OS independent +////////////////////////////////////////////////////////////////////// +// csmi_ata_device -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) +class csmi_ata_device +: virtual public /*extends*/ csmi_device, + virtual public /*implements*/ ata_device { - memset(&m_ident_buf, 0, sizeof(m_ident_buf)); - memset(&m_smart_buf, 0, sizeof(m_smart_buf)); -} +public: + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); +protected: + csmi_ata_device() + : smart_device(never_called) { } +}; -bool win_tw_cli_device::is_open() const -{ - return (m_ident_valid || m_smart_valid); -} +////////////////////////////////////////////////////////////////////// -bool win_tw_cli_device::open() +bool csmi_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) { - 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); - size = run_cmd(cmd, buffer, sizeof(buffer)); - } - 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); + if (!ata_cmd_is_supported(in, + ata_device::supports_data_out | + ata_device::supports_output_regs | + ata_device::supports_multi_sector | + ata_device::supports_48bit, + "CSMI") + ) + return false; - buffer[size] = 0; - if (ata_debugmode > 1) - pout("[\n%.100s%s\n]\n", buffer, (size>100?"...":"")); + // Create buffer with appropriate size + raw_buffer pthru_raw_buf(sizeof(CSMI_SAS_STP_PASSTHRU_BUFFER) + in.size); + CSMI_SAS_STP_PASSTHRU_BUFFER * pthru_buf = (CSMI_SAS_STP_PASSTHRU_BUFFER *)pthru_raw_buf.data(); - // 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 + // Set addresses from Phy info + CSMI_SAS_STP_PASSTHRU & pthru = pthru_buf->Parameters; + const CSMI_SAS_PHY_ENTITY & phy_ent = get_phy_ent(); + pthru.bPhyIdentifier = phy_ent.Identify.bPhyIdentifier; + pthru.bPortIdentifier = phy_ent.bPortIdentifier; + memcpy(pthru.bDestinationSASAddress, phy_ent.Attached.bSASAddress, + sizeof(pthru.bDestinationSASAddress)); + pthru.bConnectionRate = CSMI_SAS_LINK_RATE_NEGOTIATED; - // 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, " 0)) + case ata_cmd_in::data_in: + pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_READ; + pthru.uDataLength = in.size; break; - s += n; - if (*s == '<') // "
" - s += strcspn(s, "\r\n"); + case ata_cmd_in::data_out: + pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_WRITE; + pthru.uDataLength = in.size; + memcpy(pthru_buf->bDataBuffer, in.buffer, in.size); + break; + default: + return set_err(EINVAL, "csmi_ata_device::ata_pass_through: invalid direction=%d", + (int)in.direction); } - 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; + + // Set host-to-device FIS + { + unsigned char * fis = pthru.bCommandFIS; + const ata_in_regs & lo = in.in_regs; + const ata_in_regs & hi = in.in_regs.prev; + fis[ 0] = 0x27; // Type: host-to-device FIS + fis[ 1] = 0x80; // Bit7: Update command register + fis[ 2] = lo.command; + fis[ 3] = lo.features; + fis[ 4] = lo.lba_low; + fis[ 5] = lo.lba_mid; + fis[ 6] = lo.lba_high; + fis[ 7] = lo.device; + fis[ 8] = hi.lba_low; + fis[ 9] = hi.lba_mid; + fis[10] = hi.lba_high; + fis[11] = hi.features; + fis[12] = lo.sector_count; + fis[13] = hi.sector_count; } - m_ident_valid = true; - m_smart_valid = !!sd; - return true; -} + // Call ioctl + if (!csmi_ioctl(CC_CSMI_SAS_STP_PASSTHRU, &pthru_buf->IoctlHeader, pthru_raw_buf.size())) { + return false; + } + + // Get device-to-host FIS + { + const unsigned char * fis = pthru_buf->Status.bStatusFIS; + ata_out_regs & lo = out.out_regs; + lo.status = fis[ 2]; + lo.error = fis[ 3]; + lo.lba_low = fis[ 4]; + lo.lba_mid = fis[ 5]; + lo.lba_high = fis[ 6]; + lo.device = fis[ 7]; + lo.sector_count = fis[12]; + if (in.in_regs.is_48bit_cmd()) { + ata_out_regs & hi = out.out_regs.prev; + hi.lba_low = fis[ 8]; + hi.lba_mid = fis[ 9]; + hi.lba_high = fis[10]; + hi.sector_count = fis[13]; + } + } + // Get data + if (in.direction == ata_cmd_in::data_in) + // TODO: Check ptru_buf->Status.uDataBytes + memcpy(in.buffer, pthru_buf->bDataBuffer, in.size); -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_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); -///////////////////////////////////////////////////////////////////////////// -// IOCTL_STORAGE_QUERY_PROPERTY + virtual ~win_csmi_device() throw(); -union STORAGE_DEVICE_DESCRIPTOR_DATA { - STORAGE_DEVICE_DESCRIPTOR desc; - char raw[256]; -}; + virtual bool open(); -// Get STORAGE_DEVICE_DESCRIPTOR_DATA for device. -// (This works without admin rights) + virtual bool close(); -static int storage_query_property_ioctl(HANDLE hdevice, STORAGE_DEVICE_DESCRIPTOR_DATA * data) -{ - STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, {0} }; - memset(data, 0, sizeof(*data)); + virtual bool is_open() const; - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY, - &query, sizeof(query), data, sizeof(*data), &num_out, NULL)) { - if (ata_debugmode > 1 || scsi_debugmode > 1) - pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%ld\n", GetLastError()); - errno = ENOSYS; - return -1; - } + bool open_scsi(); - if (ata_debugmode > 1 || scsi_debugmode > 1) { - pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n" - " Vendor: \"%s\"\n" - " Product: \"%s\"\n" - " Revision: \"%s\"\n" - " Removable: %s\n" - " BusType: 0x%02x\n", - (data->desc.VendorIdOffset ? data->raw+data->desc.VendorIdOffset : "(null)"), - (data->desc.ProductIdOffset ? data->raw+data->desc.ProductIdOffset : "(null)"), - (data->desc.ProductRevisionOffset ? data->raw+data->desc.ProductRevisionOffset : "(null)"), - (data->desc.RemovableMedia? "Yes":"No"), data->desc.BusType - ); - } - return 0; -} +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 +}; -///////////////////////////////////////////////////////////////////////////// -// IOCTL_STORAGE_PREDICT_FAILURE -// Call IOCTL_STORAGE_PREDICT_FAILURE, return PredictFailure value -// or -1 on error, opionally return VendorSpecific data. -// (This works without admin rights) +////////////////////////////////////////////////////////////////////// -static int storage_predict_failure_ioctl(HANDLE hdevice, char * data = 0) +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) { - STORAGE_PREDICT_FAILURE pred; - memset(&pred, 0, sizeof(pred)); - - DWORD num_out; - if (!DeviceIoControl(hdevice, IOCTL_STORAGE_PREDICT_FAILURE, - 0, 0, &pred, sizeof(pred), &num_out, NULL)) { - if (ata_debugmode > 1) - pout(" IOCTL_STORAGE_PREDICT_FAILURE failed, Error=%ld\n", GetLastError()); - errno = ENOSYS; - return -1; - } - - if (ata_debugmode > 1) { - pout(" IOCTL_STORAGE_PREDICT_FAILURE returns:\n" - " PredictFailure: 0x%08lx\n" - " VendorSpecific: 0x%02x,0x%02x,0x%02x,...,0x%02x\n", - pred.PredictFailure, - pred.VendorSpecific[0], pred.VendorSpecific[1], pred.VendorSpecific[2], - pred.VendorSpecific[sizeof(pred.VendorSpecific)-1] - ); - } - if (data) - memcpy(data, pred.VendorSpecific, sizeof(pred.VendorSpecific)); - return (!pred.PredictFailure ? 0 : 1); } - -///////////////////////////////////////////////////////////////////////////// - -// Return true if Intel ICHxR RAID volume -static bool is_intel_raid_volume(const STORAGE_DEVICE_DESCRIPTOR_DATA * data) +win_csmi_device::~win_csmi_device() throw() { - 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; + if (m_fh != INVALID_HANDLE_VALUE) + CloseHandle(m_fh); } -// get DEV_* for open handle -static win_dev_type get_controller_type(HANDLE hdevice, bool admin, GETVERSIONINPARAMS_EX * ata_version_ex) +bool win_csmi_device::is_open() const { - // Get BusType from device descriptor - STORAGE_DEVICE_DESCRIPTOR_DATA data; - if (storage_query_property_ioctl(hdevice, &data)) - return DEV_UNKNOWN; + return (m_fh != INVALID_HANDLE_VALUE); +} - // Newer BusType* values are missing in older includes - switch ((int)data.desc.BusType) { - case BusTypeAta: - case 0x0b: // BusTypeSata - if (ata_version_ex) - memset(ata_version_ex, 0, sizeof(*ata_version_ex)); - return DEV_ATA; +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; +} - case BusTypeScsi: - case BusTypeRAID: - // 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 - return DEV_SCSI; +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); - case BusTypeUsb: - return DEV_USB; + // Open controller handle + char devpath[30]; + snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%u:", contr_no); - default: - return DEV_UNKNOWN; - } - /*NOTREACHED*/ -} + HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0); -// 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; + 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 (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); -} + if (scsi_debugmode > 1) + pout(" %s: successfully opened\n", devpath); -static win_dev_type get_phy_drive_type(int drive) -{ - return get_phy_drive_type(drive, 0); + m_fh = h; + m_port = port; + return true; } -// 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); -} -// Build IDENTIFY information from STORAGE_DEVICE_DESCRIPTOR -static int get_identify_from_device_property(HANDLE hdevice, ata_identify_device * id) +bool win_csmi_device::open() { - STORAGE_DEVICE_DESCRIPTOR_DATA data; - if (storage_query_property_ioctl(hdevice, &data)) - return -1; + if (!open_scsi()) + return false; - memset(id, 0, sizeof(*id)); + // Get Phy info for this drive + if (!select_port(m_port)) { + close(); + return false; + } - // Some drivers split ATA model string into VendorId and ProductId, - // others return it as ProductId only. - char model[sizeof(id->model) + 1] = ""; + return true; +} - unsigned i = 0; - if (data.desc.VendorIdOffset) { - for ( ;i < sizeof(model)-1 && data.raw[data.desc.VendorIdOffset+i]; i++) - model[i] = data.raw[data.desc.VendorIdOffset+i]; + +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); } - if (data.desc.ProductIdOffset) { - while (i > 1 && model[i-2] == ' ') // Keep last blank from VendorId - i--; - // Ignore VendorId "ATA" - if (i <= 4 && !strncmp(model, "ATA", 3) && (i == 3 || model[3] == ' ')) - i = 0; - for (unsigned j = 0; i < sizeof(model)-1 && data.raw[data.desc.ProductIdOffset+j]; i++, j++) - model[i] = data.raw[data.desc.ProductIdOffset+j]; + // 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); } - while (i > 0 && model[i-1] == ' ') - i--; - model[i] = 0; - copy_swapped(id->model, model, sizeof(id->model)); + // 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 (data.desc.ProductRevisionOffset) - copy_swapped(id->fw_rev, data.raw+data.desc.ProductRevisionOffset, sizeof(id->fw_rev)); + if (scsi_debugmode > 1) + pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) succeeded, bytes returned: %u\n", code, (unsigned)num_out); - id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid - id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid - return 0; + return true; } -///////////////////////////////////////////////////////////////////////////// -// USB ID detection using WMI +////////////////////////////////////////////////////////////////////// +// 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 -// Get USB ID for a physical drive number -static bool get_usb_id(int drive, unsigned short & vendor_id, unsigned short & product_id) +class win_tw_cli_device +: public /*implements*/ ata_device_with_command_set { - bool debug = (scsi_debugmode > 1); +public: + win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type); - wbem_services ws; - if (!ws.connect()) { - if (debug) - pout("WMI connect failed\n"); - return false; - } + virtual bool is_open() const; - // Get device name - wbem_object wo; - if (!ws.query1(wo, "SELECT Model FROM Win32_DiskDrive WHERE DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", drive)) - return false; + virtual bool open(); - std::string name = wo.get_str("Model"); - if (debug) - pout("PhysicalDrive%d, \"%s\":\n", drive, name.c_str()); + virtual bool close(); - // Get USB_CONTROLLER -> DEVICE associations - wbem_enumerator we; - if (!ws.query(we, "SELECT Antecedent,Dependent FROM Win32_USBControllerDevice")) - return false; +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); - unsigned short usb_venid = 0, prev_usb_venid = 0; - unsigned short usb_proid = 0, prev_usb_proid = 0; - std::string prev_usb_ant; - std::string prev_ant, ant, dep; +private: + bool m_ident_valid, m_smart_valid; + ata_identify_device m_ident_buf; + ata_smart_values m_smart_buf; +}; - const regular_expression regex("^.*PnPEntity\\.DeviceID=\"([^\"]*)\"", REG_EXTENDED); - while (we.next(wo)) { - prev_ant = ant; - // Find next 'USB_CONTROLLER, DEVICE' pair - ant = wo.get_str("Antecedent"); - dep = wo.get_str("Dependent"); +///////////////////////////////////////////////////////////////////////////// - if (debug && ant != prev_ant) - pout(" %s:\n", ant.c_str()); +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)); +} - // Extract DeviceID - regmatch_t match[2]; - if (!(regex.execute(dep.c_str(), 2, match) && match[1].rm_so >= 0)) { - if (debug) - pout(" | (\"%s\")\n", dep.c_str()); - continue; - } - std::string devid(dep.c_str()+match[1].rm_so, match[1].rm_eo-match[1].rm_so); +bool win_tw_cli_device::is_open() const +{ + return (m_ident_valid || m_smart_valid); +} - if (str_starts_with(devid, "USB\\\\VID_")) { - // USB bridge entry, save CONTROLLER, ID - int nc = -1; - if (!(sscanf(devid.c_str(), "USB\\\\VID_%4hx&PID_%4hx%n", - &prev_usb_venid, &prev_usb_proid, &nc) == 2 && nc == 9+4+5+4)) { - prev_usb_venid = prev_usb_proid = 0; - } - prev_usb_ant = ant; - if (debug) - pout(" +-> \"%s\" [0x%04x:0x%04x]\n", devid.c_str(), prev_usb_venid, prev_usb_proid); - continue; - } - else if (str_starts_with(devid, "USBSTOR\\\\")) { - // USBSTOR device found - if (debug) - pout(" +--> \"%s\"\n", devid.c_str()); - // Retrieve name - wbem_object wo2; - if (!ws.query1(wo2, "SELECT Name FROM Win32_PnPEntity WHERE DeviceID=\"%s\"", devid.c_str())) - continue; - std::string name2 = wo2.get_str("Name"); +// Get clipboard data - // Continue if not name of physical disk drive - if (name2 != name) { - if (debug) - pout(" +---> (\"%s\")\n", name2.c_str()); - continue; - } +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; +} - // Fail if previos USB bridge is associated to other controller or ID is unknown - if (!(ant == prev_usb_ant && prev_usb_venid)) { - if (debug) - pout(" +---> \"%s\" (Error: No USB bridge found)\n", name2.c_str()); - return false; - } - // Handle multiple devices with same name - if (usb_venid) { - // Fail if multiple devices with same name have different USB bridge types - if (!(usb_venid == prev_usb_venid && usb_proid == prev_usb_proid)) { - if (debug) - pout(" +---> \"%s\" (Error: More than one USB ID found)\n", name2.c_str()); - return false; - } - } +// Run a command, write stdout to dataout +// TODO: Combine with daemon_win32.cpp:daemon_spawn() - // Found - usb_venid = prev_usb_venid; - usb_proid = prev_usb_proid; - if (debug) - pout(" +===> \"%s\" [0x%04x:0x%04x]\n", name2.c_str(), usb_venid, usb_proid); +static int run_cmd(const char * cmd, char * dataout, int outsize) +{ + // Create stdout pipe + SECURITY_ATTRIBUTES sa = {sizeof(sa), 0, TRUE}; + HANDLE pipe_out_w, h; + if (!CreatePipe(&h, &pipe_out_w, &sa/*inherit*/, outsize)) + return -1; + HANDLE self = GetCurrentProcess(); + HANDLE pipe_out_r; + if (!DuplicateHandle(self, h, self, &pipe_out_r, + GENERIC_READ, FALSE/*!inherit*/, DUPLICATE_CLOSE_SOURCE)) { + CloseHandle(pipe_out_w); + return -1; + } + HANDLE pipe_err_w; + if (!DuplicateHandle(self, pipe_out_w, self, &pipe_err_w, + 0, TRUE/*inherit*/, DUPLICATE_SAME_ACCESS)) { + CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); + return -1; + } - // Continue to check for duplicate names ... - } - else { - if (debug) - pout(" | \"%s\"\n", devid.c_str()); - } + // Create process + STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); + si.hStdInput = INVALID_HANDLE_VALUE; + si.hStdOutput = pipe_out_w; si.hStdError = pipe_err_w; + si.dwFlags = STARTF_USESTDHANDLES; + PROCESS_INFORMATION pi; + if (!CreateProcess( + NULL, const_cast(cmd), + NULL, NULL, TRUE/*inherit*/, + CREATE_NO_WINDOW/*do not create a new console window*/, + NULL, NULL, &si, &pi)) { + CloseHandle(pipe_err_w); CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); + return -1; } + CloseHandle(pi.hThread); + CloseHandle(pipe_err_w); CloseHandle(pipe_out_w); - if (!usb_venid) - return false; - - vendor_id = usb_venid; - product_id = usb_proid; - - return true; + // Copy stdout to output buffer + int i = 0; + while (i < outsize) { + DWORD num_read; + if (!ReadFile(pipe_out_r, dataout+i, outsize-i, &num_read, NULL) || num_read == 0) + break; + i += num_read; + } + CloseHandle(pipe_out_r); + // Wait for process + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + return i; } -///////////////////////////////////////////////////////////////////////////// +static const char * findstr(const char * str, const char * sub) +{ + const char * s = strstr(str, sub); + return (s ? s+strlen(sub) : ""); +} -// Call GetDevicePowerState() if available (Win98/ME/2000/XP/2003) -// returns: 1=active, 0=standby, -1=error -// (This would also work for SCSI drives) -static int get_device_power_state(HANDLE hdevice) +bool win_tw_cli_device::open() { - static bool unsupported = false; - if (unsupported) { - errno = ENOSYS; - return -1; + 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)); } - -#ifdef __CYGWIN__ - static DWORD kernel_dll_pid = 0; -#endif - static BOOL (WINAPI * GetDevicePowerState_p)(HANDLE, BOOL *) = 0; - - if (!GetDevicePowerState_p -#ifdef __CYGWIN__ - || kernel_dll_pid != GetCurrentProcessId() // detect fork() -#endif - ) { - if (!(GetDevicePowerState_p = (BOOL (WINAPI *)(HANDLE, BOOL *)) - GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetDevicePowerState"))) { - if (ata_debugmode) - pout(" GetDevicePowerState() not found, Error=%ld\n", GetLastError()); - unsupported = true; - errno = ENOSYS; - return -1; - } -#ifdef __CYGWIN__ - kernel_dll_pid = GetCurrentProcessId(); -#endif + else if (!strcmp(name, "tw_cli/stdin")) { // read stdin + size = fread(buffer, 1, sizeof(buffer), stdin); } - - BOOL state = TRUE; - if (!GetDevicePowerState_p(hdevice, &state)) { - long err = GetLastError(); - if (ata_debugmode) - pout(" GetDevicePowerState() failed, Error=%ld\n", err); - errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO); - // TODO: This may not work as expected on transient errors, - // because smartd interprets -1 as SLEEP mode regardless of errno. - return -1; + 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); + size = run_cmd(cmd, buffer, sizeof(buffer)); + } + else { + return set_err(EINVAL); } if (ata_debugmode > 1) - pout(" GetDevicePowerState() succeeded, state=%d\n", state); - return state; -} - + 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?"...":"")); -#if WIN9X_SUPPORT -// Print SMARTVSD error message, return errno + // 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 -static int smartvsd_error() -{ - char path[MAX_PATH]; - unsigned len; - if (!(5 <= (len = GetSystemDirectoryA(path, MAX_PATH)) && len < MAX_PATH/2)) - return ENOENT; - // SMARTVSD.VXD present? - strcpy(path+len, "\\IOSUBSYS\\SMARTVSD.VXD"); - if (!access(path, 0)) { - // Yes, standard IDE driver used? - HANDLE h; - if ( (h = CreateFileA("\\\\.\\ESDI_506", - GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE - && GetLastError() == ERROR_FILE_NOT_FOUND ) { - pout("Standard IDE driver ESDI_506.PDR not used, or no IDE/ATA drives present.\n"); - return ENOENT; - } - else { - if (h != INVALID_HANDLE_VALUE) // should not happen - CloseHandle(h); - pout("SMART driver SMARTVSD.VXD is installed, but not loaded.\n"); - return ENOSYS; + // 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, " 0)) + break; + s += n; + if (*s == '<') // "
" + 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); } - return ENOSYS; + sd = 0; } -} -#endif // WIN9X_SUPPORT + m_ident_valid = true; + m_smart_valid = !!sd; + return true; +} -// Get default ATA device options -static const char * ata_get_def_options() +bool win_tw_cli_device::close() { - DWORD ver = GetVersion(); - if ((ver & 0x80000000) || (ver & 0xff) < 4) // Win9x/ME - return "s"; // SMART_* only - else if ((ver & 0xff) == 4) // WinNT4 - return "sc"; // SMART_*, SCSI_PASS_THROUGH - else // WinXP, 2003, Vista - return "pasifm"; // GetDevicePowerState(), ATA_, SMART_*, IDE_PASS_THROUGH, - // STORAGE_*, SCSI_MINIPORT_* + m_ident_valid = m_smart_valid = false; + return true; } -// Common routines for devices with HANDLEs - -win_smart_device::~win_smart_device() throw() +int win_tw_cli_device::ata_command_interface(smart_command_set command, int /*select*/, char * data) { - if (m_fh != INVALID_HANDLE_VALUE) - ::CloseHandle(m_fh); + 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; } -bool win_smart_device::is_open() const -{ - return (m_fh != INVALID_HANDLE_VALUE); -} -bool win_smart_device::close() +///////////////////////////////////////////////////////////////////////////// +// 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 { - if (m_fh == INVALID_HANDLE_VALUE) - return true; - BOOL rc = ::CloseHandle(m_fh); - m_fh = INVALID_HANDLE_VALUE; - return !!rc; -} +public: + win_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type); -// ATA + virtual bool open(); -win_ata_device::win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type) -: smart_device(intf, dev_name, "ata", req_type), - m_usr_options(false), - m_admin(false), - m_id_is_cached(false), - m_is_3ware(false), - m_drive(0), - m_port(-1), - m_smartver_state(0) -{ -} + virtual bool scsi_pass_through(scsi_cmnd_io * iop); -win_ata_device::~win_ata_device() throw() -{ -} +private: + bool open(int pd_num, int ld_num, int tape_num, int sub_addr); +}; -// Open ATA device +///////////////////////////////////////////////////////////////////////////// -bool win_ata_device::open() +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); - // [sh]d[a-z](:[saicmfp]+)? => Physical drive 0-25, with options - char drive[1+1] = "", options[8+1] = ""; int n1 = -1, n2 = -1; - if ( sscanf(name, "%*[sh]d%1[a-z]%n:%7[saicmfp]%n", drive, &n1, options, &n2) >= 1 - && ((n1 == len && !options[0]) || n2 == len) ) { - return open(drive[0] - 'a', -1, options, -1); - } - // [sh]d[a-z],N(:[saicmfp3]+)? => Physical drive 0-25, RAID port N, with options - drive[0] = 0; options[0] = 0; n1 = -1; n2 = -1; - unsigned port = ~0; - if ( sscanf(name, "%*[sh]d%1[a-z],%u%n:%8[saicmfp3]%n", drive, &port, &n1, options, &n2) >= 2 - && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) { - return open(drive[0] - 'a', -1, options, port); + // 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,N => Physical drive , RAID port N - int phydrive = -1; port = ~0; n1 = -1; n2 = -1; - if ( sscanf(name, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1 - && phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) { - return open(phydrive, -1, "", (int)port); + 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); + return open(-1, logdrive, -1, -1); + } + // n?st => tape drive (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 => tape drive + 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_ata_device::open(int phydrive, int logdrive, const char * options, int port) +bool win_scsi_device::open(int pd_num, int ld_num, int tape_num, int /*sub_addr*/) { - // path depends on Windows Version - char devpath[30]; - if (win9x && 0 <= phydrive && phydrive <= 7) - // Use patched "smartvse.vxd" for drives 4-7, see INSTALL file for details - strcpy(devpath, (phydrive <= 3 ? "\\\\.\\SMARTVSD" : "\\\\.\\SMARTVSE")); - else if (!win9x && 0 <= phydrive && phydrive <= 255) - snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", phydrive); - else if (!win9x && 0 <= logdrive && logdrive <= 'Z'-'A') - snprintf(devpath, sizeof(devpath)-1, "\\\\.\\%c:", 'A'+logdrive); - else - return set_err(ENOENT); + 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 = INVALID_HANDLE_VALUE; - if (win9x || !(*options && !options[strspn(options, "fp")])) { - // Open with admin rights - m_admin = true; - h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, - FILE_SHARE_READ|FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, 0); - } - if (!win9x && h == INVALID_HANDLE_VALUE) { - // Open without admin rights - m_admin = false; - h = CreateFileA(devpath, 0, - FILE_SHARE_READ|FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, 0); - } + HANDLE h = CreateFileA(b, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, 0); if (h == INVALID_HANDLE_VALUE) { - long err = GetLastError(); -#if WIN9X_SUPPORT - if (win9x && phydrive <= 3 && err == ERROR_FILE_NOT_FOUND) - smartvsd_error(); -#endif - 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); + set_err(ENODEV, "%s: Open failed, Error=%u", b, (unsigned)GetLastError()); return false; } set_fh(h); + return true; +} - // Warn once if admin rights are missing - if (!m_admin) { - static bool noadmin_warning = false; - if (!noadmin_warning) { - pout("Warning: Limited functionality due to missing admin rights\n"); - noadmin_warning = true; - } - } - if (ata_debugmode > 1) - pout("%s: successfully opened%s\n", devpath, (!m_admin ? " (without admin rights)" :"")); +typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[64]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; - m_usr_options = false; - if (*options) { - // Save user options - m_options = options; m_usr_options = true; - } - else if (port >= 0) - // RAID: SMART_* and SCSI_MINIPORT - m_options = "s3"; - else { - // Set default options according to Windows version - static const char * def_options = ata_get_def_options(); - m_options = def_options; - } - // NT4/2000/XP: SMART_GET_VERSION may spin up disk, so delay until first real SMART_* call - m_drive = 0; m_port = port; - if (!win9x && port < 0) - return true; +// 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]; + }; - // Win9X/ME: Get drive map - // RAID: Get port map - GETVERSIONINPARAMS_EX vers_ex; - int devmap = smart_get_version(h, &vers_ex); + SCSI_PASS_THROUGH_WITH_BUFFERS sb; + memset(&sb, 0, sizeof(sb)); - // 3ware RAID if vendor id present - m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE); + // DATA_OUT not implemented yet + if (!( sbd->spt.DataIn == SCSI_IOCTL_DATA_IN + && sbd->spt.DataTransferLength <= sizeof(sb.ucDataBuf))) + return ERROR_INVALID_PARAMETER; - unsigned long portmap = 0; - if (port >= 0 && devmap >= 0) { - // 3ware RAID: check vendor id - if (!m_is_3ware) { - pout("SMART_GET_VERSION returns unknown Identifier = 0x%04x\n" - "This is no 3ware 9000 controller or driver has no SMART support.\n", - vers_ex.wIdentifier); - devmap = -1; + 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 : ""); + 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 - portmap = vers_ex.dwDeviceMapEx; + j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n"); + pout("%s", buff); } - if (devmap < 0) { - pout("%s: ATA driver has no SMART support\n", devpath); - if (!is_permissive()) { - close(); - return set_err(ENOSYS); - } - devmap = 0x0f; + + 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; } - m_smartver_state = 1; - if (port >= 0) { - // 3ware RAID: update devicemap first + 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); - if (!update_3ware_devicemap_ioctl(h)) { - if ( smart_get_version(h, &vers_ex) >= 0 - && vers_ex.wIdentifier == SMART_VENDOR_3WARE ) - portmap = vers_ex.dwDeviceMapEx; - } - // Check port existence - if (!(portmap & (1L << port))) { - if (!is_permissive()) { - close(); - return set_err(ENOENT, "%s: Port %d is empty or does not exist", devpath, port); - } - } - return true; + 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; } - // Win9x/ME: Check device presence & type - if (((devmap >> (phydrive & 0x3)) & 0x11) != 0x01) { - unsigned char atapi = (devmap >> (phydrive & 0x3)) & 0x10; - // Win9x drive existence check may not work as expected - // The atapi.sys driver incorrectly fills in the bIDEDeviceMap with 0x01 - // (The related KB Article Q196120 is no longer available) - if (!is_permissive()) { - close(); - return set_err((atapi ? ENOSYS : ENOENT), "%s: Drive %d %s (IDEDeviceMap=0x%02x)", - devpath, phydrive, (atapi?"is an ATAPI device":"does not exist"), devmap); - } + 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(); } - // Drive number must be passed to ioctl - m_drive = (phydrive & 0x3); - return true; -} - + else + err = scsi_pass_through_indirect(get_fh(), &sb); -#if WIN9X_SUPPORT + if (err) + return set_err((err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO), + "IOCTL_SCSI_PASS_THROUGH%s failed, Error=%ld", + (direct ? "_DIRECT" : ""), err); -// Scan for ATA drives on Win9x/ME + iop->scsi_status = sb.spt.ScsiStatus; + if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) { + int slen = sb.ucSenseBuf[7] + 8; -bool win9x_smart_interface::ata_scan(smart_device_list & devlist) -{ - // Open device - const char devpath[] = "\\\\.\\SMARTVSD"; - HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, - FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); - if (h == INVALID_HANDLE_VALUE) { - if (ata_debugmode > 1) - pout(" %s: Open failed, Error=%ld\n", devpath, GetLastError()); - return true; // SMARTVSD.VXD missing or no ATA devices - } + 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; - // Get drive map - int devmap = smart_get_version(h); - CloseHandle(h); - if (devmap < 0) - return true; // Should not happen + if (iop->dxfer_len > sb.spt.DataTransferLength) + iop->resid = iop->dxfer_len - sb.spt.DataTransferLength; + else + iop->resid = 0; - // Check ATA device presence, remove ATAPI devices - devmap = (devmap & 0xf) & ~((devmap >> 4) & 0xf); - char name[20]; - for (int i = 0; i < 4; i++) { - if (!(devmap & (1 << i))) - continue; - sprintf(name, "/dev/hd%c", 'a'+i); - devlist.push_back( new win_ata_device(this, name, "ata") ); + 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; } -#endif // WIN9X_SUPPORT - ///////////////////////////////////////////////////////////////////////////// +/// Areca RAID support -// Interface to ATA devices -bool win_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +// TODO: combine with above scsi_pass_through_direct() +static long scsi_pass_through_direct(HANDLE fd, UCHAR targetid, struct scsi_cmnd_io * iop) { - // No multi-sector support for now, see above - // warning about IOCTL_ATA_PASS_THROUGH - if (!ata_cmd_is_ok(in, - true, // data_out_support - false, // !multi_sector_support - true) // ata_48bit_support - ) - return false; + int report = scsi_debugmode; // TODO - // 3ware RAID: SMART DISABLE without port number disables SMART functions - if ( m_is_3ware && m_port < 0 - && in.in_regs.command == ATA_SMART_CMD - && in.in_regs.features == ATA_SMART_DISABLE) - return set_err(ENOSYS, "SMART DISABLE requires 3ware port number"); + if (report > 0) { + int k, j; + const unsigned char * ucp = iop->cmnd; + const char * np; + char buff[256]; + const int sz = (int)sizeof(buff); - // Determine ioctl functions valid for this ATA cmd - const char * valid_options = 0; + np = scsi_get_opcode_name(ucp[0]); + j = snprintf(buff, sz, " [%s: ", np ? np : ""); + 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; - switch (in.in_regs.command) { - case ATA_IDENTIFY_DEVICE: - case ATA_IDENTIFY_PACKET_DEVICE: - // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE - // and SCSI_MINIPORT_* if requested by user - valid_options = (m_usr_options ? "saicmf" : "saicf"); - break; + 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); + } - case ATA_CHECK_POWER_MODE: - // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk - valid_options = "pai3"; - break; + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb; + if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) { + return EINVAL; + } - case ATA_SMART_CMD: - switch (in.in_regs.features) { - case ATA_SMART_READ_VALUES: - case ATA_SMART_READ_THRESHOLDS: - case ATA_SMART_AUTOSAVE: - case ATA_SMART_ENABLE: - case ATA_SMART_DISABLE: - case ATA_SMART_AUTO_OFFLINE: - // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE - // and SCSI_MINIPORT_* if requested by user - valid_options = (m_usr_options ? "saicmf" : "saicf"); - break; + 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); - case ATA_SMART_IMMEDIATE_OFFLINE: - // SMART_SEND_DRIVE_COMMAND supports ABORT_SELF_TEST only on Win9x/ME - valid_options = (m_usr_options || in.in_regs.lba_low != 127/*ABORT*/ || win9x ? - "saicm3" : "aicm3"); - break; + 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; + } - case ATA_SMART_READ_LOG_SECTOR: - // SMART_RCV_DRIVE_DATA supports this only on Win9x/ME - // Try SCSI_MINIPORT also to skip buggy class driver - // SMART functions do not support multi sector I/O. - if (in.size == 512) - valid_options = (m_usr_options || win9x ? "saicm3" : "aicm3"); - else - valid_options = "a"; - break; + 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); - case ATA_SMART_WRITE_LOG_SECTOR: - // ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT - // but SCSI_MINIPORT_* only if requested by user and single sector. - valid_options = (in.size == 512 && m_usr_options ? "am" : "a"); - break; + if (err) + { + return err; + } - case ATA_SMART_STATUS: - // May require lba_mid,lba_high register return - if (in.out_needed.is_set()) - valid_options = (m_usr_options ? "saimf" : "saif"); - else - valid_options = (m_usr_options ? "saicmf" : "saicf"); - break; + iop->scsi_status = sb.spt.ScsiStatus; + if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) { + int slen = sb.ucSenseBuf[7] + 8; - default: - // Unknown SMART command, handle below - break; + 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); } - break; + 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; - default: - // Other ATA command, handle below - break; - } + if (iop->dxfer_len > sb.spt.DataTransferLength) + iop->resid = iop->dxfer_len - sb.spt.DataTransferLength; + else + iop->resid = 0; - if (!valid_options) { - // No special ATA command found above, select a generic pass through ioctl. - if (!( in.direction == ata_cmd_in::no_data - || (in.direction == ata_cmd_in::data_in && in.size == 512)) - || in.in_regs.is_48bit_cmd() ) - // DATA_OUT, more than one sector, 48-bit command: ATA_PASS_THROUGH only - valid_options = "a"; - else if (in.out_needed.is_set()) - // Need output registers: ATA/IDE_PASS_THROUGH - valid_options = "ai"; - else - valid_options = "aic"; + 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); } - if (!m_admin) { - // Restrict to IOCTL_STORAGE_* - if (strchr(valid_options, 'f')) - valid_options = "f"; - else if (strchr(valid_options, 'p')) - valid_options = "p"; - else - return set_err(ENOSYS, "Function requires admin rights"); - } + return 0; +} - // Set IDEREGS - IDEREGS regs, prev_regs; - { - const ata_in_regs & lo = in.in_regs; - regs.bFeaturesReg = lo.features; - regs.bSectorCountReg = lo.sector_count; - regs.bSectorNumberReg = lo.lba_low; - regs.bCylLowReg = lo.lba_mid; - regs.bCylHighReg = lo.lba_high; - regs.bDriveHeadReg = lo.device; - regs.bCommandReg = lo.command; - regs.bReserved = 0; - } - if (in.in_regs.is_48bit_cmd()) { - const ata_in_regs & hi = in.in_regs.prev; - prev_regs.bFeaturesReg = hi.features; - prev_regs.bSectorCountReg = hi.sector_count; - prev_regs.bSectorNumberReg = hi.lba_low; - prev_regs.bCylLowReg = hi.lba_mid; - prev_regs.bCylHighReg = hi.lba_high; - prev_regs.bDriveHeadReg = hi.device; - prev_regs.bCommandReg = hi.command; - prev_regs.bReserved = 0; - } - // Set data direction - int datasize = 0; - char * data = 0; - switch (in.direction) { - case ata_cmd_in::no_data: - break; - case ata_cmd_in::data_in: - datasize = (int)in.size; - data = (char *)in.buffer; - break; - case ata_cmd_in::data_out: - datasize = -(int)in.size; - data = (char *)in.buffer; - break; - default: - return set_err(EINVAL, "win_ata_device::ata_pass_through: invalid direction=%d", - (int)in.direction); - } +///////////////////////////////////////////////////////////////////////////// +// 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; +}; - // Try all valid ioctls in the order specified in m_options - bool powered_up = false; - bool out_regs_set = false; - bool id_is_cached = false; - const char * options = m_options.c_str(); +///////////////////////////////////////////////////////////////////////////// - for (int i = 0; ; i++) { - char opt = options[i]; +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); +} - if (!opt) { - if (in.in_regs.command == ATA_CHECK_POWER_MODE && powered_up) { - // Power up reported by GetDevicePowerState() and no ioctl available - // to detect the actual mode of the drive => simulate ATA result ACTIVE/IDLE. - regs.bSectorCountReg = 0xff; - out_regs_set = true; - break; - } - // No IOCTL found - return set_err(ENOSYS); - } - if (!strchr(valid_options, opt)) - // Invalid for this command - continue; +bool win_areca_scsi_device::open() +{ + HANDLE hFh; - errno = 0; - assert( datasize == 0 || datasize == 512 - || (datasize == -512 && strchr("am", opt)) - || (datasize > 512 && opt == 'a')); - int rc; - switch (opt) { - default: assert(0); - case 's': - // call SMART_GET_VERSION once for each drive - if (m_smartver_state > 1) { - rc = -1; errno = ENOSYS; - break; - } - if (!m_smartver_state) { - assert(m_port == -1); - GETVERSIONINPARAMS_EX vers_ex; - if (smart_get_version(get_fh(), &vers_ex) < 0) { - if (!failuretest_permissive) { - m_smartver_state = 2; - rc = -1; errno = ENOSYS; - break; - } - failuretest_permissive--; - } - else { - // 3ware RAID if vendor id present - m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE); - } + 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; + } - m_smartver_state = 1; - } - rc = smart_ioctl(get_fh(), m_drive, ®s, data, datasize, m_port); - out_regs_set = (in.in_regs.features == ATA_SMART_STATUS); - id_is_cached = (m_port < 0 && !win9x); // Not cached by 3ware or Win9x/ME driver - break; - case 'm': - rc = ata_via_scsi_miniport_smart_ioctl(get_fh(), ®s, data, datasize); - id_is_cached = (m_port < 0 && !win9x); - break; - case 'a': - rc = ata_pass_through_ioctl(get_fh(), ®s, - (in.in_regs.is_48bit_cmd() ? &prev_regs : 0), - data, datasize); - out_regs_set = true; - break; - case 'i': - rc = ide_pass_through_ioctl(get_fh(), ®s, data, datasize); - out_regs_set = true; - break; - case 'c': - rc = ata_via_scsi_pass_through_ioctl(get_fh(), ®s, data, datasize); - break; - case 'f': - if (in.in_regs.command == ATA_IDENTIFY_DEVICE) { - rc = get_identify_from_device_property(get_fh(), (ata_identify_device *)data); - id_is_cached = true; - } - else if (in.in_regs.command == ATA_SMART_CMD) switch (in.in_regs.features) { - case ATA_SMART_READ_VALUES: - rc = storage_predict_failure_ioctl(get_fh(), data); - if (rc > 0) - rc = 0; - break; - case ATA_SMART_ENABLE: - rc = 0; - break; - case ATA_SMART_STATUS: - rc = storage_predict_failure_ioctl(get_fh()); - if (rc == 0) { - // Good SMART status - out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f; - } - else if (rc > 0) { - // Bad SMART status - out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4; - rc = 0; - } - break; - default: - errno = ENOSYS; rc = -1; - } - else { - errno = ENOSYS; rc = -1; - } - break; - case '3': - rc = ata_via_3ware_miniport_ioctl(get_fh(), ®s, data, datasize, m_port); - out_regs_set = true; - break; - case 'p': - assert(in.in_regs.command == ATA_CHECK_POWER_MODE && in.size == 0); - rc = get_device_power_state(get_fh()); - if (rc == 0) { - // Power down reported by GetDevicePowerState(), using a passthrough ioctl would - // spin up the drive => simulate ATA result STANDBY. - regs.bSectorCountReg = 0x00; - out_regs_set = true; - } - else if (rc > 0) { - // Power up reported by GetDevicePowerState(), but this reflects the actual mode - // only if it is selected by the device driver => try a passthrough ioctl to get the - // actual mode, if none available simulate ACTIVE/IDLE. - powered_up = true; - rc = -1; errno = ENOSYS; - } - break; - } + set_fh(hFh); + return true; +} - if (!rc) - // Working ioctl found - break; +smart_device * win_areca_scsi_device::autodetect_open() +{ + return this; +} - if (errno != ENOSYS) - // Abort on I/O error - return set_err(errno); +int win_areca_scsi_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) +{ + int ioctlreturn = 0; - out_regs_set = false; - // CAUTION: *_ioctl() MUST NOT change "regs" Parameter in the ENOSYS case - } + 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 IDEREGS if set - if (out_regs_set) { - ata_out_regs & lo = out.out_regs; - lo.error = regs.bFeaturesReg; - lo.sector_count = regs.bSectorCountReg; - lo.lba_low = regs.bSectorNumberReg; - lo.lba_mid = regs.bCylLowReg; - lo.lba_high = regs.bCylHighReg; - lo.device = regs.bDriveHeadReg; - lo.status = regs.bCommandReg; - if (in.in_regs.is_48bit_cmd()) { - ata_out_regs & hi = out.out_regs.prev; - hi.sector_count = prev_regs.bSectorCountReg; - hi.lba_low = prev_regs.bSectorNumberReg; - hi.lba_mid = prev_regs.bCylLowReg; - hi.lba_high = prev_regs.bCylHighReg; - } + 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"); } - if ( in.in_regs.command == ATA_IDENTIFY_DEVICE - || in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE) - // Update ata_identify_is_cached() result according to ioctl used. - m_id_is_cached = id_is_cached; + // atomic access to driver + WaitForSingleObject(m_mutex, INFINITE); return true; } -// Return true if OS caches the ATA identify sector -bool win_ata_device::ata_identify_is_cached() const + +bool win_areca_scsi_device::arcmsr_unlock() { - return m_id_is_cached; + if( m_mutex != NULL) + { + ReleaseMutex(m_mutex); + CloseHandle(m_mutex); + } + + return true; } -////////////////////////////////////////////////////////////////////// -// csmi_ata_device +///////////////////////////////////////////////////////////////////////////// +// win_areca_ata_device +// SATA(ATA) device behind Areca RAID Controller -bool csmi_device::get_phy_info(CSMI_SAS_PHY_INFO & phy_info) +class win_areca_ata_device +: public /*implements*/ areca_ata_device, + public /*extends*/ win_smart_device { - // Get driver info to check CSMI support - CSMI_SAS_DRIVER_INFO_BUFFER driver_info_buf; - memset(&driver_info_buf, 0, sizeof(driver_info_buf)); - if (!csmi_ioctl(CC_CSMI_SAS_GET_DRIVER_INFO, &driver_info_buf.IoctlHeader, sizeof(driver_info_buf))) - return false; +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); - if (scsi_debugmode > 1) { - const CSMI_SAS_DRIVER_INFO & driver_info = driver_info_buf.Information; - pout("CSMI_SAS_DRIVER_INFO:\n"); - pout(" Name: \"%.81s\"\n", driver_info.szName); - pout(" Description: \"%.81s\"\n", driver_info.szDescription); - pout(" Revision: %d.%d\n", driver_info.usMajorRevision, driver_info.usMinorRevision); - } +private: + HANDLE m_mutex; +}; - // Get Phy info - CSMI_SAS_PHY_INFO_BUFFER phy_info_buf; - memset(&phy_info_buf, 0, sizeof(phy_info_buf)); - if (!csmi_ioctl(CC_CSMI_SAS_GET_PHY_INFO, &phy_info_buf.IoctlHeader, sizeof(phy_info_buf))) - return false; - phy_info = phy_info_buf.Information; - if (phy_info.bNumberOfPhys > sizeof(phy_info.Phy)/sizeof(phy_info.Phy[0])) - return set_err(EIO, "CSMI_SAS_PHY_INFO: Bogus NumberOfPhys=%d", phy_info.bNumberOfPhys); +///////////////////////////////////////////////////////////////////////////// - if (scsi_debugmode > 1) { - pout("CSMI_SAS_PHY_INFO: NumberOfPhys=%d\n", phy_info.bNumberOfPhys); - for (int i = 0; i < phy_info.bNumberOfPhys; i++) { - const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i]; - const CSMI_SAS_IDENTIFY & id = pe.Identify, & at = pe.Attached; - pout("Phy[%d] Port: 0x%02x\n", i, pe.bPortIdentifier); - pout(" Type: 0x%02x, 0x%02x\n", id.bDeviceType, at.bDeviceType); - pout(" InitProto: 0x%02x, 0x%02x\n", id.bInitiatorPortProtocol, at.bInitiatorPortProtocol); - pout(" TargetProto: 0x%02x, 0x%02x\n", id.bTargetPortProtocol, at.bTargetPortProtocol); - pout(" PhyIdent: 0x%02x, 0x%02x\n", id.bPhyIdentifier, at.bPhyIdentifier); - const unsigned char * b = id.bSASAddress; - pout(" SASAddress: %02x %02x %02x %02x %02x %02x %02x %02x, ", - b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - b = at.bSASAddress; - pout( "%02x %02x %02x %02x %02x %02x %02x %02x\n", - b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - } +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; } -bool csmi_device::check_phy(const CSMI_SAS_PHY_INFO & phy_info, unsigned phy_no) +smart_device * win_areca_ata_device::autodetect_open() { - // Check Phy presence - if (phy_no >= phy_info.bNumberOfPhys) - return set_err(ENOENT, "Port %u does not exist (#ports: %d)", phy_no, - phy_info.bNumberOfPhys); - - const CSMI_SAS_PHY_ENTITY & phy_ent = phy_info.Phy[phy_no]; - if (phy_ent.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED) - return set_err(ENOENT, "No device on port %u", phy_no); + // autodetect device type + int is_ata = arcmsr_get_dev_type(); + if(is_ata < 0) + { + set_err(EIO); + return this; + } - switch (phy_ent.Attached.bTargetPortProtocol) { - case CSMI_SAS_PROTOCOL_SATA: - case CSMI_SAS_PROTOCOL_STP: - break; - default: - return set_err(ENOENT, "No SATA device on port %u (protocol: %u)", - phy_no, phy_ent.Attached.bTargetPortProtocol); + if(is_ata == 1) + { + // SATA device + return this; } - return true; + // 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(); } -bool csmi_device::select_phy(unsigned phy_no) +int win_areca_ata_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) { - CSMI_SAS_PHY_INFO phy_info; - if (!get_phy_info(phy_info)) - return false; + 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; + } + } - if (!check_phy(phy_info, phy_no)) - return false; - - m_phy_ent = phy_info.Phy[phy_no]; - return true; + return ioctlreturn; } - -bool csmi_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +bool win_areca_ata_device::arcmsr_lock() { - if (!ata_cmd_is_ok(in, - true, // data_out_support - true, // multi_sector_support - true) // ata_48bit_support - ) - return false; - - // Create buffer with appropriate size - raw_buffer pthru_raw_buf(sizeof(CSMI_SAS_STP_PASSTHRU_BUFFER) + in.size); - CSMI_SAS_STP_PASSTHRU_BUFFER * pthru_buf = (CSMI_SAS_STP_PASSTHRU_BUFFER *)pthru_raw_buf.data(); - - // Set addresses from Phy info - CSMI_SAS_STP_PASSTHRU & pthru = pthru_buf->Parameters; - const CSMI_SAS_PHY_ENTITY & phy_ent = get_phy_ent(); - pthru.bPhyIdentifier = phy_ent.Identify.bPhyIdentifier; - pthru.bPortIdentifier = phy_ent.bPortIdentifier; - memcpy(pthru.bDestinationSASAddress, phy_ent.Attached.bSASAddress, - sizeof(pthru.bDestinationSASAddress)); - pthru.bConnectionRate = CSMI_SAS_LINK_RATE_NEGOTIATED; +#define SYNCOBJNAME "Global\\SynIoctlMutex" + int ctlrnum = -1; + char mutexstr[64]; - // Set transfer mode - switch (in.direction) { - case ata_cmd_in::no_data: - pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_UNSPECIFIED; - break; - case ata_cmd_in::data_in: - pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_READ; - pthru.uDataLength = in.size; - break; - case ata_cmd_in::data_out: - pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_WRITE; - pthru.uDataLength = in.size; - memcpy(pthru_buf->bDataBuffer, in.buffer, in.size); - break; - default: - return set_err(EINVAL, "csmi_ata_device::ata_pass_through: invalid direction=%d", - (int)in.direction); - } + if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1) + return set_err(EINVAL, "unable to parse device name"); - // Set host-to-device FIS + snprintf(mutexstr, sizeof(mutexstr), "%s%d", SYNCOBJNAME, ctlrnum); + m_mutex = CreateMutex(NULL, FALSE, mutexstr); + if ( m_mutex == NULL ) { - unsigned char * fis = pthru.bCommandFIS; - const ata_in_regs & lo = in.in_regs; - const ata_in_regs & hi = in.in_regs.prev; - fis[ 0] = 0x27; // Type: host-to-device FIS - fis[ 1] = 0x80; // Bit7: Update command register - fis[ 2] = lo.command; - fis[ 3] = lo.features; - fis[ 4] = lo.lba_low; - fis[ 5] = lo.lba_mid; - fis[ 6] = lo.lba_high; - fis[ 7] = lo.device; - fis[ 8] = hi.lba_low; - fis[ 9] = hi.lba_mid; - fis[10] = hi.lba_high; - fis[11] = hi.features; - fis[12] = lo.sector_count; - fis[13] = hi.sector_count; + return set_err(EIO, "CreateMutex failed"); } - // Call ioctl - if (!csmi_ioctl(CC_CSMI_SAS_STP_PASSTHRU, &pthru_buf->IoctlHeader, pthru_raw_buf.size())) { - return false; - } + // atomic access to driver + WaitForSingleObject(m_mutex, INFINITE); - // Get device-to-host FIS + return true; +} + + +bool win_areca_ata_device::arcmsr_unlock() +{ + if( m_mutex != NULL) { - const unsigned char * fis = pthru_buf->Status.bStatusFIS; - ata_out_regs & lo = out.out_regs; - lo.status = fis[ 2]; - lo.error = fis[ 3]; - lo.lba_low = fis[ 4]; - lo.lba_mid = fis[ 5]; - lo.lba_high = fis[ 6]; - lo.device = fis[ 7]; - lo.sector_count = fis[12]; - if (in.in_regs.is_48bit_cmd()) { - ata_out_regs & hi = out.out_regs.prev; - hi.lba_low = fis[ 8]; - hi.lba_mid = fis[ 9]; - hi.lba_high = fis[10]; - hi.sector_count = fis[13]; - } + ReleaseMutex(m_mutex); + CloseHandle(m_mutex); } - // Get data - if (in.direction == ata_cmd_in::data_in) - // TODO: Check ptru_buf->Status.uDataBytes - memcpy(in.buffer, pthru_buf->bDataBuffer, in.size); - return true; } -////////////////////////////////////////////////////////////////////// -// win_csmi_device - -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_phy_no(0) -{ -} +///////////////////////////////////////////////////////////////////////////// +// win_aacraid_device +// PMC aacraid Support -win_csmi_device::~win_csmi_device() throw() +class win_aacraid_device +:public /*implements*/ scsi_device, +public /*extends*/ win_smart_device { - if (m_fh != INVALID_HANDLE_VALUE) - CloseHandle(m_fh); -} +public: + win_aacraid_device(smart_interface *intf, const char *dev_name,unsigned int ctrnum, unsigned int target, unsigned int lun); -bool win_csmi_device::is_open() const -{ - return (m_fh != INVALID_HANDLE_VALUE); -} + virtual ~win_aacraid_device() throw(); -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; -} + virtual bool open(); + virtual bool scsi_pass_through(struct scsi_cmnd_io *iop); -bool win_csmi_device::open_scsi() -{ - // Parse name - unsigned contr_no = ~0, phy_no = ~0; int nc = -1; - const char * name = skipdev(get_dev_name()); - if (!( sscanf(name, "csmi%u,%u%n", &contr_no, &phy_no, &nc) >= 0 - && nc == (int)strlen(name) && contr_no <= 9 && phy_no < 32) ) - return set_err(EINVAL); +private: + //Device Host number + int m_ctrnum; - // Open controller handle - char devpath[30]; - snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%u:", contr_no); + //Channel(Lun) of the device + int m_lun; - HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, - FILE_SHARE_READ|FILE_SHARE_WRITE, - (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0); + //Id of the device + int m_target; +}; - 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_phy_no = phy_no; - return true; +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_csmi_device::open() +bool win_aacraid_device::open() { - if (!open_scsi()) - return false; + if (is_open()) + return true; - // Get Phy info for this drive - if (!select_phy(m_phy_no)) { - close(); - return false; - } + 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_csmi_device::csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer, - unsigned csmi_bufsiz) +bool win_aacraid_device::scsi_pass_through(struct scsi_cmnd_io *iop) { - // 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); + 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 : ""); + 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; - // 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); + 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((const char *)iop->dxferp, + (trunc ? 256 : (int)iop->dxfer_len) , 1); + } else - return set_err(EIO, "CSMI(%u) failed with Error=%ld", code, err); + 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()); } - // Check result - if (csmi_buffer->ReturnCode) { - if (scsi_debugmode) { - pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, ReturnCode=%lu\n", - code, csmi_buffer->ReturnCode); + 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]); } - return set_err(EIO, "CSMI(%u) failed with ReturnCode=%lu", code, csmi_buffer->ReturnCode); + } + else { + iop->resp_sense_len = 0; } - if (scsi_debugmode > 1) - pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) succeeded, bytes returned: %lu\n", code, num_out); - + 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((CHAR*)pDataIO, (trunc ? 256 : (int)(iop->dxfer_len)) , 1); + } return true; } ///////////////////////////////////////////////////////////////////////////// -// ASPI Interface (for SCSI devices on 9x/ME) -///////////////////////////////////////////////////////////////////////////// - -#if WIN9X_SUPPORT - -#pragma pack(1) - -#define ASPI_SENSE_SIZE 18 - -// ASPI SCSI Request block header +// win_nvme_device -typedef struct { - unsigned char cmd; // 00: Command code - unsigned char status; // 01: ASPI status - unsigned char adapter; // 02: Host adapter number - unsigned char flags; // 03: Request flags - unsigned char reserved[4]; // 04: 0 -} ASPI_SRB_HEAD; - -// SRB for host adapter inquiry - -typedef struct { - ASPI_SRB_HEAD h; // 00: Header - unsigned char adapters; // 08: Number of adapters - unsigned char target_id; // 09: Target ID ? - char manager_id[16]; // 10: SCSI manager ID - char adapter_id[16]; // 26: Host adapter ID - unsigned char parameters[16]; // 42: Host adapter unique parmameters -} ASPI_SRB_INQUIRY; - -// SRB for get device type - -typedef struct { - ASPI_SRB_HEAD h; // 00: Header - unsigned char target_id; // 08: Target ID - unsigned char lun; // 09: LUN - unsigned char devtype; // 10: Device type - unsigned char reserved; // 11: Reserved -} ASPI_SRB_DEVTYPE; - -// SRB for SCSI I/O +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); -typedef struct { - ASPI_SRB_HEAD h; // 00: Header - unsigned char target_id; // 08: Target ID - unsigned char lun; // 09: LUN - unsigned char reserved[2]; // 10: Reserved - unsigned long data_size; // 12: Data alloc. lenght - void * data_addr; // 16: Data buffer pointer - unsigned char sense_size; // 20: Sense alloc. length - unsigned char cdb_size; // 21: CDB length - unsigned char host_status; // 22: Host status - unsigned char target_status; // 23: Target status - void * event_handle; // 24: Event handle - unsigned char workspace[20]; // 28: ASPI workspace - unsigned char cdb[16+ASPI_SENSE_SIZE]; -} ASPI_SRB_IO; - -// Macro to retrieve start of sense information -#define ASPI_SRB_SENSE(srb,cdbsz) ((srb)->cdb + 16) - -// SRB union - -typedef union { - ASPI_SRB_HEAD h; // Common header - ASPI_SRB_INQUIRY q; // Inquiry - ASPI_SRB_DEVTYPE t; // Device type - ASPI_SRB_IO i; // I/O -} ASPI_SRB; + virtual bool open(); -#pragma pack() + virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); -// ASPI commands -#define ASPI_CMD_ADAPTER_INQUIRE 0x00 -#define ASPI_CMD_GET_DEVICE_TYPE 0x01 -#define ASPI_CMD_EXECUTE_IO 0x02 -#define ASPI_CMD_ABORT_IO 0x03 - -// Request flags -#define ASPI_REQFLAG_DIR_TO_HOST 0x08 -#define ASPI_REQFLAG_DIR_TO_TARGET 0x10 -#define ASPI_REQFLAG_DIR_NO_XFER 0x18 -#define ASPI_REQFLAG_EVENT_NOTIFY 0x40 - -// ASPI status -#define ASPI_STATUS_IN_PROGRESS 0x00 -#define ASPI_STATUS_NO_ERROR 0x01 -#define ASPI_STATUS_ABORTED 0x02 -#define ASPI_STATUS_ABORT_ERR 0x03 -#define ASPI_STATUS_ERROR 0x04 -#define ASPI_STATUS_INVALID_COMMAND 0x80 -#define ASPI_STATUS_INVALID_ADAPTER 0x81 -#define ASPI_STATUS_INVALID_TARGET 0x82 -#define ASPI_STATUS_NO_ADAPTERS 0xE8 - -// Adapter (host) status -#define ASPI_HSTATUS_NO_ERROR 0x00 -#define ASPI_HSTATUS_SELECTION_TIMEOUT 0x11 -#define ASPI_HSTATUS_DATA_OVERRUN 0x12 -#define ASPI_HSTATUS_BUS_FREE 0x13 -#define ASPI_HSTATUS_BUS_PHASE_ERROR 0x14 -#define ASPI_HSTATUS_BAD_SGLIST 0x1A - -// Target status -#define ASPI_TSTATUS_NO_ERROR 0x00 -#define ASPI_TSTATUS_CHECK_CONDITION 0x02 -#define ASPI_TSTATUS_BUSY 0x08 -#define ASPI_TSTATUS_RESERV_CONFLICT 0x18 - - -static HINSTANCE h_aspi_dll; // DLL handle -static UINT (* aspi_entry)(ASPI_SRB * srb); // ASPI entrypoint -static unsigned num_aspi_adapters; - -#ifdef __CYGWIN__ -// h_aspi_dll+aspi_entry is not inherited by Cygwin's fork() -static DWORD aspi_dll_pid; // PID of DLL owner to detect fork() -#define aspi_entry_valid() (aspi_entry && (aspi_dll_pid == GetCurrentProcessId())) -#else -#define aspi_entry_valid() (!!aspi_entry) -#endif + bool open_scsi(int n); + bool probe(); -static int aspi_call(ASPI_SRB * srb) -{ - int i; - aspi_entry(srb); - i = 0; - while (((volatile ASPI_SRB *)srb)->h.status == ASPI_STATUS_IN_PROGRESS) { - if (++i > 100/*10sek*/) { - pout("ASPI Adapter %u: Timed out\n", srb->h.adapter); - aspi_entry = 0; - h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE; - errno = EIO; - return -1; - } - if (scsi_debugmode > 1) - pout("ASPI Adapter %u: Waiting (%d) ...\n", srb->h.adapter, i); - Sleep(100); - } - return 0; -} +private: + int m_scsi_no; +}; -// Get ASPI entrypoint from wnaspi32.dll +///////////////////////////////////////////////////////////////////////////// -static FARPROC aspi_get_address(const char * name, int verbose) +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) { - FARPROC addr; - assert(h_aspi_dll && h_aspi_dll != INVALID_HANDLE_VALUE); - - if (!(addr = GetProcAddress(h_aspi_dll, name))) { - if (verbose) - pout("Missing %s() in WNASPI32.DLL\n", name); - aspi_entry = 0; - FreeLibrary(h_aspi_dll); - h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE; - errno = ENOSYS; - return 0; - } - return addr; } - -static int aspi_open_dll(int verbose) +bool win_nvme_device::open_scsi(int n) { - UINT (*aspi_info)(void); - UINT info, rc; - - assert(!aspi_entry_valid()); - - // Check structure layout - assert(sizeof(ASPI_SRB_HEAD) == 8); - assert(sizeof(ASPI_SRB_INQUIRY) == 58); - assert(sizeof(ASPI_SRB_DEVTYPE) == 12); - assert(sizeof(ASPI_SRB_IO) == 64+ASPI_SENSE_SIZE); - assert(offsetof(ASPI_SRB,h.cmd) == 0); - assert(offsetof(ASPI_SRB,h.flags) == 3); - assert(offsetof(ASPI_SRB_IO,lun) == 9); - assert(offsetof(ASPI_SRB_IO,data_addr) == 16); - assert(offsetof(ASPI_SRB_IO,workspace) == 28); - assert(offsetof(ASPI_SRB_IO,cdb) == 48); - - if (h_aspi_dll == INVALID_HANDLE_VALUE) { - // do not retry - errno = ENOENT; - return -1; - } - - // Load ASPI DLL - if (!(h_aspi_dll = LoadLibraryA("WNASPI32.DLL"))) { - if (verbose) - pout("Cannot load WNASPI32.DLL, Error=%ld\n", GetLastError()); - h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE; - errno = ENOENT; - return -1; - } - if (scsi_debugmode > 1) { - // Print full path of WNASPI32.DLL - char path[MAX_PATH]; - if (!GetModuleFileName(h_aspi_dll, path, sizeof(path))) - strcpy(path, "*unknown*"); - pout("Using ASPI interface \"%s\"\n", path); - } + // TODO: Use common open function for all devices using "\\.\ScsiN:" + char devpath[32]; + snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%d:", n); - // Get ASPI entrypoints - if (!(aspi_info = (UINT (*)(void))aspi_get_address("GetASPI32SupportInfo", verbose))) - return -1; - if (!(aspi_entry = (UINT (*)(ASPI_SRB *))aspi_get_address("SendASPI32Command", verbose))) - return -1; + HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0); - // Init ASPI manager and get number of adapters - info = (aspi_info)(); - if (scsi_debugmode > 1) - pout("GetASPI32SupportInfo() returns 0x%04x\n", info); - rc = (info >> 8) & 0xff; - if (rc == ASPI_STATUS_NO_ADAPTERS) { - num_aspi_adapters = 0; - } - else if (rc == ASPI_STATUS_NO_ERROR) { - num_aspi_adapters = info & 0xff; - } - else { - if (verbose) - pout("Got strange 0x%04x from GetASPI32SupportInfo()\n", info); - aspi_entry = 0; - FreeLibrary(h_aspi_dll); - h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE; - errno = ENOENT; - return -1; + 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 (scsi_debugmode) - pout("%u ASPI Adapter%s detected\n",num_aspi_adapters, (num_aspi_adapters!=1?"s":"")); + if (nvme_debugmode > 1) + pout(" %s: successfully opened\n", devpath); -#ifdef __CYGWIN__ - // save PID to detect fork() in aspi_entry_valid() - aspi_dll_pid = GetCurrentProcessId(); -#endif - assert(aspi_entry_valid()); - return 0; + set_fh(h); + return true; } +// Check if NVMe pass-through works +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; +} -static int aspi_io_call(ASPI_SRB * srb, unsigned timeout) +bool win_nvme_device::open() { - HANDLE event; - // Create event - if (!(event = CreateEventA(NULL, FALSE, FALSE, NULL))) { - pout("CreateEvent(): Error=%ld\n", GetLastError()); return -EIO; - } - srb->i.event_handle = event; - srb->h.flags |= ASPI_REQFLAG_EVENT_NOTIFY; - // Start ASPI request - aspi_entry(srb); - if (((volatile ASPI_SRB *)srb)->h.status == ASPI_STATUS_IN_PROGRESS) { - // Wait for event - DWORD rc = WaitForSingleObject(event, timeout*1000L); - if (rc != WAIT_OBJECT_0) { - if (rc == WAIT_TIMEOUT) { - pout("ASPI Adapter %u, ID %u: Timed out after %u seconds\n", - srb->h.adapter, srb->i.target_id, timeout); - } - else { - pout("WaitForSingleObject(%lx) = 0x%lx,%ld, Error=%ld\n", - (unsigned long)(ULONG_PTR)event, rc, rc, GetLastError()); + 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(); } - // TODO: ASPI_ABORT_IO command - aspi_entry = 0; - h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE; - return -EIO; - } - } - CloseHandle(event); - return 0; -} + if (!is_open()) + return set_err(ENOENT); + clear_err(); + } + else { + // /dev/nvmesN* -> use "\\.\ScsiN:" + if (!open_scsi(no)) + return false; + m_scsi_no = no; + } -win_aspi_device::win_aspi_device(smart_interface * intf, - const char * dev_name, const char * req_type) -: smart_device(intf, dev_name, "scsi", req_type), - m_adapter(-1), m_id(0) -{ -} + if (!get_nsid()) + set_nsid(nsid); + } + else { + // Reopen same "\\.\ScsiN:" + if (!open_scsi(m_scsi_no)) + return false; + } -bool win_aspi_device::is_open() const -{ - return (m_adapter >= 0); + return true; } -bool win_aspi_device::open() +bool win_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) { - // scsi[0-9][0-f] => ASPI Adapter 0-9, ID 0-15, LUN 0 - unsigned adapter = ~0, id = ~0; int n1 = -1; - const char * name = skipdev(get_dev_name()); - if (!(sscanf(name,"scsi%1u%1x%n", &adapter, &id, &n1) == 2 && n1 == (int)strlen(name) - && adapter <= 9 && id < 16)) - return set_err(EINVAL); + // 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(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); - if (!aspi_entry_valid()) { - if (aspi_open_dll(1/*verbose*/)) - return set_err(ENOENT); - } + // Check status + unsigned status = pthru->CplEntry[3] >> 17; + if (status) + return set_nvme_err(out, status); - // Adapter OK? - if (adapter >= num_aspi_adapters) { - pout("ASPI Adapter %u does not exist (%u Adapter%s detected).\n", - adapter, num_aspi_adapters, (num_aspi_adapters!=1?"s":"")); - if (!is_permissive()) - return set_err(ENOENT); - } + if (!ok) + return set_err(EIO, "NVME_PASS_THROUGH failed, Error=%u", (unsigned)GetLastError()); - // Device present ? - ASPI_SRB srb; - memset(&srb, 0, sizeof(srb)); - srb.h.cmd = ASPI_CMD_GET_DEVICE_TYPE; - srb.h.adapter = adapter; srb.i.target_id = id; - if (aspi_call(&srb)) - return set_err(EIO); - if (srb.h.status != ASPI_STATUS_NO_ERROR) { - pout("ASPI Adapter %u, ID %u: No such device (Status=0x%02x)\n", adapter, id, srb.h.status); - if (!is_permissive()) - return set_err(srb.h.status == ASPI_STATUS_INVALID_TARGET ? ENOENT : EIO); - } - else if (scsi_debugmode) - pout("ASPI Adapter %u, ID %u: Device Type=0x%02x\n", adapter, id, srb.t.devtype); + if (in.direction() & nvme_cmd_in::data_in) + memcpy(in.buffer, pthru->DataBuffer, in.size); - m_adapter = (int)adapter; m_id = (unsigned char)id; + out.result = pthru->CplEntry[0]; return true; } -bool win_aspi_device::close() -{ - // No FreeLibrary(h_aspi_dll) to prevent problems with ASPI threads - 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); -// Scan for ASPI drives + virtual bool open(); -bool win9x_smart_interface::scsi_scan(smart_device_list & devlist) -{ - if (!aspi_entry_valid()) { - if (aspi_open_dll(scsi_debugmode/*default is quiet*/)) - return true; - } + virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); - for (unsigned ad = 0; ad < num_aspi_adapters; ad++) { - ASPI_SRB srb; +private: + bool open(int phydrive); +}; - if (ad > 9) { - if (scsi_debugmode) - pout(" ASPI Adapter %u: Ignored\n", ad); - continue; - } - // Get adapter name - memset(&srb, 0, sizeof(srb)); - srb.h.cmd = ASPI_CMD_ADAPTER_INQUIRE; - srb.h.adapter = ad; - if (aspi_call(&srb)) - break; +///////////////////////////////////////////////////////////////////////////// - if (srb.h.status != ASPI_STATUS_NO_ERROR) { - if (scsi_debugmode) - pout(" ASPI Adapter %u: Status=0x%02x\n", ad, srb.h.status); - continue; - } +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) +{ +} - if (scsi_debugmode) { - for (int i = 1; i < 16 && srb.q.adapter_id[i]; i++) - if (!(' ' <= srb.q.adapter_id[i] && srb.q.adapter_id[i] <= '~')) - srb.q.adapter_id[i] = '?'; - pout(" ASPI Adapter %u (\"%.16s\"):\n", ad, srb.q.adapter_id); - } +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)); - bool ignore = !strnicmp(srb.q.adapter_id, "3ware", 5); - - for (unsigned id = 0; id <= 7; id++) { - // Get device type - memset(&srb, 0, sizeof(srb)); - srb.h.cmd = ASPI_CMD_GET_DEVICE_TYPE; - srb.h.adapter = ad; srb.i.target_id = id; - if (aspi_call(&srb)) - return 0; - if (srb.h.status != ASPI_STATUS_NO_ERROR) { - if (scsi_debugmode > 1) - pout(" ID %u: No such device (Status=0x%02x)\n", id, srb.h.status); - continue; - } + // 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); - if (!ignore && srb.t.devtype == 0x00/*HDD*/) { - if (scsi_debugmode) - pout(" ID %u: Device Type=0x%02x\n", id, srb.t.devtype); - char name[20]; - sprintf(name, "/dev/scsi%u%u", ad, id); - devlist.push_back( new win_aspi_device(this, name, "scsi") ); - } - else if (scsi_debugmode) - pout(" ID %u: Device Type=0x%02x (ignored)\n", id, srb.t.devtype); - } - } - return true; + return set_err(EINVAL); } - -// Interface to ASPI SCSI devices -bool win_aspi_device::scsi_pass_through(scsi_cmnd_io * iop) +bool win10_nvme_device::open(int phydrive) { - int report = scsi_debugmode; // TODO - - if (m_adapter < 0) { - set_err(EBADF); - return false; - } + // TODO: Use common open function for all devices using "\\.\PhysicalDriveN" + char devpath[64]; + snprintf(devpath, sizeof(devpath) - 1, "\\\\.\\PhysicalDrive%d", phydrive); - if (!aspi_entry_valid()) { - set_err(EBADF); - return false; - } + // 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 (!(iop->cmnd_len == 6 || iop->cmnd_len == 10 || iop->cmnd_len == 12 || iop->cmnd_len == 16)) { - set_err(EINVAL, "bad CDB length"); + 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 (report > 0) { - // From os_linux.c - int k, j; - const unsigned char * ucp = iop->cmnd; - const char * np; - char buff[256]; - const int sz = (int)sizeof(buff); + if (nvme_debugmode > 1) + pout(" %s: successfully opened\n", devpath); - np = scsi_get_opcode_name(ucp[0]); - j = snprintf(buff, sz, " [%s: ", np ? np : ""); - 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; + set_fh(h); - 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); - } + // Use broadcast namespace if no NSID specified + // TODO: Get NSID of current device + if (!get_nsid()) + set_nsid(0xffffffff); + return true; +} - ASPI_SRB srb; - memset(&srb, 0, sizeof(srb)); - srb.h.cmd = ASPI_CMD_EXECUTE_IO; - srb.h.adapter = m_adapter; - srb.i.target_id = m_id; - //srb.i.lun = 0; - srb.i.sense_size = ASPI_SENSE_SIZE; - srb.i.cdb_size = iop->cmnd_len; - memcpy(srb.i.cdb, iop->cmnd, iop->cmnd_len); +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]; +}; - switch (iop->dxfer_dir) { - case DXFER_NONE: - srb.h.flags = ASPI_REQFLAG_DIR_NO_XFER; - break; - case DXFER_FROM_DEVICE: - srb.h.flags = ASPI_REQFLAG_DIR_TO_HOST; - srb.i.data_size = iop->dxfer_len; - srb.i.data_addr = iop->dxferp; +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(spsq_raw_buf.data()); + + // Set NVMe specifc 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 DXFER_TO_DEVICE: - srb.h.flags = ASPI_REQFLAG_DIR_TO_TARGET; - srb.i.data_size = iop->dxfer_len; - srb.i.data_addr = iop->dxferp; + 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: - set_err(EINVAL, "bad dxfer_dir"); - return false; + return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode); } - iop->resp_sense_len = 0; - iop->scsi_status = 0; - iop->resid = 0; - - if (aspi_io_call(&srb, (iop->timeout ? iop->timeout : 60))) { - // Timeout - set_err(EIO, "ASPI Timeout"); return false; - } - - if (srb.h.status != ASPI_STATUS_NO_ERROR) { - if ( srb.h.status == ASPI_STATUS_ERROR - && srb.i.host_status == ASPI_HSTATUS_NO_ERROR - && srb.i.target_status == ASPI_TSTATUS_CHECK_CONDITION) { - // Sense valid - const unsigned char * sense = ASPI_SRB_SENSE(&srb.i, iop->cmnd_len); - int len = (ASPI_SENSE_SIZE < iop->max_sense_len ? ASPI_SENSE_SIZE : iop->max_sense_len); - iop->scsi_status = SCSI_STATUS_CHECK_CONDITION; - if (len > 0 && iop->sensep) { - memcpy(iop->sensep, sense, len); - iop->resp_sense_len = len; - if (report > 1) { - pout(" >>> Sense buffer, len=%d:\n", (int)len); - dStrHex(iop->sensep, len , 1); - } - } - if (report) { - pout(" sense_key=%x asc=%x ascq=%x\n", - sense[2] & 0xf, sense[12], sense[13]); - } - return true; - } - else { - if (report) - pout(" ASPI call failed, (0x%02x,0x%02x,0x%02x)\n", srb.h.status, srb.i.host_status, srb.i.target_status); - set_err(EIO); - return false; - } - } + spsq->ProtocolSpecific.ProtocolDataRequestSubValue = in.nsid; // ? + spsq->ProtocolSpecific.ProtocolDataOffset = sizeof(spsq->ProtocolSpecific); + spsq->ProtocolSpecific.ProtocolDataLength = in.size; - if (report > 0) - pout(" OK\n"); + if (in.direction() & nvme_cmd_in::data_out) + memcpy(spsq->DataBuffer, in.buffer, in.size); - if (iop->dxfer_dir == DXFER_FROM_DEVICE && report > 1) { - int trunc = (iop->dxfer_len > 256) ? 1 : 0; - pout(" Incoming data, len=%d%s:\n", (int)iop->dxfer_len, - (trunc ? " [only first 256 bytes shown]" : "")); - dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + 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; } -#endif // WIN9X_SUPPORT ///////////////////////////////////////////////////////////////////////////// -// SPT Interface (for SCSI devices and ATA devices behind SATLs) -// Only supported in NT and later -///////////////////////////////////////////////////////////////////////////// +// win_smart_interface +// Platform specific interface -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) +class win_smart_interface +: public /*implements*/ smart_interface { -} +public: + virtual std::string get_os_version_str(); -bool win_scsi_device::open() -{ - const char * name = skipdev(get_dev_name()); int len = strlen(name); - // sd[a-z],N => Physical drive 0-26, RAID port N - char drive[1+1] = ""; int sub_addr = -1; int n1 = -1; int n2 = -1; - if ( sscanf(name, "sd%1[a-z]%n,%d%n", drive, &n1, &sub_addr, &n2) >= 1 - && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0)) ) { - return open(drive[0] - 'a', -1, -1, sub_addr); - } - // pd,N => Physical drive , 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 => tape drive (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 => tape drive - 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); - } + virtual std::string get_app_examples(const char * appname); + +#ifndef __CYGWIN__ + virtual int64_t get_timer_usec(); +#endif - return set_err(EINVAL); -} + virtual bool disable_system_auto_standby(bool disable); -bool win_scsi_device::open(int pd_num, int ld_num, int tape_num, int /*sub_addr*/) + 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: + ata_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() { - 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); + BOOL (WINAPI * IsWow64Process_p)(HANDLE, PBOOL) = + (BOOL (WINAPI *)(HANDLE, PBOOL)) + GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process"); + if (!IsWow64Process_p) 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=%ld", b, GetLastError()); + BOOL w64 = FALSE; + if (!IsWow64Process_p(GetCurrentProcess(), &w64)) return false; - } - set_fh(h); - return true; + 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)); -typedef struct { - SCSI_PASS_THROUGH_DIRECT spt; - ULONG Filler; - UCHAR ucSenseBuf[64]; -} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + // Starting with Windows 8.1, GetVersionEx() does no longer report the + // actual OS version, see: + // http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx + // RtlGetVersion() is not affected + LONG /*NTSTATUS*/ (WINAPI /*NTAPI*/ * RtlGetVersion_p)(LPOSVERSIONINFOEXW) = + (LONG (WINAPI *)(LPOSVERSIONINFOEXW)) + GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); -// 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]; - }; + OSVERSIONINFOEXW vi; memset(&vi, 0, sizeof(vi)); + vi.dwOSVersionInfoSize = sizeof(vi); + if (!RtlGetVersion_p || RtlGetVersion_p(&vi)) { + if (!GetVersionExW((OSVERSIONINFOW *)&vi)) + return vstr; + } - SCSI_PASS_THROUGH_WITH_BUFFERS sb; - memset(&sb, 0, sizeof(sb)); + 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; + default: w = "w10"; build = vi.dwBuildNumber; break; + } break; + case 0xa0<<1 | 1: + switch (vi.dwBuildNumber) { + case 14393: w = "2016-1607"; break; + default: w = "2016"; build = vi.dwBuildNumber; break; + } break; + } + } - // DATA_OUT not implemented yet - if (!( sbd->spt.DataIn == SCSI_IOCTL_DATA_IN - && sbd->spt.DataTransferLength <= sizeof(sb.ucDataBuf))) - return ERROR_INVALID_PARAMETER; + const char * w64 = ""; +#ifndef _WIN64 + if (is_wow64()) + w64 = "(64)"; +#endif - 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; + 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%s-b%u", w, w64, build); + else if (vi.wServicePackMinor) + snprintf(vptr, vlen, "-%s%s-sp%u.%u", w, w64, vi.wServicePackMajor, vi.wServicePackMinor); + else if (vi.wServicePackMajor) + snprintf(vptr, vlen, "-%s%s-sp%u", w, w64, vi.wServicePackMajor); + else + snprintf(vptr, vlen, "-%s%s", w, w64); + return vstr; +} - DWORD num_out; - if (!DeviceIoControl(h, IOCTL_SCSI_PASS_THROUGH, - &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0)) - return GetLastError(); +#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; - sbd->spt.ScsiStatus = sb.spt.ScsiStatus; - if (sb.spt.ScsiStatus & SCSI_STATUS_CHECK_CONDITION) - memcpy(sbd->ucSenseBuf, sb.ucSenseBuf, sizeof(sbd->ucSenseBuf)); + LARGE_INTEGER t; + if (freq == 0) + freq = (QueryPerformanceFrequency(&t) ? t.QuadPart : -1); + if (freq <= 0) + return smart_interface::get_timer_usec(); - 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; + 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__ -// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c -bool win_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop) +ata_device * win_smart_interface::get_ata_device(const char * name, const char * type) { - 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); + 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); +} - np = scsi_get_opcode_name(ucp[0]); - j = snprintf(buff, sz, " [%s: ", np ? np : ""); - 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; +scsi_device * win_smart_interface::get_scsi_device(const char * name, const char * type) +{ + return new win_scsi_device(this, name, type); +} - 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); - } +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); +} - 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); +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]; - 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; - } + 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; + } - 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(); + 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; } - 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); + // aacraid? + unsigned ctrnum, lun, target; + n1 = -1; - iop->scsi_status = sb.spt.ScsiStatus; - if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) { - int slen = sb.ucSenseBuf[7] + 8; + 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; + } - 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); + /* + 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); } - 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 > 0) && (sb.spt.DataTransferLength > 0)) - 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%s:\n", (int)iop->dxfer_len, - (trunc ? " [only first 256 bytes shown]" : "")); - dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + 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; } -// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c -static long scsi_pass_through_direct(HANDLE fd, struct scsi_cmnd_io * iop) +// Return true if Intel ICHxR RAID volume +static bool is_intel_raid_volume(const STORAGE_DEVICE_DESCRIPTOR_DATA * data) { - int report = scsi_debugmode; // TODO + 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; +} - if (report > 0) { - int k, j; - const unsigned char * ucp = iop->cmnd; - const char * np; - char buff[256]; - const int sz = (int)sizeof(buff); +// 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; - np = scsi_get_opcode_name(ucp[0]); - j = snprintf(buff, sz, " [%s: ", np ? np : ""); - 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; + // 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; - 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); - } + if (ata_version_ex) + memset(ata_version_ex, 0, sizeof(*ata_version_ex)); + return DEV_ATA; - SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb; - if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) { - return EINVAL; - } + case BusTypeScsi: + case BusTypeRAID: + if (is_sat(&data)) + return DEV_SAT; - memset(&sb, 0, sizeof(sb)); - sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); - //sb.spt.PathId = 0; - sb.spt.TargetId = 127; - //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); + // 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; - 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; + return DEV_UNKNOWN; } + /*NOTREACHED*/ +} - 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(); +// 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; } - else - err = scsi_pass_through_indirect(fd, &sb); + 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; +} - if (err) - { - return err; - } +// 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); +} - iop->scsi_status = sb.spt.ScsiStatus; - if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) { - int slen = sb.ucSenseBuf[7] + 8; +static win_dev_type get_phy_drive_type(int drive) +{ + return get_phy_drive_type(drive, 0); +} - 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; +// 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); +} - if ((iop->dxfer_len > 0) && (sb.spt.DataTransferLength > 0)) - iop->resid = iop->dxfer_len - sb.spt.DataTransferLength; - else - iop->resid = 0; +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); + } - if ((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)) { - int trunc = (iop->dxfer_len > 256) ? 1 : 0; - pout(" Incoming data, len=%d%s:\n", (int)iop->dxfer_len, - (trunc ? " [only first 256 bytes shown]" : "")); - dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + char drive[2+1] = ""; + if (sscanf(name, "sd%2[a-z]", drive) == 1) { + phydrive = sdxy_to_phydrive(drive); + return get_phy_drive_type(phydrive); } - return 0; -} + if (sscanf(name, "pd%d", &phydrive) == 1 && phydrive >= 0) + return get_phy_drive_type(phydrive); + return DEV_UNKNOWN; +} -#if 0 // For debugging areca code -static void dumpdata(unsigned char *block, int len) +ata_device * win_smart_interface::get_usb_device(const char * name, + int phydrive, int logdrive /* = -1 */) { - int ln = (len / 16) + 1; // total line# - unsigned char c; - int pos = 0; - - printf(" Address = %p, Length = (0x%x)%d\n", block, len, len); - printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F ASCII \n"); - printf("=====================================================================\n"); - - for ( int l = 0; l < ln && len; l++ ) - { - // printf the line# and the HEX data - // if a line data length < 16 then append the space to the tail of line to reach 16 chars - printf("%02X | ", l); - for ( pos = 0; pos < 16 && len; pos++, len-- ) - { - c = block[l*16+pos]; - printf("%02X ", c); - } + // 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; + } - if ( pos < 16 ) - { - for ( int loop = pos; loop < 16; loop++ ) - { - printf(" "); - } - } + // Get type name for this ID + const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id); + if (!usbtype) + return 0; - // print ASCII char - for ( int loop = 0; loop < pos; loop++ ) - { - c = block[l*16+loop]; - if ( c >= 0x20 && c <= 0x7F ) - { - printf("%c", c); - } - else - { - printf("."); - } - } - printf("\n"); - } - printf("=====================================================================\n"); + // Return SAT/USB device for this type + return get_sat_device(usbtype, new win_scsi_device(this, name, "")); } -#endif - -// PURPOSE -// This is an interface routine meant to isolate the OS dependent -// parts of the code, and to provide a debugging interface. Each -// different port and OS needs to provide it's own interface. This -// is the Windows interface to the Areca "arcmsr" driver. It allows ATA -// commands to be passed through the SCSI driver. -// DETAILED DESCRIPTION OF ARGUMENTS -// fd: is the file descriptor provided by open() -// disknum is the disk number (0 to 127) in the RAID array -// command: defines the different operations. -// select: additional input data if needed (which log, which type of -// self-test). -// data: location to write output data, if needed (512 bytes). -// Note: not all commands use all arguments. -// RETURN VALUES -// -1 if the command failed -// 0 if the command succeeded, -// STATUS_CHECK routine: -// -1 if the command failed -// 0 if the command succeeded and disk SMART status is "OK" -// 1 if the command succeeded and disk SMART status is "FAILING" -int win_areca_device::arcmsr_command_handler(HANDLE fd, unsigned long arcmsr_cmd, unsigned char *data, int data_len) +smart_device * win_smart_interface::autodetect_smart_device(const char * name) { - int ioctlreturn = 0; - sSRB_BUFFER sBuf; - struct scsi_cmnd_io io_hdr; - int dir = DXFER_TO_DEVICE; - - UINT8 cdb[10]; - UINT8 sense[32]; - - unsigned char *areca_return_packet; - int total = 0; - int expected = -1; - unsigned char return_buff[2048]; - unsigned char *ptr = &return_buff[0]; - memset(return_buff, 0, sizeof(return_buff)); + const char * testname = skipdev(name); + if (str_starts_with(testname, "hd")) + return new win_ata_device(this, name, ""); - memset((unsigned char *)&sBuf, 0, sizeof(sBuf)); - memset(&io_hdr, 0, sizeof(io_hdr)); - memset(cdb, 0, sizeof(cdb)); - memset(sense, 0, sizeof(sense)); + 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, ""); - sBuf.srbioctl.HeaderLength = sizeof(sSRB_IO_CONTROL); - memcpy(sBuf.srbioctl.Signature, ARECA_SIG_STR, strlen(ARECA_SIG_STR)); - sBuf.srbioctl.Timeout = 10000; - sBuf.srbioctl.ControlCode = arcmsr_cmd; + if (str_starts_with(testname, "nvme")) + return new win_nvme_device(this, name, "", 0 /* use default nsid */); - switch ( arcmsr_cmd ) - { - // command for writing data to driver - case ARCMSR_IOCTL_WRITE_WQBUFFER: - if ( data && data_len ) - { - sBuf.srbioctl.Length = data_len; - memcpy((unsigned char *)sBuf.ioctldatabuffer, (unsigned char *)data, data_len); - } - // commands for clearing related buffer of driver - case ARCMSR_IOCTL_CLEAR_RQBUFFER: - case ARCMSR_IOCTL_CLEAR_WQBUFFER: - cdb[0] = 0x3B; //SCSI_WRITE_BUF command; - break; - // command for reading data from driver - case ARCMSR_IOCTL_READ_RQBUFFER: - // command for identifying driver - case ARCMSR_IOCTL_RETURN_CODE_3F: - cdb[0] = 0x3C; //SCSI_READ_BUF command; - dir = DXFER_FROM_DEVICE; - break; - default: - // unknown arcmsr commands - return -1; - } + int phydrive = -1, logdrive = -1; + win_dev_type type = get_dev_type(name, phydrive, logdrive); - cdb[1] = 0x01; - cdb[2] = 0xf0; + if (type == DEV_ATA) + return new win_ata_device(this, name, ""); - io_hdr.dxfer_dir = dir; - io_hdr.dxfer_len = sizeof(sBuf); - io_hdr.dxferp = (unsigned char *)&sBuf; - io_hdr.cmnd = cdb; - io_hdr.cmnd_len = sizeof(cdb); - io_hdr.sensep = sense; - io_hdr.max_sense_len = sizeof(sense); - io_hdr.timeout = SCSI_TIMEOUT_DEFAULT; + if (type == DEV_SCSI) + return new win_scsi_device(this, name, ""); - while ( 1 ) - { - ioctlreturn = scsi_pass_through_direct(fd, &io_hdr); - if ( ioctlreturn || io_hdr.scsi_status ) - { - // errors found - break; - } + if (type == DEV_SAT) + return get_sat_device("sat", new win_scsi_device(this, name, "")); - if ( arcmsr_cmd != ARCMSR_IOCTL_READ_RQBUFFER ) - { - // if succeeded, just returns the length of outgoing data - return data_len; - } + if (type == DEV_USB) + return get_usb_device(name, phydrive, logdrive); - if ( sBuf.srbioctl.Length ) - { - //dumpdata(&sBuf.ioctldatabuffer[0], sBuf.srbioctl.Length); - memcpy(ptr, &sBuf.ioctldatabuffer[0], sBuf.srbioctl.Length); - ptr += sBuf.srbioctl.Length; - total += sBuf.srbioctl.Length; - // the returned bytes enough to compute payload length ? - if ( expected < 0 && total >= 5 ) - { - areca_return_packet = (unsigned char *)&return_buff[0]; - if ( areca_return_packet[0] == 0x5E && - areca_return_packet[1] == 0x01 && - areca_return_packet[2] == 0x61 ) - { - // valid header, let's compute the returned payload length, - // we expected the total length is - // payload + 3 bytes header + 2 bytes length + 1 byte checksum - expected = areca_return_packet[4] * 256 + areca_return_packet[3] + 6; - } - } + if (type == DEV_NVME) + return new win10_nvme_device(this, name, "", 0 /* use default nsid */); - if ( total >= 7 && total >= expected ) - { - //printf("total bytes received = %d, expected length = %d\n", total, expected); + return 0; +} - // ------ Okay! we received enough -------- - break; - } - } - } - // Deal with the different error cases - if ( arcmsr_cmd == ARCMSR_IOCTL_RETURN_CODE_3F ) - { - // Silence the ARCMSR_IOCTL_RETURN_CODE_3F's error, no pout(...) - return -4; +// 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; } - if ( ioctlreturn ) - { - pout("do_scsi_cmnd_io with write buffer failed code = %x\n", ioctlreturn); - return -2; + // 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; + } } - if ( io_hdr.scsi_status ) - { - pout("io_hdr.scsi_status with write buffer failed code = %x\n", io_hdr.scsi_status); - return -3; + // 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 } - - if ( data ) - { - memcpy(data, return_buff, total); + 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; + } } - return total; -} - - -win_areca_device::win_areca_device(smart_interface * intf, const char * dev_name, HANDLE fh, int disknum, int encnum) -: smart_device(intf, dev_name, "areca", "areca"), - m_disknum(disknum), - m_encnum(encnum) -{ - set_fh(fh); - set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum); -} + char name[20]; -bool win_areca_device::open() -{ - HANDLE hFh; + 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}; - if( is_open() ) - { - return true; - } + 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'); - 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; - } + smart_device * dev = 0; + GETVERSIONINPARAMS_EX vers_ex; - set_fh(hFh); - return true; -} + 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; -// Areca RAID Controller -bool win_areca_device::arcmsr_ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) -{ - // ATA input registers - typedef struct _ATA_INPUT_REGISTERS - { - unsigned char features; - unsigned char sector_count; - unsigned char sector_number; - unsigned char cylinder_low; - unsigned char cylinder_high; - unsigned char device_head; - unsigned char command; - unsigned char reserved[8]; - unsigned char data[512]; // [in/out] buffer for outgoing/incoming data - } sATA_INPUT_REGISTERS; - - // ATA output registers - // Note: The output registers is re-sorted for areca internal use only - typedef struct _ATA_OUTPUT_REGISTERS - { - unsigned char error; - unsigned char status; - unsigned char sector_count; - unsigned char sector_number; - unsigned char cylinder_low; - unsigned char cylinder_high; - } sATA_OUTPUT_REGISTERS; - - // Areca packet format for outgoing: - // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 - // B[3~4] : 2 bytes command length + variant data length, little endian - // B[5] : 1 bytes areca defined command code, ATA passthrough command code is 0x1c - // B[6~last-1] : variant bytes payload data - // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) - // - // - // header 3 bytes length 2 bytes cmd 1 byte payload data x bytes cs 1 byte - // +--------------------------------------------------------------------------------+ - // + 0x5E 0x01 0x61 | 0x00 0x00 | 0x1c | .................... | 0x00 | - // +--------------------------------------------------------------------------------+ - // - - //Areca packet format for incoming: - // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 - // B[3~4] : 2 bytes payload length, little endian - // B[5~last-1] : variant bytes returned payload data - // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) - // - // - // header 3 bytes length 2 bytes payload data x bytes cs 1 byte - // +-------------------------------------------------------------------+ - // + 0x5E 0x01 0x61 | 0x00 0x00 | .................... | 0x00 | - // +-------------------------------------------------------------------+ - unsigned char areca_packet[640]; - int areca_packet_len = sizeof(areca_packet); - unsigned char cs = 0; - - sATA_INPUT_REGISTERS *ata_cmd; - - // For debugging -#if 0 - memset(sInq, 0, sizeof(sInq)); - scsiStdInquiry(fd, (unsigned char *)sInq, (int)sizeof(sInq)); - dumpdata((unsigned char *)sInq, sizeof(sInq)); -#endif - memset(areca_packet, 0, areca_packet_len); + // 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 & (1L << pi)) { + snprintf(name+len, sizeof(name)-1-len, ",%u", pi); + devlist.push_back( new win_ata_device(this, name, "ata") ); + } + } + continue; + } - // ----- BEGIN TO SETUP HEADERS ------- - areca_packet[0] = 0x5E; - areca_packet[1] = 0x01; - areca_packet[2] = 0x61; - areca_packet[3] = (unsigned char)((areca_packet_len - 6) & 0xff); - areca_packet[4] = (unsigned char)(((areca_packet_len - 6) >> 8) & 0xff); - areca_packet[5] = 0x1c; // areca defined code for ATA passthrough command + dev = new win_ata_device(this, name, "ata"); + break; - // ----- BEGIN TO SETUP PAYLOAD DATA ----- - memcpy(&areca_packet[7], "SmrT", 4); // areca defined password - ata_cmd = (sATA_INPUT_REGISTERS *)&areca_packet[12]; + case DEV_SCSI: + // STORAGE_QUERY_PROPERTY returned SCSI/SAS/... + if (!scsi) + continue; + dev = new win_scsi_device(this, name, "scsi"); + break; - // Set registers - { - const ata_in_regs & r = in.in_regs; - ata_cmd->features = r.features; - ata_cmd->sector_count = r.sector_count; - ata_cmd->sector_number = r.lba_low; - ata_cmd->cylinder_low = r.lba_mid; - ata_cmd->cylinder_high = r.lba_high; - ata_cmd->device_head = r.device; - ata_cmd->command = r.command; - } - bool readdata = false; - if (in.direction == ata_cmd_in::data_in) { - readdata = true; - // the command will read data - areca_packet[6] = 0x13; - } - else if ( in.direction == ata_cmd_in::no_data ) - { - // the commands will return no data - areca_packet[6] = 0x15; - } - else if (in.direction == ata_cmd_in::data_out) - { - // the commands will write data - memcpy(ata_cmd->data, in.buffer, in.size); - areca_packet[6] = 0x14; - } - else { - // COMMAND NOT SUPPORTED VIA ARECA IOCTL INTERFACE - return set_err(ENOSYS); - } + case DEV_SAT: + // STORAGE_QUERY_PROPERTY returned VendorId "ATA " + if (!sat) + continue; + dev = get_sat_device("sat", new win_scsi_device(this, name, "")); + break; - areca_packet[11] = m_disknum - 1; // disk# - areca_packet[19] = m_encnum - 1; // enc# + 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; - // ----- BEGIN TO SETUP CHECKSUM ----- - for ( int loop = 3; loop < areca_packet_len - 1; loop++ ) - { - cs += areca_packet[loop]; - } - areca_packet[areca_packet_len-1] = cs; + case DEV_NVME: + // STORAGE_QUERY_PROPERTY returned NVMe + if (!nvme) + continue; + dev = new win10_nvme_device(this, name, "", 0 /* use default nsid */); + break; - // ----- BEGIN TO SEND TO ARECA DRIVER ------ - int expected = 0; - unsigned char return_buff[2048]; - memset(return_buff, 0, sizeof(return_buff)); + default: + // Unknown type + continue; + } - expected = arcmsr_command_handler(get_fh(), ARCMSR_IOCTL_CLEAR_RQBUFFER, NULL, 0); - if (expected==-3) { - return set_err(EIO); + devlist.push_back(dev); + } } - expected = arcmsr_command_handler(get_fh(), ARCMSR_IOCTL_CLEAR_WQBUFFER, NULL, 0); - expected = arcmsr_command_handler(get_fh(), ARCMSR_IOCTL_WRITE_WQBUFFER, areca_packet, areca_packet_len); - if ( expected > 0 ) - { - expected = arcmsr_command_handler(get_fh(), ARCMSR_IOCTL_READ_RQBUFFER, return_buff, sizeof(return_buff)); - } - if ( expected < 0 ) - { - return set_err(EIO); - } + 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; - // ----- VERIFY THE CHECKSUM ----- - cs = 0; - for ( int loop = 3; loop < expected - 1; loop++ ) - { - cs += return_buff[loop]; - } + unsigned ports_used = test_dev.get_ports_used(); + if (!ports_used) + continue; - if ( return_buff[expected - 1] != cs ) - { - return set_err(EIO); + for (int pi = 0; pi < 32; pi++) { + if (!(ports_used & (1 << pi))) + continue; + snprintf(name, sizeof(name)-1, "/dev/csmi%d,%d", i, pi); + devlist.push_back( new win_csmi_device(this, name, "ata") ); + } + } } - sATA_OUTPUT_REGISTERS *ata_out = (sATA_OUTPUT_REGISTERS *)&return_buff[5] ; - if ( ata_out->status ) - { - if ( in.in_regs.command == ATA_IDENTIFY_DEVICE - && !nonempty((unsigned char *)in.buffer, in.size)) - { - return set_err(ENODEV, "No drive on port %d", m_disknum); - } - } + 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; + } - // returns with data - if (readdata) - { - memcpy(in.buffer, &return_buff[7], in.size); - } + if (!test_dev.probe()) + continue; + if (++nvme_cnt >= 10) + break; + } - // Return register values - { - ata_out_regs & r = out.out_regs; - r.error = ata_out->error; - r.sector_count = ata_out->sector_count; - r.lba_low = ata_out->sector_number; - r.lba_mid = ata_out->cylinder_low; - r.lba_high = ata_out->cylinder_high; - r.status = ata_out->status; + 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; } -bool win_areca_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +// get examples for smartctl +std::string win_smart_interface::get_app_examples(const char * appname) { -#define SYNCOBJNAME "Global\\SynIoctlMutex" - int ctlrnum = -1; - char mutexstr[64]; - SECURITY_ATTRIBUTES sa; - PSECURITY_DESCRIPTOR pSD; - HANDLE hmutex; - - if (!ata_cmd_is_ok(in, - true, // data_out_support - false, // TODO: multi_sector_support - true) // ata_48bit_support - ) - return false; - - // Support 48-bit commands with zero high bytes - if (in.in_regs.is_real_48bit_cmd()) - return set_err(ENOSYS, "48-bit ATA commands not fully supported by Areca"); - - if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1) - return set_err(EINVAL, "unable to parse device name"); - - memset(mutexstr, 0, sizeof(mutexstr)); - sprintf(mutexstr, "%s%d",SYNCOBJNAME, ctlrnum); - pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); - if ( !InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) ) - { - LocalFree((HLOCAL)pSD); - return set_err(EIO, "InitializeSecurityDescriptor failed"); - } - - if ( !SetSecurityDescriptorDacl(pSD, TRUE, (PACL)NULL, FALSE) ) - { - LocalFree((HLOCAL)pSD); - return set_err(EIO, "SetSecurityDescriptor failed"); - } - - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.lpSecurityDescriptor = pSD; - sa.bInheritHandle = TRUE; - hmutex = CreateMutex(&sa, FALSE, mutexstr); - if ( hmutex == NULL ) - { - LocalFree((HLOCAL)pSD); - return set_err(EIO, "CreateMutex failed"); - } - - // atomic access to driver - WaitForSingleObject(hmutex, INFINITE); - bool ok = arcmsr_ata_pass_through(in,out); - ReleaseMutex(hmutex); + 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() + ); +} - if(hmutex) - { - CloseHandle(hmutex); - } - if ( (HLOCAL)pSD ) - { - LocalFree((HLOCAL)pSD); +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; + } } - return ok; + if (!SetThreadExecutionState(ES_CONTINUOUS | (disable ? ES_SYSTEM_REQUIRED : 0))) + return set_err(ENOSYS); + return true; } -////////////////////////////////////////////////////////////////////////////////////////////////// - - } // namespace ///////////////////////////////////////////////////////////////////////////// @@ -4930,19 +4729,8 @@ void smart_interface::init() SetDllDirectoryA_p(""); } - // Select interface for Windows flavor - if (GetVersion() & 0x80000000) { -#if WIN9X_SUPPORT - static os_win32::win9x_smart_interface the_win9x_interface; - smart_interface::set(&the_win9x_interface); -#else - throw std::runtime_error("Win9x/ME not supported"); -#endif - } - else { - static os_win32::winnt_smart_interface the_winnt_interface; - smart_interface::set(&the_winnt_interface); - } + static os_win32::win_smart_interface the_win_interface; + smart_interface::set(&the_win_interface); }