Code: Select all
void ataInit(AHCIController *ctrl, int portno)
{
ATADevice *dev = NEW(ATADevice);
ctrl->ataDevices[ctrl->numAtaDevices++] = dev;
mutexInit(&dev->lock);
dev->ctrl = ctrl;
dev->port = &ctrl->regs->ports[portno];
dev->sd = NULL;
// create the operations area
if (dmaCreateBuffer(&dev->dmabuf, sizeof(AHCIOpArea), 0) != 0)
{
// it didn't work
kprintf("sdahci: failed to allocate operations area\n");
kfree(dev);
ctrl->numAtaDevices--;
return;
};
// set up the operations area
AHCIOpArea *opArea = (AHCIOpArea*) dmaGetPtr(&dev->dmabuf);
memset(opArea, 0, sizeof(AHCIOpArea));
// set the command list and FIS area
dev->port->clb = dmaGetPhys(&dev->dmabuf) + __builtin_offsetof(AHCIOpArea, cmdlist);
dev->port->fb = dmaGetPhys(&dev->dmabuf) + __builtin_offsetof(AHCIOpArea, fisArea);
// we only use the first command header, so initialize it to point to the table
opArea->cmdlist[0].ctba = dmaGetPhys(&dev->dmabuf) + __builtin_offsetof(AHCIOpArea, cmdtab);
// start the command engine
ahciStartCmd(dev->port);
dev->port->serr = dev->port->serr;
dev->port->is = dev->port->is;
// send the IDENTIFY command.
opArea->cmdlist[0].cfl = sizeof(FIS_REG_H2D)/4; // FIS length in dwords
opArea->cmdlist[0].w = 0; // read data
opArea->cmdlist[0].prdtl = 1; // only one PRDT entry
opArea->cmdlist[0].p = 1;
opArea->cmdtab.prdt[0].dba = dmaGetPhys(&dev->dmabuf) + __builtin_offsetof(AHCIOpArea, id);
opArea->cmdtab.prdt[0].dbc = 511; // length-1
opArea->cmdtab.prdt[0].i = 0; // do not interrupt
// set up command FIS
FIS_REG_H2D *cmdfis = (FIS_REG_H2D*) opArea->cmdtab.cfis;
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1;
cmdfis->command = ATA_CMD_IDENTIFY;
// issue the command and await completion
__sync_synchronize();
if (ahciIssueCmd(dev->port) != 0)
{
kprintf("sdahci: error during identification\n");
return;
};
kprintf("IDENTIFY PRDC: 0x%08x\n", opArea->cmdlist[0].prdbc);
sleep(8000);
uint32_t *cmdsetptr = (uint32_t*) &opArea->id[ATA_IDENT_COMMANDSETS];
uint32_t cmdset = *cmdsetptr;
uint64_t size;
if (cmdset & (1 << 26))
{
uint64_t *szptr = (uint64_t*) &opArea->id[ATA_IDENT_MAX_LBA_EXT];
size = (*szptr) & 0xFFFFFFFFFFFF;
}
else
{
uint32_t *szptr = (uint32_t*) &opArea->id[ATA_IDENT_MAX_LBA];
size = (uint64_t) (*szptr);
};
char model[41];
int k;
for (k=0; k<40; k+=2)
{
model[k] = opArea->id[ATA_IDENT_MODEL + k + 1];
model[k+1] = opArea->id[ATA_IDENT_MODEL + k];
};
model[40] = 0;
char *check = &model[39];
while (*check == ' ')
{
if (check == model) break;
*check-- = 0;
};
kprintf("sdahci: size in MB: %lu, model: %s\n", size / 1024 / 2, model);
SDParams sdpars;
sdpars.flags = 0;
sdpars.blockSize = 512;
sdpars.totalSize = size * 512;
// do a cache flush
opArea->cmdlist[0].w = 0;
opArea->cmdlist[0].p = 0;
opArea->cmdlist[0].c = 1;
opArea->cmdlist[0].prdtl = 0;
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1;
cmdfis->command = ATA_CMD_CACHE_FLUSH_EXT;
cmdfis->lba0 = 0;
cmdfis->lba1 = 0;
cmdfis->lba2 = 0;
cmdfis->device = 1<<6; // LBA mode
cmdfis->lba3 = 0;
cmdfis->lba4 = 0;
cmdfis->lba5 = 0;
cmdfis->countl = 0;
cmdfis->counth = 0;
// issue the flush command
int status = ahciIssueCmd(dev->port);
kprintf("sdahci: cache flush status: %d\n", status);
dev->sd = sdCreate(&sdpars, model, &ataOps, dev);
if (dev->sd == NULL)
{
kprintf("sdahci: SD creation failed\n");
// NOTE: do not free anything; this is done upon removing the driver
};
};
Code: Select all
int ahciIssueCmd(volatile AHCIPort *port)
{
uint64_t startTime = getNanotime();
port->is = port->is;
port->ci = 1;
while (1)
{
if (getNanotime()-startTime > 8*NANO_PER_SEC)
{
// taking longer than 8 seconds
kprintf("sdahci: timeout; aborting command. IS=0x%08X, SERR=0x%08X, TFD=0x%08X\n", port->is, port->serr, port->tfd);
ahciStopCmd(port);
ahciStartCmd(port);
port->serr = port->serr;
return EIO;
};
if (port->is & IS_ERR_FATAL)
{
// a fatal error occured
kprintf("sdahci: fatal error. IS=0x%08X, SERR=0x%08X\n", port->is, port->serr);
ahciStopCmd(port);
ahciStartCmd(port);
port->serr = port->serr;
return EIO;
};
if ((port->ci & 1) == 0)
{
break;
};
};
int busy = STS_BSY | STS_DRQ;
while (port->tfd & busy)
{
if (getNanotime()-startTime > 8*NANO_PER_SEC)
{
kprintf("sdahci: timeout; aborting command. IS=0x%08X, SERR=0x%08X, TFD=0x%08X\n", port->is, port->serr, port->tfd);
ahciStopCmd(port);
ahciStartCmd(port);
port->serr = port->serr;
return EIO;
};
};
if (port->tfd & STS_ERR)
{
return EIO;
};
return 0;
};
Code: Select all
int ataTransferBlocks(ATADevice *dev, size_t startBlock, size_t numBlocks, void *buffer, int dir)
{
mutexLock(&dev->lock);
AHCIOpArea *opArea = (AHCIOpArea*) dmaGetPtr(&dev->dmabuf);
opArea->cmdlist[0].cfl = sizeof(FIS_REG_H2D) / 4;
opArea->cmdlist[0].c = 1; // clear BSY when done
if (dir == ATA_READ)
{
opArea->cmdlist[0].w = 0;
opArea->cmdlist[0].p = 1;
}
else
{
opArea->cmdlist[0].w = 1;
opArea->cmdlist[0].p = 0;
};
uint16_t prdtl = 0;
DMARegion reg;
for (dmaFirstRegion(®, buffer, 512*numBlocks, 0); reg.physAddr!=0; dmaNextRegion(®))
{
if (prdtl == 9) panic("unexpected input");
if (dir == ATA_READ && startBlock == 0)
{
kprintf("ATA_READ 0: Region: physAddr=0x%016lX, physSize=0x%04lX\n", reg.physAddr, reg.physSize);
};
opArea->cmdtab.prdt[prdtl].dba = reg.physAddr;
opArea->cmdtab.prdt[prdtl].dbc = reg.physSize - 1;
opArea->cmdtab.prdt[prdtl].i = 0;
prdtl++;
};
opArea->cmdlist[0].prdtl = prdtl;
FIS_REG_H2D *cmdfis = (FIS_REG_H2D*)(&opArea->cmdtab.cfis);
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1;
if (dir == ATA_READ)
{
cmdfis->command = ATA_CMD_READ_DMA_EXT;
}
else
{
cmdfis->command = ATA_CMD_WRITE_DMA_EXT;
};
cmdfis->lba0 = (uint8_t)startBlock;
cmdfis->lba1 = (uint8_t)(startBlock>>8);
cmdfis->lba2 = (uint8_t)(startBlock>>16);
cmdfis->lba3 = (uint8_t)(startBlock>>24);
cmdfis->lba4 = (uint8_t)(startBlock>>32);
cmdfis->lba5 = (uint8_t)(startBlock>>40);
cmdfis->device = 1<<6; // LBA mode
cmdfis->countl = numBlocks & 0xFF;
cmdfis->counth = (numBlocks >> 8) & 0xFF;
// issue the command
int status = ahciIssueCmd(dev->port);
if (status != 0)
{
mutexUnlock(&dev->lock);
return status;
};
kprintf("PRDC: 0x%08x, IS=0x%08x, SERR=0x%08x\n", opArea->cmdlist[0].prdbc, dev->port->is, dev->port->serr);
// do a cache flush
opArea->cmdlist[0].w = 0;
opArea->cmdlist[0].p = 0;
opArea->cmdlist[0].c = 1;
opArea->cmdlist[0].prdtl = 0;
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1;
cmdfis->command = ATA_CMD_CACHE_FLUSH_EXT;
cmdfis->lba0 = 0;
cmdfis->lba1 = 0;
cmdfis->lba2 = 0;
cmdfis->device = 1<<6; // LBA mode
cmdfis->lba3 = 0;
cmdfis->lba4 = 0;
cmdfis->lba5 = 0;
cmdfis->countl = 0;
cmdfis->counth = 0;
// issue the flush command
status = ahciIssueCmd(dev->port);
mutexUnlock(&dev->lock);
return status;
};
int ataReadBlocks(void *drvdata, size_t startBlock, size_t numBlocks, void *buffer)
{
// DEBUG
memset(buffer, 0, 512 * numBlocks);
ATADevice *dev = (ATADevice*) drvdata;
int result = ataTransferBlocks(dev, startBlock, numBlocks, buffer, ATA_READ);
if (startBlock == 0)
{
kprintf("Dumping first sector of ATA drive:");
uint8_t *buf = (uint8_t*) buffer;
for (size_t i=0; i<512; i++)
{
if ((i % 32) == 0)
{
kprintf("\n");
};
kprintf("%02hhx ", buf[i]);
};
kprintf("\nEND OF DUMP\n");
};
return result;
};
But on physical hardware, the IDENTIFY command succeeds as I mentioned, but something strange happens with the read: ahciIssueCmd() succeeds, returning 0, but PRDC is set to 0, and indeed no data is transferred (The buffer is filled with zeroes, but there by the memset()). PxSERR is set to 0 at this point and PxIS is set to 0x1. This is identical to VirtualBox, except that in VirtualBox, PRDC is 0x8000 but on physical hardware it's 0.
I checked:
* The alignment for all structures is correct.
* The regions are page-aligned, and page-sized (i.e. each is a single page).
* PxSERR is zero.
I don't understand what I'm doing differently between the 2 commands that causes the reads to fail. Can anyone see what I'm doing wrong? I'll continue debugging in the meantime.