Link to home
Start Free TrialLog in
Avatar of mollercw
mollercw

asked on

Accessing IDE S.M.A.R.T. values

I'd like to be able to access the local harddrive's IDE S.M.A.R.T. values.

I've been downloading specs and sample code (VC++).

The following resources exist, and I've downloaded them and will start working on them tonight:

http://support.microsoft.com/default.aspx?scid=kb;EN-US;q208048
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q196550
SMART IOCTL API Specification - http://www.microsoft.com/hwdev/archive/download/respec/iocltapi.rtf

Basically, I don't know where to begin (yet) or how to translate VC++ code to VB. 500 points to anyone that can give me a good starting position. I'm developing for Win2K/XP.
Avatar of mirtol
mirtol

It seems fairly straight forward from the docs you've found.

It seems simple... use CreateFile to open the device (filename of "C:"), use DeviceIOControl to your heart's content, then use CloseHandle to free the device.

Here's some translations of functions you'll need: (untested)

Type SECURITY_ATTRIBUTES
   nLength As Long
   lpSecurityDescriptor As Long
   bInheritHandle As Long
End Type


type GETVERSIONOUTPARAMS
    bVersion as byte
    bRevision as byte
    bReserved as byte
    bIDEDeviceMap as byte
    fCapabilities as long
    dwReserved(1 to 4) as long
end type


type IDEREGS
    bFeaturesReg as byte
    bSectorCountReg as byte
    bSectorNumberReg as byte
    bCylLowReg as byte
    bCylHighReg as byte
    bDriveHeadReg as byte
    bCommandReg as byte
    bReserved as byte
end type


type SENDCMDINPARAMS
    cBufferSize as long
    irDriveRegs as IDEREGS
    bDriveNumber as byte
    bReserve as string *3
    dwReserved as string*16
    bBuffer as string * ???? >> you must set this to your max value needed
end type


type DRIVERSTATUS
    bDriverError as byte
    bIDEStatus as byte
    bReserved1 as byte
    bReserved2 as byte
    dwReserved1 as long
    dwReserved2 as long
end type


type SENDCMDOUTPARAMS
    cBufferSize as long
    DriverStatus as DRIVERSTATUS
    bBuffer As byte
end type



Declare Function DeviceIoControl Lib "kernel32" ( _
           ByVal hDevice As Long, _
           ByVal dwIoControlCode As Long, _
           lpInBuffer As Any, _
           ByVal nInBufferSize As Long, _
           lpOutBuffer As Any, _
           ByVal nOutBufferSize As Long, _
           lpBytesReturned As Long, _
           lpOverlapped As Any _
) As Long

Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _
           ByVal lpFileName As String, _
           ByVal dwDesiredAccess As Long, _
           ByVal dwShareMode As Long, _
           lpSecurityAttributes As SECURITY_ATTRIBUTES, _
           ByVal dwCreationDisposition As Long, _
           ByVal dwFlagsAndAttributes As Long, _
           ByVal hTemplateFile As Long _
) As Long

Declare Function CloseHandle Lib "kernel32" ( _
           ByVal hObject As Long _
) As Long

Well, to finsish your job on the translations, mirtol: These is the smart.h untested translation (complete) as found in smartapp.exe from one of the documents:

Option Explicit

'
' Miscellaneous
'
Public Const MAX_IDE_DRIVES = 4
Public Const READ_ATTRIBUTE_BUFFER_SIZE = 512
Public Const IDENTIFY_BUFFER_SIZE = 512
Public Const READ_THRESHOLD_BUFFER_SIZE = 512

'
' IOCTL commands
'
Public Const DFP_GET_VERSION = &H74080
Public Const DFP_SEND_DRIVE_COMMAND = &H7C084
Public Const DFP_RECEIVE_DRIVE_DATA = &H7C088

