Page 1 of 1

ATAPI (again)

Posted: Sun Mar 09, 2008 11:13 am
by Kenny
Hey

I'm having a spot of bother getting ATAPI to work, and the OSDev Wiki page on the subject is, unfortunately, less than helpful (starting with the fact that the ATA page starts with the section "What is ATAPI?", eh?)

I have a test machine, with an ATAPI CD-ROM connected as the Primary Slave of the standard IDE controller on the system (i.e. Device 1 of the ATA controller at I/O port 0x1F0).

I can send an atapi command to the device as the six words of data, but as soon as I receive the interrupt, the ERR bit is set with an Error register sense key of 6 (no idea what this means in it's context).

I have tried sending PLAY AUDIO [10], TEST UNIT READY and various other commands (including six words of garbage) with exactly the same result each time. I think I may be dealing with a duff device, but I haven't been able to find any succint examples of code for SendAtapiPacket().

My version of SendAtapiPacket is as follows:

Code: Select all

void AtaPI_SendPacket( unsigned char* pPacket, int pLength )
{
	// Sends an ATAPI packet command
	// Based on ATA-ATAPI-6, PACKET command (section 8.24) and PACKET Command Protocol (section 9.8)

	short lPortBase = 0x1F0;

	char lOverlap = 0;
	char lDMA = 0;
	char lTag = 0;
	unsigned short lByteCount = 0xFFFF;
	char lDevice = 1;

	unsigned char lCount;
	unsigned short lCommand;

	lOverlap &= 0x01;
	lDMA &= 0x01;
	lTag &= 0x1F;
	lDevice &= 0x01;

	// Initial status is 0x50 (DRDY set, bit 4 set)
	Ata_ShowStatus( lPortBase );

	// Set initial imput conditions
	Ports_OutB( lPortBase + ATA_ERROR_FEATURES, (lOverlap << 1) | lDMA     );		// Sets OVERLAP and DMA
	Ports_OutB( lPortBase + ATA_SECTORCOUNT,    lTag << 3                  );		// Sets TAG
	Ports_OutB( lPortBase + ATA_LBA_LOW,        0x00                       );		// Does nothing
	Ports_OutB( lPortBase + ATA_LBA_MID,        (lByteCount & 0xFF)        );		// Sets the Byte Count Low
	Ports_OutB( lPortBase + ATA_LBA_HIGH,       (lByteCount & 0xFF00) >> 8 );		// Sets the Byte Count High
	Ports_OutB( lPortBase + ATA_DEVICE,         lDevice << 4               );		// Set the Device

	mWaitingForInterrupt = 1;
	Ports_OutB( lPortBase + ATA_STATUS_COMMAND, 0xA0 );							// Send PACKET command
	Ata_WaitForInterrupt();

	// Now in "HP0: Check_Status_A" state

	// If BSY is set, continue to wait
	Timer_Delay( 10 );
	while ( Ports_InB( lPortBase + ATA_STATUS_COMMAND ) & 0x80 )
	{
		Timer_Delay( 10 );
	}

	// Status is now 0x58 (DRQ now set)
	Ata_ShowStatus( lPortBase );

	// If BSY is clear and DRQ is clear, return to Idle
	if ( Ports_InB( lPortBase + ATA_STATUS_COMMAND ) & 0x88 == 0x00 )
	{
		// Doesn't come in here
		return;
	}

	// Now in "HP1: Send_Packet" state

	// Send Command Packet to the Data port
	mWaitingForInterrupt = 1;
	for ( lCount = 0 ; lCount < pLength ; lCount += 2 )
	{
		lCommand = *((unsigned short*)&pPacket[lCount]);

		String_kprintf( "0x%04X\n", lCommand );
		Ports_OutS( lPortBase + ATA_DATA, lCommand );

	}
	Ata_WaitForInterrupt();

	// Status is now 0x51 (DRQ clear, CHK set)
	Ata_ShowStatus( lPortBase );

	// Error Register now contains 0x60
	Ata_ShowError( lPortBase );

}

I have the ATAPI-6 document, the MMC-4 document and the SPC-4 document, and if there were any more cross-references between them, I think my head would explode.

I have looked at the code from Dex's CDPod (didn't work :( ) and from the http://www.ata-atapi.com site (can't see what I'm doing differently / wrong).

If I can get this fixed and working, I'm going to post it up to the Wiki with some other information that the page lacks (unless someone wants to beat me to it) to improve that page and help others looking into this.

(PS: Any pointing out of silly mistakes will be humbly received)

Posted: Sun Mar 09, 2008 3:34 pm
by Dex
I have coded you a simple demo (me and my demos :lol: ), to test your drive, it works on all my drives. Its a simple CLI cdplayer, that's bootable and comes with full source.
In the zip is a selfextracting exe to make a bootable floppy.
If this does not work on you cd drive, than i thing your cd drive is shot, as playing audio and open, close, cd draw etc, is very reliable.

http://dex4u.com/demos/CdPlayer.zip

Posted: Sun Mar 09, 2008 3:43 pm
by octavio
what this function does?
Ports_OutS( lPortBase + ATA_DATA, lCommand );

Posted: Sun Mar 09, 2008 3:58 pm
by Brynet-Inc
octavio wrote:what this function does?
Ports_OutS( lPortBase + ATA_DATA, lCommand );
I would assume it writes a short(aka, word) to a MMIO address..

Posted: Mon Mar 10, 2008 6:40 am
by Kenny
@Brynet-Inc, octavio

Yes and no. The Ports_OutS() function in this case is an assembly outw() call, OUTing the given 16-bit value to the given IO port. It will eventually be changed to use MMIO addresses instead, but I wanted to get it working first.

@Dex

Thanks for this, I shall run it later and see what happens. I'm also going to remove the primary master (an old 4.3Gb HDD) in case that's causing me problems on the IDE cable.

Posted: Mon Mar 10, 2008 4:23 pm
by Kenny
The good news is that the drive isn't duff because Dex's code works :) (many thanks, Dex).

