Page 1 of 1

ATA-ATAPI detection

Posted: Wed Aug 12, 2009 3:48 pm
by ~
There seems to be nonstandard issues to consider for doing that, and even BIOS functions for that purpose seem difficult to use, and the times I have done this the detection code has proven unstable, working correctly or partially at undetermined times.

I have some textmode DOS assembly code (attached at the end).
It shows 2 main letters in the whole screen: an "M" for Master and an "S" for Slave.
Also shown is the value read from the Status Register.
For example if it reads "M 00 S 00" it would mean that both Master and Slave status registers contain 00h.
Any of those letters should change in color if there's nothing connected to the respective bus (i.e., if status is 0,7F or FF).

For example, if I have disconnected everything from the IDE connectors of the PC, both those letters should change color to demonstrate that reading from the status ports 0x1F7 and 0x177. Unfortunately, every PC that I have, seems to return different "floating bus" values, and worst of all is that none of those values is 0xFF, which is a bad start.

I didn't get the 0x7F value either, which is recommended in this post:
http://forum.osdev.org/viewtopic.php?p=111815#p111815
=====================================================




So, I better start with this from the wiki, since I needed a 100% reliable "floating bus detection":

http://wiki.osdev.org/ATA_PIO_Mode#Dete ... ialization
Floating Bus
The disk that was selected last (by the BIOS, during boot) is supposed to maintain control of the electrical values on each IDE bus. If there is no disk connected to the bus at all, then the electrical values on the bus will all go "high" (to +5 volts). A computer will read this as an 0xFF byte -- this is a condition called a "floating" bus. This is an excellent way to find out if there are no drives on a bus. Before sending any data to the IO ports, read the Regular Status byte. The value 0xFF is an illegal status value, and indicates that the bus has no drives. The reason to read the port before writing anything is that the act of writing can easily cause the voltages of the wires to go screwey for a millisecond (since there may be nothing attached to the wires to control the voltages!), and mess up any attempt to measure "float".
--------------------------------
I tried to comply with that, and it worked unreliably. Here is sort of C code:

Code: Select all

//Our delayed loop value. The greater this 32-bit value,
//the less our test loop will last alive:
//     ECX == 0x00000000 will last the most  (too much time)
//     ECX == 0x00000001 will last the least (finishes too fast)
//
 ecx=0xFFF00000;


while(1)
{
 ecx++;   //Increase original ECX value from 0 to 0xFFFFFFFF
          //and back to 0 when overflow takes place to control
          //the limit of this loop.



 //Test the MASTER BUS to see if the
 //Status Register (7 + 1F0h) has the
 //floating value 0xFF or 0x7F or 0x00:
 //
   al=inportb(0x1F7);
   if( al==0xFF || al==0x00 || al==0x7F )
   {
                        ;//If yes, indicate that it's FLOATING
   }



 //Test the SLAVE BUS to see if the
 //Status Register (7 + 170h) has the
 //floating value 0xFF or 0x7F or 0x00:
 //
   al=inportb(0x177);
   if( al==0xFF || al==0x00 || al==0x7F )
   {
                        ;//If yes, indicate that it's FLOATING
   }

 if(ecx==0)break;
}
======================================================

And here is a small report of what values I got. As you can see, with this values it seems pretty unreliable to scan for a floating bus in this way. So, please tell me if I'm coding things the wrong way.

0x50 == whenever there's at least 1 disk connected to a bus
0x50 == in a Thinkpad 390X, this value appeared in the slave bus (with only one, single Primary Master!!!)
0x51 == in the Thinkpad 390X slave bus when there's nothing connected anywhere!!!
0x00 == in older machines, when there's nothing in a bus
0x20 == a 5-year old PCChips AMD when there's nothing in a bus
0x2A == a 4-year old AMD64 KM88-V when there's nothing in a bus

This would be a table of Master/Slave (MS) results, and see how the {YES/NO} and the {NO/NO} entries differ without pattern between PC's:


ThinkPad 390X:

Code: Select all

 _____________________________
|  M  |  S  |  Status (M-S)   |
|-----+-----+-----------------|
| No  |  No | Ma=0x00,Sl=0x51 |
| Yes |  No | Ma=0x50,Sl=0x50 |
|_____|_____|_________________|



Old PIII XCel2000:

Code: Select all

 _____________________________
|  M  |  S  |  Status (M-S)   |
|-----+-----+-----------------|
| No  | No  | Ma=0x00,Sl=0x00 |
| No  | Yes | Ma=0x00,Sl=0x50 |
| Yes | No  | Ma=0x50,Sl=0x00 |
| Yes | Yes | Ma=0x50,Sl=0x50 |
|_____|_____|_________________|



PCChips:

Code: Select all

 _____________________________
|  M  |  S  |  Status (M-S)   |
|-----+-----+-----------------|
| No  | No  | Ma=0x20,Sl=0x20 |
| No  | Yes | Ma=0x20,Sl=0x50 |
| Yes | No  | Ma=0x50,Sl=0x20 |
| Yes | Yes | Ma=0x50,Sl=0x50 |
|_____|_____|_________________|


K8MM-V:

Code: Select all

 _____________________________
