unit smart_drv;

interface
uses
  Windows;




procedure OpenSMART;
procedure ReadSMART;
procedure CloseSMART;
function  GetATTRName(attr:byte;var comm:string):string;
function GetFlags(flags:word):string;
procedure SwapBytes( var buf; count : integer );

Type
    TIDSECTOR = packed record
	wGenConfig:word;
	wNumCyls:word;
{	wReserved:word;}
	wNumHeads:word;
	wBytesPerTrack:word;
	wBytesPerSector:word;
	wSectorsPerTrack:word;
	wVendorUnique : array[0..2] of word;
	sSerialNumber:array[0..19] of char;
	wBufferType:word;
	wBufferSize:word;
	wECCSize:word;
	sFirmwareRev:array[0..7] of char;
	sModelNumber:array[0..39] of char;
	wMoreVendorUnique:word;
	wDoubleWordIO:word;
	wCapabilities:word;
	wReserved1:word;
	wPIOTiming:word;
	wDMATiming:word;
	wBS:word;
	wNumCurrentCyls:word;
	wNumCurrentHeads:word;
	wNumCurrentSectorsPerTrack:word;
	ulCurrentSectorCapacity:dword;
	wMultSectorStuff:word;
	ulTotalAddressableSectors:dword;
	wSingleWordDMA:word;
	wMultiWordDMA:word;
        hz           : array[1..72] of byte;
        LBA48        : array[0..5] of byte;
	bReserved:array[0..127] of byte;
        end;

    TGETVERSIONOUTPARAMS = packed record
      version      : byte;
      revision     : byte;
      reserved     : byte;
      IDEDevMap    : byte;
      Capabilities : dword;
      reserved1    : dword;
      reserved2    : dword;
      reserved3    : dword;
      reserved4    : dword;
    End;

    TIDEREGS = packed record
   	  bFeaturesReg:byte;		// Used for specifying SMART "commands".
   	  bSectorCountReg:byte;    	// IDE sector count register
   	  bSectorNumberReg:byte;        	// IDE sector number register
  	  bCylLowReg:byte;			// IDE low order cylinder value
  	  bCylHighReg:byte;		// IDE high order cylinder value
  	  bDriveHeadReg:byte;		// IDE drive/head register
  	  bCommandReg:byte;		// Actual IDE command.
  	  bReserved:byte;
    end;    			// reserved for future use.  Must be zero.


 TSENDCMDINPARAMS = packed record
     cBufferSize:dword;		// Buffer size in bytes
	   irDriveRegs:TIDERegs;		// Structure with drive register values.
	   bDriveNumber:byte;		// Physical drive number to send
							// command to (0,1,2,3).
	   bReserved: array[0..3] of byte;		// Reserved for future expansion.
	   dwReserved: array [0..4] of dword;		// For future use.
	   bBuffer:array[0..1024*8] of byte;			// Input buffer.
    end;


 TDRIVERSTATUS = packed record
	bDriverError:byte;		// Error code from driver,
					// or 0 if no error.
	bIDEStatus:byte;	  	// Contents of IDE Error register.
					// Only valid when bDriverError
					// is SMART_IDE_ERROR.
	bReserved:array[0..3] of byte;	// Reserved for future expansion.
	dwReserved:array[0..1] of dword;// Reserved for future expansion.
  end;

   TDRIVEATTRIBUTE = packed record
   	    bAttrID:byte;		// Identifies which attribute
   	    wStatusFlags:word;	// see bit definitions below
  	    Value:byte;		// Current normalized value
  	    Worst:byte;	// How bad has it ever been?
	      Raw:array[0..5] of byte;	// Un-normalized value
       	bReserved:byte;		// ...
    end;

   TATTRTHRESHOLD = packed record
        bAttrID    : byte;
        bWarrantyThreshold : byte;
        reserved   : array[0..9] of byte;
    end;

 TSENDCMDOUTPARAMS = packed record
	cBufferSize  : DWord;		// Size of bBuffer in bytes
	DriverStatus : TDriverStatus;		// Driver status structure.
	attr         : array[0..255] of TDRIVEATTRIBUTE;			// Buffer of arbitrary length in which to store the data read from the 											// drive.
    end;

   TSMARTATTRNAME = record
    value : word;
    name  : string[40];
    comm  : string[160];
   end;

   THDDInfo = record
    Model    : string;
    FW       : string;
    SN       : string;
    LBABits  : integer;
    LBASize  : int64;
    Cache    : integer;
   End;

