Page 1 of 1

AHCI write hangs on some hardware

Posted: Mon Oct 10, 2016 5:05 am
by mariuszp
My AHCI driver works on VirtualBox but fails to work in VMWare as well as on real hardware. In those cases, it reads successfully, but for some reason fails to write. When writing, it freezes on the final loop of this code, which waits for the command issue bit to clear:

Code: Select all

		dev->port->is = dev->port->is;
		
		// we don't need to protect the device with semaphores, because only this thread
		// accesses its port.

		// first find a free command slot; we may wait for some time if the drive is
		// particularly busy.
		uint32_t slot = 0;
		while (dev->port->ci & (1 << slot))
		{
			slot = (slot + 1) % 32;
			__sync_synchronize();
		};
		
		// fill in the appropriate structure.
		// do not zero it; it was already zeroed before and only this thread updates the
		// structures so we know that any unused fields are already zero.
		cmdhead[slot].cfl = sizeof(FIS_REG_H2D)/4;
		cmdhead[slot].w = 0;
		cmdhead[slot].prdtl = 1;
		__sync_synchronize();
		
		AHCICommandTable *cmdtbl = (AHCICommandTable*) ((char*)dmaGetPtr(&dev->dmabuf) + 1024+256+8*1024+256*slot);
		cmdtbl->prdt_entry[0].dba = dmaGetPhys(&dev->iobuf) + 4096*slot;
		cmdtbl->prdt_entry[0].dbc = 512*cmd->count;
		cmdtbl->prdt_entry[0].i = 0;
		
		FIS_REG_H2D *cmdfis = (FIS_REG_H2D*)(&cmdtbl->cfis);
		cmdfis->fis_type = FIS_TYPE_REG_H2D;
		cmdfis->c = 1;
		if (cmd->type == SD_CMD_READ)
		{
			cmdfis->command = ATA_CMD_READ_DMA_EXT;
		}
		else
		{
			cmdfis->command = ATA_CMD_WRITE_DMA_EXT;
		};
		
		cmdfis->lba0 = (uint8_t)cmd->index;
		cmdfis->lba1 = (uint8_t)(cmd->index>>8);
		cmdfis->lba2 = (uint8_t)(cmd->index>>16);
		cmdfis->device = 1<<6;	// LBA mode
	 
		cmdfis->lba3 = (uint8_t)(cmd->index>>24);
		cmdfis->lba4 = (uint8_t)(cmd->index>>32);
		cmdfis->lba5 = (uint8_t)(cmd->index>>40);
		
		cmdfis->countl = (uint8_t)(cmd->count);
		cmdfis->counth = (uint8_t)(cmd->count>>8);
		
		char *hwbuf = (char*) dmaGetPtr(&dev->iobuf) + (4096*slot);
		if (cmd->type == SD_CMD_WRITE)
		{
			memcpy(hwbuf, cmd->block, 512*cmd->count);
		};
		
		// wait for the port to stop being busy
		int busy = (1 << 7) | (1 << 3);
		while (dev->port->tfd & busy) __sync_synchronize();
		__sync_synchronize();
		
		if (dev->port->tfd & (1 << 0))
		{
			panic("AHCI transfer error!");
		};
		
		dev->port->ci = (1 << slot);
		__sync_synchronize();
		
		while (dev->port->ci & (1 << slot))
		{
			if (dev->port->is & (1 << 27))
			{
				panic("AHCI error!");
			};
			__sync_synchronize();
		};
		__sync_synchronize();
		
		if (cmd->type == SD_CMD_READ)
		{
			memcpy(cmd->block, hwbuf, 512*cmd->count);
		};
The code decides whether to read or write based on the value of cmd->type (SD_CMD_READ or SD_CMD_WRITE). There must obviously be some other thing I need to do when writing. Does this code appear to be missing something?

Re: AHCI write hangs on some hardware

Posted: Mon Oct 10, 2016 10:17 am
by Ch4ozz
For read I do this:

Code: Select all

cmdheader->w = 0;
And for write I do this:

Code: Select all

cmdheader->w = 1;
cmdheader->c = 1;
cmdheader->p = 1;
You only have it set to 0 here


I also clear all pending interrupt bits before I do anything with the device:

Code: Select all

port->is = 0xFFFFFFFF;

Re: AHCI write hangs on some hardware

Posted: Mon Oct 10, 2016 12:11 pm
by mariuszp
I do clear all pending interrupts:

Code: Select all

dev->port->is = dev->port->is;
Now I changed the code to:

Code: Select all

		if (cmd->type == SD_CMD_READ)
		{
			cmdhead[slot].w = 0;
			cmdhead[slot].p = 0;
			cmdhead[slot].c = 0;
		}
		else
		{
			cmdhead[slot].w = 1;
			cmdhead[slot].p = 1;
			cmdhead[slot].c = 1;
		};
However, this still does not fix the problem; it still works in VirtualBox, but freezes in VMWare.

Some debugging reveals that at the point where it hangs, the interrupt status is set to 0x40000000 which means that Task File Error status (TFES) is set.

Re: AHCI write hangs on some hardware

Posted: Wed Oct 12, 2016 2:59 am
by mariuszp
bump