|  M  |  S  |  Status (M-S)   |
|-----+-----+-----------------|
| No  | No  | Ma=0x2A,Sl=0x2A |
| No  | Yes | Ma=0x2A,Sl=0x50 |
| Yes | No  | Ma=0x50,Sl=0x2A |
| Yes | Yes | Ma=0x50,Sl=0x50 |
|_____|_____|_________________|
What should or could be done then with this idea of a floating IDE bus if a simple floating 0xFF cannot be scanned consistently but instead you get a 0x00, 0x2A, 0x20 and even 0x50 or 0x51?

Re: ATA-ATAPI detection

Posted: Wed Aug 12, 2009 8:18 pm
by geppyfx
Personally I don't seen any extreme need to test for the floating bus if you use Identify device to detect drives. Absence of some devices will be determine with reading status(good shortcut) and absence of others with Identify Device.

The way linux detects drives (or used to) is by writing some values to the drive(presumably present), reading them back and comparing for equality.

The floating bus is not something that is 100% defined or standard for every piece of hardware. Its more of a hardware malfunction or design imperfection me thinks.
0x51 == in the Thinkpad 390X slave bus when there's nothing connected anywhere!!!
What an excellent value. You got ERR bit set. Never operate with baits as a whole, always look at bits. But actually I think that only manufacture of ide controller knows that are the default values for reading status if you haven't done anything with device.

There was also written somewhere that you should start with slave (or master) detection first. Don't remember exactly but think slave first.

Re: ATA-ATAPI detection

Posted: Wed Aug 19, 2009 1:28 pm
by ~
I also have another problem with an old Seagate ST3491A. I can identify it correctly with the following code, but the problem is that it appears in both MASTER and SLAVE, causing it to detect the same drive as two, when it's connected as a single Master.

What should I do to avoid detecting this same drive twice because it seems to respond regardless the DEV bit?

I have compared it with Chris Giese's code (which works well at that) but I don't see any difference that I should apply, and I have tried to place delays and other tests according to that code, but it keeps the same:


My code:

Code: Select all

org 100h
bits 16


START:

xor edi,edi
push word 0x7FE0
pop es

mov ax,0xB800
mov ds,ax
mov gs,ax




call clearscreen
mov byte[ds:0],"1"
 mov dx,0x1F0     ;Primary
 mov al,00000000b ;Master
 call identify_device_ec
   call showregs
   call printdev
     call kbwait



call clearscreen
mov byte[ds:0],"2"
 mov dx,0x1F0     ;Primary
 mov al,00010000b ;Slave
 call identify_device_ec
   call showregs
   call printdev
     call kbwait



call clearscreen
mov byte[ds:0],"3"
 mov dx,0x170     ;Secondary
 mov al,00000000b ;Master
 call identify_device_ec
   call showregs
   call printdev
     call kbwait



call clearscreen
mov byte[ds:0],"4"
 mov dx,0x170     ;Secondary
 mov al,00010000b ;Slave
 call identify_device_ec
   call showregs
   call printdev
     call kbwait





int 20h























;//AL==DEV bit value
;//DX,ES:EDI must be properly set by some other functions
;;;
identify_device_ec:
 pushad


 push edi
 push ax
  mov cx,128
  mov eax,0x01020304
  rep stosd
 pop ax
 pop edi




  add dx,6;
   out dx,al
  sub dx,6;




  add dx,7
    mov al,0xEC
    out dx,al
  sub dx,7



  ;//Wait enough time for the
  ;//IDENTIFY DEVICE
  ;//command to complete:
  ;;;
    mov cx,11  ;Wait up to 2-3 seconds
     mov bl,0xFF    ;An invalid BCD value is needed here
                    ;for the "wait_2_sec_cmos__decCX" function
                    ;which is called by the stub "atawait" routine
    .__identifydevice_ECh__waitloop:


     call atawait   ;This call alters the BL and CX registers
                    ;to keep track how many changes have
                    ;there been
     add dx,7
     in al,dx
     sub dx,7
     test al,10000000b  ;Check the BSY bit after setting the command
     jz .checkresults
                        ;If it's clear, the command has completed
                        ;and we need to check the results


    jcxz .__identifydevice_ECh__waitloop__endl
    jmp .__identifydevice_ECh__waitloop
    .__identifydevice_ECh__waitloop__endl:

     mov ax,0xB800
     mov ds,ax
     mov byte[ds:0],"\"
     int 20h




 .checkresults:

    add dx,7
     in al,dx
    sub dx,7
   test al,00001000b   ;Watch DRQ bit to see if we have data
   jnz .goget


 jmp .endall


 

 .goget:
 mov ecx,256
 rep insw


 .endall:
 popad
ret




































atawait:
 call wait_cmos__decCX
ret




























wait_cmos__decCX:
 push ax
    mov AL,0;    ;//CMOS RAM byte offset 0 (seconds)
    out 0x70,al  ;//Send the offset to the CMOS addressing port
    in al,0x71   ;//Read the BCD seconds value from CMOS data port



   ;//Change BL and CX as soon as the
   ;//seconds value changes for the times
   ;//specified in CX:
   ;;
    mov al,0
    out 0x70,al
    in al,0x71


   cmp al,bl
   je .noe
     mov BL,AL  ;//Update BL for next comparison
     dec cx     ;//Decrease CX






   .noe:


 pop ax
ret

































;Take in AL 1 value between 0 & 255 (0-FF)
;and puts the hex ASCII hex in EAX

