Page 1 of 1

ATA DMA Mode Troubles [Solved]

Posted: Wed Nov 09, 2011 3:04 pm
by piranha
Okay, I have been battling with my ATA driver for a couple of weeks. First I noticed that sometimes PIO doesn't seem to work properly (Occasionally if I read the same sector twice I get different data [one set of data is usually half correct and half zero]). So I decided that that would be a perfect time to start getting DMA mode to actually work. So, in my spare time I've been staring at specs, staring at my code, and staring at the buggy behavior. Currently I'm getting Drive Fault errors returned in the status byte of the ATA drive (inb 0x1F7 returns 0x50). I've been unable to locate why this is, I have looked through the wiki pages on ATA stuffs and I don't see it, etc, etc.

My code:

Code: Select all

typedef struct {
	unsigned addr;
	unsigned short size;
	unsigned short last;
}__attribute__((packed)) prdtable_t;

int ata_dma_init(struct ata_controller *cont, struct ata_device *dev, int rw, unsigned char *buffer)
{
	prdtable_t *t = (prdtable_t *)cont->prdt_virt;
	t->addr = cont->dma_buf_phys;
	t->size = 512;
	t->last = 0x8000;
	outl(cont->port_bmr_base + BMR_PRDT, (unsigned)cont->prdt_phys);
	if (rw == WRITE)
		memcpy(cont->dma_buf_virt, buffer, 512);
	return 1;
}

int ata_start_command(struct ata_controller *cont, struct ata_device *dev, unsigned long long addr, char rw)
{
	unsigned char cmd=0;
	if(rw == READ)
		cmd = 0x25;
	else
		cmd = 0x35;
	outb(cont->port_cmd_base+REG_DEVICE, 0x40 | (dev->id << 4));
	ATA_DELAY(cont);
	outb(cont->port_cmd_base+REG_SEC_CNT, 0x00);
	
	outb(cont->port_cmd_base+REG_LBA_LOW, (unsigned char)(addr >> 24));
	outb(cont->port_cmd_base+REG_LBA_MID, (unsigned char)(addr >> 32));
	outb(cont->port_cmd_base+REG_LBA_HIG, (unsigned char)(addr >> 40));
	
	outb(cont->port_cmd_base+REG_SEC_CNT, 0x01);
	outb(cont->port_cmd_base+REG_LBA_LOW, (unsigned char)addr);
	outb(cont->port_cmd_base+REG_LBA_MID, (unsigned char)(addr >> 8));
	outb(cont->port_cmd_base+REG_LBA_HIG, (unsigned char)(addr >> 16));
	
	outb(cont->port_cmd_base+REG_COMMAND, cmd);
	return 1;
}

int ata_dma_rw(struct ata_controller *cont, struct ata_device *dev, int rw, unsigned blk, char *buf)
{
	ata_start_command(cont, dev, blk, rw);
	ATA_DELAY(cont);
	
	inb(cont->port_bmr_base + BMR_STATUS);
	ata_dma_init(cont, dev, rw, (unsigned char *)buf);
	inb(cont->port_bmr_base + BMR_STATUS);
	
	uint8_t cmd = inb(cont->port_bmr_base + BMR_COMMAND);
	cmd = 0x1 | (rw == READ ? 8 : 0);
	outb(cont->port_bmr_base, cmd);
	
	unsigned char st;
	while(1) {
		st = inb(cont->port_bmr_base + BMR_STATUS);
		uint8_t sus = ata_reg_inb(cont, REG_STATUS);
		
		printk(1, "poll %x %x\n", sus, st);
		if(!(st & 0x4))
			continue;
		if(st & 0x2)
			panic("DMA err");
		if(sus & STATUS_ERR)
			panic("Error in reg_stat");
		if(!(sus & STATUS_BSY) && (sus & STATUS_DRQ))
			break;
	}
	
	st = inb(cont->port_bmr_base + BMR_COMMAND);
	outb(cont->port_bmr_base + BMR_COMMAND, st);
	st = inb(cont->port_bmr_base + BMR_STATUS);
	outb(cont->port_bmr_base + BMR_STATUS, st);
	
	uint8_t status = ata_reg_inb(cont, REG_STATUS);
	if (!(status & STATUS_ERR)) {
		if(st & 0x2)
			panic("B");
	} else
		panic("A");
	if (rw == READ)
		memcpy(buf, cont->dma_buf_virt, 512);
	return 512;
}
Yeah, I will convert stuff to uint16_t and the likes once I get it working.