'
' GETVERSIONOUTPARAMS contains the data returned from the
' Get Driver Version function.
'
Public Type GETVERSIONOUTPARAMS
  bVersion As Byte ' Binary driver version
  bRevision As Byte ' Binary driver revision
  bReserved As Byte ' Not used
  bIDEDeviceMap As Byte ' Bit map of IDE Devices
  fCapabilities As Long ' Bit mask of driver capabilities
  dwReserved(0 To 4) As Long ' For future use
  '  DWORD dwReserved[4];  // For future use. Base 1?
End Type

'
' Bits returned in the fCapabilities member of GETVERSIONOUTPARAMS
'
Public Const CAP_IDE_ID_FUNCTION = 1      ' ATA ID command supported
Public Const CAP_IDE_ATAPI_ID = 2       ' ATAPI ID command supported
Public Const CAP_IDE_EXECUTE_SMART_FUNCTION = 4 ' SMART commannds supported

'
' IDE registers
'
Public Type IDEREGS
  bFeaturesReg As Byte 'Used for specifying SMART "commands".
  bSecorCountReg As Byte 'IDE sector count register
  bSectorNumberReg As Byte ' IDE sector number register
  bCylLowReg As Byte     ' IDE low order cylinder value
  bCylHighReg As Byte    'IDE high order cylinder value
  bDriveHeadReg As Byte    ' IDE drive/head register
  bCommandReg As Byte    ' Actual IDE command.
  bReserved As Byte      ' reserved for future use.  Must be zero.
End Type

'
' SENDCMDINPARAMS contains the input parameters for the
' Send Command to Drive function.
'
Public Type SENDCMDINPARAMS
  cBufferSize As Long        ' Buffer size in bytes
  irDriveRegs IDEREGS        ' Structure with drive register values.
  bDriveNumber As Byte       ' Physical drive number to send
                             ' command to (0,1,2,3).
  bReserved(0 To 3) As Byte  ' Reserved for future expansion.
  dwReserved(0 To 4) As Long ' For future use.
  bBuffer(0 To 1) As Byte    ' Input buffer.
End Type

'
' Valid values for the bCommandReg member of IDEREGS.
'
Public Const IDE_ATAPI_ID = &HA1        ' Returns ID sector for ATAPI.
Public Const IDE_ID_FUNCTION = &HEC       ' Returns ID sector for ATA.
Public Const IDE_EXECUTE_SMART_FUNCTION = &HB0  ' Performs SMART cmd.
                      ' Requires valid bFeaturesReg,
                      ' bCylLowReg, and bCylHighReg
'
' Cylinder register values required when issuing SMART command
'
Public Const SMART_CYL_LOW = &H4F
Public Const SMART_CYL_HI = &HC2

'
' Status returned from driver
Public Type DriverStatus
  bDriverError As Byte   ' Error code from driver,
                ' or 0 if no error.
  bIDEStatus As Byte 'Contents of IDE Error register.
                ' Only valid when bDriverError
                ' is SMART_IDE_ERROR.
  bReserved(0 To 2) As Byte   ' Reserved for future expansion.
  dwReserved(0 To 2) As Long     ' Reserved for future expansion.
End Type

'
' bDriverError values
'
Public Const SMART_NO_ERROR = 0     ' No error
Public Const SMART_IDE_ERROR = 1    ' Error from IDE controller
Public Const SMART_INVALID_FLAG = 2   ' Invalid command flag
Public Const SMART_INVALID_COMMAND = 3 ' Invalid command byte
Public Const SMART_INVALID_BUFFER = 4 ' Bad buffer (null, invalid addr..)
Public Const SMART_INVALID_DRIVE = 5  ' Drive number not valid
Public Const SMART_INVALID_IOCTL = 6  ' Invalid IOCTL
Public Const SMART_ERROR_NO_MEM = 7   ' Could not lock user's buffer
Public Const SMART_INVALID_REGISTER = 8 ' Some IDE Register not valid
Public Const SMART_NOT_SUPPORTED = 9  ' Invalid cmd flag set
Public Const SMART_NO_IDE_DEVICE = 10   ' Cmd issued to device not present
                  'although drive number is valid
