Page 1 of 1

ATA Issues with Bochs

Posted: Sat Jan 19, 2008 10:17 am
by Ready4Dis
Well, I'm at work, so I can't try it on a real computer, but I'm having issues with detecting drives.

If I have this in bochs config:
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=cdrom, path="Win98SE.iso", status=inserted
ata0-slave: type=disk, path="hd.img", cylinders=128, heads=10, spt=63

My code detects first the ATAPI CD drive, then the 40mb HD image... howerver... if I change the master/slave around like so:

ata0-master: type=disk, path="hd.img", cylinders=128, heads=10, spt=63
ata0-slave: type=cdrom, path="Win98SE.iso", status=inserted

It finds the 40mb HD, but fails to see the cd-rom drive. If I set up ata1 with another Hd image, it misses it completely, unless I have no devices on ata0, then it finds it no problems. It seems like an issue with bochs, but so many people use it without issues.

Another similar, but seperate issue, if I send the ATA Identify command (or ATAPI for cd roms), wait for the DRQ bit to be set, and rep insw, it fails, with bochs complaining the DRQ bit isn't set (which it is, because I check it before proceeding). However, if I instead change my rep insw with a loop of input words, it works perfectly and all the data matches correctly. Any idea why it won't let me rep insw (even though that's what the spec recommends), but it'll let me inport all the data no issues? Both are 16-bit (like specified) operands, so i have no clue, i'm only reading 512 bytes (256 words, but it complains even if I set it to read a single word using rep insw). Some code posted below for the major parts.

Code: Select all

[global		Rep_In16]
Rep_In16:
	push		ebp
	mov		ebp,	esp
	add		ebp,	8
	pusha
	mov		edi,	[ebp]
	mov		edx,	[ebp+4]
	mov		ecx,	[ebp+8]
	cld                        ;clear direction bit
	rep		insw
	popa
	pop		ebp
	ret

Code: Select all

void IdentifyDevice(struct DriveInfo_S *di)
{
	struct ATA2_S *ptrATAInfo;
	u16        *ptrBuffer;
	u32 Ctr;
	u8  Temp;

//Select correct drive
    Outport8(di->IOAddr + ATA_REG_DRVHD, di->DrvSel);
    InactiveWait(1);    //Delay 1ms for drive selection

    MyPrintf("Issuing ATA Identify...\n");

//Issue our 'Identify ATA' command
    Outport8(di->IOAddr + ATA_REG_CMD, ATA_CMD_ID);
    Temp = WaitStat(di->IOAddr,ATA_STAT_RDY | ATA_STAT_DRQ ,WAIT_ID);  //Ready or DRQ
    if (Temp & 1) //Error?
    {   //Could still be a CD-rom drive...
        MyPrintf("Not ATA, Issuing ATAPI Identify...\n");
        Outport8(di->IOAddr + ATA_REG_CMD, ATA_CMD_PID); //ATAPI Identify
        Temp = WaitStat(di->IOAddr,ATA_STAT_RDY | ATA_STAT_DRQ,WAIT_PID);
        if (Temp & 1) //Error?
        {   //Nope not a CD-rom either!
            return;
        }
        //Ok, we got a CD-Rom drive...
        MyPrintf("Found CD-Rom Drive: 0x%x %d\n",di->IOAddr,(di->DrvSel/0x10)-0xA);
        di->DriveType = ataCDRom;
    }
    else
    {
        di->DriveType = ataHardDisk;
        MyPrintf("Found hard-disk: 0x%x 0x%d\n",di->IOAddr,(di->DrvSel/0x10)-0xA);  //0xA0, 0xB0..
    }

//ATA Ready already set, so lets use it :)
    if (Temp & ATA_STAT_DRQ)    //Was DRQ set?
    {
//Yay, we can read it now :)
        ptrATAInfo = pMalloc(1);    //Grab a page, 4k is plenty ;)
        ptrBuffer = (u16*)ptrATAInfo;
        for (Ctr=0;Ctr!=256;++Ctr) //256 inputs
            ptrBuffer[Ctr] = Inport16(di->IOAddr + ATA_REG_DATA);    //Input word

//        Rep_In16(ptrATAInfo,di->IOAddr + ATA_REG_DATA, 256);	//Read 256 words in, or 512 bytes.
        di->SizeLBA = ptrATAInfo->LBASects;
        MyPrintf("  Drive Size: %dkb\n",di->SizeLBA/2);
        pFree(ptrATAInfo,1);    //Release the page when done
    }
    else
    {
        MyPrintf("DRQ never changed for me :(\n");
    }
}
Notice the Rep_In16 commented out, it works fine, if I coment out the loop and uncomment Rep_In16, it doesn't work at all. And I know DRQ is set, otherwise it wouldn't make it to that conditional statement. Anyways, all help would be greatly appreciated.

Posted: Sat Jan 19, 2008 2:58 pm
by bewing
Well, for one thing, you are trying to do that rep insw on ptrATAInfo, and there might be some reason why you should do it on ptrBuffer instead. I know they *should* be identical, but you can never really tell how C might have put that variable onto the stack -- you might have ended up with a pointer to that pointer on the stack. You might also check whether your pFree function returns an error -- you might need to pFree ptrBuffer instead.

I am also VERY confused about your WaitStat function. What you want to do is wait for the BSY flag to clear and for the DRQ flag to set. How you could possibly do BOTH when you just OR the bits together is beyond me? Somehow, I think that is the source of your "read before DRQ bit is set" error -- and maybe several other errors, too.

Posted: Sat Jan 19, 2008 3:18 pm
by Ready4Dis
bewing wrote:Well, for one thing, you are trying to do that rep insw on ptrATAInfo, and there might be some reason why you should do it on ptrBuffer instead. I know they *should* be identical, but you can never really tell how C might have put that variable onto the stack -- you might have ended up with a pointer to that pointer on the stack. You might also check whether your pFree function returns an error -- you might need to pFree ptrBuffer instead.

I am also VERY confused about your WaitStat function. What you want to do is wait for the BSY flag to clear and for the DRQ flag to set. How you could possibly do BOTH when you just OR the bits together is beyond me? Somehow, I think that is the source of your "read before DRQ bit is set" error -- and maybe several other errors, too.
There is no difference between doing it on ptrBuffer or ptrBuffer. Both are identical, same for pMalloc and pFree. I know how gcc puts variables on the stack (and all compilers *should* be the same), to pass the pointer you just use the name, to pass a value that the pointer points to, you prepend *, if you want to pass a pointer to the pointer variable, you would prepend &.

Also, I am not waiting for BSY to clear and DRQ to set, I am waiting for RDY or DRQ to set, then I am checking if DRQ was set before proceeding to rep insw, so I am 100% positive that 0x08 (DRQ) is set before making the call. If that wasn't the case, neither should have worked (which is whats throwing me off). This is the only place I use wait for DRQ and RDY, normally I just wait for the RDY bit to set to proceed (for detecting devices, since it isn't reading in data), so I don't beleive that has anything to do with the detection code going amiss.

