/****************************************************************************
*                                                                           *
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY     *
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE       *
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR     *
* PURPOSE.                                                                  *
*                                                                           *
* Copyright 1993-98  Microsoft Corporation.  All Rights Reserved.           *
*                                                                           *
****************************************************************************/

/****************************************************************************
*
* PROGRAM: SMARTAPP.C
*
* PURPOSE: Simple console application that calls SMART IOCTL
*
* FUNCTIONS:
*  main() - Console application opens SMART IOCTL which supports DeviceIoControl.
*	    SMART IOCTL will return values to this application through this 
*	    same DeviceIoControl interface.
*
****************************************************************************/
#define WINDOWS9X	// Define this to compile for Windows 9x

#include <stdio.h>
#include <conio.h>
#include <windows.h>

#pragma pack(1)		// Required to ensure correct SMART IOCTL structure setup
#include "smart.h"
#include "smartapp.h"

//
// Define global buffers.
//
BYTE	AttrOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1];
BYTE	ThreshOutCmd[sizeof(SENDCMDOUTPARAMS) + READ_THRESHOLD_BUFFER_SIZE - 1];
BYTE	IdOutCmd[sizeof(SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE - 1];

int main()
{
	HANDLE      		hSMARTIOCTL = 0;
	DWORD       		cbBytesReturned;
	GETVERSIONOUTPARAMS VersionParams;
	SENDCMDINPARAMS 	scip;
	SENDCMDOUTPARAMS	OutCmd;
	BYTE				bDfpDriveMap = 0;
	BYTE				i;
	BYTE				bSuccess = 1;
	BYTE				bIDCmd;		// IDE or ATAPI IDENTIFY cmd

	//
	// Try to get a handle to SMART IOCTL, report failure and exit if
	// can't.
	//
    if ((hSMARTIOCTL = OpenSMART()) != INVALID_HANDLE_VALUE)
    {
	//
	// Get the version, etc of SMART IOCTL
	//
	memset((void*)&VersionParams, 0, sizeof(VersionParams));

	if ( DeviceIoControl(hSMARTIOCTL, DFP_GET_VERSION,
			NULL, 
			0,
			&VersionParams,
			sizeof(VersionParams),
			&cbBytesReturned, NULL) )
    {
		printf("DFP_GET_VERSION returned:\n");
		printf("\tbVersion        = %d\n", VersionParams.bVersion);
		printf("\tbRevision       = %d\n", VersionParams.bRevision);
		printf("\tfCapabilities   = 0x%lx\n", VersionParams.fCapabilities);
		printf("\tbReserved       = 0x%x\n", VersionParams.bReserved);
		printf("\tbIDEDeviceMap   = 0x%x\n", VersionParams.bIDEDeviceMap);
		printf("\tcbBytesReturned = %d\n\n", cbBytesReturned);
    }
    else
    {
        printf("DFP_GET_VERSION failed.\n");
    }

	for (i = 0; i < MAX_IDE_DRIVES; i++)
	{
		//
		// If there is a IDE device at number "i" issue commands
		// to the device.
		//
		if (VersionParams.bIDEDeviceMap >> i & 1)
		{

			//
			// Try to enable SMART so we can tell if a drive supports it.
			// Ignore ATAPI devices.
			//

			if (!(VersionParams.bIDEDeviceMap >> i & 0x10))
			{

				memset(&scip, 0, sizeof(scip));
				memset(&OutCmd, 0, sizeof(OutCmd));

	    		if (DoEnableSMART(hSMARTIOCTL, 
					&scip, 
					&OutCmd, 
					i,
					&cbBytesReturned))
				{
					printf("SMART Enabled on Drive: %d\n", i);
					//
					// Mark the drive as SMART enabled
					//
					bDfpDriveMap |= (1 << i);
				}
				else
	    		{
					printf("SMART Enable Command Failed, Drive: %d.\n",i);
					printf(" DriverStatus: bDriverError=0x%X, bIDEStatus=0x%X\n\n", 
	    					OutCmd.DriverStatus.bDriverError, 
	    					OutCmd.DriverStatus.bIDEStatus);
	    		}
				printf("\tcbBytesReturned: %d\n\n", cbBytesReturned);
			}


			//
			// 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;

			memset(&scip, 0, sizeof(scip));
			memset(IdOutCmd, 0, sizeof(IdOutCmd));

			if ( DoIDENTIFY(hSMARTIOCTL, 
					&scip, 
					(PSENDCMDOUTPARAMS)&IdOutCmd, 
					bIDCmd,
					i,
					&cbBytesReturned))
	        	{
				DisplayIdInfo((PIDSECTOR) ((PSENDCMDOUTPARAMS)IdOutCmd)->bBuffer,
						&scip,
						bIDCmd,
						bDfpDriveMap,
						i);
				}

	       	else
	       		{
	       			printf("Identify Command Failed on Drive: %d\n", i);
					printf(" DriverStatus: bDriverError=0x%X, bIDEStatus=0x%X\n\n", 
					((PSENDCMDOUTPARAMS)IdOutCmd)->DriverStatus.bDriverError, 
					((PSENDCMDOUTPARAMS)IdOutCmd)->DriverStatus.bIDEStatus);
	        	}
	    		printf("\tcbBytesReturned: %d\n\n", cbBytesReturned);
	    }

	}

	//
	// Loop through all possible IDE drives and send commands to the ones that support SMART.
	//
	for (i = 0; i < MAX_IDE_DRIVES; i++)
	{
		if (bDfpDriveMap >> i & 1)
		{

			memset(AttrOutCmd, 0, sizeof(AttrOutCmd));
			memset(ThreshOutCmd, 0, sizeof(ThreshOutCmd));

			if ( !(bSuccess = DoReadAttributesCmd(hSMARTIOCTL, 
					&scip, 
					(PSENDCMDOUTPARAMS)&AttrOutCmd, 
					i)))
			{
				printf("\nSMART Read Attr Command Failed on Drive: %d.\n", i);
				printf(" DriverStatus: bDriverError=0x%X, bIDEStatus=0x%X\n\n", 
					((PSENDCMDOUTPARAMS)AttrOutCmd)->DriverStatus.bDriverError, 
					((PSENDCMDOUTPARAMS)AttrOutCmd)->DriverStatus.bIDEStatus);
			}	
	
			// ReadAttributes worked. Try ReadThresholds.
			else if ( !(DoReadThresholdsCmd(hSMARTIOCTL, 
					&scip, 
					(PSENDCMDOUTPARAMS)&ThreshOutCmd, 
					i)))
			{
				printf("\nSMART Read Thrsh Command Failed on Drive: %d.\n", i);
				printf(" DriverStatus: bDriverError=0x%X, bIDEStatus=0x%X\n\n", 
					((PSENDCMDOUTPARAMS)ThreshOutCmd)->DriverStatus.bDriverError, 
					((PSENDCMDOUTPARAMS)ThreshOutCmd)->DriverStatus.bIDEStatus);
			}

			//
			// The following report will print if ReadAttributes works.
			// If ReadThresholds works, the report will also show values for
			// Threshold values.
			//
			if (bSuccess)
			{
					DoPrintData(((PSENDCMDOUTPARAMS)AttrOutCmd)->bBuffer, 
							((PSENDCMDOUTPARAMS)ThreshOutCmd)->bBuffer, 
							i);
			}
	
		}
	}

	//
    // Close SMART.
	//
        CloseHandle(hSMARTIOCTL);
    }

    return(0);
}


/****************************************************************************
*
* DoIDENTIFY
*
* FUNCTION: Send an IDENTIFY command to the drive
* bDriveNum = 0-3
* bIDCmd = IDE_ID_FUNCTION or IDE_ATAPI_ID
*
****************************************************************************/
BOOL DoIDENTIFY(HANDLE hSMARTIOCTL, PSENDCMDINPARAMS pSCIP,
	PSENDCMDOUTPARAMS pSCOP, BYTE bIDCmd, BYTE bDriveNum, PDWORD lpcbBytesReturned)
{
	//
	// Set up data structures for IDENTIFY command.
	//

	pSCIP->cBufferSize = IDENTIFY_BUFFER_SIZE;

	pSCIP->irDriveRegs.bFeaturesReg = 0;
	pSCIP->irDriveRegs.bSectorCountReg = 1;
	pSCIP->irDriveRegs.bSectorNumberReg = 1;
	pSCIP->irDriveRegs.bCylLowReg = 0;
	pSCIP->irDriveRegs.bCylHighReg = 0;

	//
	// Compute the drive number.
	//
	pSCIP->irDriveRegs.bDriveHeadReg = 0xA0 | ((bDriveNum & 1) << 4); 

	//
	// The command can either be IDE identify or ATAPI identify.
	//
	pSCIP->irDriveRegs.bCommandReg = bIDCmd;
	pSCIP->bDriveNumber = bDriveNum;
	pSCIP->cBufferSize = IDENTIFY_BUFFER_SIZE;

    return ( DeviceIoControl(hSMARTIOCTL, DFP_RECEIVE_DRIVE_DATA,
             	(LPVOID)pSCIP, sizeof(SENDCMDINPARAMS) - 1,
               	(LPVOID)pSCOP, sizeof(SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE - 1,
               	lpcbBytesReturned, NULL) );
}

/****************************************************************************
*
* DisplayIdInfo
*
* Display the contents of the ID buffer
*
****************************************************************************/
VOID DisplayIdInfo(PIDSECTOR pids, PSENDCMDINPARAMS pSCIP, BYTE bIDCmd, BYTE bDfpDriveMap, BYTE bDriveNum)
{
	BYTE	szOutBuffer[41];

	if (bIDCmd == IDE_ID_FUNCTION)
	{
		printf("Drive %d is an IDE Hard drive", bDriveNum); 
		printf("%s\n", ( bDfpDriveMap >> bDriveNum & 1) ? \
				" that supports SMART" : "");
		printf("\t#Cylinders: %d, #Heads: %d, #Sectors per Track: %d\n",
				pids->wNumCyls, 
				pids->wNumHeads, 
				pids->wSectorsPerTrack);

		PrintIDERegs(pSCIP);

	}
	else
	{
		printf("Drive %d is an ATAPI device.\n", bDriveNum); 
	}

	//
	// Change the WORD array to a BYTE
	// array
	//
	ChangeByteOrder(pids->sModelNumber, 
		sizeof pids->sModelNumber);

	memset(szOutBuffer,0, sizeof(szOutBuffer));
	strncpy(szOutBuffer, pids->sModelNumber, sizeof(pids->sModelNumber));

	printf("\tModel number: %s\n", szOutBuffer);


	//
	// Change the WORD array to a BYTE
	// array
	//
	ChangeByteOrder(pids->sFirmwareRev, 
		sizeof pids->sFirmwareRev);

	memset(szOutBuffer,0, sizeof(szOutBuffer));
	strncpy(szOutBuffer, pids->sFirmwareRev, sizeof(pids->sFirmwareRev));

	printf("\tFirmware rev: %s\n", szOutBuffer);


	//
	// Change the WORD array to a BYTE
	// array
	//
	ChangeByteOrder(pids->sSerialNumber, 
		sizeof pids->sSerialNumber);

	memset(szOutBuffer,0, sizeof(szOutBuffer));
	strncpy(szOutBuffer, pids->sSerialNumber, sizeof(pids->sSerialNumber));

	printf("\tSerial number: %s\n",szOutBuffer );
       
}

/****************************************************************************
*
* DoEnableSMART
*
* FUNCTION: Send a SMART_ENABLE_SMART_OPERATIONS command to the drive
* bDriveNum = 0-3
*
****************************************************************************/
BOOL DoEnableSMART(HANDLE hSMARTIOCTL, PSENDCMDINPARAMS pSCIP, PSENDCMDOUTPARAMS pSCOP, BYTE bDriveNum, PDWORD lpcbBytesReturned)
{
	//
	// Set up data structures for Enable SMART Command.
	//
	pSCIP->cBufferSize = 0;

	pSCIP->irDriveRegs.bFeaturesReg = SMART_ENABLE_SMART_OPERATIONS;
	pSCIP->irDriveRegs.bSectorCountReg = 1;
	pSCIP->irDriveRegs.bSectorNumberReg = 1;
	pSCIP->irDriveRegs.bCylLowReg = SMART_CYL_LOW;
	pSCIP->irDriveRegs.bCylHighReg = SMART_CYL_HI;

	//
	// Compute the drive number.
	//
	pSCIP->irDriveRegs.bDriveHeadReg = 0xA0 | ((bDriveNum & 1) << 4); 
	pSCIP->irDriveRegs.bCommandReg = IDE_EXECUTE_SMART_FUNCTION;
	pSCIP->bDriveNumber = bDriveNum;

        return ( DeviceIoControl(hSMARTIOCTL, DFP_SEND_DRIVE_COMMAND,
                (LPVOID)pSCIP, sizeof(SENDCMDINPARAMS) - 1,
                (LPVOID)pSCOP, sizeof(SENDCMDOUTPARAMS) - 1,
                lpcbBytesReturned, NULL) );
}

/****************************************************************************
*
* DoReadAttributesCmd
*
* FUNCTION: Send a SMART_READ_ATTRIBUTE_VALUES command to the drive
* bDriveNum = 0-3
*
****************************************************************************/
BOOL DoReadAttributesCmd(HANDLE hSMARTIOCTL, PSENDCMDINPARAMS pSCIP, PSENDCMDOUTPARAMS pSCOP, BYTE bDriveNum)
{
DWORD	cbBytesReturned;

	//
	// Set up data structures for Read Attributes SMART Command.
	//

	pSCIP->cBufferSize = READ_ATTRIBUTE_BUFFER_SIZE;

	pSCIP->irDriveRegs.bFeaturesReg = SMART_READ_ATTRIBUTE_VALUES;
	pSCIP->irDriveRegs.bSectorCountReg = 1;
	pSCIP->irDriveRegs.bSectorNumberReg = 1;
	pSCIP->irDriveRegs.bCylLowReg = SMART_CYL_LOW;
	pSCIP->irDriveRegs.bCylHighReg = SMART_CYL_HI;

	//
	// Compute the drive number.
	//
	pSCIP->irDriveRegs.bDriveHeadReg = 0xA0 | ((bDriveNum & 1) << 4); 
	pSCIP->irDriveRegs.bCommandReg = IDE_EXECUTE_SMART_FUNCTION;
	pSCIP->bDriveNumber = bDriveNum;
        return ( DeviceIoControl(hSMARTIOCTL, DFP_RECEIVE_DRIVE_DATA,
                (LPVOID)pSCIP, sizeof(SENDCMDINPARAMS) - 1,
                (LPVOID)pSCOP, sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1,
                &cbBytesReturned, NULL) );
}

/****************************************************************************
*
* DoReadThresholdsCmd
*
* FUNCTION: Send a SMART_READ_ATTRIBUTE_THRESHOLDS command to the drive
* bDriveNum = 0-3
*
****************************************************************************/
BOOL DoReadThresholdsCmd(HANDLE hSMARTIOCTL, PSENDCMDINPARAMS pSCIP, PSENDCMDOUTPARAMS pSCOP, BYTE bDriveNum)
{
DWORD	cbBytesReturned;

	//
	// Set up data structures for Read Thresholds SMART Command.
	//

	pSCIP->cBufferSize = READ_THRESHOLD_BUFFER_SIZE;

	pSCIP->irDriveRegs.bFeaturesReg = SMART_READ_ATTRIBUTE_THRESHOLDS;
	pSCIP->irDriveRegs.bSectorCountReg = 1;
	pSCIP->irDriveRegs.bSectorNumberReg = 1;
	pSCIP->irDriveRegs.bCylLowReg = SMART_CYL_LOW;
	pSCIP->irDriveRegs.bCylHighReg = SMART_CYL_HI;

	//
	// Compute the drive number.
	//
	pSCIP->irDriveRegs.bDriveHeadReg = 0xA0 | ((bDriveNum & 1) << 4); 
	pSCIP->irDriveRegs.bCommandReg = IDE_EXECUTE_SMART_FUNCTION;
	pSCIP->bDriveNumber = bDriveNum;


        return ( DeviceIoControl(hSMARTIOCTL, DFP_RECEIVE_DRIVE_DATA,
                (LPVOID)pSCIP, sizeof(SENDCMDINPARAMS) - 1,
                (LPVOID)pSCOP, sizeof(SENDCMDOUTPARAMS) + READ_THRESHOLD_BUFFER_SIZE - 1,
                &cbBytesReturned, NULL) );
}
//
// Declare a global structure to help print the data.
// NOTE: Per ATA3 and ATA4 specs, these attribute definitions are defined by the drive vendor
// and hence their attributes may vary between vendors.
//
PCHAR	pAttrNames[] = {
	"No Attribute Here       ", //0
	"Raw Read Error Rate     ", //1
	"Throughput Performance  ", //2
	"Spin Up Time            ", //3
	"Start/Stop Count        ", //4
	"Reallocated Sector Count", //5
	"Read Channel Margin     ", //6
	"Seek Error Rate         ", //7
	"Seek Time Performance   ", //8
	"Power On Hours Count    ", //9
	"Spin Retry Count        ", //10
	"Calibration Retry Count ", //11
	"Power Cycle Count       ", //12
	"(Unknown attribute)     "
};
#define MAX_KNOWN_ATTRIBUTES	12

/****************************************************************************
*
* DoPrintData
*
* FUNCTION: Display the SMART Attributes and Thresholds
*
****************************************************************************/
VOID DoPrintData(PCHAR pAttrBuffer, PCHAR pThrsBuffer, BYTE bDriveNum)
{
int	i;
PDRIVEATTRIBUTE	pDA;
PATTRTHRESHOLD	pAT;
BYTE Attr;

	//
	// Print the drive number
	//
	printf("\nData for Drive Number %d\n", bDriveNum);
	//
	// Print the revisions of the data structures
	//
	printf("Attribute Structure Revision          Threshold Structure Revision\n");
	printf("             %d                                      %d\n\n", 
				(WORD)pAttrBuffer[0], 
				(WORD)pThrsBuffer[0]);

	//
	// Print the header and loop through the structures, printing
	// the structures when the attribute ID is known.
	//
	printf("   -Attribute Name-      -Attribute Value-     -Threshold Value-\n");

	pDA = (PDRIVEATTRIBUTE)&pAttrBuffer[2];
	pAT = (PATTRTHRESHOLD)&pThrsBuffer[2];

	for (i = 0; i < NUM_ATTRIBUTE_STRUCTS; i++)
	{
		Attr = pDA->bAttrID;
		if (Attr)
		{
			if (Attr > MAX_KNOWN_ATTRIBUTES)
				Attr = MAX_KNOWN_ATTRIBUTES+1;
			printf("%2X %-29s%d%20c%d\n", 
					pDA->bAttrID,
					pAttrNames[Attr], 
					pDA->bAttrValue, 
					' ',
					pAT->bWarrantyThreshold );
		}
		pDA++;
		pAT++;
	}

}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
VOID ChangeByteOrder(PCHAR szString, USHORT uscStrSize)
{

USHORT	i;
CHAR	temp;

	for (i = 0; i < uscStrSize; i+=2)
	{
		temp = szString[i];
		szString[i] = szString[i+1];
		szString[i+1] = temp;
	}
}

//---------------------------------------------------------------------
// Display contents of IDE hardware registers reported by SMART
//---------------------------------------------------------------------
VOID PrintIDERegs(PSENDCMDINPARAMS pscip)
{
	printf("\tIDE TASK FILE REGISTERS:\n");

	printf("\t\tbFeaturesReg     = 0x%X\n", pscip->irDriveRegs.bFeaturesReg);
	printf("\t\tbSectorCountReg  = 0x%X\n", pscip->irDriveRegs.bSectorCountReg);
	printf("\t\tbSectorNumberReg = 0x%X\n", pscip->irDriveRegs.bSectorNumberReg);
	printf("\t\tbCylLowReg       = 0x%X\n", pscip->irDriveRegs.bCylLowReg);
	printf("\t\tbCylHighReg      = 0x%X\n", pscip->irDriveRegs.bCylHighReg);
	printf("\t\tbDriveHeadReg    = 0x%X\n", pscip->irDriveRegs.bDriveHeadReg); 
	printf("\t\tStatus           = 0x%X\n", pscip->irDriveRegs.bCommandReg);
}

//---------------------------------------------------------------------
// Open SMART to allow DeviceIoControl communications.
//---------------------------------------------------------------------
HANDLE OpenSMART(VOID)
{
HANDLE	hSMARTIOCTL = 0;
	
#ifdef WINDOWS9X
	// Version Windows 95 OSR2, Windows 98
	if ((hSMARTIOCTL = CreateFile("\\\\.\\SMARTVSD", 0,0,0,
                        CREATE_NEW, 0, 0)) == INVALID_HANDLE_VALUE)
	{
		printf("Unable to open SMARTVSD, error code: 0x%lX\n", GetLastError());
    }
   	else
	{
		printf("SMARTVSD opened successfully\n");
	}
#else
	// Windows NT, Windows 2000
	if ((hSMARTIOCTL = CreateFile("\\\\.\\PhysicalDrive0",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,
							OPEN_EXISTING,0,NULL)) == INVALID_HANDLE_VALUE)
	{
		printf("Unable to open physical drive, error code: 0x%lX\n", GetLastError());
    }
   	else
	{
		printf("Physical drive opened successfully\n");
	}
#endif

	return hSMARTIOCTL;

}
