AHCI controller reports success but reads no data
Posted: Mon Nov 13, 2023 4:54 pm
My AHCI driver works correctly in VirtualBox, I'm able to read and write sectors and perform a full installation of my OS. On my physical machine, I'm getting some strange behaviour. It successfully initializes the port, sends an IDENTIFY command, and receives the correct disk size and name, and PRDC is set to 0x200 after this operation - the correct size. Here is the code for ataInit(), where the IDENTIFY command is sent:
It prints "IDENTIFY PRDC: 0x200" on real hardware. ahciIssueCmd() is defined as:
And here is how I implement reading, along with some deubgging:
In VirtualBox, the read operation works too. It reports no errors (PxSERR = 0), and PRDC=0x8000, the requested size. The debugging code prints the regions it iterates over when building the PRDT, there are a few regions all of which are page-aligned and page-sized (That is, each one is a single page). It prints the correct hex dump of the first sector, where I can clearly see the boot signature.
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.
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.