mirror of
https://git.proxmox.com/git/mirror_smartmontools-debian
synced 2025-07-14 23:10:05 +00:00
4972 lines
147 KiB
C++
4972 lines
147 KiB
C++
/*
|
|
* os_win32.cpp
|
|
*
|
|
* Home page of code is: http://smartmontools.sourceforge.net
|
|
*
|
|
* Copyright (C) 2004-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
|
|
* Copyright (C) 2012 Hank Wu <hank@areca.com.tw>
|
|
*
|
|
* 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
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#define WINVER 0x0502
|
|
#define _WIN32_WINNT WINVER
|
|
|
|
#include "int64.h"
|
|
#include "atacmds.h"
|
|
#include "scsicmds.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 "os_win32/wmiquery.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#ifdef _DEBUG
|
|
#include <assert.h>
|
|
#else
|
|
#undef assert
|
|
#define assert(x) /* */
|
|
#endif
|
|
|
|
#include <stddef.h> // offsetof()
|
|
#include <io.h> // access()
|
|
|
|
// WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h>
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
#if HAVE_NTDDDISK_H
|
|
// i686-w64-mingw32, x86_64-w64-mingw32
|
|
// (Missing: FILE_DEVICE_SCSI)
|
|
#include <devioctl.h>
|
|
#include <ntdddisk.h>
|
|
#include <ntddscsi.h>
|
|
#include <ntddstor.h>
|
|
#elif HAVE_DDK_NTDDDISK_H
|
|
// i686-pc-cygwin, i686-pc-mingw32, i586-mingw32msvc
|
|
// (Missing: IOCTL_IDE_PASS_THROUGH, IOCTL_ATA_PASS_THROUGH, FILE_DEVICE_SCSI)
|
|
#include <ddk/ntdddisk.h>
|
|
#include <ddk/ntddscsi.h>
|
|
#include <ddk/ntddstor.h>
|
|
#else
|
|
// MSVC10, older MinGW
|
|
// (Missing: IOCTL_SCSI_MINIPORT_*)
|
|
#include <ntddscsi.h>
|
|
#include <winioctl.h>
|
|
#endif
|
|
|
|
// CSMI support
|
|
#include "csmisas.h"
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <cygwin/version.h> // CYGWIN_VERSION_DLL_MAJOR
|
|
#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]
|
|
#define ASSERT_SIZEOF(t, n) \
|
|
typedef char assert_sizeof_##t[(sizeof(t) == (n)) ? 1 : -1]
|
|
|
|
#ifndef _WIN64
|
|
#define SELECT_WIN_32_64(x32, x64) (x32)
|
|
#else
|
|
#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
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Windows I/O-controls, some declarations are missing in the include files
|
|
|
|
extern "C" {
|
|
|
|
// SMART_* IOCTLs, also known as DFP_* (Disk Fault Protection)
|
|
|
|
ASSERT_CONST(SMART_GET_VERSION, 0x074080);
|
|
ASSERT_CONST(SMART_SEND_DRIVE_COMMAND, 0x07c084);
|
|
ASSERT_CONST(SMART_RCV_DRIVE_DATA, 0x07c088);
|
|
ASSERT_SIZEOF(GETVERSIONINPARAMS, 24);
|
|
ASSERT_SIZEOF(SENDCMDINPARAMS, 32+1);
|
|
ASSERT_SIZEOF(SENDCMDOUTPARAMS, 16+1);
|
|
|
|
|
|
// IDE PASS THROUGH (2000, XP, undocumented)
|
|
|
|
#ifndef IOCTL_IDE_PASS_THROUGH
|
|
|
|
#define IOCTL_IDE_PASS_THROUGH \
|
|
CTL_CODE(IOCTL_SCSI_BASE, 0x040A, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
|
|
|
#endif // IOCTL_IDE_PASS_THROUGH
|
|
|
|
#pragma pack(1)
|
|
|
|
typedef struct {
|
|
IDEREGS IdeReg;
|
|
ULONG DataBufferSize;
|
|
UCHAR DataBuffer[1];
|
|
} ATA_PASS_THROUGH;
|
|
|
|
#pragma pack()
|
|
|
|
ASSERT_CONST(IOCTL_IDE_PASS_THROUGH, 0x04d028);
|
|
ASSERT_SIZEOF(ATA_PASS_THROUGH, 12+1);
|
|
|
|
|
|
// ATA PASS THROUGH (Win2003, XP SP2)
|
|
|
|
#ifndef IOCTL_ATA_PASS_THROUGH
|
|
|
|
#define IOCTL_ATA_PASS_THROUGH \
|
|
CTL_CODE(IOCTL_SCSI_BASE, 0x040B, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
|
|
|
typedef struct _ATA_PASS_THROUGH_EX {
|
|
USHORT Length;
|
|
USHORT AtaFlags;
|
|
UCHAR PathId;
|
|
UCHAR TargetId;
|
|
UCHAR Lun;
|
|
UCHAR ReservedAsUchar;
|
|
ULONG DataTransferLength;
|
|
ULONG TimeOutValue;
|
|
ULONG ReservedAsUlong;
|
|
ULONG_PTR DataBufferOffset;
|
|
UCHAR PreviousTaskFile[8];
|
|
UCHAR CurrentTaskFile[8];
|
|
} ATA_PASS_THROUGH_EX;
|
|
|
|
#define ATA_FLAGS_DRDY_REQUIRED 0x01
|
|
#define ATA_FLAGS_DATA_IN 0x02
|
|
#define ATA_FLAGS_DATA_OUT 0x04
|
|
#define ATA_FLAGS_48BIT_COMMAND 0x08
|
|
#define ATA_FLAGS_USE_DMA 0x10
|
|
#define ATA_FLAGS_NO_MULTIPLE 0x20 // Vista
|
|
|
|
#endif // IOCTL_ATA_PASS_THROUGH
|
|
|
|
ASSERT_CONST(IOCTL_ATA_PASS_THROUGH, 0x04d02c);
|
|
ASSERT_SIZEOF(ATA_PASS_THROUGH_EX, SELECT_WIN_32_64(40, 48));
|
|
|
|
|
|
// IOCTL_SCSI_PASS_THROUGH[_DIRECT]
|
|
|
|
ASSERT_CONST(IOCTL_SCSI_PASS_THROUGH, 0x04d004);
|
|
ASSERT_CONST(IOCTL_SCSI_PASS_THROUGH_DIRECT, 0x04d014);
|
|
ASSERT_SIZEOF(SCSI_PASS_THROUGH, SELECT_WIN_32_64(44, 56));
|
|
ASSERT_SIZEOF(SCSI_PASS_THROUGH_DIRECT, SELECT_WIN_32_64(44, 56));
|
|
|
|
|
|
// SMART IOCTL via SCSI MINIPORT ioctl
|
|
|
|
#ifndef FILE_DEVICE_SCSI
|
|
#define FILE_DEVICE_SCSI 0x001b
|
|
#endif
|
|
|
|
#ifndef IOCTL_SCSI_MINIPORT_SMART_VERSION
|
|
|
|
#define IOCTL_SCSI_MINIPORT_SMART_VERSION ((FILE_DEVICE_SCSI << 16) + 0x0500)
|
|
#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI << 16) + 0x0501)
|
|
#define IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS ((FILE_DEVICE_SCSI << 16) + 0x0502)
|
|
#define IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS ((FILE_DEVICE_SCSI << 16) + 0x0503)
|
|
#define IOCTL_SCSI_MINIPORT_ENABLE_SMART ((FILE_DEVICE_SCSI << 16) + 0x0504)
|
|
#define IOCTL_SCSI_MINIPORT_DISABLE_SMART ((FILE_DEVICE_SCSI << 16) + 0x0505)
|
|
#define IOCTL_SCSI_MINIPORT_RETURN_STATUS ((FILE_DEVICE_SCSI << 16) + 0x0506)
|
|
#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE ((FILE_DEVICE_SCSI << 16) + 0x0507)
|
|
#define IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES ((FILE_DEVICE_SCSI << 16) + 0x0508)
|
|
#define IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS ((FILE_DEVICE_SCSI << 16) + 0x0509)
|
|
#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE ((FILE_DEVICE_SCSI << 16) + 0x050a)
|
|
#define IOCTL_SCSI_MINIPORT_READ_SMART_LOG ((FILE_DEVICE_SCSI << 16) + 0x050b)
|
|
#define IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG ((FILE_DEVICE_SCSI << 16) + 0x050c)
|
|
|
|
#endif // IOCTL_SCSI_MINIPORT_SMART_VERSION
|
|
|
|
ASSERT_CONST(IOCTL_SCSI_MINIPORT, 0x04d008);
|
|
ASSERT_SIZEOF(SRB_IO_CONTROL, 28);
|
|
|
|
|
|
// IOCTL_STORAGE_QUERY_PROPERTY
|
|
|
|
#ifndef IOCTL_STORAGE_QUERY_PROPERTY
|
|
|
|
#define IOCTL_STORAGE_QUERY_PROPERTY \
|
|
CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
|
|
typedef struct _STORAGE_DEVICE_DESCRIPTOR {
|
|
ULONG Version;
|
|
ULONG Size;
|
|
UCHAR DeviceType;
|
|
UCHAR DeviceTypeModifier;
|
|
BOOLEAN RemovableMedia;
|
|
BOOLEAN CommandQueueing;
|
|
ULONG VendorIdOffset;
|
|
ULONG ProductIdOffset;
|
|
ULONG ProductRevisionOffset;
|
|
ULONG SerialNumberOffset;
|
|
STORAGE_BUS_TYPE BusType;
|
|
ULONG RawPropertiesLength;
|
|
UCHAR RawDeviceProperties[1];
|
|
} STORAGE_DEVICE_DESCRIPTOR;
|
|
|
|
typedef enum _STORAGE_QUERY_TYPE {
|
|
PropertyStandardQuery = 0,
|
|
PropertyExistsQuery,
|
|
PropertyMaskQuery,
|
|
PropertyQueryMaxDefined
|
|
} STORAGE_QUERY_TYPE;
|
|
|
|
typedef enum _STORAGE_PROPERTY_ID {
|
|
StorageDeviceProperty = 0,
|
|
StorageAdapterProperty,
|
|
StorageDeviceIdProperty,
|
|
StorageDeviceUniqueIdProperty,
|
|
StorageDeviceWriteCacheProperty,
|
|
StorageMiniportProperty,
|
|
StorageAccessAlignmentProperty
|
|
} STORAGE_PROPERTY_ID;
|
|
|
|
typedef struct _STORAGE_PROPERTY_QUERY {
|
|
STORAGE_PROPERTY_ID PropertyId;
|
|
STORAGE_QUERY_TYPE QueryType;
|
|
UCHAR AdditionalParameters[1];
|
|
} STORAGE_PROPERTY_QUERY;
|
|
|
|
#endif // IOCTL_STORAGE_QUERY_PROPERTY
|
|
|
|
ASSERT_CONST(IOCTL_STORAGE_QUERY_PROPERTY, 0x002d1400);
|
|
ASSERT_SIZEOF(STORAGE_DEVICE_DESCRIPTOR, 36+1+3);
|
|
ASSERT_SIZEOF(STORAGE_PROPERTY_QUERY, 8+1+3);
|
|
|
|
|
|
// IOCTL_STORAGE_PREDICT_FAILURE
|
|
|
|
ASSERT_CONST(IOCTL_STORAGE_PREDICT_FAILURE, 0x002d1100);
|
|
ASSERT_SIZEOF(STORAGE_PREDICT_FAILURE, 4+512);
|
|
|
|
|
|
// 3ware specific versions of SMART ioctl structs
|
|
|
|
#define SMART_VENDOR_3WARE 0x13C1 // identifies 3ware specific parameters
|
|
|
|
#pragma pack(1)
|
|
|
|
typedef struct _GETVERSIONINPARAMS_EX {
|
|
BYTE bVersion;
|
|
BYTE bRevision;
|
|
BYTE bReserved;
|
|
BYTE bIDEDeviceMap;
|
|
DWORD fCapabilities;
|
|
DWORD dwDeviceMapEx; // 3ware specific: RAID drive bit map
|
|
WORD wIdentifier; // Vendor specific identifier
|
|
WORD wControllerId; // 3ware specific: Controller ID (0,1,...)
|
|
ULONG dwReserved[2];
|
|
} GETVERSIONINPARAMS_EX;
|
|
|
|
typedef struct _SENDCMDINPARAMS_EX {
|
|
DWORD cBufferSize;
|
|
IDEREGS irDriveRegs;
|
|
BYTE bDriveNumber;
|
|
BYTE bPortNumber; // 3ware specific: port number
|
|
WORD wIdentifier; // Vendor specific identifier
|
|
DWORD dwReserved[4];
|
|
BYTE bBuffer[1];
|
|
} SENDCMDINPARAMS_EX;
|
|
|
|
#pragma pack()
|
|
|
|
ASSERT_SIZEOF(GETVERSIONINPARAMS_EX, sizeof(GETVERSIONINPARAMS));
|
|
ASSERT_SIZEOF(SENDCMDINPARAMS_EX, sizeof(SENDCMDINPARAMS));
|
|
|
|
|
|
// CSMI structs
|
|
|
|
ASSERT_SIZEOF(IOCTL_HEADER, sizeof(SRB_IO_CONTROL));
|
|
ASSERT_SIZEOF(CSMI_SAS_DRIVER_INFO_BUFFER, 204);
|
|
ASSERT_SIZEOF(CSMI_SAS_PHY_INFO_BUFFER, 2080);
|
|
ASSERT_SIZEOF(CSMI_SAS_STP_PASSTHRU_BUFFER, 168);
|
|
|
|
} // extern "C"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace os_win32 { // no need to publish anything, name provided for Doxygen
|
|
|
|
#ifdef _MSC_VER
|
|
#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
|
|
|
|
|
|
class win_smart_device
|
|
: virtual public /*implements*/ smart_device
|
|
{
|
|
public:
|
|
win_smart_device()
|
|
: smart_device(never_called),
|
|
m_fh(INVALID_HANDLE_VALUE)
|
|
{ }
|
|
|
|
virtual ~win_smart_device() throw();
|
|
|
|
virtual bool is_open() const;
|
|
|
|
virtual bool close();
|
|
|
|
protected:
|
|
/// Set handle for open() in derived classes.
|
|
void set_fh(HANDLE fh)
|
|
{ m_fh = fh; }
|
|
|
|
/// Return handle for derived classes.
|
|
HANDLE get_fh() const
|
|
{ return m_fh; }
|
|
|
|
private:
|
|
HANDLE m_fh; ///< File handle
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
class win_ata_device
|
|
: public /*implements*/ ata_device,
|
|
public /*extends*/ win_smart_device
|
|
{
|
|
public:
|
|
win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type);
|
|
|
|
virtual ~win_ata_device() throw();
|
|
|
|
virtual bool open();
|
|
|
|
virtual bool 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);
|
|
|
|
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;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
class win_scsi_device
|
|
: public /*implements*/ scsi_device,
|
|
virtual public /*extends*/ win_smart_device
|
|
{
|
|
public:
|
|
win_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type);
|
|
|
|
virtual bool open();
|
|
|
|
virtual bool scsi_pass_through(scsi_cmnd_io * iop);
|
|
|
|
private:
|
|
bool open(int pd_num, int ld_num, int tape_num, int sub_addr);
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if WIN9X_SUPPORT
|
|
|
|
class win_aspi_device
|
|
: public /*implements*/ scsi_device
|
|
{
|
|
public:
|
|
win_aspi_device(smart_interface * intf, const char * dev_name, const char * req_type);
|
|
|
|
virtual bool is_open() const;
|
|
|
|
virtual bool open();
|
|
|
|
virtual bool close();
|
|
|
|
virtual bool scsi_pass_through(scsi_cmnd_io * iop);
|
|
|
|
private:
|
|
int m_adapter;
|
|
unsigned char m_id;
|
|
};
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
class csmi_device
|
|
: virtual public /*extends*/ smart_device
|
|
{
|
|
public:
|
|
/// Get phy info
|
|
bool get_phy_info(CSMI_SAS_PHY_INFO & phy_info);
|
|
|
|
/// Check physical drive existence
|
|
bool check_phy(const CSMI_SAS_PHY_INFO & phy_info, unsigned phy_no);
|
|
|
|
protected:
|
|
csmi_device()
|
|
: smart_device(never_called)
|
|
{ memset(&m_phy_ent, 0, sizeof(m_phy_ent)); }
|
|
|
|
/// Select physical drive
|
|
bool select_phy(unsigned phy_no);
|
|
|
|
/// 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
|
|
};
|
|
|
|
|
|
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) { }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
class win_csmi_device
|
|
: public /*implements*/ csmi_ata_device
|
|
{
|
|
public:
|
|
win_csmi_device(smart_interface * intf, const char * dev_name,
|
|
const char * req_type);
|
|
|
|
virtual ~win_csmi_device() throw();
|
|
|
|
virtual bool open();
|
|
|
|
virtual bool close();
|
|
|
|
virtual bool is_open() const;
|
|
|
|
bool open_scsi();
|
|
|
|
protected:
|
|
virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
|
|
unsigned csmi_bufsiz);
|
|
|
|
private:
|
|
HANDLE m_fh; ///< Controller device handle
|
|
unsigned m_phy_no; ///< Physical drive number
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
class win_tw_cli_device
|
|
: public /*implements*/ ata_device_with_command_set
|
|
{
|
|
public:
|
|
win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type);
|
|
|
|
virtual bool is_open() const;
|
|
|
|
virtual bool open();
|
|
|
|
virtual bool close();
|
|
|
|
protected:
|
|
virtual int ata_command_interface(smart_command_set command, int select, char * data);
|
|
|
|
private:
|
|
bool m_ident_valid, m_smart_valid;
|
|
ata_identify_device m_ident_buf;
|
|
ata_smart_values m_smart_buf;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/// 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"
|
|
|
|
|
|
// 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;
|
|
|
|
class win_areca_device
|
|
: public /*implements*/ ata_device,
|
|
public /*extends*/ win_smart_device
|
|
{
|
|
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);
|
|
|
|
protected:
|
|
virtual bool open();
|
|
|
|
virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
|
|
|
|
bool arcmsr_ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out);
|
|
|
|
private:
|
|
int m_disknum; ///< Disk number.
|
|
int m_encnum; ///< Enclosure number.
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Platform specific interfaces
|
|
|
|
// Common to all windows flavors
|
|
class win_smart_interface
|
|
: public /*implements part of*/ smart_interface
|
|
{
|
|
public:
|
|
virtual std::string get_os_version_str();
|
|
|
|
virtual std::string get_app_examples(const char * appname);
|
|
|
|
#ifndef __CYGWIN__
|
|
virtual int64_t get_timer_usec();
|
|
#endif
|
|
|
|
//virtual bool 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 smart_device * autodetect_smart_device(const char * name);
|
|
};
|
|
|
|
#if WIN9X_SUPPORT
|
|
|
|
// Win9x/ME reduced functionality
|
|
class win9x_smart_interface
|
|
: public /*extends*/ win_smart_interface
|
|
{
|
|
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);
|
|
};
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
// WinNT,2000,XP,...
|
|
class winnt_smart_interface
|
|
: public /*extends*/ win_smart_interface
|
|
{
|
|
public:
|
|
virtual bool disable_system_auto_standby(bool disable);
|
|
|
|
virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
|
|
const char * pattern = 0);
|
|
|
|
protected:
|
|
virtual scsi_device * get_scsi_device(const char * name, const char * type);
|
|
|
|
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();
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef _WIN64
|
|
// Running on 64-bit Windows as 32-bit app ?
|
|
static bool is_wow64()
|
|
{
|
|
BOOL (WINAPI * IsWow64Process_p)(HANDLE, PBOOL) =
|
|
(BOOL (WINAPI *)(HANDLE, PBOOL))
|
|
GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process");
|
|
if (!IsWow64Process_p)
|
|
return false;
|
|
BOOL w64 = FALSE;
|
|
if (!IsWow64Process_p(GetCurrentProcess(), &w64))
|
|
return false;
|
|
return !!w64;
|
|
}
|
|
#endif // _WIN64
|
|
|
|
// Return info string about build host and OS version
|
|
std::string win_smart_interface::get_os_version_str()
|
|
{
|
|
char vstr[sizeof(SMARTMONTOOLS_BUILD_HOST)-1+sizeof("-2003r2(64)-sp2.1")+13]
|
|
= SMARTMONTOOLS_BUILD_HOST;
|
|
if (vstr[1] < '6')
|
|
vstr[1] = '6';
|
|
char * const vptr = vstr+sizeof(SMARTMONTOOLS_BUILD_HOST)-1;
|
|
const int vlen = sizeof(vstr)-sizeof(SMARTMONTOOLS_BUILD_HOST);
|
|
assert(vptr == vstr+strlen(vstr) && vptr+vlen+1 == vstr+sizeof(vstr));
|
|
|
|
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 (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;
|
|
}
|
|
|
|
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))
|
|
return -1;
|
|
|
|
return (t.QuadPart * 1000000LL) / freq;
|
|
}
|
|
#endif // __CYGWIN__
|
|
|
|
|
|
// Return value for device detection functions
|
|
enum win_dev_type { DEV_UNKNOWN = 0, DEV_ATA, DEV_SCSI, DEV_USB };
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef WIN9X_SUPPORT
|
|
|
|
scsi_device * win9x_smart_interface::get_scsi_device(const char * name, const char * type)
|
|
{
|
|
return new win_aspi_device(this, name, type);
|
|
}
|
|
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
static win_dev_type get_dev_type(const char * name, int & phydrive)
|
|
{
|
|
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;
|
|
|
|
int logdrive = drive_letter(name);
|
|
if (logdrive >= 0) {
|
|
win_dev_type type = get_log_drive_type(logdrive);
|
|
return (type != DEV_UNKNOWN ? type : DEV_SCSI);
|
|
}
|
|
|
|
char drive[1+1] = "";
|
|
if (sscanf(name, "sd%1[a-z]", drive) == 1) {
|
|
phydrive = drive[0] - 'a';
|
|
return get_phy_drive_type(phydrive);
|
|
}
|
|
|
|
phydrive = -1;
|
|
if (sscanf(name, "pd%d", &phydrive) == 1 && phydrive >= 0)
|
|
return get_phy_drive_type(phydrive);
|
|
return DEV_UNKNOWN;
|
|
}
|
|
|
|
smart_device * win_smart_interface::autodetect_smart_device(const char * name)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
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];
|
|
|
|
if (sscanf(type, "areca,%n%d/%d%n", &n1, &disknum, &encnum, &n2) >= 1 || n1 == 6) {
|
|
if (!(1 <= disknum && disknum <= 128)) {
|
|
set_err(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum);
|
|
return 0;
|
|
}
|
|
if (!(1 <= encnum && encnum <= 8)) {
|
|
set_err(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum);
|
|
return 0;
|
|
}
|
|
|
|
name = skipdev(name);
|
|
#define ARECA_MAX_CTLR_NUM 16
|
|
n1 = -1;
|
|
int ctlrindex = 0;
|
|
if (sscanf(name, "arcmsr%d%n", &ctlrindex, &n1) >= 1 && n1 == (int)strlen(name)) {
|
|
/*
|
|
1. scan from "\\\\.\\scsi[0]:" up to "\\\\.\\scsi[ARECA_MAX_CTLR_NUM]:" and
|
|
2. map arcmsrX into "\\\\.\\scsiX"
|
|
*/
|
|
for (int idx = 0; idx < ARECA_MAX_CTLR_NUM; idx++) {
|
|
memset(devpath, 0, sizeof(devpath));
|
|
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");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::string winnt_smart_interface::get_valid_custom_dev_types_str()
|
|
{
|
|
return "areca,N[/E]";
|
|
}
|
|
|
|
|
|
smart_device * winnt_smart_interface::autodetect_smart_device(const char * name)
|
|
{
|
|
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, "");
|
|
|
|
int phydrive = -1;
|
|
win_dev_type type = get_dev_type(name, phydrive);
|
|
|
|
if (type == DEV_ATA)
|
|
return new win_ata_device(this, name, "");
|
|
if (type == DEV_SCSI)
|
|
return new win_scsi_device(this, name, "");
|
|
|
|
if (type == DEV_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, ""));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#if WIN9X_SUPPORT
|
|
|
|
// Scan for devices on Win9x/ME
|
|
|
|
bool win9x_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 (!type || !strcmp(type, "ata")) {
|
|
if (!ata_scan(devlist))
|
|
return false;
|
|
}
|
|
|
|
if (!type || !strcmp(type, "scsi")) {
|
|
if (!scsi_scan(devlist))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
|
|
// 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");
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Scan up to 10 drives and 2 3ware controllers
|
|
const int max_raid = 2;
|
|
bool raid_seen[max_raid] = {false, false};
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
case DEV_SCSI:
|
|
// STORAGE_QUERY_PROPERTY returned SCSI/SAS/...
|
|
if (!scsi)
|
|
continue;
|
|
devlist.push_back( new win_scsi_device(this, name, "scsi") );
|
|
break;
|
|
|
|
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;
|
|
|
|
default:
|
|
// Unknown type
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
CSMI_SAS_PHY_INFO phy_info;
|
|
if (!test_dev.get_phy_info(phy_info))
|
|
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") );
|
|
}
|
|
}
|
|
}
|
|
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()
|
|
);
|
|
}
|
|
|
|
|
|
bool winnt_smart_interface::disable_system_auto_standby(bool disable)
|
|
{
|
|
if (disable) {
|
|
SYSTEM_POWER_STATUS ps;
|
|
if (!GetSystemPowerStatus(&ps))
|
|
return set_err(ENOSYS, "Unknown power status");
|
|
if (ps.ACLineStatus != 1) {
|
|
SetThreadExecutionState(ES_CONTINUOUS);
|
|
if (ps.ACLineStatus == 0)
|
|
set_err(EIO, "AC offline");
|
|
else
|
|
set_err(EIO, "Unknown AC line status");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!SetThreadExecutionState(ES_CONTINUOUS | (disable ? ES_SYSTEM_REQUIRED : 0)))
|
|
return set_err(ENOSYS);
|
|
return true;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// ATA Interface
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define SMART_CYL_LOW 0x4F
|
|
#define SMART_CYL_HI 0xC2
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// call SMART_GET_VERSION, return device map or -1 on error
|
|
|
|
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;
|
|
|
|
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));
|
|
|
|
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);
|
|
}
|
|
|
|
if (ata_version_ex)
|
|
*ata_version_ex = vers_ex;
|
|
|
|
// TODO: Check vers.fCapabilities here?
|
|
return vers.bIDEDeviceMap;
|
|
}
|
|
|
|
|
|
// call SMART_* ioctl
|
|
|
|
static int smart_ioctl(HANDLE hdevice, int drive, IDEREGS * regs, char * data, unsigned datasize, int port)
|
|
{
|
|
SENDCMDINPARAMS inpar;
|
|
SENDCMDINPARAMS_EX & inpar_ex = (SENDCMDINPARAMS_EX &)inpar;
|
|
|
|
unsigned char outbuf[sizeof(SENDCMDOUTPARAMS)-1 + 512];
|
|
const SENDCMDOUTPARAMS * outpar;
|
|
DWORD code, num_out;
|
|
unsigned int size_out;
|
|
const char * name;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
memset(&outbuf, 0, sizeof(outbuf));
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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 (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;
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
static int ide_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize)
|
|
{
|
|
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;
|
|
}
|
|
|
|
buf->IdeReg = *regs;
|
|
buf->DataBufferSize = datasize;
|
|
if (datasize)
|
|
buf->DataBuffer[0] = magic;
|
|
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// ATA PASS THROUGH (Win2003, XP SP2)
|
|
|
|
// Warning:
|
|
// IOCTL_ATA_PASS_THROUGH[_DIRECT] can only handle one interrupt/DRQ data
|
|
// transfer per command. Therefore, multi-sector transfers are only supported
|
|
// for the READ/WRITE MULTIPLE [EXT] commands. Other commands like READ/WRITE SECTORS
|
|
// or READ/WRITE LOG EXT work only with single sector transfers.
|
|
// The latter are supported on Vista (only) through new ATA_FLAGS_NO_MULTIPLE.
|
|
// See:
|
|
// http://social.msdn.microsoft.com/Forums/en-US/storageplatformata/thread/eb408507-f221-455b-9bbb-d1069b29c4da
|
|
|
|
static int ata_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, IDEREGS * prev_regs, char * data, int datasize)
|
|
{
|
|
const int max_sectors = 32; // TODO: Allocate dynamic buffer
|
|
|
|
typedef struct {
|
|
ATA_PASS_THROUGH_EX apt;
|
|
ULONG Filler;
|
|
UCHAR ucDataBuf[max_sectors * 512];
|
|
} ATA_PASS_THROUGH_EX_WITH_BUFFERS;
|
|
|
|
const unsigned char magic = 0xcf;
|
|
|
|
ATA_PASS_THROUGH_EX_WITH_BUFFERS ab; memset(&ab, 0, sizeof(ab));
|
|
ab.apt.Length = sizeof(ATA_PASS_THROUGH_EX);
|
|
//ab.apt.PathId = 0;
|
|
//ab.apt.TargetId = 0;
|
|
//ab.apt.Lun = 0;
|
|
ab.apt.TimeOutValue = 10;
|
|
unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf);
|
|
ab.apt.DataBufferOffset = size;
|
|
|
|
if (datasize > 0) {
|
|
if (datasize > (int)sizeof(ab.ucDataBuf)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
ab.apt.AtaFlags = ATA_FLAGS_DATA_IN;
|
|
ab.apt.DataTransferLength = datasize;
|
|
size += datasize;
|
|
ab.ucDataBuf[0] = magic;
|
|
}
|
|
else if (datasize < 0) {
|
|
if (-datasize > (int)sizeof(ab.ucDataBuf)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
ab.apt.AtaFlags = ATA_FLAGS_DATA_OUT;
|
|
ab.apt.DataTransferLength = -datasize;
|
|
size += -datasize;
|
|
memcpy(ab.ucDataBuf, data, -datasize);
|
|
}
|
|
else {
|
|
assert(ab.apt.AtaFlags == 0);
|
|
assert(ab.apt.DataTransferLength == 0);
|
|
}
|
|
|
|
assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS));
|
|
IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile;
|
|
IDEREGS * ptfregs = (IDEREGS *)ab.apt.PreviousTaskFile;
|
|
*ctfregs = *regs;
|
|
|
|
if (prev_regs) {
|
|
*ptfregs = *prev_regs;
|
|
ab.apt.AtaFlags |= ATA_FLAGS_48BIT_COMMAND;
|
|
}
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH,
|
|
&ab, size, &ab, size, &num_out, NULL)) {
|
|
long err = GetLastError();
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
|
|
return -1;
|
|
}
|
|
|
|
// Check ATA status
|
|
if (ctfregs->bCommandReg/*Status*/ & (0x01/*Err*/|0x08/*DRQ*/)) {
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_ATA_PASS_THROUGH command failed:\n");
|
|
print_ide_regs_io(regs, ctfregs);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
// Check and copy data
|
|
if (datasize > 0) {
|
|
if ( num_out != size
|
|
|| (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) {
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_ATA_PASS_THROUGH output data missing (%lu)\n", num_out);
|
|
print_ide_regs_io(regs, ctfregs);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
memcpy(data, ab.ucDataBuf, datasize);
|
|
}
|
|
|
|
if (ata_debugmode > 1) {
|
|
pout(" IOCTL_ATA_PASS_THROUGH suceeded, bytes returned: %lu\n", num_out);
|
|
print_ide_regs_io(regs, ctfregs);
|
|
}
|
|
*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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (ata_debugmode > 1) {
|
|
pout(" ATA via IOCTL_SCSI_PASS_THROUGH suceeded, bytes returned: %lu\n", num_out);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// SMART IOCTL via SCSI MINIPORT ioctl
|
|
|
|
// This function is handled by ATAPI port driver (atapi.sys) or by SCSI
|
|
// miniport driver (via SCSI port driver scsiport.sys).
|
|
// It can be used to skip the missing or broken handling of some SMART
|
|
// command codes (e.g. READ_LOG) in the disk class driver (disk.sys)
|
|
|
|
static int ata_via_scsi_miniport_smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize)
|
|
{
|
|
// Select code
|
|
DWORD code = 0; const char * name = 0;
|
|
if (regs->bCommandReg == ATA_IDENTIFY_DEVICE) {
|
|
code = IOCTL_SCSI_MINIPORT_IDENTIFY; name = "IDENTIFY";
|
|
}
|
|
else if (regs->bCommandReg == ATA_SMART_CMD) switch (regs->bFeaturesReg) {
|
|
case ATA_SMART_READ_VALUES:
|
|
code = IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS; name = "READ_SMART_ATTRIBS"; break;
|
|
case ATA_SMART_READ_THRESHOLDS:
|
|
code = IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS; name = "READ_SMART_THRESHOLDS"; break;
|
|
case ATA_SMART_ENABLE:
|
|
code = IOCTL_SCSI_MINIPORT_ENABLE_SMART; name = "ENABLE_SMART"; break;
|
|
case ATA_SMART_DISABLE:
|
|
code = IOCTL_SCSI_MINIPORT_DISABLE_SMART; name = "DISABLE_SMART"; break;
|
|
case ATA_SMART_STATUS:
|
|
code = IOCTL_SCSI_MINIPORT_RETURN_STATUS; name = "RETURN_STATUS"; break;
|
|
case ATA_SMART_AUTOSAVE:
|
|
code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE; name = "ENABLE_DISABLE_AUTOSAVE"; break;
|
|
//case ATA_SMART_SAVE: // obsolete since ATA-6, not used by smartmontools
|
|
// code = IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES; name = "SAVE_ATTRIBUTE_VALUES"; break;
|
|
case ATA_SMART_IMMEDIATE_OFFLINE:
|
|
code = IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS; name = "EXECUTE_OFFLINE_DIAGS"; break;
|
|
case ATA_SMART_AUTO_OFFLINE:
|
|
code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE; name = "ENABLE_DISABLE_AUTO_OFFLINE"; break;
|
|
case ATA_SMART_READ_LOG_SECTOR:
|
|
code = IOCTL_SCSI_MINIPORT_READ_SMART_LOG; name = "READ_SMART_LOG"; break;
|
|
case ATA_SMART_WRITE_LOG_SECTOR:
|
|
code = IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG; name = "WRITE_SMART_LOG"; break;
|
|
}
|
|
if (!code) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
// Set SRB
|
|
struct {
|
|
SRB_IO_CONTROL srbc;
|
|
union {
|
|
SENDCMDINPARAMS in;
|
|
SENDCMDOUTPARAMS out;
|
|
} params;
|
|
char space[512-1];
|
|
} sb;
|
|
ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1+512);
|
|
memset(&sb, 0, sizeof(sb));
|
|
|
|
unsigned size;
|
|
if (datasize > 0) {
|
|
if (datasize > (int)sizeof(sb.space)+1) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
size = datasize;
|
|
}
|
|
else if (datasize < 0) {
|
|
if (-datasize > (int)sizeof(sb.space)+1) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
size = -datasize;
|
|
memcpy(sb.params.in.bBuffer, data, size);
|
|
}
|
|
else if (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
|
|
size = sizeof(IDEREGS);
|
|
else
|
|
size = 0;
|
|
sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
|
|
memcpy(sb.srbc.Signature, "SCSIDISK", 8); // atapi.sys
|
|
sb.srbc.Timeout = 60; // seconds
|
|
sb.srbc.ControlCode = code;
|
|
//sb.srbc.ReturnCode = 0;
|
|
sb.srbc.Length = sizeof(SENDCMDINPARAMS)-1 + size;
|
|
sb.params.in.irDriveRegs = *regs;
|
|
sb.params.in.cBufferSize = size;
|
|
|
|
// Call miniport ioctl
|
|
size += sizeof(SRB_IO_CONTROL) + sizeof(SENDCMDINPARAMS)-1;
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
|
|
&sb, size, &sb, size, &num_out, NULL)) {
|
|
long err = GetLastError();
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_SCSI_MINIPORT_%s failed, Error=%ld\n", name, err);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
|
|
return -1;
|
|
}
|
|
|
|
// Check result
|
|
if (sb.srbc.ReturnCode) {
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_SCSI_MINIPORT_%s failed, ReturnCode=0x%08lx\n", name, sb.srbc.ReturnCode);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
if (sb.params.out.DriverStatus.bDriverError) {
|
|
if (ata_debugmode) {
|
|
pout(" IOCTL_SCSI_MINIPORT_%s failed, DriverError=0x%02x, IDEError=0x%02x\n", name,
|
|
sb.params.out.DriverStatus.bDriverError, sb.params.out.DriverStatus.bIDEError);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = (!sb.params.out.DriverStatus.bIDEError ? ENOSYS : EIO);
|
|
return -1;
|
|
}
|
|
|
|
if (ata_debugmode > 1) {
|
|
pout(" IOCTL_SCSI_MINIPORT_%s 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));
|
|
}
|
|
|
|
if (datasize > 0)
|
|
memcpy(data, sb.params.out.bBuffer, datasize);
|
|
else if (datasize == 0 && code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
|
|
memcpy(regs, sb.params.out.bBuffer, sizeof(IDEREGS));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ATA PASS THROUGH via 3ware specific SCSI MINIPORT ioctl
|
|
|
|
static int ata_via_3ware_miniport_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize, int port)
|
|
{
|
|
struct {
|
|
SRB_IO_CONTROL srbc;
|
|
IDEREGS regs;
|
|
UCHAR buffer[512];
|
|
} sb;
|
|
ASSERT_SIZEOF(sb, sizeof(SRB_IO_CONTROL)+sizeof(IDEREGS)+512);
|
|
|
|
if (!(0 <= datasize && datasize <= (int)sizeof(sb.buffer) && port >= 0)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
memset(&sb, 0, sizeof(sb));
|
|
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;
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
|
|
&sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) {
|
|
long err = GetLastError();
|
|
if (ata_debugmode) {
|
|
pout(" ATA via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
|
|
return -1;
|
|
}
|
|
|
|
if (sb.srbc.ReturnCode) {
|
|
if (ata_debugmode) {
|
|
pout(" ATA via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08lx\n", sb.srbc.ReturnCode);
|
|
print_ide_regs_io(regs, NULL);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
// Copy data
|
|
if (datasize > 0)
|
|
memcpy(data, sb.buffer, datasize);
|
|
|
|
if (ata_debugmode > 1) {
|
|
pout(" ATA via IOCTL_SCSI_MINIPORT suceeded, bytes returned: %lu\n", num_out);
|
|
print_ide_regs_io(regs, &sb.regs);
|
|
}
|
|
*regs = sb.regs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 3ware specific call to update the devicemap returned by SMART_GET_VERSION.
|
|
// 3DM/CLI "Rescan Controller" function does not to always update it.
|
|
|
|
static int update_3ware_devicemap_ioctl(HANDLE hdevice)
|
|
{
|
|
SRB_IO_CONTROL srbc;
|
|
memset(&srbc, 0, sizeof(srbc));
|
|
strcpy((char *)srbc.Signature, "<3ware>");
|
|
srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
|
|
srbc.Timeout = 60; // seconds
|
|
srbc.ControlCode = 0xCC010014;
|
|
srbc.ReturnCode = 0;
|
|
srbc.Length = 0;
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
|
|
&srbc, sizeof(srbc), &srbc, sizeof(srbc), &num_out, NULL)) {
|
|
long err = GetLastError();
|
|
if (ata_debugmode)
|
|
pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
|
|
errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
|
|
return -1;
|
|
}
|
|
if (srbc.ReturnCode) {
|
|
if (ata_debugmode)
|
|
pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08lx\n", srbc.ReturnCode);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
if (ata_debugmode > 1)
|
|
pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT suceeded\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Routines for pseudo device /dev/tw_cli/*
|
|
// Parses output of 3ware "tw_cli /cx/py show all" or 3DM SMART data window
|
|
|
|
|
|
// Get clipboard data
|
|
|
|
static int get_clipboard(char * data, int datasize)
|
|
{
|
|
if (!OpenClipboard(NULL))
|
|
return -1;
|
|
HANDLE h = GetClipboardData(CF_TEXT);
|
|
if (!h) {
|
|
CloseClipboard();
|
|
return 0;
|
|
}
|
|
const void * p = GlobalLock(h);
|
|
int n = GlobalSize(h);
|
|
if (n > datasize)
|
|
n = datasize;
|
|
memcpy(data, p, n);
|
|
GlobalFree(h);
|
|
CloseClipboard();
|
|
return n;
|
|
}
|
|
|
|
|
|
// Run a command, write stdout to dataout
|
|
// TODO: Combine with daemon_win32.cpp:daemon_spawn()
|
|
|
|
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;
|
|
}
|
|
|
|
// 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<char *>(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);
|
|
|
|
// 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) : "");
|
|
}
|
|
|
|
|
|
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];
|
|
}
|
|
|
|
|
|
// TODO: This is OS independent
|
|
|
|
win_tw_cli_device::win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type)
|
|
: smart_device(intf, dev_name, "tw_cli", req_type),
|
|
m_ident_valid(false), m_smart_valid(false)
|
|
{
|
|
memset(&m_ident_buf, 0, sizeof(m_ident_buf));
|
|
memset(&m_smart_buf, 0, sizeof(m_smart_buf));
|
|
}
|
|
|
|
|
|
bool win_tw_cli_device::is_open() const
|
|
{
|
|
return (m_ident_valid || m_smart_valid);
|
|
}
|
|
|
|
|
|
bool win_tw_cli_device::open()
|
|
{
|
|
m_ident_valid = m_smart_valid = false;
|
|
const char * name = skipdev(get_dev_name());
|
|
// Read tw_cli or 3DM browser output into buffer
|
|
char buffer[4096];
|
|
int size = -1, n1 = -1, n2 = -1;
|
|
if (!strcmp(name, "tw_cli/clip")) { // read clipboard
|
|
size = get_clipboard(buffer, sizeof(buffer));
|
|
}
|
|
else if (!strcmp(name, "tw_cli/stdin")) { // read stdin
|
|
size = fread(buffer, 1, sizeof(buffer), stdin);
|
|
}
|
|
else if (sscanf(name, "tw_cli/%nc%*u/p%*u%n", &n1, &n2) >= 0 && n2 == (int)strlen(name)) {
|
|
// tw_cli/cx/py => read output from "tw_cli /cx/py show all"
|
|
char cmd[100];
|
|
snprintf(cmd, sizeof(cmd), "tw_cli /%s show all", name+n1);
|
|
if (ata_debugmode > 1)
|
|
pout("%s: Run: \"%s\"\n", name, cmd);
|
|
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);
|
|
|
|
buffer[size] = 0;
|
|
if (ata_debugmode > 1)
|
|
pout("[\n%.100s%s\n]\n", buffer, (size>100?"...":""));
|
|
|
|
// Fake identify sector
|
|
ASSERT_SIZEOF(ata_identify_device, 512);
|
|
ata_identify_device * id = &m_ident_buf;
|
|
memset(id, 0, sizeof(*id));
|
|
copy_swapped(id->model , findstr(buffer, " Model = " ), sizeof(id->model));
|
|
copy_swapped(id->fw_rev , findstr(buffer, " Firmware Version = "), sizeof(id->fw_rev));
|
|
copy_swapped(id->serial_no, findstr(buffer, " Serial = " ), sizeof(id->serial_no));
|
|
unsigned long nblocks = 0; // "Capacity = N.N GB (N Blocks)"
|
|
sscanf(findstr(buffer, "Capacity = "), "%*[^(\r\n](%lu", &nblocks);
|
|
if (nblocks) {
|
|
id->words047_079[49-47] = 0x0200; // size valid
|
|
id->words047_079[60-47] = (unsigned short)(nblocks ); // secs_16
|
|
id->words047_079[61-47] = (unsigned short)(nblocks>>16); // secs_32
|
|
}
|
|
id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
|
|
id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
|
|
|
|
// Parse smart data hex dump
|
|
const char * s = findstr(buffer, "Drive Smart Data:");
|
|
if (!*s)
|
|
s = findstr(buffer, "Drive SMART Data:"); // tw_cli from 9.5.x
|
|
if (!*s) {
|
|
s = findstr(buffer, "S.M.A.R.T. (Controller"); // from 3DM browser window
|
|
if (*s) {
|
|
const char * s1 = findstr(s, "<td class"); // html version
|
|
if (*s1)
|
|
s = s1;
|
|
s += strcspn(s, "\r\n");
|
|
}
|
|
else
|
|
s = buffer; // try raw hex dump without header
|
|
}
|
|
unsigned char * sd = (unsigned char *)&m_smart_buf;
|
|
int i = 0;
|
|
for (;;) {
|
|
unsigned x = ~0; int n = -1;
|
|
if (!(sscanf(s, "%x %n", &x, &n) == 1 && !(x & ~0xff)))
|
|
break;
|
|
sd[i] = (unsigned char)x;
|
|
if (!(++i < 512 && n > 0))
|
|
break;
|
|
s += n;
|
|
if (*s == '<') // "<br>"
|
|
s += strcspn(s, "\r\n");
|
|
}
|
|
if (i < 512) {
|
|
if (!id->model[1]) {
|
|
// No useful data found
|
|
char * err = strstr(buffer, "Error:");
|
|
if (!err)
|
|
err = strstr(buffer, "error :");
|
|
if (err && (err = strchr(err, ':'))) {
|
|
// Show tw_cli error message
|
|
err++;
|
|
err[strcspn(err, "\r\n")] = 0;
|
|
return set_err(EIO, "%s", err);
|
|
}
|
|
return set_err(EIO);
|
|
}
|
|
sd = 0;
|
|
}
|
|
|
|
m_ident_valid = true;
|
|
m_smart_valid = !!sd;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool win_tw_cli_device::close()
|
|
{
|
|
m_ident_valid = m_smart_valid = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
int win_tw_cli_device::ata_command_interface(smart_command_set command, int /*select*/, char * data)
|
|
{
|
|
switch (command) {
|
|
case IDENTIFY:
|
|
if (!m_ident_valid)
|
|
break;
|
|
memcpy(data, &m_ident_buf, 512);
|
|
return 0;
|
|
case READ_VALUES:
|
|
if (!m_smart_valid)
|
|
break;
|
|
memcpy(data, &m_smart_buf, 512);
|
|
return 0;
|
|
case ENABLE:
|
|
case STATUS:
|
|
case STATUS_CHECK: // Fake "good" SMART status
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
// Arrive here for all unsupported commands
|
|
set_err(ENOSYS);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// IOCTL_STORAGE_QUERY_PROPERTY
|
|
|
|
union STORAGE_DEVICE_DESCRIPTOR_DATA {
|
|
STORAGE_DEVICE_DESCRIPTOR desc;
|
|
char raw[256];
|
|
};
|
|
|
|
// Get STORAGE_DEVICE_DESCRIPTOR_DATA for device.
|
|
// (This works without admin rights)
|
|
|
|
static int storage_query_property_ioctl(HANDLE hdevice, STORAGE_DEVICE_DESCRIPTOR_DATA * data)
|
|
{
|
|
STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, {0} };
|
|
memset(data, 0, sizeof(*data));
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
|
|
&query, sizeof(query), data, sizeof(*data), &num_out, NULL)) {
|
|
if (ata_debugmode > 1 || scsi_debugmode > 1)
|
|
pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%ld\n", GetLastError());
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
if (ata_debugmode > 1 || scsi_debugmode > 1) {
|
|
pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n"
|
|
" Vendor: \"%s\"\n"
|
|
" Product: \"%s\"\n"
|
|
" Revision: \"%s\"\n"
|
|
" Removable: %s\n"
|
|
" BusType: 0x%02x\n",
|
|
(data->desc.VendorIdOffset ? data->raw+data->desc.VendorIdOffset : "(null)"),
|
|
(data->desc.ProductIdOffset ? data->raw+data->desc.ProductIdOffset : "(null)"),
|
|
(data->desc.ProductRevisionOffset ? data->raw+data->desc.ProductRevisionOffset : "(null)"),
|
|
(data->desc.RemovableMedia? "Yes":"No"), data->desc.BusType
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// IOCTL_STORAGE_PREDICT_FAILURE
|
|
|
|
// Call IOCTL_STORAGE_PREDICT_FAILURE, return PredictFailure value
|
|
// or -1 on error, opionally return VendorSpecific data.
|
|
// (This works without admin rights)
|
|
|
|
static int storage_predict_failure_ioctl(HANDLE hdevice, char * data = 0)
|
|
{
|
|
STORAGE_PREDICT_FAILURE pred;
|
|
memset(&pred, 0, sizeof(pred));
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(hdevice, IOCTL_STORAGE_PREDICT_FAILURE,
|
|
0, 0, &pred, sizeof(pred), &num_out, NULL)) {
|
|
if (ata_debugmode > 1)
|
|
pout(" IOCTL_STORAGE_PREDICT_FAILURE failed, Error=%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)
|
|
{
|
|
if (!(data->desc.VendorIdOffset && data->desc.ProductIdOffset))
|
|
return false;
|
|
const char * vendor = data->raw + data->desc.VendorIdOffset;
|
|
if (!(!strnicmp(vendor, "Intel", 5) && strspn(vendor+5, " ") == strlen(vendor+5)))
|
|
return false;
|
|
if (strnicmp(data->raw + data->desc.ProductIdOffset, "Raid ", 5))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// get DEV_* for open handle
|
|
static win_dev_type get_controller_type(HANDLE hdevice, bool admin, GETVERSIONINPARAMS_EX * ata_version_ex)
|
|
{
|
|
// Get BusType from device descriptor
|
|
STORAGE_DEVICE_DESCRIPTOR_DATA data;
|
|
if (storage_query_property_ioctl(hdevice, &data))
|
|
return DEV_UNKNOWN;
|
|
|
|
// Newer BusType* values are missing in older includes
|
|
switch ((int)data.desc.BusType) {
|
|
case BusTypeAta:
|
|
case 0x0b: // BusTypeSata
|
|
if (ata_version_ex)
|
|
memset(ata_version_ex, 0, sizeof(*ata_version_ex));
|
|
return DEV_ATA;
|
|
|
|
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;
|
|
|
|
case BusTypeUsb:
|
|
return DEV_USB;
|
|
|
|
default:
|
|
return DEV_UNKNOWN;
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
// get DEV_* for device path
|
|
static win_dev_type get_controller_type(const char * path, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
|
|
{
|
|
bool admin = true;
|
|
HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
admin = false;
|
|
h = CreateFileA(path, 0,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return DEV_UNKNOWN;
|
|
}
|
|
if (ata_debugmode > 1 || scsi_debugmode > 1)
|
|
pout(" %s: successfully opened%s\n", path, (!admin ? " (without admin rights)" :""));
|
|
win_dev_type type = get_controller_type(h, admin, ata_version_ex);
|
|
CloseHandle(h);
|
|
return type;
|
|
}
|
|
|
|
// get DEV_* for physical drive number
|
|
static win_dev_type get_phy_drive_type(int drive, GETVERSIONINPARAMS_EX * ata_version_ex)
|
|
{
|
|
char path[30];
|
|
snprintf(path, sizeof(path)-1, "\\\\.\\PhysicalDrive%d", drive);
|
|
return get_controller_type(path, ata_version_ex);
|
|
}
|
|
|
|
static win_dev_type get_phy_drive_type(int drive)
|
|
{
|
|
return get_phy_drive_type(drive, 0);
|
|
}
|
|
|
|
// get DEV_* for logical drive number
|
|
static win_dev_type get_log_drive_type(int drive)
|
|
{
|
|
char path[30];
|
|
snprintf(path, sizeof(path)-1, "\\\\.\\%c:", 'A'+drive);
|
|
return get_controller_type(path);
|
|
}
|
|
|
|
// Build IDENTIFY information from STORAGE_DEVICE_DESCRIPTOR
|
|
static int get_identify_from_device_property(HANDLE hdevice, ata_identify_device * id)
|
|
{
|
|
STORAGE_DEVICE_DESCRIPTOR_DATA data;
|
|
if (storage_query_property_ioctl(hdevice, &data))
|
|
return -1;
|
|
|
|
memset(id, 0, sizeof(*id));
|
|
|
|
// Some drivers split ATA model string into VendorId and ProductId,
|
|
// others return it as ProductId only.
|
|
char model[sizeof(id->model) + 1] = "";
|
|
|
|
unsigned i = 0;
|
|
if (data.desc.VendorIdOffset) {
|
|
for ( ;i < sizeof(model)-1 && data.raw[data.desc.VendorIdOffset+i]; i++)
|
|
model[i] = data.raw[data.desc.VendorIdOffset+i];
|
|
}
|
|
|
|
if (data.desc.ProductIdOffset) {
|
|
while (i > 1 && model[i-2] == ' ') // Keep last blank from VendorId
|
|
i--;
|
|
// Ignore VendorId "ATA"
|
|
if (i <= 4 && !strncmp(model, "ATA", 3) && (i == 3 || model[3] == ' '))
|
|
i = 0;
|
|
for (unsigned j = 0; i < sizeof(model)-1 && data.raw[data.desc.ProductIdOffset+j]; i++, j++)
|
|
model[i] = data.raw[data.desc.ProductIdOffset+j];
|
|
}
|
|
|
|
while (i > 0 && model[i-1] == ' ')
|
|
i--;
|
|
model[i] = 0;
|
|
copy_swapped(id->model, model, sizeof(id->model));
|
|
|
|
if (data.desc.ProductRevisionOffset)
|
|
copy_swapped(id->fw_rev, data.raw+data.desc.ProductRevisionOffset, sizeof(id->fw_rev));
|
|
|
|
id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
|
|
id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
|
|
return 0;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// USB ID detection using WMI
|
|
|
|
// Get USB ID for a physical drive number
|
|
static bool get_usb_id(int drive, unsigned short & vendor_id, unsigned short & product_id)
|
|
{
|
|
bool debug = (scsi_debugmode > 1);
|
|
|
|
wbem_services ws;
|
|
if (!ws.connect()) {
|
|
if (debug)
|
|
pout("WMI connect failed\n");
|
|
return false;
|
|
}
|
|
|
|
// Get device name
|
|
wbem_object wo;
|
|
if (!ws.query1(wo, "SELECT Model FROM Win32_DiskDrive WHERE DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", drive))
|
|
return false;
|
|
|
|
std::string name = wo.get_str("Model");
|
|
if (debug)
|
|
pout("PhysicalDrive%d, \"%s\":\n", drive, name.c_str());
|
|
|
|
// Get USB_CONTROLLER -> DEVICE associations
|
|
wbem_enumerator we;
|
|
if (!ws.query(we, "SELECT Antecedent,Dependent FROM Win32_USBControllerDevice"))
|
|
return false;
|
|
|
|
unsigned short usb_venid = 0, prev_usb_venid = 0;
|
|
unsigned short usb_proid = 0, prev_usb_proid = 0;
|
|
std::string prev_usb_ant;
|
|
std::string prev_ant, ant, dep;
|
|
|
|
const regular_expression regex("^.*PnPEntity\\.DeviceID=\"([^\"]*)\"", 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());
|
|
|
|
// 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);
|
|
|
|
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");
|
|
|
|
// Continue if not name of physical disk drive
|
|
if (name2 != name) {
|
|
if (debug)
|
|
pout(" +---> (\"%s\")\n", name2.c_str());
|
|
continue;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Found
|
|
usb_venid = prev_usb_venid;
|
|
usb_proid = prev_usb_proid;
|
|
if (debug)
|
|
pout(" +===> \"%s\" [0x%04x:0x%04x]\n", name2.c_str(), usb_venid, usb_proid);
|
|
|
|
// Continue to check for duplicate names ...
|
|
}
|
|
else {
|
|
if (debug)
|
|
pout(" | \"%s\"\n", devid.c_str());
|
|
}
|
|
}
|
|
|
|
if (!usb_venid)
|
|
return false;
|
|
|
|
vendor_id = usb_venid;
|
|
product_id = usb_proid;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Call GetDevicePowerState() 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)
|
|
{
|
|
static bool unsupported = false;
|
|
if (unsupported) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
#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
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (ata_debugmode > 1)
|
|
pout(" GetDevicePowerState() succeeded, state=%d\n", state);
|
|
return state;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if WIN9X_SUPPORT
|
|
// Print SMARTVSD error message, return errno
|
|
|
|
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;
|
|
}
|
|
}
|
|
else {
|
|
strcpy(path+len, "\\SMARTVSD.VXD");
|
|
if (!access(path, 0)) {
|
|
// Some Windows versions install SMARTVSD.VXD in SYSTEM directory
|
|
// (http://support.microsoft.com/kb/265854/en-us).
|
|
path[len] = 0;
|
|
pout("SMART driver is not properly installed,\n"
|
|
" move SMARTVSD.VXD from \"%s\" to \"%s\\IOSUBSYS\"\n"
|
|
" and reboot Windows.\n", path, path);
|
|
}
|
|
else {
|
|
// Some Windows versions do not provide SMARTVSD.VXD
|
|
// (http://support.microsoft.com/kb/199886/en-us).
|
|
path[len] = 0;
|
|
pout("SMARTVSD.VXD is missing in folder \"%s\\IOSUBSYS\".\n", path);
|
|
}
|
|
return ENOSYS;
|
|
}
|
|
}
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
// Get default ATA device options
|
|
|
|
static const char * ata_get_def_options()
|
|
{
|
|
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_*
|
|
}
|
|
|
|
|
|
// Common routines for devices with HANDLEs
|
|
|
|
win_smart_device::~win_smart_device() throw()
|
|
{
|
|
if (m_fh != INVALID_HANDLE_VALUE)
|
|
::CloseHandle(m_fh);
|
|
}
|
|
|
|
bool win_smart_device::is_open() const
|
|
{
|
|
return (m_fh != INVALID_HANDLE_VALUE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ATA
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
win_ata_device::~win_ata_device() throw()
|
|
{
|
|
}
|
|
|
|
|
|
// Open ATA device
|
|
|
|
bool win_ata_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);
|
|
}
|
|
// pd<m>,N => Physical drive <m>, RAID port N
|
|
int phydrive = -1; port = ~0; n1 = -1; n2 = -1;
|
|
if ( sscanf(name, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1
|
|
&& phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) {
|
|
return open(phydrive, -1, "", (int)port);
|
|
}
|
|
// [a-zA-Z]: => Physical drive behind logical drive 0-25
|
|
int logdrive = drive_letter(name);
|
|
if (logdrive >= 0) {
|
|
return open(-1, logdrive, "", -1);
|
|
}
|
|
|
|
return set_err(EINVAL);
|
|
}
|
|
|
|
|
|
bool win_ata_device::open(int phydrive, int logdrive, const char * options, int port)
|
|
{
|
|
// 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);
|
|
|
|
// 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);
|
|
}
|
|
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);
|
|
return false;
|
|
}
|
|
set_fh(h);
|
|
|
|
// 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)" :""));
|
|
|
|
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;
|
|
|
|
// Win9X/ME: Get drive map
|
|
// 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;
|
|
}
|
|
else
|
|
portmap = vers_ex.dwDeviceMapEx;
|
|
}
|
|
if (devmap < 0) {
|
|
pout("%s: ATA driver has no SMART support\n", devpath);
|
|
if (!is_permissive()) {
|
|
close();
|
|
return set_err(ENOSYS);
|
|
}
|
|
devmap = 0x0f;
|
|
}
|
|
m_smartver_state = 1;
|
|
|
|
if (port >= 0) {
|
|
// 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);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
// Drive number must be passed to ioctl
|
|
m_drive = (phydrive & 0x3);
|
|
return true;
|
|
}
|
|
|
|
|
|
#if WIN9X_SUPPORT
|
|
|
|
// Scan for ATA drives on Win9x/ME
|
|
|
|
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
|
|
}
|
|
|
|
// Get drive map
|
|
int devmap = smart_get_version(h);
|
|
CloseHandle(h);
|
|
if (devmap < 0)
|
|
return true; // Should not happen
|
|
|
|
// 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") );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Interface to ATA devices
|
|
bool win_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
|
|
{
|
|
// No multi-sector support for now, see above
|
|
// warning about IOCTL_ATA_PASS_THROUGH
|
|
if (!ata_cmd_is_ok(in,
|
|
true, // data_out_support
|
|
false, // !multi_sector_support
|
|
true) // ata_48bit_support
|
|
)
|
|
return false;
|
|
|
|
// 3ware RAID: SMART DISABLE without port number disables SMART functions
|
|
if ( m_is_3ware && m_port < 0
|
|
&& in.in_regs.command == ATA_SMART_CMD
|
|
&& in.in_regs.features == ATA_SMART_DISABLE)
|
|
return set_err(ENOSYS, "SMART DISABLE requires 3ware port number");
|
|
|
|
// Determine ioctl functions valid for this ATA cmd
|
|
const char * valid_options = 0;
|
|
|
|
switch (in.in_regs.command) {
|
|
case ATA_IDENTIFY_DEVICE:
|
|
case ATA_IDENTIFY_PACKET_DEVICE:
|
|
// SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
|
|
// and SCSI_MINIPORT_* if requested by user
|
|
valid_options = (m_usr_options ? "saicmf" : "saicf");
|
|
break;
|
|
|
|
case ATA_CHECK_POWER_MODE:
|
|
// Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk
|
|
valid_options = "pai3";
|
|
break;
|
|
|
|
case ATA_SMART_CMD:
|
|
switch (in.in_regs.features) {
|
|
case ATA_SMART_READ_VALUES:
|
|
case ATA_SMART_READ_THRESHOLDS:
|
|
case ATA_SMART_AUTOSAVE:
|
|
case ATA_SMART_ENABLE:
|
|
case ATA_SMART_DISABLE:
|
|
case ATA_SMART_AUTO_OFFLINE:
|
|
// SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
|
|
// and SCSI_MINIPORT_* if requested by user
|
|
valid_options = (m_usr_options ? "saicmf" : "saicf");
|
|
break;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
case ATA_SMART_WRITE_LOG_SECTOR:
|
|
// ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT
|
|
// but SCSI_MINIPORT_* only if requested by user and single sector.
|
|
valid_options = (in.size == 512 && m_usr_options ? "am" : "a");
|
|
break;
|
|
|
|
case ATA_SMART_STATUS:
|
|
// 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;
|
|
|
|
default:
|
|
// Unknown SMART command, handle below
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Other ATA command, handle below
|
|
break;
|
|
}
|
|
|
|
if (!valid_options) {
|
|
// No special ATA command found above, select a generic pass through ioctl.
|
|
if (!( in.direction == ata_cmd_in::no_data
|
|
|| (in.direction == ata_cmd_in::data_in && in.size == 512))
|
|
|| in.in_regs.is_48bit_cmd() )
|
|
// DATA_OUT, more than one sector, 48-bit command: ATA_PASS_THROUGH only
|
|
valid_options = "a";
|
|
else if (in.out_needed.is_set())
|
|
// Need output registers: ATA/IDE_PASS_THROUGH
|
|
valid_options = "ai";
|
|
else
|
|
valid_options = "aic";
|
|
}
|
|
|
|
if (!m_admin) {
|
|
// Restrict to IOCTL_STORAGE_*
|
|
if (strchr(valid_options, 'f'))
|
|
valid_options = "f";
|
|
else if (strchr(valid_options, 'p'))
|
|
valid_options = "p";
|
|
else
|
|
return set_err(ENOSYS, "Function requires admin rights");
|
|
}
|
|
|
|
// Set IDEREGS
|
|
IDEREGS regs, prev_regs;
|
|
{
|
|
const ata_in_regs & lo = in.in_regs;
|
|
regs.bFeaturesReg = lo.features;
|
|
regs.bSectorCountReg = lo.sector_count;
|
|
regs.bSectorNumberReg = lo.lba_low;
|
|
regs.bCylLowReg = lo.lba_mid;
|
|
regs.bCylHighReg = lo.lba_high;
|
|
regs.bDriveHeadReg = lo.device;
|
|
regs.bCommandReg = lo.command;
|
|
regs.bReserved = 0;
|
|
}
|
|
if (in.in_regs.is_48bit_cmd()) {
|
|
const ata_in_regs & hi = in.in_regs.prev;
|
|
prev_regs.bFeaturesReg = hi.features;
|
|
prev_regs.bSectorCountReg = hi.sector_count;
|
|
prev_regs.bSectorNumberReg = hi.lba_low;
|
|
prev_regs.bCylLowReg = hi.lba_mid;
|
|
prev_regs.bCylHighReg = hi.lba_high;
|
|
prev_regs.bDriveHeadReg = hi.device;
|
|
prev_regs.bCommandReg = hi.command;
|
|
prev_regs.bReserved = 0;
|
|
}
|
|
|
|
// Set data direction
|
|
int datasize = 0;
|
|
char * data = 0;
|
|
switch (in.direction) {
|
|
case ata_cmd_in::no_data:
|
|
break;
|
|
case ata_cmd_in::data_in:
|
|
datasize = (int)in.size;
|
|
data = (char *)in.buffer;
|
|
break;
|
|
case ata_cmd_in::data_out:
|
|
datasize = -(int)in.size;
|
|
data = (char *)in.buffer;
|
|
break;
|
|
default:
|
|
return set_err(EINVAL, "win_ata_device::ata_pass_through: invalid direction=%d",
|
|
(int)in.direction);
|
|
}
|
|
|
|
|
|
// Try all valid ioctls in the order specified in m_options
|
|
bool powered_up = false;
|
|
bool out_regs_set = false;
|
|
bool id_is_cached = false;
|
|
const char * options = m_options.c_str();
|
|
|
|
for (int i = 0; ; i++) {
|
|
char opt = options[i];
|
|
|
|
if (!opt) {
|
|
if (in.in_regs.command == ATA_CHECK_POWER_MODE && powered_up) {
|
|
// Power up reported by GetDevicePowerState() and no ioctl available
|
|
// to detect the actual mode of the drive => simulate ATA result ACTIVE/IDLE.
|
|
regs.bSectorCountReg = 0xff;
|
|
out_regs_set = true;
|
|
break;
|
|
}
|
|
// No IOCTL found
|
|
return set_err(ENOSYS);
|
|
}
|
|
if (!strchr(valid_options, opt))
|
|
// Invalid for this command
|
|
continue;
|
|
|
|
errno = 0;
|
|
assert( datasize == 0 || datasize == 512
|
|
|| (datasize == -512 && strchr("am", opt))
|
|
|| (datasize > 512 && opt == 'a'));
|
|
int rc;
|
|
switch (opt) {
|
|
default: assert(0);
|
|
case 's':
|
|
// call SMART_GET_VERSION once for each drive
|
|
if (m_smartver_state > 1) {
|
|
rc = -1; errno = ENOSYS;
|
|
break;
|
|
}
|
|
if (!m_smartver_state) {
|
|
assert(m_port == -1);
|
|
GETVERSIONINPARAMS_EX vers_ex;
|
|
if (smart_get_version(get_fh(), &vers_ex) < 0) {
|
|
if (!failuretest_permissive) {
|
|
m_smartver_state = 2;
|
|
rc = -1; errno = ENOSYS;
|
|
break;
|
|
}
|
|
failuretest_permissive--;
|
|
}
|
|
else {
|
|
// 3ware RAID if vendor id present
|
|
m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE);
|
|
}
|
|
|
|
m_smartver_state = 1;
|
|
}
|
|
rc = smart_ioctl(get_fh(), 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;
|
|
}
|
|
|
|
if (!rc)
|
|
// Working ioctl found
|
|
break;
|
|
|
|
if (errno != ENOSYS)
|
|
// Abort on I/O error
|
|
return set_err(errno);
|
|
|
|
out_regs_set = false;
|
|
// CAUTION: *_ioctl() MUST NOT change "regs" Parameter in the ENOSYS case
|
|
}
|
|
|
|
// Return IDEREGS if set
|
|
if (out_regs_set) {
|
|
ata_out_regs & lo = out.out_regs;
|
|
lo.error = regs.bFeaturesReg;
|
|
lo.sector_count = regs.bSectorCountReg;
|
|
lo.lba_low = regs.bSectorNumberReg;
|
|
lo.lba_mid = regs.bCylLowReg;
|
|
lo.lba_high = regs.bCylHighReg;
|
|
lo.device = regs.bDriveHeadReg;
|
|
lo.status = regs.bCommandReg;
|
|
if (in.in_regs.is_48bit_cmd()) {
|
|
ata_out_regs & hi = out.out_regs.prev;
|
|
hi.sector_count = prev_regs.bSectorCountReg;
|
|
hi.lba_low = prev_regs.bSectorNumberReg;
|
|
hi.lba_mid = prev_regs.bCylLowReg;
|
|
hi.lba_high = prev_regs.bCylHighReg;
|
|
}
|
|
}
|
|
|
|
if ( in.in_regs.command == ATA_IDENTIFY_DEVICE
|
|
|| in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE)
|
|
// Update ata_identify_is_cached() result according to ioctl used.
|
|
m_id_is_cached = id_is_cached;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Return true if OS caches the ATA identify sector
|
|
bool win_ata_device::ata_identify_is_cached() const
|
|
{
|
|
return m_id_is_cached;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// csmi_ata_device
|
|
|
|
bool csmi_device::get_phy_info(CSMI_SAS_PHY_INFO & phy_info)
|
|
{
|
|
// 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;
|
|
|
|
if (scsi_debugmode > 1) {
|
|
const CSMI_SAS_DRIVER_INFO & driver_info = driver_info_buf.Information;
|
|
pout("CSMI_SAS_DRIVER_INFO:\n");
|
|
pout(" Name: \"%.81s\"\n", driver_info.szName);
|
|
pout(" Description: \"%.81s\"\n", driver_info.szDescription);
|
|
pout(" Revision: %d.%d\n", driver_info.usMajorRevision, driver_info.usMinorRevision);
|
|
}
|
|
|
|
// Get Phy info
|
|
CSMI_SAS_PHY_INFO_BUFFER phy_info_buf;
|
|
memset(&phy_info_buf, 0, sizeof(phy_info_buf));
|
|
if (!csmi_ioctl(CC_CSMI_SAS_GET_PHY_INFO, &phy_info_buf.IoctlHeader, sizeof(phy_info_buf)))
|
|
return 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]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool csmi_device::check_phy(const CSMI_SAS_PHY_INFO & phy_info, unsigned phy_no)
|
|
{
|
|
// 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);
|
|
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool csmi_device::select_phy(unsigned phy_no)
|
|
{
|
|
CSMI_SAS_PHY_INFO phy_info;
|
|
if (!get_phy_info(phy_info))
|
|
return false;
|
|
|
|
|
|
if (!check_phy(phy_info, phy_no))
|
|
return false;
|
|
|
|
m_phy_ent = phy_info.Phy[phy_no];
|
|
return true;
|
|
}
|
|
|
|
|
|
bool csmi_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
|
|
{
|
|
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;
|
|
|
|
// Set transfer mode
|
|
switch (in.direction) {
|
|
case ata_cmd_in::no_data:
|
|
pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_UNSPECIFIED;
|
|
break;
|
|
case ata_cmd_in::data_in:
|
|
pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_READ;
|
|
pthru.uDataLength = in.size;
|
|
break;
|
|
case ata_cmd_in::data_out:
|
|
pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_WRITE;
|
|
pthru.uDataLength = in.size;
|
|
memcpy(pthru_buf->bDataBuffer, in.buffer, in.size);
|
|
break;
|
|
default:
|
|
return set_err(EINVAL, "csmi_ata_device::ata_pass_through: invalid direction=%d",
|
|
(int)in.direction);
|
|
}
|
|
|
|
// Set host-to-device FIS
|
|
{
|
|
unsigned char * fis = pthru.bCommandFIS;
|
|
const ata_in_regs & lo = in.in_regs;
|
|
const ata_in_regs & hi = in.in_regs.prev;
|
|
fis[ 0] = 0x27; // Type: host-to-device FIS
|
|
fis[ 1] = 0x80; // Bit7: Update command register
|
|
fis[ 2] = lo.command;
|
|
fis[ 3] = lo.features;
|
|
fis[ 4] = lo.lba_low;
|
|
fis[ 5] = lo.lba_mid;
|
|
fis[ 6] = lo.lba_high;
|
|
fis[ 7] = lo.device;
|
|
fis[ 8] = hi.lba_low;
|
|
fis[ 9] = hi.lba_mid;
|
|
fis[10] = hi.lba_high;
|
|
fis[11] = hi.features;
|
|
fis[12] = lo.sector_count;
|
|
fis[13] = hi.sector_count;
|
|
}
|
|
|
|
// Call ioctl
|
|
if (!csmi_ioctl(CC_CSMI_SAS_STP_PASSTHRU, &pthru_buf->IoctlHeader, pthru_raw_buf.size())) {
|
|
return false;
|
|
}
|
|
|
|
// Get device-to-host FIS
|
|
{
|
|
const unsigned char * fis = pthru_buf->Status.bStatusFIS;
|
|
ata_out_regs & lo = out.out_regs;
|
|
lo.status = fis[ 2];
|
|
lo.error = fis[ 3];
|
|
lo.lba_low = fis[ 4];
|
|
lo.lba_mid = fis[ 5];
|
|
lo.lba_high = fis[ 6];
|
|
lo.device = fis[ 7];
|
|
lo.sector_count = fis[12];
|
|
if (in.in_regs.is_48bit_cmd()) {
|
|
ata_out_regs & hi = out.out_regs.prev;
|
|
hi.lba_low = fis[ 8];
|
|
hi.lba_mid = fis[ 9];
|
|
hi.lba_high = fis[10];
|
|
hi.sector_count = fis[13];
|
|
}
|
|
}
|
|
|
|
// Get data
|
|
if (in.direction == ata_cmd_in::data_in)
|
|
// TODO: Check ptru_buf->Status.uDataBytes
|
|
memcpy(in.buffer, pthru_buf->bDataBuffer, in.size);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// win_csmi_device
|
|
|
|
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_csmi_device::~win_csmi_device() throw()
|
|
{
|
|
if (m_fh != INVALID_HANDLE_VALUE)
|
|
CloseHandle(m_fh);
|
|
}
|
|
|
|
bool win_csmi_device::is_open() const
|
|
{
|
|
return (m_fh != INVALID_HANDLE_VALUE);
|
|
}
|
|
|
|
bool win_csmi_device::close()
|
|
{
|
|
if (m_fh == INVALID_HANDLE_VALUE)
|
|
return true;
|
|
BOOL rc = CloseHandle(m_fh);
|
|
m_fh = INVALID_HANDLE_VALUE;
|
|
return !!rc;
|
|
}
|
|
|
|
|
|
bool win_csmi_device::open_scsi()
|
|
{
|
|
// Parse name
|
|
unsigned contr_no = ~0, 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);
|
|
|
|
// Open controller handle
|
|
char devpath[30];
|
|
snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%u:", contr_no);
|
|
|
|
HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
(SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0);
|
|
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
long err = GetLastError();
|
|
if (err == ERROR_FILE_NOT_FOUND)
|
|
set_err(ENOENT, "%s: not found", devpath);
|
|
else if (err == ERROR_ACCESS_DENIED)
|
|
set_err(EACCES, "%s: access denied", devpath);
|
|
else
|
|
set_err(EIO, "%s: Error=%ld", devpath, err);
|
|
return false;
|
|
}
|
|
|
|
if (scsi_debugmode > 1)
|
|
pout(" %s: successfully opened\n", devpath);
|
|
|
|
m_fh = h;
|
|
m_phy_no = phy_no;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool win_csmi_device::open()
|
|
{
|
|
if (!open_scsi())
|
|
return false;
|
|
|
|
// Get Phy info for this drive
|
|
if (!select_phy(m_phy_no)) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool win_csmi_device::csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
|
|
unsigned csmi_bufsiz)
|
|
{
|
|
// Determine signature
|
|
const char * sig;
|
|
switch (code) {
|
|
case CC_CSMI_SAS_GET_DRIVER_INFO:
|
|
sig = CSMI_ALL_SIGNATURE; break;
|
|
case CC_CSMI_SAS_GET_PHY_INFO:
|
|
case CC_CSMI_SAS_STP_PASSTHRU:
|
|
sig = CSMI_SAS_SIGNATURE; break;
|
|
default:
|
|
return set_err(ENOSYS, "Unknown CSMI code=%u", code);
|
|
}
|
|
|
|
// Set header
|
|
csmi_buffer->HeaderLength = sizeof(IOCTL_HEADER);
|
|
strncpy((char *)csmi_buffer->Signature, sig, sizeof(csmi_buffer->Signature));
|
|
csmi_buffer->Timeout = CSMI_SAS_TIMEOUT;
|
|
csmi_buffer->ControlCode = code;
|
|
csmi_buffer->ReturnCode = 0;
|
|
csmi_buffer->Length = csmi_bufsiz - sizeof(IOCTL_HEADER);
|
|
|
|
// Call function
|
|
DWORD num_out = 0;
|
|
if (!DeviceIoControl(m_fh, IOCTL_SCSI_MINIPORT,
|
|
csmi_buffer, csmi_bufsiz, csmi_buffer, csmi_bufsiz, &num_out, (OVERLAPPED*)0)) {
|
|
long err = GetLastError();
|
|
if (scsi_debugmode)
|
|
pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, Error=%ld\n", code, err);
|
|
if ( err == ERROR_INVALID_FUNCTION
|
|
|| err == ERROR_NOT_SUPPORTED
|
|
|| err == ERROR_DEV_NOT_EXIST)
|
|
return set_err(ENOSYS, "CSMI is not supported (Error=%ld)", err);
|
|
else
|
|
return set_err(EIO, "CSMI(%u) failed with Error=%ld", code, err);
|
|
}
|
|
|
|
// Check result
|
|
if (csmi_buffer->ReturnCode) {
|
|
if (scsi_debugmode) {
|
|
pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, ReturnCode=%lu\n",
|
|
code, csmi_buffer->ReturnCode);
|
|
}
|
|
return set_err(EIO, "CSMI(%u) failed with ReturnCode=%lu", code, csmi_buffer->ReturnCode);
|
|
}
|
|
|
|
if (scsi_debugmode > 1)
|
|
pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) succeeded, bytes returned: %lu\n", code, num_out);
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
#pragma pack()
|
|
|
|
// 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
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// Get ASPI entrypoint from wnaspi32.dll
|
|
|
|
static FARPROC aspi_get_address(const char * name, int verbose)
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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 (scsi_debugmode)
|
|
pout("%u ASPI Adapter%s detected\n",num_aspi_adapters, (num_aspi_adapters!=1?"s":""));
|
|
|
|
#ifdef __CYGWIN__
|
|
// save PID to detect fork() in aspi_entry_valid()
|
|
aspi_dll_pid = GetCurrentProcessId();
|
|
#endif
|
|
assert(aspi_entry_valid());
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int aspi_io_call(ASPI_SRB * srb, unsigned timeout)
|
|
{
|
|
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());
|
|
}
|
|
// TODO: ASPI_ABORT_IO command
|
|
aspi_entry = 0;
|
|
h_aspi_dll = (HINSTANCE)INVALID_HANDLE_VALUE;
|
|
return -EIO;
|
|
}
|
|
}
|
|
CloseHandle(event);
|
|
return 0;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
bool win_aspi_device::is_open() const
|
|
{
|
|
return (m_adapter >= 0);
|
|
}
|
|
|
|
bool win_aspi_device::open()
|
|
{
|
|
// 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);
|
|
|
|
if (!aspi_entry_valid()) {
|
|
if (aspi_open_dll(1/*verbose*/))
|
|
return set_err(ENOENT);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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);
|
|
|
|
m_adapter = (int)adapter; m_id = (unsigned char)id;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool win_aspi_device::close()
|
|
{
|
|
// No FreeLibrary(h_aspi_dll) to prevent problems with ASPI threads
|
|
return true;
|
|
}
|
|
|
|
|
|
// Scan for ASPI drives
|
|
|
|
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;
|
|
}
|
|
|
|
for (unsigned ad = 0; ad < num_aspi_adapters; ad++) {
|
|
ASPI_SRB srb;
|
|
|
|
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;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// Interface to ASPI SCSI devices
|
|
bool win_aspi_device::scsi_pass_through(scsi_cmnd_io * iop)
|
|
{
|
|
int report = scsi_debugmode; // TODO
|
|
|
|
if (m_adapter < 0) {
|
|
set_err(EBADF);
|
|
return false;
|
|
}
|
|
|
|
if (!aspi_entry_valid()) {
|
|
set_err(EBADF);
|
|
return false;
|
|
}
|
|
|
|
if (!(iop->cmnd_len == 6 || iop->cmnd_len == 10 || iop->cmnd_len == 12 || iop->cmnd_len == 16)) {
|
|
set_err(EINVAL, "bad CDB length");
|
|
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);
|
|
|
|
np = scsi_get_opcode_name(ucp[0]);
|
|
j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
|
|
for (k = 0; k < (int)iop->cmnd_len; ++k)
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
|
|
if ((report > 1) &&
|
|
(DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
|
|
int trunc = (iop->dxfer_len > 256) ? 1 : 0;
|
|
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
|
|
"data, len=%d%s:\n", (int)iop->dxfer_len,
|
|
(trunc ? " [only first 256 bytes shown]" : ""));
|
|
dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
|
|
}
|
|
else
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
|
|
pout("%s", buff);
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
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;
|
|
break;
|
|
default:
|
|
set_err(EINVAL, "bad dxfer_dir");
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (report > 0)
|
|
pout(" OK\n");
|
|
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // WIN9X_SUPPORT
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// SPT Interface (for SCSI devices and ATA devices behind SATLs)
|
|
// Only supported in NT and later
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
win_scsi_device::win_scsi_device(smart_interface * intf,
|
|
const char * dev_name, const char * req_type)
|
|
: smart_device(intf, dev_name, "scsi", req_type)
|
|
{
|
|
}
|
|
|
|
bool win_scsi_device::open()
|
|
{
|
|
const char * name = skipdev(get_dev_name()); int len = strlen(name);
|
|
// sd[a-z],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<m>,N => Physical drive <m>, RAID port N
|
|
int pd_num = -1; sub_addr = -1; n1 = -1; n2 = -1;
|
|
if ( sscanf(name, "pd%d%n,%d%n", &pd_num, &n1, &sub_addr, &n2) >= 1
|
|
&& pd_num >= 0 && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0))) {
|
|
return open(pd_num, -1, -1, sub_addr);
|
|
}
|
|
// [a-zA-Z]: => Physical drive behind logical drive 0-25
|
|
int logdrive = drive_letter(name);
|
|
if (logdrive >= 0) {
|
|
return open(-1, logdrive, -1, -1);
|
|
}
|
|
// n?st<m> => tape drive <m> (same names used in Cygwin's /dev emulation)
|
|
int tape_num = -1; n1 = -1;
|
|
if (sscanf(name, "st%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
|
|
return open(-1, -1, tape_num, -1);
|
|
}
|
|
tape_num = -1; n1 = -1;
|
|
if (sscanf(name, "nst%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
|
|
return open(-1, -1, tape_num, -1);
|
|
}
|
|
// tape<m> => tape drive <m>
|
|
tape_num = -1; n1 = -1;
|
|
if (sscanf(name, "tape%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
|
|
return open(-1, -1, tape_num, -1);
|
|
}
|
|
|
|
return set_err(EINVAL);
|
|
}
|
|
|
|
bool win_scsi_device::open(int pd_num, int ld_num, int tape_num, int /*sub_addr*/)
|
|
{
|
|
char b[128];
|
|
b[sizeof(b) - 1] = '\0';
|
|
if (pd_num >= 0)
|
|
snprintf(b, sizeof(b) - 1, "\\\\.\\PhysicalDrive%d", pd_num);
|
|
else if (ld_num >= 0)
|
|
snprintf(b, sizeof(b) - 1, "\\\\.\\%c:", 'A' + ld_num);
|
|
else if (tape_num >= 0)
|
|
snprintf(b, sizeof(b) - 1, "\\\\.\\TAPE%d", tape_num);
|
|
else {
|
|
set_err(EINVAL);
|
|
return false;
|
|
}
|
|
|
|
// Open device
|
|
HANDLE h = CreateFileA(b, GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
|
|
OPEN_EXISTING, 0, 0);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
set_err(ENODEV, "%s: Open failed, Error=%ld", b, GetLastError());
|
|
return false;
|
|
}
|
|
set_fh(h);
|
|
return true;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
SCSI_PASS_THROUGH_DIRECT spt;
|
|
ULONG Filler;
|
|
UCHAR ucSenseBuf[64];
|
|
} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
|
|
|
|
|
|
// Issue command via IOCTL_SCSI_PASS_THROUGH instead of *_DIRECT.
|
|
// Used if DataTransferLength not supported by *_DIRECT.
|
|
static long scsi_pass_through_indirect(HANDLE h,
|
|
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER * sbd)
|
|
{
|
|
struct SCSI_PASS_THROUGH_WITH_BUFFERS {
|
|
SCSI_PASS_THROUGH spt;
|
|
ULONG Filler;
|
|
UCHAR ucSenseBuf[sizeof(sbd->ucSenseBuf)];
|
|
UCHAR ucDataBuf[512];
|
|
};
|
|
|
|
SCSI_PASS_THROUGH_WITH_BUFFERS sb;
|
|
memset(&sb, 0, sizeof(sb));
|
|
|
|
// DATA_OUT not implemented yet
|
|
if (!( sbd->spt.DataIn == SCSI_IOCTL_DATA_IN
|
|
&& sbd->spt.DataTransferLength <= sizeof(sb.ucDataBuf)))
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
sb.spt.Length = sizeof(sb.spt);
|
|
sb.spt.CdbLength = sbd->spt.CdbLength;
|
|
memcpy(sb.spt.Cdb, sbd->spt.Cdb, sizeof(sb.spt.Cdb));
|
|
sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
|
|
sb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
|
|
sb.spt.DataIn = sbd->spt.DataIn;
|
|
sb.spt.DataTransferLength = sbd->spt.DataTransferLength;
|
|
sb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
|
|
sb.spt.TimeOutValue = sbd->spt.TimeOutValue;
|
|
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(h, IOCTL_SCSI_PASS_THROUGH,
|
|
&sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
|
|
return GetLastError();
|
|
|
|
sbd->spt.ScsiStatus = sb.spt.ScsiStatus;
|
|
if (sb.spt.ScsiStatus & SCSI_STATUS_CHECK_CONDITION)
|
|
memcpy(sbd->ucSenseBuf, sb.ucSenseBuf, sizeof(sbd->ucSenseBuf));
|
|
|
|
sbd->spt.DataTransferLength = sb.spt.DataTransferLength;
|
|
if (sbd->spt.DataIn == SCSI_IOCTL_DATA_IN && sb.spt.DataTransferLength > 0)
|
|
memcpy(sbd->spt.DataBuffer, sb.ucDataBuf, sb.spt.DataTransferLength);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c
|
|
bool win_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop)
|
|
{
|
|
int report = scsi_debugmode; // TODO
|
|
|
|
if (report > 0) {
|
|
int k, j;
|
|
const unsigned char * ucp = iop->cmnd;
|
|
const char * np;
|
|
char buff[256];
|
|
const int sz = (int)sizeof(buff);
|
|
|
|
np = scsi_get_opcode_name(ucp[0]);
|
|
j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
|
|
for (k = 0; k < (int)iop->cmnd_len; ++k)
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
|
|
if ((report > 1) &&
|
|
(DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
|
|
int trunc = (iop->dxfer_len > 256) ? 1 : 0;
|
|
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
|
|
"data, len=%d%s:\n", (int)iop->dxfer_len,
|
|
(trunc ? " [only first 256 bytes shown]" : ""));
|
|
dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
|
|
}
|
|
else
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
|
|
pout("%s", buff);
|
|
}
|
|
|
|
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
|
|
if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
|
|
set_err(EINVAL, "cmnd_len too large");
|
|
return false;
|
|
}
|
|
|
|
memset(&sb, 0, sizeof(sb));
|
|
sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
|
|
sb.spt.CdbLength = iop->cmnd_len;
|
|
memcpy(sb.spt.Cdb, iop->cmnd, iop->cmnd_len);
|
|
sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
|
|
sb.spt.SenseInfoOffset =
|
|
offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
|
|
sb.spt.TimeOutValue = (iop->timeout ? iop->timeout : 60);
|
|
|
|
bool direct = true;
|
|
switch (iop->dxfer_dir) {
|
|
case DXFER_NONE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
|
|
break;
|
|
case DXFER_FROM_DEVICE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
|
|
sb.spt.DataTransferLength = iop->dxfer_len;
|
|
sb.spt.DataBuffer = iop->dxferp;
|
|
// IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
|
|
// transfers (needed for SMART STATUS check of JMicron USB bridges)
|
|
if (sb.spt.DataTransferLength == 1)
|
|
direct = false;
|
|
break;
|
|
case DXFER_TO_DEVICE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
|
|
sb.spt.DataTransferLength = iop->dxfer_len;
|
|
sb.spt.DataBuffer = iop->dxferp;
|
|
break;
|
|
default:
|
|
set_err(EINVAL, "bad dxfer_dir");
|
|
return false;
|
|
}
|
|
|
|
long err = 0;
|
|
if (direct) {
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(get_fh(), IOCTL_SCSI_PASS_THROUGH_DIRECT,
|
|
&sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
|
|
err = GetLastError();
|
|
}
|
|
else
|
|
err = scsi_pass_through_indirect(get_fh(), &sb);
|
|
|
|
if (err)
|
|
return set_err((err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO),
|
|
"IOCTL_SCSI_PASS_THROUGH%s failed, Error=%ld",
|
|
(direct ? "_DIRECT" : ""), err);
|
|
|
|
iop->scsi_status = sb.spt.ScsiStatus;
|
|
if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
|
|
int slen = sb.ucSenseBuf[7] + 8;
|
|
|
|
if (slen > (int)sizeof(sb.ucSenseBuf))
|
|
slen = sizeof(sb.ucSenseBuf);
|
|
if (slen > (int)iop->max_sense_len)
|
|
slen = iop->max_sense_len;
|
|
memcpy(iop->sensep, sb.ucSenseBuf, slen);
|
|
iop->resp_sense_len = slen;
|
|
if (report) {
|
|
if (report > 1) {
|
|
pout(" >>> Sense buffer, len=%d:\n", slen);
|
|
dStrHex(iop->sensep, slen , 1);
|
|
}
|
|
if ((iop->sensep[0] & 0x7f) > 0x71)
|
|
pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
|
|
iop->scsi_status, iop->sensep[1] & 0xf,
|
|
iop->sensep[2], iop->sensep[3]);
|
|
else
|
|
pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
|
|
iop->scsi_status, iop->sensep[2] & 0xf,
|
|
iop->sensep[12], iop->sensep[13]);
|
|
}
|
|
} else
|
|
iop->resp_sense_len = 0;
|
|
|
|
if ((iop->dxfer_len > 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);
|
|
}
|
|
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)
|
|
{
|
|
int report = scsi_debugmode; // TODO
|
|
|
|
if (report > 0) {
|
|
int k, j;
|
|
const unsigned char * ucp = iop->cmnd;
|
|
const char * np;
|
|
char buff[256];
|
|
const int sz = (int)sizeof(buff);
|
|
|
|
np = scsi_get_opcode_name(ucp[0]);
|
|
j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
|
|
for (k = 0; k < (int)iop->cmnd_len; ++k)
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
|
|
if ((report > 1) &&
|
|
(DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
|
|
int trunc = (iop->dxfer_len > 256) ? 1 : 0;
|
|
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
|
|
"data, len=%d%s:\n", (int)iop->dxfer_len,
|
|
(trunc ? " [only first 256 bytes shown]" : ""));
|
|
dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
|
|
}
|
|
else
|
|
j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
|
|
pout("%s", buff);
|
|
}
|
|
|
|
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
|
|
if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
memset(&sb, 0, sizeof(sb));
|
|
sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
|
|
//sb.spt.PathId = 0;
|
|
sb.spt.TargetId = 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);
|
|
|
|
bool direct = true;
|
|
switch (iop->dxfer_dir) {
|
|
case DXFER_NONE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
|
|
break;
|
|
case DXFER_FROM_DEVICE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
|
|
sb.spt.DataTransferLength = iop->dxfer_len;
|
|
sb.spt.DataBuffer = iop->dxferp;
|
|
// IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
|
|
// transfers (needed for SMART STATUS check of JMicron USB bridges)
|
|
if (sb.spt.DataTransferLength == 1)
|
|
direct = false;
|
|
break;
|
|
case DXFER_TO_DEVICE:
|
|
sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
|
|
sb.spt.DataTransferLength = iop->dxfer_len;
|
|
sb.spt.DataBuffer = iop->dxferp;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
long err = 0;
|
|
if (direct) {
|
|
DWORD num_out;
|
|
if (!DeviceIoControl(fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
|
|
&sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
|
|
err = GetLastError();
|
|
}
|
|
else
|
|
err = scsi_pass_through_indirect(fd, &sb);
|
|
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
iop->scsi_status = sb.spt.ScsiStatus;
|
|
if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
|
|
int slen = sb.ucSenseBuf[7] + 8;
|
|
|
|
if (slen > (int)sizeof(sb.ucSenseBuf))
|
|
slen = sizeof(sb.ucSenseBuf);
|
|
if (slen > (int)iop->max_sense_len)
|
|
slen = iop->max_sense_len;
|
|
memcpy(iop->sensep, sb.ucSenseBuf, slen);
|
|
iop->resp_sense_len = slen;
|
|
if (report) {
|
|
if (report > 1) {
|
|
pout(" >>> Sense buffer, len=%d:\n", slen);
|
|
dStrHex(iop->sensep, slen , 1);
|
|
}
|
|
if ((iop->sensep[0] & 0x7f) > 0x71)
|
|
pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
|
|
iop->scsi_status, iop->sensep[1] & 0xf,
|
|
iop->sensep[2], iop->sensep[3]);
|
|
else
|
|
pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
|
|
iop->scsi_status, iop->sensep[2] & 0xf,
|
|
iop->sensep[12], iop->sensep[13]);
|
|
}
|
|
} else
|
|
iop->resp_sense_len = 0;
|
|
|
|
if ((iop->dxfer_len > 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);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#if 0 // For debugging areca code
|
|
|
|
static void dumpdata(unsigned char *block, int len)
|
|
{
|
|
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);
|
|
}
|
|
|
|
if ( pos < 16 )
|
|
{
|
|
for ( int loop = pos; loop < 16; loop++ )
|
|
{
|
|
printf(" ");
|
|
}
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
#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)
|
|
{
|
|
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));
|
|
|
|
memset((unsigned char *)&sBuf, 0, sizeof(sBuf));
|
|
memset(&io_hdr, 0, sizeof(io_hdr));
|
|
memset(cdb, 0, sizeof(cdb));
|
|
memset(sense, 0, sizeof(sense));
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
cdb[1] = 0x01;
|
|
cdb[2] = 0xf0;
|
|
|
|
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;
|
|
|
|
while ( 1 )
|
|
{
|
|
ioctlreturn = scsi_pass_through_direct(fd, &io_hdr);
|
|
if ( ioctlreturn || io_hdr.scsi_status )
|
|
{
|
|
// errors found
|
|
break;
|
|
}
|
|
|
|
if ( arcmsr_cmd != ARCMSR_IOCTL_READ_RQBUFFER )
|
|
{
|
|
// if succeeded, just returns the length of outgoing data
|
|
return data_len;
|
|
}
|
|
|
|
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 ( total >= 7 && total >= expected )
|
|
{
|
|
//printf("total bytes received = %d, expected length = %d\n", total, expected);
|
|
|
|
// ------ 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;
|
|
}
|
|
|
|
if ( ioctlreturn )
|
|
{
|
|
pout("do_scsi_cmnd_io with write buffer failed code = %x\n", ioctlreturn);
|
|
return -2;
|
|
}
|
|
|
|
if ( io_hdr.scsi_status )
|
|
{
|
|
pout("io_hdr.scsi_status with write buffer failed code = %x\n", io_hdr.scsi_status);
|
|
return -3;
|
|
}
|
|
|
|
if ( data )
|
|
{
|
|
memcpy(data, return_buff, total);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool win_areca_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;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// ----- 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
|
|
|
|
// ----- BEGIN TO SETUP PAYLOAD DATA -----
|
|
memcpy(&areca_packet[7], "SmrT", 4); // areca defined password
|
|
ata_cmd = (sATA_INPUT_REGISTERS *)&areca_packet[12];
|
|
|
|
// 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);
|
|
}
|
|
|
|
areca_packet[11] = m_disknum - 1; // disk#
|
|
areca_packet[19] = m_encnum - 1; // enc#
|
|
|
|
// ----- 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;
|
|
|
|
// ----- BEGIN TO SEND TO ARECA DRIVER ------
|
|
int expected = 0;
|
|
unsigned char return_buff[2048];
|
|
memset(return_buff, 0, sizeof(return_buff));
|
|
|
|
expected = arcmsr_command_handler(get_fh(), ARCMSR_IOCTL_CLEAR_RQBUFFER, NULL, 0);
|
|
if (expected==-3) {
|
|
return set_err(EIO);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// ----- VERIFY THE CHECKSUM -----
|
|
cs = 0;
|
|
for ( int loop = 3; loop < expected - 1; loop++ )
|
|
{
|
|
cs += return_buff[loop];
|
|
}
|
|
|
|
if ( return_buff[expected - 1] != cs )
|
|
{
|
|
return set_err(EIO);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// returns with data
|
|
if (readdata)
|
|
{
|
|
memcpy(in.buffer, &return_buff[7], in.size);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool win_areca_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
|
|
{
|
|
#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(hmutex)
|
|
{
|
|
CloseHandle(hmutex);
|
|
}
|
|
|
|
if ( (HLOCAL)pSD )
|
|
{
|
|
LocalFree((HLOCAL)pSD);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
} // namespace
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Initialize platform interface and register with smi()
|
|
void smart_interface::init()
|
|
{
|
|
{
|
|
// Remove "." from DLL search path if supported
|
|
// to prevent DLL preloading attacks
|
|
BOOL (WINAPI * SetDllDirectoryA_p)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))
|
|
GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetDllDirectoryA");
|
|
if (SetDllDirectoryA_p)
|
|
SetDllDirectoryA_p("");
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef __CYGWIN__
|
|
|
|
// Get exe directory
|
|
// (prototype in utiliy.h)
|
|
std::string get_exe_dir()
|
|
{
|
|
char path[MAX_PATH];
|
|
// Get path of this exe
|
|
if (!GetModuleFileNameA(GetModuleHandleA(0), path, sizeof(path)))
|
|
throw std::runtime_error("GetModuleFileName() failed");
|
|
// Replace backslash by slash
|
|
int sl = -1;
|
|
for (int i = 0; path[i]; i++)
|
|
if (path[i] == '\\') {
|
|
path[i] = '/'; sl = i;
|
|
}
|
|
// Remove filename
|
|
if (sl >= 0)
|
|
path[sl] = 0;
|
|
return path;
|
|
}
|
|
|
|
#endif
|