'===========================================================================
' Subject: DISPLAY ATA/ATAPI DEVICE INFO      Date: 06-18-98 (00:19)       
'  Author: Alec Skelly                        Code: QB, PDS                
'  Origin: skellya@usa.net                  Packet: DISK.ABC
'===========================================================================
'****************************************************************************
'***            PIO_IN by Alec Skelly 6-17-98 (QuickBasic 4.5)            ***
'*** This program displays ATA/ATAPI device information.  Because it uses ***
'*** the device's own protocol (PIO Data In) to talk directly through the ***
'*** controller, it can detect devices that aren't configured in CMOS.    ***
'*** It will not detect devices under WIN95/98--reboot to MS-DOS mode.    ***
'*** A far more powerful version of this program, ATA_INFO, can be down-  ***
'*** loaded free from my web page: http://www.geocities.com/Athens/8254.  ***
'****************************************************************************
DECLARE SUB PIODataIn (port%, device%, Buffer AS ANY, opcode%, result%, nest%)
DECLARE FUNCTION INP16% (port%)
DECLARE SUB POKE16 (ptr%, word%)
DECLARE SUB StringFix (t$)
DEFINT A-Z
'$INCLUDE: 'QB.BI'
TYPE ATA
  GenConfig AS INTEGER        'For a complete description of these
  LogCyl AS INTEGER           'fields, obtain a copy of a recent ATA spec.
  Reserved1 AS INTEGER
  LogHead AS INTEGER
  Vendor1 AS INTEGER
  Vendor2 AS INTEGER
  LogSect AS INTEGER
  Vendor3 AS INTEGER
  Vendor4 AS INTEGER
  Vendor5 AS INTEGER
  Serial AS STRING * 20       'serial number
  Vendor6 AS INTEGER
  Vendor7 AS INTEGER
  ReadBytes AS INTEGER
  Revision AS STRING * 8      'firmware revision
  Model AS STRING * 40        'model name/number
  RWMulti AS INTEGER
  Reserved2 AS INTEGER
  Capabilities AS INTEGER
  Reserved3 AS INTEGER
  PIOMode AS INTEGER
  DMAMode AS INTEGER
  FieldsValid AS INTEGER
  CurCyl AS INTEGER
  CurHead AS INTEGER
  CurSect AS INTEGER
  CurTotSect AS LONG
  CurRWMulti AS INTEGER
  LBASectors AS LONG
  SingleDMA AS INTEGER
  MultiDMA AS INTEGER
  AdvPIOMode AS INTEGER
  MinDMAns AS INTEGER
  DMAns AS INTEGER
  MinPIOns AS INTEGER
  MinPIOCHRDYns AS INTEGER
  Reserved4 AS STRING * 38
  UDMA AS INTEGER
  Reserved5 AS STRING * 334
END TYPE
TYPE DataBuffer
  b AS STRING * 512
END TYPE
DIM Buffer AS DataBuffer
DIM ATADevice AS ATA
DATA &H1F0, &H170, &H168, &H1E8
Disk = -1
FOR i = 1 TO 4
  READ port
  FOR dev = 0 TO 1
    Buffer.b = ""
    PIODataIn port, dev, Buffer, &HEC, result, 0
    LSET ATADevice = Buffer
    IF result THEN
      Disk = Disk + 1
      StringFix ATADevice.Model
      StringFix ATADevice.Revision
      StringFix ATADevice.Serial
      CLS
      COLOR 15
      PRINT "Device"; Disk; "("; HEX$(port); ","; HEX$(dev); ")"
      PRINT "ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ"
      COLOR 7
      PRINT "Type     : ";
      IF result > 0 THEN a$ = "ATA" ELSE a$ = "ATAPI"
      PRINT a$
      PRINT "Model    : "; LTRIM$(RTRIM$(ATADevice.Model))
      PRINT "Revision : "; LTRIM$(RTRIM$(ATADevice.Revision))
      PRINT "Serial   : "; LTRIM$(RTRIM$(ATADevice.Serial))
      sz& = (ATADevice.LogCyl + 1&) * (ATADevice.LogHead + 1&)
      sz& = sz& * ATADevice.LogSect \ 2048&
      PRINT "Size     :"; sz&; "MB"
      COLOR 15
      PRINT "ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ"
      COLOR 7
      PRINT
      PRINT "Press ESC to exit or any other key to continue...";
      k$ = ""
      DO
	k$ = INKEY$
      LOOP WHILE LEN(k$) = 0
      LOCATE , 1: PRINT SPACE$(80);
      IF k$ = CHR$(27) THEN END
    END IF
  NEXT