bin2hex:
 push ebx
  and eax,11111111b

  mov ah,al

  shr al,4
  cmp al,0x09
  ja noner1
   add al,0x30
   jmp noner1_1

  noner1:
   add al,0x37

  noner1_1:



  and ah,00001111b
  cmp ah,0x09
  ja noner2
   add ah,0x30
   jmp noner2_2

  noner2:
   add ah,0x37

  noner2_2:


  mov bl,00111111b

  shl ebx,8
  mov bl,ah

  shl ebx,8
  mov bl,00111111b

  shl ebx,8
  mov bl,al


  mov eax,ebx


 pop ebx
ret












printdev:
pushad
push ds
push es

push word 0x7FE0
pop ds     ;DS==0x7FE00


mov ax,0xB840
mov es,ax  ;ES==0xB8400





mov cx,512  ;512 bytes

xor di,di
xor si,si



.loo:
 mov al,[si]
 mov byte[es:di],al
  inc si
  inc di
  inc di

dec cx
jcxz .endloo
jmp .loo
.endloo:



pop es
pop ds
popad
ret


















clearscreen:
 pusha
  ;//Try to clear the screen:
  ;;;
    mov ah,6       ;//VIDEO - SCROLL UP WINDOW
    mov al,0       ;//number of lines by which to scroll up
                   ;//(00h = clear entire window)
    mov bh,0x7     ;//attribute used to write blank lines
                   ;//at bottom of window
    xor cx,cx      ;//row,column of window's upper left corner
    mov dx,0x1950  ;//80x25 ;DH,DL row,column of window's
                   ;//lower right corner
     int 10h

  ;//Set position of cursor at top of screen:
  ;;;
    mov ah,2   ;//VIDEO - SET CURSOR POSITION
    mov bh,0   ;//page number
               ;//   0-3 in modes 2&3
               ;//   0-7 in modes 0&1
               ;//   0 in graphics modes
    xor dx,dx  ;//DH,DL row (00h is top), column (00h is left)
      int 10h
 popa
ret




















kbwait:
 xor ah,ah
 int 16h
ret

























showregs:
pushad
push ds
push es
push fs
push gs








 mov edi,0x7000
 mov ax,0
 mov ds,ax
 mov es,ax
 mov gs,ax



 mov ax,0xB800
 mov fs,ax







 ;//Now we shall read the signature:
 ;//
  ;//Try to read the master status
  ;;;

   ;Save original device register
    add dx,6
     in al,dx
    sub dx,6
    mov bl,al


    mov al,00000000b
    add dx,6
    out dx,al
    sub dx,6



   xor ax,ax ;counter
   .__signatureloopmaster:
    inc dx    ;point to 0x1F1 or 0x171
    inc al   ;count one more signature byte read

    a32 insb   ;Read 0x1F1-0x1F6 or 0x171-0x176


   cmp al,6
   jne .__signatureloopmaster
   sub dx,ax





  ;//Try to read the slave status
  ;;;
    mov al,00010000b
    add dx,6
    out dx,al
    sub dx,6



    xor ax,ax  ;counter
   .__signatureloopslave:
    inc dx    ;point to 0x1F1 or 0x171
    inc al   ;count one more signature byte read

    a32 insb   ;Read 0x1F1-0x1F6 or 0x171-0x176


   cmp al,6
   jne .__signatureloopslave
   sub dx,ax








   ;Restore original device register
    mov al,bl
    add dx,6
     out dx,al
    sub dx,6









 mov edi,0x7000
 mov ax,0
 mov ds,ax
 mov gs,ax



 mov ax,0xB800
 mov fs,ax





mov al,[gs:di]
call bin2hex
mov dword[fs:20],eax

mov al,[gs:di+1]
call bin2hex
mov dword[fs:24],eax

mov al,[gs:di+2]
call bin2hex
mov dword[fs:28],eax

mov al,[gs:di+3]
call bin2hex
mov dword[fs:32],eax

mov al,[gs:di+4]
call bin2hex
mov dword[fs:36],eax

mov al,[gs:di+5]
call bin2hex
mov dword[fs:40],eax



;;slave
mov al,[gs:di+6]
call bin2hex
mov dword[fs:48],eax

mov al,[gs:di+7]
call bin2hex
mov dword[fs:52],eax

mov al,[gs:di+8]
call bin2hex
mov dword[fs:56],eax

mov al,[gs:di+9]
call bin2hex
mov dword[fs:60],eax

mov al,[gs:di+10]
call bin2hex
mov dword[fs:64],eax

mov al,[gs:di+11]
call bin2hex
mov dword[fs:68],eax


pop gs
pop fs
pop es
pop ds
popad
ret


Chris Giese code:

Code: Select all

/*****************************************************************************
ATA (IDE) hard drive detection code
Chris Giese <[email protected]>	http://my.execpc.com/~geezer
Release date: May 13, 2003
This code is public domain (no copyright).
You can do whatever you want with it.

This code based on Phoenix document
"Autotyping ATA Disk Drives", version 0.9 (July 26, 1995)

This code builds with Turbo C, DJGPP, or 16- or 32-bit Watcom C
It will NOT work inside a Windows DOS box
Not tested with ATAPI drives (e.g. IDE CD-ROMs)
*****************************************************************************/
#include <stdio.h> /* printf() */
#include <dos.h> /* inportb(), delay() */