var
  VersParams  : TGETVERSIONOUTPARAMS;
  SCOP        : array[0..3] of TSENDCMDOUTPARAMS;
  AttrCnt     : array[0..3] of word;
  hSMARTIOCTL : array[0..3] of thandle;
  IDSECTOR    : array[0..3] of TIDSECTOR;
  Thresholds  : array[0..3,0..255] of TATTRTHRESHOLD;
  HDDInfo     : array[0..3] of THDDInfo;

const
  PRE_FAILURE_WARRANTY       =     $01;
  ON_LINE_COLLECTION         =     $02;
  PERFORMANCE_ATTRIBUTE      =     $04;
  ERROR_RATE_ATTRIBUTE       =     $08;
  EVENT_COUNT_ATTRIBUTE      =     $10;
  SELF_PRESERVING_ATTRIBUTE  =     $20;
  IDENTIFY_BUFFER_SIZE	     =     512;
  READ_THRESHOLD_BUFFER_SIZE =     512;

  SMART_READ_ATTRIBUTE_THRESHOLDS = $D1;

  IDE_ATAPI_ID		     =     $A1;	// Returns ID sector for ATAPI.
  IDE_ID_FUNCTION	     =     $EC;	// Returns ID sector for ATA.


var
   SCIP    : TSENDCMDINPARAMS;

const
   DFP_GET_VERSION        = $00074080;
   DFP_RECEIVE_DRIVE_DATA = $0007c088;
   DFP_SEND_DRIVE_COMMAND = $0007c084;
   SMART_ENABLE_SMART_OPERATIONS  = $D8;
   SMART_DISABLE_SMART_OPERATIONS = $D9;
   SMART_RETURN_SMART_STATUS	  = $DA;
   SMART_CYL_LOW                  = $4F;
   SMART_CYL_HI                   = $C2;
   IDE_EXECUTE_SMART_FUNCTION	  = $B0;
   READ_ATTRIBUTE_BUFFER_SIZE	  = 512;
   SMART_READ_ATTRIBUTE_VALUES	  = $D0;	// ATA4: Renamed

