08467a88a6
Replace the use of off_t in the various DiskImage related classes with std::streampos. off_t is a signed 32 bit integer on most 32-bit systems, whereas std::streampos is normally a 64 bit integer on most modern systems. Furthermore, std::streampos is the type used by tellg() and seekg() in the standard library, so it should have been used in the first place. This patch makes it possible to use disk images larger than 2 GiB on 32 bit systems with a modern C++ standard library.
1138 lines
32 KiB
C++
1138 lines
32 KiB
C++
/*
|
|
* Copyright (c) 2004-2005 The Regents of The University of Michigan
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met: redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer;
|
|
* redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution;
|
|
* neither the name of the copyright holders nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* Authors: Andrew Schultz
|
|
* Ali Saidi
|
|
*/
|
|
|
|
/** @file
|
|
* Device model implementation for an IDE disk
|
|
*/
|
|
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <string>
|
|
|
|
#include "arch/isa_traits.hh"
|
|
#include "base/chunk_generator.hh"
|
|
#include "base/cprintf.hh" // csprintf
|
|
#include "base/trace.hh"
|
|
#include "config/the_isa.hh"
|
|
#include "debug/IdeDisk.hh"
|
|
#include "dev/disk_image.hh"
|
|
#include "dev/ide_ctrl.hh"
|
|
#include "dev/ide_disk.hh"
|
|
#include "sim/core.hh"
|
|
#include "sim/sim_object.hh"
|
|
|
|
using namespace std;
|
|
using namespace TheISA;
|
|
|
|
IdeDisk::IdeDisk(const Params *p)
|
|
: SimObject(p), ctrl(NULL), image(p->image), diskDelay(p->delay),
|
|
dmaTransferEvent(this), dmaReadCG(NULL), dmaReadWaitEvent(this),
|
|
dmaWriteCG(NULL), dmaWriteWaitEvent(this), dmaPrdReadEvent(this),
|
|
dmaReadEvent(this), dmaWriteEvent(this)
|
|
{
|
|
// Reset the device state
|
|
reset(p->driveID);
|
|
|
|
// fill out the drive ID structure
|
|
memset(&driveID, 0, sizeof(struct ataparams));
|
|
|
|
// Calculate LBA and C/H/S values
|
|
uint16_t cylinders;
|
|
uint8_t heads;
|
|
uint8_t sectors;
|
|
|
|
uint32_t lba_size = image->size();
|
|
if (lba_size >= 16383*16*63) {
|
|
cylinders = 16383;
|
|
heads = 16;
|
|
sectors = 63;
|
|
} else {
|
|
if (lba_size >= 63)
|
|
sectors = 63;
|
|
else
|
|
sectors = lba_size;
|
|
|
|
if ((lba_size / sectors) >= 16)
|
|
heads = 16;
|
|
else
|
|
heads = (lba_size / sectors);
|
|
|
|
cylinders = lba_size / (heads * sectors);
|
|
}
|
|
|
|
// Setup the model name
|
|
strncpy((char *)driveID.atap_model, "5MI EDD si k",
|
|
sizeof(driveID.atap_model));
|
|
// Set the maximum multisector transfer size
|
|
driveID.atap_multi = MAX_MULTSECT;
|
|
// IORDY supported, IORDY disabled, LBA enabled, DMA enabled
|
|
driveID.atap_capabilities1 = 0x7;
|
|
// UDMA support, EIDE support
|
|
driveID.atap_extensions = 0x6;
|
|
// Setup default C/H/S settings
|
|
driveID.atap_cylinders = cylinders;
|
|
driveID.atap_sectors = sectors;
|
|
driveID.atap_heads = heads;
|
|
// Setup the current multisector transfer size
|
|
driveID.atap_curmulti = MAX_MULTSECT;
|
|
driveID.atap_curmulti_valid = 0x1;
|
|
// Number of sectors on disk
|
|
driveID.atap_capacity = lba_size;
|
|
// Multiword DMA mode 2 and below supported
|
|
driveID.atap_dmamode_supp = 0x4;
|
|
// Set PIO mode 4 and 3 supported
|
|
driveID.atap_piomode_supp = 0x3;
|
|
// Set DMA mode 4 and below supported
|
|
driveID.atap_udmamode_supp = 0x1f;
|
|
// Statically set hardware config word
|
|
driveID.atap_hwreset_res = 0x4001;
|
|
|
|
//arbitrary for now...
|
|
driveID.atap_ata_major = WDC_VER_ATA7;
|
|
}
|
|
|
|
IdeDisk::~IdeDisk()
|
|
{
|
|
// destroy the data buffer
|
|
delete [] dataBuffer;
|
|
}
|
|
|
|
void
|
|
IdeDisk::reset(int id)
|
|
{
|
|
// initialize the data buffer and shadow registers
|
|
dataBuffer = new uint8_t[MAX_DMA_SIZE];
|
|
|
|
memset(dataBuffer, 0, MAX_DMA_SIZE);
|
|
memset(&cmdReg, 0, sizeof(CommandReg_t));
|
|
memset(&curPrd.entry, 0, sizeof(PrdEntry_t));
|
|
|
|
curPrdAddr = 0;
|
|
curSector = 0;
|
|
cmdBytes = 0;
|
|
cmdBytesLeft = 0;
|
|
drqBytesLeft = 0;
|
|
dmaRead = false;
|
|
intrPending = false;
|
|
|
|
// set the device state to idle
|
|
dmaState = Dma_Idle;
|
|
|
|
if (id == DEV0) {
|
|
devState = Device_Idle_S;
|
|
devID = DEV0;
|
|
} else if (id == DEV1) {
|
|
devState = Device_Idle_NS;
|
|
devID = DEV1;
|
|
} else {
|
|
panic("Invalid device ID: %#x\n", id);
|
|
}
|
|
|
|
// set the device ready bit
|
|
status = STATUS_DRDY_BIT;
|
|
|
|
/* The error register must be set to 0x1 on start-up to
|
|
indicate that no diagnostic error was detected */
|
|
cmdReg.error = 0x1;
|
|
}
|
|
|
|
////
|
|
// Utility functions
|
|
////
|
|
|
|
bool
|
|
IdeDisk::isDEVSelect()
|
|
{
|
|
return ctrl->isDiskSelected(this);
|
|
}
|
|
|
|
Addr
|
|
IdeDisk::pciToDma(Addr pciAddr)
|
|
{
|
|
if (ctrl)
|
|
return ctrl->pciToDma(pciAddr);
|
|
else
|
|
panic("Access to unset controller!\n");
|
|
}
|
|
|
|
////
|
|
// Device registers read/write
|
|
////
|
|
|
|
void
|
|
IdeDisk::readCommand(const Addr offset, int size, uint8_t *data)
|
|
{
|
|
if (offset == DATA_OFFSET) {
|
|
if (size == sizeof(uint16_t)) {
|
|
*(uint16_t *)data = cmdReg.data;
|
|
} else if (size == sizeof(uint32_t)) {
|
|
*(uint16_t *)data = cmdReg.data;
|
|
updateState(ACT_DATA_READ_SHORT);
|
|
*((uint16_t *)data + 1) = cmdReg.data;
|
|
} else {
|
|
panic("Data read of unsupported size %d.\n", size);
|
|
}
|
|
updateState(ACT_DATA_READ_SHORT);
|
|
return;
|
|
}
|
|
assert(size == sizeof(uint8_t));
|
|
switch (offset) {
|
|
case ERROR_OFFSET:
|
|
*data = cmdReg.error;
|
|
break;
|
|
case NSECTOR_OFFSET:
|
|
*data = cmdReg.sec_count;
|
|
break;
|
|
case SECTOR_OFFSET:
|
|
*data = cmdReg.sec_num;
|
|
break;
|
|
case LCYL_OFFSET:
|
|
*data = cmdReg.cyl_low;
|
|
break;
|
|
case HCYL_OFFSET:
|
|
*data = cmdReg.cyl_high;
|
|
break;
|
|
case DRIVE_OFFSET:
|
|
*data = cmdReg.drive;
|
|
break;
|
|
case STATUS_OFFSET:
|
|
*data = status;
|
|
updateState(ACT_STAT_READ);
|
|
break;
|
|
default:
|
|
panic("Invalid IDE command register offset: %#x\n", offset);
|
|
}
|
|
DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data);
|
|
}
|
|
|
|
void
|
|
IdeDisk::readControl(const Addr offset, int size, uint8_t *data)
|
|
{
|
|
assert(size == sizeof(uint8_t));
|
|
*data = status;
|
|
if (offset != ALTSTAT_OFFSET)
|
|
panic("Invalid IDE control register offset: %#x\n", offset);
|
|
DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data);
|
|
}
|
|
|
|
void
|
|
IdeDisk::writeCommand(const Addr offset, int size, const uint8_t *data)
|
|
{
|
|
if (offset == DATA_OFFSET) {
|
|
if (size == sizeof(uint16_t)) {
|
|
cmdReg.data = *(const uint16_t *)data;
|
|
} else if (size == sizeof(uint32_t)) {
|
|
cmdReg.data = *(const uint16_t *)data;
|
|
updateState(ACT_DATA_WRITE_SHORT);
|
|
cmdReg.data = *((const uint16_t *)data + 1);
|
|
} else {
|
|
panic("Data write of unsupported size %d.\n", size);
|
|
}
|
|
updateState(ACT_DATA_WRITE_SHORT);
|
|
return;
|
|
}
|
|
|
|
assert(size == sizeof(uint8_t));
|
|
switch (offset) {
|
|
case FEATURES_OFFSET:
|
|
break;
|
|
case NSECTOR_OFFSET:
|
|
cmdReg.sec_count = *data;
|
|
break;
|
|
case SECTOR_OFFSET:
|
|
cmdReg.sec_num = *data;
|
|
break;
|
|
case LCYL_OFFSET:
|
|
cmdReg.cyl_low = *data;
|
|
break;
|
|
case HCYL_OFFSET:
|
|
cmdReg.cyl_high = *data;
|
|
break;
|
|
case DRIVE_OFFSET:
|
|
cmdReg.drive = *data;
|
|
updateState(ACT_SELECT_WRITE);
|
|
break;
|
|
case COMMAND_OFFSET:
|
|
cmdReg.command = *data;
|
|
updateState(ACT_CMD_WRITE);
|
|
break;
|
|
default:
|
|
panic("Invalid IDE command register offset: %#x\n", offset);
|
|
}
|
|
DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset,
|
|
(uint32_t)*data);
|
|
}
|
|
|
|
void
|
|
IdeDisk::writeControl(const Addr offset, int size, const uint8_t *data)
|
|
{
|
|
if (offset != CONTROL_OFFSET)
|
|
panic("Invalid IDE control register offset: %#x\n", offset);
|
|
|
|
if (*data & CONTROL_RST_BIT) {
|
|
// force the device into the reset state
|
|
devState = Device_Srst;
|
|
updateState(ACT_SRST_SET);
|
|
} else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) {
|
|
updateState(ACT_SRST_CLEAR);
|
|
}
|
|
|
|
nIENBit = *data & CONTROL_IEN_BIT;
|
|
|
|
DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset,
|
|
(uint32_t)*data);
|
|
}
|
|
|
|
////
|
|
// Perform DMA transactions
|
|
////
|
|
|
|
void
|
|
IdeDisk::doDmaTransfer()
|
|
{
|
|
if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma)
|
|
panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n",
|
|
dmaState, devState);
|
|
|
|
if (ctrl->dmaPending() || ctrl->getDrainState() != Drainable::Running) {
|
|
schedule(dmaTransferEvent, curTick() + DMA_BACKOFF_PERIOD);
|
|
return;
|
|
} else
|
|
ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent,
|
|
(uint8_t*)&curPrd.entry);
|
|
}
|
|
|
|
void
|
|
IdeDisk::dmaPrdReadDone()
|
|
{
|
|
|
|
DPRINTF(IdeDisk,
|
|
"PRD: baseAddr:%#x (%#x) byteCount:%d (%d) eot:%#x sector:%d\n",
|
|
curPrd.getBaseAddr(), pciToDma(curPrd.getBaseAddr()),
|
|
curPrd.getByteCount(), (cmdBytesLeft/SectorSize),
|
|
curPrd.getEOT(), curSector);
|
|
|
|
// the prd pointer has already been translated, so just do an increment
|
|
curPrdAddr = curPrdAddr + sizeof(PrdEntry_t);
|
|
|
|
if (dmaRead)
|
|
doDmaDataRead();
|
|
else
|
|
doDmaDataWrite();
|
|
}
|
|
|
|
void
|
|
IdeDisk::doDmaDataRead()
|
|
{
|
|
/** @todo we need to figure out what the delay actually will be */
|
|
Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize);
|
|
|
|
DPRINTF(IdeDisk, "doDmaRead, diskDelay: %d totalDiskDelay: %d\n",
|
|
diskDelay, totalDiskDelay);
|
|
|
|
schedule(dmaReadWaitEvent, curTick() + totalDiskDelay);
|
|
}
|
|
|
|
void
|
|
IdeDisk::regStats()
|
|
{
|
|
using namespace Stats;
|
|
dmaReadFullPages
|
|
.name(name() + ".dma_read_full_pages")
|
|
.desc("Number of full page size DMA reads (not PRD).")
|
|
;
|
|
dmaReadBytes
|
|
.name(name() + ".dma_read_bytes")
|
|
.desc("Number of bytes transfered via DMA reads (not PRD).")
|
|
;
|
|
dmaReadTxs
|
|
.name(name() + ".dma_read_txs")
|
|
.desc("Number of DMA read transactions (not PRD).")
|
|
;
|
|
|
|
dmaWriteFullPages
|
|
.name(name() + ".dma_write_full_pages")
|
|
.desc("Number of full page size DMA writes.")
|
|
;
|
|
dmaWriteBytes
|
|
.name(name() + ".dma_write_bytes")
|
|
.desc("Number of bytes transfered via DMA writes.")
|
|
;
|
|
dmaWriteTxs
|
|
.name(name() + ".dma_write_txs")
|
|
.desc("Number of DMA write transactions.")
|
|
;
|
|
}
|
|
|
|
void
|
|
IdeDisk::doDmaRead()
|
|
{
|
|
|
|
if (!dmaReadCG) {
|
|
// clear out the data buffer
|
|
memset(dataBuffer, 0, MAX_DMA_SIZE);
|
|
dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(),
|
|
curPrd.getByteCount(), TheISA::PageBytes);
|
|
|
|
}
|
|
if (ctrl->dmaPending() || ctrl->getDrainState() != Drainable::Running) {
|
|
schedule(dmaReadWaitEvent, curTick() + DMA_BACKOFF_PERIOD);
|
|
return;
|
|
} else if (!dmaReadCG->done()) {
|
|
assert(dmaReadCG->complete() < MAX_DMA_SIZE);
|
|
ctrl->dmaRead(pciToDma(dmaReadCG->addr()), dmaReadCG->size(),
|
|
&dmaReadWaitEvent, dataBuffer + dmaReadCG->complete());
|
|
dmaReadBytes += dmaReadCG->size();
|
|
dmaReadTxs++;
|
|
if (dmaReadCG->size() == TheISA::PageBytes)
|
|
dmaReadFullPages++;
|
|
dmaReadCG->next();
|
|
} else {
|
|
assert(dmaReadCG->done());
|
|
delete dmaReadCG;
|
|
dmaReadCG = NULL;
|
|
dmaReadDone();
|
|
}
|
|
}
|
|
|
|
void
|
|
IdeDisk::dmaReadDone()
|
|
{
|
|
|
|
uint32_t bytesWritten = 0;
|
|
|
|
|
|
// write the data to the disk image
|
|
for (bytesWritten = 0; bytesWritten < curPrd.getByteCount();
|
|
bytesWritten += SectorSize) {
|
|
|
|
cmdBytesLeft -= SectorSize;
|
|
writeDisk(curSector++, (uint8_t *)(dataBuffer + bytesWritten));
|
|
}
|
|
|
|
// check for the EOT
|
|
if (curPrd.getEOT()) {
|
|
assert(cmdBytesLeft == 0);
|
|
dmaState = Dma_Idle;
|
|
updateState(ACT_DMA_DONE);
|
|
} else {
|
|
doDmaTransfer();
|
|
}
|
|
}
|
|
|
|
void
|
|
IdeDisk::doDmaDataWrite()
|
|
{
|
|
/** @todo we need to figure out what the delay actually will be */
|
|
Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize);
|
|
uint32_t bytesRead = 0;
|
|
|
|
DPRINTF(IdeDisk, "doDmaWrite, diskDelay: %d totalDiskDelay: %d\n",
|
|
diskDelay, totalDiskDelay);
|
|
|
|
memset(dataBuffer, 0, MAX_DMA_SIZE);
|
|
assert(cmdBytesLeft <= MAX_DMA_SIZE);
|
|
while (bytesRead < curPrd.getByteCount()) {
|
|
readDisk(curSector++, (uint8_t *)(dataBuffer + bytesRead));
|
|
bytesRead += SectorSize;
|
|
cmdBytesLeft -= SectorSize;
|
|
}
|
|
DPRINTF(IdeDisk, "doDmaWrite, bytesRead: %d cmdBytesLeft: %d\n",
|
|
bytesRead, cmdBytesLeft);
|
|
|
|
schedule(dmaWriteWaitEvent, curTick() + totalDiskDelay);
|
|
}
|
|
|
|
void
|
|
IdeDisk::doDmaWrite()
|
|
{
|
|
DPRINTF(IdeDisk, "doDmaWrite: rescheduling\n");
|
|
if (!dmaWriteCG) {
|
|
// clear out the data buffer
|
|
dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(),
|
|
curPrd.getByteCount(), TheISA::PageBytes);
|
|
}
|
|
if (ctrl->dmaPending() || ctrl->getDrainState() != Drainable::Running) {
|
|
schedule(dmaWriteWaitEvent, curTick() + DMA_BACKOFF_PERIOD);
|
|
DPRINTF(IdeDisk, "doDmaWrite: rescheduling\n");
|
|
return;
|
|
} else if (!dmaWriteCG->done()) {
|
|
assert(dmaWriteCG->complete() < MAX_DMA_SIZE);
|
|
ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(),
|
|
&dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete());
|
|
DPRINTF(IdeDisk, "doDmaWrite: not done curPrd byte count %d, eot %#x\n",
|
|
curPrd.getByteCount(), curPrd.getEOT());
|
|
dmaWriteBytes += dmaWriteCG->size();
|
|
dmaWriteTxs++;
|
|
if (dmaWriteCG->size() == TheISA::PageBytes)
|
|
dmaWriteFullPages++;
|
|
dmaWriteCG->next();
|
|
} else {
|
|
DPRINTF(IdeDisk, "doDmaWrite: done curPrd byte count %d, eot %#x\n",
|
|
curPrd.getByteCount(), curPrd.getEOT());
|
|
assert(dmaWriteCG->done());
|
|
delete dmaWriteCG;
|
|
dmaWriteCG = NULL;
|
|
dmaWriteDone();
|
|
}
|
|
}
|
|
|
|
void
|
|
IdeDisk::dmaWriteDone()
|
|
{
|
|
DPRINTF(IdeDisk, "doWriteDone: curPrd byte count %d, eot %#x cmd bytes left:%d\n",
|
|
curPrd.getByteCount(), curPrd.getEOT(), cmdBytesLeft);
|
|
// check for the EOT
|
|
if (curPrd.getEOT()) {
|
|
assert(cmdBytesLeft == 0);
|
|
dmaState = Dma_Idle;
|
|
updateState(ACT_DMA_DONE);
|
|
} else {
|
|
doDmaTransfer();
|
|
}
|
|
}
|
|
|
|
////
|
|
// Disk utility routines
|
|
///
|
|
|
|
void
|
|
IdeDisk::readDisk(uint32_t sector, uint8_t *data)
|
|
{
|
|
uint32_t bytesRead = image->read(data, sector);
|
|
|
|
if (bytesRead != SectorSize)
|
|
panic("Can't read from %s. Only %d of %d read. errno=%d\n",
|
|
name(), bytesRead, SectorSize, errno);
|
|
}
|
|
|
|
void
|
|
IdeDisk::writeDisk(uint32_t sector, uint8_t *data)
|
|
{
|
|
uint32_t bytesWritten = image->write(data, sector);
|
|
|
|
if (bytesWritten != SectorSize)
|
|
panic("Can't write to %s. Only %d of %d written. errno=%d\n",
|
|
name(), bytesWritten, SectorSize, errno);
|
|
}
|
|
|
|
////
|
|
// Setup and handle commands
|
|
////
|
|
|
|
void
|
|
IdeDisk::startDma(const uint32_t &prdTableBase)
|
|
{
|
|
if (dmaState != Dma_Start)
|
|
panic("Inconsistent DMA state, should be in Dma_Start!\n");
|
|
|
|
if (devState != Transfer_Data_Dma)
|
|
panic("Inconsistent device state for DMA start!\n");
|
|
|
|
// PRD base address is given by bits 31:2
|
|
curPrdAddr = pciToDma((Addr)(prdTableBase & ~ULL(0x3)));
|
|
|
|
dmaState = Dma_Transfer;
|
|
|
|
// schedule dma transfer (doDmaTransfer)
|
|
schedule(dmaTransferEvent, curTick() + 1);
|
|
}
|
|
|
|
void
|
|
IdeDisk::abortDma()
|
|
{
|
|
if (dmaState == Dma_Idle)
|
|
panic("Inconsistent DMA state, should be Start or Transfer!");
|
|
|
|
if (devState != Transfer_Data_Dma && devState != Prepare_Data_Dma)
|
|
panic("Inconsistent device state, should be Transfer or Prepare!\n");
|
|
|
|
updateState(ACT_CMD_ERROR);
|
|
}
|
|
|
|
void
|
|
IdeDisk::startCommand()
|
|
{
|
|
DevAction_t action = ACT_NONE;
|
|
uint32_t size = 0;
|
|
dmaRead = false;
|
|
|
|
// Decode commands
|
|
switch (cmdReg.command) {
|
|
// Supported non-data commands
|
|
case WDSF_READ_NATIVE_MAX:
|
|
size = (uint32_t)image->size() - 1;
|
|
cmdReg.sec_num = (size & 0xff);
|
|
cmdReg.cyl_low = ((size & 0xff00) >> 8);
|
|
cmdReg.cyl_high = ((size & 0xff0000) >> 16);
|
|
cmdReg.head = ((size & 0xf000000) >> 24);
|
|
|
|
devState = Command_Execution;
|
|
action = ACT_CMD_COMPLETE;
|
|
break;
|
|
|
|
case WDCC_RECAL:
|
|
case WDCC_IDP:
|
|
case WDCC_STANDBY_IMMED:
|
|
case WDCC_FLUSHCACHE:
|
|
case WDSF_VERIFY:
|
|
case WDSF_SEEK:
|
|
case SET_FEATURES:
|
|
case WDCC_SETMULTI:
|
|
devState = Command_Execution;
|
|
action = ACT_CMD_COMPLETE;
|
|
break;
|
|
|
|
// Supported PIO data-in commands
|
|
case WDCC_IDENTIFY:
|
|
cmdBytes = cmdBytesLeft = sizeof(struct ataparams);
|
|
devState = Prepare_Data_In;
|
|
action = ACT_DATA_READY;
|
|
break;
|
|
|
|
case WDCC_READMULTI:
|
|
case WDCC_READ:
|
|
if (!(cmdReg.drive & DRIVE_LBA_BIT))
|
|
panic("Attempt to perform CHS access, only supports LBA\n");
|
|
|
|
if (cmdReg.sec_count == 0)
|
|
cmdBytes = cmdBytesLeft = (256 * SectorSize);
|
|
else
|
|
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
|
|
|
|
curSector = getLBABase();
|
|
|
|
/** @todo make this a scheduled event to simulate disk delay */
|
|
devState = Prepare_Data_In;
|
|
action = ACT_DATA_READY;
|
|
break;
|
|
|
|
// Supported PIO data-out commands
|
|
case WDCC_WRITEMULTI:
|
|
case WDCC_WRITE:
|
|
if (!(cmdReg.drive & DRIVE_LBA_BIT))
|
|
panic("Attempt to perform CHS access, only supports LBA\n");
|
|
|
|
if (cmdReg.sec_count == 0)
|
|
cmdBytes = cmdBytesLeft = (256 * SectorSize);
|
|
else
|
|
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
|
|
DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d\n", cmdBytesLeft);
|
|
curSector = getLBABase();
|
|
|
|
devState = Prepare_Data_Out;
|
|
action = ACT_DATA_READY;
|
|
break;
|
|
|
|
// Supported DMA commands
|
|
case WDCC_WRITEDMA:
|
|
dmaRead = true; // a write to the disk is a DMA read from memory
|
|
case WDCC_READDMA:
|
|
if (!(cmdReg.drive & DRIVE_LBA_BIT))
|
|
panic("Attempt to perform CHS access, only supports LBA\n");
|
|
|
|
if (cmdReg.sec_count == 0)
|
|
cmdBytes = cmdBytesLeft = (256 * SectorSize);
|
|
else
|
|
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
|
|
DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d in readdma\n", cmdBytesLeft);
|
|
|
|
curSector = getLBABase();
|
|
|
|
devState = Prepare_Data_Dma;
|
|
action = ACT_DMA_READY;
|
|
break;
|
|
|
|
default:
|
|
panic("Unsupported ATA command: %#x\n", cmdReg.command);
|
|
}
|
|
|
|
if (action != ACT_NONE) {
|
|
// set the BSY bit
|
|
status |= STATUS_BSY_BIT;
|
|
// clear the DRQ bit
|
|
status &= ~STATUS_DRQ_BIT;
|
|
// clear the DF bit
|
|
status &= ~STATUS_DF_BIT;
|
|
|
|
updateState(action);
|
|
}
|
|
}
|
|
|
|
////
|
|
// Handle setting and clearing interrupts
|
|
////
|
|
|
|
void
|
|
IdeDisk::intrPost()
|
|
{
|
|
DPRINTF(IdeDisk, "Posting Interrupt\n");
|
|
if (intrPending)
|
|
panic("Attempt to post an interrupt with one pending\n");
|
|
|
|
intrPending = true;
|
|
|
|
// talk to controller to set interrupt
|
|
if (ctrl) {
|
|
ctrl->intrPost();
|
|
}
|
|
}
|
|
|
|
void
|
|
IdeDisk::intrClear()
|
|
{
|
|
DPRINTF(IdeDisk, "Clearing Interrupt\n");
|
|
if (!intrPending)
|
|
panic("Attempt to clear a non-pending interrupt\n");
|
|
|
|
intrPending = false;
|
|
|
|
// talk to controller to clear interrupt
|
|
if (ctrl)
|
|
ctrl->intrClear();
|
|
}
|
|
|
|
////
|
|
// Manage the device internal state machine
|
|
////
|
|
|
|
void
|
|
IdeDisk::updateState(DevAction_t action)
|
|
{
|
|
switch (devState) {
|
|
case Device_Srst:
|
|
if (action == ACT_SRST_SET) {
|
|
// set the BSY bit
|
|
status |= STATUS_BSY_BIT;
|
|
} else if (action == ACT_SRST_CLEAR) {
|
|
// clear the BSY bit
|
|
status &= ~STATUS_BSY_BIT;
|
|
|
|
// reset the device state
|
|
reset(devID);
|
|
}
|
|
break;
|
|
|
|
case Device_Idle_S:
|
|
if (action == ACT_SELECT_WRITE && !isDEVSelect()) {
|
|
devState = Device_Idle_NS;
|
|
} else if (action == ACT_CMD_WRITE) {
|
|
startCommand();
|
|
}
|
|
|
|
break;
|
|
|
|
case Device_Idle_SI:
|
|
if (action == ACT_SELECT_WRITE && !isDEVSelect()) {
|
|
devState = Device_Idle_NS;
|
|
intrClear();
|
|
} else if (action == ACT_STAT_READ || isIENSet()) {
|
|
devState = Device_Idle_S;
|
|
intrClear();
|
|
} else if (action == ACT_CMD_WRITE) {
|
|
intrClear();
|
|
startCommand();
|
|
}
|
|
|
|
break;
|
|
|
|
case Device_Idle_NS:
|
|
if (action == ACT_SELECT_WRITE && isDEVSelect()) {
|
|
if (!isIENSet() && intrPending) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
}
|
|
if (isIENSet() || !intrPending) {
|
|
devState = Device_Idle_S;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Command_Execution:
|
|
if (action == ACT_CMD_COMPLETE) {
|
|
// clear the BSY bit
|
|
setComplete();
|
|
|
|
if (!isIENSet()) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
} else {
|
|
devState = Device_Idle_S;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Prepare_Data_In:
|
|
if (action == ACT_CMD_ERROR) {
|
|
// clear the BSY bit
|
|
setComplete();
|
|
|
|
if (!isIENSet()) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
} else {
|
|
devState = Device_Idle_S;
|
|
}
|
|
} else if (action == ACT_DATA_READY) {
|
|
// clear the BSY bit
|
|
status &= ~STATUS_BSY_BIT;
|
|
// set the DRQ bit
|
|
status |= STATUS_DRQ_BIT;
|
|
|
|
// copy the data into the data buffer
|
|
if (cmdReg.command == WDCC_IDENTIFY) {
|
|
// Reset the drqBytes for this block
|
|
drqBytesLeft = sizeof(struct ataparams);
|
|
|
|
memcpy((void *)dataBuffer, (void *)&driveID,
|
|
sizeof(struct ataparams));
|
|
} else {
|
|
// Reset the drqBytes for this block
|
|
drqBytesLeft = SectorSize;
|
|
|
|
readDisk(curSector++, dataBuffer);
|
|
}
|
|
|
|
// put the first two bytes into the data register
|
|
memcpy((void *)&cmdReg.data, (void *)dataBuffer,
|
|
sizeof(uint16_t));
|
|
|
|
if (!isIENSet()) {
|
|
devState = Data_Ready_INTRQ_In;
|
|
intrPost();
|
|
} else {
|
|
devState = Transfer_Data_In;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Data_Ready_INTRQ_In:
|
|
if (action == ACT_STAT_READ) {
|
|
devState = Transfer_Data_In;
|
|
intrClear();
|
|
}
|
|
break;
|
|
|
|
case Transfer_Data_In:
|
|
if (action == ACT_DATA_READ_BYTE || action == ACT_DATA_READ_SHORT) {
|
|
if (action == ACT_DATA_READ_BYTE) {
|
|
panic("DEBUG: READING DATA ONE BYTE AT A TIME!\n");
|
|
} else {
|
|
drqBytesLeft -= 2;
|
|
cmdBytesLeft -= 2;
|
|
|
|
// copy next short into data registers
|
|
if (drqBytesLeft)
|
|
memcpy((void *)&cmdReg.data,
|
|
(void *)&dataBuffer[SectorSize - drqBytesLeft],
|
|
sizeof(uint16_t));
|
|
}
|
|
|
|
if (drqBytesLeft == 0) {
|
|
if (cmdBytesLeft == 0) {
|
|
// Clear the BSY bit
|
|
setComplete();
|
|
devState = Device_Idle_S;
|
|
} else {
|
|
devState = Prepare_Data_In;
|
|
// set the BSY_BIT
|
|
status |= STATUS_BSY_BIT;
|
|
// clear the DRQ_BIT
|
|
status &= ~STATUS_DRQ_BIT;
|
|
|
|
/** @todo change this to a scheduled event to simulate
|
|
disk delay */
|
|
updateState(ACT_DATA_READY);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Prepare_Data_Out:
|
|
if (action == ACT_CMD_ERROR || cmdBytesLeft == 0) {
|
|
// clear the BSY bit
|
|
setComplete();
|
|
|
|
if (!isIENSet()) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
} else {
|
|
devState = Device_Idle_S;
|
|
}
|
|
} else if (action == ACT_DATA_READY && cmdBytesLeft != 0) {
|
|
// clear the BSY bit
|
|
status &= ~STATUS_BSY_BIT;
|
|
// set the DRQ bit
|
|
status |= STATUS_DRQ_BIT;
|
|
|
|
// clear the data buffer to get it ready for writes
|
|
memset(dataBuffer, 0, MAX_DMA_SIZE);
|
|
|
|
// reset the drqBytes for this block
|
|
drqBytesLeft = SectorSize;
|
|
|
|
if (cmdBytesLeft == cmdBytes || isIENSet()) {
|
|
devState = Transfer_Data_Out;
|
|
} else {
|
|
devState = Data_Ready_INTRQ_Out;
|
|
intrPost();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Data_Ready_INTRQ_Out:
|
|
if (action == ACT_STAT_READ) {
|
|
devState = Transfer_Data_Out;
|
|
intrClear();
|
|
}
|
|
break;
|
|
|
|
case Transfer_Data_Out:
|
|
if (action == ACT_DATA_WRITE_BYTE ||
|
|
action == ACT_DATA_WRITE_SHORT) {
|
|
|
|
if (action == ACT_DATA_READ_BYTE) {
|
|
panic("DEBUG: WRITING DATA ONE BYTE AT A TIME!\n");
|
|
} else {
|
|
// copy the latest short into the data buffer
|
|
memcpy((void *)&dataBuffer[SectorSize - drqBytesLeft],
|
|
(void *)&cmdReg.data,
|
|
sizeof(uint16_t));
|
|
|
|
drqBytesLeft -= 2;
|
|
cmdBytesLeft -= 2;
|
|
}
|
|
|
|
if (drqBytesLeft == 0) {
|
|
// copy the block to the disk
|
|
writeDisk(curSector++, dataBuffer);
|
|
|
|
// set the BSY bit
|
|
status |= STATUS_BSY_BIT;
|
|
// set the seek bit
|
|
status |= STATUS_SEEK_BIT;
|
|
// clear the DRQ bit
|
|
status &= ~STATUS_DRQ_BIT;
|
|
|
|
devState = Prepare_Data_Out;
|
|
|
|
/** @todo change this to a scheduled event to simulate
|
|
disk delay */
|
|
updateState(ACT_DATA_READY);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Prepare_Data_Dma:
|
|
if (action == ACT_CMD_ERROR) {
|
|
// clear the BSY bit
|
|
setComplete();
|
|
|
|
if (!isIENSet()) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
} else {
|
|
devState = Device_Idle_S;
|
|
}
|
|
} else if (action == ACT_DMA_READY) {
|
|
// clear the BSY bit
|
|
status &= ~STATUS_BSY_BIT;
|
|
// set the DRQ bit
|
|
status |= STATUS_DRQ_BIT;
|
|
|
|
devState = Transfer_Data_Dma;
|
|
|
|
if (dmaState != Dma_Idle)
|
|
panic("Inconsistent DMA state, should be Dma_Idle\n");
|
|
|
|
dmaState = Dma_Start;
|
|
// wait for the write to the DMA start bit
|
|
}
|
|
break;
|
|
|
|
case Transfer_Data_Dma:
|
|
if (action == ACT_CMD_ERROR || action == ACT_DMA_DONE) {
|
|
// clear the BSY bit
|
|
setComplete();
|
|
// set the seek bit
|
|
status |= STATUS_SEEK_BIT;
|
|
// clear the controller state for DMA transfer
|
|
ctrl->setDmaComplete(this);
|
|
|
|
if (!isIENSet()) {
|
|
devState = Device_Idle_SI;
|
|
intrPost();
|
|
} else {
|
|
devState = Device_Idle_S;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
panic("Unknown IDE device state: %#x\n", devState);
|
|
}
|
|
}
|
|
|
|
void
|
|
IdeDisk::serialize(ostream &os)
|
|
{
|
|
// Check all outstanding events to see if they are scheduled
|
|
// these are all mutually exclusive
|
|
Tick reschedule = 0;
|
|
Events_t event = None;
|
|
|
|
int eventCount = 0;
|
|
|
|
if (dmaTransferEvent.scheduled()) {
|
|
reschedule = dmaTransferEvent.when();
|
|
event = Transfer;
|
|
eventCount++;
|
|
}
|
|
if (dmaReadWaitEvent.scheduled()) {
|
|
reschedule = dmaReadWaitEvent.when();
|
|
event = ReadWait;
|
|
eventCount++;
|
|
}
|
|
if (dmaWriteWaitEvent.scheduled()) {
|
|
reschedule = dmaWriteWaitEvent.when();
|
|
event = WriteWait;
|
|
eventCount++;
|
|
}
|
|
if (dmaPrdReadEvent.scheduled()) {
|
|
reschedule = dmaPrdReadEvent.when();
|
|
event = PrdRead;
|
|
eventCount++;
|
|
}
|
|
if (dmaReadEvent.scheduled()) {
|
|
reschedule = dmaReadEvent.when();
|
|
event = DmaRead;
|
|
eventCount++;
|
|
}
|
|
if (dmaWriteEvent.scheduled()) {
|
|
reschedule = dmaWriteEvent.when();
|
|
event = DmaWrite;
|
|
eventCount++;
|
|
}
|
|
|
|
assert(eventCount <= 1);
|
|
|
|
SERIALIZE_SCALAR(reschedule);
|
|
SERIALIZE_ENUM(event);
|
|
|
|
// Serialize device registers
|
|
SERIALIZE_SCALAR(cmdReg.data);
|
|
SERIALIZE_SCALAR(cmdReg.sec_count);
|
|
SERIALIZE_SCALAR(cmdReg.sec_num);
|
|
SERIALIZE_SCALAR(cmdReg.cyl_low);
|
|
SERIALIZE_SCALAR(cmdReg.cyl_high);
|
|
SERIALIZE_SCALAR(cmdReg.drive);
|
|
SERIALIZE_SCALAR(cmdReg.command);
|
|
SERIALIZE_SCALAR(status);
|
|
SERIALIZE_SCALAR(nIENBit);
|
|
SERIALIZE_SCALAR(devID);
|
|
|
|
// Serialize the PRD related information
|
|
SERIALIZE_SCALAR(curPrd.entry.baseAddr);
|
|
SERIALIZE_SCALAR(curPrd.entry.byteCount);
|
|
SERIALIZE_SCALAR(curPrd.entry.endOfTable);
|
|
SERIALIZE_SCALAR(curPrdAddr);
|
|
|
|
/** @todo need to serialized chunk generator stuff!! */
|
|
// Serialize current transfer related information
|
|
SERIALIZE_SCALAR(cmdBytesLeft);
|
|
SERIALIZE_SCALAR(cmdBytes);
|
|
SERIALIZE_SCALAR(drqBytesLeft);
|
|
SERIALIZE_SCALAR(curSector);
|
|
SERIALIZE_SCALAR(dmaRead);
|
|
SERIALIZE_SCALAR(intrPending);
|
|
SERIALIZE_ENUM(devState);
|
|
SERIALIZE_ENUM(dmaState);
|
|
SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
|
|
}
|
|
|
|
void
|
|
IdeDisk::unserialize(Checkpoint *cp, const string §ion)
|
|
{
|
|
// Reschedule events that were outstanding
|
|
// these are all mutually exclusive
|
|
Tick reschedule = 0;
|
|
Events_t event = None;
|
|
|
|
UNSERIALIZE_SCALAR(reschedule);
|
|
UNSERIALIZE_ENUM(event);
|
|
|
|
switch (event) {
|
|
case None : break;
|
|
case Transfer : schedule(dmaTransferEvent, reschedule); break;
|
|
case ReadWait : schedule(dmaReadWaitEvent, reschedule); break;
|
|
case WriteWait : schedule(dmaWriteWaitEvent, reschedule); break;
|
|
case PrdRead : schedule(dmaPrdReadEvent, reschedule); break;
|
|
case DmaRead : schedule(dmaReadEvent, reschedule); break;
|
|
case DmaWrite : schedule(dmaWriteEvent, reschedule); break;
|
|
}
|
|
|
|
// Unserialize device registers
|
|
UNSERIALIZE_SCALAR(cmdReg.data);
|
|
UNSERIALIZE_SCALAR(cmdReg.sec_count);
|
|
UNSERIALIZE_SCALAR(cmdReg.sec_num);
|
|
UNSERIALIZE_SCALAR(cmdReg.cyl_low);
|
|
UNSERIALIZE_SCALAR(cmdReg.cyl_high);
|
|
UNSERIALIZE_SCALAR(cmdReg.drive);
|
|
UNSERIALIZE_SCALAR(cmdReg.command);
|
|
UNSERIALIZE_SCALAR(status);
|
|
UNSERIALIZE_SCALAR(nIENBit);
|
|
UNSERIALIZE_SCALAR(devID);
|
|
|
|
// Unserialize the PRD related information
|
|
UNSERIALIZE_SCALAR(curPrd.entry.baseAddr);
|
|
UNSERIALIZE_SCALAR(curPrd.entry.byteCount);
|
|
UNSERIALIZE_SCALAR(curPrd.entry.endOfTable);
|
|
UNSERIALIZE_SCALAR(curPrdAddr);
|
|
|
|
/** @todo need to serialized chunk generator stuff!! */
|
|
// Unserialize current transfer related information
|
|
UNSERIALIZE_SCALAR(cmdBytes);
|
|
UNSERIALIZE_SCALAR(cmdBytesLeft);
|
|
UNSERIALIZE_SCALAR(drqBytesLeft);
|
|
UNSERIALIZE_SCALAR(curSector);
|
|
UNSERIALIZE_SCALAR(dmaRead);
|
|
UNSERIALIZE_SCALAR(intrPending);
|
|
UNSERIALIZE_ENUM(devState);
|
|
UNSERIALIZE_ENUM(dmaState);
|
|
UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
|
|
}
|
|
|
|
IdeDisk *
|
|
IdeDiskParams::create()
|
|
{
|
|
return new IdeDisk(this);
|
|
}
|