#if defined(__WATCOMC__)
#include <conio.h>
#define	inportb(P)	inp(P)
#define	outportb(P,V)	outp(P,V)
#endif
/*****************************************************************************
*****************************************************************************/
static void msleep(unsigned milliseconds)
{
	delay(milliseconds);
}
/*****************************************************************************
*****************************************************************************/
static void nsleep(unsigned nanoseconds)
{
}
/*****************************************************************************
*****************************************************************************/
static void detect_drives(unsigned io_adr)
{
	static /*const*/ char *msg[] =
	{
		"Unknown error", "OK", "Formatter device error",
		"Sector buffer error", "ECC circuitry error",
		"Controller microprocessor errror"
	};
/**/
	unsigned timeout, byte0, byte1;

printf("Interface 0x%03X:\n", io_adr);
/* check for 0xFF; the value of a floating bus
"debounce" this value for a period of 20 ms */
	for(timeout = 20; timeout != 0; timeout--)
	{
		if(inportb(io_adr + 7) != 0xFF)
			break;
		msleep(1);
	}
/* probe cylinder registers to make absolutely sure there's no drive */
	if(timeout == 0)
	{
printf("floating bus; probing cylinder registers...\n");
		outportb(io_adr + 4, 0x55);
		outportb(io_adr + 5, 0xAA);
		byte0 = inportb(io_adr + 4);
/* if drive is present, next inportb() will read 0xAA. May read 0xAA
even if no drive (because of bus capacitance), so force 0x55 onto bus */
		outportb(0x80, 0x55);
		byte1 = inportb(io_adr + 5);
		if(byte0 != 0x55 || byte1 != 0xAA)
		{
printf("floating bus; no drive(s) present\n");
			return;
		}
	}
/* wait (xxx - how long?) until BSY=0 */
	for(timeout = 5000; timeout != 0; timeout--)
	{
		if((inportb(io_adr + 7) & 0x80) == 0)
			break;
		msleep(1);
	}
	if(timeout == 0)
	{
printf("BSY != 0; no drive(s) present\n");
		return;
	}
/* Execute Drive Diagnostics */
	outportb(io_adr + 7, 0x90);
	nsleep(400);
/* ...can take up to 5 seconds
I think you can use interrupts here, if you want */
	for(timeout = 5000; timeout != 0; timeout--)
	{
		if((inportb(io_adr + 7) & 0x80) == 0)
			break;
		msleep(1);
	}
	if(timeout == 0)
	{
printf("Execute Drive Diagnostics did not complete; no drive(s) present\n");
		return;
	}
	byte0 = inportb(io_adr + 1);	/* b0-b6=master diagnostic code */
	byte1 = byte0 & 0x7F;		/* b7=1 if slave error */
	if(byte1 >= sizeof(msg) / sizeof(msg[0]))
		byte1 = 0;
printf("  master drive: %s\n", msg[byte1]);
printf("  slave  drive: ");
	if(byte0 < 0x80)
printf("NONE\n");
	else
	{
		outportb(io_adr + 6, 0xB0); /* select slave */
		nsleep(400);
		byte0 = inportb(io_adr + 1); /* read slave diagnostic */
		byte1 = byte0 & 0x7F;
		if(byte1 >= sizeof(msg) / sizeof(msg[0]))
			byte1 = 0;
printf("%s\n", msg[byte1]);
	}
/* at this point, according to the Phoenix docs, you should
use the ATA "Identify Device" command to get the following info:
- Maximum sector count for Read Multiple and Write Multiple commands
- LBA capability	- CHS geometry
- Fastest PIO mode	- Fastest DMA mode

I would soft-reset the drive(s), then test the registers to see
if we've got ATA or ATAPI drives, and use the appropriate version
of "Identify Device" */
}
/*****************************************************************************
*****************************************************************************/
int main(void)
{
	detect_drives(0x1F0);
	detect_drives(0x170);
	return 0;
}

I must also say that the "floating bus" method is a valid one, which may or may not work across different machines and disks, for what I have been testing. But as you can see in this Phoenix document that's just a part of the detection:

http://phoenix.com/NR/rdonlyres/BE84F91 ... utohdd.pdf
Image