' 11-255 reserved

'
' Structure returned by SMART IOCTL for several commands
'
Public Type SENDCMDOUTPARAMS
  cBufferSize As Long    ' Size of bBuffer in bytes
  DriverStatus As DriverStatus    ' Driver status structure.
  bBuffer(0 To 2) As Byte     ' Buffer of arbitrary length in which to store the data read from the drive.
End Type

'
' Feature register defines for SMART "sub commands"
'
Public Const SMART_READ_ATTRIBUTE_VALUES = &HD0       ' ATA4: Renamed
                            ' SMART READ DATA
Public Const SMART_READ_ATTRIBUTE_THRESHOLDS = &HD1     ' Obsoleted in ATA4!
Public Const SMART_ENABLE_DISABLE_ATTRIBUTE_AUTOSAVE = &HD2
Public Const SMART_SAVE_ATTRIBUTE_VALUES = &HD3
Public Const SMART_EXECUTE_OFFLINE_IMMEDIATE = &HD4     ' ATA4
' Vendor specific commands:
Public Const SMART_ENABLE_SMART_OPERATIONS = &HD8
Public Const SMART_DISABLE_SMART_OPERATIONS = &HD9
Public Const SMART_RETURN_SMART_STATUS = &HDA

'
' The following structure defines the structure of a Drive Attribute
'
Public Type DRIVEATTRIBUTE
  bAttrID As Byte    ' Identifies which attribute
  wStatusFlags As Long ' see bit definitions below
  bAttrValue As Byte   ' Current normalized value
  bWorstValue As Byte  ' How bad has it ever been?
  bRawValue(0 To 6) As Byte ' Un-normalized value
  bReserved As Byte    ' ...
End Type

'
' The following structure defines the structure of a Warranty Threshold
' Obsoleted in ATA4!
'
Public Type ATTRTHRESHOLD
  bAttrID As Byte      ' Identifies which attribute
  bWarrantyThreshold As Byte ' Triggering value
  bReserved(0 To 10)    ' ...
End Type

'
' The following struct defines the interesting part of the IDENTIFY
' buffer:
'
Public Type IDSECTOR
  wGenConfig As Integer
  wNumCyls As Integer
  wReserved As Integer
  wNumHeads As Integer
  wBytesPerTrack As Integer
  wBytesPerSector As Integer
  wSectorsPerTrack As Integer
  wVendorUnique(0 To 3) As Integer
  sSerialNumber(0 To 20) As Byte
  wBufferType As Integer
  wBufferSize As Integer
  wECCSize As Integer
  sFirmwareRev(0 To 8) As Byte
  sModelNumber(0 To 40) As Byte
  wMoreVendorUnique As Integer
  wDoubleWordIO  As Integer
  wCapabilities  As Integer
  wReserved1 As Integer
  wPIOTiming As Integer
  wDMATiming As Integer
  wBS As Integer
  wNumCurrentCyls As Integer
  wNumCurrentHeads As Integer
  wNumCurrentSectorsPerTrack As Integer
  ulCurrentSectorCapacity As Long
  wMultSectorStuff As Integer
  ulTotalAddressableSectors As Long
  wSingleWordDMA As Integer
  wMultiWordDMA As Integer
  bReserved(0 To 128) As Byte
End Type

'
' Valid Attribute IDs
'
Public Const ATTR_INVALID = 0
Public Const ATTR_READ_ERROR_RATE = 1
Public Const ATTR_THROUGHPUT_PERF = 2
Public Const ATTR_SPIN_UP_TIME = 3
Public Const ATTR_START_STOP_COUNT = 4
Public Const ATTR_REALLOC_SECTOR_COUNT = 5
Public Const ATTR_READ_CHANNEL_MARGIN = 6
Public Const ATTR_SEEK_ERROR_RATE = 7
Public Const ATTR_SEEK_TIME_PERF = 8
Public Const ATTR_POWER_ON_HRS_COUNT = 9
Public Const ATTR_SPIN_RETRY_COUNT = 10
Public Const ATTR_CALIBRATION_RETRY_COUNT = 11
Public Const ATTR_POWER_CYCLE_COUNT = 12