CAP_ATA_ID_CMD = 1;   //    'ATA ID command supported
CAP_ATAPI_ID_CMD = 2; //    'ATAPI ID command supported
CAP_SMART_CMD = 4;    //    'SMART commannds supported

   AttrCount = 45;
   SmartAttrNames : array[1..AttrCount] of TSMARTAttrName=
   (
   (Value:1;Name:'Raw Read Error Rate';Comm:'Frequency of errors appearance while reading RAW data from a disk'),
   (Value:2;Name:'Throughput Performance';Comm:'The average efficiency of hard disk'),
   (Value:3;Name:'Spin Up Time';Comm:'Time needed by spindle to spin-up'),
   (Value:4;Name:'Start/Stop Count';Comm:'Number of start/stop cycles of spindle'),
   (Value:5;Name:'Reallocated Sector Count';Comm:'Quantity of remapped sectors'),
   (Value:6;Name:'Read Channel Margin';Comm:'Reserve of channel while reading'),
   (Value:7;Name:'Seek Error Rate';Comm:'Frequency of errors appearance while positioning'),
   (Value:8;Name:'Seek Time Performance';Comm:'The average efficiency of operations while positioning'),
   (Value:9;Name:'Power-On Hours Count';Comm:'Quantity of elapsed hours in the switched-on state'),
   (Value:10;Name:'Spin-up Retry Count';Comm:'Number of attempts to start a spindle of a disk'),
   (Value:11;Name:'Calibration Retry Count';Comm:'Number of attempts to calibrate a drive'),
   (Value:12;Name:'Power Cycle Count';Comm:'Number of complete start/stop cycles of hard disk'),
   (Value:13;Name:'Soft Read Error Rate';Comm:'Frequency of "program" errors appearance while reading data from a disk'),
   (Value:191;Name:'G-Sense Error Rate';Comm:'Frequency of mistakes appearance as a result of impact loads'),
   (Value:192;Name:'Power-Off Retract Cycle';Comm:'Number of the fixed "turning off" drive cycles (Fujitsu: Emergency Retract Cycle Count)'),
   (Value:193;Name:'Load/Unload Cycle Count';Comm:'Number of cycles into Landing Zone position'),
   (Value:194;Name:'HDD Temperature';Comm:'Temperature of a Hard Disk Assembly'),
   (Value:195;Name:'Hardware ECC Recovered';Comm:'Frequency of the on the fly errors (Fujitsu: ECC On The Fly Count)'),
   (Value:196;Name:'Reallocated Event Count';Comm:'Quantity of remapping operations'),
   (Value:197;Name:'Current Pending Sector Count';Comm:'Current quantity of unstable sectors (waiting for remapping)'),
   (Value:198;Name:'Off-line Scan Uncorrectable Count';Comm:'Quantity of uncorrected errors'),
   (Value:199;Name:'UltraDMA CRC Error Rate';Comm:'Total quantity of errors CRC during UltraDMA mode'),
   (Value:200;Name:'Write Error Rate';Comm:'Frequency of errors appearance while recording data into disk (Western Digital: Multi Zone Error Rate)'),
   (Value:201;Name:'Soft Read Error Rate';Comm:'Frequency of the off track errors (Maxtor: Off Track Errors)'),
   (Value:202;Name:'Data Address Mark Errors';Comm:'Frequency of the Data Address Mark errors'),
   (Value:203;Name:'Run Out Cancel';Comm:'Frequency of the ECC errors (Maxtor: ECC Errors)'),
   (Value:204;Name:'Soft ECC Correction';Comm:'Quantity of errors corrected by software ECC'),
   (Value:205;Name:'Thermal Asperity Rate';Comm:'Frequency of the thermal asperity errors'),
   (Value:206;Name:'Flying Height';Comm:'The height of the disk heads above the disk surface'),
   (Value:207;Name:'Spin High Current';Comm:'Quantity of used high current to spin up drive'),
   (Value:208;Name:'Spin Buzz';Comm:'Quantity of used buzz routines to spin up drive'),
   (Value:209;Name:'Offline Seek Performance';Comm:'Drives seek performance during offline operations'),
   (Value:220;Name:'Disk Shift';Comm:'Shift of disk is possible as a result of strong shock loading in the store, as a result of it`s falling or for other reasons'),
   (Value:221;Name:'G-Sense Error Rate';Comm:'This attribute is an indication of shock-sensitive sensor - total quantity of errors appearance as a result of impact loads '),
   (Value:222;Name:'Loaded Hours';Comm:'Loading on drive caused by the general operating time of hours it stores'),
   (Value:223;Name:'Load/Unload Retry Count';Comm:'Loading on drive caused by numerous recurrences of operations like: reading, recording, positioning of heads, etc.'),
   (Value:224;Name:'Load Friction';Comm:'Loading on drive caused by friction in mechanical parts of the store'),
   (Value:225;Name:'Load/Unload Cycle Count';Comm:'Total of cycles of loading on drive'),
   (Value:226;Name:'Load-in Time';Comm:'General time of loading for drive'),
   (Value:227;Name:'Torque Amplification Count';Comm:'Quantity efforts of the rotating moment of a drive'),
   (Value:228;Name:'Power-Off Retract Count';Comm:'Quantity of the fixed turning off`s a drive'),
   (Value:230;Name:'GMR Head Amplitude';Comm:'Amplitude of heads trembling (GMR-head) in running mode'),
   (Value:231;Name:'Temperature';Comm:'Temperature of a drive'),
   (Value:240;Name:'Head Flying Hours';Comm:'Time while head is positioning'),
   (Value:250;Name:'Read Error Retry Rate';Comm:'Frequency of errors appearance while reading data from a disk')
   );

   flagnames : array[0..5] of string[2] = ('PF','OC','PA','ER','EC','SP');

implementation


function IntToStr(Num : Integer) : String; 
begin
  Str(Num, result);
end;

{------------------------------------------------------------------}
function GetFlags(flags:word):string;
var
  i : integer;
  s : string;

Begin
 s := '';
  for i := 0 to 5 do
    if flags and (1 shl i) <> 0 then
      s := s + flagnames[i]+#32;
 GetFlags := s;     
End;

{------------------------------------------------------------------}
function  GetATTRName(attr:byte;var comm:string):string;
var
  i : integer;
begin
  for i := 1 to attrCount do
   begin
    if attr = SmartAttrNames[i].Value then
     begin
      GetAttrName := SmartAttrNames[i].name;
      Comm := SmartAttrNames[i].comm;
      exit;
     end;
   end;
 GetAttrName := 'Unknown';
end;

{------------------------------------------------------------------}
procedure SwapBytes( var buf; count : integer );
Assembler;
Asm
        pushad
        mov     esi, buf
        mov     edi, esi
        mov     ecx, count
        shr     ecx, 1
        test    ecx, ecx
        jz      @@exit
        @@rep:
        lodsw
        xchg    ah, al
        stosw
        dec     ecx
        jnz     @@rep
@@exit:
        popad