NEXT
IF Disk = -1 THEN PRINT "No devices detected."
END

'****************************************************************************
'***         INP16 is like INP but reads a word instead of a byte         ***
'****************************************************************************
FUNCTION INP16 (port)
d = 0: dptr = VARPTR(d)
ASM$ = CHR$(&HBA) + MKI$(port)                     'mov dx,port
ASM$ = ASM$ + CHR$(&HED)                           'in ax,dx
ASM$ = ASM$ + CHR$(&HBB) + MKI$(dptr)              'mov bx,dptr
ASM$ = ASM$ + CHR$(&H89) + CHR$(7)                 'mov [bx],ax
ASM$ = ASM$ + CHR$(&HCB)                           'retf
CALL ABSOLUTE(SADD(ASM$))
INP16 = d
END FUNCTION

'***************************************************************************
'*** PIODataIn is a limited, slightly out of spec implementation of the  ***
'*** ATA PIO Data In protocol.  In this case, "out of spec" means "works ***
'*** even with old or out of spec hard drives."
'*** ---------------------------------------------
'*** port is the controller's base address in hex.
'*** device is the device number: 0 for master, 1 for slave.
'*** Buffer is a TYPEd 512 byte string.
'*** opcode is the ATA command number: ECh for IDENTIFY DEVICE
'***                                   A1h for ATAPI IDENTIFY DEVICE
'*** result is the number of bytes read (negative value indicates ATAPI)
'*** nest is a counter to keep track of recursion--start with 0.
'*** ---------------------------------------------
'***************************************************************************
SUB PIODataIn (port, device, Buffer AS DataBuffer, opcode, result, nest)
nest = nest + 1
dev = device * 16                                   'device #: 0=0, 1=16
ptr = VARPTR(Buffer)
result = 0
tout& = 25000                                       'timeout loop count
GOSUB WaitForNotBusy
OUT port + 6, dev                                   'set device #
GOSUB WaitForNotBusy
IF INP(port + 7) = 0 THEN EXIT SUB                  'device not present
t& = 0
DO WHILE ((INP(port + 7) AND 64) = 0) AND (t& < tout& * 2) 'wait for DRDY=1
  t& = t& + 1
LOOP
OUT port + 7, opcode                                'issue command
FOR r = 0 TO 510 STEP 2
  GOSUB WaitForNotBusy
  IF t& >= tout& THEN EXIT FOR                      'timed out
  a = INP(port + 7)                                 'get status
  IF (a AND 1) THEN                                 'if ERR=1
    IF (INP(port + 1) AND 4) AND nest < 2 THEN        'if ABRT=1
      PIODataIn port, device, Buffer, &HA1, result, nest 'ATAPI Identify Dev.
      result = result * -1
    END IF
    EXIT SUB
  END IF
  a = INP16(port)                                   'read word
  POKE16 ptr + r, a                                 'copy to buffer
  result = r + 2
NEXT
EXIT SUB

WaitForNotBusy:
t& = 0
DO WHILE (INP(port + 7) AND 128) AND (t& < tout&)   'wait for BSY=0
  t& = t& + 1
LOOP
RETURN

END SUB

'****************************************************************************
'***      POKE16 is like POKE but writes a word instead of a byte         ***
'****************************************************************************
SUB POKE16 (ptr, word)
ASM$ = CHR$(&HBA) + MKI$(word)                   'mov dx,word
ASM$ = ASM$ + CHR$(&HBB) + MKI$(ptr)             'mov bx,ptr
ASM$ = ASM$ + CHR$(&H89) + CHR$(&H17)            'mov [bx],dx
ASM$ = ASM$ + CHR$(&HCB)                         'retf
CALL ABSOLUTE(SADD(ASM$))
END SUB

'****************************************************************************
'***  ATA spec. requires each pair of bytes in a string to be inverted.   ***
'***  StringFix returns them to readable form.                            ***
'****************************************************************************
SUB StringFix (t$)
DO WHILE a < LEN(t$)
  a = a + 2
  a$ = a$ + MID$(t$, a, 1)
  a$ = a$ + MID$(t$, a - 1, 1)
LOOP
FOR i = 1 TO LEN(a$)
  IF MID$(a$, i, 1) = CHR$(0) THEN MID$(a$, i) = " "
NEXT
t$ = a$
END SUB
