Contributor: IVAN HAMILTON

{

 The code below identifies some of the IDE params. Actually, it identifies
 most of what you'd ever need.
 Theres only one catch. The main program checks the letter given on the
 command line using a function:
 DriveValid(Drive: Char; var Drv: Byte): Boolean; assembler;
 This returns the numeric equivalent of the Drive character into Drv and
 returns true/false if the Drive is Valid. The trick is the validity check
 is done by DOS. Meaning that on my machine, my second drive, a HPFS drive
 won't show up unless I stick something like Drive=3 (Second HDD) and
 disable the DriveValid check. I hope this helps.

}

(*******************************************************************
    idediag
    shows characteristics of IDE hard disks.
    Public Domain by Paolo Bevilacqua, Rome.
    Rewritten from C to Turbo Pascal 7.0 by Ivan Peev, Sofia.
    You can add more disk type to the idetypes[]
    table, and distribuite freely.
********************************************************************)

const
{ read/write }
   HDC_DATA    = $01F0;
   HDC_ERROR   = $01F1;
   HDC_SECCOU  = $01F2;
   HDC_SECNUM  = $01F3;
   HDC_CYLLOW  = $01F4;
   HDC_CYLHIGH = $01F5;
   HDC_SDH     = $01F6;

{ read }
   HDC_STATUS  : Word = $01F7;
   HDC_ALTSTA  = $03F6;

{ write }
   HDC_COMMAND = $01F7;
   HDC_FIXED   = $03F6;

{ commands }
   HDC_COMMAND_RESTORE = $10;
   HDC_COMMAND_SEEK    = $70;
   HDC_COMMAND_READ    = $20;
   HDC_COMMAND_WRITE   = $30;
   HDC_COMMAND_FORMAT  = $50;
   HDC_COMMAND_READVER = $90;
   HDC_COMMAND_DIAG    = $90;
   HDC_COMMAND_SETPAR  = $91;
   HDC_COMMAND_WRSTACK = $E8;
   HDC_COMMAND_RDSTACK = $E4;
   HDC_COMMAND_READPAR = $EC;
   HDC_COMMAND_POWER   = $E0;

   HDC_FIXED_IRQ       = $02;
   HDC_FIXED_RESET     = $04;

   HDC_STATUS_ERROR    = $01;
   HDC_STATUS_INDEX    = $02;
   HDC_STATUS_ECC      = $04;
   HDC_STATUS_DRQ      = $08;
   HDC_STATUS_COMPLETE = $10;
   HDC_STATUS_WRFAULT  = $20;
   HDC_STATUS_READY    = $40;
   HDC_STATUS_BUSY     = $80;

type
   TIdeTypes = record
      Cylinders,
      Heads,
      Sectors: Word;
      Name: String[38];
   end;

   PIdeInfo = ^TIdeInfo;
   TIdeInfo = record
      genconf,
      fixcyls,
      remcyls,
      heads,
      bytetrack,                     { bytes per track }
      bytesector,                    { bytes per sector }
      sectors,                             { sectors per track }
      byteisg,                             { bytes intesector gap }
      byteplo,                             { bytes in sync }
      worduniq: Word;                { words unique status }
      serial: array[1..20] of Char;
      contype,                       { controller type }
      bufsiz,                             { buffer size in 512 byte blocks }
      byteecc: Word;                     { ECC bytes trasferred in read/write long }
      firmware: array[1..8] of Char; { firmware revision }
      model: array[1..40] of Char;   { model ID }
      secsint,                             { number of sectors transferred per
interrupt }      dblword,                             { double word transfer flag }
      writepro: Word;                { write protect }
   end;

const
   IdesInDataBase = 17;

   IdeTypes: array[1..IdesInDataBase] of TIdeTypes =
   ((Cylinders:667;  Heads:4;  Sectors:33; Name:'Fujitsu M2611T (42.9 MB)'),
    (Cylinders:667;  Heads:8;  Sectors:33; Name:'Fujitsu M2612T (85.9 MB)'),
    (Cylinders:667;  Heads:12; Sectors:33; Name:'Fujitsu M2613T (128.9 MB)'),
    (Cylinders:667;  Heads:16; Sectors:33; Name:'Fujitsu M2614T (171.9 MB)'),
    (Cylinders:782;  Heads:2;  Sectors:27; Name:'Western Digital WD93024-A (20.6 MB)'),
    (Cylinders:782;  Heads:4;  Sectors:27; Name:'Western Digital WD93044-A (41.2 MB)'),
    (Cylinders:845;  Heads:3;  Sectors:35; Name:'Toshiba MK232FC (45.4 MB'),
    (Cylinders:845;  Heads:7;  Sectors:35; Name:'Toshiba MK234FC (106 MB'),
    (Cylinders:965;  Heads:5;  Sectors:17; Name:'Quantum ProDrive 40AT (40 MB)'),
    (Cylinders:965;  Heads:10; Sectors:17; Name:'Quantum ProDrive 80AT (80 MB)'),
    (Cylinders:1050; Heads:2;  Sectors:40; Name:'Teac SD-340 (41 MB)'),
    (Cylinders:776;  Heads:8;  Sectors:33; Name:'Conner CP-3104 (100 MB)'),
    (Cylinders:745;  Heads:4;  Sectors:28; Name:'Priam 3804M (40.7 MB)'),
    (Cylinders:980;  Heads:10; Sectors:17; Name:'Western Digitial Caviar AC280 (81 MB)'),
    (Cylinders:560;  Heads:6;  Sectors:26; Name:'Seagate ST157A (42 MB)'),
    (Cylinders:732;  Heads:8;  Sectors:35; Name:'ALPS ELECTRIC Co.,LTD. DR311C (102 MB)'),
    (Cylinders:0;    Heads:0;  Sectors:0;  Name:''));