I think that the wiki should state somehow that a "floating bus" is in fact the one that has no drives attached, and to detect it the theory says that, apart from reading the original STATUS REGISTER value, other registers should be written to see if they keep their values (if so, then there's something to be detected).

Re: ATA-ATAPI detection

Posted: Wed Aug 19, 2009 10:50 pm
by bewing
The problem with your seagate disk is probably a jumper setting on the disk itself that is wrong.

And the problem with that method of detecting a non-floating bus is that when you write a value to a floating bus, you may very well be able to read the same value back off the bus because of stray capacitance -- even with no drives connected.

If you get a legal value on the status register, you simply have to assume that the bus has at least one drive on it, and just start sending IDENTIFY commands.

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 8:52 am
by ~
bewing wrote:The problem with your seagate disk is probably a jumper setting on the disk itself that is wrong.
After several days of testing, it was clear that the jumper wasn't wrong. It not even had one and thus was set to Master (an old 250MB disk).

I have found several weird ATA controllers in different motherboards, all of them have contradictory behaviors compared to each other, and I'd like to point it out to see what others have found or if they know of a more correct way. Note that the intention of this information is to make clear how difficult it can become detecting floating buses.

At least if I am not able to detect the floating buses, I will eventually read garbage and count that garbage as an actual drive, for the conditions that you will see below. The code I'm making has been in the need of being full of different tests, andd has failed and worked in others, but it almost starts to seem that it is finally working stably but only after investigating by hand a lot of things that are not mentioned in the ATA standards and therefore are left at the will of the different manufacturers.
=================================================

Several different combinations of disks and controllers seem to produce slightly different reactions.

But there are some major "weird" controllers:

- When a bus has nothing connected, it can have any vendor-specific pattern in ALL of its registers, from 1 to 8 (including the Alternate Status). It may contain 0x00, 0x20, 0xFF, etc. It is easy to see that it complicates finding an invalid value in the Status Register, if it contained maybe something like 0x6F.

- There are some buses that are half-floating. That means that a bus cable can have a master-only or slave-only and an empty slot. It may happen that when you select the empty slot device with the DEV bit it appears as if that bus is floating (it can produce an 0xFF, etc., see previous point) and whhen you select the slot connected to a device with the DEV bit you don't see any signs of a floating bus. But this half-floating bus doesn't happen with all IDE motherboard controllers. You can see how a whole bus with one valid side could get discarded if the tests consists of a simple reading of the Status Register if for some reason the selected slot is the one that is empty. It seems much better to read all 8 registers to make a comparison.

- There can also be floating or half-floating controllers that retain the last value written beyond capacitance time when you write the Device Register (it stays there until you change it again), at least it was so in one of my machines. It means that if you write for example an 0xB1 to the Device Register and it's a half-floating bus, all 8 registers of the floating slot, be it master or slave, will get the value of 0xB1, as well as the Device Register of the valid slot.

- If you write something into the Device Register in a controller of the type of the previous point, and you write it in the side of a master-only or slave only, that value will also affect all the registers of the floating side. For example if you write a 0x10 in the side of an existing Slave, the Master side, which is assumed here to be floating, will have all its registers set to 0x10 because of the write to the Device Register, even for selecting the Slave.

- In Bochs, a floating bus retains permanently the value of the Device Register, and all other registers seem to stay at 0x00.
==============================================


So the best floating bus test I have been able to build is as follows. Please point out some other way of doing it so I or others that need it can test it. There are two different test. Both them may fail in different ways and situations and assume that there's nothing when in fact there is something.

These floating bus tests should be done for both devices by selecting them with the DEV bit to read the validity of the Master and the Slave slots.



Test 1:
=========

This test is at least good for:
- valid buses with something connected
- floating buses with static values, specially with the value of 0xFF


This test will fail with floating buses that change to the last value written to them, or maybe to those floating buses that don't contain 0xFF in their registers.


We will write several ATA registers and will see if they keep their values. If all those registers keep their values, then we must assume that there is something.

Code: Select all

ata_atapi_floating_test1:
pushad


;//0015 -- Write AAh to "Cylinder Low" Register (as for ATA-3)
;//also known as "LBA Mid" Register (as for ATA-6, ATA-7)
;;;
 .gotoStep0015:
   add dx,4     ;Point to the register
    mov al,0xAA
    out dx,al
   sub dx,4


;//0016 -- Write 55h to "Cylinder High" Register (as for ATA-3)
;//also known as "LBA High" Register (as for ATA-6, ATA-7)
;;;
 .gotoStep0016:
   add dx,5    ;Point to the register
    mov al,0x55
    out dx,al
   sub dx,5


;//0017 -- Write FFh to "Sector Count" Register:
;;;
 .gotoStep0017:
   add dx,2      ;Point to the register
    mov al,0xFF
    out dx,al
   sub dx,2


;//0018 -- Read "Cylinder Low" Register (as for ATA-3)
;//also known as "LBA Mid" Register (as for ATA-6, ATA-7)
;;;
 .gotoStep0018:
   add dx,4        ;Point to the register
    in al,dx
   sub dx,4


;//0019 -- Value==AAh?
;;;
 .gotoStep0019:
  cmp al,0xAA
  je .gotoStep0005  ;If yes, indicate there's something



;//0020 -- Value==FFh?
;;;
 .gotoStep0020:
  cmp al,0xFF        ;This is why this test is more specific for 0xFF
  je .gotoStep0006   ;If yes, the channel seems to be empty


 ;If not, let's .gotoStep0021



;//0021 -- Read Cylinder High Register
;;;
 .gotoStep0021:
  add dx,5
  in al,dx
  sub dx,5


;//0022 -- Value==55h?
;;;
 .gotoStep0022:
  cmp al,0x55
  je  .gotoStep0005  ;If yes, indicate there's something




  jmp .gotoStep0006  ;If not, goto "Yes, bus is floating"



.end:

popad
ret


.gotoStep0005
clc       ;INDICATE THERE IS SOMETHING
jmp .end


.gotoStep0006
stc       ;INDICATE THAT THE BUS IS FLOATING
jmp .end





Test 2:
=========

This test makes use of the Test 1 and its own checkings.

This test is at least good for:
- floating buses that contain any and the same pattern in all of their registers



What we must do is individually select a device with the DEV bit again just like before and then still see if all of the 8 registers (except the Data Register) contain the exact same value. If so, then we must assume there's nothing there. Otherwise we will eventually read garbage when we need to use IDENTIFY xxxxx commands in case all correct bits (DRQ=1, BSY=0...) are set by a dummy value.

Code: Select all

ata_atapi__determine_active_contacts:
 pushad


 ;Select MASTER side and read the 8 registers
 ;to decide the state of the slot
 ;;;
 call ata_atapi__master
 call ata_atapi__readregs
     or byte[read_pri_sec_master_slave],00000000b  ;clear results



   ;See if the Test 1 finds something
   ;;;
    call contacts_2
    jc .nocountmaster2     ;If CF is set, it means there's nothing here
     or byte[read_pri_sec_master_slave],00000001b   ;flag MASTER PRESENT
     jmp .mastercounted   ;If Test 1 found something, stop searching
   .nocountmaster2:



   ;If Test 1 didn't find anything, try to find something through
   ;Test 2:
   ;;;
   call check_valid_ata_regs
   jc .nocountmaster                ;If carry set, the contents are stalled

     or byte[read_pri_sec_master_slave],00000001b     ;flag MASTER PRESENT
     jmp .mastercounted   ;If Test 2 found something, ;stop searching and go
   .nocountmaster:                                    ;look for a possible slave



   .mastercounted:




;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;




 ;Select SLAVE side and read the 8 registers
 ;to decide the state of the slot
 ;;;
 call ata_atapi__slave
 call ata_atapi__readregs

   ;See if the Test 1 finds something
   ;;;
    call contacts_2
    jc .nocountslave2
     or byte[read_pri_sec_master_slave],00000010b  ;Flag SLAVE PRESENT
     jmp .slavecounted   ;If Test 1 found something, stop searching
   .nocountslave2:



   ;If Test 1 didn't find anything, try to find something through
   ;Test 2:
   ;;;
   call check_valid_ata_regs
   jc .nocountslave                ;If carry set, the contents are stalled

     or byte[read_pri_sec_master_slave],00000010b  ;flag SLAVE PRESENT
     jmp .slavecounted   ;If Test 2 found something, stop searching

   .nocountslave:

   .slavecounted:


  ;Debug information. Output the registers to the screen:
  ;;;
   call ata_atapi__readregs
   call ata_atapi_showregs

 popad
ret

Does anybody know if a value of 0x00 in all of the "AT Task File" registers is a valid state, or if some byte pattern could be considered valid? At least it hasn's happened to me not even with an old ATA controller for ISA.

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 10:41 am
by coolcoolm
Well for the problem with seagate is that the hard drives have a auto detect jumper on the hard drive and when its set that way it shows both master and slave at the same time. The way i figure you can get it so it doesn't show twice is to compare the drives serial numbers in a array. Seagate has three settings Master, Slave and Auto

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 11:31 am
by Owen
A device which presents itself twice is in blatant violation of the standards. Are you sure the absence of a jumper sets it to master? No hard drive I know of works that way (In fact all of them fail to detect at all...).

As for the "auto" jumper setting, it is supposed to cause the device to automatically detect it's primary/secondary status based upon it's position on the cable,

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 5:22 pm
by ~
No, that disk doesn't have cable select. You can see it in Google:

http://images.google.com/images?q=ST3491A

Fortunately, when it was tested, it didn't work as Slave-only, and when an IDENTIFY DEVICE command was sent to the Slave slot, the master responded, but also fortunately the result of the command set the ERR bit to 1, but not when sent to the Master (the right slot).

The ATA-7 says that the ERR bit must be cleared to 0, and that no errors shall be reported by a device not implementing this command (an ATAPI device). It means that this slight condition allows the program to discard that spurious detection, even when it can read valid identification data.
===================================================

Now to another problem, due to the behavior of motherboard IDE controllers, the EXECUTE DEVICE DIAGNOSTIC becomes a little unreliable, but not mainly to the device.

However, sometimes the following may happen:

- There are some ATAPI devices that respond to that command wit a value of
01 01 00 00 00 (the ATA signature!)

- There are other ATAPI devices that can report a value of
03 01 00 00 00 or 02 01 14 EB B1 or something like that.

- There are ATA devices that respond with
01 01 00 00 ?? instead of with 01 01 00 00 00

- In floating buses, due to this command, a resulting value of
FF in the Error Register may become 6F

=====================================================

Fortunately, in response to IDENTIFY DEVICE, an ATAPI device seems to at least always return the
14 EB part of the 01 01 14 EB 00 signature, and that is what we should use to know if an IDENTIFY DEVICE command was "aborted" by a device requiring IDENTIFY PACKET DEVICE.


=====================================================


Here is another awful problem that, if not handled, could eventually make fail a detection program.


The ATA-7 standard says this for command 0xEC (IDENTIFY DEVICE):
6.17.5.2 Outputs for PACKET Command feature set devices
In response to this command, devices that implement the PACKET Command feature set shall post command aborted and place the PACKET Command feature set signature in the Command Block registers (See 5.15).

And says this for command A1h (IDENTIFY PACKET DEVICE):
6.18.6 Error outputs
The device shall return command aborted if the device does not implement this command, otherwise, the device shall not report an error.


Now, we see that the standard states that both commands will set to 1 the ABRT bit in the Error Register (bit 2).

It also says that, if we send the command 0xEC to an ATAPI device, it will put its signature in the registers specified in 5.15...


The first problem is that if you send the command 0xA1 to an ATA device, it will NOT put its signature in the registers specified in 5.15... because the standard doesn't says it, so a vendor may or may not do it

The second, and the worst problem, is the following:

- Suppose you have an ATAPI device (a CD-DVD-R-RAM-RW...).
- Suppose that you send it the command 0xEC (IDENTIFY DEVICE) in an attempt to identify a device you know is there.
- It will surely should set the ABRT bit to 1 because you sent the command that was intended for ATA and not for ATAPI (almost the same but different)

- Now suppose that you read the ABRT bit, you see that it's 1 and also see a resemblance of the ATAPI signature specified in 5.15, so you decide to try with the other command
- Now you send the "correct" command 0xA1 (ID) and it eventually sets the DRQ bit to 1 indicating that it has the 256 words of identification ready
- Now watch this: The device sets the ABRT bit to 1 if you send the IDENTIFY xxxxx command that doesn't corresponds to it, but some devices do NOT clear the ABRT bit to 0 when the command is actually accepted because the standard doesn't says it


Now, what is worse. Both commands state this in their descriptions:
The IDENTIFY DEVICE/IDENTIFY PACKET DEVICE commands eneble the host to receive parameter information from a device.....

Some devices may have to read the media in order to complete this command.

But the bold statement shoud read like this:
Some devices may or may not have to read the media in order to complete this command, EVEN WHEN SUCH COMMAND IS ABORTED.

Even if you tried to use IRQ's, with a device that aborts the command but also sends data anyway, you could not directly know if the ID is valid when you got to send the "correct" command if also the ABRT stays set to 1 by then. In those cases, maybe the "direct" solution would be to actually interptret the ID results, and if it's garbabe say something like "ATAPI incompatible" as when the BIOS had detected a device and then the PC is turned off and then it's suddenly unplugged for the next POST.

======================================
============================================
=================================================

So what we are left with is with some devices that will set the ABRT bit to 1 if you send them the "wrong" command, but when you get to send them the "right" command will NOT clear it so you could check what has happened, and since the ERR bit should normally be set to 0, and some devices require you to read the 256-word identification even when it's garbage (for them to not get stalled) when the command is NOT aborted, you have no easy way to know when a 256-ID is valid or is a requirement even when the command was aborted, for the simple fact of setting the ABRT bit on error but not clearing it on success.


======================================
============================================
=================================================

So what we could do is this:

- Try the commands in this order:
First IDENTIFY DEVICE (0xEC)
Second IDENTIFY PACKET DEVICE (0xA1)

- If the device is ATA, it will be detected right away and will never deal with the contradictions of sending it an IDENTIFY PACKET DEVICE.
- If the device is ATAPI, if we send it the 0xEC command it will give us the ATAPI signature, and that's how we'll know that we must identify it with command 0xA1.


Now:

- Before the command, write 0 to the Sector Count, LBA Low, LBA Mid and LBA High registers.
- Send the command 0xEC
- If the command is aborted (because of ATAPI) do NOT check the ABRT bit, but instead the signature in the registers mentioned above.
- ALWAYS read 512 bytes as 256 words even when the command is aborted, just don't consider the device detected if the other checks above fail
- Now send the command 0xA1 (if the command is aborted)
- If everything is OK, then keep going. The ERR bit should still be checked for cases like the one of the ST3491A.


Here is the code I've managed to write for what I have described:

Code: Select all

EXECUTE_DEVICE_DIAGNOSTIC_signatures:
                                     times 8 db 0   ;For Master
                                     times 8 db 0   ;For Slave




;//This routine sets the CF to 1 if there was
;//an error (nothing found) and clears it if
;//it was successful.
;//
;//The BH register will contain the command that
;//was executed so that external functions can
;//know immediately if the device was ATA or ATAPI
;//
;//If the CARRY FLAG (CF) is set to 1, the value
;//in BH is not valid for determinining a device type,
;//neither ATA nor ATAPI.
;;;
scan_for_identify_ata_atapi_command:
 push eax
 push ecx
 pushad


 ;Zero-out our special-case variables:
 ;;;
   mov ax,0x0000
   mov cx,ax

 ;Zero-out the relevant ATA registers to leave
 ;no chance of malfunction by spurious values:
 ;;;
   add dx,2
   out dx,al   ;Write sector count
   inc dx
   out dx,al   ;Write LBA Low
   inc dx
   out dx,al   ;Write LBA Mid
   inc dx
   out dx,al   ;Write LBA High
   sub dx,5


 mov bh,0xEC  ;IDENTIFY DEVICE command
 call identify_ata_atapi_device
 jnc .allok__ATAonly


  call ata_atapi__readregs
  mov eax,[EXECUTE_DEVICE_DIAGNOSTIC_signatures+1]
  mov ecx,0xEB140000
  and eax,ecx
  cmp eax,ecx
  jne .deviceisnotatapi
    mov cl,0x32   ;flag this as "special ATAPI"
    jmp .tryatapi
  .deviceisnotatapi:

 
  xor cx,cx


 .tryatapi:
  cmp cl,0x32
  jne .nospecialatapi
    mov ch,0x64  ;flag a "special ATAPI" to not discard it by error

  .nospecialatapi:


 ;//If the CARRY FLAG in the EFLAGS register is set,
 ;//we will need to execute the IDENTIFY PACKET DEVICE command
 ;;;
  mov bh,0xA1  ;IDENTIFY PACKET DEVICE command
  call identify_ata_atapi_device
  jnc .allok__specials_ATAPI

  .errors:
 ;//If the CARRY FLAG (CF) was set after that, then there is
 ;//no valid ATA-ATAPI drive for this bus and master/slave socket:
 ;;;
  stc  ;ERROR
  jmp .end


 .allok__specials_ATAPI:
;  cmp ch,0x64
;  jne .errors

;  cmp cl,0x32
;  jne .errors

  .wasspecialatapi__not_an_error:
  .allok__ATAonly:
  clc   ;All OK


 .end:
  call ata_atapi_showregs

 popad
 pop ecx
 pop eax
ret









;//BH==ECh for IDENTIFY DEVICE command
;//    A1h for IDENTIFY PACKET DEVICE command
;//EDX,ES:EDI must be properly set by some other functions
;;;
identify_ata_atapi_device:
 push eax
 push ecx
 push esi
 push edi
  mov esi,[EXECUTE_DEVICE_DIAGNOSTIC_signatures_ptr]  ;get value in the pointer


  ;//Send the requested command:
  ;;;
   add dx,7
     mov al,bh  ;Take the command that was put in BH
     out dx,al
   sub dx,7


   mov ecx,0xFFFF  ;It still might timeout some time but has been "calibrated"
   .waitdrq:       ;for the slowest device in the fastest machine I have at hand
     call ata_atapi__readregs
      mov al,[esi+7]
      test al,00001000b    ;wait for DRQ==1
      jnz .waitdrq_end

   loop .waitdrq
   .waitdrq_end:


 ;Write the DEVICE REGISTER
 ;;;
  push ax
  add dx,6
  in al,dx           ;Read DEVICE REGISTER
  or al,10000001b    ;Preserve all bits including DEV bit
                     ;and...
                       ;Set BSY and ERR/CHK
                       ;This is for some weird buses
  out dx,al            ;that are half-floating and that also
                       ;change all of their floating registers to
  sub dx,6             ;the same last value written (to the device register).
  pop ax               ;If that's the case, it will cause the bits 0 and 7
                       ;to be set to 1, even for the Status Register, which
                       ;correspond to the ERR/CHK and the BSY bits,
                       ;respectively.
                       ;
                       ;At the same time, those bits correspond to bits that
                       ;are not used for the IDENTIFY DEVICE or
                       ;IDENTIFY PACKET DEVICE commands, and correspond to
                       ;the obsolete, ATA-3 HS0 (bit 0) and a 1 (bit 7).


  call ata_atapi__readregs   ;Inform the program on the device state


;                                                                               ;   mov al,[esi]
;                                                                               ;   test al,00000100b    ;ABRT
;                                                                               ;   jnz .nocount
   mov al,[esi+7]
   test al,00000001b    ;ERR/CHK==1 is error
   jnz .nocount

   test al,00001000b    ;DRQ==0 is error
   jz .nocount


   test al,10000000b    ;BSY==1 is error
   jnz .nocount
    clc      ;All OK, the device will be counted later on
    jmp .end



 .nocount:
   stc   ;Error, the possible device will be discarded


  .end:
   pushf
   mov esi,ata_atapi_ident_buff   ;A 512-byte buffer
   mov edi,esi                    ;Put also in EDI
   call ata_atapi__r512      ;ALWAYS read the identification
   call ata_atapi__pr512     ;Put it on screen
   call ata_atapi__readregs  ;Check the state of the registers.
   popf                      ;This will also provide us with a delay
                             ;equivalent to the read of 8 registers and
                             ;at the same time will give us the complete
                             ;register state for the selected device
 pop edi
 pop esi
 pop ecx
 pop eax
ret







ata_atapi__readregs:
 pushf
 push ecx
 push edi

 mov ecx,7
 mov edi,[EXECUTE_DEVICE_DIAGNOSTIC_signatures_ptr]  ;get value in the pointer


  push dx
  .loop
   inc dx
   a32 insb

  loop .loop
  pop dx

  ror edx,16
  a32 insb
  ror edx,16


 pop edi
 pop ecx
 popf
ret
====================================================
====================================================
====================================================

It has worked for me so far, but I need to know about suggestions or other alternatives. It is better to be able to disregard all these complications.

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 5:57 pm
by ~
I have a request so that people can test all of the code I have for this to see if it's valid.

Can you please see if it detect not less and not more and correctly all of the ATA-ATAPI hard disks in some of your machines?

Here is the attachment. You must put all the files in the same directory and rename "dosbuild.bat.txt" to "dosbuild.bat"

If you have other 16-bit environment, you only have to call the main function called "atadiskdetect".

Here is also the binary program. Rename it to "16.com.txt" to "somename.com".

You need NASM or YASM to assemble it.

Use something like this in a BAT file:

Code: Select all

REM We will try to run both NASM and YASM in case some of them is missing.
REM Since YASM produces better code, it's called last.

nasm main16.asm -o 16.com
yasm main16.asm -o 16.com
pause
All source code and binary zipped here (might be offline):
http://126.sytes.net/tmp/atadiskdetect.zip

Please run it more than once to ensure that it doesn't become unstable or misses something after several times.

Re: ATA-ATAPI detection

Posted: Tue Aug 25, 2009 6:50 pm
by whowhatwhere
To the thread OP and those that have responded so far, thank you. These posts are of high quality and description and is the sort of discussion that I like seeing on OSDev, and it reminds me of what the forum can and should be. =D>