'
' Status Flags Values
'
Public Const PRE_FAILURE_WARRANTY = &H1
Public Const ON_LINE_COLLECTION = &H2
Public Const PERFORMANCE_ATTRIBUTE = &H4
Public Const ERROR_RATE_ATTRIBUTE = &H8
Public Const EVENT_COUNT_ATTRIBUTE = &H10
Public Const SELF_PRESERVING_ATTRIBUTE = &H20
Public Const NUM_ATTRIBUTE_STRUCTS = 30

Next part i'll explain some of the things in the smartapp code...
Hi,

I'll try to explain some things i found in the code of smartapp.c..

First thing, start reading from the int main() stuff. That is the code that gets executed when the program is ran in compiled mode.

First thing odd you will notice is the line

//
// Get the version, etc of SMART IOCTL
//
memset((void*)&VersionParams, 0, sizeof(VersionParams));

You might be tempted to think that this indeeds gets the version. That however is not the case. This only sets the complete structure of VersionParams to 0 bytes. I.o.w. Imagine the following before and after this memset call:

Before: This is a test of the memset function
After:  0000000000000000000000000000000000000

Next thing odd you might find is the following:

//
// Now, get the ID sector for all IDE devices in the system.
// If the device is ATAPI use the IDE_ATAPI_ID command,
// otherwise use the IDE_ID_FUNCTION command.
//
bIDCmd = (VersionParams.bIDEDeviceMap >> i & 0x10) ? \
                    IDE_ATAPI_ID : IDE_ID_FUNCTION;

This is a bitwise operation on the bidedevicemap, which is a bitmask. You might have to fiddle around with this and read up on the documentation of the C++ language and try to understand this line. From what I can tell right out of the top of my head that this is a RightShift the bits with an bitwise AND &h10. Next it does something even I have to look into.. :)

Well, that might get you started for some things, i'll check back later with perhaps a working version code, but that will be late into tomorrow (i'm at GMT +1).

Cheers..

Grtz©

D.
bIDCmd = (VersionParams.bIDEDeviceMap >> i & 0x10) ? \
                   IDE_ATAPI_ID : IDE_ID_FUNCTION;


VB equiv:

If (VersionParams.bIDEDeviceMap / (2 ^ i)) And &H10 Then
    bIDCmd = IDE_ATAPI_ID
Else
    bIDCmd = IDE_ID_FUNCTION
End If


You can of course write the condition more efficiently but the above is a direct translation.
Btw daffyduck (!), just had a quick look at some of your C to VB translations and you've mistranslated all the arrays

char xxx[3];

Is equivalent to

xxx(0 to 2) As String

Plus you haven't translated any functions?
Avatar of mollercw

ASKER

Wow, thanks for all the help =) I got quite far last night (GMT +2). One of the questions I wanted to ask was about doing bitwise operations in VB (thanks daffyduck and mirtol for the answers already). The other question is about type casting.

I've defined the following:
  Dim AttrOutCmd(528) As Byte   'BYTE AttrOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1];
  Dim ThreshOutCmd(528) As Byte 'BYTE ThreshOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_THRESHOLD_BUFFER_SIZE - 1];

Type SENDCMDOUTPARAMS 'size 17 bytes
  cBufferSize As Long
  DriverStatus As DriverStatus
  bBuffer As Byte
End Type

Now the problem is that the following function call needs to be sent a variable of type SENDCMDOUTPARAMS, but it has to be the size of AttrOutCmd. So how do I make VB think AttrOutCmd is a SENDCMDOUTPARAMS with a really big buffer?