The bad news is that this means my code is broken somewhere :(

I shall work through it again to see if I can see anything I missed

ATAPI (again) (SOLVED) (Free codez)

Posted: Wed Mar 12, 2008 5:12 pm
by Kenny
Ok, think I can close this now. Whilst experimenting with Dex's program, I noticed that the commands weren't always being honoured by the drive. With a bit more poking, it seems that the first ATAPI command that I send to my CD-ROM drive has a 95% (or so) chance of failing with an ERR flag. After that, it seems to work happily.

Also complicating matters was the fact that the TEST_UNIT_READY command fails 100% of the time. Whether I have malformed the command packet, or the unit simply isn't ready, I don't know, but that was also messing with my test results.

I'd guess that the BIOS is leaving the drive in an uncertain state, and that causes the command to fail. I'll try resetting the drive during initialisation to see if I can fix this.

So, to recap, the above code works (on my machine) to send an ATAPI packet to a CD-ROM drive on the Primary Slave channel when called in a way such as:

Code: Select all

	unsigned char lEjectCd[12] = { 0x1B, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 };
	AtaPI_SendPacket( lEjectCd, 12 );
except that it ALWAYS fails the first time.

When I get granted editing rights to the wiki and add some device reset code, I shall craft it up into a tutorial, but it will have to live here for the time being.

PS: Don't know if it's legal to do this (IANAL), but the above code (in the original post) is mine and I wrote it, and I say that it's public domain, so do with it what you will. However, the code could potentially cause damage to your system or device, so use it AT YOUR OWN RISK and I disclaim all responsibility for it. I would rather you though of it as an educational example, and developed your own code based on it (there is much room for improvment).

PPS: You really have to use it as an educational resource because I haven't included all the helper functions that it needs to run. I'll add them for the wiki.

Posted: Wed Mar 12, 2008 7:58 pm
by Dex
One thing i will add, is that one of my cd drives, which is one of many test PC, will always fail on first packet sent, but it works fine after that, but its only like that on that one drive, the other's work first time.

Posted: Thu Mar 13, 2008 3:30 am
by AJ
Kenny wrote:When I get granted editing rights to the wiki and add some device reset code, I shall craft it up into a tutorial, but it will have to live here for the time being.
Hi,

If you have registered with the wiki group, you should already be able to edit the wiki - you don't need to wait for confirmation.

Cheers,
Adam

Posted: Thu Mar 13, 2008 7:20 am
by blackoil
I got the exact problem as Kenny.

My code works fine in BOCHS. But In real machine, I can read cdrom sector successfully, after the 3rd call to ATAPI_ReadSector.

The first call returns 80, 88, 81, 96 (96=sense key 6, mean UNIT ATTENTION)

The second call returns 80, 88, 81, 48 (48=sense key 2, mean DEVICE NOT READY)

The third call returns 80, 88, 88 I can read the sector now.


After third call, I can read sector without problem. It seems BOCHS put CDROM into ready condition.



Code: Select all


#define	ATA_Cmd_Reset			0x08
#define	ATA_Cmd_Read			0x20
#define	ATA_Cmd_Write 			0x30
#define	ATA_Cmd_Packet 			0xA0

#define	ATAPI_Cmd_Read12		0xA8
#define	ATAPI_Cmd_ReadTOC		0x43
#define	ATAPI_Cmd_Start			0x1B
#define	ATAPI_Cmd_Test			0x00

#define	ATA_Bit_Command_OBSBIT7		10000000b
#define	ATA_Bit_Command_LBAEnabled	01000000b
#define	ATA_Bit_Command_OBSBIT5		00100000b
#define	ATA_Bit_Command_Slave		00010000b

#define	ATA_Bit_Status_BSY			10000000b
#define	ATA_Bit_Status_DRDY			01000000b
#define	ATA_Bit_Status_DWF			00100000b
#define	ATA_Bit_Status_DSC			00010000b
#define	ATA_Bit_Status_DRQ			00001000b
#define	ATA_Bit_Status_CORR			00000100b
#define	ATA_Bit_Status_IDX			00000010b
#define	ATA_Bit_Status_ERR			00000001b

#define	ATA_Bit_Error_BBK			10000000b//BBK (Bad Block)
#define	ATA_Bit_Error_UNC			01000000b//UNC (Uncorrectable data error)
#define	ATA_Bit_Error_MC			00100000b//MC (Media Changed)
#define	ATA_Bit_Error_IDNF			00010000b//IDNF (ID mark Not Found)
#define	ATA_Bit_Error_MCR			00001000b//MCR (Media Change Requested)
#define	ATA_Bit_Error_ABRT			00000100b//ABRT (command aborted)
#define	ATA_Bit_Error_TKONF			00000010b//TK0NF (Track 0 Not Found)
#define	ATA_Bit_Error_AMNF			00000001b//AMNF (Address Mark Not Found)

#define	ATA_Bit_DevCtrl_Default		00001000b
#define	ATA_Bit_DevCtrl_SRST		00000100b
#define	ATA_Bit_DevCtrl_nIEN		00000010b

#define	ATA_Port_Primary_Data			0x1F0
#define	ATA_Port_Primary_Error			0x1F1
#define	ATA_Port_Primary_Feature		0x1F1
#define	ATA_Port_Primary_SectorCount	0x1F2
#define	ATA_Port_Primary_LBA			0x1F3
#define	ATA_Port_Primary_LBA8			0x1F4
#define	ATA_Port_Primary_LBA16			0x1F5
#define	ATA_Port_Primary_LBA24			0x1F6
#define	ATA_Port_Primary_Status			0x1F7
#define	ATA_Port_Primary_Command		0x1F7
#define	ATA_Port_Primary_AlterStatus	0x3F6
#define	ATA_Port_Primary_DeviceCtrl		0x3F6

#define	ATA_Port_Secondary_Data			0x170
#define	ATA_Port_Secondary_Error		0x171
#define	ATA_Port_Secondary_Feature		0x171
#define	ATA_Port_Secondary_SectorCount	0x172
#define	ATA_Port_Secondary_LBA			0x173
#define	ATA_Port_Secondary_LBA8			0x174
#define	ATA_Port_Secondary_LBA16		0x175
#define	ATA_Port_Secondary_LBA24		0x176
#define	ATA_Port_Secondary_Status		0x177
#define	ATA_Port_Secondary_Command		0x177
#define	ATA_Port_Secondary_AlterStatus	0x376
#define	ATA_Port_Secondary_DeviceCtrl	0x376


void	ATAPI_ReadSector(unsigned int LBA,unsigned int DEV,unsigned byte * M)
{
	ATA_Status=InPortB(ATA_Port_Secondary_Status);
	PutDecimal((unsigned int)ATA_Status);
	PutCRLF();

	unsigned dword LBAlo,LBAhi;

	Counter=0;
	ATA_interrupt=0;

	OutPortB(ATA_Port_Secondary_DeviceCtrl, ATA_Bit_DevCtrl_Default);
	OutPortB(ATA_Port_Secondary_LBA24, DEV);
	OutPortB(ATA_Port_Secondary_LBA16, 0);
	OutPortB(ATA_Port_Secondary_LBA8, 12);
	OutPortB(ATA_Port_Secondary_Command,ATA_Cmd_Packet);

	asm
		{
		push dword [LBA]
		pop ax
		pop dx
		xchg al,ah
		xchg dl,dh
		mov [LBAlo],ax
		mov [LBAhi],dx
		}

	ATA_Status=InPortB(ATA_Port_Secondary_Status);
	PutDecimal((unsigned int)ATA_Status);
	PutCRLF();

	OutPortW(ATA_Port_Secondary_Data,ATAPI_Cmd_Read12);
	OutPortW(ATA_Port_Secondary_Data,LBAhi);
	OutPortW(ATA_Port_Secondary_Data,LBAlo);
	OutPortW(ATA_Port_Secondary_Data,0);
	OutPortW(ATA_Port_Secondary_Data,1<<8);
	OutPortW(ATA_Port_Secondary_Data,0);

	while(ATA_interrupt==0);
        ATA_Status=InPortB(ATA_Port_Secondary_Status);
        PutDecimal((unsigned int)ATA_Status);
        PutCRLF();
        if(ATA_Status&1==1)
			{
			PutDecimal((unsigned int)InPortB(ATA_Port_Secondary_Error));
			return;
			}
	asm
	{
	cld
	mov ecx,2048/2
	mov edi,[M]
	mov dx,ATA_Port_Secondary_Data
	rep insw
	}

	return;
}

Posted: Thu Mar 13, 2008 9:00 am
by zaleschiemilgabriel
How do I delete this damn post? :d

Posted: Thu Mar 13, 2008 9:42 am
by lukem95
Well why not modify your command code, so that if it returns an error, it tries again until it returns success (or if say the 5th attempt fails, print an error message and stop).

Posted: Thu Mar 13, 2008 1:57 pm
by Dex
lukem_95 is right, you should try a number of times with a small delay in between, but out of all the devices the CD is so pickey, your code could work fine on one drive and not so on another, with fdd or hdd if it works on one, it will in most caser's work on all, but not with cd, it makes a differanc if it a dvd or cd or using a cd-r or cd-rw.

Posted: Thu Mar 13, 2008 2:10 pm
by octavio
Sometimes the CD returns errors to signal that something has changed
like cd disk change then the driver must send a command to get event notification.

Posted: Fri Mar 14, 2008 8:28 am
by blackoil
I tried SRST bit operation before the first ATAPI_ReadSector call, it works, I can get the sector data at once. but I don't know how to use SRST bit properly, I got BSY bit set and no interrupt.