unit HDScsiInfo;
interface
uses
Windows, SysUtils;
const
IDENTIFY_BUFFER_SIZE = 512;
FILE_DEVICE_SCSI = $0000001b;
IOCTL_SCSI_MINIPORT_IDENTIFY = ((FILE_DEVICE_SCSI
shl 16) + $0501);
IDE_ATA_IDENTIFY = $
EC;
// Returns ID sector for ATA.
IOCTL_SCSI_MINIPORT = $0004D008;
// see NTDDSCSI.H for definition
type
TDiskData =
array [0..256-1]
of DWORD;
TDriveInfo =
record
ControllerType: Integer;
//0 - primary, 1 - secondary, 2 - Tertiary, 3 - Quaternary
DriveMS: Integer;
//0 - master, 1 - slave
DriveModelNumber:
String;
DriveSerialNumber:
String;
DriveControllerRevisionNumber:
String;
ControllerBufferSizeOnDrive: Int64;
DriveType:
String;
//fixed or removable or unknown
DriveSizeBytes: Int64;
end;
THDScsiInfo =
class (TObject)
private
FDriveNumber: Byte;
FFileHandle: Cardinal;
FInfoAvailable: Boolean;
FProductRevision:
string;
FSerialNumber:
string;
FControllerType: Integer;
FDriveMS: Integer;
FDriveModelNumber:
string;
FControllerBufferSizeOnDrive: Int64;
FDriveType:
string;
FDriveSizeBytes: Int64;
procedure ReadInfo;
procedure SetDriveNumber(
const Value: Byte);
procedure PrintIdeInfo(DiskData: TDiskData);
public
constructor Create;
property DriveNumber: Byte
read FDriveNumber
write SetDriveNumber;
property ProductRevision:
string read FProductRevision;
property SerialNumber:
string read FSerialNumber;
property ControllerType: Integer
read FControllerType;
property DriveMS: Integer
read FDriveMS;
property DriveModelNumber:
string read FDriveModelNumber;
property ControllerBufferSizeOnDrive: Int64
read FControllerBufferSizeOnDrive;
property DriveType:
string read FDriveType;
property DriveSizeBytes: Int64
read FDriveSizeBytes;
function IsInfoAvailable: Boolean;
end;
implementation
type
SRB_IO_CONTROL =
record
HeaderLength: Cardinal;
Signature:
array [0..8-1]
of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
end;
PSRB_IO_CONTROL = ^SRB_IO_CONTROL;
DRIVERSTATUS =
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..1]
of Byte;
// Reserved for future expansion.
dwReserved:
array [0..1]
of Longword;
// Reserved for future expansion.
end;
SENDCMDOUTPARAMS =
record
cBufferSize: Longword;
// Size of bBuffer in bytes
DriverStatus: DRIVERSTATUS;
// Driver status structure.
bBuffer:
array [0..0]
of Byte;
// Buffer of arbitrary length in which to store the data read from the // drive.
end;
IDEREGS =
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;
// reserved for future use. Must be zero.
end;
SENDCMDINPARAMS =
record
cBufferSize: Longword;
// Buffer size in bytes
irDriveRegs: IDEREGS;
// Structure with drive register values.
bDriveNumber: Byte;
// Physical drive number to send
// command to (0,1,2,3).
bReserved:
array[0..2]
of Byte;
// Reserved for future expansion.
dwReserved:
array [0..3]
of Longword;
// For future use.
bBuffer:
array [0..0]
of Byte;
// Input buffer. //!TODO: this is array of single element
end;
PSENDCMDINPARAMS = ^SENDCMDINPARAMS;
PSENDCMDOUTPARAMS = ^SENDCMDOUTPARAMS;
IDSECTOR =
record
wGenConfig: Word;
wNumCyls: Word;
wReserved: Word;
wNumHeads: Word;
wBytesPerTrack: Word;
wBytesPerSector: Word;
wSectorsPerTrack: Word;
wVendorUnique:
array [0..3-1]
of Word;
sSerialNumber:
array [0..20-1]
of AnsiChar;
wBufferType: Word;
wBufferSize: Word;
wECCSize: Word;
sFirmwareRev:
array [0..8-1]
of AnsiChar;
sModelNumber:
array [0..40-1]
of AnsiChar;
wMoreVendorUnique: Word;
wDoubleWordIO: Word;
wCapabilities: Word;
wReserved1: Word;
wPIOTiming: Word;
wDMATiming: Word;
wBS: Word;
wNumCurrentCyls: Word;
wNumCurrentHeads: Word;
wNumCurrentSectorsPerTrack: Word;
ulCurrentSectorCapacity: Cardinal;
wMultSectorStuff: Word;
ulTotalAddressableSectors: Cardinal;
wSingleWordDMA: Word;
wMultiWordDMA: Word;
bReserved:
array [0..128-1]
of Byte;
end;
PIDSECTOR = ^IDSECTOR;
TArrayDriveInfo =
array of TDriveInfo;
type
DeviceQuery =
record
HeaderLength: Cardinal;
Signature:
array [0..8-1]
of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
cBufferSize: Longword;
// Buffer size in bytes
irDriveRegs: IDEREGS;
// Structure with drive register values.
bDriveNumber: Byte;
// Physical drive number to send
bReserved:
array[0..2]
of Byte;
// Reserved for future expansion.
dwReserved:
array [0..3]
of Longword;
// For future use.
bBuffer:
array [0..0]
of Byte;
// Input buffer. //!TODO: this is array of single element
end;
function ConvertToString (diskdata: TDiskData;
firstIndex: Integer;
lastIndex: Integer;
buf: PAnsiChar): PAnsiChar;
var
index: Integer;
position: Integer;
begin
position := 0;
// each integer has two characters stored in it backwards
for index := firstIndex
to lastIndex
do begin
// get high byte for 1st character
buf[position] := AnsiChar(Chr(diskdata [
index]
div 256));
inc(position);
// get low byte for 2nd character
buf [position] := AnsiChar(Chr(diskdata [
index]
mod 256));
inc(position);
end;
// end the string
buf[position] := Chr(0);
// cut off the trailing blanks
index := position - 1;
while (
index >0)
do begin
// if not IsSpace(AnsiChar(buf[index]))
if (AnsiChar(buf[
index]) <> '
')
then break;
buf [
index] := Chr(0);
dec(
index);
end;
Result := buf;
end;
constructor THDScsiInfo.Create;
begin
inherited;
SetDriveNumber(0);
end;
function THDScsiInfo.IsInfoAvailable: Boolean;
begin
Result := FInfoAvailable
end;
procedure THDScsiInfo.PrintIdeInfo (DiskData: TDiskData);
var
nSectors: Int64;
serialNumber:
array [0..1024-1]
of AnsiChar;
modelNumber:
array [0..1024-1]
of AnsiChar;
revisionNumber:
array [0..1024-1]
of AnsiChar;
begin
// copy the hard drive serial number to the buffer
ConvertToString (DiskData, 10, 19, @serialNumber);
ConvertToString (DiskData, 27, 46, @modelNumber);
ConvertToString (DiskData, 23, 26, @revisionNumber);
FControllerType := FDriveNumber
div 2;
FDriveMS := FDriveNumber
mod 2;
FDriveModelNumber := modelNumber;
FSerialNumber := serialNumber;
FProductRevision := revisionNumber;
FControllerBufferSizeOnDrive := DiskData [21] * 512;
if ((DiskData [0]
and $0080) <> 0)
then FDriveType := '
Removable'
else if ((DiskData [0]
and $0040) <> 0)
then FDriveType := '
Fixed'
else FDriveType := '
Unknown';
// calculate size based on 28 bit or 48 bit addressing
// 48 bit addressing is reflected by bit 10 of word 83
if ((DiskData[83]
and $400) <> 0)
then begin
nSectors := DiskData[103] * Int64(65536) * Int64(65536) * Int64(65536) +
DiskData[102] * Int64(65536) * Int64(65536) +
DiskData[101] * Int64(65536) +
DiskData[100];
end else begin
nSectors := DiskData [61] * 65536 + DiskData [60];
end;
// there are 512 bytes in a sector
FDriveSizeBytes := nSectors * 512;
end;
procedure THDScsiInfo.ReadInfo;
type
DataArry =
array [0..256-1]
of WORD;
PDataArray = ^DataArry;
const
SENDIDLENGTH = sizeof (SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE;
var
I: Integer;
buffer:
array [0..sizeof (SRB_IO_CONTROL) + SENDIDLENGTH - 1]
of AnsiChar;
dQuery: DeviceQuery;
dummy: DWORD;
pOut: PSENDCMDOUTPARAMS;
pId: PIDSECTOR;
DiskData: TDiskData;
pIdSectorPtr: PWord;
begin
FInfoAvailable := False;
FFileHandle := CreateFile (PChar(Format('
\\.\Scsi%d:', [FDriveNumber])),
GENERIC_READ
or GENERIC_WRITE,
FILE_SHARE_READ
or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING, 0, 0);
if (FFileHandle <> INVALID_HANDLE_VALUE)
then begin
ZeroMemory(@dQuery, SizeOf(dQuery));
dQuery.HeaderLength := sizeof (SRB_IO_CONTROL);
dQuery.Timeout := 10000;
dQuery.Length := SENDIDLENGTH;
dQuery.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;
StrLCopy(@dQuery.Signature, '
SCSIDISK', 8);
dQuery.irDriveRegs.bCommandReg := IDE_ATA_IDENTIFY;
dQuery.bDriveNumber := FDriveNumber;
if (DeviceIoControl (FFileHandle, IOCTL_SCSI_MINIPORT,
@dQuery,
SizeOf(dQuery),
@buffer,
sizeof (SRB_IO_CONTROL) + SENDIDLENGTH,
dummy,
nil))
then begin
pOut := PSENDCMDOUTPARAMS(buffer + sizeof (SRB_IO_CONTROL));
//!TOCHECK
pId := PIDSECTOR(@pOut^.bBuffer[0]);
if (pId^.sModelNumber[0] <> Chr(0) )
then begin
pIdSectorPtr := PWord(pId);
for I := 0
to 256-1
do
DiskData[I] := PDataArray(pIdSectorPtr)[I];
PrintIdeInfo (DiskData);
FInfoAvailable := True;
end;
end;
CloseHandle(FFileHandle);
end;
end;
procedure THDScsiInfo.SetDriveNumber(
const Value: Byte);
begin
FDriveNumber := Value;
ReadInfo;
end;
end.