blob: e43437f1a405cdeb17f8990fc019eb164733bd14 [file] [log] [blame]
/*
* Copyright (c) 2013 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* 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.
*/
/** @file
* Device model implementation for an IDE disk
*/
#include "dev/storage/ide_disk.hh"
#include <cerrno>
#include <cstring>
#include <deque>
#include <string>
#include "base/bitfield.hh"
#include "base/chunk_generator.hh"
#include "base/compiler.hh"
#include "base/cprintf.hh" // csprintf
#include "base/trace.hh"
#include "debug/IdeDisk.hh"
#include "dev/storage/disk_image.hh"
#include "dev/storage/ide_ctrl.hh"
#include "sim/cur_tick.hh"
#include "sim/sim_object.hh"
namespace gem5
{
IdeDisk::IdeDisk(const Params &p)
: SimObject(p), image(p.image), diskDelay(p.delay), ideDiskStats(this),
dmaTransferEvent([this]{ doDmaTransfer(); }, name()),
dmaReadWaitEvent([this]{ doDmaRead(); }, name()),
dmaWriteWaitEvent([this]{ doDmaWrite(); }, name()),
dmaPrdReadEvent([this]{ dmaPrdReadDone(); }, name()),
dmaReadEvent([this]{ dmaReadDone(); }, name()),
dmaWriteEvent([this]{ dmaWriteDone(); }, name())
{
// 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 if (lba_size == 0)
panic("Bad IDE image size: 0\n");
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;
pendingInterrupt = false;
dmaAborted = 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 channel->selected() == this;
}
Addr
IdeDisk::pciToDma(Addr pciAddr)
{
panic_if(!ctrl, "Access to unset controller!");
return ctrl->pciToDma(pciAddr);
}
////
// 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 (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted before reading PRD entry\n");
updateState(ACT_CMD_ERROR);
return;
}
if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma) {
panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n",
dmaState, devState);
}
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) {
schedule(dmaTransferEvent, curTick() + DMA_BACKOFF_PERIOD);
return;
} else {
ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent,
(uint8_t*)&curPrd.entry);
}
}
void
IdeDisk::dmaPrdReadDone()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted while reading PRD entry\n");
updateState(ACT_CMD_ERROR);
return;
}
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);
}
IdeDisk::
IdeDiskStats::IdeDiskStats(statistics::Group *parent)
: statistics::Group(parent, "IdeDisk"),
ADD_STAT(dmaReadFullPages, statistics::units::Count::get(),
"Number of full page size DMA reads (not PRD)."),
ADD_STAT(dmaReadBytes, statistics::units::Byte::get(),
"Number of bytes transfered via DMA reads (not PRD)."),
ADD_STAT(dmaReadTxs, statistics::units::Count::get(),
"Number of DMA read transactions (not PRD)."),
ADD_STAT(dmaWriteFullPages, statistics::units::Count::get(),
"Number of full page size DMA writes."),
ADD_STAT(dmaWriteBytes, statistics::units::Byte::get(),
"Number of bytes transfered via DMA writes."),
ADD_STAT(dmaWriteTxs, statistics::units::Count::get(),
"Number of DMA write transactions.")
{
}
void
IdeDisk::doDmaRead()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted in middle of Dma Read\n");
if (dmaReadCG)
delete dmaReadCG;
dmaReadCG = NULL;
updateState(ACT_CMD_ERROR);
return;
}
if (!dmaReadCG) {
// clear out the data buffer
memset(dataBuffer, 0, MAX_DMA_SIZE);
dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(),
curPrd.getByteCount(), chunkBytes);
}
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::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());
ideDiskStats.dmaReadBytes += dmaReadCG->size();
ideDiskStats.dmaReadTxs++;
if (dmaReadCG->size() == chunkBytes)
ideDiskStats.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()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted while doing DMA Write\n");
if (dmaWriteCG)
delete dmaWriteCG;
dmaWriteCG = NULL;
updateState(ACT_CMD_ERROR);
return;
}
if (!dmaWriteCG) {
// clear out the data buffer
dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(),
curPrd.getByteCount(), chunkBytes);
}
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::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());
ideDiskStats.dmaWriteBytes += dmaWriteCG->size();
ideDiskStats.dmaWriteTxs++;
if (dmaWriteCG->size() == chunkBytes)
ideDiskStats.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);
panic_if(bytesRead != SectorSize,
"Can't read from %s. Only %d of %d read. errno=%d",
name(), bytesRead, SectorSize, errno);
}
void
IdeDisk::writeDisk(uint32_t sector, uint8_t *data)
{
uint32_t bytesWritten = image->write(data, sector);
panic_if(bytesWritten != SectorSize,
"Can't write to %s. Only %d of %d written. errno=%d",
name(), bytesWritten, SectorSize, errno);
}
////
// Setup and handle commands
////
void
IdeDisk::startDma(const uint32_t &prdTableBase)
{
panic_if(dmaState != Dma_Start,
"Inconsistent DMA state, should be in Dma_Start!");
panic_if(devState != Transfer_Data_Dma,
"Inconsistent device state for DMA start!");
// PRD base address is given by bits 31:2
curPrdAddr = pciToDma((Addr)(prdTableBase & ~0x3ULL));
dmaState = Dma_Transfer;
// schedule dma transfer (doDmaTransfer)
schedule(dmaTransferEvent, curTick() + 1);
}
void
IdeDisk::abortDma()
{
panic_if(dmaState == Dma_Idle,
"Inconsistent DMA state, should be Start or Transfer!");
panic_if(devState != Transfer_Data_Dma && devState != Prepare_Data_Dma,
"Inconsistent device state, should be Transfer or Prepare!");
updateState(ACT_CMD_ERROR);
}
void
IdeDisk::startCommand()
{
DevAction_t action = ACT_NONE;
uint32_t size = 0;
dmaRead = false;
// Clear any existing errors.
replaceBits(status, 0, 0);
replaceBits(cmdReg.error, 2, 0);
// 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:
case WDCC_IDLE:
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 ATAPI_IDENTIFY_DEVICE:
// We're not an ATAPI device, so this command isn't implemented.
devState = Command_Execution;
action = ACT_CMD_ERROR;
replaceBits(cmdReg.error, 2, 1);
replaceBits(status, 0, 1);
break;
case WDCC_READMULTI:
case WDCC_READ:
panic_if(!(cmdReg.drive & DRIVE_LBA_BIT),
"Attempt to perform CHS access, only supports LBA");
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:
panic_if(!(cmdReg.drive & DRIVE_LBA_BIT),
"Attempt to perform CHS access, only supports LBA");
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
[[fallthrough]];
case WDCC_READDMA:
panic_if(!(cmdReg.drive & DRIVE_LBA_BIT),
"Attempt to perform CHS access, only supports LBA");
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::postInterrupt()
{
DPRINTF(IdeDisk, "Posting Interrupt\n");
panic_if(pendingInterrupt,
"Attempt to post an interrupt with one pending");
pendingInterrupt = true;
assert(channel);
channel->postInterrupt();
}
void
IdeDisk::clearInterrupt()
{
DPRINTF(IdeDisk, "Clearing Interrupt\n");
panic_if(!pendingInterrupt, "Attempt to clear a non-pending interrupt");
pendingInterrupt = false;
assert(channel);
channel->clearInterrupt();
}
////
// 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;
clearInterrupt();
} else if (action == ACT_STAT_READ || isIENSet()) {
devState = Device_Idle_S;
clearInterrupt();
} else if (action == ACT_CMD_WRITE) {
clearInterrupt();
startCommand();
}
break;
case Device_Idle_NS:
if (action == ACT_SELECT_WRITE && isDEVSelect()) {
if (!isIENSet() && pendingInterrupt) {
devState = Device_Idle_SI;
postInterrupt();
}
if (isIENSet() || !pendingInterrupt) {
devState = Device_Idle_S;
}
}
break;
case Command_Execution:
if (action == ACT_CMD_ERROR || action == ACT_CMD_COMPLETE) {
// clear the BSY bit
setComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
postInterrupt();
} 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;
postInterrupt();
} 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 ||
cmdReg.command == ATAPI_IDENTIFY_DEVICE) {
// 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;
postInterrupt();
} else {
devState = Transfer_Data_In;
}
}
break;
case Data_Ready_INTRQ_In:
if (action == ACT_STAT_READ) {
devState = Transfer_Data_In;
clearInterrupt();
}
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;
postInterrupt();
} 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;
postInterrupt();
}
}
break;
case Data_Ready_INTRQ_Out:
if (action == ACT_STAT_READ) {
devState = Transfer_Data_Out;
clearInterrupt();
}
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;
postInterrupt();
} 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) {
dmaAborted = true;
devState = Device_Dma_Abort;
} else if (action == ACT_DMA_DONE) {
// clear the BSY bit
setComplete();
// set the seek bit
status |= STATUS_SEEK_BIT;
// clear the controller state for DMA transfer
channel->setDmaComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
postInterrupt();
} else {
devState = Device_Idle_S;
}
}
break;
case Device_Dma_Abort:
if (action == ACT_CMD_ERROR) {
setComplete();
status |= STATUS_SEEK_BIT;
channel->setDmaComplete();
dmaAborted = false;
dmaState = Dma_Idle;
if (!isIENSet()) {
devState = Device_Idle_SI;
postInterrupt();
} else {
devState = Device_Idle_S;
}
} else {
DPRINTF(IdeDisk, "Disk still busy aborting previous DMA command\n");
}
break;
default:
panic("Unknown IDE device state: %#x\n", devState);
}
}
void
IdeDisk::serialize(CheckpointOut &cp) const
{
// 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);
paramOut(cp, "intrPending", pendingInterrupt);
SERIALIZE_SCALAR(dmaAborted);
SERIALIZE_ENUM(devState);
SERIALIZE_ENUM(dmaState);
SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
}
void
IdeDisk::unserialize(CheckpointIn &cp)
{
// 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);
paramIn(cp, "intrPending", pendingInterrupt);
UNSERIALIZE_SCALAR(dmaAborted);
UNSERIALIZE_ENUM(devState);
UNSERIALIZE_ENUM(dmaState);
UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
}
} // namespace gem5