When I try to detect devices, I issue an ATA Drive Diagnose command, then wait for the RDY bit set. After that, I set the master and read it's Error, then I set the slave and read its Error (waiting for RDY bit inbetween). When I have the CD as master, both return a 0x01 which means a device is present (although, the master *should* return 0x81 to denote a slave is attached, I can work around that glitch), then read the slave and it returns 0x01, meaning a slave is present. When I swap the 2 devices in the config, the master returns 0x01 still, but the slave returns 0x00 (nothing present), so I don't even get far enough to issue the ATA Identify (although, I set it to call anyways, and still returned nothing, and I know that code detects the HD properly with the Inport16 call when it's master). Anyways, I really need to get this on a real machine and check if it works, or maybe qemu or vmware or something when I get home.

Posted: Sat Jan 19, 2008 6:41 pm
by bewing
If, for some reason, a C compiler thinks that a variable is bigger than 32bits, then it very well might pass a pointer to that variable, rather than the value itself -- even without you specifying a * or &. You are not in full control over the argument passing, as you seem to be saying.
But I use a rep insw myself, in bochs and on a real PC, and it works fine. And the main difference in your code that I see between the asm routine, and the one-at-a-time loop is that they use different input variables -- just so long as you really are getting ECX, EDX, and EDI loaded properly, and ES is set properly.

If BSY is set, then *none* of the other bits in the status register are technically valid. The DRQ and/or RDY bits could be set or not, and you still are not allowed to read the data port until BSY clears.

And technically, you are supposed to access the slave first, then the master second. I think the PC BIOS handles this for you, but I'm not sure if bochs emulates it, or if the bochs BIOS does it for you.

But what I am specifically noticing is that when you identify the hard disk, that is what crashes the rest of the detection process -- you are never able to access any drive on any bus past the hard disk. This suggests that something in your identify technique is causing a (bochs emulated) bus error.

Posted: Sat Jan 19, 2008 7:21 pm
by Ready4Dis
bewing wrote:If, for some reason, a C compiler thinks that a variable is bigger than 32bits, then it very well might pass a pointer to that variable, rather than the value itself -- even without you specifying a * or &. You are not in full control over the argument passing, as you seem to be saying.
But I use a rep insw myself, in bochs and on a real PC, and it works fine. And the main difference in your code that I see between the asm routine, and the one-at-a-time loop is that they use different input variables -- just so long as you really are getting ECX, EDX, and EDI loaded properly, and ES is set properly.

If BSY is set, then *none* of the other bits in the status register are technically valid. The DRQ and/or RDY bits could be set or not, and you still are not allowed to read the data port until BSY clears.

And technically, you are supposed to access the slave first, then the master second. I think the PC BIOS handles this for you, but I'm not sure if bochs emulates it, or if the bochs BIOS does it for you.

But what I am specifically noticing is that when you identify the hard disk, that is what crashes the rest of the detection process -- you are never able to access any drive on any bus past the hard disk. This suggests that something in your identify technique is causing a (bochs emulated) bus error.
Ok, so it is true you don't always have 100% control, however in my case I am 100% sure it's working, because I have tested my pMalloc and pFree with many different types of pointers, and i'm using a 32-bit version of gcc, so no chance it's a 64-bit pointer. Haha, ok, just for fun I tried passing it using ptrBuffer, and it worked fine, no errors in bochs, so I changed my Rep_In16 to .j jmp .j so I could check all registers to see what was going on... compiled it both ways, and all registers checked out fine (as I said, they are being passed fine). I then took out the .j jmp .j (effectively putting it back to original) and left it with the ptrATAInfo and it worked fine... I have no clue, because I recompiled and tried this thing like 15 times, and it just started working for no reason in bochs, which is weird, because if the code is broken, bochs shouldn't just start working. Well, that was awkward..

Anyways, it still returns 0x1 and 0x0 depending on what order my config file is. I know what you're saying about reading the slave first, I read that a while ago, and I had it reading the slave first in my code, but it made no difference so I put it back. The code reading the ATA info would have nothing to do with it since that is called only after I read both slave/master registers to determine if they are present. I will take your advice and add a wait for BSY to clear also, but I don't plan on leaving it like this for long before making use of int, which I'm currently catching, but disregarding at the moment. Here is my code to check for devises on the ATA port...

Code: Select all

void DetectATADrives(void)
{
	u32	Ctr;
	u8	DeviceErrorMaster, DeviceErrorSlave;

	for (Ctr=0;Ctr<8;Ctr+=2)	//Check all 8 drives, 2 at a time
	{
		DriveInfo[Ctr].Drive = Ctr;

        MyPrintf("Checking IO 0x%x\n",DriveInfo[Ctr].IOAddr);

        if (FloatingBus(DriveInfo[Ctr].IOAddr))     //Posible floating bus
        {   //Possible floating bus!
            continue;   //Skip to next IO Address
        }

        //Ok, it was a valid ATA controller, not floating... lets make sure it's ready with no errors
        MyPrintf("Found ATA Controller 0x%x\n",DriveInfo[Ctr].IOAddr);
        if (WaitStat(DriveInfo[Ctr].IOAddr,ATA_STAT_RDY,WAIT_READY) & 1) //Error?
        {   //Controller never got ready for us
            continue;
        }

        MyPrintf("Issuing Drive Diagnostics...\n");

//At this point we know we're ready because of our last waitstat, so lets issue it :)
        Outport8(DriveInfo[Ctr].IOAddr + ATA_REG_CMD, ATA_DRV_DIAG);    //Run diagnostic...
        if (WaitStat(DriveInfo[Ctr].IOAddr,ATA_STAT_RDY,WAIT_ID) & 1) //Error?
        {   //Controller never get ready for us
            continue;
        }
        MyPrintf("Found possible device... checking device\n");

        //Select master
        Outport8(DriveInfo[Ctr].IOAddr + ATA_REG_DRVHD, DriveInfo[Ctr].DrvSel);
        InactiveWait(1);    //Delay 1ms for drive selection
        DeviceErrorMaster = Inport8(DriveInfo[Ctr].IOAddr + ATA_REG_ERR);    //check for problems...
        if (WaitStat(DriveInfo[Ctr].IOAddr,ATA_STAT_RDY,WAIT_READY) & 1) //Error?
        {   //Controller never get ready for us
            continue;
        }
        //Select slave
        Outport8(DriveInfo[Ctr].IOAddr + ATA_REG_DRVHD, DriveInfo[Ctr+1].DrvSel);
        InactiveWait(1);    //Delay 1ms for drive selection
        DeviceErrorSlave = Inport8(DriveInfo[Ctr+1].IOAddr + ATA_REG_ERR);    //check for problems...

        MyPrintf("Diagnostic Error report: Master 0x%x, Slave 0x%x \n",DeviceErrorMaster,DeviceErrorSlave);

        if ((DeviceErrorSlave & 0x7f) == 1) //Slave ok
        {
            MyPrintf("Found Slave!\n");
            IdentifyDevice(&DriveInfo[Ctr+1]);
        }

        if ((DeviceErrorMaster & 0x7f) == 1) //Master ok
        {
            MyPrintf("Found Master!\n");
            IdentifyDevice(&DriveInfo[Ctr]);
        }
	}
}
Notice the IdentifyDevice is called first for Slave, very easy to move these around, but DeviceErrorSlave is not being set properly with certain configs in Bochs (technically according to the ATA specs, neither is the master even when the slave is set right, since with a slave the master should return 0x81, while it's returning 0x1 instead). Anyways, I do appreciate you looking through and coming up with ideas, no clue why the Rep_In16 stuff started working, I reread my original post, and my code and nothing has been modified, maybe just a hickup with the machine i'm on or something, i have no clue.

Posted: Sun Jan 20, 2008 9:29 am
by bewing
Heh. Yeah, isn't that annoying?
And that's the thing. Your code looks fine, even after reading it over a couple times, closely -- so it has to be something tricky or weird.

Posted: Sun Jan 20, 2008 10:58 am
by Ready4Dis
bewing wrote:Heh. Yeah, isn't that annoying?
And that's the thing. Your code looks fine, even after reading it over a couple times, closely -- so it has to be something tricky or weird.
Heh, alright, well thanks, like I said, I tried changing the order, adding delays, etc, but nothing works, maybe i'll update bochs and see if a new version (or older version) works any better. Thanks for all the help though, I really do appreciate it.

Posted: Mon Jan 21, 2008 7:37 pm
by bewing
I just happened to be looking over the wiki, seeing what needed work. I reread the ATA article. http://www.osdev.org/wiki/ATA

Interestingly, the article has a section all about using the RDY bit to detect devices, and has a comment that I don't understand about how that technique fails in bochs. You might want to take a peek.

Posted: Tue Jan 22, 2008 7:12 am
by Ready4Dis
bewing wrote:I just happened to be looking over the wiki, seeing what needed work. I reread the ATA article. http://www.osdev.org/wiki/ATA

Interestingly, the article has a section all about using the RDY bit to detect devices, and has a comment that I don't understand about how that technique fails in bochs. You might want to take a peek.
Alright, reading it now, i've been readind data-sheets and other references to the ATA interface, and have written my code to work with most.

Ok, after reading it, they are going about it completely different, they are not checking for a floating bus before randomly issuing commands, nor do they issue the ATA diagnostic call, they just start writing commands and checking for a suspected value, also the bochs problem they were having had to do with delays and a write fault, I am not getting any faults at all, it just says everything is working and nothing is there. The thing that makes no sense is that it works one way, and not the other, and it's not returning any errors at all. I am going to have to just try it on a real-computer and make sure it works, then just use bochs with the cd-rom as master and hard drive as slave to keep working on my OS. It's still weird that bochs only returns 0x1 even if the second device is present, it should be returnig 0x81, so something tells me bochs isn't sticking with the ATA specs very nicely, but I need to verify on a few machines first. Thanks for the help though. I am going to try to Increase the time of my inactive waits just incase, but I don't really see it helping since the resolution of my InactiveWait is actually 10ms right now, so it waits a minimum of 10ms, but up to 19.9999 ms (can't set it just at 10 otherwise a clock interrupt might be right behind it, so you need to increment it, so 20ms possible, 10ms minimum), so i'm sure it's waiting plenty of time. And since the typical time is 400us, 10ms should be way more than enough.

Posted: Tue Jan 22, 2008 1:43 pm
by Ready4Dis
Ok, so I decided maybe i'll try a newer verson of Bochs, since 2.3.6 came out... well, now it doesn't matter which order I have them, it only returns the first. So... I decided it's time to try detecting them in another manner, so I changed over to another method that people use.. writing a known value to the sect count, sect num registers, then reading it back... so it does that, receives the correct info. Problem is, when I issue a device soft reset, it fails with an error of command aborted... well, the busy flag clears while i'm waiting (like it should), but it returns 0x51, which means ready/error :P, so I read the error register, and it returns 0x04 which is command aborted. And it does this for both drivers, not just the first one... so i'm back to square 1, does ATA in bochs work? I noticed on their page, they have listed under the bugs/people needed sectio, IDE Controller... so maybe it's not just me, but i've seen plenty of people work on these things, even my old version used to work (on a long outdated version of bochs, maybe i'll re-implement my old version and see if it still works). I was trying to go more by the specs, and take less chances with hardware (like checking for a floating bus before issuing commands, checking the diagnostics before issuing commands, etc), i guess that's why nobody does it the 'correct' way :P.

Posted: Wed Jan 23, 2008 12:47 pm
by Ready4Dis
Well, I was right, my old code did work, and it still does, haha, I resorted back and it detects them fine, albeit not the 'proper' way. I may add in my floating bus function to verify a controller is present, but I guess I'll just stick with it since it works on bochs and tried on a real pc. Here is the basic of it incase someone else has the same issues.

Code: Select all

void DetectType(struct DriveInfo_S *di)
{
	u32		Ctr;
	u8		Temp, CylLow, CylHigh;
	u16		IOAddr;
	u32		WaitTime;
	struct	ATA2_S	*ATAInfo;
    
	IOAddr = di->IOAddr;

	InterruptValue = 0;
	Outport8(IOAddr + ATA_REG_STAT, ATA_CMD_ID);

	WaitInterrupt(0xC000,WAIT_ID);

	Temp = PollStatus(IOAddr,ATA_STAT_RDY,100);
	if (Temp & 1)	//Error?
	{
//Lets check if it's an ATAPI device?
		CylLow = Inport8(IOAddr + ATA_REG_LOCYL);
		CylHigh = Inport8(IOAddr + ATA_REG_HICYL);

		if (CylLow != 0x14 || CylHigh != 0xEB)
		{
			di->IOAddr = 0;
			return;
		}

		Outport8(IOAddr +  ATA_REG_STAT, ATA_CMD_PID);
		WaitInterrupt(0xC000,WAIT_PID);					//Wait for drive to be ready..
		Temp = WaitStatus(IOAddr,ATA_STAT_BSY,10);		//Wait for it to not be busy..
		if (Temp & 1)	//Error, not ATAPI either?
		{
			di->IOAddr = 0;
			return;
		}
		else
		{
			//Ok, atapi, now lets get the info about it ;)
			di->DriveType = ataCDRom;
		}
	}
	else
	{
		di->DriveType = ataHardDisk;
	}

    ATAInfo = pMalloc(1);   //Grab a page

//Supported drive, lets read info...
	Rep_In16(ATAInfo,IOAddr + ATA_REG_DATA, sizeof(struct ATA2_S)/2);	//Size of ATA2 /2
	di->SizeLBA = ATAInfo->LBASects;		//Number of sectors on this drive..

    MyPrintf("Found a disk: %s\n",DriveNames[di->DriveType]);
//Lets swap bytes...
	for (Ctr=0;Ctr!=40;Ctr+=2)
	{
		ATAInfo->Model[Ctr]^=ATAInfo->Model[Ctr+1];
		ATAInfo->Model[Ctr+1]^=ATAInfo->Model[Ctr];
		ATAInfo->Model[Ctr]^=ATAInfo->Model[Ctr+1];
	}
	
	pFree(ATAInfo,1);  //Free this page
}

void DetectATADrives(void)
{
	unsigned long	Ctr;
	unsigned long	TimerCtr;
	unsigned short	IOAddr;
	unsigned char	Temp[2];

	for (Ctr=0;Ctr<8;++Ctr)	//Check all 8 drives
	{
		DriveInfo[Ctr].Drive = Ctr;
		IOAddr = DriveInfo[Ctr].IOAddr;
		//Select the drive...
		SelectDrive(&DriveInfo[Ctr]);

		Outport8(IOAddr + ATA_REG_CNT, 0x55);
		Outport8(IOAddr + ATA_REG_SECT, 0xAA);
		SlightDelay(1);
		Temp[0] = Inport8(IOAddr + ATA_REG_CNT);
		Temp[1] = Inport8(IOAddr + ATA_REG_SECT);
		if (Temp[0] != 0x55 || Temp[1] != 0xAA)
		{
			DriveInfo[Ctr].DriveType = ataNone;
			DriveInfo[Ctr].IOAddr = 0;
			DriveInfo[Ctr+1].IOAddr = 0;
			++Ctr;						//Skip next!
			continue;	//Check next...
		}
		DetectType(&DriveInfo[Ctr]);
	}
}
If anybody has problems, let me know and i'll be glad to fill in the missing functions. I guess bochs just isn't right, because my other code is more correct and doesn't work.