End;

{------------------------------------------------------------------}
procedure OpenSMART;
var
  i       : integer;
  c ,nRetCount      : dword;
  s   : string;
  os          : TOSVERSIONINFO;
  osver       : (wvNT3,wvNT4,wvW2k,wvXP,wv95,wv98,wvME);
begin
 for i := 0 to 3 do
  begin

 os.dwPlatformId := 0;
 os.dwOSVersionInfoSize := sizeof(OSVERSIONINFO);
 GetVersionEx( os );

    osver := wv98;
    case OS.DwMajorVersion of
      3:  osver := wvNT3;
      4:  case OS.DwMinorVersion of
            0: if OS.dwPlatformId = VER_PLATFORM_WIN32_NT
               then osver := wvNT4
               else osver := wv95;
            10: osver := wv98;
            90: osver := wvME;
          end;
      5:  case OS.DwMinorVersion of
            0: osver := wvW2K;
            1: osver := wvXP;
          end;
    end;

    hSMARTIOCTL[i] := 0;

    if (osver = wv98) or (osver = wv95) or (osver=wvME) then
    hSMARTIOCTL[i] := CreateFile('\\.\SMARTVSD', 0,0,NIL,
                        CREATE_NEW, 0, 0)
    else

     hSMARTIOCTL[i] := CreateFile(pchar( '\\.\PhysicalDrive'+inttostr(i)),
                    GENERIC_READ or GENERIC_WRITE,
                    FILE_SHARE_READ or FILE_SHARE_WRITE,
                    NIL,
                    OPEN_EXISTING,
                    0,
                    0);

     if hSMARTIOCTL[i] = INVALID_HANDLE_VALUE then continue;
     //Check for Smart compatibly
     DeviceIoControl(hSMARTIOCTL[i], DFP_GET_VERSION,  nil, 0, @VersParams, sizeof(VersParams), nRetCount, nil);
     
     if (VersParams.Capabilities and CAP_SMART_CMD)=0 then
     begin
          hSMARTIOCTL[i] := INVALID_HANDLE_VALUE;
          continue; 
     end;
        FillChar( SCIP, SizeOf( SCIP ), 0 );
	SCIP.cBufferSize := 0;

	SCIP.irDriveRegs.bFeaturesReg := SMART_ENABLE_SMART_OPERATIONS;
	SCIP.irDriveRegs.bSectorCountReg := 1;
	SCIP.irDriveRegs.bSectorNumberReg := 1;
	SCIP.irDriveRegs.bCylLowReg := SMART_CYL_LOW;
	SCIP.irDriveRegs.bCylHighReg := SMART_CYL_HI;

	//
	// Compute the drive number.
	//
	SCIP.irDriveRegs.bDriveHeadReg := $A0 or (( i and 1) shl 4);
	SCIP.irDriveRegs.bCommandReg := IDE_EXECUTE_SMART_FUNCTION;
	SCIP.bDriveNumber := i;

        DeviceIoControl(hSMARTIOCTL[i], DFP_SEND_DRIVE_COMMAND,
                @SCIP, sizeof(TSENDCMDINPARAMS) - 1,
                @SCOP[i], sizeof(TSENDCMDOUTPARAMS) - 1,
                c, NIL);

	SCIP.cBufferSize := IDENTIFY_BUFFER_SIZE;
	SCIP.irDriveRegs.bFeaturesReg := 0;
	SCIP.irDriveRegs.bSectorCountReg := 1;
	SCIP.irDriveRegs.bSectorNumberReg := 1;
	SCIP.irDriveRegs.bCylLowReg := 0;
	SCIP.irDriveRegs.bCylHighReg := 0;

	SCIP.irDriveRegs.bDriveHeadReg := $A0 or ((i and 1) shl 4);
	SCIP.irDriveRegs.bCommandReg := IDE_ID_FUNCTION{ or IDE_ATAPI_ID};
	SCIP.bDriveNumber := i;
	SCIP.cBufferSize := IDENTIFY_BUFFER_SIZE;

        DeviceIoControl(hSMARTIOCTL[i], DFP_RECEIVE_DRIVE_DATA,
        @SCIP, sizeof(TSENDCMDINPARAMS) - 1,
        @SCOP[i], sizeof(TSENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE - 1,
        c, NIL) ;

        Move(SCOP[i].attr, IDSECTOR[i], SizeOf( IDSECTOR[i] ) );

        SCIP.cBufferSize := READ_THRESHOLD_BUFFER_SIZE;

        SCIP.irDriveRegs.bFeaturesReg := SMART_READ_ATTRIBUTE_THRESHOLDS;
        SCIP.irDriveRegs.bSectorCountReg := 1;
        SCIP.irDriveRegs.bSectorNumberReg := 1;
        SCIP.irDriveRegs.bCylLowReg := SMART_CYL_LOW;
        SCIP.irDriveRegs.bCylHighReg := SMART_CYL_HI;

        SCIP.irDriveRegs.bDriveHeadReg := $A0 or ((i and 1) shl 4);
        SCIP.irDriveRegs.bCommandReg := IDE_EXECUTE_SMART_FUNCTION;
        SCIP.bDriveNumber := i;

        DeviceIoControl(hSMARTIOCTL[i], DFP_RECEIVE_DRIVE_DATA,
        @SCIP, sizeof(TSENDCMDINPARAMS) - 1,
        @SCOP[i], sizeof(TSENDCMDOUTPARAMS) + READ_THRESHOLD_BUFFER_SIZE - 1,
        c, NIL) ;

        Move( SCOP[ i ].attr, Thresholds[ i ], SizeOf( Thresholds[ i ] ) );

        HDDInfo[ i ].LBASize := 0;
        HDDInfo[ i ].LBABits := 28;

   if idsector[i].ulTotalAddressableSectors = $FFFFFFF then
      begin
       move( idsector[i].lba48,HDDInfo[ i ].LBASize,6);
       HDDInfo[ i ].LBABits := 48;
      end
         else
       move( idsector[i].ulTotalAddressableSectors,HDDInfo[ i ].LBASize,4);

       s := IDSECTOR[i].sModelNumber +#32;
       SwapBytes( s[1], Length(s) );

       While (Length(s)<>0) and (s[Length(s)-1]=#32) do
         SetLength( s, length(s)-1);

   HDDInfo[ i ].Model := s;

   s := IDSECTOR[i].sFirmwareRev +#32;
   SwapBytes( s[1], Length(s) );

   HDDInfo[ i ].FW := s;

   s := IDSECTOR[i].sSerialNumber+#32;
   SwapBytes( s[1], Length(s) );
    While (Length(s)<>0) and (s[1]=#32) do
     delete(s,1,1);

   HDDInfo[ i ].SN := s;
   HDDInfo[ i ].Cache := IDSECTOR[i].wBufferSIze div 2;

  end;
end;

{------------------------------------------------------------------}
procedure CloseSMART;
var
  i : integer;
Begin
 for i := 0 to 3 do
 begin
     if hSMARTIOCTL[i] = INVALID_HANDLE_VALUE then continue;
     CloseHandle(hSMARTIOCTL[i]);
 end;

End;

{------------------------------------------------------------------}
procedure ReadSMART;
var
  i,j     : integer;
  c       : dword;
Begin

//OpenSMART;
 for i := 0 to 3 do
  begin
     if hSMARTIOCTL[i] = INVALID_HANDLE_VALUE then continue;

	SCIP.cBufferSize := READ_ATTRIBUTE_BUFFER_SIZE;
	SCIP.irDriveRegs.bFeaturesReg := SMART_READ_ATTRIBUTE_VALUES;
	SCIP.irDriveRegs.bSectorCountReg := 1;
	SCIP.irDriveRegs.bSectorNumberReg := 1;
	SCIP.irDriveRegs.bCylLowReg := SMART_CYL_LOW;
	SCIP.irDriveRegs.bCylHighReg := SMART_CYL_HI;

	//
	// Compute the drive number.
	//
	SCIP.irDriveRegs.bDriveHeadReg := $A0 or (( i and 1) shl 4);
	SCIP.irDriveRegs.bCommandReg := IDE_EXECUTE_SMART_FUNCTION;
	SCIP.bDriveNumber := i;
        FillChar( SCOP[i], SizeOf( SCOP[i] ), 0 );

        DeviceIoControl(hSMARTIOCTL[i], DFP_RECEIVE_DRIVE_DATA,
                @SCIP, sizeof(TSENDCMDINPARAMS) - 1,
                @SCOP[ i ], sizeof(TSENDCMDOUTPARAMS) - 1 + READ_ATTRIBUTE_BUFFER_SIZE,
                c, NIL);

     AttrCnt[ i ] := 0;
     for j := 0 to 255 do
      if SCOP[ i ].attr[ j ].bAttrID = 0 then
       begin
         AttrCnt[ i ] := j;
         break;
       end;
  end;
// CloseSMART;

End;

{------------------------------------------------------------------}
end.