Do I define SENDCMDOUTPARAMS with a really big buffer and just ignore the big buffer most of the time? Or is this type of type extension (bad choice of words) possible in VB?

PS. All my (and your) translations work up to the DoReadAttributesCmd function call. Thanks a lot =) I left out the DoIDENTIFY bit for now, I'm more interested in the SMART values than the disk's specs (unless there's some dependency I'm missing here).
Actually posting the "following function call" I'm referring to would also help, hey? =)

Private Sub Form_Load()

  Dim hSMARTIOCTL As Long
  Dim VersionParams As GETVERSIONOUTPARAMS
  Dim cbBytesReturned As Long
  Dim cmdIn As SENDCMDINPARAMS
  Dim cmdOut As SENDCMDOUTPARAMS
  Dim AttrOutCmd(528) As Byte 'BYTE AttrOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1];
  Dim ThreshOutCmd(528) As Byte 'BYTE ThreshOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_THRESHOLD_BUFFER_SIZE - 1];

<snip>

  'this works
  If (DoEnableSMART(hSMARTIOCTL, cmdIn, cmdOut, 0, cbBytesReturned)) Then
    Debug.Print "SMART Enabled on Drive: " & 0
    'this doesn't, invalid type error for AttrOutCmd
    If Not DoReadAttributesCmd(hSMARTIOCTL, cmdIn, AttrOutCmd, 0) Then
        Debug.Print "SMART Read Attr Command Failed on Drive: " & 0 & vbCrLf & "DriverStatus: bDriverError = " & AttrOutCmd.DriverStatus.bDriverError & vbCrLf & "bIDEStatus = " & AttrOutCmd.DriverStatus.bIDEStatus
      Else
        'ReadAttributes worked. Try ReadThresholds.
        If Not DoReadThresholdsCmd(hSMARTIOCTL, cmdIn, ThreshOutCmd, 0) Then
          Debug.Print "SMART Read Thrsh Command Failed on Drive: " & 0 & vbCrLf & "DriverStatus: bDriverError = " & ThreshOutCmd.DriverStatus.bDriverError & vbCrLf & "bIDEStatus = " & ThreshOutCmd.DriverStatus.bIDEStatus
        End If
      End If

<snip>

  CloseHandle (hSMARTIOCTL)

End Sub

DoReadThresholdsCmd and DoReadAttributesCmd are both defined as follows:

Public Function DoReadThresholdsCmd(hSMARTIOCTL As Long, ByRef pSCIP As SENDCMDINPARAMS, ByRef pSCOP As SENDCMDOUTPARAMS, bDriveNum As Byte) As Boolean

Another question: Does VB have a sizeof() function like C?
ASKER CERTIFIED SOLUTION
Avatar of mirtol
mirtol

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Sort of, the lenb function returns the size of a variable, sting or type. This is different from the len function. Lenb returns the length in bytes, len can sometimes return the number of chars in for example a string.

And mirtol, I didn't realize C++ counts base 1. If that is so, then my translations where indeed wrong.

Grtz.&copy;

D.
Well in general, it has indeces from 0 to [Count]-1 and talks about [Count] entries.

I guess they added the lenb function since I was a VB user...
Thanks for all the help =) I just got it working perfectly a few minutes ago. Now I just have to check where all the return values belong. It'll probably just be a simple mapping from the C++ code.
Hi,

Just wanted to share a link about reading C++ files and how to convert them to VB code. This has some usefull information in it, that you might need someday.

http://www.vbthunder.com/reference/readcpp.html

Grtz&copy;

D.
I just ran into an unexpected complication. According to the documentation, every value I read is vendor specific. EVERY SINGLE DRIVE could respond differently. Bloody hell. I'll keep on looking until I get some kind of lowest common denominator or at least some explicit data from some vendors (I specifically want Western Digital and Fujitsu).

Thanks again for all your help =)
I wish I could split the points between you two, or at least give it to both of you.
Hi,

That's not needed, glad we could be of any help.

:)

Grtz.&copy;

D.