In Qemu it just flat out fails every time. It gets into the polling loop and spits out 'poll 50 4' until the end of time. In bochs, it seems to sometimes work, at least the first time. Sometimes it will correctly read the first sector, but when it tries to read another sector it freezes, spitting out 'poll 58 0' occasionally interspersed with 'poll 5A 0'. Sometimes it has the same behavior, except that it reads the sector as all zeros. Most of the time its the first one though.

I'm sure that its something really simple, but I've been staring at the code for a long time with no results, so I figured I'd ask here.
Thanks,
-JL

EDIT: I Feel like its a timing issue, since I never had the problems with PIO mode until I switched to a different (slower) computer, but I could be wildly wrong about that.

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 3:44 pm
by gerryg400
Just had a quick look. Aren't you issuing the command before (in ata_start_command) setting up your DMA ?

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 3:48 pm
by piranha
As far as I can tell, it makes no difference where I put the call the ata_start_command, be it at the beginning, right before polling, or right before issuing the DMA command to the BMR.

If I'm supposed to issue it after I set up DMA (which is how it was originally, I was just playing around with the code), I'll do that.

-JL

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 3:57 pm
by gerryg400
I need to go now, but I'll have a closer look later. My code also waits after setting the register before issuing the read/write command. It looks a little like this

Code: Select all

    set up DMA
    wait for !ATA_BUSY
    setup control and lba regs
    wait for !ATA_BUSY && ATA_READY   <<<==== Add this 
    send read/write command
    

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 4:11 pm
by piranha
Okay, I changed it so that in the main function its:

Code: Select all

	inb(cont->port_bmr_base + BMR_STATUS);
	ata_dma_init(cont, dev, rw, (unsigned char *)buf);
	inb(cont->port_bmr_base + BMR_STATUS);
	uint8_t cmdReg = inb(cont->port_bmr_base + BMR_COMMAND);
	cmdReg = 0x1 | (rw == READ ? 8 : 0);
	outb(cont->port_bmr_base, cmdReg);
	while(1)
	{
		int x = ata_reg_inb(cont, REG_STATUS);
		if(!(x & STATUS_BSY))
			break;
	}
	ata_start_command(cont, dev, blk, rw);
And in ata_start_command its:

Code: Select all

	outb(cont->port_cmd_base+REG_LBA_HIG, (unsigned char)(addr >> 16));
	
	while(1)
	{
		int x = ata_reg_inb(cont, REG_STATUS);
		if(!(x & STATUS_BSY) && (x & STATUS_DRDY))
			break;
	}
	outb(cont->port_cmd_base+REG_COMMAND, cmd);
Unfortunately, I get the exact same behavior.

-JL

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 7:25 pm
by piranha
Okay, it would seem that I'm not supposed to poll for DRQ after starting the command for DMA transfers. At least, I assume thats right. It seems to be working now...

-JL

Re: ATA DMA Mode Troubles

Posted: Wed Nov 09, 2011 7:31 pm
by gerryg400
Do you mind posting the working version ? Just the important part. TIA.

Re: ATA DMA Mode Troubles

Posted: Thu Nov 10, 2011 1:14 pm
by piranha
Here is the working version of the code. At least, it appears to work, though it wouldn't surprise me if there were still issue with it.

Code: Select all

typedef struct {
	unsigned addr;
	unsigned short size;
	unsigned short last;
}__attribute__((packed)) prdtable_t;

