AHCI write hangs on some hardware
Posted: Mon Oct 10, 2016 5:05 am
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:
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?
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);
};