*
* Home page of code is: http://smartmontools.sourceforge.net
*
- * Copyright (C) 2004-6 Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2004-7 Christian Franke <smartmontools-support@lists.sourceforge.net>
*
* 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
#include <stddef.h> // offsetof()
#include <io.h> // access()
-#define ARGUSED(x) ((void)(x))
-
// 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]
// Needed by '-V' option (CVS versioning) of smartd/smartctl
-const char *os_XXXX_c_cvsid="$Id: os_win32.cpp,v 1.42 2006/09/27 21:42:03 chrfranke Exp $"
+const char *os_XXXX_c_cvsid="$Id: os_win32.cpp,v 1.59 2007/10/31 22:08:04 chrfranke Exp $"
ATACMDS_H_CVSID CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
+// Running on Win9x/ME ?
+static inline bool is_win9x()
+{
+ return !!(GetVersion() & 0x80000000);
+}
+
+// Running on 64-bit Windows as 32-bit app ?
+static bool is_wow64()
+{
+ HMODULE hk = GetModuleHandleA("kernel32");
+ if (!hk)
+ return false;
+ BOOL (WINAPI * IsWow64Process_p)(HANDLE, PBOOL) =
+ (BOOL (WINAPI *)(HANDLE, PBOOL))GetProcAddress(hk, "IsWow64Process");
+ if (!IsWow64Process_p)
+ return false;
+ BOOL w64 = FALSE;
+ if (!IsWow64Process_p(GetCurrentProcess(), &w64))
+ return false;
+ return !!w64;
+}
+
+
#ifndef HAVE_GET_OS_VERSION_STR
#error define of HAVE_GET_OS_VERSION_STR missing in config.h
#endif
// Return build host and OS version as static string
const char * get_os_version_str()
{
- static char vstr[sizeof(SMARTMONTOOLS_BUILD_HOST)-3-1+sizeof("-2003r2-sp2.1")+13];
+ static char vstr[sizeof(SMARTMONTOOLS_BUILD_HOST)-3-1+sizeof("-2003r2(64)-sp2.1")+13];
char * const vptr = vstr+sizeof(SMARTMONTOOLS_BUILD_HOST)-3-1;
const int vlen = sizeof(vstr)-(sizeof(SMARTMONTOOLS_BUILD_HOST)-3);
- OSVERSIONINFOEXA vi;
- const char * w;
-
// remove "-pc" to avoid long lines
assert(!strncmp(SMARTMONTOOLS_BUILD_HOST+5, "pc-", 3));
strcpy(vstr, "i686-"); strcpy(vstr+5, SMARTMONTOOLS_BUILD_HOST+5+3);
assert(vptr == vstr+strlen(vstr) && vptr+vlen+1 == vstr+sizeof(vstr));
- memset(&vi, 0, sizeof(vi));
+ OSVERSIONINFOEXA vi; memset(&vi, 0, sizeof(vi));
vi.dwOSVersionInfoSize = sizeof(vi);
if (!GetVersionExA((OSVERSIONINFOA *)&vi)) {
memset(&vi, 0, sizeof(vi));
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' ||
default: w = 0; break;
}
+ const char * w64 = (is_wow64() ? "(64)" : "");
if (!w)
- snprintf(vptr, vlen, "-%s%lu.%lu",
+ snprintf(vptr, vlen, "-%s%lu.%lu%s",
(vi.dwPlatformId==VER_PLATFORM_WIN32_NT ? "nt" : "9x"),
- vi.dwMajorVersion, vi.dwMinorVersion);
+ vi.dwMajorVersion, vi.dwMinorVersion, w64);
else if (vi.wServicePackMinor)
- snprintf(vptr, vlen, "-%s-sp%u.%u", w, vi.wServicePackMajor, vi.wServicePackMinor);
+ snprintf(vptr, vlen, "-%s%s-sp%u.%u", w, w64, vi.wServicePackMajor, vi.wServicePackMinor);
else if (vi.wServicePackMajor)
- snprintf(vptr, vlen, "-%s-sp%u", w, vi.wServicePackMajor);
+ snprintf(vptr, vlen, "-%s%s-sp%u", w, w64, vi.wServicePackMajor);
else
- snprintf(vptr, vlen, "-%s", w);
+ snprintf(vptr, vlen, "-%s%s", w, w64);
return vstr;
}
-static int ata_open(int drive, const char * options, int port);
+static int get_phy_drive_type(int drive);
+static int get_log_drive_type(int drive);
+
+#define ATARAID_FDOFFSET 0x0200
+
+static int ata_open(int phydrive, int logdrive, const char * options, int port);
static void ata_close(int fd);
+static int ata_scan_win9x(unsigned long * drives);
static int ata_scan(unsigned long * drives, int * rdriveno, unsigned long * rdrives);
static const char * ata_get_def_options(void);
+#define TW_CLI_FDOFFSET 0x0300
+
+static int tw_cli_open(const char * name);
+static void tw_cli_close();
+
+#define ASPI_FDOFFSET 0x0100
+
static int aspi_open(unsigned adapter, unsigned id);
static void aspi_close(int fd);
static int aspi_scan(unsigned long * drives);
+#define SPT_FDOFFSET 0x0400
+
+static int spt_open(int pd_num, int ld_num, int tape_num, int sub_addr);
+static void spt_close(int fd);
+static int spt_scan(unsigned long * drives);
+
static int is_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) ? s + 5 : s);
+ return (!strncmp(s, "/dev/", 5) && drive_letter(s+5) < 0 ? s+5 : s);
}
int guess_device_type (const char * dev_name)
{
dev_name = skipdev(dev_name);
+ if (!strncmp(dev_name, "scsi", 4))
+ return CONTROLLER_SCSI;
+ if (is_win9x())
+ return CONTROLLER_ATA;
if (!strncmp(dev_name, "hd", 2))
return CONTROLLER_ATA;
- if (!strncmp(dev_name, "scsi", 4))
+ if (!strncmp(dev_name, "tw_cli", 6))
+ return CONTROLLER_ATA;
+ if (!strncmp(dev_name, "st", 2))
+ return CONTROLLER_SCSI;
+ if (!strncmp(dev_name, "nst", 3))
+ return CONTROLLER_SCSI;
+ if (!strncmp(dev_name, "tape", 4))
return CONTROLLER_SCSI;
+ int logdrive = drive_letter(dev_name);
+ if (logdrive >= 0) {
+ int type = get_log_drive_type(logdrive);
+ return (type != CONTROLLER_UNKNOWN ? type : CONTROLLER_SCSI);
+ }
+ char drive[1+1] = "";
+ if (sscanf(dev_name, "sd%1[a-z]", drive) == 1)
+ return get_phy_drive_type(drive[0]-'a');
+ int phydrive = -1;
+ if (sscanf(dev_name, "pd%d", &phydrive) == 1 && phydrive >= 0)
+ return get_phy_drive_type(phydrive);
return CONTROLLER_UNKNOWN;
}
rdriveno[0] = rdriveno[1] = -1;
rdrives[0] = rdrives[1] = 0;
+ bool win9x = is_win9x();
if (!strcmp(type, "ATA")) {
// bit i set => drive i present
- n = ata_scan(drives, rdriveno, rdrives);
- path = "/dev/hda";
+ if (win9x) {
+ n = ata_scan_win9x(drives);
+ path = "/dev/hda";
+ }
+ else {
+ n = ata_scan(drives, rdriveno, rdrives);
+ path = "/dev/sda";
+ }
nmax = 10;
}
else if (!strcmp(type, "SCSI")) {
- // bit i set => drive with ID (i & 0x7) on adapter (i >> 3) present
- n = aspi_scan(drives);
- path = "/dev/scsi00";
- nmax = 10*8;
+ if (win9x) {
+ // bit i set => drive with ID (i & 0x7) on adapter (i >> 3) present
+ n = aspi_scan(drives);
+ path = "/dev/scsi00";
+ nmax = 10*8;
+ }
+ else {
+ // bit i set => drive i present
+ n = spt_scan(drives);
+ path = "/dev/sda";
+ nmax = 10;
+ }
}
else
return -1;
if (!(rdrives[ci] & (1L << pi)))
continue;
char rpath[20];
- sprintf(rpath, "/dev/hd%c,%u", 'a'+j, pi);
+ sprintf(rpath, "/dev/sd%c,%u", 'a'+j, pi);
sz = strlen(rpath)+1;
char * s = (char *)malloc(sz); bytes += sz;
strcpy(s, rpath);
// within this file (see os_freebsd.cpp for an example).
int deviceopen(const char * pathname, char *type)
{
- int len;
pathname = skipdev(pathname);
- len = strlen(pathname);
+ int len = strlen(pathname);
if (!strcmp(type, "ATA")) {
- // hd[a-j](:[saic]+)? => ATA 0-9 with options
- char drive[1+1] = "", options[5+1] = ""; int n1 = -1, n2 = -1;
- if ( sscanf(pathname, "hd%1[a-j]%n:%5[saicp]%n", drive, &n1, options, &n2) >= 1
- && ((n1 == len && !options[0]) || n2 == len) ) {
- return ata_open(drive[0] - 'a', options, -1);
+ // [sh]d[a-z](:[saicp]+)? => Physical drive 0-25, with options
+ char drive[1+1] = "", options[7+1] = ""; int n1 = -1, n2 = -1;
+ if ( sscanf(pathname, "%*[sh]d%1[a-z]%n:%6[saicmp]%n", drive, &n1, options, &n2) >= 1
+ && ((n1 == len && !options[0]) || n2 == len) ) {
+ return ata_open(drive[0] - 'a', -1, options, -1);
}
- // hd[a-j],N => Physical drive 0-9, RAID port N
+ // [sh]d[a-z],N(:[saicp]+)? => Physical drive 0-25, RAID port N, with options
drive[0] = 0; options[0] = 0; n1 = -1; n2 = -1;
unsigned port = ~0;
- if ( sscanf(pathname, "hd%1[a-j],%u%n:%5[saicp]%n", drive, &port, &n1, options, &n2) >= 2
- && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) {
- return ata_open(drive[0] - 'a', options, port);
+ if ( sscanf(pathname, "%*[sh]d%1[a-z],%u%n:%7[saicmp3]%n", drive, &port, &n1, options, &n2) >= 2
+ && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) {
+ return ata_open(drive[0] - 'a', -1, options, port);
}
- }
-
- else if (!strcmp(type, "SCSI")) {
- // scsi[0-9][0-f] => SCSI Adapter 0-9, ID 0-15, LUN 0
- unsigned adapter = ~0, id = ~0; int n = -1;
- if (sscanf(pathname,"scsi%1u%1x%n", &adapter, &id, &n) == 2 && n == len) {
+ // pd<m>,N => Physical drive <m>, RAID port N
+ int phydrive = -1; port = ~0; n1 = -1; n2 = -1;
+ if ( sscanf(pathname, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1
+ && phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) {
+ return ata_open(phydrive, -1, "", (int)port);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(pathname);
+ if (logdrive >= 0) {
+ return ata_open(-1, logdrive, "", -1);
+ }
+ // tw_cli/... => Parse tw_cli output
+ if (!strncmp(pathname, "tw_cli/", 7)) {
+ return tw_cli_open(pathname+7);
+ }
+ } else if (!strcmp(type, "SCSI")) {
+ // scsi[0-9][0-f] => ASPI Adapter 0-9, ID 0-15, LUN 0
+ unsigned adapter = ~0, id = ~0; int n1 = -1;
+ if (sscanf(pathname,"scsi%1u%1x%n", &adapter, &id, &n1) == 2 && n1 == len) {
return aspi_open(adapter, id);
}
+ // sd[a-z],N => Physical drive 0-25, RAID port N
+ char drive[1+1] = ""; int sub_addr = -1; n1 = -1; int n2 = -1;
+ if ( sscanf(pathname, "sd%1[a-z]%n,%d%n", drive, &n1, &sub_addr, &n2) >= 1
+ && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0)) ) {
+ return spt_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(pathname, "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 spt_open(pd_num, -1, -1, sub_addr);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(pathname);
+ if (logdrive >= 0) {
+ return spt_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(pathname, "st%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return spt_open(-1, -1, tape_num, -1);
+ }
+ tape_num = -1; n1 = -1;
+ if (sscanf(pathname, "nst%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return spt_open(-1, -1, tape_num, -1);
+ }
+ // tape<m> => tape drive <m>
+ tape_num = -1; n1 = -1;
+ if (sscanf(pathname, "tape%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return spt_open(-1, -1, tape_num, -1);
+ }
}
errno = EINVAL;
// (Never called in smartctl!)
int deviceclose(int fd)
{
- if ((fd & 0xff00) != 0x0100) {
- ata_close(fd);
- }
- else {
+ if ((fd & 0xff00) == ASPI_FDOFFSET)
aspi_close(fd);
- }
+ else if (fd >= SPT_FDOFFSET)
+ spt_close(fd);
+ else if (fd == TW_CLI_FDOFFSET)
+ tw_cli_close();
+ else
+ ata_close(fd);
return 0;
}
#endif
" smartctl -a /dev/scsi21\n"
" (Prints all information for SCSI disk on ASPI adapter 2, ID 1)\n"
+ " 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"
"\n"
" ATA SMART access methods and ordering may be specified by modifiers\n"
- " following the device name: /dev/hdX:[saic], where\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"
+ " 'i': IOCTL_IDE_PASS_THROUGH, 'c': ATA via IOCTL_SCSI_PASS_THROUGH,\n"
+ " 'm': IOCTL_SCSI_MINIPORT_*.\n"
" The default on this system is /dev/hdX:%s\n", ata_get_def_options()
);
}
// SMART_* IOCTLs, also known as DFP_* (Disk Fault Protection)
-// Deklarations from:
-// http://cvs.sourceforge.net/viewcvs.py/mingw/w32api/include/ddk/ntdddisk.h?rev=1.3
-
#define FILE_READ_ACCESS 0x0001
#define FILE_WRITE_ACCESS 0x0002
#define METHOD_BUFFERED 0
static void print_ide_regs(const IDEREGS * r, int out)
{
- pout("%s=0x%02x,%s=0x%02x, SC=0x%02x, NS=0x%02x, CL=0x%02x, CH=0x%02x, SEL=0x%02x\n",
+ 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);
}
// call SMART_GET_VERSION, return device map or -1 on error
-static int smart_get_version(HANDLE hdevice, unsigned long * portmap = 0)
+static int smart_get_version(HANDLE hdevice, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
{
- GETVERSIONOUTPARAMS vers;
+ GETVERSIONOUTPARAMS vers; memset(&vers, 0, sizeof(vers));
const GETVERSIONINPARAMS_EX & vers_ex = (const GETVERSIONINPARAMS_EX &)vers;
DWORD num_out;
- memset(&vers, 0, sizeof(vers));
if (!DeviceIoControl(hdevice, SMART_GET_VERSION,
NULL, 0, &vers, sizeof(vers), &num_out, NULL)) {
if (con->reportataioctl)
}
assert(num_out == sizeof(GETVERSIONOUTPARAMS));
- if (portmap) {
- // Return bitmask of valid RAID ports
- if (vers_ex.wIdentifier != SMART_VENDOR_3WARE) {
- pout(" SMART_GET_VERSION returns unknown Identifier = %04x\n"
- " This is no 3ware 9000 controller or driver has no SMART support.\n", vers_ex.wIdentifier);
- errno = ENOENT;
- return -1;
- }
- *portmap = vers_ex.dwDeviceMapEx;
- }
-
if (con->reportataioctl > 1) {
pout(" SMART_GET_VERSION suceeded, bytes returned: %lu\n"
" Vers = %d.%d, Caps = 0x%lx, DeviceMap = 0x%02x\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;
}
}
else {
code = SMART_SEND_DRIVE_COMMAND; name = "SMART_SEND_DRIVE_COMMAND";
- if (regs->bFeaturesReg == ATA_SMART_STATUS) {
+ if (regs->bFeaturesReg == ATA_SMART_STATUS)
size_out = sizeof(IDEREGS); // ioctl returns new IDEREGS as data
// Note: cBufferSize must be 0 on Win9x
- inpar.cBufferSize = size_out;
- }
else
size_out = 0;
}
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*/ ? ENOSYS : EIO);
+ 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
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;
print_ide_regs_io(regs, NULL);
}
VirtualFree(buf, 0, MEM_RELEASE);
- errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
return -1;
}
UCHAR ucDataBuf[512];
} ATA_PASS_THROUGH_EX_WITH_BUFFERS;
- ATA_PASS_THROUGH_EX_WITH_BUFFERS ab;
- IDEREGS * ctfregs;
- unsigned int size;
- DWORD num_out;
const unsigned char magic = 0xcf;
- memset(&ab, 0, sizeof(ab));
+ 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;
- size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf);
+ unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf);
ab.apt.DataBufferOffset = size;
- if (datasize) {
- if (!(0 <= datasize && datasize <= (int)sizeof(ab.ucDataBuf))) {
+ if (datasize > 0) {
+ if (datasize > (int)sizeof(ab.ucDataBuf)) {
errno = EINVAL;
return -1;
}
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 {
- //ab.apt.AtaFlags = 0;
- //ab.apt.DataTransferLength = 0;
+ assert(ab.apt.AtaFlags == 0);
+ assert(ab.apt.DataTransferLength == 0);
}
assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS));
- ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile;
+ IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile;
*ctfregs = *regs;
+ DWORD num_out;
if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH,
&ab, size, &ab, size, &num_out, NULL)) {
long err = GetLastError();
if (con->reportataioctl) {
- pout(" IOCTL_ATA_PASS_THROUGH_EX failed, Error=%ld\n", err);
+ pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err);
print_ide_regs_io(regs, NULL);
}
- errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check ATA status
+ if (ctfregs->bCommandReg/*Status*/ & 0x01) {
+ if (con->reportataioctl) {
+ pout(" IOCTL_ATA_PASS_THROUGH command failed:\n");
+ print_ide_regs_io(regs, ctfregs);
+ }
+ errno = EIO;
return -1;
}
// Check and copy data
- if (datasize) {
+ if (datasize > 0) {
if ( num_out != size
|| (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) {
if (con->reportataioctl) {
- pout(" IOCTL_ATA_PASS_THROUGH_EX output data missing (%lu)\n", num_out);
+ pout(" IOCTL_ATA_PASS_THROUGH output data missing (%lu)\n", num_out);
print_ide_regs_io(regs, ctfregs);
}
errno = EIO;
}
if (con->reportataioctl > 1) {
- pout(" IOCTL_ATA_PASS_THROUGH_EX suceeded, bytes returned: %lu\n", num_out);
+ pout(" IOCTL_ATA_PASS_THROUGH suceeded, bytes returned: %lu\n", num_out);
print_ide_regs_io(regs, ctfregs);
}
*regs = *ctfregs;
// ATA PASS THROUGH via SCSI PASS THROUGH (WinNT4 only)
-// Declarations from:
-// http://cvs.sourceforge.net/viewcvs.py/mingw/w32api/include/ddk/ntddscsi.h?rev=1.2
-
#define IOCTL_SCSI_PASS_THROUGH \
CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
long err = GetLastError();
if (con->reportataioctl)
pout(" ATA via IOCTL_SCSI_PASS_THROUGH failed, Error=%ld\n", err);
- errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
return -1;
}
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)
+
+#define IOCTL_SCSI_MINIPORT \
+ CTL_CODE(IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+ASSERT_CONST(IOCTL_SCSI_MINIPORT, 0x04d008);
+
+typedef struct _SRB_IO_CONTROL {
+ ULONG HeaderLength;
+ UCHAR Signature[8];
+ ULONG Timeout;
+ ULONG ControlCode;
+ ULONG ReturnCode;
+ ULONG Length;
+} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
+
+ASSERT_SIZEOF(SRB_IO_CONTROL, 28);
+
+#define FILE_DEVICE_SCSI 0x001b
+
+#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)
+
+/////////////////////////////////////////////////////////////////////////////
+
+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 (con->reportataioctl) {
+ 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 (con->reportataioctl) {
+ 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 (con->reportataioctl) {
+ 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 (con->reportataioctl > 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)
+ *regs = *(const IDEREGS *)(sb.params.out.bBuffer);
+
+ 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 (con->reportataioctl) {
+ 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 (con->reportataioctl) {
+ 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 (con->reportataioctl > 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 (con->reportataioctl)
+ 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 (con->reportataioctl)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08lx\n", srbc.ReturnCode);
+ errno = EIO;
+ return -1;
+ }
+ if (con->reportataioctl > 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];
+}
+
+
+static ata_identify_device * tw_cli_identbuf = 0;
+static ata_smart_values * tw_cli_smartbuf = 0;
+
+static int tw_cli_open(const char * name)
+{
+ // Read tw_cli or 3DM browser output into buffer
+ char buffer[4096];
+ int size = -1, n1 = -1;
+ if (!strcmp(name, "clip")) { // tw_cli/clip => read clipboard
+ size = get_clipboard(buffer, sizeof(buffer));
+ }
+ else if (!strcmp(name, "stdin")) { // tw_cli/stdin => read stdin
+ size = fread(buffer, 1, sizeof(buffer), stdin);
+ }
+ else if (sscanf(name, "c%*u/p%*u%n", &n1) >= 0 && n1 == (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);
+ if (con->reportataioctl > 1)
+ pout("tw_cli/%s: Run: \"%s\"\n", name, cmd);
+ size = run_cmd(cmd, buffer, sizeof(buffer));
+ }
+ else {
+ errno = EINVAL; return -1;
+ }
+
+ if (con->reportataioctl > 1)
+ pout("tw_cli/%s: Read %d bytes\n", name, size);
+ if (size <= 0) {
+ errno = ENOENT; return -1;
+ }
+ if (size >= (int)sizeof(buffer)) {
+ errno = EIO; return -1;
+ }
+ buffer[size] = 0;
+ if (con->reportataioctl > 1)
+ pout("[\n%.100s%s\n]\n", buffer, (size>100?"...":""));
+
+ // Fake identify sector
+ ASSERT_SIZEOF(ata_identify_device, 512);
+ ata_identify_device * id = (ata_identify_device *)malloc(sizeof(ata_identify_device));
+ 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->major_rev_num = 0x1<<3; // ATA-3
+ 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, "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 *)malloc(512);
+ 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) {
+ free(sd);
+ if (!id->model[1]) {
+ // No useful data found
+ free(id);
+ char * err = strstr(buffer, "Error:");
+ if (!err)
+ err = strstr(buffer, "error :");
+ if (err) {
+ // Print tw_cli error message
+ err[strcspn(err, "\r\n")] = 0;
+ pout("%s\n", err);
+ }
+ errno = EIO;
+ return -1;
+ }
+ sd = 0;
+ }
+
+ tw_cli_identbuf = id;
+ tw_cli_smartbuf = (ata_smart_values *)sd;
+ return TW_CLI_FDOFFSET;
+}
+
+
+static void tw_cli_close()
+{
+ if (tw_cli_identbuf) {
+ free(tw_cli_identbuf); tw_cli_identbuf = 0;
+ }
+ if (tw_cli_smartbuf) {
+ free(tw_cli_smartbuf); tw_cli_smartbuf = 0;
+ }
+}
+
+
+static int tw_cli_command_interface(smart_command_set command, int /*select*/, char * data)
+{
+ switch (command) {
+ case IDENTIFY:
+ if (!tw_cli_identbuf)
+ break;
+ memcpy(data, tw_cli_identbuf, 512);
+ return 0;
+ case READ_VALUES:
+ if (!tw_cli_smartbuf)
+ break;
+ memcpy(data, tw_cli_smartbuf, 512);
+ return 0;
+ case READ_THRESHOLDS:
+ if (!tw_cli_smartbuf)
+ break;
+ // Fake zero thresholds
+ {
+ const ata_smart_values * sv = tw_cli_smartbuf;
+ ata_smart_thresholds_pvt * tr = (ata_smart_thresholds_pvt *)data;
+ memset(tr, 0, 512);
+ // TODO: Indicate missing thresholds in ataprint.cpp:PrintSmartAttribWithThres()
+ // (ATA_SMART_READ_THRESHOLDS is marked obsolete since ATA-5)
+ for (int i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++)
+ tr->chksum -= tr->thres_entries[i].id = sv->vendor_attributes[i].id;
+ }
+ return 0;
+ case ENABLE:
+ case STATUS:
+ case STATUS_CHECK: // Fake "good" SMART status
+ return 0;
+ default:
+ break;
+ }
+ // Arrive here for all unsupported commands
+ errno = ENOSYS;
+ return -1;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+#define FILE_DEVICE_MASS_STORAGE 0x0000002d
+#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE
+#define FILE_ANY_ACCESS 0
+
+#define IOCTL_STORAGE_QUERY_PROPERTY \
+ CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+typedef enum _STORAGE_BUS_TYPE {
+ BusTypeUnknown = 0x00,
+ BusTypeScsi = 0x01,
+ BusTypeAtapi = 0x02,
+ BusTypeAta = 0x03,
+ BusType1394 = 0x04,
+ BusTypeSsa = 0x05,
+ BusTypeFibre = 0x06,
+ BusTypeUsb = 0x07,
+ BusTypeRAID = 0x08,
+ BusTypeiScsi = 0x09,
+ BusTypeSas = 0x0A,
+ BusTypeSata = 0x0B,
+ BusTypeSd = 0x0C,
+ BusTypeMmc = 0x0D,
+ BusTypeMax = 0x0E,
+ BusTypeMaxReserved = 0x7F
+} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
+
+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, *PSTORAGE_DEVICE_DESCRIPTOR;
+
+typedef enum _STORAGE_QUERY_TYPE {
+ PropertyStandardQuery = 0,
+ PropertyExistsQuery,
+ PropertyMaskQuery,
+ PropertyQueryMaxDefined
+} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
+
+typedef enum _STORAGE_PROPERTY_ID {
+ StorageDeviceProperty = 0,
+ StorageAdapterProperty,
+ StorageDeviceIdProperty,
+ StorageDeviceUniqueIdProperty,
+ StorageDeviceWriteCacheProperty,
+ StorageMiniportProperty,
+ StorageAccessAlignmentProperty
+} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
+
+typedef struct _STORAGE_PROPERTY_QUERY {
+ STORAGE_PROPERTY_ID PropertyId;
+ STORAGE_QUERY_TYPE QueryType;
+ UCHAR AdditionalParameters[1];
+} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Return STORAGE_BUS_TYPE for device, BusTypeUnknown on error.
+// (HANDLE does not need any access rights, therefore this works
+// without admin rights)
+
+static STORAGE_BUS_TYPE ioctl_get_storage_bus_type(HANDLE hdevice)
+{
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, 0};
+
+ union {
+ STORAGE_DEVICE_DESCRIPTOR dev;
+ char raw[256];
+ } prop;
+ memset(&prop, 0, sizeof(prop));
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), &prop, sizeof(prop), &num_out, NULL)) {
+ if (con->reportataioctl > 1 || con->reportscsiioctl > 1)
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%ld\n", GetLastError());
+ return BusTypeUnknown;
+ }
+
+ if (con->reportataioctl > 1 || con->reportscsiioctl > 1) {
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n"
+ " Vendor: \"%s\"\n"
+ " Product: \"%s\"\n"
+ " Revision: \"%s\"\n"
+ " Removable: %s\n"
+ " BusType: 0x%02x\n",
+ (prop.dev.VendorIdOffset ? prop.raw+prop.dev.VendorIdOffset : ""),
+ (prop.dev.ProductIdOffset ? prop.raw+prop.dev.ProductIdOffset : ""),
+ (prop.dev.ProductRevisionOffset ? prop.raw+prop.dev.ProductRevisionOffset : ""),
+ (prop.dev.RemovableMedia? "Yes":"No"), prop.dev.BusType
+ );
+ }
+ return prop.dev.BusType;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// get CONTROLLER_* for open handle
+static int get_controller_type(HANDLE hdevice, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
+{
+ // Try SMART_GET_VERSION first to detect ATA SMART support
+ // for drivers reporting BusTypeScsi (3ware)
+ if (smart_get_version(hdevice, ata_version_ex) >= 0)
+ return CONTROLLER_ATA;
+
+ STORAGE_BUS_TYPE type = ioctl_get_storage_bus_type(hdevice);
+ switch (type) {
+ case BusTypeAta:
+ case BusTypeSata:
+ if (ata_version_ex)
+ memset(ata_version_ex, 0, sizeof(*ata_version_ex));
+ return CONTROLLER_ATA;
+ case BusTypeScsi:
+ case BusTypeiScsi:
+ case BusTypeSas:
+ return CONTROLLER_SCSI;
+ default:
+ return CONTROLLER_UNKNOWN;
+ }
+ /*NOTREACHED*/
+}
+
+// get CONTROLLER_* for device path
+static int get_controller_type(const char * path, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
+{
+ HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ return CONTROLLER_UNKNOWN;
+ if (con->reportataioctl > 1 || con->reportscsiioctl > 1)
+ pout(" %s: successfully opened\n", path);
+ int type = get_controller_type(h, ata_version_ex);
+ CloseHandle(h);
+ return type;
+}
+
+// get CONTROLLER_* for physical drive number
+static int 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 int get_phy_drive_type(int drive)
+{
+ return get_phy_drive_type(drive, 0);
+}
+
+// get CONTROLLER_* for logical drive number
+static int get_log_drive_type(int drive)
+{
+ char path[30];
+ snprintf(path, sizeof(path)-1, "\\\\.\\%c:", 'A'+drive);
+ return get_controller_type(path);
+}
+
+
/////////////////////////////////////////////////////////////////////////////
// Call GetDevicePowerState() if available (Win98/ME/2000/XP/2003)
// TODO: Put in a struct indexed by fd (or better a C++ object of course ;-)
static HANDLE h_ata_ioctl = 0;
static const char * ata_def_options;
-static char * ata_cur_options;
+static char * ata_usr_options;
+const int max_ata_driveno = 25;
static int ata_driveno; // Drive number
-static char ata_smartver_state[10]; // SMART_GET_VERSION: 0=unknown, 1=OK, 2=failed
+static int ata_driveno_is_log = -1; // 0=physical drivenumber, 1=logical drive number, -1=unknown
+static char ata_smartver_state[max_ata_driveno+1]; // SMART_GET_VERSION: 0=unknown, 1=OK, 2=failed
// Print SMARTVSD error message, return errno
else if ((ver & 0xff) == 4) // WinNT4
return "sc"; // SMART_*, SCSI_PASS_THROUGH
else // WinXP, 2003, Vista
- return "psai"; // GetDevicePowerState(), SMART_*, ATA_, IDE_PASS_THROUGH
+ return "psaim"; // GetDevicePowerState(), SMART_*, ATA_, IDE_PASS_THROUGH, SCSI_MINIPORT_*
}
// Open ATA device
-static int ata_open(int drive, const char * options, int port)
+static int ata_open(int phydrive, int logdrive, const char * options, int port)
{
- int win9x;
- char devpath[30];
- int devmap;
-
// TODO: This version does not allow to open more than 1 ATA devices
if (h_ata_ioctl) {
errno = ENFILE;
return -1;
}
- win9x = ((GetVersion() & 0x80000000) != 0);
-
- if (!(0 <= drive && drive <= (win9x ? 7 : 9))) {
- errno = ENOENT;
+ // Using both physical and logical drive names (in smartd.conf) not supported yet
+ if (!( ata_driveno_is_log < 0
+ || (phydrive >= 0 && !ata_driveno_is_log)
+ || (logdrive >= 0 && ata_driveno_is_log))) {
+ pout("Using both /dev/hdX and X: is not supported\n");
+ errno = EINVAL;
return -1;
}
// path depends on Windows Version
- if (win9x)
+ bool win9x = is_win9x();
+ char devpath[30];
+ if (win9x && 0 <= phydrive && phydrive <= 7)
// Use patched "smartvse.vxd" for drives 4-7, see INSTALL file for details
- strcpy(devpath, (drive <= 3 ? "\\\\.\\SMARTVSD" : "\\\\.\\SMARTVSE"));
- else
- snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", drive);
+ strcpy(devpath, (phydrive <= 3 ? "\\\\.\\SMARTVSD" : "\\\\.\\SMARTVSE"));
+ else if (!win9x && 0 <= phydrive && phydrive <= max_ata_driveno)
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", phydrive);
+ else if (!win9x && 0 <= logdrive && logdrive <= max_ata_driveno) {
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\%c:", 'A'+logdrive);
+ }
+ else {
+ errno = ENOENT;
+ return -1;
+ }
// Open device
if ((h_ata_ioctl = CreateFileA(devpath,
long err = GetLastError();
pout("Cannot open device %s, Error=%ld\n", devpath, err);
if (err == ERROR_FILE_NOT_FOUND)
- errno = (win9x && drive <= 3 ? smartvsd_error() : ENOENT);
+ errno = (win9x && phydrive <= 3 ? smartvsd_error() : ENOENT);
else if (err == ERROR_ACCESS_DENIED) {
if (!win9x)
pout("Administrator rights are necessary to access physical drives.\n");
if (con->reportataioctl > 1)
pout("%s: successfully opened\n", devpath);
- // Save options
- if (!*options) {
- // Set default options according to Windows version
- if (!ata_def_options)
- ata_def_options = ata_get_def_options();
- options = (port < 0 ? ata_def_options : "s"); // RAID: SMART_* only
- }
- ata_cur_options = strdup(options);
+ // Set default options according to Windows version
+ if (!ata_def_options)
+ ata_def_options = ata_get_def_options();
+ // Save user options
+ if (port >= 0 && !*options)
+ options = "s3"; // RAID: SMART_* and SCSI_MINIPORT
+ assert(!ata_usr_options);
+ if (*options)
+ ata_usr_options = strdup(options);
// NT4/2000/XP: SMART_GET_VERSION may spin up disk, so delay until first real SMART_* call
- ata_driveno = drive;
+ if (phydrive >= 0) {
+ ata_driveno = phydrive; ata_driveno_is_log = 0;
+ }
+ else {
+ assert(logdrive >= 0);
+ ata_driveno = logdrive; ata_driveno_is_log = 1;
+ }
if (!win9x && port < 0)
return 0;
// Win9X/ME: Get drive map
// RAID: Get port map
+ GETVERSIONINPARAMS_EX vers_ex;
+ int devmap = smart_get_version(h_ata_ioctl, (port >= 0 ? &vers_ex : 0));
+
unsigned long portmap = 0;
- devmap = smart_get_version(h_ata_ioctl, (port >= 0 ? &portmap : 0));
+ if (port >= 0 && devmap >= 0) {
+ // 3ware RAID: check vendor id
+ if (vers_ex.wIdentifier != SMART_VENDOR_3WARE) {
+ pout("SMART_GET_VERSION returns unknown Identifier = %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()) {
ata_close(0);
errno = ENOSYS;
}
devmap = 0x0f;
}
- ata_smartver_state[drive] = 1;
+ ata_smartver_state[ata_driveno] = 1;
if (port >= 0) {
- // RAID: Check port existence
+ // 3ware RAID: update devicemap first
+ if (!update_3ware_devicemap_ioctl(h_ata_ioctl)) {
+ if ( smart_get_version(h_ata_ioctl, &vers_ex) >= 0
+ && vers_ex.wIdentifier == SMART_VENDOR_3WARE )
+ portmap = vers_ex.dwDeviceMapEx;
+ }
+ // Check port existence
if (!(portmap & (1L << port))) {
pout("%s: Port %d is empty or does not exist\n", devpath, port);
if (!is_permissive()) {
}
}
// Encode port into pseudo fd
- return (0x0200 | port);
+ return (ATARAID_FDOFFSET | port);
}
// Win9x/ME: Check device presence & type
- if (((devmap >> (drive & 0x3)) & 0x11) != 0x01) {
- unsigned char atapi = (devmap >> (drive & 0x3)) & 0x10;
+ if (((devmap >> (ata_driveno & 0x3)) & 0x11) != 0x01) {
+ unsigned char atapi = (devmap >> (ata_driveno & 0x3)) & 0x10;
pout("%s: Drive %d %s (IDEDeviceMap=0x%02x).\n", devpath,
- drive, (atapi?"is an ATAPI device":"does not exist"), devmap);
+ ata_driveno, (atapi?"is an ATAPI device":"does not exist"), devmap);
// 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)
}
}
// Use drive number as fd for ioctl
- return (drive & 0x3);
+ return (ata_driveno & 0x3);
}
-static void ata_close(int fd)
+static void ata_close(int /*fd*/)
{
- ARGUSED(fd);
CloseHandle(h_ata_ioctl);
h_ata_ioctl = 0;
- if (ata_cur_options) {
- free(ata_cur_options);
- ata_cur_options = 0;
+ if (ata_usr_options) {
+ free(ata_usr_options);
+ ata_usr_options = 0;
}
}
-// Scan for ATA drives, fill bitmask of drives present, return #drives
+// Scan for ATA drives on Win9x/ME, fill bitmask of drives present, return #drives
-static int ata_scan(unsigned long * drives, int * rdriveno, unsigned long * rdrives)
+static int ata_scan_win9x(unsigned long * drives)
{
- int win9x = ((GetVersion() & 0x80000000) != 0);
- int cnt = 0, i;
-
- for (i = 0; i <= 9; i++) {
- char devpath[30];
- GETVERSIONOUTPARAMS vers;
- const GETVERSIONINPARAMS_EX & vers_ex = (const GETVERSIONINPARAMS_EX &)vers;
- DWORD num_out;
- HANDLE h;
- if (win9x)
- strcpy(devpath, "\\\\.\\SMARTVSD");
- else
- snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", i);
-
- // Open device
- if ((h = CreateFileA(devpath,
- GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
- NULL, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) {
- if (con->reportataioctl > 1)
- pout(" %s: Open failed, Error=%ld\n", devpath, GetLastError());
- if (win9x)
- break; // SMARTVSD.VXD missing or no ATA devices
- continue; // Disk not found or access denied (break;?)
- }
-
- // Get drive map
- memset(&vers, 0, sizeof(vers));
- if (!DeviceIoControl(h, SMART_GET_VERSION,
- NULL, 0, &vers, sizeof(vers), &num_out, NULL)) {
- if (con->reportataioctl)
- pout(" %s: SMART_GET_VERSION failed, Error=%ld\n", devpath, GetLastError());
- CloseHandle(h);
- if (win9x)
- break; // Should not happen
- continue; // Non ATA disk or no SMART ioctl support (possibly SCSI disk)
- }
- CloseHandle(h);
+ // 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 (con->reportataioctl > 1)
+ pout(" %s: Open failed, Error=%ld\n", devpath, GetLastError());
+ return 0; // SMARTVSD.VXD missing or no ATA devices
+ }
+
+ // Get drive map
+ int devmap = smart_get_version(h);
+ CloseHandle(h);
+ if (devmap < 0)
+ return 0; // Should not happen
+
+ // Check ATA device presence, remove ATAPI devices
+ drives[0] = (devmap & 0xf) & ~((devmap >> 4) & 0xf);
+ return (drives[0]&1) + ((drives[0]>>1)&1) + ((drives[0]>>2)&1) + ((drives[0]>>3)&1);
+}
- if (con->reportataioctl) {
- pout(" %s: SMART_GET_VERSION (%ld bytes):\n"
- " Vers = %d.%d, Caps = 0x%lx, DeviceMap = 0x%02x\n",
- devpath, 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 (win9x) {
- // Check ATA device presence, remove ATAPI devices
- drives[0] = (vers.bIDEDeviceMap & 0xf) & ~((vers.bIDEDeviceMap >> 4) & 0xf);
- cnt = (drives[0]&1) + ((drives[0]>>1)&1) + ((drives[0]>>2)&1) + ((drives[0]>>3)&1);
- break;
- }
+// Scan for ATA drives, fill bitmask of drives present, return #drives
+static int ata_scan(unsigned long * drives, int * rdriveno, unsigned long * rdrives)
+{
+ int cnt = 0;
+ for (int i = 0; i <= 9; i++) {
+ GETVERSIONINPARAMS_EX vers_ex;
+ if (get_phy_drive_type(i, &vers_ex) != CONTROLLER_ATA)
+ continue;
+
+ // Interpret RAID drive map if present
if (vers_ex.wIdentifier == SMART_VENDOR_3WARE) {
// Skip if more than 2 controllers or logical drive from this controller already seen
if (vers_ex.wControllerId >= 2 || rdriveno[vers_ex.wControllerId] >= 0)
cnt += pcnt-1;
}
- // ATA drive exists and driver supports SMART ioctl
+ // Driver supports SMART_GET_VERSION or STORAGE_QUERY_PROPERTY returns ATA/SATA
drives[0] |= (1L << i);
cnt++;
}
// Interface to ATA devices. See os_linux.c
int ata_command_interface(int fd, smart_command_set command, int select, char * data)
{
- IDEREGS regs;
- int datasize;
- const char * valid_options;
- int i;
+ if (fd == TW_CLI_FDOFFSET) // Parse tw_cli output
+ return tw_cli_command_interface(command, select, data);
int port = -1;
- if ((fd & ~0x1f) == 0x0200) {
+ if ((fd & ~0x1f) == ATARAID_FDOFFSET) {
// RAID Port encoded into pseudo fd
port = fd & 0x1f;
fd = 0;
}
// CMD,CYL default to SMART, changed by P?IDENTIFY and CHECK_POWER_MODE
- memset(®s, 0, sizeof(regs));
+ IDEREGS regs; memset(®s, 0, sizeof(regs));
regs.bCommandReg = ATA_SMART_CMD;
regs.bCylHighReg = SMART_CYL_HI; regs.bCylLowReg = SMART_CYL_LOW;
- datasize = 0;
+ int datasize = 0;
- // Try all IOCTLS by default: SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH
- valid_options = "saic";
+ // Try by default: SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH,
+ // and SCSI_MINIPORT_* if requested by user
+ const char * valid_options = (ata_usr_options ? "saicm" : "saic");
switch (command) {
- case WRITE_LOG:
- // TODO. Requires DATA OUT support
- errno = ENOSYS;
- return -1;
case CHECK_POWER_MODE:
// Not a SMART command, needs IDE register return
regs.bCommandReg = ATA_CHECK_POWER_MODE;
regs.bCylLowReg = regs.bCylHighReg = 0;
- valid_options = "pai"; // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk
+ valid_options = "pai3"; // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk
// Note: returns SectorCountReg in data[0]
break;
case READ_VALUES:
regs.bFeaturesReg = ATA_SMART_READ_LOG_SECTOR;
regs.bSectorNumberReg = select;
regs.bSectorCountReg = 1;
- // Note: SMART_RCV_DRIVE_DATA supports this only on Win9x/ME
+ // SMART_RCV_DRIVE_DATA supports this only on Win9x/ME
+ // Try SCSI_MINIPORT also to skip buggy class driver
+ valid_options = (ata_usr_options || is_win9x() ? "saicm3" : "aicm3");
datasize = 512;
break;
+ case WRITE_LOG:
+ regs.bFeaturesReg = ATA_SMART_WRITE_LOG_SECTOR;
+ regs.bSectorNumberReg = select;
+ regs.bSectorCountReg = 1;
+ // ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT
+ // but SCSI_MINIPORT_* only if requested by user
+ valid_options = (ata_usr_options ? "am" : "a");
+ datasize = -512; // DATA_OUT!
+ break;
case IDENTIFY:
// Note: WinNT4/2000/XP return identify data cached during boot
// (true for SMART_RCV_DRIVE_DATA and IOCTL_IDE_PASS_THROUGH)
regs.bSectorNumberReg = 1;
break;
case STATUS_CHECK:
- valid_options = "sai"; // Needs IDE register return
+ // Requires CL,CH register return
+ valid_options = (ata_usr_options ? "saim" : "sai");
case STATUS:
regs.bFeaturesReg = ATA_SMART_STATUS;
break;
case IMMEDIATE_OFFLINE:
regs.bFeaturesReg = ATA_SMART_IMMEDIATE_OFFLINE;
regs.bSectorNumberReg = select;
- // Note: SMART_SEND_DRIVE_COMMAND supports ABORT_SELF_TEST only on Win9x/ME
+ // SMART_SEND_DRIVE_COMMAND supports ABORT_SELF_TEST only on Win9x/ME
+ valid_options = (ata_usr_options || select != 127/*ABORT*/ || is_win9x() ?
+ "saicm3" : "aicm3");
break;
default:
pout("Unrecognized command %d in win32_ata_command_interface()\n"
}
// Try all valid ioctls in the order specified in dev_ioctls;
- for (i = 0; ; i++) {
- char opt = ata_cur_options[i];
- int rc;
+ bool powered_up = false;
+ assert(ata_def_options);
+ const char * options = (ata_usr_options ? ata_usr_options : ata_def_options);
+ for (int i = 0; ; i++) {
+ char opt = options[i];
if (!opt) {
+ if (command == 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;
+ break;
+ }
// No IOCTL found
errno = ENOSYS;
return -1;
continue;
errno = 0;
- assert(datasize == 0 || datasize == 512);
+ assert(datasize == 0 || datasize == 512 || (strchr("am", opt) && datasize == -512));
+ int rc;
switch (opt) {
default: assert(0);
case 's':
assert(port == -1);
if (smart_get_version(h_ata_ioctl) < 0) {
if (!con->permissive) {
- pout("ATA/SATA driver is possibly a SCSI class driver not supporting SMART.\n");
- pout("If this is a SCSI disk, try \"scsi<adapter><id>\".\n");
+ pout("ATA/SATA driver is possibly a SCSI driver not supporting SMART.\n");
+ pout("If this is a SCSI disk, please try adding '-d scsi'.\n");
ata_smartver_state[ata_driveno] = 2;
rc = -1; errno = ENOSYS;
break;
}
rc = smart_ioctl(h_ata_ioctl, fd, ®s, data, datasize, port);
break;
+ case 'm':
+ rc = ata_via_scsi_miniport_smart_ioctl(h_ata_ioctl, ®s, data, datasize);
+ break;
case 'a':
rc = ata_pass_through_ioctl(h_ata_ioctl, ®s, data, datasize);
break;
case 'c':
rc = ata_via_scsi_pass_through_ioctl(h_ata_ioctl, ®s, data, datasize);
break;
+ case '3':
+ rc = ata_via_3ware_miniport_ioctl(h_ata_ioctl, ®s, data, datasize, port);
+ break;
case 'p':
assert(command == CHECK_POWER_MODE && datasize == 0);
rc = get_device_power_state(h_ata_ioctl);
- if (rc >= 0) { // Simulate ATA command result
- regs.bSectorCountReg = (rc ? 0xff/*ACTIVE/IDLE*/ : 0x00/*STANDBY*/);
- rc = 0;
+ if (rc == 0) {
+ // Power down reported by GetDevicePowerState(), using a passthrough ioctl would
+ // spin up the drive => simulate ATA result STANDBY.
+ regs.bSectorCountReg = 0x00;
+ }
+ 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;
+ errno = ENOSYS; rc = -1;
}
break;
}
int ata_identify_is_cached(int fd)
{
// Not RAID and WinNT4/2000/XP => true, RAID or Win9x/ME => false
- return (!(fd & 0xff00) && (GetVersion() & 0x80000000) == 0);
+ return (!(fd & 0xff00) && !is_win9x());
}
}
// Interface to ATA devices behind 3ware escalade RAID controller cards. See os_linux.c
-int escalade_command_interface(int fd, int disknum, int escalade_type, smart_command_set command, int select, char *data)
+int escalade_command_interface(int /*fd*/, int disknum, int /*escalade_type*/, smart_command_set /*command*/, int /*select*/, char * /*data*/)
{
static int warned = 0;
- ARGUSED(fd); ARGUSED(escalade_type); ARGUSED(command); ARGUSED(select); ARGUSED(data);
if (!warned) {
pout("Option '-d 3ware,%d' does not work on Windows.\n"
"Controller port can be specified in the device name: '/dev/hd%c,%d'.\n\n",
}
// Interface to ATA devices behind Marvell chip-set based controllers. See os_linux.c
-int marvell_command_interface(int fd, smart_command_set command, int select, char * data)
+int marvell_command_interface(int /*fd*/, smart_command_set /*command*/, int /*select*/, char * /*data*/)
{
static int warned = 0;
- ARGUSED(fd); ARGUSED(command); ARGUSED(select); ARGUSED(data);
pr_not_impl("Marvell chip-set command routine marvell_command_interface()", &warned);
errno = ENOSYS;
return -1;
}
// Interface to ATA devices behind HighPoint Raid controllers. See os_linux.c
-int highpoint_command_interface(int fd, smart_command_set command, int select, char * data)
+int highpoint_command_interface(int /*fd*/, smart_command_set /*command*/, int /*select*/, char * /*data*/)
{
static int warned = 0;
- ARGUSED(fd); ARGUSED(command); ARGUSED(select); ARGUSED(data);
pr_not_impl("HighPoint raid controller command routine highpoint_command_interface()", &warned);
errno = ENOSYS;
return -1;
/////////////////////////////////////////////////////////////////////////////
-// ASPI Interface
+// ASPI Interface (for SCSI devices)
/////////////////////////////////////////////////////////////////////////////
#pragma pack(1)
else if (con->reportscsiioctl)
pout("ASPI Adapter %u, ID %u: Device Type=0x%02x\n", adapter, id, srb.t.devtype);
- return (0x0100 | ((adapter & 0xf)<<4) | (id & 0xf));
+ return (ASPI_FDOFFSET | ((adapter & 0xf)<<4) | (id & 0xf));
}
-static void aspi_close(int fd)
+static void aspi_close(int /*fd*/)
{
// No FreeLibrary(h_aspi_dll) to prevent problems with ASPI threads
- ARGUSED(fd);
}
/////////////////////////////////////////////////////////////////////////////
-// Interface to SCSI devices. See os_linux.c
-int do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report)
+// Interface to ASPI SCSI devices. See scsicmds.h and os_linux.c
+static int do_aspi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report)
{
ASPI_SRB srb;
if (!aspi_entry_valid())
return -EBADF;
- if (!((fd & ~0xff) == 0x100))
+ if (!((fd & ~0xff) == ASPI_FDOFFSET))
return -EBADF;
if (!(iop->cmnd_len == 6 || iop->cmnd_len == 10 || iop->cmnd_len == 12 || iop->cmnd_len == 16)) {
- pout("do_scsi_cmnd_io: bad CDB length\n");
+ pout("do_aspi_cmnd_io: bad CDB length\n");
return -EINVAL;
}
srb.i.data_addr = iop->dxferp;
break;
default:
- pout("do_scsi_cmnd_io: bad dxfer_dir\n");
+ pout("do_aspi_cmnd_io: bad dxfer_dir\n");
return -EINVAL;
}
return 0;
}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// SPT Interface (for SCSI devices and ATA devices behind SATLs)
+// Only supported in NT and later
+/////////////////////////////////////////////////////////////////////////////
+
+#define SPT_MAXDEV 64
+
+struct spt_dev_info {
+ HANDLE h_spt_ioctl;
+ int sub_addr; // addressing disks within a RAID, for example
+};
+
+// Private table of open devices: guaranteed zero on startup since
+// part of static data.
+static struct spt_dev_info * spt_dev_arr[SPT_MAXDEV];
+
+
+static int spt_open(int pd_num, int ld_num, int tape_num, int sub_addr)
+{
+ int k;
+ struct spt_dev_info * sdip;
+ char b[128];
+ HANDLE h;
+
+ for (k = 0; k < SPT_MAXDEV; k++)
+ if (! spt_dev_arr[k])
+ break;
+
+ // If no free entry found, return error. We have max allowed number
+ // of "file descriptors" already allocated.
+ if (k == SPT_MAXDEV) {
+ if (con->reportscsiioctl)
+ pout("spt_open: too many open file descriptors (%d)\n",
+ SPT_MAXDEV);
+ errno = EMFILE;
+ return -1;
+ }
+ sdip = (struct spt_dev_info *)malloc(sizeof(struct spt_dev_info));
+ if (NULL == sdip) {
+ errno = ENOMEM;
+ return -1;
+ }
+ spt_dev_arr[k] = sdip;
+ sdip->sub_addr = sub_addr;
+
+ 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 {
+ if (con->reportscsiioctl)
+ pout("spt_open: bad parameters\n");
+ errno = EINVAL;
+ goto err_out;
+ }
+
+ // Open device
+ if ((h = CreateFileA(b, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) {
+ if (con->reportscsiioctl)
+ pout(" %s: Open failed, Error=%ld\n", b, GetLastError());
+ errno = ENODEV;
+ goto err_out;
+ }
+ sdip->h_spt_ioctl = h;
+ return k + SPT_FDOFFSET;
+
+err_out:
+ spt_dev_arr[k] = NULL;
+ free(sdip);
+ return -1;
+}
+
+
+static void spt_close(int fd)
+{
+ struct spt_dev_info * sdip;
+ int index = fd - SPT_FDOFFSET;
+
+ if ((index < 0) || (index >= SPT_MAXDEV)) {
+ if (con->reportscsiioctl)
+ pout("spt_close: bad fd range\n");
+ return;
+ }
+ sdip = spt_dev_arr[index];
+ if (NULL == sdip) {
+ if (con->reportscsiioctl)
+ pout("spt_close: fd already closed\n");
+ return;
+ }
+ free(sdip);
+ spt_dev_arr[index] = NULL;
+}
+
+
+static int spt_scan(unsigned long * drives)
+{
+ int cnt = 0;
+ for (int i = 0; i <= 9; i++) {
+ if (get_phy_drive_type(i) != CONTROLLER_SCSI)
+ continue;
+ // STORAGE_QUERY_PROPERTY returned SCSI/SAS/...
+ drives[0] |= (1L << i);
+ cnt++;
+ }
+ return cnt;
+}
+
+
+#define IOCTL_SCSI_PASS_THROUGH_DIRECT \
+ CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+typedef struct _SCSI_PASS_THROUGH_DIRECT {
+ USHORT Length;
+ UCHAR ScsiStatus;
+ UCHAR PathId;
+ UCHAR TargetId;
+ UCHAR Lun;
+ UCHAR CdbLength;
+ UCHAR SenseInfoLength;
+ UCHAR DataIn;
+ ULONG DataTransferLength;
+ ULONG TimeOutValue;
+ PVOID DataBuffer;
+ ULONG SenseInfoOffset;
+ UCHAR Cdb[16];
+} SCSI_PASS_THROUGH_DIRECT;
+
+typedef struct {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[64];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c
+static int do_spt_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report)
+{
+ struct spt_dev_info * sdip;
+ int index = fd - SPT_FDOFFSET;
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
+ DWORD num_out;
+
+ if ((index < 0) || (index >= SPT_MAXDEV)) {
+ if (report)
+ pout("do_spt_cmnd_io: bad fd range\n");
+ return -EBADF;
+ }
+ sdip = spt_dev_arr[index];
+ if (NULL == sdip) {
+ if (report)
+ pout("do_spt_cmnd_io: fd already closed\n");
+ return -EBADF;
+ }
+
+ 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(buff);
+ }
+ if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
+ if (report)
+ pout("do_spt_cmnd_io: cmnd_len too large\n");
+ return -EINVAL;
+ }
+
+ 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);
+ 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;
+ 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:
+ pout("do_spt_cmnd_io: bad dxfer_dir\n");
+ return -EINVAL;
+ }
+
+ if (! DeviceIoControl(sdip->h_spt_ioctl, IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) {
+ long err = GetLastError();
+
+ if (report)
+ pout(" IOCTL_SCSI_PASS_THROUGH_DIRECT failed, Error=%ld\n", err);
+ return -(err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ }
+
+ 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 ((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;
+}
+
+
+// Decides which SCSI implementation based on pseudo fd.
+// Declaration and explanation in scsicmds.h
+int do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report)
+{
+ if ((fd & ~0xff) == ASPI_FDOFFSET)
+ return do_aspi_cmnd_io(fd, iop, report);
+ else
+ return do_spt_cmnd_io(fd, iop, report);
+}