int ata_dma_init(struct ata_controller *cont, struct ata_device *dev, int size, int rw, unsigned char *buffer)
{
	prdtable_t *t = (prdtable_t *)cont->prdt_virt;
	t->addr = cont->dma_buf_phys;
	t->size = size;
	t->last = 0x8000;
	outl(cont->port_bmr_base + BMR_PRDT, (unsigned)cont->prdt_phys);
	if (rw == WRITE)
		memcpy(cont->dma_buf_virt, buffer, size);
	unsigned char st = inb(cont->port_bmr_base + BMR_STATUS);
	st |= BMR_STATUS_ERROR | BMR_STATUS_IRQ;
	outb(cont->port_bmr_base + BMR_STATUS, st);
	return 1;
}

int ata_start_command(struct ata_controller *cont, struct ata_device *dev, unsigned block, char rw)
{
	unsigned long long addr = block;
	unsigned char cmd=0;
	if(rw == READ)
		cmd = 0x25;
	else
		cmd = 0x35;
	if(cont->selected != dev) {
		outb(cont->port_cmd_base+REG_DEVICE, 0x40 | (dev->id << 4));
		cont->selected = dev;
		ATA_DELAY(cont);
	}
	outb(cont->port_cmd_base+REG_SEC_CNT, 0x00);
	outb(cont->port_cmd_base+REG_LBA_LOW, (unsigned char)(addr >> 24));
	outb(cont->port_cmd_base+REG_LBA_MID, (unsigned char)(addr >> 32));
	outb(cont->port_cmd_base+REG_LBA_HIG, (unsigned char)(addr >> 40));
	outb(cont->port_cmd_base+REG_SEC_CNT, 0x01);
	outb(cont->port_cmd_base+REG_LBA_LOW, (unsigned char)addr);
	outb(cont->port_cmd_base+REG_LBA_MID, (unsigned char)(addr >> 8));
	outb(cont->port_cmd_base+REG_LBA_HIG, (unsigned char)(addr >> 16));
	
	while(1)
	{
		int x = ata_reg_inb(cont, REG_STATUS);
		if(!(x & STATUS_BSY) && (x & STATUS_DRDY))
			break;
	}
	outb(cont->port_cmd_base+REG_COMMAND, cmd);
	return 1;
}

int ata_dma_rw(struct ata_controller *cont, struct ata_device *dev, int rw, unsigned blk, char *buf)
{
	unsigned size=512;
	ata_dma_init(cont, dev, size, rw, (unsigned char *)buf);
	uint8_t cmdReg = inb(cont->port_bmr_base + BMR_COMMAND);
	cmdReg = (rw == READ ? 8 : 0);
	outb(cont->port_bmr_base, cmdReg);
	while(1)
	{
		int x = ata_reg_inb(cont, REG_STATUS);
		if(!(x & STATUS_BSY))
			break;
	}
	ata_start_command(cont, dev, blk, rw);
	ATA_DELAY(cont);
	cmdReg = inb(cont->port_bmr_base + BMR_COMMAND);
	cmdReg |= 0x1 | (rw == READ ? 8 : 0);
	outb(cont->port_bmr_base, cmdReg);
	unsigned char st;
	while(1) {
		st = inb(cont->port_bmr_base + BMR_STATUS);
		uint8_t sus = ata_reg_inb(cont, REG_STATUS);
		if(!(st & 0x4))
			continue;
		if(st & 0x2)
			panic("DMA err");
		if(sus & STATUS_ERR)
			panic("Error in reg_stat");
		if(!(sus & STATUS_BSY))
			break;
		
	}
	uint8_t status = ata_reg_inb(cont, REG_STATUS);
	if (!(status & STATUS_ERR)) {
		if(st & 0x2)
			panic("B");
	} else
	{
                panic("A");
	}
	if (rw == READ)
		memcpy(buf, cont->dma_buf_virt, 512);
	
	st = inb(cont->port_bmr_base + BMR_COMMAND);
	outb(cont->port_bmr_base + BMR_COMMAND, st & ~0x1);
	st = inb(cont->port_bmr_base + BMR_STATUS);
	outb(cont->port_bmr_base + BMR_STATUS, st | BMR_STATUS_ERROR | BMR_STATUS_IRQ);
	return 512;
}
Hope this helps someone!

-JL