type
   parray = ^tarray;
   tarray = array[1..256] of Word;

var
   secbuf: parray;
   drive: Byte;
   drv: String[1];

procedure printinfo;

var
   id: TIdeInfo;
   capacity: Word;
   types: String;
   i: Integer;

   function zo(const value: Byte): String;
   begin
      if Boolean(value) then
         zo := ''
      else
         zo := 'not';
   end;

   function ToStr(value: LongInt): String;
   var
      S: String;
   begin
      Str(value, S);
      ToStr := S;
   end;

   function ConvertHex(Value: Word): String;

   const
      hexTable: array[0..15] of Char = '0123456789ABCDEF';

   begin
      ConvertHex := hexTable[Hi(Value) shr 4] + hexTable[Hi(Value) and $f] +
                    hexTable[Lo(Value) shr 4] + hexTable[Lo(Value) and $f];
   end;

   procedure SwapBytes(var Source, Dest; Len: Byte); assembler;
   asm
       push  ds

       lds   si, Source
       les   di, Dest
       mov   cl, len
       xor   ch, ch

   @1: mov   ax, ds:[si]
       xchg  ah, al
       mov   es:[di], ax
       inc   si
       inc   si
       inc   di
       inc   di
       loop  @1

       pop    ds
   end;

begin
   id := PIdeInfo(secbuf)^;

   { get disk type by characteristics }
   i := 1;
   while IdeTypes[i].Cylinders <> 0 do
      Begin
         if (IdeTypes[i].cylinders = id.fixcyls) and
            (IdeTypes[i].heads = id.heads) and
            (IdeTypes[i].sectors = id.sectors) then
            Begin
               types := IdeTypes[i].name;
               break;
            end;
         inc(i);
      end;

   { unknown disk }
   if (IdeTypes[i].cylinders = 0) then
      Begin
         types := 'Unknown ';

         { calculate capacity in MB }
         capacity := (LongInt(id.fixcyls) * id.heads * id.sectors) div 2048;
         types := types + ToStr(capacity);
         types := types + ' Mbytes';
      end;

   { swap bytes in ASCII fields except for WD disks }

   if (i <> 4) and (i <> 5) then
      Begin
         SwapBytes(id.serial, id.serial, 10);
         SwapBytes(id.firmware, id.firmware, 4);
         SwapBytes(id.model, id.model, 20);
      end;

   WriteLn('Informations for drive ', drive-2, ', ', types);
   WriteLn('Drive ID ', ConvertHex(id.genconf));
   WriteLn(id.fixcyls, ' fixed cylinders, ', id.remcyls, ' removables');
   WriteLn(id.heads, ' heads, ', id.sectors, ' sectors');
   WriteLn('Serial number: ', id.serial);
   WriteLn('Controller firmware: ', id.firmware);
   WriteLn('Controller model: ', id.model);
   WriteLn(id.bytetrack, ' bytes per track, ', id.bytesector, ' per sector');
   WriteLn(id.byteisg, ' bytes of intersector gap, ', id.byteplo, ' of sync');
   WriteLn('Controller type ', id.contype, ', buffer ', id.bufsiz div 2, 'KBytes');
   WriteLn(id.byteecc, ' bytes of ECC, ', id.secsint, ' sector(s) transferred per interrupt');
   WriteLn('Double word transfer ', zo(id.dblword),' allowed, ', zo(id.writepro), 'write protected.');
end;

procedure readsect; assembler;
asm
{ poll DRQ }
@1: mov   dx, HDC_STATUS
    in    al, dx
    and   al, HDC_STATUS_BUSY
    or    al, al
    jne   @1

{ read up sector }
    mov   cx, 256
    mov   dx, HDC_DATA
    les   di, secbuf
@2: in    ax, dx
    mov   es:[di], ax
    inc   di
    inc   di
    loop  @2
end;

function DriveValid(Drive: Char; var Drv: Byte): Boolean; assembler;
asm
    mov   ah, 19h      { Save the current drive in BL }
    int   21h
    mov   bl, al
    mov   dl, Drive    { Select the given drive }
    sub   dl, 'A'
    les   di, DRV
    mov   es:[di], dl
    mov   ah, 0Eh
    int   21h
    mov   ah, 19h      { Retrieve what DOS thinks is current }
    int   21h
    mov   cx, 0        { Assume false }
    cmp   al, dl       { Is the current drive the given drive? }
    jne   @1
    mov   cx, 1        { It is, so the drive is valid }
    mov   dl, bl       { Restore the old drive }
    mov   ah, 0eh
    int   21h
@1: xchg  ax, cx       { Put the return value into AX }
end;

function CurDisk: Byte; assembler;
{ Returns current drive }
asm
    mov   ah, 19h
    int   21h
end;

begin
   if ParamCount > 0 then
      Begin
         drv := ParamStr(1);
         drv[1] := UpCase(drv[1]);
         if not DriveValid(drv[1], Drive) or not (drv[1] in ['C'..'Z']) then
            Begin
               WriteLn('There isn''t such drive or drive invalid!');
               Halt(1);
            end;
      end
   else
      drive := CurDisk;

   { disable interrupt from drive }
   Port[HDC_FIXED] := HDC_FIXED_IRQ;

   { set up task file parameter }
   Port[HDC_SDH] := $A0 + (drive shl 4);

   { issue read parameters }
   Port[HDC_COMMAND] := HDC_COMMAND_READPAR;

   GetMem(secbuf, SizeOf(secbuf));

   { read up sector }
   readsect;

   { print out info }
   printinfo;

   FreeMem(secbuf, SizeOf(secbuf));
end.